• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.GoToDefinition {
3    export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined {
4        const resolvedRef = getReferenceAtPosition(sourceFile, position, program);
5        if (resolvedRef) {
6            return [getDefinitionInfoForFileReference(resolvedRef.reference.fileName, resolvedRef.file.fileName)];
7        }
8
9        const node = getTouchingPropertyName(sourceFile, position);
10        if (node === sourceFile) {
11            return undefined;
12        }
13        const { parent } = node;
14
15        const typeChecker = program.getTypeChecker();
16
17        // Labels
18        if (isJumpStatementTarget(node)) {
19            const label = getTargetLabel(node.parent, node.text);
20            return label ? [createDefinitionInfoFromName(typeChecker, label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217
21        }
22
23        const symbol = getSymbol(node, typeChecker);
24
25        // Could not find a symbol e.g. node is string or number keyword,
26        // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol
27        if (!symbol) {
28            return getDefinitionInfoForIndexSignatures(node, typeChecker);
29        }
30
31        if (parent.kind === SyntaxKind.CallExpression || (parent.kind === SyntaxKind.EtsComponentExpression && isCalledStructDeclaration(symbol.getDeclarations()))) {
32            const declarations = symbol.getDeclarations();
33            if (declarations?.length && declarations[0].kind === SyntaxKind.StructDeclaration) {
34                return getDefinitionFromSymbol(typeChecker, symbol, node);
35            }
36        }
37        const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node);
38        const compilerOptions = program.getCompilerOptions();
39        // Don't go to the component constructor definition for a JSX element, just go to the component definition.
40        if (calledDeclaration && !(isJsxOpeningLikeElement(node.parent) && isConstructorLike(calledDeclaration)) && !isVirtualConstructor(typeChecker, calledDeclaration.symbol, calledDeclaration)) {
41            const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration);
42            // For a function, if this is the original function definition, return just sigInfo.
43            // If this is the original constructor definition, parent is the class.
44            if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration))) {
45                return [sigInfo];
46            }
47            else if (isIdentifier(node)
48                && isNewExpression(parent)
49                && compilerOptions.ets?.components.some(component => component === node.escapedText.toString())
50            ) {
51                return [sigInfo];
52            }
53            else {
54                const defs = getDefinitionFromSymbol(typeChecker, symbol, node, calledDeclaration) || emptyArray;
55                if (isIdentifier(node) && isEtsComponentExpression(parent)) {
56                    return [...defs];
57                }
58                // For a 'super()' call, put the signature first, else put the variable first.
59                return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo];
60            }
61        }
62
63        // Because name in short-hand property assignment has two different meanings: property name and property value,
64        // using go-to-definition at such position should go to the variable declaration of the property value rather than
65        // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition
66        // is performed at the location of property access, we would like to go to definition of the property in the short-hand
67        // assignment. This case and others are handled by the following code.
68        if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
69            const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration);
70            return shorthandSymbol ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node)) : [];
71        }
72
73        // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the
74        // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern
75        // and return the property declaration for the referenced property.
76        // For example:
77        //      import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo"
78        //
79        //      function bar<T>(onfulfilled: (value: T) => void) { //....}
80        //      interface Test {
81        //          pr/*destination*/op1: number
82        //      }
83        //      bar<Test>(({pr/*goto*/op1})=>{});
84        if (isPropertyName(node) && isBindingElement(parent) && isObjectBindingPattern(parent.parent) &&
85            (node === (parent.propertyName || parent.name))) {
86            const name = getNameFromPropertyName(node);
87            const type = typeChecker.getTypeAtLocation(parent.parent);
88            return name === undefined ? emptyArray : flatMap(type.isUnion() ? type.types : [type], t => {
89                const prop = t.getProperty(name);
90                return prop && getDefinitionFromSymbol(typeChecker, prop, node);
91            });
92        }
93
94        // If the current location we want to find its definition is in an object literal, try to get the contextual type for the
95        // object literal, lookup the property symbol in the contextual type, and use this for goto-definition.
96        // For example
97        //      interface Props{
98        //          /*first*/prop1: number
99        //          prop2: boolean
100        //      }
101        //      function Foo(arg: Props) {}
102        //      Foo( { pr/*1*/op1: 10, prop2: true })
103        const element = getContainingObjectLiteralElement(node);
104        if (element) {
105            const contextualType = element && typeChecker.getContextualType(element.parent);
106            if (contextualType) {
107                return flatMap(getPropertySymbolsFromContextualType(element, typeChecker, contextualType, /*unionSymbolOk*/ false), propertySymbol =>
108                    getDefinitionFromSymbol(typeChecker, propertySymbol, node));
109            }
110        }
111
112        return getDefinitionFromSymbol(typeChecker, symbol, node);
113    }
114
115    /**
116     * True if we should not add definitions for both the signature symbol and the definition symbol.
117     * True for `const |f = |() => 0`, false for `function |f() {} const |g = f;`.
118     * Also true for any assignment RHS.
119     */
120    function symbolMatchesSignature(s: Symbol, calledDeclaration: SignatureDeclaration) {
121        return s === calledDeclaration.symbol
122            || s === calledDeclaration.symbol.parent
123            || isAssignmentExpression(calledDeclaration.parent)
124            || (!isCallLikeExpression(calledDeclaration.parent) && s === calledDeclaration.parent.symbol);
125    }
126
127    export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { reference: FileReference, file: SourceFile } | undefined {
128        const referencePath = findReferenceInPosition(sourceFile.referencedFiles, position);
129        if (referencePath) {
130            const file = program.getSourceFileFromReference(sourceFile, referencePath);
131            return file && { reference: referencePath, file };
132        }
133
134        const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position);
135        if (typeReferenceDirective) {
136            const reference = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName);
137            const file = reference && program.getSourceFile(reference.resolvedFileName!); // TODO:GH#18217
138            return file && { reference: typeReferenceDirective, file };
139        }
140
141        const libReferenceDirective = findReferenceInPosition(sourceFile.libReferenceDirectives, position);
142        if (libReferenceDirective) {
143            const file = program.getLibFileFromReference(libReferenceDirective);
144            return file && { reference: libReferenceDirective, file };
145        }
146
147        return undefined;
148    }
149
150    /// Goto type
151    export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined {
152        const node = getTouchingPropertyName(sourceFile, position);
153        if (node === sourceFile) {
154            return undefined;
155        }
156
157        const symbol = typeChecker.getSymbolAtLocation(node);
158        if (!symbol) return undefined;
159
160        const typeAtLocation = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
161        const returnType = tryGetReturnTypeOfFunction(symbol, typeAtLocation, typeChecker);
162        const fromReturnType = returnType && definitionFromType(returnType, typeChecker, node);
163        // If a function returns 'void' or some other type with no definition, just return the function definition.
164        return fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node);
165    }
166
167    function definitionFromType(type: Type, checker: TypeChecker, node: Node): readonly DefinitionInfo[] {
168        return flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t =>
169            t.symbol && getDefinitionFromSymbol(checker, t.symbol, node));
170    }
171
172    function tryGetReturnTypeOfFunction(symbol: Symbol, type: Type, checker: TypeChecker): Type | undefined {
173        // If the type is just a function's inferred type,
174        // go-to-type should go to the return type instead, since go-to-definition takes you to the function anyway.
175        if (type.symbol === symbol ||
176            // At `const f = () => {}`, the symbol is `f` and the type symbol is at `() => {}`
177            symbol.valueDeclaration && type.symbol && isVariableDeclaration(symbol.valueDeclaration) && symbol.valueDeclaration.initializer === type.symbol.valueDeclaration as Node) {
178            const sigs = type.getCallSignatures();
179            if (sigs.length === 1) return checker.getReturnTypeOfSignature(first(sigs));
180        }
181        return undefined;
182    }
183
184    export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number): DefinitionInfoAndBoundSpan | undefined {
185        const definitions = getDefinitionAtPosition(program, sourceFile, position);
186
187        if (!definitions || definitions.length === 0) {
188            return undefined;
189        }
190
191        // Check if position is on triple slash reference.
192        const comment = findReferenceInPosition(sourceFile.referencedFiles, position) ||
193            findReferenceInPosition(sourceFile.typeReferenceDirectives, position) ||
194            findReferenceInPosition(sourceFile.libReferenceDirectives, position);
195
196        if (comment) {
197            return { definitions, textSpan: createTextSpanFromRange(comment) };
198        }
199
200        const node = getTouchingPropertyName(sourceFile, position);
201        const textSpan = createTextSpan(node.getStart(), node.getWidth());
202
203        return { definitions, textSpan };
204    }
205
206    // At 'x.foo', see if the type of 'x' has an index signature, and if so find its declarations.
207    function getDefinitionInfoForIndexSignatures(node: Node, checker: TypeChecker): DefinitionInfo[] | undefined {
208        if (!isPropertyAccessExpression(node.parent) || node.parent.name !== node) return;
209        const type = checker.getTypeAtLocation(node.parent.expression);
210        return mapDefined(type.isUnionOrIntersection() ? type.types : [type], nonUnionType => {
211            const info = checker.getIndexInfoOfType(nonUnionType, IndexKind.String);
212            return info && info.declaration && createDefinitionFromSignatureDeclaration(checker, info.declaration);
213        });
214    }
215
216    function getSymbol(node: Node, checker: TypeChecker): Symbol | undefined {
217        const symbol = checker.getSymbolAtLocation(node);
218        // If this is an alias, and the request came at the declaration location
219        // get the aliased symbol instead. This allows for goto def on an import e.g.
220        //   import {A, B} from "mod";
221        // to jump to the implementation directly.
222        if (symbol && symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) {
223            const aliased = checker.getAliasedSymbol(symbol);
224            if (aliased.declarations) {
225                return aliased;
226            }
227        }
228        return symbol;
229    }
230
231    // Go to the original declaration for cases:
232    //
233    //   (1) when the aliased symbol was declared in the location(parent).
234    //   (2) when the aliased symbol is originating from an import.
235    //
236    function shouldSkipAlias(node: Node, declaration: Node): boolean {
237        if (node.kind !== SyntaxKind.Identifier) {
238            return false;
239        }
240        if (node.parent === declaration) {
241            return true;
242        }
243        switch (declaration.kind) {
244            case SyntaxKind.ImportClause:
245            case SyntaxKind.ImportEqualsDeclaration:
246                return true;
247            case SyntaxKind.ImportSpecifier:
248                return declaration.parent.kind === SyntaxKind.NamedImports;
249            case SyntaxKind.BindingElement:
250            case SyntaxKind.VariableDeclaration:
251                return isInJSFile(declaration) && isRequireVariableDeclaration(declaration, /*requireStringLiteralLikeArgument*/ true);
252            default:
253                return false;
254        }
255    }
256
257    function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, declarationNode?: Node): DefinitionInfo[] | undefined {
258        // There are cases when you extend a function by adding properties to it afterwards,
259        // we want to strip those extra properties.
260        // For deduping purposes, we also want to exclude any declarationNodes if provided.
261        const filteredDeclarations =
262            filter(symbol.declarations, d => d !== declarationNode && (!isAssignmentDeclaration(d) || d === symbol.valueDeclaration))
263            || undefined;
264        return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(filteredDeclarations, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node));
265
266        function getConstructSignatureDefinition(): DefinitionInfo[] | undefined {
267            // Applicable only if we are in a new expression, or we are on a constructor declaration
268            // and in either case the symbol has a construct signature definition, i.e. class
269            if (symbol.flags & SymbolFlags.Class && !(symbol.flags & (SymbolFlags.Function | SymbolFlags.Variable)) && (isNewExpressionTarget(node) || node.kind === SyntaxKind.ConstructorKeyword)) {
270                const cls = find(filteredDeclarations, isClassLike) || Debug.fail("Expected declaration to have at least one class-like declaration");
271                return getSignatureDefinition(cls.members, /*selectConstructors*/ true);
272            }
273        }
274
275        function getCallSignatureDefinition(): DefinitionInfo[] | undefined {
276            return isCallOrNewExpressionTarget(node) || isNameOfFunctionDeclaration(node)
277                ? getSignatureDefinition(filteredDeclarations, /*selectConstructors*/ false)
278                : undefined;
279        }
280
281        function getSignatureDefinition(signatureDeclarations: readonly Declaration[] | undefined, selectConstructors: boolean): DefinitionInfo[] | undefined {
282            if (!signatureDeclarations) {
283                return undefined;
284            }
285            const declarations = signatureDeclarations.filter(selectConstructors ? isConstructorDeclaration : isFunctionLike);
286            const declarationsWithBody = declarations.filter(d => !!(<FunctionLikeDeclaration>d).body);
287
288            // declarations defined on the global scope can be defined on multiple files. Get all of them.
289            return declarations.length
290                ? declarationsWithBody.length !== 0
291                    ? declarationsWithBody.map(x => createDefinitionInfo(x, typeChecker, symbol, node))
292                    : [createDefinitionInfo(last(declarations), typeChecker, symbol, node)]
293                : undefined;
294        }
295    }
296
297    /** Creates a DefinitionInfo from a Declaration, using the declaration's name if possible. */
298    function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node): DefinitionInfo {
299        const symbolName = checker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol
300        const symbolKind = SymbolDisplay.getSymbolKind(checker, symbol, node);
301        const containerName = symbol.parent ? checker.symbolToString(symbol.parent, node) : "";
302        return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName);
303    }
304
305    /** Creates a DefinitionInfo directly from the name of a declaration. */
306    function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string): DefinitionInfo {
307        const name = getNameOfDeclaration(declaration) || declaration;
308        const sourceFile = name.getSourceFile();
309        const textSpan = createTextSpanFromNode(name, sourceFile);
310        return {
311            fileName: sourceFile.fileName,
312            textSpan,
313            kind: symbolKind,
314            name: symbolName,
315            containerKind: undefined!, // TODO: GH#18217
316            containerName,
317            ...FindAllReferences.toContextSpan(
318                textSpan,
319                sourceFile,
320                FindAllReferences.getContextNode(declaration)
321            ),
322            isLocal: !checker.isDeclarationVisible(declaration)
323        };
324    }
325
326    function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration): DefinitionInfo {
327        return createDefinitionInfo(decl, typeChecker, decl.symbol, decl);
328    }
329
330    export function findReferenceInPosition(refs: readonly FileReference[], pos: number): FileReference | undefined {
331        return find(refs, ref => textRangeContainsPositionInclusive(ref, pos));
332    }
333
334    function getDefinitionInfoForFileReference(name: string, targetFileName: string): DefinitionInfo {
335        return {
336            fileName: targetFileName,
337            textSpan: createTextSpanFromBounds(0, 0),
338            kind: ScriptElementKind.scriptElement,
339            name,
340            containerName: undefined!,
341            containerKind: undefined!, // TODO: GH#18217
342        };
343    }
344
345    /** Returns a CallLikeExpression where `node` is the target being invoked. */
346    function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined {
347        const target = findAncestor(node, n => !isRightSideOfPropertyAccess(n));
348        const callLike = target?.parent;
349        return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target ? callLike : undefined;
350    }
351
352    function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined {
353        const callLike = getAncestorCallLikeExpression(node);
354        const signature = callLike && typeChecker.getResolvedSignature(callLike);
355        // Don't go to a function type, go to the value having that type.
356        return tryCast(signature && signature.declaration, (d): d is SignatureDeclaration => isFunctionLike(d) && !isFunctionTypeNode(d));
357    }
358
359    function isConstructorLike(node: Node): boolean {
360        switch (node.kind) {
361            case SyntaxKind.Constructor:
362            case SyntaxKind.ConstructorType:
363            case SyntaxKind.ConstructSignature:
364                return true;
365            default:
366                return false;
367        }
368    }
369}
370