package ls import ( "context" "slices" "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/collections" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/ls/lsconv" "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/scanner" ) func (l *LanguageService) ProvideDefinition( ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position, ) (lsproto.DefinitionResponse, error) { if l.UserPreferences().PreferGoToSourceDefinition { return l.ProvideSourceDefinition(ctx, documentURI, position) } return l.provideDefinitionWorker(ctx, documentURI, position) } func (l *LanguageService) provideDefinitionWorker( ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position, ) (lsproto.DefinitionResponse, error) { caps := lsproto.GetClientCapabilities(ctx) clientSupportsLink := caps.TextDocument.Definition.LinkSupport program, file := l.getProgramAndFile(documentURI) pos := int(l.converters.LineAndCharacterToPosition(file, position)) node := astnav.GetTouchingPropertyName(file, pos) reference := getReferenceAtPosition(file, pos, program) if node.Kind == ast.KindSourceFile { return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{}, nil } originSelectionRange := l.createLspRangeFromNode(node, file) if reference != nil && reference.file != nil { return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, []*ast.Node{}, reference), nil } c, done := program.GetTypeCheckerForFile(ctx, file) defer done() if node.Kind == ast.KindOverrideKeyword { if sym := getSymbolForOverriddenMember(c, node); sym != nil { return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, sym.Declarations, nil /*reference*/), nil } } if ast.IsJumpStatementTarget(node) { if label := getTargetLabel(node.Parent, node.Text()); label != nil { return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, []*ast.Node{label}, nil /*reference*/), nil } } if node.Kind == ast.KindCaseKeyword || node.Kind == ast.KindDefaultKeyword && ast.IsDefaultClause(node.Parent) { if stmt := ast.FindAncestor(node.Parent, ast.IsSwitchStatement); stmt != nil { file := ast.GetSourceFileOfNode(stmt) return l.createLocationFromFileAndRange(file, scanner.GetRangeOfTokenAtPosition(file, stmt.Pos())), nil } } if node.Kind == ast.KindReturnKeyword || node.Kind == ast.KindYieldKeyword || node.Kind == ast.KindAwaitKeyword { if fn := ast.FindAncestor(node, ast.IsFunctionLikeDeclaration); fn != nil { return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, []*ast.Node{fn}, nil /*reference*/), nil } } declarations := getDeclarationsFromLocation(c, node) calledDeclaration := tryGetSignatureDeclaration(c, node) if calledDeclaration != nil && !(ast.IsJsxOpeningLikeElement(node.Parent) && isJsxConstructorLike(calledDeclaration)) { symbol := c.GetSymbolAtLocation(getDeclarationNameForKeyword(node)) if symbol != nil && core.Some(c.GetRootSymbols(symbol), func(rootSymbol *ast.Symbol) bool { return symbolMatchesSignature(rootSymbol, calledDeclaration) }) { if !ast.IsConstructorDeclaration(calledDeclaration) { declarations = nil } else { declarations = core.Filter(slices.Clip(declarations), func(node *ast.Node) bool { return node != calledDeclaration && (ast.IsClassDeclaration(node) || ast.IsClassExpression(node)) }) } } else { declarations = core.Filter(slices.Clip(declarations), func(node *ast.Node) bool { return node != calledDeclaration }) } declarations = append(declarations, calledDeclaration) } return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, declarations, reference), nil } func (l *LanguageService) ProvideTypeDefinition( ctx context.Context, documentURI lsproto.DocumentUri, position lsproto.Position, ) (lsproto.TypeDefinitionResponse, error) { caps := lsproto.GetClientCapabilities(ctx) clientSupportsLink := caps.TextDocument.TypeDefinition.LinkSupport program, file := l.getProgramAndFile(documentURI) node := astnav.GetTouchingPropertyName(file, int(l.converters.LineAndCharacterToPosition(file, position))) if node.Kind == ast.KindSourceFile { return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{}, nil } originSelectionRange := l.createLspRangeFromNode(node, file) c, done := program.GetTypeCheckerForFile(ctx, file) defer done() node = getDeclarationNameForKeyword(node) if symbol := c.GetSymbolAtLocation(node); symbol != nil { symbolType := getTypeOfSymbolAtLocation(c, symbol, node) declarations := getDeclarationsFromType(symbolType) if typeArgument := c.GetFirstTypeArgumentFromKnownType(symbolType); typeArgument != nil { declarations = core.Concatenate(getDeclarationsFromType(typeArgument), declarations) } if len(declarations) != 0 { return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, declarations, nil /*reference*/), nil } if symbol.Flags&ast.SymbolFlagsValue == 0 && symbol.Flags&ast.SymbolFlagsType != 0 { return l.createDefinitionLocations(originSelectionRange, clientSupportsLink, symbol.Declarations, nil /*reference*/), nil } } return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{}, nil } func getDeclarationNameForKeyword(node *ast.Node) *ast.Node { if node.Kind >= ast.KindFirstKeyword && node.Kind <= ast.KindLastKeyword { if ast.IsVariableDeclarationList(node.Parent) { if decl := core.FirstOrNil(node.Parent.AsVariableDeclarationList().Declarations.Nodes); decl != nil && decl.Name() != nil { return decl.Name() } } else if node.Parent.DeclarationData() != nil && node.Parent.Name() != nil && node.Pos() < node.Parent.Name().Pos() { return node.Parent.Name() } } return node } type fileRange struct { fileName string fileRange core.TextRange } func (l *LanguageService) createDefinitionLocations( originSelectionRange lsproto.Range, clientSupportsLink bool, declarations []*ast.Node, reference *refInfo, ) lsproto.DefinitionResponse { locations := make([]*lsproto.LocationLink, 0) locationRanges := collections.Set[fileRange]{} if reference != nil { targetRange := lsproto.Range{ Start: lsproto.Position{ Line: 0, Character: 0, }, End: lsproto.Position{ Line: 0, Character: 0, }, } locations = append(locations, &lsproto.LocationLink{ OriginSelectionRange: &originSelectionRange, TargetUri: lsconv.FileNameToDocumentURI(reference.fileName), TargetRange: targetRange, TargetSelectionRange: targetRange, }) } for _, decl := range declarations { file := ast.GetSourceFileOfNode(decl) fileName := file.FileName() name := core.OrElse(ast.GetNameOfDeclaration(decl), decl) var nameRange core.TextRange if name.Kind == ast.KindEmptyStatement { nameRange = core.NewTextRange(name.Pos(), name.Pos()) } else { nameRange = createRangeFromNode(name, file) } if locationRanges.AddIfAbsent(fileRange{fileName, nameRange}) { contextNode := core.OrElse(getContextNode(decl), decl) contextRange := core.OrElse(toContextRange(&nameRange, file, contextNode), &nameRange) targetSelectionLoc := l.getMappedLocation(fileName, nameRange) targetLoc := l.getMappedLocation(fileName, *contextRange) locations = append(locations, &lsproto.LocationLink{ OriginSelectionRange: &originSelectionRange, TargetSelectionRange: targetSelectionLoc.Range, TargetUri: targetLoc.Uri, TargetRange: targetLoc.Range, }) } } if clientSupportsLink { return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{DefinitionLinks: &locations} } return createLocationsFromLinks(locations) } func createLocationsFromLinks(links []*lsproto.LocationLink) lsproto.DefinitionResponse { locations := core.Map(links, func(link *lsproto.LocationLink) lsproto.Location { return lsproto.Location{ Uri: link.TargetUri, Range: link.TargetSelectionRange, } }) return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{Locations: &locations} } func (l *LanguageService) createLocationFromFileAndRange(file *ast.SourceFile, textRange core.TextRange) lsproto.DefinitionResponse { mappedLocation := l.getMappedLocation(file.FileName(), textRange) return lsproto.LocationOrLocationsOrDefinitionLinksOrNull{ Location: &mappedLocation, } } func getDeclarationsFromLocation(c *checker.Checker, node *ast.Node) []*ast.Node { if ast.IsIdentifier(node) && ast.IsShorthandPropertyAssignment(node.Parent) { // Because name in short-hand property assignment has two different meanings: property name and property value, // using go-to-definition at such position should go to the variable declaration of the property value rather than // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition // is performed at the location of property access, we would like to go to definition of the property in the short-hand // assignment. This case and others are handled by the following code. // and the contextual type's property declarations shorthandSymbol := c.GetResolvedSymbol(node) var declarations []*ast.Node if shorthandSymbol != nil { declarations = shorthandSymbol.Declarations } contextualDeclarations := getDeclarationsFromObjectLiteralElement(c, node) return core.Concatenate(declarations, contextualDeclarations) } if ast.IsPropertyName(node) && ast.IsBindingElement(node.Parent) && ast.IsObjectBindingPattern(node.Parent.Parent) { // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the // declaration of the symbol (which is itself), we should try to get to the original type of the // ObjectBindingPattern and return the property declaration for the referenced property. // For example: // import('./foo').then(({ bar }) => undefined); => should navigate to the declaration in file "./foo" // // function bar(onfulfilled: (value: T) => void) { } // interface Test { prop1: number } // bar(({ prop1 }) => {}); => should navigate to prop1 in Test bindingEl := node.Parent.AsBindingElement() if bindingEl.DotDotDotToken == nil && node == core.OrElse(bindingEl.PropertyName, node.Parent.Name()) { if name, ok := ast.TryGetTextOfPropertyName(node); ok { t := c.GetTypeAtLocation(node.Parent.Parent) types := []*checker.Type{t} if t.IsUnion() { types = t.Types() } var result []*ast.Node for _, unionType := range types { if prop := c.GetPropertyOfType(unionType, name); prop != nil { result = append(result, prop.Declarations...) } } return result } } } node = getDeclarationNameForKeyword(node) if symbol := c.GetSymbolAtLocation(node); symbol != nil { if symbol.Flags&ast.SymbolFlagsClass != 0 && symbol.Flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsVariable) == 0 && node.Kind == ast.KindConstructorKeyword { if constructor := symbol.Members[ast.InternalSymbolNameConstructor]; constructor != nil { symbol = constructor } } if symbol.Flags&ast.SymbolFlagsAlias != 0 { if resolved, ok := c.ResolveAlias(symbol); ok { symbol = resolved } } objectLiteralElementDeclarations := getDeclarationsFromObjectLiteralElement(c, node) if len(objectLiteralElementDeclarations) > 0 { return objectLiteralElementDeclarations } if len(symbol.Declarations) > 0 { return symbol.Declarations } } if indexInfos := c.GetIndexSignaturesAtLocation(node); len(indexInfos) != 0 { return indexInfos } return nil } // getDeclarationsFromObjectLiteralElement returns declarations from the contextual type // of an object literal element, if available. func getDeclarationsFromObjectLiteralElement(c *checker.Checker, node *ast.Node) []*ast.Node { element := getContainingObjectLiteralElement(node) if element == nil { return nil } contextualType := c.GetContextualType(element.Parent, checker.ContextFlagsNone) if contextualType == nil { return nil } properties := c.GetPropertySymbolsFromContextualType(element, contextualType, false /*unionSymbolOk*/) if core.Some(properties, func(p *ast.Symbol) bool { return p.ValueDeclaration != nil && ast.IsObjectLiteralExpression(p.ValueDeclaration.Parent) && ast.IsObjectLiteralElement(p.ValueDeclaration) && p.ValueDeclaration.Name() == node }) { if withoutNodeInferencesType := c.GetContextualType(element.Parent, checker.ContextFlagsIgnoreNodeInferences); withoutNodeInferencesType != nil { if withoutNodeInferencesProperties := c.GetPropertySymbolsFromContextualType(element, withoutNodeInferencesType, false /*unionSymbolOk*/); len(withoutNodeInferencesProperties) > 0 { properties = withoutNodeInferencesProperties } } } var result []*ast.Node for _, prop := range properties { result = append(result, prop.Declarations...) } return result } // Returns a CallLikeExpression where `node` is the target being invoked. func getAncestorCallLikeExpression(node *ast.Node) *ast.Node { target := ast.FindAncestor(node, func(n *ast.Node) bool { return !ast.IsRightSideOfPropertyAccess(n) }) callLike := target.Parent if callLike != nil && ast.IsCallLikeExpression(callLike) && ast.GetInvokedExpression(callLike) == target { return callLike } return nil } func tryGetSignatureDeclaration(typeChecker *checker.Checker, node *ast.Node) *ast.Node { var signature *checker.Signature callLike := getAncestorCallLikeExpression(node) if callLike != nil { signature = typeChecker.GetResolvedSignature(callLike) } // Don't go to a function type, go to the value having that type. var declaration *ast.Node if signature != nil && signature.Declaration() != nil { declaration = signature.Declaration() if ast.IsFunctionLike(declaration) && !ast.IsFunctionTypeNode(declaration) { return declaration } } return nil } func isJsxConstructorLike(node *ast.Node) bool { switch { case ast.IsConstructorDeclaration(node), ast.IsConstructorTypeNode(node), ast.IsCallSignatureDeclaration(node), ast.IsConstructSignatureDeclaration(node): return true default: return false } } func symbolMatchesSignature(symbol *ast.Symbol, calledDeclaration *ast.Node) bool { if symbol == nil || calledDeclaration == nil { return false } calledSymbol := calledDeclaration.Symbol() if symbol == calledSymbol || calledSymbol != nil && symbol == calledSymbol.Parent { return true } parent := calledDeclaration.Parent return parent != nil && (ast.IsAssignmentExpression(parent, false /*excludeCompoundAssignment*/) || !ast.IsCallLikeExpression(parent) && ast.CanHaveSymbol(parent) && symbol == parent.Symbol()) } func getSymbolForOverriddenMember(typeChecker *checker.Checker, node *ast.Node) *ast.Symbol { classElement := ast.FindAncestor(node, ast.IsClassElement) if classElement == nil || classElement.Name() == nil { return nil } baseDeclaration := ast.FindAncestor(classElement, ast.IsClassLike) if baseDeclaration == nil { return nil } baseTypeNode := ast.GetClassExtendsHeritageElement(baseDeclaration) if baseTypeNode == nil { return nil } expression := ast.SkipParentheses(baseTypeNode.Expression()) var base *ast.Symbol if ast.IsClassExpression(expression) { base = expression.Symbol() } else { base = typeChecker.GetSymbolAtLocation(expression) } if base == nil { return nil } name := ast.GetTextOfPropertyName(classElement.Name()) if ast.HasStaticModifier(classElement) { return typeChecker.GetPropertyOfType(typeChecker.GetTypeOfSymbol(base), name) } return typeChecker.GetPropertyOfType(typeChecker.GetDeclaredTypeOfSymbol(base), name) } func getTypeOfSymbolAtLocation(c *checker.Checker, symbol *ast.Symbol, node *ast.Node) *checker.Type { t := c.GetTypeOfSymbolAtLocation(symbol, node) // If the type is just a function's inferred type, go-to-type should go to the return type instead since // go-to-definition takes you to the function anyway. if t.Symbol() == symbol || t.Symbol() != nil && symbol.ValueDeclaration != nil && ast.IsVariableDeclaration(symbol.ValueDeclaration) && symbol.ValueDeclaration.Initializer() == t.Symbol().ValueDeclaration { sigs := c.GetCallSignatures(t) if len(sigs) == 1 { return c.GetReturnTypeOfSignature(sigs[0]) } } return t } func getDeclarationsFromType(t *checker.Type) []*ast.Node { var result []*ast.Node for _, t := range t.Distributed() { if t.Symbol() != nil { for _, decl := range t.Symbol().Declarations { result = core.AppendIfUnique(result, decl) } } } return result }