From 99c6de44d0b39f92ce9879d7d699ddd488720543 Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Thu, 16 Jun 2022 13:43:23 +0100 Subject: [PATCH 01/12] Add diagnostics messages Signed-off-by: Richard Lynch --- src/compiler/diagnosticMessages.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9b30a51a07b2a..74019fceba284 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -6598,7 +6598,14 @@ "category": "Message", "code": 90058 }, - + "Export '{0}' from module '{1}'": { + "category": "Message", + "code": 90059 + }, + "Add all missing exports": { + "category": "Message", + "code": 90060 + }, "Convert function to an ES2015 class": { "category": "Message", "code": 95001 From ffa9cf26cf1fcecbe187fbf44a26e0c083955a93 Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Thu, 16 Jun 2022 13:44:34 +0100 Subject: [PATCH 02/12] Update `insertExportModifier` to support `VariableDeclarationList` Signed-off-by: Richard Lynch --- src/services/textChanges.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 59f22c8a9fbe0..ff7abe3973636 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -780,8 +780,20 @@ namespace ts.textChanges { } } - public insertExportModifier(sourceFile: SourceFile, node: DeclarationStatement | VariableStatement): void { - this.insertText(sourceFile, node.getStart(sourceFile), "export "); + public insertExportModifier( + sourceFile: SourceFile, + node: DeclarationStatement | VariableStatement | VariableDeclarationList + ): void { + Debug.assert( + /*expression*/ !(isVariableDeclarationList(node) && node.declarations.length !== 1), + /*message*/ "Only allow adding export modifier to variable lists with 1 element" + ); + + this.insertText( + sourceFile, + node.getStart(sourceFile, /*includeJsDocComment*/ false), + "export " + ); } public insertImportSpecifierAtIndex(sourceFile: SourceFile, importSpecifier: ImportSpecifier, namedImports: NamedImports, index: number) { From f3ec897e2f8860948e5f5db55dc122e98187bf71 Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Thu, 16 Jun 2022 13:45:02 +0100 Subject: [PATCH 03/12] Add `fixImportNonExportedMember` Signed-off-by: Richard Lynch --- .../codefixes/fixImportNonExportedMember.ts | 229 ++++++++++++++++++ src/services/tsconfig.json | 3 +- 2 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 src/services/codefixes/fixImportNonExportedMember.ts diff --git a/src/services/codefixes/fixImportNonExportedMember.ts b/src/services/codefixes/fixImportNonExportedMember.ts new file mode 100644 index 0000000000000..554734668f3b0 --- /dev/null +++ b/src/services/codefixes/fixImportNonExportedMember.ts @@ -0,0 +1,229 @@ +/* @internal */ +namespace ts.codefix { + const fixId = "importNonExportedMember"; + + const errorCodes = [ + Diagnostics.Module_0_declares_1_locally_but_it_is_not_exported.code, + ]; + + registerCodeFix({ + errorCodes, + + getCodeActions(context) { + const { sourceFile } = context; + + const info = getInfo(sourceFile, context, context.span.start); + + if (!info || info.originSourceFile.isDeclarationFile) { + return undefined; + } + + const changes = textChanges.ChangeTracker.with(context, (t) => + doChange(t, info.originSourceFile, info.node) + ); + + return [ + createCodeFixAction( + /*fixName*/ fixId, + changes, + /*description*/ [ + Diagnostics.Export_0_from_module_1, + info.node.text, + showModuleSpecifier(info.importDecl), + ], + fixId, + /*fixAllDescription*/ Diagnostics.Add_all_missing_exports + ), + ]; + }, + + fixIds: [fixId], + + getAllCodeActions: (context) => + codeFixAll(context, errorCodes, (changes, diag) => { + const info = getInfo(diag.file, context, diag.start); + + if (info) { + doChange(changes, info.originSourceFile, info.node); + } + }), + }); + + interface Info { + readonly node: Identifier; + + readonly importDecl: ImportDeclaration; + + readonly originSourceFile: SourceFile; + } + + function getInfo( + sourceFile: SourceFile, + context: CodeFixContext | CodeFixAllContext, + pos: number + ): Info | undefined { + const node = getTokenAtPosition(sourceFile, pos); + + if (!isIdentifier(node)) { + return; + } + + const importDecl = findAncestor(node, isImportDeclaration); + + if (!importDecl || !isStringLiteralLike(importDecl.moduleSpecifier)) { + return undefined; + } + + const resolvedModule = getResolvedModule( + sourceFile, + /*moduleName*/ importDecl.moduleSpecifier.text, + /*mode*/ undefined + ); + + if (!resolvedModule) { + return undefined; + } + + const originSourceFile = context.program.getSourceFile( + resolvedModule.resolvedFileName + ); + + if (!originSourceFile) { + return undefined; + } + + return { node, importDecl, originSourceFile }; + } + + function getNamedExportDeclaration( + sourceFile: SourceFile + ): ExportDeclaration | undefined { + let namedExport; + + for (const statement of sourceFile.statements) { + if ( + isExportDeclaration(statement) && + statement.exportClause && + isNamedExports(statement.exportClause) + ) { + namedExport = statement; + } + } + + return namedExport; + } + + function compareIdentifiers(s1: Identifier, s2: Identifier) { + return compareStringsCaseInsensitive(s1.text, s2.text); + } + + function sortSpecifiers( + specifiers: ExportSpecifier[] + ): readonly ExportSpecifier[] { + return stableSort(specifiers, (s1, s2) => + compareIdentifiers( + s1.propertyName || s1.name, + s2.propertyName || s2.name + ) + ); + } + + const isVariableDeclarationListWith1Element = ( + node: Node + ): node is VariableDeclarationList => + !(isVariableDeclarationList(node) && node.declarations.length !== 1); + + function doChange( + changes: textChanges.ChangeTracker, + sourceFile: SourceFile, + node: Identifier + ): void { + const moduleSymbol = sourceFile.localSymbol ?? sourceFile.symbol; + + const localSymbol = moduleSymbol.valueDeclaration?.locals?.get( + node.escapedText + ); + + if (localSymbol === undefined) { + return; + } + + // Node we need to export is a function + if (isFunctionSymbol(localSymbol)) { + const node = localSymbol.valueDeclaration; + + if (node === undefined) { + return; + } + + if (!isDeclarationStatement(node) && !isVariableStatement(node)) { + return; + } + + return changes.insertExportModifier(sourceFile, node); + } + // Node we need to export is a variable declaration + else if ( + localSymbol.valueDeclaration && + isVariableDeclarationListWith1Element( + localSymbol.valueDeclaration.parent + ) + ) { + const node = localSymbol.valueDeclaration.parent; + + return changes.insertExportModifier(sourceFile, node); + } + + // In all other cases => the node is a variable, and should be exported via `export {a}` + const namedExportDeclaration = getNamedExportDeclaration(sourceFile); + + // If there is an existing export + if ( + namedExportDeclaration?.exportClause && + isNamedExports(namedExportDeclaration.exportClause) + ) { + return changes.replaceNode( + sourceFile, + namedExportDeclaration, + factory.updateExportDeclaration( + /*node*/ namedExportDeclaration, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + /*exportClause*/ factory.updateNamedExports( + /*node*/ namedExportDeclaration.exportClause, + /*elements*/ sortSpecifiers( + namedExportDeclaration.exportClause.elements.concat( + factory.createExportSpecifier( + /*isTypeOnly*/ false, + /*propertyName*/ undefined, + node + ) + ) + ) + ), + /*moduleSpecifier*/ undefined, + /*assertClause*/ undefined + ) + ); + } + // There is no existing export + else { + return changes.insertNodeAtEndOfScope( + sourceFile, + sourceFile, + factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + /*exportClause*/ factory.createNamedExports([ + factory.createExportSpecifier( + /*isTypeOnly*/ false, + /*propertyName*/ undefined, + node + ), + ]), + /*moduleSpecifier*/ undefined + ) + ); + } + } +} diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index cef6b9139698a..87b6c01ffc754 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -115,6 +115,7 @@ "codefixes/convertConstToLet.ts", "codefixes/fixExpectedComma.ts", "codefixes/fixAddVoidToPromise.ts", + "codefixes/fixImportNonExportedMember.ts", "refactors/convertExport.ts", "refactors/convertImport.ts", "refactors/convertToOptionalChainExpression.ts", @@ -134,6 +135,6 @@ "transform.ts", "shims.ts", "globalThisShim.ts", - "exportAsModule.ts" + "exportAsModule.ts", ] } From c68ae3bf64d41227a1b9f3ae25fcd76d6ea5b9b5 Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Thu, 16 Jun 2022 13:45:12 +0100 Subject: [PATCH 04/12] Add tests for `fixImportNonExportedMember` Signed-off-by: Richard Lynch --- .../codeFixImportNonExportedMember1.ts | 29 ++++++++++++ .../codeFixImportNonExportedMember2.ts | 45 +++++++++++++++++++ .../codeFixImportNonExportedMember3.ts | 27 +++++++++++ .../codeFixImportNonExportedMember4.ts | 9 ++++ .../codeFixImportNonExportedMember5.ts | 29 ++++++++++++ .../codeFixImportNonExportedMember6.ts | 27 +++++++++++ .../codeFixImportNonExportedMember_all.ts | 22 +++++++++ 7 files changed, 188 insertions(+) create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember1.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember2.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember3.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember4.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember5.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember6.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_all.ts diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember1.ts b/tests/cases/fourslash/codeFixImportNonExportedMember1.ts new file mode 100644 index 0000000000000..416b333143d84 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember1.ts @@ -0,0 +1,29 @@ +/// +// @Filename: /a.ts +////declare function zoo(): any; +////export { zoo }; + +// @Filename: /b.ts +////declare function foo(): any; +////function bar(): any; +////export { foo }; + +// @Filename: /c.ts +////import { zoo } from "./a"; +////import { bar } from "./b"; + +goTo.file("/c.ts"); +verify.codeFixAvailable([ + { description: `Export 'bar' from module './b'` }, + { description: `Remove import from './a'` }, + { description: `Remove import from './b'` }, +]); +verify.codeFix({ + index: 0, + description: `Export 'bar' from module './b'`, + newFileContent: { + "/b.ts": `declare function foo(): any; +export function bar(): any; +export { foo };`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember2.ts b/tests/cases/fourslash/codeFixImportNonExportedMember2.ts new file mode 100644 index 0000000000000..5868bcf70eb2c --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember2.ts @@ -0,0 +1,45 @@ +/// + +// @Filename: /a.ts +////let a = 1, b = 2, c = 3; +////export function whatever() { +////} + +// @Filename: /b.ts +////let d = 4; +////export function whatever2() { +////} + +// @Filename: /c.ts +////import { a } from "./a" +////import { d } from "./b" + +goTo.file("/c.ts"); +verify.codeFixAvailable([ + { description: `Export 'a' from module './a'` }, + { description: `Export 'd' from module './b'` }, + { description: `Remove import from './a'` }, + { description: `Remove import from './b'` }, +]); +verify.codeFix({ + index: 0, + description: `Export 'a' from module './a'`, + newFileContent: { + "/a.ts": `let a = 1, b = 2, c = 3; +export function whatever() { +} + +export { a }; +`, + }, +}); + +verify.codeFix({ + index: 1, + description: `Export 'd' from module './b'`, + newFileContent: { + "/b.ts": `export let d = 4; +export function whatever2() { +}`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember3.ts b/tests/cases/fourslash/codeFixImportNonExportedMember3.ts new file mode 100644 index 0000000000000..99c8c887b8480 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember3.ts @@ -0,0 +1,27 @@ +/// +// @Filename: /a.ts +////let a = 1, b = 2, c = 3; +////let d = 4; +////export function whatever() { +////} +////export { d } + +// @Filename: /b.ts +////import { a, d } from "./a" + +goTo.file("/b.ts"); +verify.codeFixAvailable([ + { description: `Export 'a' from module './a'` }, + { description: `Remove import from './a'` }, +]); +verify.codeFix({ + index: 0, + description: `Export 'a' from module './a'`, + newFileContent: { + "/a.ts": `let a = 1, b = 2, c = 3; +let d = 4; +export function whatever() { +} +export { a, d };`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember4.ts b/tests/cases/fourslash/codeFixImportNonExportedMember4.ts new file mode 100644 index 0000000000000..212d5511aee34 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember4.ts @@ -0,0 +1,9 @@ +/// +// @Filename: /node_modules/foo/index.d.ts +////let a = 0 +////module.exports = 0; + +// @Filename: /a.ts +////import { a } from "foo"; + +verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember5.ts b/tests/cases/fourslash/codeFixImportNonExportedMember5.ts new file mode 100644 index 0000000000000..fe0a9652b87eb --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember5.ts @@ -0,0 +1,29 @@ +/// +// @Filename: /a.ts +/////** +//// * hello +//// */ +////function a() { +////} +////export { d } + +// @Filename: /b.ts +////import { a, d } from "./a" + +goTo.file("/b.ts"); +verify.codeFixAvailable([ + { description: `Export 'a' from module './a'` }, + { description: `Remove import from './a'` }, +]); +verify.codeFix({ + index: 0, + description: `Export 'a' from module './a'`, + newFileContent: { + "/a.ts": `/** + * hello + */ +export function a() { +} +export { d }`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember6.ts b/tests/cases/fourslash/codeFixImportNonExportedMember6.ts new file mode 100644 index 0000000000000..839646d372f69 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember6.ts @@ -0,0 +1,27 @@ +/// +// @Filename: /a.ts +////let a = 1, b = 2, c = 3; +////let d = 4; +////export function whatever() { +////} +////export { a } + +// @Filename: /b.ts +////import { a, b } from "./a" + +goTo.file("/b.ts"); +verify.codeFixAvailable([ + { description: `Export 'b' from module './a'` }, + { description: `Remove import from './a'` }, +]); +verify.codeFix({ + index: 0, + description: `Export 'b' from module './a'`, + newFileContent: { + "/a.ts": `let a = 1, b = 2, c = 3; +let d = 4; +export function whatever() { +} +export { a, b };`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_all.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_all.ts new file mode 100644 index 0000000000000..e2102eefa3954 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_all.ts @@ -0,0 +1,22 @@ +/// + +// @Filename: /a.ts +////declare function foo(): any; +////declare function bar(): any; +////declare function zoo(): any; +////export { zoo } + +// @Filename: /b.ts +////import { foo, bar } from "./a"; + +goTo.file("/b.ts"); +verify.codeFixAll({ + fixId: "importNonExportedMember", + fixAllDescription: ts.Diagnostics.Add_all_missing_exports.message, + newFileContent: { + "/a.ts": `export declare function foo(): any; +export declare function bar(): any; +declare function zoo(): any; +export { zoo }`, + }, +}); From 5cc5f52ab59fa601ad0a3631b2a8ae0601e04af4 Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Thu, 23 Jun 2022 14:35:45 +0100 Subject: [PATCH 05/12] Ensure that `export type` isn't clobbered --- .../codefixes/fixImportNonExportedMember.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/services/codefixes/fixImportNonExportedMember.ts b/src/services/codefixes/fixImportNonExportedMember.ts index 554734668f3b0..fbc4371fe709a 100644 --- a/src/services/codefixes/fixImportNonExportedMember.ts +++ b/src/services/codefixes/fixImportNonExportedMember.ts @@ -131,7 +131,7 @@ namespace ts.codefix { const isVariableDeclarationListWith1Element = ( node: Node ): node is VariableDeclarationList => - !(isVariableDeclarationList(node) && node.declarations.length !== 1); + isVariableDeclarationList(node) && node.declarations.length === 1; function doChange( changes: textChanges.ChangeTracker, @@ -148,18 +148,10 @@ namespace ts.codefix { return; } - // Node we need to export is a function - if (isFunctionSymbol(localSymbol)) { + // Node we need to export is a function, class, or variable declaration + if (localSymbol.valueDeclaration!== undefined && (isDeclarationStatement(localSymbol.valueDeclaration) || isVariableStatement(localSymbol.valueDeclaration))) { const node = localSymbol.valueDeclaration; - if (node === undefined) { - return; - } - - if (!isDeclarationStatement(node) && !isVariableStatement(node)) { - return; - } - return changes.insertExportModifier(sourceFile, node); } // Node we need to export is a variable declaration @@ -179,7 +171,9 @@ namespace ts.codefix { // If there is an existing export if ( - namedExportDeclaration?.exportClause && + namedExportDeclaration && + !namedExportDeclaration.isTypeOnly && + namedExportDeclaration.exportClause && isNamedExports(namedExportDeclaration.exportClause) ) { return changes.replaceNode( From 04f106631719bb9f95150c13696a5a48ed11c4c0 Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Thu, 23 Jun 2022 14:35:55 +0100 Subject: [PATCH 06/12] Add test for `export type` --- .../codeFixImportNonExportedMember7.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember7.ts diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember7.ts b/tests/cases/fourslash/codeFixImportNonExportedMember7.ts new file mode 100644 index 0000000000000..c6ffc8fbff126 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember7.ts @@ -0,0 +1,27 @@ +/// +// @Filename: /a.ts +////let a = 1, b = 2; +////type c = {foo: string}; +////export type { c }; + +// @Filename: /b.ts +////import { a } from "./a" + +// Doesn't use/clobber `export type {...}` +goTo.file("/b.ts"); +verify.codeFixAvailable([ + { description: `Export 'a' from module './a'` }, + { description: `Remove import from './a'` }, +]); +verify.codeFix({ + index: 0, + description: `Export 'a' from module './a'`, + newFileContent: { + "/a.ts": `let a = 1, b = 2; +type c = {foo: string}; +export type { c }; + +export { a }; +`, + }, +}); \ No newline at end of file From a71c68f253370ef924e037b7c7364c58801b9aef Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Thu, 23 Jun 2022 14:36:04 +0100 Subject: [PATCH 07/12] Add more tests --- .../codeFixImportNonExportedMember1.ts | 2 + .../codeFixImportNonExportedMember2.ts | 22 ++++++----- .../codeFixImportNonExportedMember3.ts | 1 + .../codeFixImportNonExportedMember4.ts | 1 + .../codeFixImportNonExportedMember5.ts | 18 ++++++--- .../codeFixImportNonExportedMember6.ts | 39 +++++++++++++------ 6 files changed, 57 insertions(+), 26 deletions(-) diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember1.ts b/tests/cases/fourslash/codeFixImportNonExportedMember1.ts index 416b333143d84..740b7c6e56446 100644 --- a/tests/cases/fourslash/codeFixImportNonExportedMember1.ts +++ b/tests/cases/fourslash/codeFixImportNonExportedMember1.ts @@ -13,11 +13,13 @@ ////import { bar } from "./b"; goTo.file("/c.ts"); +// Recognises that importing from a file with a `declare` is ok if its exported verify.codeFixAvailable([ { description: `Export 'bar' from module './b'` }, { description: `Remove import from './a'` }, { description: `Remove import from './b'` }, ]); +// Exports a function with a `declare` correctly verify.codeFix({ index: 0, description: `Export 'bar' from module './b'`, diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember2.ts b/tests/cases/fourslash/codeFixImportNonExportedMember2.ts index 5868bcf70eb2c..68fadb80a28c6 100644 --- a/tests/cases/fourslash/codeFixImportNonExportedMember2.ts +++ b/tests/cases/fourslash/codeFixImportNonExportedMember2.ts @@ -21,6 +21,18 @@ verify.codeFixAvailable([ { description: `Remove import from './a'` }, { description: `Remove import from './b'` }, ]); + +// Can fix a single variable +verify.codeFix({ + index: 1, + description: `Export 'd' from module './b'`, + newFileContent: { + "/b.ts": `export let d = 4; +export function whatever2() { +}`, + }, +}); +// Can fix a variable in a list (adds a named export) verify.codeFix({ index: 0, description: `Export 'a' from module './a'`, @@ -33,13 +45,3 @@ export { a }; `, }, }); - -verify.codeFix({ - index: 1, - description: `Export 'd' from module './b'`, - newFileContent: { - "/b.ts": `export let d = 4; -export function whatever2() { -}`, - }, -}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember3.ts b/tests/cases/fourslash/codeFixImportNonExportedMember3.ts index 99c8c887b8480..669f228cdb0e2 100644 --- a/tests/cases/fourslash/codeFixImportNonExportedMember3.ts +++ b/tests/cases/fourslash/codeFixImportNonExportedMember3.ts @@ -14,6 +14,7 @@ verify.codeFixAvailable([ { description: `Export 'a' from module './a'` }, { description: `Remove import from './a'` }, ]); +// Can export from list using an existing export verify.codeFix({ index: 0, description: `Export 'a' from module './a'`, diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember4.ts b/tests/cases/fourslash/codeFixImportNonExportedMember4.ts index 212d5511aee34..89bee7093b507 100644 --- a/tests/cases/fourslash/codeFixImportNonExportedMember4.ts +++ b/tests/cases/fourslash/codeFixImportNonExportedMember4.ts @@ -6,4 +6,5 @@ // @Filename: /a.ts ////import { a } from "foo"; +// Won't apply fix if `module.exports` is used verify.not.codeFixAvailable(); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember5.ts b/tests/cases/fourslash/codeFixImportNonExportedMember5.ts index fe0a9652b87eb..6f8e2f898319d 100644 --- a/tests/cases/fourslash/codeFixImportNonExportedMember5.ts +++ b/tests/cases/fourslash/codeFixImportNonExportedMember5.ts @@ -1,29 +1,37 @@ /// // @Filename: /a.ts /////** -//// * hello +//// * foo //// */ ////function a() { ////} -////export { d } +/////** +//// * bar +//// */ +////let b = 4; // @Filename: /b.ts -////import { a, d } from "./a" +////import { a, b } from "./a" goTo.file("/b.ts"); verify.codeFixAvailable([ { description: `Export 'a' from module './a'` }, + { description: `Export 'b' from module './a'` }, { description: `Remove import from './a'` }, ]); +// Doesn't clobber jsdoc on functions verify.codeFix({ index: 0, description: `Export 'a' from module './a'`, newFileContent: { "/a.ts": `/** - * hello + * foo */ export function a() { } -export { d }`, +/** + * bar + */ +let b = 4;`, }, }); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember6.ts b/tests/cases/fourslash/codeFixImportNonExportedMember6.ts index 839646d372f69..159a60ca3e9da 100644 --- a/tests/cases/fourslash/codeFixImportNonExportedMember6.ts +++ b/tests/cases/fourslash/codeFixImportNonExportedMember6.ts @@ -1,27 +1,44 @@ /// // @Filename: /a.ts -////let a = 1, b = 2, c = 3; -////let d = 4; -////export function whatever() { -////} -////export { a } +////class a{}; +////class b{}, class c{}; // @Filename: /b.ts -////import { a, b } from "./a" +////import { a, b, c } from "./a" goTo.file("/b.ts"); verify.codeFixAvailable([ + { description: `Export 'a' from module './a'` }, { description: `Export 'b' from module './a'` }, + { description: `Export 'c' from module './a'` }, { description: `Remove import from './a'` }, ]); +// Can export a class +verify.codeFix({ + index: 0, + description: `Export 'a' from module './a'`, + newFileContent: { + "/a.ts": `export class a{}; +class b{}, class c{};`, + }, +}); + +// Can export first class in list verify.codeFix({ index: 0, description: `Export 'b' from module './a'`, newFileContent: { - "/a.ts": `let a = 1, b = 2, c = 3; -let d = 4; -export function whatever() { -} -export { a, b };`, + "/a.ts": `class a{}; +export class b{}, class c{};`, }, }); + +// Can export second class in list +verify.codeFix({ + index: 0, + description: `Export 'c' from module './a'`, + newFileContent: { + "/a.ts": `class a{}; +class b{}, export class c{};`, + }, +}); \ No newline at end of file From 700141ee13eaf26b84d855435bc691128612f3ae Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Thu, 23 Jun 2022 14:49:16 +0100 Subject: [PATCH 08/12] Ensure `export {...} from` is not used for the export --- .../codefixes/fixImportNonExportedMember.ts | 11 ++++++-- .../codeFixImportNonExportedMember8.ts | 28 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember8.ts diff --git a/src/services/codefixes/fixImportNonExportedMember.ts b/src/services/codefixes/fixImportNonExportedMember.ts index fbc4371fe709a..71a9377866e0c 100644 --- a/src/services/codefixes/fixImportNonExportedMember.ts +++ b/src/services/codefixes/fixImportNonExportedMember.ts @@ -104,7 +104,9 @@ namespace ts.codefix { if ( isExportDeclaration(statement) && statement.exportClause && - isNamedExports(statement.exportClause) + isNamedExports(statement.exportClause) && + !statement.isTypeOnly && + statement.moduleSpecifier === undefined ) { namedExport = statement; } @@ -149,7 +151,11 @@ namespace ts.codefix { } // Node we need to export is a function, class, or variable declaration - if (localSymbol.valueDeclaration!== undefined && (isDeclarationStatement(localSymbol.valueDeclaration) || isVariableStatement(localSymbol.valueDeclaration))) { + if ( + localSymbol.valueDeclaration !== undefined && + (isDeclarationStatement(localSymbol.valueDeclaration) || + isVariableStatement(localSymbol.valueDeclaration)) + ) { const node = localSymbol.valueDeclaration; return changes.insertExportModifier(sourceFile, node); @@ -173,6 +179,7 @@ namespace ts.codefix { if ( namedExportDeclaration && !namedExportDeclaration.isTypeOnly && + !namedExportDeclaration.moduleSpecifier === undefined && namedExportDeclaration.exportClause && isNamedExports(namedExportDeclaration.exportClause) ) { diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember8.ts b/tests/cases/fourslash/codeFixImportNonExportedMember8.ts new file mode 100644 index 0000000000000..8e13d92c97922 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember8.ts @@ -0,0 +1,28 @@ +/// +// @Filename: /a.ts +////export let a = 1; + +// @Filename: /b.ts +////let b = 2, c = 3; +////export { a } from "./a"; + +// @Filename: /c.ts +////import { a, b } from "./b" + +// Doesn't use/clobber `export {...} from` +goTo.file("/c.ts"); +verify.codeFixAvailable([ + { description: `Export 'b' from module './b'` }, + { description: `Remove import from './b'` }, +]); +verify.codeFix({ + index: 0, + description: `Export 'b' from module './b'`, + newFileContent: { + "/b.ts": `let b = 2, c = 3; +export { a } from "./a"; + +export { b }; +`, + }, +}); From 22257819dd8e384373533ab14c1e0b299daa20f0 Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Thu, 23 Jun 2022 14:58:59 +0100 Subject: [PATCH 09/12] Simplify main logic flow --- .../codefixes/fixImportNonExportedMember.ts | 132 +++++++----------- 1 file changed, 53 insertions(+), 79 deletions(-) diff --git a/src/services/codefixes/fixImportNonExportedMember.ts b/src/services/codefixes/fixImportNonExportedMember.ts index 71a9377866e0c..7ca9fa0de24e2 100644 --- a/src/services/codefixes/fixImportNonExportedMember.ts +++ b/src/services/codefixes/fixImportNonExportedMember.ts @@ -95,37 +95,13 @@ namespace ts.codefix { return { node, importDecl, originSourceFile }; } - function getNamedExportDeclaration( - sourceFile: SourceFile - ): ExportDeclaration | undefined { - let namedExport; - - for (const statement of sourceFile.statements) { - if ( - isExportDeclaration(statement) && - statement.exportClause && - isNamedExports(statement.exportClause) && - !statement.isTypeOnly && - statement.moduleSpecifier === undefined - ) { - namedExport = statement; - } - } - - return namedExport; - } - - function compareIdentifiers(s1: Identifier, s2: Identifier) { - return compareStringsCaseInsensitive(s1.text, s2.text); - } - function sortSpecifiers( specifiers: ExportSpecifier[] ): readonly ExportSpecifier[] { return stableSort(specifiers, (s1, s2) => - compareIdentifiers( - s1.propertyName || s1.name, - s2.propertyName || s2.name + compareStringsCaseInsensitive( + (s1.propertyName || s1.name).text, + (s2.propertyName || s2.name).text ) ); } @@ -150,7 +126,7 @@ namespace ts.codefix { return; } - // Node we need to export is a function, class, or variable declaration + // Node we need to export is a function, class, or variable declaration which can have `export` prepended if ( localSymbol.valueDeclaration !== undefined && (isDeclarationStatement(localSymbol.valueDeclaration) || @@ -160,7 +136,6 @@ namespace ts.codefix { return changes.insertExportModifier(sourceFile, node); } - // Node we need to export is a variable declaration else if ( localSymbol.valueDeclaration && isVariableDeclarationListWith1Element( @@ -172,59 +147,58 @@ namespace ts.codefix { return changes.insertExportModifier(sourceFile, node); } - // In all other cases => the node is a variable, and should be exported via `export {a}` - const namedExportDeclaration = getNamedExportDeclaration(sourceFile); - - // If there is an existing export - if ( - namedExportDeclaration && - !namedExportDeclaration.isTypeOnly && - !namedExportDeclaration.moduleSpecifier === undefined && - namedExportDeclaration.exportClause && - isNamedExports(namedExportDeclaration.exportClause) - ) { - return changes.replaceNode( - sourceFile, - namedExportDeclaration, - factory.updateExportDeclaration( - /*node*/ namedExportDeclaration, - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - /*exportClause*/ factory.updateNamedExports( - /*node*/ namedExportDeclaration.exportClause, - /*elements*/ sortSpecifiers( - namedExportDeclaration.exportClause.elements.concat( - factory.createExportSpecifier( - /*isTypeOnly*/ false, - /*propertyName*/ undefined, - node + // In all other cases the node should be exported via `export {a}` + // Search for an export statement we can use + for (const namedExportDeclaration of sourceFile.statements) { + if ( + isExportDeclaration(namedExportDeclaration) && + namedExportDeclaration.exportClause && + isNamedExports(namedExportDeclaration.exportClause) && + !namedExportDeclaration.isTypeOnly && // don't use `export type {...}` + namedExportDeclaration.moduleSpecifier === undefined // don't use `export {...} from` + ) { + return changes.replaceNode( + sourceFile, + namedExportDeclaration, + factory.updateExportDeclaration( + namedExportDeclaration, + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + /*exportClause*/ factory.updateNamedExports( + namedExportDeclaration.exportClause, + /*elements*/ sortSpecifiers( + namedExportDeclaration.exportClause.elements.concat( + factory.createExportSpecifier( + /*isTypeOnly*/ false, + /*propertyName*/ undefined, + node + ) ) ) - ) - ), - /*moduleSpecifier*/ undefined, - /*assertClause*/ undefined - ) - ); - } - // There is no existing export - else { - return changes.insertNodeAtEndOfScope( - sourceFile, - sourceFile, - factory.createExportDeclaration( - /*modifiers*/ undefined, - /*isTypeOnly*/ false, - /*exportClause*/ factory.createNamedExports([ - factory.createExportSpecifier( - /*isTypeOnly*/ false, - /*propertyName*/ undefined, - node ), - ]), - /*moduleSpecifier*/ undefined - ) - ); + /*moduleSpecifier*/ undefined, + /*assertClause*/ undefined + ) + ); + } } + + // If we won't find an existing `export` statement we can use, create one + return changes.insertNodeAtEndOfScope( + sourceFile, + sourceFile, + factory.createExportDeclaration( + /*modifiers*/ undefined, + /*isTypeOnly*/ false, + /*exportClause*/ factory.createNamedExports([ + factory.createExportSpecifier( + /*isTypeOnly*/ false, + /*propertyName*/ undefined, + node + ), + ]), + /*moduleSpecifier*/ undefined + ) + ); } } From 5ff147bb54c04775ba152d6129d6c2a3b827d55c Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Thu, 23 Jun 2022 15:01:07 +0100 Subject: [PATCH 10/12] Add test for `export * as` --- .../codeFixImportNonExportedMember9.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember9.ts diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember9.ts b/tests/cases/fourslash/codeFixImportNonExportedMember9.ts new file mode 100644 index 0000000000000..86d95aa8b6a44 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember9.ts @@ -0,0 +1,28 @@ +/// +// @Filename: /a.ts +////export let a = 1; + +// @Filename: /b.ts +////let b = 2, c = 3; +////export * as a from "./a"; + +// @Filename: /c.ts +////import { a, b } from "./b" + +// Doesn't use/clobber `export * as a from` +goTo.file("/c.ts"); +verify.codeFixAvailable([ + { description: `Export 'b' from module './b'` }, + { description: `Remove import from './b'` }, +]); +verify.codeFix({ + index: 0, + description: `Export 'b' from module './b'`, + newFileContent: { + "/b.ts": `let b = 2, c = 3; +export * as a from "./a"; + +export { b }; +`, + }, +}); From 6dba610549ac58d516a0b494d9bad4542aba8345 Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Fri, 24 Jun 2022 12:12:16 +0100 Subject: [PATCH 11/12] Update diagnostics message --- src/compiler/diagnosticMessages.json | 2 +- src/services/codefixes/fixImportNonExportedMember.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 74019fceba284..16dac1ac07c36 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -6602,7 +6602,7 @@ "category": "Message", "code": 90059 }, - "Add all missing exports": { + "Export all missing members from modules": { "category": "Message", "code": 90060 }, diff --git a/src/services/codefixes/fixImportNonExportedMember.ts b/src/services/codefixes/fixImportNonExportedMember.ts index 7ca9fa0de24e2..6c957a41900fc 100644 --- a/src/services/codefixes/fixImportNonExportedMember.ts +++ b/src/services/codefixes/fixImportNonExportedMember.ts @@ -32,7 +32,7 @@ namespace ts.codefix { showModuleSpecifier(info.importDecl), ], fixId, - /*fixAllDescription*/ Diagnostics.Add_all_missing_exports + /*fixAllDescription*/ Diagnostics.Export_all_missing_members_from_modules ), ]; }, From ec6324a2f39e64d3fd20c9dc192eb228d2dc0332 Mon Sep 17 00:00:00 2001 From: Richard Lynch Date: Fri, 24 Jun 2022 12:12:21 +0100 Subject: [PATCH 12/12] Refactor tests --- .../codeFixImportNonExportedMember2.ts | 47 ------------------- .../codeFixImportNonExportedMember6.ts | 44 ----------------- .../codeFixImportNonExportedMember_all.ts | 3 +- ...ixImportNonExportedMember_class_inAList.ts | 24 ++++++++++ ...xImportNonExportedMember_class_onItsOwn.ts | 22 +++++++++ ...NonExportedMember_function_withDeclare.ts} | 0 ...ixImportNonExportedMember_jsdoc_onClass.ts | 29 ++++++++++++ ...portNonExportedMember_jsdoc_onFunction.ts} | 20 +++----- ...mportNonExportedMember_jsdoc_onVariable.ts | 30 ++++++++++++ ...mportNonExportedMember_variable_inAList.ts | 27 +++++++++++ ...portNonExportedMember_variable_onItsOwn.ts | 24 ++++++++++ ...Member_will_useExistingExportStatement.ts} | 0 ...tedMember_wont_applyToModuleDotExports.ts} | 0 ...NonExportedMember_wont_clobberExportAs.ts} | 0 ...nExportedMember_wont_clobberExportFrom.ts} | 0 ...nExportedMember_wont_clobberExportType.ts} | 4 +- 16 files changed, 166 insertions(+), 108 deletions(-) delete mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember2.ts delete mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember6.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_class_inAList.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_class_onItsOwn.ts rename tests/cases/fourslash/{codeFixImportNonExportedMember1.ts => codeFixImportNonExportedMember_function_withDeclare.ts} (100%) create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_jsdoc_onClass.ts rename tests/cases/fourslash/{codeFixImportNonExportedMember5.ts => codeFixImportNonExportedMember_jsdoc_onFunction.ts} (68%) create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_jsdoc_onVariable.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_variable_inAList.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_variable_onItsOwn.ts rename tests/cases/fourslash/{codeFixImportNonExportedMember3.ts => codeFixImportNonExportedMember_will_useExistingExportStatement.ts} (100%) rename tests/cases/fourslash/{codeFixImportNonExportedMember4.ts => codeFixImportNonExportedMember_wont_applyToModuleDotExports.ts} (100%) rename tests/cases/fourslash/{codeFixImportNonExportedMember9.ts => codeFixImportNonExportedMember_wont_clobberExportAs.ts} (100%) rename tests/cases/fourslash/{codeFixImportNonExportedMember8.ts => codeFixImportNonExportedMember_wont_clobberExportFrom.ts} (100%) rename tests/cases/fourslash/{codeFixImportNonExportedMember7.ts => codeFixImportNonExportedMember_wont_clobberExportType.ts} (87%) diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember2.ts b/tests/cases/fourslash/codeFixImportNonExportedMember2.ts deleted file mode 100644 index 68fadb80a28c6..0000000000000 --- a/tests/cases/fourslash/codeFixImportNonExportedMember2.ts +++ /dev/null @@ -1,47 +0,0 @@ -/// - -// @Filename: /a.ts -////let a = 1, b = 2, c = 3; -////export function whatever() { -////} - -// @Filename: /b.ts -////let d = 4; -////export function whatever2() { -////} - -// @Filename: /c.ts -////import { a } from "./a" -////import { d } from "./b" - -goTo.file("/c.ts"); -verify.codeFixAvailable([ - { description: `Export 'a' from module './a'` }, - { description: `Export 'd' from module './b'` }, - { description: `Remove import from './a'` }, - { description: `Remove import from './b'` }, -]); - -// Can fix a single variable -verify.codeFix({ - index: 1, - description: `Export 'd' from module './b'`, - newFileContent: { - "/b.ts": `export let d = 4; -export function whatever2() { -}`, - }, -}); -// Can fix a variable in a list (adds a named export) -verify.codeFix({ - index: 0, - description: `Export 'a' from module './a'`, - newFileContent: { - "/a.ts": `let a = 1, b = 2, c = 3; -export function whatever() { -} - -export { a }; -`, - }, -}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember6.ts b/tests/cases/fourslash/codeFixImportNonExportedMember6.ts deleted file mode 100644 index 159a60ca3e9da..0000000000000 --- a/tests/cases/fourslash/codeFixImportNonExportedMember6.ts +++ /dev/null @@ -1,44 +0,0 @@ -/// -// @Filename: /a.ts -////class a{}; -////class b{}, class c{}; - -// @Filename: /b.ts -////import { a, b, c } from "./a" - -goTo.file("/b.ts"); -verify.codeFixAvailable([ - { description: `Export 'a' from module './a'` }, - { description: `Export 'b' from module './a'` }, - { description: `Export 'c' from module './a'` }, - { description: `Remove import from './a'` }, -]); -// Can export a class -verify.codeFix({ - index: 0, - description: `Export 'a' from module './a'`, - newFileContent: { - "/a.ts": `export class a{}; -class b{}, class c{};`, - }, -}); - -// Can export first class in list -verify.codeFix({ - index: 0, - description: `Export 'b' from module './a'`, - newFileContent: { - "/a.ts": `class a{}; -export class b{}, class c{};`, - }, -}); - -// Can export second class in list -verify.codeFix({ - index: 0, - description: `Export 'c' from module './a'`, - newFileContent: { - "/a.ts": `class a{}; -class b{}, export class c{};`, - }, -}); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_all.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_all.ts index e2102eefa3954..87fe4311a9cc6 100644 --- a/tests/cases/fourslash/codeFixImportNonExportedMember_all.ts +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_all.ts @@ -12,7 +12,8 @@ goTo.file("/b.ts"); verify.codeFixAll({ fixId: "importNonExportedMember", - fixAllDescription: ts.Diagnostics.Add_all_missing_exports.message, + fixAllDescription: + ts.Diagnostics.Export_all_missing_members_from_modules.message, newFileContent: { "/a.ts": `export declare function foo(): any; export declare function bar(): any; diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_class_inAList.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_class_inAList.ts new file mode 100644 index 0000000000000..88faee12ce68b --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_class_inAList.ts @@ -0,0 +1,24 @@ +/// +// @Filename: /a.ts +////class a{}, class b{}; +////export let c = 3; + +// @Filename: /b.ts +////import { a, b, c } from "./a" + +goTo.file("/b.ts"); +verify.codeFixAvailable([ + { description: `Export 'a' from module './a'` }, + { description: `Export 'b' from module './a'` }, + { description: `Remove import from './a'` }, +]); + +// Can export class in list +verify.codeFix({ + index: 1, + description: `Export 'b' from module './a'`, + newFileContent: { + "/a.ts": `class a{}, export class b{}; +export let c = 3;`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_class_onItsOwn.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_class_onItsOwn.ts new file mode 100644 index 0000000000000..c369c0013c4df --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_class_onItsOwn.ts @@ -0,0 +1,22 @@ +/// +// @Filename: /a.ts +////class a{}; +////export let b = 2; + +// @Filename: /b.ts +////import { a, b } from "./a" + +goTo.file("/b.ts"); +verify.codeFixAvailable([ + { description: `Export 'a' from module './a'` }, + { description: `Remove import from './a'` }, +]); +// Can export a class +verify.codeFix({ + index: 0, + description: `Export 'a' from module './a'`, + newFileContent: { + "/a.ts": `export class a{}; +export let b = 2;`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember1.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_function_withDeclare.ts similarity index 100% rename from tests/cases/fourslash/codeFixImportNonExportedMember1.ts rename to tests/cases/fourslash/codeFixImportNonExportedMember_function_withDeclare.ts diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_jsdoc_onClass.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_jsdoc_onClass.ts new file mode 100644 index 0000000000000..d590b39f174fe --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_jsdoc_onClass.ts @@ -0,0 +1,29 @@ +/// +// @Filename: /a.ts +/////** +//// * baz +//// */ +////class a{}; +////export let b = 3; + +// @Filename: /b.ts +////import { a, b } from "./a" + +goTo.file("/b.ts"); +verify.codeFixAvailable([ + { description: `Export 'a' from module './a'` }, + { description: `Remove import from './a'` }, +]); + +// Doesn't clobber jsdoc on classes +verify.codeFix({ + index: 0, + description: `Export 'a' from module './a'`, + newFileContent: { + "/a.ts": `/** + * baz + */ +export class a{}; +export let b = 3;`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember5.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_jsdoc_onFunction.ts similarity index 68% rename from tests/cases/fourslash/codeFixImportNonExportedMember5.ts rename to tests/cases/fourslash/codeFixImportNonExportedMember_jsdoc_onFunction.ts index 6f8e2f898319d..e85b1685f188f 100644 --- a/tests/cases/fourslash/codeFixImportNonExportedMember5.ts +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_jsdoc_onFunction.ts @@ -3,12 +3,8 @@ /////** //// * foo //// */ -////function a() { -////} -/////** -//// * bar -//// */ -////let b = 4; +////function a(){}; +////export let b = 3; // @Filename: /b.ts ////import { a, b } from "./a" @@ -16,9 +12,9 @@ goTo.file("/b.ts"); verify.codeFixAvailable([ { description: `Export 'a' from module './a'` }, - { description: `Export 'b' from module './a'` }, { description: `Remove import from './a'` }, ]); + // Doesn't clobber jsdoc on functions verify.codeFix({ index: 0, @@ -27,11 +23,7 @@ verify.codeFix({ "/a.ts": `/** * foo */ -export function a() { -} -/** - * bar - */ -let b = 4;`, +export function a(){}; +export let b = 3;`, }, -}); +}); \ No newline at end of file diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_jsdoc_onVariable.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_jsdoc_onVariable.ts new file mode 100644 index 0000000000000..fe8cd30bb1795 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_jsdoc_onVariable.ts @@ -0,0 +1,30 @@ +/// +// @Filename: /a.ts +/////** +//// * bar +//// */ +////let a = 4; +////export let b = 3; + +// @Filename: /b.ts +////import { a, b } from "./a" + +goTo.file("/b.ts"); +verify.codeFixAvailable([ + { description: `Export 'a' from module './a'` }, + { description: `Remove import from './a'` }, +]); + + +// Doesn't clobber jsdoc on variables +verify.codeFix({ + index: 0, + description: `Export 'a' from module './a'`, + newFileContent: { + "/a.ts": `/** + * bar + */ +export let a = 4; +export let b = 3;`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_variable_inAList.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_variable_inAList.ts new file mode 100644 index 0000000000000..103e29ec6db05 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_variable_inAList.ts @@ -0,0 +1,27 @@ +/// + +// @Filename: /a.ts +////let a = 1, b = 2, c = 3; +////export let d = 4; + +// @Filename: /b.ts +////import { a } from "./a" + +goTo.file("/b.ts"); +verify.codeFixAvailable([ + { description: `Export 'a' from module './a'` }, + { description: `Remove import from './a'` }, +]); + +// Can fix a variable in a list (adds a named export) +verify.codeFix({ + index: 0, + description: `Export 'a' from module './a'`, + newFileContent: { + "/a.ts": `let a = 1, b = 2, c = 3; +export let d = 4; + +export { a }; +`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_variable_onItsOwn.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_variable_onItsOwn.ts new file mode 100644 index 0000000000000..fef6d78bb68f7 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_variable_onItsOwn.ts @@ -0,0 +1,24 @@ +/// +// @Filename: /a.ts +////let a = 4; +////export let b = 2; + +// @Filename: /b.ts +////import { a, b } from "./a" + + +goTo.file("/b.ts"); +verify.codeFixAvailable([ + { description: `Export 'a' from module './a'` }, + { description: `Remove import from './a'` }, +]); + +// Can fix a single variable +verify.codeFix({ + index: 0, + description: `Export 'a' from module './a'`, + newFileContent: { + "/a.ts": `export let a = 4; +export let b = 2;`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember3.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_will_useExistingExportStatement.ts similarity index 100% rename from tests/cases/fourslash/codeFixImportNonExportedMember3.ts rename to tests/cases/fourslash/codeFixImportNonExportedMember_will_useExistingExportStatement.ts diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember4.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_wont_applyToModuleDotExports.ts similarity index 100% rename from tests/cases/fourslash/codeFixImportNonExportedMember4.ts rename to tests/cases/fourslash/codeFixImportNonExportedMember_wont_applyToModuleDotExports.ts diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember9.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_wont_clobberExportAs.ts similarity index 100% rename from tests/cases/fourslash/codeFixImportNonExportedMember9.ts rename to tests/cases/fourslash/codeFixImportNonExportedMember_wont_clobberExportAs.ts diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember8.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_wont_clobberExportFrom.ts similarity index 100% rename from tests/cases/fourslash/codeFixImportNonExportedMember8.ts rename to tests/cases/fourslash/codeFixImportNonExportedMember_wont_clobberExportFrom.ts diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember7.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_wont_clobberExportType.ts similarity index 87% rename from tests/cases/fourslash/codeFixImportNonExportedMember7.ts rename to tests/cases/fourslash/codeFixImportNonExportedMember_wont_clobberExportType.ts index c6ffc8fbff126..f2a33b00f0821 100644 --- a/tests/cases/fourslash/codeFixImportNonExportedMember7.ts +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_wont_clobberExportType.ts @@ -7,7 +7,7 @@ // @Filename: /b.ts ////import { a } from "./a" -// Doesn't use/clobber `export type {...}` +// Doesn't use/clobber `export type {...}` goTo.file("/b.ts"); verify.codeFixAvailable([ { description: `Export 'a' from module './a'` }, @@ -24,4 +24,4 @@ export type { c }; export { a }; `, }, -}); \ No newline at end of file +});