• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    AssignmentDeclarationKind, AssignmentExpression, AssignmentOperatorToken, CallLikeExpression, concatenate,
3    createTextSpan, createTextSpanFromBounds, createTextSpanFromNode, createTextSpanFromRange, Debug, Declaration,
4    DefinitionInfo, DefinitionInfoAndBoundSpan, emptyArray, every, FileReference, filter, find, FindAllReferences,
5    findAncestor, first, flatMap, forEach, FunctionLikeDeclaration, getAssignmentDeclarationKind,
6    getContainingObjectLiteralElement, getDirectoryPath, getEffectiveBaseTypeNode, getInvokedExpression,
7    getModeForUsageLocation, getNameFromPropertyName, getNameOfDeclaration, getPropertySymbolsFromContextualType,
8    getTargetLabel, getTextOfPropertyName, getTouchingPropertyName, getTouchingToken, hasEffectiveModifier,
9    hasInitializer, hasStaticModifier, isAnyImportOrBareOrAccessedRequire, isAssignmentDeclaration,
10    isAssignmentExpression, isBindingElement, isCalledStructDeclaration, isCallLikeExpression,
11    isCallOrNewExpressionTarget, isClassElement, isClassExpression, isClassLike, isClassStaticBlockDeclaration,
12    isConstructorDeclaration, isDeclarationFileName, isEtsComponentExpression, isExternalModuleNameRelative,
13    isFunctionLike, isFunctionLikeDeclaration, isFunctionTypeNode, isIdentifier, isImportMeta, isJSDocOverrideTag,
14    isJsxOpeningLikeElement, isJumpStatementTarget, isModuleSpecifierLike, isNameOfFunctionDeclaration, isNewExpression,
15    isNewExpressionTarget, isObjectBindingPattern, isPropertyName, isRightSideOfPropertyAccess, isStaticModifier,
16    isVariableDeclaration, isVirtualConstructor, last, map, mapDefined, ModifierFlags, moveRangePastModifiers, Node,
17    NodeFlags, Program, resolvePath, ScriptElementKind, SignatureDeclaration, skipAlias, skipParentheses, skipTrivia,
18    some, SourceFile, Symbol, SymbolDisplay, SymbolFlags, SyntaxKind, textRangeContainsPositionInclusive, TextSpan,
19    tryCast, tryGetModuleSpecifierFromDeclaration, Type, TypeChecker, TypeFlags, unescapeLeadingUnderscores,
20} from "./_namespaces/ts";
21
22/** @internal */
23export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number, searchOtherFilesOnly?: boolean, stopAtAlias?: boolean): readonly DefinitionInfo[] | undefined {
24    const resolvedRef = getReferenceAtPosition(sourceFile, position, program);
25    const fileReferenceDefinition = resolvedRef && [getDefinitionInfoForFileReference(resolvedRef.reference.fileName, resolvedRef.fileName, resolvedRef.unverified)] || emptyArray;
26    if (resolvedRef?.file) {
27        // If `file` is missing, do a symbol-based lookup as well
28        return fileReferenceDefinition;
29    }
30
31    const node = getTouchingPropertyName(sourceFile, position);
32    if (node === sourceFile) {
33        return undefined;
34    }
35
36    const { parent } = node;
37    const typeChecker = program.getTypeChecker();
38
39    if (node.kind === SyntaxKind.OverrideKeyword || (isIdentifier(node) && isJSDocOverrideTag(parent) && parent.tagName === node)) {
40        return getDefinitionFromOverriddenMember(typeChecker, node) || emptyArray;
41    }
42
43    // Labels
44    if (isJumpStatementTarget(node)) {
45        const label = getTargetLabel(node.parent, node.text);
46        return label ? [createDefinitionInfoFromName(typeChecker, label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217
47    }
48
49    if (node.kind === SyntaxKind.ReturnKeyword) {
50        const functionDeclaration = findAncestor(node.parent, n =>
51            isClassStaticBlockDeclaration(n) ? "quit" : isFunctionLikeDeclaration(n)) as FunctionLikeDeclaration | undefined;
52        return functionDeclaration ? [createDefinitionFromSignatureDeclaration(typeChecker, functionDeclaration)] : undefined;
53    }
54
55    if (isStaticModifier(node) && isClassStaticBlockDeclaration(node.parent)) {
56        const classDecl = node.parent.parent;
57        const { symbol, failedAliasResolution } = getSymbol(classDecl, typeChecker, stopAtAlias);
58
59        const staticBlocks = filter(classDecl.members, isClassStaticBlockDeclaration);
60        const containerName = symbol ? typeChecker.symbolToString(symbol, classDecl) : "";
61        const sourceFile = node.getSourceFile();
62        return map(staticBlocks, staticBlock => {
63            let { pos } = moveRangePastModifiers(staticBlock);
64            pos = skipTrivia(sourceFile.text, pos);
65            return createDefinitionInfoFromName(typeChecker, staticBlock, ScriptElementKind.constructorImplementationElement, "static {}", containerName, /*unverified*/ false, failedAliasResolution, { start: pos, length: "static".length });
66        });
67    }
68
69    let { symbol, failedAliasResolution } = getSymbol(node, typeChecker, stopAtAlias);
70    let fallbackNode = node;
71
72    if (searchOtherFilesOnly && failedAliasResolution) {
73        // We couldn't resolve the specific import, try on the module specifier.
74        const importDeclaration = forEach([node, ...symbol?.declarations || emptyArray], n => findAncestor(n, isAnyImportOrBareOrAccessedRequire));
75        const moduleSpecifier = importDeclaration && tryGetModuleSpecifierFromDeclaration(importDeclaration);
76        if (moduleSpecifier) {
77            ({ symbol, failedAliasResolution } = getSymbol(moduleSpecifier, typeChecker, stopAtAlias));
78            fallbackNode = moduleSpecifier;
79        }
80    }
81
82    if (!symbol && isModuleSpecifierLike(fallbackNode)) {
83        // We couldn't resolve the module specifier as an external module, but it could
84        // be that module resolution succeeded but the target was not a module.
85        const ref = sourceFile.resolvedModules?.get(fallbackNode.text, getModeForUsageLocation(sourceFile, fallbackNode));
86        if (ref) {
87            return [{
88                name: fallbackNode.text,
89                fileName: ref.resolvedFileName,
90                containerName: undefined!,
91                containerKind: undefined!,
92                kind: ScriptElementKind.scriptElement,
93                textSpan: createTextSpan(0, 0),
94                failedAliasResolution,
95                isAmbient: isDeclarationFileName(ref.resolvedFileName),
96                unverified: fallbackNode !== node,
97            }];
98        }
99    }
100
101    // Could not find a symbol e.g. node is string or number keyword,
102    // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol
103    if (!symbol) {
104        return concatenate(fileReferenceDefinition, getDefinitionInfoForIndexSignatures(node, typeChecker));
105    }
106
107    if (searchOtherFilesOnly && every(symbol.declarations, d => d.getSourceFile().fileName === sourceFile.fileName)) return undefined;
108
109    if (parent.kind === SyntaxKind.CallExpression || (parent.kind === SyntaxKind.EtsComponentExpression && isCalledStructDeclaration(symbol.getDeclarations()))) {
110        const declarations = symbol.getDeclarations();
111        if (declarations?.length && declarations[0].kind === SyntaxKind.StructDeclaration) {
112            return getDefinitionFromSymbol(typeChecker, symbol, node);
113        }
114    }
115
116    const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node);
117    const compilerOptions = program.getCompilerOptions();
118    // Don't go to the component constructor definition for a JSX element, just go to the component definition.
119    if (calledDeclaration && !(isJsxOpeningLikeElement(node.parent) && isConstructorLike(calledDeclaration)) && !isVirtualConstructor(typeChecker, calledDeclaration.symbol, calledDeclaration)) {
120        const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration, failedAliasResolution);
121        // For a function, if this is the original function definition, return just sigInfo.
122        // If this is the original constructor definition, parent is the class.
123        if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration))) {
124            return [sigInfo];
125        }
126        else if (isIdentifier(node)
127            && isNewExpression(parent)
128            && compilerOptions.ets?.components.some(component => component === node.escapedText.toString())
129        ) {
130            return [sigInfo];
131        }
132        else {
133            const defs = getDefinitionFromSymbol(typeChecker, symbol, node, failedAliasResolution, calledDeclaration) || emptyArray;
134            if (isIdentifier(node) && isEtsComponentExpression(parent)) {
135                return [...defs];
136            }
137            // For a 'super()' call, put the signature first, else put the variable first.
138            return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo];
139        }
140    }
141
142    // Because name in short-hand property assignment has two different meanings: property name and property value,
143    // using go-to-definition at such position should go to the variable declaration of the property value rather than
144    // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition
145    // is performed at the location of property access, we would like to go to definition of the property in the short-hand
146    // assignment. This case and others are handled by the following code.
147    if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
148        const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration);
149        const definitions = shorthandSymbol?.declarations ? shorthandSymbol.declarations.map(decl => createDefinitionInfo(decl, typeChecker, shorthandSymbol, node, /*unverified*/ false, failedAliasResolution)) : emptyArray;
150        return concatenate(definitions, getDefinitionFromObjectLiteralElement(typeChecker, node) || emptyArray);
151    }
152
153    // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the
154    // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern
155    // and return the property declaration for the referenced property.
156    // For example:
157    //      import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo"
158    //
159    //      function bar<T>(onfulfilled: (value: T) => void) { //....}
160    //      interface Test {
161    //          pr/*destination*/op1: number
162    //      }
163    //      bar<Test>(({pr/*goto*/op1})=>{});
164    if (isPropertyName(node) && isBindingElement(parent) && isObjectBindingPattern(parent.parent) &&
165        (node === (parent.propertyName || parent.name))) {
166        const name = getNameFromPropertyName(node);
167        const type = typeChecker.getTypeAtLocation(parent.parent);
168        return name === undefined ? emptyArray : flatMap(type.isUnion() ? type.types : [type], t => {
169            const prop = t.getProperty(name);
170            return prop && getDefinitionFromSymbol(typeChecker, prop, node);
171        });
172    }
173
174    return concatenate(fileReferenceDefinition, getDefinitionFromObjectLiteralElement(typeChecker, node) || getDefinitionFromSymbol(typeChecker, symbol, node, failedAliasResolution));
175}
176
177/**
178 * True if we should not add definitions for both the signature symbol and the definition symbol.
179 * True for `const |f = |() => 0`, false for `function |f() {} const |g = f;`.
180 * Also true for any assignment RHS.
181 */
182function symbolMatchesSignature(s: Symbol, calledDeclaration: SignatureDeclaration) {
183    return s === calledDeclaration.symbol
184        || s === calledDeclaration.symbol.parent
185        || isAssignmentExpression(calledDeclaration.parent)
186        || (!isCallLikeExpression(calledDeclaration.parent) && s === calledDeclaration.parent.symbol);
187}
188
189// If the current location we want to find its definition is in an object literal, try to get the contextual type for the
190// object literal, lookup the property symbol in the contextual type, and use this for goto-definition.
191// For example
192//      interface Props{
193//          /*first*/prop1: number
194//          prop2: boolean
195//      }
196//      function Foo(arg: Props) {}
197//      Foo( { pr/*1*/op1: 10, prop2: true })
198function getDefinitionFromObjectLiteralElement(typeChecker: TypeChecker, node: Node) {
199    const element = getContainingObjectLiteralElement(node);
200    if (element) {
201        const contextualType = element && typeChecker.getContextualType(element.parent);
202        if (contextualType) {
203            return flatMap(getPropertySymbolsFromContextualType(element, typeChecker, contextualType, /*unionSymbolOk*/ false), propertySymbol =>
204                getDefinitionFromSymbol(typeChecker, propertySymbol, node));
205        }
206    }
207}
208
209function getDefinitionFromOverriddenMember(typeChecker: TypeChecker, node: Node) {
210    const classElement = findAncestor(node, isClassElement);
211    if (!(classElement && classElement.name)) return;
212
213    const baseDeclaration = findAncestor(classElement, isClassLike);
214    if (!baseDeclaration) return;
215
216    const baseTypeNode = getEffectiveBaseTypeNode(baseDeclaration);
217    if (!baseTypeNode) return;
218    const expression = skipParentheses(baseTypeNode.expression);
219    const base = isClassExpression(expression) ? expression.symbol : typeChecker.getSymbolAtLocation(expression);
220    if (!base) return;
221
222    const name = unescapeLeadingUnderscores(getTextOfPropertyName(classElement.name));
223    const symbol = hasStaticModifier(classElement)
224        ? typeChecker.getPropertyOfType(typeChecker.getTypeOfSymbol(base), name)
225        : typeChecker.getPropertyOfType(typeChecker.getDeclaredTypeOfSymbol(base), name);
226    if (!symbol) return;
227
228    return getDefinitionFromSymbol(typeChecker, symbol, node);
229}
230
231/** @internal */
232export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { reference: FileReference, fileName: string, unverified: boolean, file?: SourceFile } | undefined {
233    const referencePath = findReferenceInPosition(sourceFile.referencedFiles, position);
234    if (referencePath) {
235        const file = program.getSourceFileFromReference(sourceFile, referencePath);
236        return file && { reference: referencePath, fileName: file.fileName, file, unverified: false };
237    }
238
239    const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position);
240    if (typeReferenceDirective) {
241        const reference = program.getResolvedTypeReferenceDirectives().get(typeReferenceDirective.fileName, typeReferenceDirective.resolutionMode || sourceFile.impliedNodeFormat);
242        const file = reference && program.getSourceFile(reference.resolvedFileName!); // TODO:GH#18217
243        return file && { reference: typeReferenceDirective, fileName: file.fileName, file, unverified: false };
244    }
245
246    const libReferenceDirective = findReferenceInPosition(sourceFile.libReferenceDirectives, position);
247    if (libReferenceDirective) {
248        const file = program.getLibFileFromReference(libReferenceDirective);
249        return file && { reference: libReferenceDirective, fileName: file.fileName, file, unverified: false };
250    }
251
252    if (sourceFile.resolvedModules?.size()) {
253        const node = getTouchingToken(sourceFile, position);
254        if (isModuleSpecifierLike(node) && isExternalModuleNameRelative(node.text) && sourceFile.resolvedModules.has(node.text, getModeForUsageLocation(sourceFile, node))) {
255            const verifiedFileName = sourceFile.resolvedModules.get(node.text, getModeForUsageLocation(sourceFile, node))?.resolvedFileName;
256            const fileName = verifiedFileName || resolvePath(getDirectoryPath(sourceFile.fileName), node.text);
257            return {
258                file: program.getSourceFile(fileName),
259                fileName,
260                reference: {
261                    pos: node.getStart(),
262                    end: node.getEnd(),
263                    fileName: node.text
264                },
265                unverified: !verifiedFileName,
266            };
267        }
268    }
269
270    return undefined;
271}
272
273/// Goto type
274/** @internal */
275export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): readonly DefinitionInfo[] | undefined {
276    const node = getTouchingPropertyName(sourceFile, position);
277    if (node === sourceFile) {
278        return undefined;
279    }
280
281    if (isImportMeta(node.parent) && node.parent.name === node) {
282        return definitionFromType(typeChecker.getTypeAtLocation(node.parent), typeChecker, node.parent, /*failedAliasResolution*/ false);
283    }
284
285    const { symbol, failedAliasResolution } = getSymbol(node, typeChecker, /*stopAtAlias*/ false);
286    if (!symbol) return undefined;
287
288    const typeAtLocation = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
289    const returnType = tryGetReturnTypeOfFunction(symbol, typeAtLocation, typeChecker);
290    const fromReturnType = returnType && definitionFromType(returnType, typeChecker, node, failedAliasResolution);
291    // If a function returns 'void' or some other type with no definition, just return the function definition.
292    const typeDefinitions = fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node, failedAliasResolution);
293    return typeDefinitions.length ? typeDefinitions
294        : !(symbol.flags & SymbolFlags.Value) && symbol.flags & SymbolFlags.Type ? getDefinitionFromSymbol(typeChecker, skipAlias(symbol, typeChecker), node, failedAliasResolution)
295        : undefined;
296}
297
298function definitionFromType(type: Type, checker: TypeChecker, node: Node, failedAliasResolution: boolean | undefined): readonly DefinitionInfo[] {
299    return flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t =>
300        t.symbol && getDefinitionFromSymbol(checker, t.symbol, node, failedAliasResolution));
301}
302
303function tryGetReturnTypeOfFunction(symbol: Symbol, type: Type, checker: TypeChecker): Type | undefined {
304    // If the type is just a function's inferred type,
305    // go-to-type should go to the return type instead, since go-to-definition takes you to the function anyway.
306    if (type.symbol === symbol ||
307        // At `const f = () => {}`, the symbol is `f` and the type symbol is at `() => {}`
308        symbol.valueDeclaration && type.symbol && isVariableDeclaration(symbol.valueDeclaration) && symbol.valueDeclaration.initializer === type.symbol.valueDeclaration as Node) {
309        const sigs = type.getCallSignatures();
310        if (sigs.length === 1) return checker.getReturnTypeOfSignature(first(sigs));
311    }
312    return undefined;
313}
314
315/** @internal */
316export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number): DefinitionInfoAndBoundSpan | undefined {
317    const definitions = getDefinitionAtPosition(program, sourceFile, position);
318
319    if (!definitions || definitions.length === 0) {
320        return undefined;
321    }
322
323    // Check if position is on triple slash reference.
324    const comment = findReferenceInPosition(sourceFile.referencedFiles, position) ||
325        findReferenceInPosition(sourceFile.typeReferenceDirectives, position) ||
326        findReferenceInPosition(sourceFile.libReferenceDirectives, position);
327
328    if (comment) {
329        return { definitions, textSpan: createTextSpanFromRange(comment) };
330    }
331
332    const node = getTouchingPropertyName(sourceFile, position);
333    const textSpan = createTextSpan(node.getStart(), node.getWidth());
334
335    return { definitions, textSpan };
336}
337
338// At 'x.foo', see if the type of 'x' has an index signature, and if so find its declarations.
339function getDefinitionInfoForIndexSignatures(node: Node, checker: TypeChecker): DefinitionInfo[] | undefined {
340    return mapDefined(checker.getIndexInfosAtLocation(node), info => info.declaration && createDefinitionFromSignatureDeclaration(checker, info.declaration));
341}
342
343function getSymbol(node: Node, checker: TypeChecker, stopAtAlias: boolean | undefined) {
344    const symbol = checker.getSymbolAtLocation(node);
345    // If this is an alias, and the request came at the declaration location
346    // get the aliased symbol instead. This allows for goto def on an import e.g.
347    //   import {A, B} from "mod";
348    // to jump to the implementation directly.
349    let failedAliasResolution = false;
350    if (symbol?.declarations && symbol.flags & SymbolFlags.Alias && !stopAtAlias && shouldSkipAlias(node, symbol.declarations[0])) {
351        const aliased = checker.getAliasedSymbol(symbol);
352        if (aliased.declarations) {
353            return { symbol: aliased };
354        }
355        else {
356            failedAliasResolution = true;
357        }
358    }
359    return { symbol, failedAliasResolution };
360}
361
362// Go to the original declaration for cases:
363//
364//   (1) when the aliased symbol was declared in the location(parent).
365//   (2) when the aliased symbol is originating from an import.
366//
367function shouldSkipAlias(node: Node, declaration: Node): boolean {
368    if (node.kind !== SyntaxKind.Identifier) {
369        return false;
370    }
371    if (node.parent === declaration) {
372        return true;
373    }
374    if (declaration.kind === SyntaxKind.NamespaceImport) {
375        return false;
376    }
377    return true;
378}
379
380/**
381 * ```ts
382 * function f() {}
383 * f.foo = 0;
384 * ```
385 *
386 * Here, `f` has two declarations: the function declaration, and the identifier in the next line.
387 * The latter is a declaration for `f` because it gives `f` the `SymbolFlags.Namespace` meaning so
388 * it can contain `foo`. However, that declaration is pretty uninteresting and not intuitively a
389 * "definition" for `f`. Ideally, the question we'd like to answer is "what SymbolFlags does this
390 * declaration contribute to the symbol for `f`?" If the answer is just `Namespace` and the
391 * declaration looks like an assignment, that declaration is in no sense a definition for `f`.
392 * But that information is totally lost during binding and/or symbol merging, so we need to do
393 * our best to reconstruct it or use other heuristics. This function (and the logic around its
394 * calling) covers our tests but feels like a hack, and it would be great if someone could come
395 * up with a more precise definition of what counts as a definition.
396 */
397function isExpandoDeclaration(node: Declaration): boolean {
398    if (!isAssignmentDeclaration(node)) return false;
399    const containingAssignment = findAncestor(node, p => {
400        if (isAssignmentExpression(p)) return true;
401        if (!isAssignmentDeclaration(p as Declaration)) return "quit";
402        return false;
403    }) as AssignmentExpression<AssignmentOperatorToken> | undefined;
404    return !!containingAssignment && getAssignmentDeclarationKind(containingAssignment) === AssignmentDeclarationKind.Property;
405}
406
407function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node, failedAliasResolution?: boolean, excludeDeclaration?: Node): DefinitionInfo[] | undefined {
408    const filteredDeclarations = filter(symbol.declarations, d => d !== excludeDeclaration);
409    const withoutExpandos = filter(filteredDeclarations, d => !isExpandoDeclaration(d));
410    const results = some(withoutExpandos) ? withoutExpandos : filteredDeclarations;
411    return getConstructSignatureDefinition() || getCallSignatureDefinition() || map(results, declaration => createDefinitionInfo(declaration, typeChecker, symbol, node, /*unverified*/ false, failedAliasResolution));
412
413    function getConstructSignatureDefinition(): DefinitionInfo[] | undefined {
414        // Applicable only if we are in a new expression, or we are on a constructor declaration
415        // and in either case the symbol has a construct signature definition, i.e. class
416        if (symbol.flags & SymbolFlags.Class && !(symbol.flags & (SymbolFlags.Function | SymbolFlags.Variable)) && (isNewExpressionTarget(node) || node.kind === SyntaxKind.ConstructorKeyword)) {
417            const cls = find(filteredDeclarations, isClassLike) || Debug.fail("Expected declaration to have at least one class-like declaration");
418            return getSignatureDefinition(cls.members, /*selectConstructors*/ true);
419        }
420    }
421
422    function getCallSignatureDefinition(): DefinitionInfo[] | undefined {
423        return isCallOrNewExpressionTarget(node) || isNameOfFunctionDeclaration(node)
424            ? getSignatureDefinition(filteredDeclarations, /*selectConstructors*/ false)
425            : undefined;
426    }
427
428    function getSignatureDefinition(signatureDeclarations: readonly Declaration[] | undefined, selectConstructors: boolean): DefinitionInfo[] | undefined {
429        if (!signatureDeclarations) {
430            return undefined;
431        }
432        const declarations = signatureDeclarations.filter(selectConstructors ? isConstructorDeclaration : isFunctionLike);
433        const declarationsWithBody = declarations.filter(d => !!(d as FunctionLikeDeclaration).body);
434
435        // declarations defined on the global scope can be defined on multiple files. Get all of them.
436        return declarations.length
437            ? declarationsWithBody.length !== 0
438                ? declarationsWithBody.map(x => createDefinitionInfo(x, typeChecker, symbol, node))
439                : [createDefinitionInfo(last(declarations), typeChecker, symbol, node, /*unverified*/ false, failedAliasResolution)]
440            : undefined;
441    }
442}
443
444/**
445 * Creates a DefinitionInfo from a Declaration, using the declaration's name if possible.
446 *
447 * @internal
448 */
449export function createDefinitionInfo(declaration: Declaration, checker: TypeChecker, symbol: Symbol, node: Node, unverified?: boolean, failedAliasResolution?: boolean): DefinitionInfo {
450    const symbolName = checker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol
451    const symbolKind = SymbolDisplay.getSymbolKind(checker, symbol, node);
452    const containerName = symbol.parent ? checker.symbolToString(symbol.parent, node) : "";
453    return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName, unverified, failedAliasResolution);
454}
455
456/** Creates a DefinitionInfo directly from the name of a declaration. */
457function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string, unverified?: boolean, failedAliasResolution?: boolean, textSpan?: TextSpan): DefinitionInfo {
458    const sourceFile = declaration.getSourceFile();
459    if (!textSpan) {
460        const name = getNameOfDeclaration(declaration) || declaration;
461        textSpan = createTextSpanFromNode(name, sourceFile);
462    }
463    return {
464        fileName: sourceFile.fileName,
465        textSpan,
466        kind: symbolKind,
467        name: symbolName,
468        containerKind: undefined!, // TODO: GH#18217
469        containerName,
470        ...FindAllReferences.toContextSpan(
471            textSpan,
472            sourceFile,
473            FindAllReferences.getContextNode(declaration)
474        ),
475        isLocal: !isDefinitionVisible(checker, declaration),
476        isAmbient: !!(declaration.flags & NodeFlags.Ambient),
477        unverified,
478        failedAliasResolution,
479    };
480}
481
482function isDefinitionVisible(checker: TypeChecker, declaration: Declaration): boolean {
483    if (checker.isDeclarationVisible(declaration)) return true;
484    if (!declaration.parent) return false;
485
486    // Variable initializers are visible if variable is visible
487    if (hasInitializer(declaration.parent) && declaration.parent.initializer === declaration) return isDefinitionVisible(checker, declaration.parent as Declaration);
488
489    // Handle some exceptions here like arrow function, members of class and object literal expression which are technically not visible but we want the definition to be determined by its parent
490    switch (declaration.kind) {
491        case SyntaxKind.PropertyDeclaration:
492        case SyntaxKind.GetAccessor:
493        case SyntaxKind.SetAccessor:
494        case SyntaxKind.MethodDeclaration:
495            // Private/protected properties/methods are not visible
496            if (hasEffectiveModifier(declaration, ModifierFlags.Private)) return false;
497        // Public properties/methods are visible if its parents are visible, so:
498        // falls through
499
500        case SyntaxKind.Constructor:
501        case SyntaxKind.PropertyAssignment:
502        case SyntaxKind.ShorthandPropertyAssignment:
503        case SyntaxKind.ObjectLiteralExpression:
504        case SyntaxKind.ClassExpression:
505        case SyntaxKind.ArrowFunction:
506        case SyntaxKind.FunctionExpression:
507            return isDefinitionVisible(checker, declaration.parent as Declaration);
508        default:
509            return false;
510    }
511}
512
513function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration, failedAliasResolution?: boolean): DefinitionInfo {
514    return createDefinitionInfo(decl, typeChecker, decl.symbol, decl, /*unverified*/ false, failedAliasResolution);
515}
516
517/** @internal */
518export function findReferenceInPosition(refs: readonly FileReference[], pos: number): FileReference | undefined {
519    return find(refs, ref => textRangeContainsPositionInclusive(ref, pos));
520}
521
522function getDefinitionInfoForFileReference(name: string, targetFileName: string, unverified: boolean): DefinitionInfo {
523    return {
524        fileName: targetFileName,
525        textSpan: createTextSpanFromBounds(0, 0),
526        kind: ScriptElementKind.scriptElement,
527        name,
528        containerName: undefined!,
529        containerKind: undefined!, // TODO: GH#18217
530        unverified,
531    };
532}
533
534/** Returns a CallLikeExpression where `node` is the target being invoked. */
535function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined {
536    const target = findAncestor(node, n => !isRightSideOfPropertyAccess(n));
537    const callLike = target?.parent;
538    return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target ? callLike : undefined;
539}
540
541function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined {
542    const callLike = getAncestorCallLikeExpression(node);
543    const signature = callLike && typeChecker.getResolvedSignature(callLike);
544    // Don't go to a function type, go to the value having that type.
545    return tryCast(signature && signature.declaration, (d): d is SignatureDeclaration => isFunctionLike(d) && !isFunctionTypeNode(d));
546}
547
548function isConstructorLike(node: Node): boolean {
549    switch (node.kind) {
550        case SyntaxKind.Constructor:
551        case SyntaxKind.ConstructorType:
552        case SyntaxKind.ConstructSignature:
553            return true;
554        default:
555            return false;
556    }
557}
558