Skip to content

Commit 8bd7ce6

Browse files
authored
Find Source Definition (#48264)
* Prototype resolving to JS when go-to-def aliases all resolve to ambient declarations * Add test infrastructure * Start fleshing out test coverage * Fix some go-to-def stuff * Finish lodash test case * Make go-to-implementation never return ambient results * Build new functionality into go-to-implementation * Update baselines * Two more test cases * Refine definition searches for unresolved imports * Revert "Build new functionality into go-to-implementation" This reverts commit 381799d. * Fix tests * Revert go-to-implementation changes * Wow a bunch of code was unnecessary * Update baselines and go-to-def test * Fix navigation on symbols that are not aliases but resolve through aliases in chain * Temporarily replace go-to-def with new command implementation * Revert "Temporarily replace go-to-def with new command implementation" This reverts commit 34c6cfd. * Revert "Wow a bunch of code was unnecessary" This reverts commit 1cb2ba6. * Bring back some deleted code needed for a new test case * Clean up a little * Rename more stuff * Update test * Update API baseline * Temporarily replace go-to-def with new command implementation * PR review fixes * Fix getTopMostDeclarationNamesInFile * Rename local * Use hash set * Remove option from commandLineParser * Keep noDtsResolution project around * Handle AuxiliaryProject kind in ScriptInfo getDefaultProject etc. * Do not run updateGraph in the background for AuxiliaryProject * Don’t create auxiliary project outside of semantic mode * No-op on scheduled invalidation * Add comments to unit test * Sync compiler options to auxiliary project * Fix case sensitivity * Update extensionIsOk with new file extensions * PR feedback * Update API baseline * Mark scheduleInvalidateResolutionsOfFailedLookupLocations internal * Use same heuristics on property accesses of loosely-resolvable aliases as unresolvable named imports * Rename command, and no need to return the bound span * Update API baseline
1 parent e6dcf6f commit 8bd7ce6

36 files changed

+1056
-104
lines changed

Diff for: src/compiler/checker.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9206,7 +9206,7 @@ namespace ts {
92069206
}
92079207
}
92089208
const sourceTypes = some(constructorTypes, t => !!(t.flags & ~TypeFlags.Nullable)) ? constructorTypes : types; // TODO: GH#18217
9209-
type = getUnionType(sourceTypes!, UnionReduction.Subtype);
9209+
type = getUnionType(sourceTypes!);
92109210
}
92119211
}
92129212
const widened = getWidenedType(addOptionality(type, /*isProperty*/ false, definedInMethod && !definedInConstructor));

Diff for: src/compiler/moduleNameResolver.ts

+33-10
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ namespace ts {
6969
JavaScript, /** '.js' or '.jsx' */
7070
Json, /** '.json' */
7171
TSConfig, /** '.json' with `tsconfig` used instead of `index` */
72-
DtsOnly /** Only '.d.ts' */
72+
DtsOnly, /** Only '.d.ts' */
73+
TsOnly, /** '.[cm]tsx?' but not .d.ts variants */
7374
}
7475

7576
interface PathAndPackageId {
@@ -1290,7 +1291,19 @@ namespace ts {
12901291
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations;
12911292
/* @internal */ export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations; // eslint-disable-line @typescript-eslint/unified-signatures
12921293
export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference, lookupConfig?: boolean): ResolvedModuleWithFailedLookupLocations {
1293-
return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, lookupConfig ? tsconfigExtensions : (compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions), redirectedReference);
1294+
let extensions;
1295+
if (lookupConfig) {
1296+
extensions = tsconfigExtensions;
1297+
}
1298+
else if (compilerOptions.noDtsResolution) {
1299+
extensions = [Extensions.TsOnly];
1300+
if (compilerOptions.allowJs) extensions.push(Extensions.JavaScript);
1301+
if (compilerOptions.resolveJsonModule) extensions.push(Extensions.Json);
1302+
}
1303+
else {
1304+
extensions = compilerOptions.resolveJsonModule ? tsPlusJsonExtensions : tsExtensions;
1305+
}
1306+
return nodeModuleNameResolverWorker(NodeResolutionFeatures.None, moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, extensions, redirectedReference);
12941307
}
12951308

