Skip to content

Commit aaf1d80

Browse files
author
Andy
authored
Support finding references at module in module.exports = or export in export = (#28221)
* Support finding references at `module` in `module.exports =` or `export` in `export =` * Add json test
1 parent 69c61cd commit aaf1d80

8 files changed

+82
-19
lines changed

src/compiler/checker.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28100,6 +28100,9 @@ namespace ts {
2810028100
case SyntaxKind.ImportType:
2810128101
return isLiteralImportTypeNode(node) ? getSymbolAtLocation(node.argument.literal) : undefined;
2810228102

28103+
case SyntaxKind.ExportKeyword:
28104+
return isExportAssignment(node.parent) ? Debug.assertDefined(node.parent.symbol) : undefined;
28105+
2810328106
default:
2810428107
return undefined;
2810528108
}

src/services/findAllReferences.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,10 @@ namespace ts.FindAllReferences.Core {
368368
return !options.implementations && isStringLiteral(node) ? getReferencesForStringLiteral(node, sourceFiles, cancellationToken) : undefined;
369369
}
370370

371+
if (symbol.escapedName === InternalSymbolName.ExportEquals) {
372+
return getReferencedSymbolsForModule(program, symbol.parent!, /*excludeImportTypeOfExportEquals*/ false, sourceFiles, sourceFilesSet);
373+
}
374+
371375
let moduleReferences: SymbolAndEntries[] = emptyArray;
372376
const moduleSourceFile = isModuleSymbol(symbol);
373377
let referencedNode: Node | undefined = node;
@@ -427,6 +431,22 @@ namespace ts.FindAllReferences.Core {
427431
}
428432
}
429433

434+
const exported = symbol.exports!.get(InternalSymbolName.ExportEquals);
435+
if (exported) {
436+
for (const decl of exported.declarations) {
437+
const sourceFile = decl.getSourceFile();
438+
if (sourceFilesSet.has(sourceFile.fileName)) {
439+
// At `module.exports = ...`, reference node is `module`
440+
const node = isBinaryExpression(decl) && isPropertyAccessExpression(decl.left)
441+
? decl.left.expression
442+
: isExportAssignment(decl)
443+
? Debug.assertDefined(findChildOfKind(decl, SyntaxKind.ExportKeyword, sourceFile))
444+
: getNameOfDeclaration(decl) || decl;
445+
references.push(nodeEntry(node));
446+
}
447+
}
448+
}
449+
430450
return references.length ? [{ definition: { type: DefinitionKind.Symbol, symbol }, references }] : emptyArray;
431451
}
432452

tests/cases/fourslash/findAllRefsExportEquals.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
// @Filename: /a.ts
44
////type [|{| "isWriteAccess": true, "isDefinition": true |}T|] = number;
5-
////export = [|T|];
5+
////[|export|] = [|T|];
66

77
// @Filename: /b.ts
88
////import [|{| "isWriteAccess": true, "isDefinition": true |}T|] = require("[|./a|]");
99

10-
const [r0, r1, r2, r3] = test.ranges();
11-
const mod = { definition: 'module "/a"', ranges: [r3] };
12-
const a = { definition: "type T = number", ranges: [r0, r1] };
13-
const b = { definition: '(alias) type T = number\nimport T = require("./a")', ranges: [r2] };
14-
verify.referenceGroups([r0, r1], [a, b]);
15-
verify.referenceGroups(r2, [b, a]);
16-
verify.referenceGroups(r3, [mod, a, b]);
10+
const [r0, r1, r2, r3, r4] = test.ranges();
11+
const mod = { definition: 'module "/a"', ranges: [r4, r1] };
12+
const a = { definition: "type T = number", ranges: [r0, r2] };
13+
const b = { definition: '(alias) type T = number\nimport T = require("./a")', ranges: [r3] };
14+
verify.referenceGroups([r0, r2], [a, b]);
15+
verify.referenceGroups(r3, [b, a]);
16+
verify.referenceGroups(r4, [mod, a, b]);
17+
verify.referenceGroups(r1, [mod]);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowJs: true
4+
// @checkJs: true
5+
// @resolveJsonModule: true
6+
7+
// @Filename: /a.ts
8+
////import [|{| "isWriteAccess": true, "isDefinition": true |}j|] = require("[|./j.json|]");
9+
////[|j|];
10+
11+
// @Filename: /b.js
12+
////const [|{| "isWriteAccess": true, "isDefinition": true |}j|] = require("[|./j.json|]");
13+
////[|j|];
14+
15+
// @Filename: /j.json
16+
////[|{ "x": 0 }|]
17+
18+
verify.noErrors();
19+
20+
const [r0, r1, r2, r3, r4, r5, r6] = test.ranges();
21+
verify.singleReferenceGroup('import j = require("./j.json")', [r0, r2]);
22+
verify.referenceGroups([r1, r4], [{ definition: 'module "/j"', ranges: [r1, r4, r6] }]);
23+
verify.singleReferenceGroup('const j: {\n "x": number;\n}', [r3, r5]);
24+
verify.referenceGroups(r6, undefined);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowJs: true
4+
5+
// @Filename: /a.js
6+
////const b = require("[|./b|]");
7+
8+
// @Filename: /b.js
9+
////[|module|].exports = 0;
10+
11+
verify.singleReferenceGroup('module "/b"')

