|
| 1 | +/* @internal */ |
| 2 | +namespace ts.GoToDefinition { |
| 3 | + export function getDefinitionAtPosition(program: Program, sourceFile: SourceFile, position: number): DefinitionInfo[] { |
| 4 | + /// Triple slash reference comments |
| 5 | + const comment = findReferenceInPosition(sourceFile.referencedFiles, position); |
| 6 | + if (comment) { |
| 7 | + const referenceFile = tryResolveScriptReference(program, sourceFile, comment); |
| 8 | + if (referenceFile) { |
| 9 | + return [getDefinitionInfoForFileReference(comment.fileName, referenceFile.fileName)]; |
| 10 | + } |
| 11 | + return undefined; |
| 12 | + } |
| 13 | + |
| 14 | + // Type reference directives |
| 15 | + const typeReferenceDirective = findReferenceInPosition(sourceFile.typeReferenceDirectives, position); |
| 16 | + if (typeReferenceDirective) { |
| 17 | + const referenceFile = program.getResolvedTypeReferenceDirectives()[typeReferenceDirective.fileName]; |
| 18 | + if (referenceFile && referenceFile.resolvedFileName) { |
| 19 | + return [getDefinitionInfoForFileReference(typeReferenceDirective.fileName, referenceFile.resolvedFileName)]; |
| 20 | + } |
| 21 | + return undefined; |
| 22 | + } |
| 23 | + |
| 24 | + const node = getTouchingPropertyName(sourceFile, position); |
| 25 | + if (node === sourceFile) { |
| 26 | + return undefined; |
| 27 | + } |
| 28 | + |
| 29 | + // Labels |
| 30 | + if (isJumpStatementTarget(node)) { |
| 31 | + const labelName = (<Identifier>node).text; |
| 32 | + const label = getTargetLabel((<BreakOrContinueStatement>node.parent), (<Identifier>node).text); |
| 33 | + return label ? [createDefinitionInfo(label, ScriptElementKind.label, labelName, /*containerName*/ undefined)] : undefined; |
| 34 | + } |
| 35 | + |
| 36 | + const typeChecker = program.getTypeChecker(); |
| 37 | + |
| 38 | + const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node); |
| 39 | + if (calledDeclaration) { |
| 40 | + return [createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration)]; |
| 41 | + } |
| 42 | + |
| 43 | + let symbol = typeChecker.getSymbolAtLocation(node); |
| 44 | + |
| 45 | + // Could not find a symbol e.g. node is string or number keyword, |
| 46 | + // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol |
| 47 | + if (!symbol) { |
| 48 | + return undefined; |
| 49 | + } |
| 50 | + |
| 51 | + // If this is an alias, and the request came at the declaration location |
| 52 | + // get the aliased symbol instead. This allows for goto def on an import e.g. |
| 53 | + // import {A, B} from "mod"; |
| 54 | + // to jump to the implementation directly. |
| 55 | + if (symbol.flags & SymbolFlags.Alias) { |
| 56 | + const declaration = symbol.declarations[0]; |
| 57 | + |
| 58 | + // Go to the original declaration for cases: |
| 59 | + // |
| 60 | + // (1) when the aliased symbol was declared in the location(parent). |
| 61 | + // (2) when the aliased symbol is originating from a named import. |
| 62 | + // |
| 63 | + if (node.kind === SyntaxKind.Identifier && |
| 64 | + (node.parent === declaration || |
| 65 | + (declaration.kind === SyntaxKind.ImportSpecifier && declaration.parent && declaration.parent.kind === SyntaxKind.NamedImports))) { |
| 66 | + |
| 67 | + symbol = typeChecker.getAliasedSymbol(symbol); |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + // Because name in short-hand property assignment has two different meanings: property name and property value, |
| 72 | + // using go-to-definition at such position should go to the variable declaration of the property value rather than |
| 73 | + // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition |
| 74 | + // is performed at the location of property access, we would like to go to definition of the property in the short-hand |
| 75 | + // assignment. This case and others are handled by the following code. |
| 76 | + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { |
| 77 | + const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration); |
| 78 | + if (!shorthandSymbol) { |
| 79 | + return []; |
| 80 | + } |
| 81 | + |
| 82 | + const shorthandDeclarations = shorthandSymbol.getDeclarations(); |
| 83 | + const shorthandSymbolKind = SymbolDisplay.getSymbolKind(typeChecker, shorthandSymbol, node); |
| 84 | + const shorthandSymbolName = typeChecker.symbolToString(shorthandSymbol); |
| 85 | + const shorthandContainerName = typeChecker.symbolToString(symbol.parent, node); |
| 86 | + return map(shorthandDeclarations, |
| 87 | + declaration => createDefinitionInfo(declaration, shorthandSymbolKind, shorthandSymbolName, shorthandContainerName)); |
| 88 | + } |
| 89 | + |
| 90 | + return getDefinitionFromSymbol(typeChecker, symbol, node); |
| 91 | + } |
| 92 | + |
| 93 | + /// Goto type |
| 94 | + export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): DefinitionInfo[] { |
| 95 | + const node = getTouchingPropertyName(sourceFile, position); |
| 96 | + if (node === sourceFile) { |
| 97 | + return undefined; |
| 98 | + } |
| 99 | + |
| 100 | + const symbol = typeChecker.getSymbolAtLocation(node); |
| 101 | + if (!symbol) { |
| 102 | + return undefined; |
| 103 | + } |
| 104 | + |
| 105 | + const type = typeChecker.getTypeOfSymbolAtLocation(symbol, node); |
| 106 | + if (!type) { |
| 107 | + return undefined; |
| 108 | + } |
| 109 | + |
| 110 | + if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Enum)) { |
| 111 | + const result: DefinitionInfo[] = []; |
| 112 | + forEach((<UnionType>type).types, t => { |
| 113 | + if (t.symbol) { |
| 114 | + addRange(/*to*/ result, /*from*/ getDefinitionFromSymbol(typeChecker, t.symbol, node)); |
| 115 | + } |
| 116 | + }); |
| 117 | + return result; |
| 118 | + } |
| 119 | + |
| 120 | + if (!type.symbol) { |
| 121 | + return undefined; |
| 122 | + } |
| 123 | + |
| 124 | + return getDefinitionFromSymbol(typeChecker, type.symbol, node); |
| 125 | + } |
| 126 | + |
| 127 | + function getDefinitionFromSymbol(typeChecker: TypeChecker, symbol: Symbol, node: Node): DefinitionInfo[] { |
| 128 | + const result: DefinitionInfo[] = []; |
| 129 | + const declarations = symbol.getDeclarations(); |
| 130 | + const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, symbol, node); |
| 131 | + |
| 132 | + if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) && |
| 133 | + !tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) { |
| 134 | + // Just add all the declarations. |
| 135 | + forEach(declarations, declaration => { |
| 136 | + result.push(createDefinitionInfo(declaration, symbolKind, symbolName, containerName)); |
| 137 | + }); |
| 138 | + } |
| 139 | + |
| 140 | + return result; |
| 141 | + |
| 142 | + function tryAddConstructSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) { |
| 143 | + // Applicable only if we are in a new expression, or we are on a constructor declaration |
| 144 | + // and in either case the symbol has a construct signature definition, i.e. class |
| 145 | + if (isNewExpressionTarget(location) || location.kind === SyntaxKind.ConstructorKeyword) { |
| 146 | + if (symbol.flags & SymbolFlags.Class) { |
| 147 | + // Find the first class-like declaration and try to get the construct signature. |
| 148 | + for (const declaration of symbol.getDeclarations()) { |
| 149 | + if (isClassLike(declaration)) { |
| 150 | + return tryAddSignature(declaration.members, |
| 151 | + /*selectConstructors*/ true, |
| 152 | + symbolKind, |
| 153 | + symbolName, |
| 154 | + containerName, |
| 155 | + result); |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + Debug.fail("Expected declaration to have at least one class-like declaration"); |
| 160 | + } |
| 161 | + } |
| 162 | + return false; |
| 163 | + } |
| 164 | + |
| 165 | + function tryAddCallSignature(symbol: Symbol, location: Node, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) { |
| 166 | + if (isCallExpressionTarget(location) || isNewExpressionTarget(location) || isNameOfFunctionDeclaration(location)) { |
| 167 | + return tryAddSignature(symbol.declarations, /*selectConstructors*/ false, symbolKind, symbolName, containerName, result); |
| 168 | + } |
| 169 | + return false; |
| 170 | + } |
| 171 | + |
| 172 | + function tryAddSignature(signatureDeclarations: Declaration[], selectConstructors: boolean, symbolKind: string, symbolName: string, containerName: string, result: DefinitionInfo[]) { |
| 173 | + const declarations: Declaration[] = []; |
| 174 | + let definition: Declaration; |
| 175 | + |
| 176 | + forEach(signatureDeclarations, d => { |
| 177 | + if ((selectConstructors && d.kind === SyntaxKind.Constructor) || |
| 178 | + (!selectConstructors && (d.kind === SyntaxKind.FunctionDeclaration || d.kind === SyntaxKind.MethodDeclaration || d.kind === SyntaxKind.MethodSignature))) { |
| 179 | + declarations.push(d); |
| 180 | + if ((<FunctionLikeDeclaration>d).body) definition = d; |
| 181 | + } |
| 182 | + }); |
| 183 | + |
| 184 | + if (definition) { |
| 185 | + result.push(createDefinitionInfo(definition, symbolKind, symbolName, containerName)); |
| 186 | + return true; |
| 187 | + } |
| 188 | + else if (declarations.length) { |
| 189 | + result.push(createDefinitionInfo(lastOrUndefined(declarations), symbolKind, symbolName, containerName)); |
| 190 | + return true; |
| 191 | + } |
| 192 | + |
| 193 | + return false; |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + function createDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo { |
| 198 | + return { |
| 199 | + fileName: node.getSourceFile().fileName, |
| 200 | + textSpan: createTextSpanFromBounds(node.getStart(), node.getEnd()), |
| 201 | + kind: symbolKind, |
| 202 | + name: symbolName, |
| 203 | + containerKind: undefined, |
| 204 | + containerName |
| 205 | + }; |
| 206 | + } |
| 207 | + |
| 208 | + function getSymbolInfo(typeChecker: TypeChecker, symbol: Symbol, node: Node) { |
| 209 | + return { |
| 210 | + symbolName: typeChecker.symbolToString(symbol), // Do not get scoped name, just the name of the symbol |
| 211 | + symbolKind: SymbolDisplay.getSymbolKind(typeChecker, symbol, node), |
| 212 | + containerName: symbol.parent ? typeChecker.symbolToString(symbol.parent, node) : "" |
| 213 | + }; |
| 214 | + } |
| 215 | + |
| 216 | + function createDefinitionFromSignatureDeclaration(typeChecker: TypeChecker, decl: SignatureDeclaration): DefinitionInfo { |
| 217 | + const { symbolName, symbolKind, containerName } = getSymbolInfo(typeChecker, decl.symbol, decl); |
| 218 | + return createDefinitionInfo(decl, symbolKind, symbolName, containerName); |
| 219 | + } |
| 220 | + |
| 221 | + function findReferenceInPosition(refs: FileReference[], pos: number): FileReference { |
| 222 | + for (const ref of refs) { |
| 223 | + if (ref.pos <= pos && pos < ref.end) { |
| 224 | + return ref; |
| 225 | + } |
| 226 | + } |
| 227 | + return undefined; |
| 228 | + } |
| 229 | + |
| 230 | + function getDefinitionInfoForFileReference(name: string, targetFileName: string): DefinitionInfo { |
| 231 | + return { |
| 232 | + fileName: targetFileName, |
| 233 | + textSpan: createTextSpanFromBounds(0, 0), |
| 234 | + kind: ScriptElementKind.scriptElement, |
| 235 | + name: name, |
| 236 | + containerName: undefined, |
| 237 | + containerKind: undefined |
| 238 | + }; |
| 239 | + } |
| 240 | + |
| 241 | + /** Returns a CallLikeExpression where `node` is the target being invoked. */ |
| 242 | + function getAncestorCallLikeExpression(node: Node): CallLikeExpression | undefined { |
| 243 | + const target = climbPastManyPropertyAccesses(node); |
| 244 | + const callLike = target.parent; |
| 245 | + return callLike && isCallLikeExpression(callLike) && getInvokedExpression(callLike) === target && callLike; |
| 246 | + } |
| 247 | + |
| 248 | + function climbPastManyPropertyAccesses(node: Node): Node { |
| 249 | + return isRightSideOfPropertyAccess(node) ? climbPastManyPropertyAccesses(node.parent) : node; |
| 250 | + } |
| 251 | + |
| 252 | + function tryGetSignatureDeclaration(typeChecker: TypeChecker, node: Node): SignatureDeclaration | undefined { |
| 253 | + const callLike = getAncestorCallLikeExpression(node); |
| 254 | + return callLike && typeChecker.getResolvedSignature(callLike).declaration; |
| 255 | + } |
| 256 | +} |
0 commit comments