Skip to content

Commit 6ee4a6b

Browse files
Skip past module.exports = { Foo } in go-to-defintion on 'Foo' (#40835)
* Add test * Skip shorthand property assignments of module.exports in go-to-definition * Skip past shorthand property assignments in module.exports in go-to-definition * Revert WIP change * Fix comment typo Co-authored-by: Nathan Shively-Sanders <[email protected]> Co-authored-by: Nathan Shively-Sanders <[email protected]>
1 parent 736363b commit 6ee4a6b

File tree

5 files changed

+71
-9
lines changed

5 files changed

+71
-9
lines changed

src/harness/fourslashImpl.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -759,7 +759,18 @@ namespace FourSlash {
759759
ts.zipWith(endMarkers, definitions, (endMarker, definition, i) => {
760760
const marker = this.getMarkerByName(endMarker);
761761
if (ts.comparePaths(marker.fileName, definition.fileName, /*ignoreCase*/ true) !== ts.Comparison.EqualTo || marker.position !== definition.textSpan.start) {
762-
this.raiseError(`${testName} failed for definition ${endMarker} (${i}): expected ${marker.fileName} at ${marker.position}, got ${definition.fileName} at ${definition.textSpan.start}`);
762+
const filesToDisplay = ts.deduplicate([marker.fileName, definition.fileName], ts.equateValues);
763+
const markers = [{ text: "EXPECTED", fileName: marker.fileName, position: marker.position }, { text: "ACTUAL", fileName: definition.fileName, position: definition.textSpan.start }];
764+
const text = filesToDisplay.map(fileName => {
765+
const markersToRender = markers.filter(m => m.fileName === fileName).sort((a, b) => b.position - a.position);
766+
let fileContent = this.getFileContent(fileName);
767+
for (const marker of markersToRender) {
768+
fileContent = fileContent.slice(0, marker.position) + `\x1b[1;4m/*${marker.text}*/\x1b[0;31m` + fileContent.slice(marker.position);
769+
}
770+
return `// @Filename: ${fileName}\n${fileContent}`;
771+
}).join("\n\n");
772+
773+
this.raiseError(`${testName} failed for definition ${endMarker} (${i}): expected ${marker.fileName} at ${marker.position}, got ${definition.fileName} at ${definition.textSpan.start}\n\n${text}\n`);
763774
}
764775
});
765776
}
@@ -777,7 +788,7 @@ namespace FourSlash {
777788
const fileContent = this.getFileContent(startFile);
778789
const spanContent = fileContent.slice(defs.textSpan.start, ts.textSpanEnd(defs.textSpan));
779790
const spanContentWithMarker = spanContent.slice(0, marker.position - defs.textSpan.start) + `/*${startMarkerName}*/` + spanContent.slice(marker.position - defs.textSpan.start);
780-
const suggestedFileContent = (fileContent.slice(0, defs.textSpan.start) + `\x1b[1;4m[|${spanContentWithMarker}|]\x1b[31m` + fileContent.slice(ts.textSpanEnd(defs.textSpan)))
791+
const suggestedFileContent = (fileContent.slice(0, defs.textSpan.start) + `\x1b[1;4m[|${spanContentWithMarker}|]\x1b[0;31m` + fileContent.slice(ts.textSpanEnd(defs.textSpan)))
781792
.split(/\r?\n/).map(line => " ".repeat(6) + line).join(ts.sys.newLine);
782793
this.raiseError(`goToDefinitionsAndBoundSpan failed. Found a starting TextSpan around '${spanContent}' in '${startFile}' (at position ${defs.textSpan.start}). `
783794
+ `If this is the correct input span, put a fourslash range around it: \n\n${suggestedFileContent}\n`);

src/services/goToDefinition.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,16 @@ namespace ts.GoToDefinition {
9292
getDefinitionFromSymbol(typeChecker, propertySymbol, node));
9393
}
9494
}
95+
9596
return getDefinitionFromSymbol(typeChecker, symbol, node);
9697
}
9798

99+
function isShorthandPropertyAssignmentOfModuleExports(symbol: Symbol): boolean {
100+
const shorthandProperty = tryCast(symbol.valueDeclaration, isShorthandPropertyAssignment);
101+
const binaryExpression = tryCast(shorthandProperty?.parent.parent, isAssignmentExpression);
102+
return !!binaryExpression && getAssignmentDeclarationKind(binaryExpression) === AssignmentDeclarationKind.ModuleExports;
103+
}
104+
98105
/**
99106
* True if we should not add definitions for both the signature symbol and the definition symbol.
100107
* True for `const |f = |() => 0`, false for `function |f() {} const |g = f;`.
@@ -197,15 +204,29 @@ namespace ts.GoToDefinition {
197204
}
198205

199206
function getSymbol(node: Node, checker: TypeChecker): Symbol | undefined {
200-
const symbol = checker.getSymbolAtLocation(node);
207+
let symbol = checker.getSymbolAtLocation(node);
201208
// If this is an alias, and the request came at the declaration location
202209
// get the aliased symbol instead. This allows for goto def on an import e.g.
203210
// import {A, B} from "mod";
204211
// to jump to the implementation directly.
205-
if (symbol && symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) {
206-
const aliased = checker.getAliasedSymbol(symbol);
207-
if (aliased.declarations) {
208-
return aliased;
212+
while (symbol) {
213+
if (symbol.flags & SymbolFlags.Alias && shouldSkipAlias(node, symbol.declarations[0])) {
214+
const aliased = checker.getAliasedSymbol(symbol);
215+
if (!aliased.declarations) {
216+
break;
217+
}
218+
symbol = aliased;
219+
}
220+
else if (isShorthandPropertyAssignmentOfModuleExports(symbol)) {
221+
// Skip past `module.exports = { Foo }` even though 'Foo' is not a real alias
222+
const shorthandTarget = checker.resolveName(symbol.name, symbol.valueDeclaration, SymbolFlags.Value, /*excludeGlobals*/ false);
223+
if (!some(shorthandTarget?.declarations)) {
224+
break;
225+
}
226+
symbol = shorthandTarget;
227+
}
228+
else {
229+
break;
209230
}
210231
}
211232
return symbol;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @allowJs: true
4+
5+
// @Filename: util.js
6+
//// class /*2*/Util {}
7+
//// module.exports = { Util };
8+
9+
// @Filename: index.js
10+
//// const { Util } = require('./util');
11+
//// new [|Util/*1*/|]()
12+
13+
verify.goToDefinition("1", "2");
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
// @allowJs: true
4+
5+
// @Filename: util.js
6+
//// class /*2*/Util {}
7+
//// module.exports = { Util };
8+
9+
// @Filename: reexport.js
10+
//// const { Util } = require('./util');
11+
//// module.exports = { Util };
12+
13+
// @Filename: index.js
14+
//// const { Util } = require('./reexport');
15+
//// new [|Util/*1*/|]()
16+
17+
verify.goToDefinition("1", "2");

tests/cases/fourslash/goToDefinitionImportedNames11.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// @allowjs: true
33

44
// @Filename: a.js
5-
//// class Class {
5+
//// class /*classDefinition*/Class {
66
//// f;
77
//// }
8-
//// module.exports = { /*classDefinition*/Class };
8+
//// module.exports = { Class };
99

1010
// @Filename: b.js
1111
////const { Class } = require("./a");

0 commit comments

Comments
 (0)