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:
Andy 2017-09-07 07:26:22 -07:00 committed by GitHub
parent 817c329667
commit b3c87aa919
8 changed files with 159 additions and 44 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 };
}
}

View File

@ -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]);

View File

@ -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]);

View File

@ -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] },
]);

View File

@ -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] },
]);

View File

@ -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") {