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