diff --git a/internal/ast/ast.go b/internal/ast/ast.go index e991bd08ad..68d2ddf440 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -412,6 +412,18 @@ func (n *Node) Expression() *Node { panic("Unhandled case in Node.Expression: " + n.Kind.String()) } +func (n *Node) RawText() string { + switch n.Kind { + case KindTemplateHead: + return n.AsTemplateHead().RawText + case KindTemplateMiddle: + return n.AsTemplateMiddle().RawText + case KindTemplateTail: + return n.AsTemplateTail().RawText + } + panic("Unhandled case in Node.RawText: " + n.Kind.String()) +} + func (m *mutableNode) SetExpression(expr *Node) { n := (*Node)(m) switch n.Kind { @@ -2057,59 +2069,63 @@ type NodeBase struct { // Aliases for Node unions type ( - Statement = Node // Node with StatementBase - Declaration = Node // Node with DeclarationBase - Expression = Node // Node with ExpressionBase - TypeNode = Node // Node with TypeNodeBase - TypeElement = Node // Node with TypeElementBase - ClassElement = Node // Node with ClassElementBase - NamedMember = Node // Node with NamedMemberBase - ObjectLiteralElement = Node // Node with ObjectLiteralElementBase - BlockOrExpression = Node // Block | Expression - AccessExpression = Node // PropertyAccessExpression | ElementAccessExpression - DeclarationName = Node // Identifier | PrivateIdentifier | StringLiteral | NumericLiteral | BigIntLiteral | NoSubstitutionTemplateLiteral | ComputedPropertyName | BindingPattern | ElementAccessExpression - ModuleName = Node // Identifier | StringLiteral - ModuleExportName = Node // Identifier | StringLiteral - PropertyName = Node // Identifier | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | ComputedPropertyName | PrivateIdentifier | BigIntLiteral - ModuleBody = Node // ModuleBlock | ModuleDeclaration - ForInitializer = Node // Expression | MissingDeclaration | VariableDeclarationList - ModuleReference = Node // Identifier | QualifiedName | ExternalModuleReference - NamedImportBindings = Node // NamespaceImport | NamedImports - NamedExportBindings = Node // NamespaceExport | NamedExports - MemberName = Node // Identifier | PrivateIdentifier - EntityName = Node // Identifier | QualifiedName - BindingName = Node // Identifier | BindingPattern - ModifierLike = Node // Modifier | Decorator - JsxChild = Node // JsxText | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment - JsxAttributeLike = Node // JsxAttribute | JsxSpreadAttribute - JsxAttributeName = Node // Identifier | JsxNamespacedName - JsxAttributeValue = Node // StringLiteral | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment - JsxTagNameExpression = Node // IdentifierReference | KeywordExpression | JsxTagNamePropertyAccess | JsxNamespacedName - ClassLikeDeclaration = Node // ClassDeclaration | ClassExpression - AccessorDeclaration = Node // GetAccessorDeclaration | SetAccessorDeclaration - LiteralLikeNode = Node // StringLiteral | NumericLiteral | BigIntLiteral | RegularExpressionLiteral | TemplateLiteralLikeNode | JsxText - LiteralExpression = Node // StringLiteral | NumericLiteral | BigIntLiteral | RegularExpressionLiteral | NoSubstitutionTemplateLiteral - UnionOrIntersectionTypeNode = Node // UnionTypeNode | IntersectionTypeNode - TemplateLiteralLikeNode = Node // TemplateHead | TemplateMiddle | TemplateTail - TemplateMiddleOrTail = Node // TemplateMiddle | TemplateTail - TemplateLiteral = Node // TemplateExpression | NoSubstitutionTemplateLiteral - TypePredicateParameterName = Node // Identifier | ThisTypeNode - ImportAttributeName = Node // Identifier | StringLiteral - LeftHandSideExpression = Node // subset of Expression - JSDocComment = Node // JSDocText | JSDocLink | JSDocLinkCode | JSDocLinkPlain; - JSDocTag = Node // Node with JSDocTagBase - SignatureDeclaration = Node // CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | AccessorDeclaration | FunctionExpression | ArrowFunction; - StringLiteralLike = Node // StringLiteral | NoSubstitutionTemplateLiteral - AnyValidImportOrReExport = Node // (ImportDeclaration | ExportDeclaration | JSDocImportTag) & { moduleSpecifier: StringLiteral } | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteral }} | RequireOrImportCall | ValidImportTypeNode - ValidImportTypeNode = Node // ImportTypeNode & { argument: LiteralTypeNode & { literal: StringLiteral } } - NumericOrStringLikeLiteral = Node // StringLiteralLike | NumericLiteral - TypeOnlyImportDeclaration = Node // ImportClause | ImportEqualsDeclaration | ImportSpecifier | NamespaceImport with isTypeOnly: true - ObjectLiteralLike = Node // ObjectLiteralExpression | ObjectBindingPattern - ObjectTypeDeclaration = Node // ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode - JsxOpeningLikeElement = Node // JsxOpeningElement | JsxSelfClosingElement - NamedImportsOrExports = Node // NamedImports | NamedExports - BreakOrContinueStatement = Node // BreakStatement | ContinueStatement - CallLikeExpression = Node // CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxCallLike | InstanceofExpression + Statement = Node // Node with StatementBase + Declaration = Node // Node with DeclarationBase + Expression = Node // Node with ExpressionBase + TypeNode = Node // Node with TypeNodeBase + TypeElement = Node // Node with TypeElementBase + ClassElement = Node // Node with ClassElementBase + NamedMember = Node // Node with NamedMemberBase + ObjectLiteralElement = Node // Node with ObjectLiteralElementBase + BlockOrExpression = Node // Block | Expression + AccessExpression = Node // PropertyAccessExpression | ElementAccessExpression + DeclarationName = Node // Identifier | PrivateIdentifier | StringLiteral | NumericLiteral | BigIntLiteral | NoSubstitutionTemplateLiteral | ComputedPropertyName | BindingPattern | ElementAccessExpression + ModuleName = Node // Identifier | StringLiteral + ModuleExportName = Node // Identifier | StringLiteral + PropertyName = Node // Identifier | StringLiteral | NoSubstitutionTemplateLiteral | NumericLiteral | ComputedPropertyName | PrivateIdentifier | BigIntLiteral + ModuleBody = Node // ModuleBlock | ModuleDeclaration + ForInitializer = Node // Expression | MissingDeclaration | VariableDeclarationList + ModuleReference = Node // Identifier | QualifiedName | ExternalModuleReference + NamedImportBindings = Node // NamespaceImport | NamedImports + NamedExportBindings = Node // NamespaceExport | NamedExports + MemberName = Node // Identifier | PrivateIdentifier + EntityName = Node // Identifier | QualifiedName + BindingName = Node // Identifier | BindingPattern + ModifierLike = Node // Modifier | Decorator + JsxChild = Node // JsxText | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment + JsxAttributeLike = Node // JsxAttribute | JsxSpreadAttribute + JsxAttributeName = Node // Identifier | JsxNamespacedName + JsxAttributeValue = Node // StringLiteral | JsxExpression | JsxElement | JsxSelfClosingElement | JsxFragment + JsxTagNameExpression = Node // IdentifierReference | KeywordExpression | JsxTagNamePropertyAccess | JsxNamespacedName + ClassLikeDeclaration = Node // ClassDeclaration | ClassExpression + AccessorDeclaration = Node // GetAccessorDeclaration | SetAccessorDeclaration + LiteralLikeNode = Node // StringLiteral | NumericLiteral | BigIntLiteral | RegularExpressionLiteral | TemplateLiteralLikeNode | JsxText + LiteralExpression = Node // StringLiteral | NumericLiteral | BigIntLiteral | RegularExpressionLiteral | NoSubstitutionTemplateLiteral + UnionOrIntersectionTypeNode = Node // UnionTypeNode | IntersectionTypeNode + TemplateLiteralLikeNode = Node // TemplateHead | TemplateMiddle | TemplateTail + TemplateMiddleOrTail = Node // TemplateMiddle | TemplateTail + TemplateLiteral = Node // TemplateExpression | NoSubstitutionTemplateLiteral + TypePredicateParameterName = Node // Identifier | ThisTypeNode + ImportAttributeName = Node // Identifier | StringLiteral + LeftHandSideExpression = Node // subset of Expression + JSDocComment = Node // JSDocText | JSDocLink | JSDocLinkCode | JSDocLinkPlain; + JSDocTag = Node // Node with JSDocTagBase + SignatureDeclaration = Node // CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | AccessorDeclaration | FunctionExpression | ArrowFunction; + StringLiteralLike = Node // StringLiteral | NoSubstitutionTemplateLiteral + AnyValidImportOrReExport = Node // (ImportDeclaration | ExportDeclaration | JSDocImportTag) & { moduleSpecifier: StringLiteral } | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteral }} | RequireOrImportCall | ValidImportTypeNode + ValidImportTypeNode = Node // ImportTypeNode & { argument: LiteralTypeNode & { literal: StringLiteral } } + NumericOrStringLikeLiteral = Node // StringLiteralLike | NumericLiteral + TypeOnlyImportDeclaration = Node // ImportClause | ImportEqualsDeclaration | ImportSpecifier | NamespaceImport with isTypeOnly: true + ObjectLiteralLike = Node // ObjectLiteralExpression | ObjectBindingPattern + ObjectTypeDeclaration = Node // ClassLikeDeclaration | InterfaceDeclaration | TypeLiteralNode + JsxOpeningLikeElement = Node // JsxOpeningElement | JsxSelfClosingElement + NamedImportsOrExports = Node // NamedImports | NamedExports + BreakOrContinueStatement = Node // BreakStatement | ContinueStatement + CallLikeExpression = Node // CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxCallLike | InstanceofExpression + FunctionLikeDeclaration = Node // FunctionDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ConstructorDeclaration | FunctionExpression | ArrowFunction + VariableOrParameterDeclaration = Node // VariableDeclaration | ParameterDeclaration + VariableOrPropertyDeclaration = Node // VariableDeclaration | PropertyDeclaration + CallOrNewExpression = Node // CallExpression | NewExpression ) // Aliases for node singletons @@ -2160,6 +2176,7 @@ type ( LiteralType = Node JSDocNode = Node BindingPatternNode = Node + TypePredicateNodeNode = Node ) type ( diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 1c65dee66c..2bb58167f5 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -3860,3 +3860,26 @@ func IsJSDocNameReferenceContext(node *Node) bool { func IsImportOrImportEqualsDeclaration(node *Node) bool { return IsImportDeclaration(node) || IsImportEqualsDeclaration(node) } + +func HasContextSensitiveParameters(node *Node) bool { + // Functions with type parameters are not context sensitive. + if node.TypeParameters() == nil { + // Functions with any parameters that lack type annotations are context sensitive. + if core.Some(node.Parameters(), func(p *Node) bool { return p.Type() == nil }) { + return true + } + if !IsArrowFunction(node) { + // If the first parameter is not an explicit 'this' parameter, then the function has + // an implicit 'this' parameter which is subject to contextual typing. + parameter := core.FirstOrNil(node.Parameters()) + if parameter == nil || !IsThisParameter(parameter) { + return true + } + } + } + return false +} + +func IsInfinityOrNaNString(name string) bool { + return name == "Infinity" || name == "-Infinity" || name == "NaN" +} diff --git a/internal/checker/checker.go b/internal/checker/checker.go index b6d46d2e70..85e35b043c 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -9763,7 +9763,7 @@ func (c *Checker) checkFunctionExpressionOrObjectLiteralMethod(node *ast.Node, c } if checkMode&CheckModeSkipContextSensitive != 0 && c.isContextSensitive(node) { // Skip parameters, return signature with return type that retains noncontextual parts so inferences can still be drawn in an early stage - if node.Type() == nil && !hasContextSensitiveParameters(node) { + if node.Type() == nil && !ast.HasContextSensitiveParameters(node) { // Return plain anyFunctionType if there is no possibility we'll make inferences from the return type contextualSignature := c.getContextualSignature(node) if contextualSignature != nil && c.couldContainTypeVariables(c.getReturnTypeOfSignature(contextualSignature)) { @@ -23053,7 +23053,7 @@ func (c *Checker) computeEnumMemberValue(member *ast.Node, autoValue jsnum.Numbe c.error(member.Name(), diagnostics.Computed_property_names_are_not_allowed_in_enums) } else { text := ast.GetTextOfPropertyName(member.Name()) - if isNumericLiteralName(text) && !isInfinityOrNaNString(text) { + if isNumericLiteralName(text) && !ast.IsInfinityOrNaNString(text) { c.error(member.Name(), diagnostics.An_enum_member_cannot_have_a_numeric_name) } } @@ -23120,7 +23120,7 @@ func (c *Checker) evaluateEntity(expr *ast.Node, location *ast.Node) evaluator.R return evaluator.NewResult(nil, false, false, false) } if expr.Kind == ast.KindIdentifier { - if isInfinityOrNaNString(expr.Text()) && (symbol == c.getGlobalSymbol(expr.Text(), ast.SymbolFlagsValue, nil /*diagnostic*/)) { + if ast.IsInfinityOrNaNString(expr.Text()) && (symbol == c.getGlobalSymbol(expr.Text(), ast.SymbolFlagsValue, nil /*diagnostic*/)) { // Technically we resolved a global lib file here, but the decision to treat this as numeric // is more predicated on the fact that the single-file resolution *didn't* resolve to a // different meaning of `Infinity` or `NaN`. Transpilers handle this no problem. @@ -29554,7 +29554,7 @@ func (c *Checker) isContextSensitive(node *ast.Node) bool { } func (c *Checker) isContextSensitiveFunctionLikeDeclaration(node *ast.Node) bool { - return hasContextSensitiveParameters(node) || c.hasContextSensitiveReturnExpression(node) + return ast.HasContextSensitiveParameters(node) || c.hasContextSensitiveReturnExpression(node) } func (c *Checker) hasContextSensitiveReturnExpression(node *ast.Node) bool { diff --git a/internal/checker/grammarchecks.go b/internal/checker/grammarchecks.go index dc98b20881..63052f7399 100644 --- a/internal/checker/grammarchecks.go +++ b/internal/checker/grammarchecks.go @@ -214,7 +214,7 @@ func (c *Checker) checkGrammarModifiers(node *ast.Node /*Union[HasModifiers, Has if c.reportObviousDecoratorErrors(node) || c.reportObviousModifierErrors(node) { return true } - if ast.IsParameter(node) && ast.IsThisParameter(node) { + if ast.IsThisParameter(node) { return c.grammarErrorOnFirstToken(node, diagnostics.Neither_decorators_nor_modifiers_may_be_applied_to_this_parameters) } blockScopeKind := ast.NodeFlagsNone diff --git a/internal/checker/printer.go b/internal/checker/printer.go index 8e82a2350f..7e6d665bdd 100644 --- a/internal/checker/printer.go +++ b/internal/checker/printer.go @@ -12,7 +12,7 @@ func createPrinterWithDefaults(emitContext *printer.EmitContext) *printer.Printe return printer.NewPrinter(printer.PrinterOptions{}, printer.PrintHandlers{}, emitContext) } -func createPrinterWithRemoveComments(emitContext *printer.EmitContext) *printer.Printer { +func CreatePrinterWithRemoveComments(emitContext *printer.EmitContext) *printer.Printer { return printer.NewPrinter(printer.PrinterOptions{RemoveComments: true}, printer.PrintHandlers{}, emitContext) } @@ -199,7 +199,7 @@ func (c *Checker) typeToStringEx(t *Type, enclosingDeclaration *ast.Node, flags if t == c.unresolvedType { printer = createPrinterWithDefaults(nodeBuilder.EmitContext()) } else { - printer = createPrinterWithRemoveComments(nodeBuilder.EmitContext()) + printer = CreatePrinterWithRemoveComments(nodeBuilder.EmitContext()) } var sourceFile *ast.SourceFile if enclosingDeclaration != nil { @@ -262,7 +262,7 @@ func (c *Checker) symbolToStringEx(symbol *ast.Symbol, enclosingDeclaration *ast if enclosingDeclaration != nil && enclosingDeclaration.Kind == ast.KindSourceFile { printer_ = createPrinterWithRemoveCommentsNeverAsciiEscape(nodeBuilder.EmitContext()) } else { - printer_ = createPrinterWithRemoveComments(nodeBuilder.EmitContext()) + printer_ = CreatePrinterWithRemoveComments(nodeBuilder.EmitContext()) } var builder func(symbol *ast.Symbol, meaning ast.SymbolFlags, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node @@ -325,7 +325,7 @@ func (c *Checker) typePredicateToStringEx(typePredicate *TypePredicate, enclosin nodeBuilder := c.getNodeBuilder() combinedFlags := toNodeBuilderFlags(flags) | nodebuilder.FlagsIgnoreErrors | nodebuilder.FlagsWriteTypeParametersInQualifiedName predicate := nodeBuilder.TypePredicateToTypePredicateNode(typePredicate, enclosingDeclaration, combinedFlags, nodebuilder.InternalFlagsNone, nil) // TODO: GH#18217 - printer_ := createPrinterWithRemoveComments(nodeBuilder.EmitContext()) + printer_ := CreatePrinterWithRemoveComments(nodeBuilder.EmitContext()) var sourceFile *ast.SourceFile if enclosingDeclaration != nil { sourceFile = ast.GetSourceFileOfNode(enclosingDeclaration) @@ -377,3 +377,8 @@ func (c *Checker) TypeToTypeNode(t *Type, enclosingDeclaration *ast.Node, flags nodeBuilder := c.getNodeBuilder() return nodeBuilder.TypeToTypeNode(t, enclosingDeclaration, flags, nodebuilder.InternalFlagsNone, nil) } + +func (c *Checker) TypePredicateToTypePredicateNode(t *TypePredicate, enclosingDeclaration *ast.Node, flags nodebuilder.Flags) *ast.TypePredicateNodeNode { + nodeBuilder := c.getNodeBuilder() + return nodeBuilder.TypePredicateToTypePredicateNode(t, enclosingDeclaration, flags, nodebuilder.InternalFlagsNone, nil) +} diff --git a/internal/checker/services.go b/internal/checker/services.go index caed1fbccc..f4e219d96a 100644 --- a/internal/checker/services.go +++ b/internal/checker/services.go @@ -833,3 +833,7 @@ func isArrayLiteralOrObjectLiteralDestructuringPattern(node *ast.Node) bool { // [x, [a, b, c] ] = someExpression return isArrayLiteralOrObjectLiteralDestructuringPattern(parent) } + +func (c *Checker) GetSignatureFromDeclaration(node *ast.Node) *Signature { + return c.getSignatureFromDeclaration(node) +} diff --git a/internal/checker/types.go b/internal/checker/types.go index 0a66c81c2f..af0d867ac8 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -716,6 +716,10 @@ func (t *Type) IsIndex() bool { return t.flags&TypeFlagsIndex != 0 } +func (t *Type) IsTupleType() bool { + return isTupleType(t) +} + // TypeData type TypeData interface { @@ -929,6 +933,7 @@ type TupleElementInfo struct { } func (t *TupleElementInfo) TupleElementFlags() ElementFlags { return t.flags } +func (t *TupleElementInfo) LabeledDeclaration() *ast.Node { return t.labeledDeclaration } type TupleType struct { InterfaceType @@ -947,6 +952,7 @@ func (t *TupleType) ElementFlags() []ElementFlags { } return elementFlags } +func (t *TupleType) ElementInfos() []TupleElementInfo { return t.elementInfos } // SingleSignatureType @@ -1192,6 +1198,10 @@ type TypePredicate struct { t *Type } +func (typePredicate *TypePredicate) Type() *Type { + return typePredicate.t +} + // IndexInfo type IndexInfo struct { diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index 10f1c6c0c7..ebd6375217 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -1159,10 +1159,6 @@ func reverseAccessKind(a AccessKind) AccessKind { panic("Unhandled case in reverseAccessKind") } -func isInfinityOrNaNString(name string) bool { - return name == "Infinity" || name == "-Infinity" || name == "NaN" -} - func (c *Checker) isConstantVariable(symbol *ast.Symbol) bool { return symbol.Flags&ast.SymbolFlagsVariable != 0 && (c.getDeclarationNodeFlagsFromSymbol(symbol)&ast.NodeFlagsConstant) != 0 } @@ -1224,25 +1220,6 @@ func getBindingElementPropertyName(node *ast.Node) *ast.Node { return node.Name() } -func hasContextSensitiveParameters(node *ast.Node) bool { - // Functions with type parameters are not context sensitive. - if node.TypeParameters() == nil { - // Functions with any parameters that lack type annotations are context sensitive. - if core.Some(node.Parameters(), func(p *ast.Node) bool { return p.Type() == nil }) { - return true - } - if !ast.IsArrowFunction(node) { - // If the first parameter is not an explicit 'this' parameter, then the function has - // an implicit 'this' parameter which is subject to contextual typing. - parameter := core.FirstOrNil(node.Parameters()) - if parameter == nil || !ast.IsThisParameter(parameter) { - return true - } - } - } - return false -} - func isCallChain(node *ast.Node) bool { return ast.IsCallExpression(node) && node.Flags&ast.NodeFlagsOptionalChain != 0 } diff --git a/internal/core/text.go b/internal/core/text.go index d911b839b1..e6762da38e 100644 --- a/internal/core/text.go +++ b/internal/core/text.go @@ -64,3 +64,11 @@ func (t TextRange) Overlaps(t2 TextRange) bool { end := min(t.end, t2.end) return start < end } + +// Similar to Overlaps, but treats touching ranges as intersecting. +// For example, [0, 5) intersects [5, 10). +func (t TextRange) Intersects(t2 TextRange) bool { + start := max(t.pos, t2.pos) + end := min(t.end, t2.end) + return start <= end +} diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 8927813263..452813d855 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -1665,3 +1665,63 @@ func (f *FourslashTest) verifyBaselines(t *testing.T) { baseline.Run(t, getBaselineFileName(t, command), content.String(), getBaselineOptions(command)) } } + +// !!! user preferences +func (f *FourslashTest) VerifyBaselineInlayHints( + t *testing.T, + span *lsproto.Range, + preferences *ls.UserPreferences, +) { + fileName := f.activeFilename + var lspRange lsproto.Range + if span == nil { + lspRange = f.converters.ToLSPRange(f.getScriptInfo(fileName), core.NewTextRange(0, len(f.scriptInfos[fileName].content))) + } else { + lspRange = *span + } + + params := &lsproto.InlayHintParams{ + TextDocument: lsproto.TextDocumentIdentifier{Uri: ls.FileNameToDocumentURI(fileName)}, + Range: lspRange, + } + + // !!! here: set preferences + prefix := fmt.Sprintf("At position (Ln %d, Col %d): ", lspRange.Start.Line, lspRange.Start.Character) + resMsg, result, resultOk := sendRequest(t, f, lsproto.TextDocumentInlayHintInfo, params) + if resMsg == nil { + t.Fatal(prefix + "Nil response received for inlay hints request") + } + if !resultOk { + t.Fatalf(prefix+"Unexpected response type for inlay hints request: %T", resMsg.AsResponse().Result) + } + + fileLines := strings.Split(f.getScriptInfo(fileName).content, "\n") + slices.SortFunc(*result.InlayHints, func(a, b *lsproto.InlayHint) int { + return ls.ComparePositions(a.Position, b.Position) + }) + annotations := core.Map(*result.InlayHints, func(hint *lsproto.InlayHint) string { + if hint.Label.InlayHintLabelParts != nil { + for _, part := range *hint.Label.InlayHintLabelParts { + if part.Location != nil && tspath.IsDeclarationFileName(part.Location.Uri.FileName()) { + part.Location.Range.Start = lsproto.Position{Line: 0, Character: 0} // !!! here: verify + } + } + } + + underline := strings.Repeat(" ", int(hint.Position.Character)) + "^" + hintJson, err := core.StringifyJson(hint, "", " ") + if err != nil { + t.Fatalf(prefix+"Failed to stringify inlay hint for baseline: %v", err) + } + annotation := fileLines[hint.Position.Line] + annotation += "\n" + underline + "\n" + hintJson + return annotation + }) + + if len(annotations) == 0 { + annotations = append(annotations, "=== No inlay hints ===") + } + + f.addResultToBaseline(t, "InlayHints", strings.Join(annotations, "\n\n")) + // !!! diff submodule +} diff --git a/internal/ls/inlay_hints.go b/internal/ls/inlay_hints.go new file mode 100644 index 0000000000..11f4e86a60 --- /dev/null +++ b/internal/ls/inlay_hints.go @@ -0,0 +1,922 @@ +package ls + +import ( + "context" + "slices" + "strings" + "unicode" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/astnav" + "github.com/microsoft/typescript-go/internal/checker" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/debug" + "github.com/microsoft/typescript-go/internal/evaluator" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/nodebuilder" + "github.com/microsoft/typescript-go/internal/printer" + "github.com/microsoft/typescript-go/internal/scanner" + "github.com/microsoft/typescript-go/internal/stringutil" +) + +func (l *LanguageService) ProvideInlayHint( + ctx context.Context, + params *lsproto.InlayHintParams, + preferences *UserPreferences, +) (lsproto.InlayHintResponse, error) { + program, file := l.getProgramAndFile(params.TextDocument.Uri) + quotePreference := getQuotePreference(file, preferences) + + checker, done := program.GetTypeCheckerForFile(ctx, file) + defer done() + inlayHintState := &inlayHintState{ + ctx: ctx, + span: l.converters.FromLSPRange(file, params.Range), + preferences: preferences, + quotePreference: quotePreference, + file: file, + checker: checker, + converters: l.converters, + } + inlayHintState.visit(file.AsNode()) + return lsproto.InlayHintsOrNull{InlayHints: &inlayHintState.result}, nil +} + +type inlayHintState struct { + ctx context.Context + span core.TextRange + preferences *UserPreferences + quotePreference quotePreference + file *ast.SourceFile + checker *checker.Checker + converters *Converters + result []*lsproto.InlayHint +} + +func (s *inlayHintState) visit(node *ast.Node) bool { + if node == nil || node.End()-node.Pos() == 0 { + return false + } + + switch node.Kind { + case ast.KindModuleDeclaration, ast.KindClassDeclaration, ast.KindInterfaceDeclaration, + ast.KindFunctionDeclaration, ast.KindClassExpression, ast.KindFunctionExpression, + ast.KindMethodDeclaration, ast.KindArrowFunction: + if s.ctx.Err() != nil { + return true + } + } + + if !s.span.Intersects(node.Loc) { + return false + } + + if ast.IsTypeNode(node) && !ast.IsExpressionWithTypeArguments(node) { + return false + } + + if ptrIsTrue(s.preferences.IncludeInlayVariableTypeHints) && ast.IsVariableDeclaration(node) { + s.visitVariableLikeDeclaration(node) + } else if ptrIsTrue(s.preferences.IncludeInlayPropertyDeclarationTypeHints) && ast.IsPropertyDeclaration(node) { + s.visitVariableLikeDeclaration(node) + } else if ptrIsTrue(s.preferences.IncludeInlayEnumMemberValueHints) && ast.IsEnumMember(node) { + s.visitEnumMember(node) + } else if shouldShowParameterNameHints(s.preferences) && (ast.IsCallExpression(node) || ast.IsNewExpression(node)) { + s.visitCallOrNewExpression(node) + } else { + if ptrIsTrue(s.preferences.IncludeInlayFunctionParameterTypeHints) && + ast.IsFunctionLikeDeclaration(node) && + ast.HasContextSensitiveParameters(node) { + s.visitFunctionLikeForParameterType(node) + } + if ptrIsTrue(s.preferences.IncludeInlayFunctionLikeReturnTypeHints) && + isSignatureSupportingReturnAnnotation(node) { + s.visitFunctionDeclarationLikeForReturnType(node) + } + } + return node.ForEachChild(s.visit) +} + +// FunctionDeclaration | MethodDeclaration | GetAccessorDeclaration | FunctionExpression | ArrowFunction +func (s *inlayHintState) visitFunctionDeclarationLikeForReturnType(decl *ast.FunctionLikeDeclaration) { + if ast.IsArrowFunction(decl) { + if findChildOfKind(decl, ast.KindOpenParenToken, s.file) == nil { + return + } + } + + typeAnnotation := decl.Type() + if typeAnnotation != nil || decl.Body() == nil { + return + } + + signature := s.checker.GetSignatureFromDeclaration(decl) + if signature == nil { + return + } + + typePredicate := s.checker.GetTypePredicateOfSignature(signature) + + if typePredicate != nil && typePredicate.Type() != nil { + hintParts := s.typePredicateToInlayHintParts(typePredicate) + s.addTypeHints(hintParts, s.getTypeAnnotationPosition(decl)) + return + } + + returnType := s.checker.GetReturnTypeOfSignature(signature) + if isModuleReferenceType(returnType) { + return + } + + hintParts := s.typeToInlayHintParts(returnType) + s.addTypeHints(hintParts, s.getTypeAnnotationPosition(decl)) +} + +func (s *inlayHintState) visitCallOrNewExpression(expr *ast.CallOrNewExpression) { + args := expr.Arguments() + if len(args) == 0 { + return + } + + signature := s.checker.GetResolvedSignature(expr) + if signature == nil { + return + } + + signatureParamPos := 0 + for _, originalArg := range args { + arg := ast.SkipParentheses(originalArg) + if shouldShowLiteralParameterNameHintsOnly(s.preferences) && !isHintableLiteral(arg) { + signatureParamPos++ + continue + } + + spreadArgs := 0 + if ast.IsSpreadElement(arg) { + spreadType := s.checker.GetTypeAtLocation(arg.Expression()) + if spreadType.IsTupleType() { + elementFlags := spreadType.AsTupleType().ElementFlags() + fixedLength := spreadType.AsTupleType().FixedLength() + if fixedLength == 0 { + continue + } + firstOptionalIndex := slices.IndexFunc(elementFlags, func(f checker.ElementFlags) bool { + return f&checker.ElementFlagsRequired == 0 + }) + requiredArgs := core.IfElse(firstOptionalIndex < 0, fixedLength, firstOptionalIndex) + if requiredArgs > 0 { + spreadArgs = requiredArgs + } + } + } + + identifierInfo := s.getParameterIdentifierInfoAtPosition(signature, signatureParamPos) + signatureParamPos = signatureParamPos + core.IfElse(spreadArgs > 0, spreadArgs, 1) + if identifierInfo == nil { + return + } + + parameter := identifierInfo.parameter + parameterName := identifierInfo.name + isFirstVariadicArgument := identifierInfo.isRestParameter + parameterNameNotSameAsArgument := ptrIsTrue(s.preferences.IncludeInlayParameterNameHintsWhenArgumentMatchesName) || + !identifierOrAccessExpressionPostfixMatchesParameterName(arg, parameterName) + if !parameterNameNotSameAsArgument && !isFirstVariadicArgument { + continue + } + + if s.leadingCommentsContainsParameterName(arg, parameterName) { + continue + } + + s.addParameterHints( + parameterName, + parameter, + astnav.GetStartOfNode(originalArg, s.file, false /*includeJSDoc*/), + isFirstVariadicArgument, + ) + } +} + +func (s *inlayHintState) visitEnumMember(member *ast.EnumMemberNode) { + if member.Initializer() != nil { + return + } + + enumValue := s.checker.GetConstantValue(member) + if enumValue != nil { + s.addEnumMemberValueHints(evaluator.AnyToString(enumValue), member.End()) + } +} + +func (s *inlayHintState) visitVariableLikeDeclaration(decl *ast.VariableOrPropertyDeclaration) { + if decl.Initializer() == nil && + !(ast.IsPropertyDeclaration(decl) && s.checker.GetTypeAtLocation(decl).Flags()&checker.TypeFlagsAny == 0) || + ast.IsBindingPattern(decl.Name()) || (ast.IsVariableDeclaration(decl) && !isHintableDeclaration(decl)) { + return + } + + typeAnnotation := decl.Type() + if typeAnnotation != nil { + return + } + + declarationType := s.checker.GetTypeAtLocation(decl) + if isModuleReferenceType(declarationType) { + return + } + + hintParts := s.typeToInlayHintParts(declarationType) + var hintText string + if hintParts.String != nil { + hintText = *hintParts.String + } else if hintParts.InlayHintLabelParts != nil { + var b strings.Builder + for _, part := range *hintParts.InlayHintLabelParts { + b.WriteString(part.Value) + } + hintText = b.String() + } + if ptrIsFalse(s.preferences.IncludeInlayVariableTypeHintsWhenTypeMatchesName) && + stringutil.EquateStringCaseInsensitive(decl.Name().Text(), hintText) { + return + } + s.addTypeHints(hintParts, decl.Name().End()) +} + +func (s *inlayHintState) visitFunctionLikeForParameterType(node *ast.FunctionLikeDeclaration) { + signature := s.checker.GetSignatureFromDeclaration(node) + if signature == nil { + return + } + + pos := 0 + for _, param := range node.Parameters() { + if isHintableDeclaration(param) { + var symbol *ast.Symbol + if ast.IsThisParameter(param) { + symbol = signature.ThisParameter() + } else { + symbol = signature.Parameters()[pos] + } + s.addParameterTypeHint(param, symbol) + } + if ast.IsThisParameter(param) { + continue + } + pos++ + } +} + +func (s *inlayHintState) addParameterTypeHint(node *ast.ParameterDeclarationNode, symbol *ast.Symbol) { + typeAnnotation := node.Type() + if typeAnnotation != nil || symbol == nil { + return + } + typeHints := s.getParameterDeclarationTypeHints(symbol) + if typeHints == nil { + return + } + var pos int + if node.AsParameterDeclaration().QuestionToken != nil { + pos = node.AsParameterDeclaration().QuestionToken.End() + } else { + pos = node.Name().End() + } + s.addTypeHints(*typeHints, pos) +} + +func (s *inlayHintState) getParameterDeclarationTypeHints(symbol *ast.Symbol) *lsproto.StringOrInlayHintLabelParts { + valueDeclaration := symbol.ValueDeclaration + if valueDeclaration == nil || !ast.IsParameter(valueDeclaration) { + return nil + } + + signatureParamType := s.checker.GetTypeOfSymbolAtLocation(symbol, valueDeclaration) + if isModuleReferenceType(signatureParamType) { + return nil + } + + return ptrTo(s.typeToInlayHintParts(signatureParamType)) +} + +func (s *inlayHintState) typeToInlayHintParts(t *checker.Type) lsproto.StringOrInlayHintLabelParts { + if !shouldUseInteractiveInlayHints(s.preferences) { + return lsproto.StringOrInlayHintLabelParts{String: ptrTo(s.printTypeInSingleLine(t))} + } + + flags := nodebuilder.FlagsIgnoreErrors | nodebuilder.FlagsAllowUniqueESSymbolType | + nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope + typeNode := s.checker.TypeToTypeNode(t, nil /*enclosingDeclaration*/, flags) + debug.AssertIsDefined(typeNode, "should always get typenode") + return lsproto.StringOrInlayHintLabelParts{ + InlayHintLabelParts: ptrTo(s.getInlayHintLabelParts(typeNode)), + } +} + +func (s *inlayHintState) typePredicateToInlayHintParts(typePredicate *checker.TypePredicate) lsproto.StringOrInlayHintLabelParts { + if !shouldUseInteractiveInlayHints(s.preferences) { + return lsproto.StringOrInlayHintLabelParts{String: ptrTo(s.printTypePredicateInSingleLine(typePredicate))} + } + + flags := nodebuilder.FlagsIgnoreErrors | nodebuilder.FlagsAllowUniqueESSymbolType | + nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope + typeNode := s.checker.TypePredicateToTypePredicateNode(typePredicate, nil /*enclosingDeclaration*/, flags) + debug.AssertIsDefined(typeNode, "should always get typePredicateNode") + return lsproto.StringOrInlayHintLabelParts{ + InlayHintLabelParts: ptrTo(s.getInlayHintLabelParts(typeNode)), + } +} + +func (s *inlayHintState) printTypePredicateInSingleLine(typePredicate *checker.TypePredicate) string { + flags := nodebuilder.FlagsIgnoreErrors | nodebuilder.FlagsAllowUniqueESSymbolType | + nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope + p := checker.CreatePrinterWithRemoveComments(nil /*emitContext*/) + writer, done := printer.GetSingleLineStringWriter() + defer done() + typeNode := s.checker.TypePredicateToTypePredicateNode(typePredicate, nil /*enclosingDeclaration*/, flags) + debug.AssertIsDefined(typeNode, "should always get typePredicateNode") + p.Write(typeNode, s.file, writer, nil /*sourceMapGenerator*/) + return writer.String() +} + +func (s *inlayHintState) printTypeInSingleLine(t *checker.Type) string { + flags := nodebuilder.FlagsIgnoreErrors | nodebuilder.FlagsAllowUniqueESSymbolType | + nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope + p := checker.CreatePrinterWithRemoveComments(nil /*emitContext*/) + writer, done := printer.GetSingleLineStringWriter() + defer done() + typeNode := s.checker.TypeToTypeNode(t, nil /*enclosingDeclaration*/, flags) + debug.AssertIsDefined(typeNode, "should always get typeNode") + p.Write(typeNode, s.file, writer, nil /*sourceMapGenerator*/) + return writer.String() +} + +func (s *inlayHintState) addTypeHints(hint lsproto.StringOrInlayHintLabelParts, position int) { + if hint.String != nil { + hint.String = ptrTo(": " + *hint.String) + } else { + hint.InlayHintLabelParts = ptrTo(append([]*lsproto.InlayHintLabelPart{{Value: ": "}}, *hint.InlayHintLabelParts...)) + } + s.result = append(s.result, &lsproto.InlayHint{ + Label: hint, + Position: s.converters.PositionToLineAndCharacter(s.file, core.TextPos(position)), + Kind: ptrTo(lsproto.InlayHintKindType), + PaddingLeft: ptrTo(true), + }) +} + +func (s *inlayHintState) addEnumMemberValueHints(text string, position int) { + s.result = append(s.result, &lsproto.InlayHint{ + Label: lsproto.StringOrInlayHintLabelParts{ + String: ptrTo("= " + text), + }, + Position: s.converters.PositionToLineAndCharacter(s.file, core.TextPos(position)), + PaddingLeft: ptrTo(true), + }) +} + +func (s *inlayHintState) addParameterHints(text string, parameter *ast.IdentifierNode, position int, isFirstVariadicArgument bool) { + hintText := core.IfElse(isFirstVariadicArgument, "...", "") + text + var labelParts lsproto.StringOrInlayHintLabelParts + if shouldUseInteractiveInlayHints(s.preferences) { + displayParts := []*lsproto.InlayHintLabelPart{ + s.getNodeDisplayPart(hintText, parameter), + { + Value: ":", + }, + } + labelParts = lsproto.StringOrInlayHintLabelParts{InlayHintLabelParts: &displayParts} + } else { + labelParts = lsproto.StringOrInlayHintLabelParts{String: ptrTo(hintText + ":")} + } + + s.result = append(s.result, &lsproto.InlayHint{ + Label: labelParts, + Position: s.converters.PositionToLineAndCharacter(s.file, core.TextPos(position)), + Kind: ptrTo(lsproto.InlayHintKindParameter), + PaddingRight: ptrTo(true), + }) +} + +func shouldShowParameterNameHints(preferences *UserPreferences) bool { + return preferences.IncludeInlayParameterNameHints != nil && + (*preferences.IncludeInlayParameterNameHints == InlayHintParameterNameHintsLiterals || + *preferences.IncludeInlayParameterNameHints == InlayHintParameterNameHintsAll) +} + +func shouldShowLiteralParameterNameHintsOnly(preferences *UserPreferences) bool { + return preferences.IncludeInlayParameterNameHints != nil && + *preferences.IncludeInlayParameterNameHints == InlayHintParameterNameHintsLiterals +} + +func shouldUseInteractiveInlayHints(preferences *UserPreferences) bool { + return ptrIsTrue(preferences.InteractiveInlayHints) +} + +// node is FunctionDeclaration | ArrowFunction | FunctionExpression | MethodDeclaration | GetAccessorDeclaration +func isSignatureSupportingReturnAnnotation(node *ast.Node) bool { + return ast.IsArrowFunction(node) || ast.IsFunctionExpression(node) || ast.IsFunctionDeclaration(node) || + ast.IsMethodDeclaration(node) || ast.IsGetAccessorDeclaration(node) +} + +func isHintableDeclaration(node *ast.VariableOrParameterDeclaration) bool { + if (ast.IsPartOfParameterDeclaration(node) || ast.IsVariableDeclaration(node) && ast.IsVarConst(node)) && + node.Initializer() != nil { + initializer := ast.SkipParentheses(node.Initializer()) + return !(isHintableLiteral(initializer) || ast.IsNewExpression(initializer) || + ast.IsObjectLiteralExpression(initializer) || ast.IsAssertionExpression(initializer)) + } + return true +} + +func isHintableLiteral(node *ast.Node) bool { + switch node.Kind { + case ast.KindPrefixUnaryExpression: + operand := node.AsPrefixUnaryExpression().Operand + return ast.IsLiteralExpression(operand) || ast.IsIdentifier(operand) && ast.IsInfinityOrNaNString(operand.Text()) + case ast.KindTrueKeyword, ast.KindFalseKeyword, ast.KindNullKeyword, + ast.KindNoSubstitutionTemplateLiteral, ast.KindTemplateExpression: + return true + case ast.KindIdentifier: + name := node.Text() + return name == "undefined" || ast.IsInfinityOrNaNString(name) + } + return ast.IsLiteralExpression(node) +} + +func isModuleReferenceType(t *checker.Type) bool { + symbol := t.Symbol() + return symbol != nil && symbol.Flags&ast.SymbolFlagsModule != 0 +} + +func (s *inlayHintState) getInlayHintLabelParts(node *ast.Node) []*lsproto.InlayHintLabelPart { + var parts []*lsproto.InlayHintLabelPart + + var visitForDisplayParts func(node *ast.Node) + var visitDisplayPartList func(nodes []*ast.Node, separator string) + var visitParametersAndTypeParameters func(node *ast.SignatureDeclaration) + + visitForDisplayParts = func(node *ast.Node) { + if node == nil { + return + } + + tokenString := scanner.TokenToString(node.Kind) + if tokenString != "" { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: tokenString}) + return + } + + if ast.IsLiteralExpression(node) { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: s.getLiteralText(node)}) + return + } + + switch node.Kind { + case ast.KindIdentifier: + identifierText := node.Text() + var name *ast.Node + if node.Symbol() != nil && len(node.Symbol().Declarations) != 0 { + name = ast.GetNameOfDeclaration(node.Symbol().Declarations[0]) + } + if name != nil { + parts = append(parts, s.getNodeDisplayPart(identifierText, name)) + } else { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: identifierText}) + } + case ast.KindQualifiedName: + visitForDisplayParts(node.AsQualifiedName().Left) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "."}) + visitForDisplayParts(node.AsQualifiedName().Right) + case ast.KindTypePredicate: + if node.AsTypePredicateNode().AssertsModifier != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "asserts "}) + } + visitForDisplayParts(node.AsTypePredicateNode().ParameterName) + if node.Type() != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " is "}) + visitForDisplayParts(node.Type()) + } + case ast.KindTypeReference: + visitForDisplayParts(node.AsTypeReferenceNode().TypeName) + if len(node.TypeArguments()) > 0 { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "<"}) + visitDisplayPartList(node.TypeArguments(), ",") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ">"}) + } + case ast.KindTypeParameter: + if len(node.ModifierNodes()) > 0 { + visitDisplayPartList(node.ModifierNodes(), "") + } + visitForDisplayParts(node.Name()) + if node.AsTypeParameter().Constraint != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " extends "}) + visitForDisplayParts(node.AsTypeParameter().Constraint) + } + if node.AsTypeParameter().DefaultType != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " = "}) + visitForDisplayParts(node.AsTypeParameter().DefaultType) + } + case ast.KindParameter: + if len(node.ModifierNodes()) > 0 { + visitDisplayPartList(node.ModifierNodes(), " ") + } + if node.AsParameterDeclaration().DotDotDotToken != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "..."}) + } + visitForDisplayParts(node.Name()) + if node.AsParameterDeclaration().QuestionToken != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "?"}) + } + if node.Type() != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ": "}) + visitForDisplayParts(node.Type()) + } + case ast.KindConstructorType: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "new "}) + visitParametersAndTypeParameters(node) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " => "}) + visitForDisplayParts(node.Type()) + case ast.KindTypeQuery: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "typeof "}) + visitForDisplayParts(node.AsTypeQueryNode().ExprName) + if len(node.TypeArguments()) > 0 { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "<"}) + visitDisplayPartList(node.TypeArguments(), ", ") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ">"}) + } + case ast.KindTypeLiteral: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "{"}) + if len(node.Members()) > 0 { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " "}) + visitDisplayPartList(node.Members(), "; ") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " "}) + } + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "}"}) + case ast.KindArrayType: + visitForDisplayParts(node.AsArrayTypeNode().ElementType) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "[]"}) + case ast.KindTupleType: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "["}) + visitDisplayPartList(node.Elements(), ", ") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "]"}) + case ast.KindNamedTupleMember: + if node.AsNamedTupleMember().DotDotDotToken != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "..."}) + } + visitForDisplayParts(node.Name()) + if node.AsNamedTupleMember().QuestionToken != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "?"}) + } + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ": "}) + visitForDisplayParts(node.Type()) + case ast.KindOptionalType: + visitForDisplayParts(node.Type()) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "?"}) + case ast.KindRestType: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "..."}) + visitForDisplayParts(node.Type()) + case ast.KindUnionType: + if node.AsUnionTypeNode().Types != nil { + visitDisplayPartList(node.AsUnionTypeNode().Types.Nodes, " | ") + } + case ast.KindIntersectionType: + if node.AsIntersectionTypeNode().Types != nil { + visitDisplayPartList(node.AsIntersectionTypeNode().Types.Nodes, " & ") + } + case ast.KindConditionalType: + visitForDisplayParts(node.AsConditionalTypeNode().CheckType) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " extends "}) + visitForDisplayParts(node.AsConditionalTypeNode().ExtendsType) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " ? "}) + visitForDisplayParts(node.AsConditionalTypeNode().TrueType) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " : "}) + visitForDisplayParts(node.AsConditionalTypeNode().FalseType) + case ast.KindInferType: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "infer "}) + visitForDisplayParts(node.AsInferTypeNode().TypeParameter) + case ast.KindParenthesizedType: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "("}) + visitForDisplayParts(node.Type()) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ")"}) + case ast.KindTypeOperator: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: scanner.TokenToString(node.AsTypeOperatorNode().Operator)}) + visitForDisplayParts(node.Type()) + case ast.KindIndexedAccessType: + visitForDisplayParts(node.AsIndexedAccessTypeNode().ObjectType) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "["}) + visitForDisplayParts(node.AsIndexedAccessTypeNode().IndexType) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "]"}) + case ast.KindMappedType: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "{ "}) + if node.AsMappedTypeNode().ReadonlyToken != nil { + if node.AsMappedTypeNode().ReadonlyToken.Kind == ast.KindPlusToken { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "+"}) + } else if node.AsMappedTypeNode().ReadonlyToken.Kind == ast.KindMinusToken { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "-"}) + } + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "readonly "}) + } + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "["}) + visitForDisplayParts(node.AsMappedTypeNode().TypeParameter) + if node.AsMappedTypeNode().NameType != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " as "}) + visitForDisplayParts(node.AsMappedTypeNode().NameType) + } + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "]"}) + if node.AsMappedTypeNode().QuestionToken != nil { + if node.AsMappedTypeNode().QuestionToken.Kind == ast.KindPlusToken { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "+"}) + } else if node.AsMappedTypeNode().QuestionToken.Kind == ast.KindMinusToken { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "-"}) + } + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "?"}) + } + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ": "}) + if node.Type() != nil { + visitForDisplayParts(node.Type()) + } + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "; }"}) + case ast.KindLiteralType: + visitForDisplayParts(node.AsLiteralTypeNode().Literal) + case ast.KindFunctionType: + visitParametersAndTypeParameters(node) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " => "}) + visitForDisplayParts(node.Type()) + case ast.KindImportType: + if node.AsImportTypeNode().IsTypeOf { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "typeof "}) + } + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "import("}) + visitForDisplayParts(node.AsImportTypeNode().Argument) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ")"}) + if node.AsImportTypeNode().Qualifier != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "."}) + visitForDisplayParts(node.AsImportTypeNode().Qualifier) + } + if len(node.TypeArguments()) > 0 { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "<"}) + visitDisplayPartList(node.TypeArguments(), ", ") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ">"}) + } + case ast.KindPropertySignature: + if len(node.ModifierNodes()) > 0 { + visitDisplayPartList(node.ModifierNodes(), " ") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " "}) + } + visitForDisplayParts(node.Name()) + if node.AsPropertySignatureDeclaration().PostfixToken != nil { + parts = append( + parts, + &lsproto.InlayHintLabelPart{ + Value: scanner.TokenToString(node.AsPropertySignatureDeclaration().PostfixToken.Kind), + }) + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "?"}) + } + if node.Type() != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ": "}) + visitForDisplayParts(node.Type()) + } + case ast.KindIndexSignature: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "["}) + visitDisplayPartList(node.Parameters(), ", ") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "]"}) + if node.Type() != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ": "}) + visitForDisplayParts(node.Type()) + } + case ast.KindMethodSignature: + if len(node.ModifierNodes()) > 0 { + visitDisplayPartList(node.ModifierNodes(), " ") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " "}) + } + visitForDisplayParts(node.Name()) + if node.AsMethodSignatureDeclaration().PostfixToken != nil { + parts = append( + parts, + &lsproto.InlayHintLabelPart{ + Value: scanner.TokenToString(node.AsPropertySignatureDeclaration().PostfixToken.Kind), + }) + } + visitParametersAndTypeParameters(node) + if node.Type() != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ": "}) + visitForDisplayParts(node.Type()) + } + case ast.KindCallSignature: + visitParametersAndTypeParameters(node) + if node.Type() != nil { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ": "}) + visitForDisplayParts(node.Type()) + } + case ast.KindArrayBindingPattern: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "["}) + visitDisplayPartList(node.Elements(), ", ") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "]"}) + case ast.KindObjectBindingPattern: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "{"}) + if len(node.Elements()) > 0 { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " "}) + visitDisplayPartList(node.Elements(), ", ") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: " "}) + } + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "}"}) + case ast.KindBindingElement: + visitForDisplayParts(node.Name()) + case ast.KindPrefixUnaryExpression: + parts = append( + parts, + &lsproto.InlayHintLabelPart{ + Value: scanner.TokenToString(node.AsPrefixUnaryExpression().Operator), + }) + visitForDisplayParts(node.AsPrefixUnaryExpression().Operand) + case ast.KindTemplateLiteralType: + visitForDisplayParts(node.AsTemplateLiteralTypeNode().Head) + for _, span := range node.AsTemplateLiteralTypeNode().TemplateSpans.Nodes { + visitForDisplayParts(span) + } + case ast.KindTemplateHead: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: s.getLiteralText(node)}) + case ast.KindTemplateLiteralTypeSpan: + visitForDisplayParts(node.Type()) + visitForDisplayParts(node.AsTemplateLiteralTypeSpan().Literal) + case ast.KindTemplateMiddle, ast.KindTemplateTail: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: s.getLiteralText(node)}) + case ast.KindThisType: + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "this"}) + default: + debug.FailBadSyntaxKind(node) + } + } + + visitDisplayPartList = func(nodes []*ast.Node, separator string) { + for i, n := range nodes { + if i > 0 { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: separator}) + } + visitForDisplayParts(n) + } + } + + visitParametersAndTypeParameters = func(node *ast.SignatureDeclaration) { + if len(node.TypeParameters()) > 0 { + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "<"}) + visitDisplayPartList(node.TypeParameters(), ", ") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ">"}) + } + parts = append(parts, &lsproto.InlayHintLabelPart{Value: "("}) + visitDisplayPartList(node.Parameters(), ", ") + parts = append(parts, &lsproto.InlayHintLabelPart{Value: ")"}) + } + + visitForDisplayParts(node) + return parts +} + +func (s *inlayHintState) getNodeDisplayPart(text string, node *ast.Node) *lsproto.InlayHintLabelPart { + file := ast.GetSourceFileOfNode(node) + return &lsproto.InlayHintLabelPart{ + Value: text, + Location: &lsproto.Location{ + Uri: FileNameToDocumentURI(file.FileName()), + Range: s.converters.ToLSPRange(file, node.Loc), + }, + } +} + +func (s *inlayHintState) getLiteralText(node *ast.LiteralLikeNode) string { + switch node.Kind { + case ast.KindStringLiteral: + if s.quotePreference == quotePreferenceSingle { + return `'` + printer.EscapeString(node.Text(), printer.QuoteCharSingleQuote) + `'` + } + return `"` + printer.EscapeString(node.Text(), printer.QuoteCharDoubleQuote) + `"` + case ast.KindTemplateHead, ast.KindTemplateMiddle, ast.KindTemplateTail: + rawText := node.RawText() + switch node.Kind { + case ast.KindTemplateHead: + return "`" + rawText + "${" + case ast.KindTemplateMiddle: + return "}" + rawText + "${" + case ast.KindTemplateTail: + return "}" + rawText + "`" + } + } + return node.Text() +} + +type parameterInfo struct { + parameter *ast.IdentifierNode + name string + isRestParameter bool +} + +func (s *inlayHintState) getParameterIdentifierInfoAtPosition(signature *checker.Signature, pos int) *parameterInfo { + parameters := signature.Parameters() + paramCount := len(parameters) - core.IfElse(signature.HasRestParameter(), 1, 0) + if pos < paramCount { + param := parameters[pos] + paramId := getParameterDeclarationIdentifier(param) + if paramId == nil { + return nil + } + return ¶meterInfo{ + parameter: paramId, + name: paramId.Text(), + isRestParameter: false, + } + } + + var restParameter *ast.Symbol + var restId *ast.IdentifierNode + if paramCount < len(parameters) { + restParameter = parameters[paramCount] + restId = getParameterDeclarationIdentifier(restParameter) + } + if restId == nil { + return nil + } + + restType := s.checker.GetTypeOfSymbol(restParameter) + if restType.IsTupleType() { + associatedNames := make([]*ast.Node, 0, len(restType.AsTupleType().ElementInfos())) + for _, elementInfo := range restType.AsTupleType().ElementInfos() { + labeledElement := elementInfo.LabeledDeclaration() + associatedNames = append(associatedNames, labeledElement) + } + index := pos - paramCount + if index < len(associatedNames) { + associatedName := associatedNames[index] + if associatedName != nil { + debug.Assert(ast.IsIdentifier(associatedName.Name())) + var isRestTupleElement bool + if ast.IsNamedTupleMember(associatedName) { + isRestTupleElement = associatedName.AsNamedTupleMember().DotDotDotToken != nil + } else { + isRestTupleElement = associatedName.AsParameterDeclaration().DotDotDotToken != nil + } + return ¶meterInfo{ + parameter: restId, + name: restParameter.Name, + isRestParameter: isRestTupleElement, + } + } + } + + return nil + } + + if pos == paramCount { + return ¶meterInfo{ + parameter: restId, + name: restParameter.Name, + isRestParameter: true, + } + } + return nil +} + +func getParameterDeclarationIdentifier(symbol *ast.Symbol) *ast.IdentifierNode { + if symbol.ValueDeclaration != nil && ast.IsParameter(symbol.ValueDeclaration) && ast.IsIdentifier(symbol.ValueDeclaration.Name()) { + return symbol.ValueDeclaration.Name() + } + return nil +} + +func identifierOrAccessExpressionPostfixMatchesParameterName(expr *ast.Expression, parameterName string) bool { + if ast.IsIdentifier(expr) { + return expr.Text() == parameterName + } + if ast.IsPropertyAccessExpression(expr) { + return expr.Name().Text() == parameterName + } + return false +} + +func (s *inlayHintState) leadingCommentsContainsParameterName(node *ast.Node, name string) bool { + if !scanner.IsIdentifierText(name, s.file.LanguageVariant) { + return false + } + + ranges := getLeadingCommentRangesOfNode(node, s.file) + fileText := s.file.Text() + for r := range ranges { + commentText := strings.TrimFunc(fileText[r.Pos():r.End()], func(r rune) bool { + return unicode.IsSpace(r) || r == '/' || r == '*' + }) + if commentText == name { + return true + } + } + + return false +} + +func (s *inlayHintState) getTypeAnnotationPosition(decl *ast.FunctionLikeDeclaration) int { + closeParenToken := findChildOfKind(decl, ast.KindCloseParenToken, s.file) + if closeParenToken != nil { + return closeParenToken.End() + } + return decl.ParameterList().End() +} diff --git a/internal/ls/types.go b/internal/ls/types.go index f5f9d19014..34bcadd34b 100644 --- a/internal/ls/types.go +++ b/internal/ls/types.go @@ -20,6 +20,14 @@ const ( QuotePreferenceSingle QuotePreference = "single" ) +type InlayHintParameterNameHints string + +const ( + InlayHintParameterNameHintsNone InlayHintParameterNameHints = "none" + InlayHintParameterNameHintsLiterals InlayHintParameterNameHints = "literals" + InlayHintParameterNameHintsAll InlayHintParameterNameHints = "all" +) + type UserPreferences struct { QuotePreference *QuotePreference // If enabled, TypeScript will search through all external modules' exports and add them to the completions list. @@ -56,6 +64,16 @@ type UserPreferences struct { AutoImportFileExcludePatterns []string UseAliasesForRename *bool + + IncludeInlayParameterNameHints *InlayHintParameterNameHints + IncludeInlayParameterNameHintsWhenArgumentMatchesName *bool + IncludeInlayFunctionParameterTypeHints *bool + IncludeInlayVariableTypeHints *bool + IncludeInlayVariableTypeHintsWhenTypeMatchesName *bool + IncludeInlayPropertyDeclarationTypeHints *bool + IncludeInlayFunctionLikeReturnTypeHints *bool + IncludeInlayEnumMemberValueHints *bool + InteractiveInlayHints *bool } func (p *UserPreferences) ModuleSpecifierPreferences() modulespecifiers.UserPreferences { diff --git a/internal/lsp/server.go b/internal/lsp/server.go index e057e28d61..b707650f31 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -460,6 +460,7 @@ var handlers = sync.OnceValue(func() handlerMap { registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentOnTypeFormattingInfo, (*Server).handleDocumentOnTypeFormat) registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDocumentSymbolInfo, (*Server).handleDocumentSymbol) registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentRenameInfo, (*Server).handleRename) + registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentInlayHintInfo, (*Server).handleInlayHint) registerRequestHandler(handlers, lsproto.WorkspaceSymbolInfo, (*Server).handleWorkspaceSymbol) registerRequestHandler(handlers, lsproto.CompletionItemResolveInfo, (*Server).handleCompletionItemResolve) @@ -636,6 +637,9 @@ func (s *Server) handleInitialize(ctx context.Context, params *lsproto.Initializ RenameProvider: &lsproto.BooleanOrRenameOptions{ Boolean: ptrTo(true), }, + InlayHintProvider: &lsproto.BooleanOrInlayHintOptionsOrInlayHintRegistrationOptions{ + Boolean: ptrTo(true), + }, }, } @@ -894,3 +898,12 @@ func getCompletionClientCapabilities(params *lsproto.InitializeParams) *lsproto. } return params.Capabilities.TextDocument.Completion } + +func (s *Server) handleInlayHint( + ctx context.Context, + languageService *ls.LanguageService, + params *lsproto.InlayHintParams, +) (lsproto.InlayHintResponse, error) { + // !!! get user preferences + return languageService.ProvideInlayHint(ctx, params, &ls.UserPreferences{}) +}