mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-06 02:33:53 -06:00
Support find-all-references for default keyword (#17992)
* Support find-all-references for anonymous default exports * Also handle re-exported default exports * Add test for using `export =` with `--allowSyntheticDefaultExports`
This commit is contained in:
parent
817c329667
commit
b3c87aa919
@ -22987,6 +22987,9 @@ namespace ts {
|
||||
: undefined;
|
||||
return objectType && getPropertyOfType(objectType, escapeLeadingUnderscores((node as StringLiteral | NumericLiteral).text));
|
||||
|
||||
case SyntaxKind.DefaultKeyword:
|
||||
return getSymbolOfNode(node.parent);
|
||||
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -176,7 +176,9 @@ namespace ts.FindAllReferences {
|
||||
fileName: node.getSourceFile().fileName,
|
||||
textSpan: getTextSpan(node),
|
||||
isWriteAccess: isWriteAccess(node),
|
||||
isDefinition: isAnyDeclarationName(node) || isLiteralComputedPropertyDeclarationName(node),
|
||||
isDefinition: node.kind === SyntaxKind.DefaultKeyword
|
||||
|| isAnyDeclarationName(node)
|
||||
|| isLiteralComputedPropertyDeclarationName(node),
|
||||
isInString
|
||||
};
|
||||
}
|
||||
@ -243,7 +245,7 @@ namespace ts.FindAllReferences {
|
||||
|
||||
/** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */
|
||||
function isWriteAccess(node: Node): boolean {
|
||||
if (isAnyDeclarationName(node)) {
|
||||
if (node.kind === SyntaxKind.DefaultKeyword || isAnyDeclarationName(node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -743,7 +745,7 @@ namespace ts.FindAllReferences.Core {
|
||||
|
||||
function isValidReferencePosition(node: Node, searchSymbolName: string): boolean {
|
||||
// Compare the length so we filter out strict superstrings of the symbol we are looking for
|
||||
switch (node && node.kind) {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.Identifier:
|
||||
return (node as Identifier).text.length === searchSymbolName.length;
|
||||
|
||||
@ -754,6 +756,9 @@ namespace ts.FindAllReferences.Core {
|
||||
case SyntaxKind.NumericLiteral:
|
||||
return isLiteralNameOfPropertyDeclarationOrIndexAccess(node as NumericLiteral) && (node as NumericLiteral).text.length === searchSymbolName.length;
|
||||
|
||||
case SyntaxKind.DefaultKeyword:
|
||||
return "default".length === searchSymbolName.length;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -180,7 +180,6 @@ namespace ts.FindAllReferences {
|
||||
* But re-exports will be placed in 'singleReferences' since they cannot be locally referenced.
|
||||
*/
|
||||
function getSearchesFromDirectImports(directImports: Importer[], exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker, isForRename: boolean): Pick<ImportsResult, "importSearches" | "singleReferences"> {
|
||||
const exportName = exportSymbol.escapedName;
|
||||
const importSearches: Array<[Identifier, Symbol]> = [];
|
||||
const singleReferences: Identifier[] = [];
|
||||
function addSearch(location: Identifier, symbol: Symbol): void {
|
||||
@ -218,12 +217,11 @@ namespace ts.FindAllReferences {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!decl.importClause) {
|
||||
const { importClause } = decl;
|
||||
if (!importClause) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { importClause } = decl;
|
||||
|
||||
const { namedBindings } = importClause;
|
||||
if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) {
|
||||
handleNamespaceImportLike(namedBindings.name);
|
||||
@ -245,7 +243,6 @@ namespace ts.FindAllReferences {
|
||||
|
||||
// 'default' might be accessed as a named import `{ default as foo }`.
|
||||
if (!isForRename && exportKind === ExportKind.Default) {
|
||||
Debug.assert(exportName === "default");
|
||||
searchForNamedImport(namedBindings as NamedImports | undefined);
|
||||
}
|
||||
}
|
||||
@ -258,36 +255,43 @@ namespace ts.FindAllReferences {
|
||||
*/
|
||||
function handleNamespaceImportLike(importName: Identifier): void {
|
||||
// Don't rename an import that already has a different name than the export.
|
||||
if (exportKind === ExportKind.ExportEquals && (!isForRename || importName.escapedText === exportName)) {
|
||||
if (exportKind === ExportKind.ExportEquals && (!isForRename || isNameMatch(importName.escapedText))) {
|
||||
addSearch(importName, checker.getSymbolAtLocation(importName));
|
||||
}
|
||||
}
|
||||
|
||||
function searchForNamedImport(namedBindings: NamedImportsOrExports | undefined): void {
|
||||
if (namedBindings) {
|
||||
for (const element of namedBindings.elements) {
|
||||
const { name, propertyName } = element;
|
||||
if ((propertyName || name).escapedText !== exportName) {
|
||||
continue;
|
||||
}
|
||||
if (!namedBindings) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (propertyName) {
|
||||
// This is `import { foo as bar } from "./a"` or `export { foo as bar } from "./a"`. `foo` isn't a local in the file, so just add it as a single reference.
|
||||
singleReferences.push(propertyName);
|
||||
if (!isForRename) { // If renaming `foo`, don't touch `bar`, just `foo`.
|
||||
// Search locally for `bar`.
|
||||
addSearch(name, checker.getSymbolAtLocation(name));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const localSymbol = element.kind === SyntaxKind.ExportSpecifier && element.propertyName
|
||||
? checker.getExportSpecifierLocalTargetSymbol(element) // For re-exporting under a different name, we want to get the re-exported symbol.
|
||||
: checker.getSymbolAtLocation(name);
|
||||
addSearch(name, localSymbol);
|
||||
for (const element of namedBindings.elements) {
|
||||
const { name, propertyName } = element;
|
||||
if (!isNameMatch((propertyName || name).escapedText)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (propertyName) {
|
||||
// This is `import { foo as bar } from "./a"` or `export { foo as bar } from "./a"`. `foo` isn't a local in the file, so just add it as a single reference.
|
||||
singleReferences.push(propertyName);
|
||||
if (!isForRename) { // If renaming `foo`, don't touch `bar`, just `foo`.
|
||||
// Search locally for `bar`.
|
||||
addSearch(name, checker.getSymbolAtLocation(name));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const localSymbol = element.kind === SyntaxKind.ExportSpecifier && element.propertyName
|
||||
? checker.getExportSpecifierLocalTargetSymbol(element) // For re-exporting under a different name, we want to get the re-exported symbol.
|
||||
: checker.getSymbolAtLocation(name);
|
||||
addSearch(name, localSymbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isNameMatch(name: __String): boolean {
|
||||
// Use name of "default" even in `export =` case because we may have allowSyntheticDefaultImports
|
||||
return name === exportSymbol.escapedName || exportKind !== ExportKind.Named && name === "default";
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns 'true' is the namespace 'name' is re-exported from this module, and 'false' if it is only used locally. */
|
||||
@ -413,7 +417,7 @@ namespace ts.FindAllReferences {
|
||||
case SyntaxKind.ExternalModuleReference:
|
||||
return (decl as ExternalModuleReference).parent;
|
||||
default:
|
||||
Debug.fail(`Unexpected module specifier parent: ${decl.kind}`);
|
||||
Debug.fail("Unexpected module specifier parent: " + decl.kind);
|
||||
}
|
||||
}
|
||||
|
||||
@ -468,11 +472,11 @@ namespace ts.FindAllReferences {
|
||||
return exportInfo(symbol, getExportKindForDeclaration(exportNode));
|
||||
}
|
||||
}
|
||||
// If we are in `export = a;`, `parent` is the export assignment.
|
||||
// If we are in `export = a;` or `export default a;`, `parent` is the export assignment.
|
||||
else if (isExportAssignment(parent)) {
|
||||
return getExportAssignmentExport(parent);
|
||||
}
|
||||
// If we are in `export = class A {};` at `A`, `parent.parent` is the export assignment.
|
||||
// If we are in `export = class A {};` (or `export = class A {};`) at `A`, `parent.parent` is the export assignment.
|
||||
else if (isExportAssignment(parent.parent)) {
|
||||
return getExportAssignmentExport(parent.parent);
|
||||
}
|
||||
@ -489,7 +493,8 @@ namespace ts.FindAllReferences {
|
||||
// Get the symbol for the `export =` node; its parent is the module it's the export of.
|
||||
const exportingModuleSymbol = ex.symbol.parent;
|
||||
Debug.assert(!!exportingModuleSymbol);
|
||||
return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol, exportKind: ExportKind.ExportEquals } };
|
||||
const exportKind = ex.isExportEquals ? ExportKind.ExportEquals : ExportKind.Default;
|
||||
return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol, exportKind } };
|
||||
}
|
||||
|
||||
function getSpecialPropertyExport(node: ts.BinaryExpression, useLhsSymbol: boolean): ExportedSymbol | undefined {
|
||||
@ -525,7 +530,11 @@ namespace ts.FindAllReferences {
|
||||
importedSymbol = getExportEqualsLocalSymbol(importedSymbol, checker);
|
||||
}
|
||||
|
||||
if (symbolName(importedSymbol) === symbol.escapedName) { // If this is a rename import, do not continue searching.
|
||||
// If the import has a different name than the export, do not continue searching.
|
||||
// If `importedName` is undefined, do continue searching as the export is anonymous.
|
||||
// (All imports returned from this function will be ignored anyway if we are in rename and this is a not a named export.)
|
||||
const importedName = symbolName(importedSymbol);
|
||||
if (importedName === undefined || importedName === "default" || importedName === symbol.escapedName) {
|
||||
return { kind: ImportExport.Import, symbol: importedSymbol, ...isImport };
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,15 +2,24 @@
|
||||
|
||||
// @Filename: /a.ts
|
||||
////const [|{| "isWriteAccess": true, "isDefinition": true |}a|] = 0;
|
||||
////export default [|a|];
|
||||
////export [|{| "isWriteAccess": true, "isDefinition": true |}default|] [|a|];
|
||||
|
||||
// @Filename: /b.ts
|
||||
////import [|{| "isWriteAccess": true, "isDefinition": true |}a|] from "./a";
|
||||
////[|a|];
|
||||
|
||||
const [r0, r1, r2, r3] = test.ranges();
|
||||
verify.referenceGroups([r0, r1], [
|
||||
{ definition: "const a: 0", ranges: [r0, r1] },
|
||||
{ definition: "import a", ranges: [r2, r3] }
|
||||
const [r0, r1, r2, r3, r4] = test.ranges();
|
||||
verify.referenceGroups([r0, r2], [
|
||||
{ definition: "const a: 0", ranges: [r0, r2] },
|
||||
{ definition: "import a", ranges: [r3, r4] }
|
||||
]);
|
||||
verify.referenceGroups(r1, [
|
||||
// TODO:GH#17990
|
||||
{ definition: "import default", ranges: [r1] },
|
||||
{ definition: "import a", ranges: [r3, r4] },
|
||||
]);
|
||||
verify.referenceGroups([r3, r4], [
|
||||
{ definition: "import a", ranges: [r3, r4] },
|
||||
// TODO:GH#17990
|
||||
{ definition: "import default", ranges: [r1] },
|
||||
]);
|
||||
verify.singleReferenceGroup("import a", [r2, r3]);
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @Filename: /a.ts
|
||||
////export [|{| "isWriteAccess": true, "isDefinition": true |}default|] function() {}
|
||||
|
||||
// @Filename: /b.ts
|
||||
////import [|{| "isWriteAccess": true, "isDefinition": true |}f|] from "./a";
|
||||
|
||||
const [r0, r1] = test.ranges();
|
||||
verify.referenceGroups(r0, [
|
||||
{ definition: "function default(): void", ranges: [r0] },
|
||||
{ definition: "import f", ranges: [r1] },
|
||||
]);
|
||||
verify.referenceGroups(r1, [
|
||||
{ definition: "import f", ranges: [r1] },
|
||||
{ definition: "function default(): void", ranges: [r0] },
|
||||
]);
|
||||
|
||||
// Verify that it doesn't try to rename "default"
|
||||
goTo.rangeStart(r0);
|
||||
verify.renameInfoFailed();
|
||||
verify.renameLocations(r1, [r1]);
|
||||
@ -0,0 +1,30 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @Filename: /export.ts
|
||||
////const [|{| "isWriteAccess": true, "isDefinition": true |}foo|] = 1;
|
||||
////export default [|foo|];
|
||||
|
||||
// @Filename: /re-export.ts
|
||||
////export { [|{| "isWriteAccess": true, "isDefinition": true |}default|] } from "./export";
|
||||
|
||||
// @Filename: /re-export-dep.ts
|
||||
////import [|{| "isWriteAccess": true, "isDefinition": true |}fooDefault|] from "./re-export";
|
||||
|
||||
verify.noErrors();
|
||||
|
||||
const [r0, r1, r2, r3] = test.ranges();
|
||||
verify.referenceGroups([r0, r1], [
|
||||
{ definition: "const foo: 1", ranges: [r0, r1] },
|
||||
{ definition: "import default", ranges: [r2], },
|
||||
{ definition: "import fooDefault", ranges: [r3] },
|
||||
]);
|
||||
verify.referenceGroups(r2, [
|
||||
{ definition: "import default", ranges: [r2] },
|
||||
{ definition: "import fooDefault", ranges: [r3] },
|
||||
{ definition: "const foo: 1", ranges: [r0, r1] },
|
||||
]);
|
||||
verify.referenceGroups(r3, [
|
||||
{ definition: "import fooDefault", ranges: [r3] },
|
||||
{ definition: "import default", ranges: [r2] },
|
||||
{ definition: "const foo: 1", ranges: [r0, r1] },
|
||||
]);
|
||||
@ -0,0 +1,32 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @allowSyntheticDefaultImports: true
|
||||
|
||||
// @Filename: /export.ts
|
||||
////const [|{| "isWriteAccess": true, "isDefinition": true |}foo|] = 1;
|
||||
////export = [|foo|];
|
||||
|
||||
// @Filename: /re-export.ts
|
||||
////export { [|{| "isWriteAccess": true, "isDefinition": true |}default|] } from "./export";
|
||||
|
||||
// @Filename: /re-export-dep.ts
|
||||
////import [|{| "isWriteAccess": true, "isDefinition": true |}fooDefault|] from "./re-export";
|
||||
|
||||
verify.noErrors();
|
||||
|
||||
const [r0, r1, r2, r3] = test.ranges();
|
||||
verify.referenceGroups([r0, r1], [
|
||||
{ definition: "const foo: 1", ranges: [r0, r1] },
|
||||
{ definition: "import default", ranges: [r2], },
|
||||
{ definition: "import fooDefault", ranges: [r3] },
|
||||
]);
|
||||
verify.referenceGroups(r2, [
|
||||
{ definition: "import default", ranges: [r2] },
|
||||
{ definition: "import fooDefault", ranges: [r3] },
|
||||
{ definition: "const foo: 1", ranges: [r0, r1] },
|
||||
]);
|
||||
verify.referenceGroups(r3, [
|
||||
{ definition: "import fooDefault", ranges: [r3] },
|
||||
{ definition: "import default", ranges: [r2] },
|
||||
{ definition: "const foo: 1", ranges: [r0, r1] },
|
||||
]);
|
||||
@ -39,14 +39,19 @@ verify.referenceGroups(bar2, [{ ...eBar, definition: "(alias) bar(): void\nimpor
|
||||
verify.referenceGroups([defaultC], [c, d, eBoom, eBaz, eBang]);
|
||||
verify.referenceGroups(defaultD, [d, eBoom, a, b, eBar,c, eBaz, eBang]);
|
||||
verify.referenceGroups(defaultE, [c, d, eBoom, eBaz, eBang]);
|
||||
verify.referenceGroups(baz0, [eBaz]);
|
||||
verify.referenceGroups(baz1, [{ ...eBaz, definition: "(alias) baz(): void\nimport baz" }]);
|
||||
verify.referenceGroups(baz0, [eBaz, c, d, eBoom, eBang]);
|
||||
verify.referenceGroups(baz1, [
|
||||
{ ...eBaz, definition: "(alias) baz(): void\nimport baz" },
|
||||
c, d, eBoom, eBang,
|
||||
]);
|
||||
|
||||
verify.referenceGroups(bang0, [eBang]);
|
||||
verify.referenceGroups(bang1, [{ ...eBang, definition: "(alias) bang(): void\nimport bang" }]);
|
||||
|
||||
verify.referenceGroups(boom0, [eBoom]);
|
||||
verify.referenceGroups(boom1, [{ ...eBoom, definition: "(alias) boom(): void\nimport boom" }]);
|
||||
verify.referenceGroups(boom0, [eBoom, d, a, b, eBar, c, eBaz, eBang]);
|
||||
verify.referenceGroups(boom1, [
|
||||
{ ...eBoom, definition: "(alias) boom(): void\nimport boom" },
|
||||
d, a, b, eBar, c, eBaz, eBang,
|
||||
]);
|
||||
|
||||
test.rangesByText().forEach((ranges, text) => {
|
||||
if (text === "default") {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user