12961309
function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, extensions: Extensions[], redirectedReference: ResolvedProjectReference | undefined): ResolvedModuleWithFailedLookupLocations {
@@ -1299,14 +1312,19 @@ namespace ts {
12991312
const failedLookupLocations: string[] = [];
13001313
// conditions are only used by the node12/nodenext resolver - there's no priority order in the list,
13011314
//it's essentially a set (priority is determined by object insertion order in the object we look at).
1315+
const conditions = features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"];
1316+
if (compilerOptions.noDtsResolution) {
1317+
conditions.pop();
1318+
}
1319+
13021320
const state: ModuleResolutionState = {
13031321
compilerOptions,
13041322
host,
13051323
traceEnabled,
13061324
failedLookupLocations,
13071325
packageJsonInfoCache: cache,
13081326
features,
1309-
conditions: features & NodeResolutionFeatures.EsmMode ? ["node", "import", "types"] : ["node", "require", "types"]
1327+
conditions,
13101328
};
13111329

13121330
const result = forEach(extensions, ext => tryResolve(ext));
@@ -1533,20 +1551,22 @@ namespace ts {
15331551
default: return tryExtension(Extension.Dts);
15341552
}
15351553
case Extensions.TypeScript:
1554+
case Extensions.TsOnly:
1555+
const useDts = extensions === Extensions.TypeScript;
15361556
switch (originalExtension) {
15371557
case Extension.Mjs:
15381558
case Extension.Mts:
15391559
case Extension.Dmts:
1540-
return tryExtension(Extension.Mts) || tryExtension(Extension.Dmts);
1560+
return tryExtension(Extension.Mts) || (useDts ? tryExtension(Extension.Dmts) : undefined);
15411561
case Extension.Cjs:
15421562
case Extension.Cts:
15431563
case Extension.Dcts:
1544-
return tryExtension(Extension.Cts) || tryExtension(Extension.Dcts);
1564+
return tryExtension(Extension.Cts) || (useDts ? tryExtension(Extension.Dcts) : undefined);
15451565
case Extension.Json:
15461566
candidate += Extension.Json;
1547-
return tryExtension(Extension.Dts);
1567+
return useDts ? tryExtension(Extension.Dts) : undefined;
15481568
default:
1549-
return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts);
1569+
return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || (useDts ? tryExtension(Extension.Dts) : undefined);
15501570
}
15511571
case Extensions.JavaScript:
15521572
switch (originalExtension) {
@@ -1813,6 +1833,7 @@ namespace ts {
18131833
switch (extensions) {
18141834
case Extensions.JavaScript:
18151835
case Extensions.Json:
1836+
case Extensions.TsOnly:
18161837
packageFile = readPackageJsonMainField(jsonContent, candidate, state);
18171838
break;
18181839
case Extensions.TypeScript:
@@ -1893,14 +1914,16 @@ namespace ts {
18931914
function extensionIsOk(extensions: Extensions, extension: Extension): boolean {
18941915
switch (extensions) {
18951916
case Extensions.JavaScript:
1896-
return extension === Extension.Js || extension === Extension.Jsx;
1917+
return extension === Extension.Js || extension === Extension.Jsx || extension === Extension.Mjs || extension === Extension.Cjs;
18971918
case Extensions.TSConfig:
18981919
case Extensions.Json:
18991920
return extension === Extension.Json;
19001921
case Extensions.TypeScript:
1901-
return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Dts;
1922+
return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts || extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts;
1923+
case Extensions.TsOnly:
1924+
return extension === Extension.Ts || extension === Extension.Tsx || extension === Extension.Mts || extension === Extension.Cts;
19021925
case Extensions.DtsOnly:
1903-
return extension === Extension.Dts;
1926+
return extension === Extension.Dts || extension === Extension.Dmts || extension === Extension.Dcts;
19041927
}
19051928
}
19061929

Diff for: src/compiler/types.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -4668,8 +4668,10 @@ namespace ts {
46684668
export type AnyImportOrRequire = AnyImportSyntax | VariableDeclarationInitializedTo<RequireOrImportCall>;
46694669

46704670
/* @internal */
4671-
export type AnyImportOrRequireStatement = AnyImportSyntax | RequireVariableStatement;
4671+
export type AnyImportOrBareOrAccessedRequire = AnyImportSyntax | VariableDeclarationInitializedTo<RequireOrImportCall | AccessExpression>;
46724672

4673+
/* @internal */
4674+
export type AnyImportOrRequireStatement = AnyImportSyntax | RequireVariableStatement;
46734675

46744676
/* @internal */
46754677
export type AnyImportOrReExport = AnyImportSyntax | ExportDeclaration;
@@ -6178,6 +6180,8 @@ namespace ts {
61786180
assumeChangesOnlyAffectDirectDependencies?: boolean;
61796181
noLib?: boolean;
61806182
noResolve?: boolean;
6183+
/*@internal*/
6184+
noDtsResolution?: boolean;
61816185
noUncheckedIndexedAccess?: boolean;
61826186
out?: string;
61836187
outDir?: string;

Diff for: src/compiler/utilities.ts

+70-11
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,10 @@ namespace ts {
933933
}
934934
}
935935

936+
export function isAnyImportOrBareOrAccessedRequire(node: Node): node is AnyImportOrBareOrAccessedRequire {
937+
return isAnyImportSyntax(node) || isVariableDeclarationInitializedToBareOrAccessedRequire(node);
938+
}
939+
936940
export function isLateVisibilityPaintedStatement(node: Node): node is LateVisibilityPaintedStatement {
937941
switch (node.kind) {
938942
case SyntaxKind.ImportDeclaration:
@@ -2558,14 +2562,14 @@ namespace ts {
25582562
return decl.kind === SyntaxKind.FunctionDeclaration || isVariableDeclaration(decl) && decl.initializer && isFunctionLike(decl.initializer);
25592563
}
25602564

2561-
export function tryGetModuleSpecifierFromDeclaration(node: AnyImportOrRequire): string | undefined {
2565+
export function tryGetModuleSpecifierFromDeclaration(node: AnyImportOrBareOrAccessedRequire): StringLiteralLike | undefined {
25622566
switch (node.kind) {
25632567
case SyntaxKind.VariableDeclaration:
2564-
return node.initializer.arguments[0].text;
2568+
return findAncestor(node.initializer, (node): node is RequireOrImportCall => isRequireCall(node, /*requireStringLiteralLikeArgument*/ true))?.arguments[0];
25652569
case SyntaxKind.ImportDeclaration:
2566-
return tryCast(node.moduleSpecifier, isStringLiteralLike)?.text;
2570+
return tryCast(node.moduleSpecifier, isStringLiteralLike);
25672571
case SyntaxKind.ImportEqualsDeclaration:
2568-
return tryCast(tryCast(node.moduleReference, isExternalModuleReference)?.expression, isStringLiteralLike)?.text;
2572+
return tryCast(tryCast(node.moduleReference, isExternalModuleReference)?.expression, isStringLiteralLike);
25692573
default:
25702574
Debug.assertNever(node);
25712575
}
@@ -3131,21 +3135,31 @@ namespace ts {
31313135
// export = <EntityNameExpression>
31323136
// export default <EntityNameExpression>
31333137
// module.exports = <EntityNameExpression>
3134-
// {<Identifier>}
3135-
// {name: <EntityNameExpression>}
3138+
// module.exports.x = <EntityNameExpression>
3139+
// const x = require("...")
3140+
// const { x } = require("...")
3141+
// const x = require("...").y
3142+
// const { x } = require("...").y
31363143
export function isAliasSymbolDeclaration(node: Node): boolean {
3137-
return node.kind === SyntaxKind.ImportEqualsDeclaration ||
3144+
if (node.kind === SyntaxKind.ImportEqualsDeclaration ||
31383145
node.kind === SyntaxKind.NamespaceExportDeclaration ||
31393146
node.kind === SyntaxKind.ImportClause && !!(node as ImportClause).name ||
31403147
node.kind === SyntaxKind.NamespaceImport ||
31413148
node.kind === SyntaxKind.NamespaceExport ||
31423149
node.kind === SyntaxKind.ImportSpecifier ||
31433150
node.kind === SyntaxKind.ExportSpecifier ||
3144-
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node as ExportAssignment) ||
3151+
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(node as ExportAssignment)
3152+
) {
3153+
return true;
3154+
}
3155+
3156+
return isInJSFile(node) && (
31453157
isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node) ||
3146-
isPropertyAccessExpression(node) && isBinaryExpression(node.parent) && node.parent.left === node && node.parent.operatorToken.kind === SyntaxKind.EqualsToken && isAliasableExpression(node.parent.right) ||
3147-
node.kind === SyntaxKind.ShorthandPropertyAssignment ||
3148-
node.kind === SyntaxKind.PropertyAssignment && isAliasableExpression((node as PropertyAssignment).initializer);
3158+
isPropertyAccessExpression(node)
3159+
&& isBinaryExpression(node.parent)
3160+
&& node.parent.left === node
3161+
&& node.parent.operatorToken.kind === SyntaxKind.EqualsToken
3162+
&& isAliasableExpression(node.parent.right));
31493163
}
31503164

31513165
export function getAliasDeclarationFromName(node: EntityName): Declaration | undefined {
@@ -3156,6 +3170,7 @@ namespace ts {
31563170
case SyntaxKind.ExportSpecifier:
31573171
case SyntaxKind.ExportAssignment:
31583172
case SyntaxKind.ImportEqualsDeclaration:
3173+
case SyntaxKind.NamespaceExport:
31593174
return node.parent as Declaration;
31603175
case SyntaxKind.QualifiedName:
31613176
do {
@@ -5142,6 +5157,11 @@ namespace ts {
51425157
(node.parent.kind === SyntaxKind.PropertyAccessExpression && (node.parent as PropertyAccessExpression).name === node);
51435158
}
51445159

5160+
export function isRightSideOfAccessExpression(node: Node) {
5161+
return isPropertyAccessExpression(node.parent) && node.parent.name === node
5162+
|| isElementAccessExpression(node.parent) && node.parent.argumentExpression === node;
5163+
}
5164+
51455165
export function isRightSideOfQualifiedNameOrPropertyAccessOrJSDocMemberName(node: Node) {
51465166
return isQualifiedName(node.parent) && node.parent.right === node
51475167
|| isPropertyAccessExpression(node.parent) && node.parent.name === node
@@ -5829,6 +5849,45 @@ namespace ts {
58295849
return expr;
58305850
}
58315851

5852+
export function forEachNameInAccessChainWalkingLeft<T>(name: MemberName | StringLiteralLike, action: (name: MemberName | StringLiteralLike) => T | undefined): T | undefined {
5853+
if (isAccessExpression(name.parent) && isRightSideOfAccessExpression(name)) {
5854+
return walkAccessExpression(name.parent);
5855+
}
5856+
5857+
function walkAccessExpression(access: AccessExpression): T | undefined {
5858+
if (access.kind === SyntaxKind.PropertyAccessExpression) {
5859+
const res = action(access.name);
5860+
if (res !== undefined) {
5861+
return res;
5862+
}
5863+
}
5864+
else if (access.kind === SyntaxKind.ElementAccessExpression) {
5865+
if (isIdentifier(access.argumentExpression) || isStringLiteralLike(access.argumentExpression)) {
5866+
const res = action(access.argumentExpression);
5867+
if (res !== undefined) {
5868+
return res;
5869+
}
5870+
}
5871+
else {
5872+
// Chain interrupted by non-static-name access 'x[expr()].y.z'
5873+
return undefined;
5874+
}
5875+
}
5876+
5877+
if (isAccessExpression(access.expression)) {
5878+
return walkAccessExpression(access.expression);
5879+
}
5880+
if (isIdentifier(access.expression)) {
5881+
// End of chain at Identifier 'x.y.z'
5882+
return action(access.expression);
5883+
}
5884+
// End of chain at non-Identifier 'x().y.z'
5885+
return undefined;
5886+
}
5887+
}
5888+
5889+
5890+
58325891
export function getLeftmostExpression(node: Expression, stopAtCallExpressions: boolean) {
58335892
while (true) {
58345893
switch (node.kind) {

Diff for: src/harness/client.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ namespace ts.server {
298298
getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan {
299299
const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position);
300300

301-
const request = this.processRequest<protocol.DefinitionRequest>(CommandNames.DefinitionAndBoundSpan, args);
301+
const request = this.processRequest<protocol.DefinitionAndBoundSpanRequest>(CommandNames.DefinitionAndBoundSpan, args);
302302
const response = this.processResponse<protocol.DefinitionInfoAndBoundSpanResponse>(request);
303303
const body = Debug.checkDefined(response.body); // TODO: GH#18217
304304

@@ -332,6 +332,23 @@ namespace ts.server {
332332
}));
333333
}
334334

335+
getSourceDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfo[] {
336+
const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position);
337+
const request = this.processRequest<protocol.FindSourceDefinitionRequest>(CommandNames.FindSourceDefinition, args);
338+
const response = this.processResponse<protocol.DefinitionResponse>(request);
339+
const body = Debug.checkDefined(response.body); // TODO: GH#18217
340+
341+
return body.map(entry => ({
342+
containerKind: ScriptElementKind.unknown,
343+
containerName: "",
344+
fileName: entry.file,
345+
textSpan: this.decodeSpan(entry),
346+
kind: ScriptElementKind.unknown,
347+
name: "",
348+
unverified: entry.unverified,
349+
}));
350+
}
351+
335352
getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] {
336353
const args = this.createFileLocationRequestArgs(fileName, position);
337354

0 commit comments

Comments
 (0)