Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support find-all-references for default keyword #17992

Merged
4 commits merged into from
Sep 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23016,6 +23016,9 @@ namespace ts {
: undefined;
return objectType && getPropertyOfType(objectType, escapeLeadingUnderscores((node as StringLiteral | NumericLiteral).text));

case SyntaxKind.DefaultKeyword:
return getSymbolOfNode(node.parent);

default:
return undefined;
}
Expand Down
11 changes: 8 additions & 3 deletions src/services/findAllReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;

Expand All @@ -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;
}
Expand Down
69 changes: 39 additions & 30 deletions src/services/importTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
}
Expand All @@ -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. */
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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 };
}
}
Expand Down
21 changes: 15 additions & 6 deletions tests/cases/fourslash/findAllRefsForDefaultExport04.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
22 changes: 22 additions & 0 deletions tests/cases/fourslash/findAllRefsForDefaultExportAnonymous.ts
Original file line number Diff line number Diff line change
@@ -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]);
30 changes: 30 additions & 0 deletions tests/cases/fourslash/findAllRefsForDefaultExport_reExport.ts
Original file line number Diff line number Diff line change
@@ -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] },
]);
Original file line number Diff line number Diff line change
@@ -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] },
]);
15 changes: 10 additions & 5 deletions tests/cases/fourslash/findAllRefsReExports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down