diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9174aa3c0232f..6e1c4d4ae6132 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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; } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 04d383748c8c0..242c3ec166eed 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -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; } diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index d4301db2f8332..65b504e7bd8e4 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -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 { - 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 }; } } diff --git a/tests/cases/fourslash/findAllRefsForDefaultExport04.ts b/tests/cases/fourslash/findAllRefsForDefaultExport04.ts index c8fdb0a614954..1b5eb7a282db8 100644 --- a/tests/cases/fourslash/findAllRefsForDefaultExport04.ts +++ b/tests/cases/fourslash/findAllRefsForDefaultExport04.ts @@ -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]); diff --git a/tests/cases/fourslash/findAllRefsForDefaultExportAnonymous.ts b/tests/cases/fourslash/findAllRefsForDefaultExportAnonymous.ts new file mode 100644 index 0000000000000..3beb5b5942415 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsForDefaultExportAnonymous.ts @@ -0,0 +1,22 @@ +/// + +// @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]); diff --git a/tests/cases/fourslash/findAllRefsForDefaultExport_reExport.ts b/tests/cases/fourslash/findAllRefsForDefaultExport_reExport.ts new file mode 100644 index 0000000000000..401db1a8033d7 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsForDefaultExport_reExport.ts @@ -0,0 +1,30 @@ +/// + +// @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] }, +]); diff --git a/tests/cases/fourslash/findAllRefsForDefaultExport_reExport_allowSyntheticDefaultImports.ts b/tests/cases/fourslash/findAllRefsForDefaultExport_reExport_allowSyntheticDefaultImports.ts new file mode 100644 index 0000000000000..26a05f2e12e7a --- /dev/null +++ b/tests/cases/fourslash/findAllRefsForDefaultExport_reExport_allowSyntheticDefaultImports.ts @@ -0,0 +1,32 @@ +/// + +// @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] }, +]); diff --git a/tests/cases/fourslash/findAllRefsReExports.ts b/tests/cases/fourslash/findAllRefsReExports.ts index a4cced049b9fc..e993686760409 100644 --- a/tests/cases/fourslash/findAllRefsReExports.ts +++ b/tests/cases/fourslash/findAllRefsReExports.ts @@ -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") {