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