tests/cases/fourslash/findAllRefs_importType_exportEquals.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,21 @@
55
////namespace [|{| "isWriteAccess": true, "isDefinition": true |}T|] {
66
//// export type U = string;
77
////}
8-
////export = [|T|];
8+
////[|export|] = [|T|];
99

1010
// @Filename: /b.ts
1111
////const x: import("[|./[|a|]|]") = 0;
1212
////const y: import("[|./[|a|]|]").U = "";
1313

1414
verify.noErrors();
1515

16-
const [r0, r1, r2, r3, r3b, r4, r4b] = test.ranges();
16+
const [r0, r1, rExport, r2, r3, r3b, r4, r4b] = test.ranges();
1717
verify.referenceGroups(r0, [{ definition: "type T = number\nnamespace T", ranges: [r0, r2, r3] }]);
1818
verify.referenceGroups(r1, [{ definition: "namespace T", ranges: [r1, r2] }]);
19-
verify.referenceGroups(r2, [{ definition: "type T = number\nnamespace T", ranges: [r0, r1, r2, r3] }]);
20-
verify.referenceGroups([r3, r4], [
21-
{ definition: 'module "/a"', ranges: [r4] },
22-
{ definition: "type T = number\nnamespace T", ranges: [r0, r1, r2, r3] },
23-
]);
19+
const t: FourSlashInterface.ReferenceGroup = { definition: "type T = number\nnamespace T", ranges: [r0, r1, r2, r3] };
20+
verify.referenceGroups(r2, [t]);
21+
verify.referenceGroups([r3, r4], [{ definition: 'module "/a"', ranges: [r4, rExport] }, t]);
22+
verify.referenceGroups(rExport, [{ definition: 'module "/a"', ranges: [r3, r4, rExport] }]);
2423

2524
verify.renameLocations(r0, [r0, r2]);
2625
verify.renameLocations(r1, [r1, r2]);

tests/cases/fourslash/findAllRefs_importType_js.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// @checkJs: true
55

66
// @Filename: /a.js
7-
////module.exports = class [|{| "isWriteAccess": true, "isDefinition": true |}C|] {};
7+
////[|module|].exports = class [|{| "isWriteAccess": true, "isDefinition": true |}C|] {};
88
////module.exports.[|{| "isWriteAccess": true, "isDefinition": true |}D|] = class [|{| "isWriteAccess": true, "isDefinition": true |}D|] {};
99

1010
// @Filename: /b.js
@@ -17,7 +17,8 @@ verify.noErrors();
1717

1818
// TODO: GH#24025
1919

20-
const [r0, r1, r2, r3, r4, r5] = test.ranges();
20+
const [rModule, r0, r1, r2, r3, r4, r5] = test.ranges();
21+
verify.referenceGroups(rModule, [{ definition: 'module "/a"', ranges: [r3, r4, rModule] }]);
2122
verify.referenceGroups(r0, [
2223
{ definition: "(local class) C", ranges: [r0] },
2324
// TODO: This definition is really ugly
@@ -31,7 +32,7 @@ verify.referenceGroups(r2, [
3132
{ definition: "class D\n(property) D: typeof D", ranges: [r5] },
3233
]);
3334
verify.referenceGroups([r3, r4], [
34-
{ definition: 'module "/a"', ranges: [r4] },
35+
{ definition: 'module "/a"', ranges: [r4, rModule] },
3536
{ definition: "(local class) C", ranges: [r0] },
3637
{ definition: "(alias) (local class) export=\nimport export=", ranges: [r3] },
3738
]);

tests/cases/fourslash/fourslash.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ declare namespace FourSlashInterface {
231231
* For each of starts, asserts the ranges that are referenced from there.
232232
* This uses the 'findReferences' command instead of 'getReferencesAtPosition', so references are grouped by their definition.
233233
*/
234-
referenceGroups(starts: ArrayOrSingle<string> | ArrayOrSingle<Range>, parts: Array<{ definition: ReferencesDefinition, ranges: Range[] }>): void;
234+
referenceGroups(starts: ArrayOrSingle<string> | ArrayOrSingle<Range>, parts: ReadonlyArray<ReferenceGroup>): void;
235235
singleReferenceGroup(definition: ReferencesDefinition, ranges?: Range[]): void;
236236
rangesAreOccurrences(isWriteAccess?: boolean): void;
237237
rangesWithSameTextAreRenameLocations(): void;
@@ -487,6 +487,10 @@ declare namespace FourSlashInterface {
487487
};
488488
}
489489

490+
interface ReferenceGroup {
491+
readonly definition: ReferencesDefinition;
492+
readonly ranges: ReadonlyArray<Range>;
493+
}
490494
type ReferencesDefinition = string | {
491495
text: string;
492496
range: Range;

0 commit comments

Comments
 (0)