From 2bcfed01f3458996e71ce37af43e3495cb7e4950 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Fri, 14 Oct 2022 01:43:49 +0300 Subject: [PATCH] feat(37440): Provide a quick-fix for non-exported types (#51038) * feat(37440): add QF to handle missing exports * change diagnostic message * add type modifier only if isolatedModules is set or if the export declaration already uses type modifiers --- src/compiler/checker.ts | 31 ---- src/compiler/diagnosticMessages.json | 8 + src/compiler/utilities.ts | 26 +++ src/services/codefixes/fixAddMissingMember.ts | 4 - .../codefixes/fixImportNonExportedMember.ts | 163 ++++++++++++++++++ src/services/tsconfig.json | 1 + src/services/utilities.ts | 4 + .../codeFixImportNonExportedMember1.ts | 22 +++ .../codeFixImportNonExportedMember10.ts | 26 +++ .../codeFixImportNonExportedMember11.ts | 21 +++ .../codeFixImportNonExportedMember12.ts | 22 +++ .../codeFixImportNonExportedMember13.ts | 23 +++ .../codeFixImportNonExportedMember2.ts | 20 +++ .../codeFixImportNonExportedMember3.ts | 23 +++ .../codeFixImportNonExportedMember4.ts | 12 ++ .../codeFixImportNonExportedMember5.ts | 12 ++ .../codeFixImportNonExportedMember6.ts | 25 +++ .../codeFixImportNonExportedMember7.ts | 30 ++++ .../codeFixImportNonExportedMember8.ts | 22 +++ .../codeFixImportNonExportedMember9.ts | 26 +++ .../codeFixImportNonExportedMember_all1.ts | 22 +++ .../codeFixImportNonExportedMember_all2.ts | 24 +++ .../codeFixImportNonExportedMember_all3.ts | 25 +++ .../codeFixImportNonExportedMember_all4.ts | 28 +++ .../codeFixImportNonExportedMember_all5.ts | 79 +++++++++ .../codeFixImportNonExportedMember_all6.ts | 35 ++++ .../codeFixImportNonExportedMember_all7.ts | 26 +++ 27 files changed, 725 insertions(+), 35 deletions(-) create mode 100644 src/services/codefixes/fixImportNonExportedMember.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember1.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember10.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember11.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember12.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember13.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/codeFixImportNonExportedMember7.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember8.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember9.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_all1.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_all2.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_all3.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_all4.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_all5.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_all6.ts create mode 100644 tests/cases/fourslash/codeFixImportNonExportedMember_all7.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 72a6934b0413f..d439ec7db6350 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7235,16 +7235,6 @@ namespace ts { return statements; } - function canHaveExportModifier(node: Statement): node is Extract { - return isEnumDeclaration(node) || - isVariableStatement(node) || - isFunctionDeclaration(node) || - isClassDeclaration(node) || - (isModuleDeclaration(node) && !isExternalModuleAugmentation(node) && !isGlobalScopeAugmentation(node)) || - isInterfaceDeclaration(node) || - isTypeDeclaration(node); - } - function addExportModifier(node: Extract) { const flags = (getEffectiveModifierFlags(node) | ModifierFlags.Export) & ~ModifierFlags.Ambient; return factory.updateModifiers(node, flags); @@ -42691,27 +42681,6 @@ namespace ts { getNameOfDeclaration(name.parent) === name; } - function isTypeDeclaration(node: Node): node is TypeParameterDeclaration | ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag | EnumDeclaration | ImportClause | ImportSpecifier | ExportSpecifier { - switch (node.kind) { - case SyntaxKind.TypeParameter: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.JSDocCallbackTag: - case SyntaxKind.JSDocEnumTag: - return true; - case SyntaxKind.ImportClause: - return (node as ImportClause).isTypeOnly; - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - return (node as ImportSpecifier | ExportSpecifier).parent.parent.isTypeOnly; - default: - return false; - } - } - // True if the given identifier is part of a type reference function isTypeReferenceIdentifier(node: EntityName): boolean { while (node.parent.kind === SyntaxKind.QualifiedName) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 7a8f3192d917c..62f26cf014cc9 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -6687,6 +6687,14 @@ "category": "Message", "code": 90058 }, + "Export '{0}' from module '{1}'": { + "category": "Message", + "code": 90059 + }, + "Export all referenced locals": { + "category": "Message", + "code": 90060 + }, "Convert function to an ES2015 class": { "category": "Message", diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index cdc0db9407b34..0571a9c92df0d 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7752,4 +7752,30 @@ namespace ts { export function getParameterTypeNode(parameter: ParameterDeclaration | JSDocParameterTag) { return parameter.kind === SyntaxKind.JSDocParameterTag ? parameter.typeExpression?.type : parameter.type; } + + export function isTypeDeclaration(node: Node): node is TypeParameterDeclaration | ClassDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag | EnumDeclaration | ImportClause | ImportSpecifier | ExportSpecifier { + switch (node.kind) { + case SyntaxKind.TypeParameter: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.JSDocCallbackTag: + case SyntaxKind.JSDocEnumTag: + return true; + case SyntaxKind.ImportClause: + return (node as ImportClause).isTypeOnly; + case SyntaxKind.ImportSpecifier: + case SyntaxKind.ExportSpecifier: + return (node as ImportSpecifier | ExportSpecifier).parent.parent.isTypeOnly; + default: + return false; + } + } + + export function canHaveExportModifier(node: Node): node is Extract { + return isEnumDeclaration(node) || isVariableStatement(node) || isFunctionDeclaration(node) || isClassDeclaration(node) + || isInterfaceDeclaration(node) || isTypeDeclaration(node) || (isModuleDeclaration(node) && !isExternalModuleAugmentation(node) && !isGlobalScopeAugmentation(node)); + } } diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 80f4f51c06a8c..69cd8d0a8ae30 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -264,10 +264,6 @@ namespace ts.codefix { return undefined; } - function isSourceFileFromLibrary(program: Program, node: SourceFile) { - return program.isSourceFileFromExternalLibrary(node) || program.isSourceFileDefaultLibrary(node); - } - function getActionsForMissingMemberDeclaration(context: CodeFixContext, info: TypeLikeDeclarationInfo): CodeFixAction[] | undefined { return info.isJSFile ? singleElementArray(createActionForAddMissingMemberInJavascriptFile(context, info)) : createActionsForAddMissingMemberInTypeScriptFile(context, info); diff --git a/src/services/codefixes/fixImportNonExportedMember.ts b/src/services/codefixes/fixImportNonExportedMember.ts new file mode 100644 index 0000000000000..5eb3a0e81d301 --- /dev/null +++ b/src/services/codefixes/fixImportNonExportedMember.ts @@ -0,0 +1,163 @@ +/* @internal */ +namespace ts.codefix { + const fixId = "fixImportNonExportedMember"; + const errorCodes = [ + Diagnostics.Module_0_declares_1_locally_but_it_is_not_exported.code, + ]; + + registerCodeFix({ + errorCodes, + fixIds: [fixId], + getCodeActions(context) { + const { sourceFile, span, program } = context; + const info = getInfo(sourceFile, span.start, program); + if (info === undefined) return undefined; + + const changes = textChanges.ChangeTracker.with(context, t => doChange(t, program, info)); + return [createCodeFixAction(fixId, changes, [Diagnostics.Export_0_from_module_1, info.exportName.node.text, info.moduleSpecifier], fixId, Diagnostics.Export_all_referenced_locals)]; + }, + getAllCodeActions(context) { + const { program } = context; + return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { + const exports = new Map(); + + eachDiagnostic(context, errorCodes, diag => { + const info = getInfo(diag.file, diag.start, program); + if (info === undefined) return undefined; + + const { exportName, node, moduleSourceFile } = info; + if (tryGetExportDeclaration(moduleSourceFile, exportName.isTypeOnly) === undefined && canHaveExportModifier(node)) { + changes.insertExportModifier(moduleSourceFile, node); + } + else { + const moduleExports = exports.get(moduleSourceFile) || { typeOnlyExports: [], exports: [] }; + if (exportName.isTypeOnly) { + moduleExports.typeOnlyExports.push(exportName); + } + else { + moduleExports.exports.push(exportName); + } + exports.set(moduleSourceFile, moduleExports); + } + }); + + exports.forEach((moduleExports, moduleSourceFile) => { + const exportDeclaration = tryGetExportDeclaration(moduleSourceFile, /*isTypeOnly*/ true); + if (exportDeclaration && exportDeclaration.isTypeOnly) { + doChanges(changes, program, moduleSourceFile, moduleExports.typeOnlyExports, exportDeclaration); + doChanges(changes, program, moduleSourceFile, moduleExports.exports, tryGetExportDeclaration(moduleSourceFile, /*isTypeOnly*/ false)); + } + else { + doChanges(changes, program, moduleSourceFile, [...moduleExports.exports, ...moduleExports.typeOnlyExports], exportDeclaration); + } + }); + })); + } + }); + + interface ModuleExports { + typeOnlyExports: ExportName[]; + exports: ExportName[]; + } + + interface ExportName { + node: Identifier; + isTypeOnly: boolean; + } + + interface Info { + exportName: ExportName; + node: Declaration | VariableStatement; + moduleSourceFile: SourceFile; + moduleSpecifier: string; + } + + function getInfo(sourceFile: SourceFile, pos: number, program: Program): Info | undefined { + const token = getTokenAtPosition(sourceFile, pos); + if (isIdentifier(token)) { + const importDeclaration = findAncestor(token, isImportDeclaration); + if (importDeclaration === undefined) return undefined; + + const moduleSpecifier = isStringLiteral(importDeclaration.moduleSpecifier) ? importDeclaration.moduleSpecifier.text : undefined; + if (moduleSpecifier === undefined) return undefined; + + const resolvedModule = getResolvedModule(sourceFile, moduleSpecifier, /*mode*/ undefined); + if (resolvedModule === undefined) return undefined; + + const moduleSourceFile = program.getSourceFile(resolvedModule.resolvedFileName); + if (moduleSourceFile === undefined || isSourceFileFromLibrary(program, moduleSourceFile)) return undefined; + + const moduleSymbol = moduleSourceFile.symbol; + const locals = moduleSymbol.valueDeclaration?.locals; + if (locals === undefined) return undefined; + + const localSymbol = locals.get(token.escapedText); + if (localSymbol === undefined) return undefined; + + const node = getNodeOfSymbol(localSymbol); + if (node === undefined) return undefined; + + const exportName = { node: token, isTypeOnly: isTypeDeclaration(node) }; + return { exportName, node, moduleSourceFile, moduleSpecifier }; + } + return undefined; + } + + function doChange(changes: textChanges.ChangeTracker, program: Program, { exportName, node, moduleSourceFile }: Info) { + const exportDeclaration = tryGetExportDeclaration(moduleSourceFile, exportName.isTypeOnly); + if (exportDeclaration) { + updateExport(changes, program, moduleSourceFile, exportDeclaration, [exportName]); + } + else if (canHaveExportModifier(node)) { + changes.insertExportModifier(moduleSourceFile, node); + } + else { + createExport(changes, program, moduleSourceFile, [exportName]); + } + } + + function doChanges(changes: textChanges.ChangeTracker, program: Program, sourceFile: SourceFile, moduleExports: ExportName[], node: ExportDeclaration | undefined) { + if (length(moduleExports)) { + if (node) { + updateExport(changes, program, sourceFile, node, moduleExports); + } + else { + createExport(changes, program, sourceFile, moduleExports); + } + } + } + + function tryGetExportDeclaration(sourceFile: SourceFile, isTypeOnly: boolean) { + const predicate = (node: Node): node is ExportDeclaration => + isExportDeclaration(node) && (isTypeOnly && node.isTypeOnly || !node.isTypeOnly); + return findLast(sourceFile.statements, predicate); + } + + function updateExport(changes: textChanges.ChangeTracker, program: Program, sourceFile: SourceFile, node: ExportDeclaration, names: ExportName[]) { + const namedExports = node.exportClause && isNamedExports(node.exportClause) ? node.exportClause.elements : factory.createNodeArray([]); + const allowTypeModifier = !node.isTypeOnly && !!(program.getCompilerOptions().isolatedModules || find(namedExports, e => e.isTypeOnly)); + changes.replaceNode(sourceFile, node, + factory.updateExportDeclaration(node, node.modifiers, node.isTypeOnly, + factory.createNamedExports( + factory.createNodeArray([...namedExports, ...createExportSpecifiers(names, allowTypeModifier)], /*hasTrailingComma*/ namedExports.hasTrailingComma)), node.moduleSpecifier, node.assertClause)); + } + + function createExport(changes: textChanges.ChangeTracker, program: Program, sourceFile: SourceFile, names: ExportName[]) { + changes.insertNodeAtEndOfScope(sourceFile, sourceFile, + factory.createExportDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, + factory.createNamedExports(createExportSpecifiers(names, /*allowTypeModifier*/ !!program.getCompilerOptions().isolatedModules)), /*moduleSpecifier*/ undefined, /*assertClause*/ undefined)); + } + + function createExportSpecifiers(names: ExportName[], allowTypeModifier: boolean) { + return factory.createNodeArray(map(names, n => factory.createExportSpecifier(allowTypeModifier && n.isTypeOnly, /*propertyName*/ undefined, n.node))); + } + + function getNodeOfSymbol(symbol: Symbol) { + if (symbol.valueDeclaration === undefined) { + return firstOrUndefined(symbol.declarations); + } + const declaration = symbol.valueDeclaration; + const variableStatement = isVariableDeclaration(declaration) ? tryCast(declaration.parent.parent, isVariableStatement) : undefined; + return variableStatement && length(variableStatement.declarationList.declarations) === 1 ? variableStatement : declaration; + } +} diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 2237163ebb20d..2099fe308c911 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -71,6 +71,7 @@ "codefixes/fixOverrideModifier.ts", "codefixes/fixNoPropertyAccessFromIndexSignature.ts", "codefixes/fixImplicitThis.ts", + "codefixes/fixImportNonExportedMember.ts", "codefixes/fixIncorrectNamedTupleSyntax.ts", "codefixes/fixSpelling.ts", "codefixes/returnValueCorrect.ts", diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 58d1b1cbca1e8..76d63dd114e97 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -3423,5 +3423,9 @@ namespace ts { return jsx === JsxEmit.React || jsx === JsxEmit.ReactNative; } + export function isSourceFileFromLibrary(program: Program, node: SourceFile) { + return program.isSourceFileFromExternalLibrary(node) || program.isSourceFileDefaultLibrary(node); + } + // #endregion } diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember1.ts b/tests/cases/fourslash/codeFixImportNonExportedMember1.ts new file mode 100644 index 0000000000000..0cdc08cdc0428 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember1.ts @@ -0,0 +1,22 @@ +/// + +// @module: esnext +// @filename: /a.ts +////declare function foo(): any +////declare function bar(): any; +////export { foo }; + +// @filename: /b.ts +////import { bar } from "./a"; + +goTo.file("/b.ts"); +verify.codeFix({ + description: [ts.Diagnostics.Export_0_from_module_1.message, "bar", "./a"], + index: 0, + newFileContent: { + "/a.ts": +`declare function foo(): any +declare function bar(): any; +export { foo, bar };`, + } +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember10.ts b/tests/cases/fourslash/codeFixImportNonExportedMember10.ts new file mode 100644 index 0000000000000..a9d74e1874de0 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember10.ts @@ -0,0 +1,26 @@ +/// + +// @module: esnext +// @filename: /a.ts +/////** +//// * foo +//// */ +////function foo() {} +////export const bar = 1; + +// @filename: /b.ts +////import { foo } from "./a"; + +goTo.file("/b.ts"); +verify.codeFix({ + description: [ts.Diagnostics.Export_0_from_module_1.message, "foo", "./a"], + index: 0, + newFileContent: { + "/a.ts": +`/** + * foo + */ +export function foo() {} +export const bar = 1;`, + } +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember11.ts b/tests/cases/fourslash/codeFixImportNonExportedMember11.ts new file mode 100644 index 0000000000000..c386f3e5f8270 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember11.ts @@ -0,0 +1,21 @@ +/// + +// @module: esnext +// @isolatedModules: true +// @filename: /a.ts +////type T = {}; +////export {}; + +// @filename: /b.ts +////import { T } from "./a"; + +goTo.file("/b.ts"); +verify.codeFix({ + description: [ts.Diagnostics.Export_0_from_module_1.message, "T", "./a"], + index: 0, + newFileContent: { + "/a.ts": +`type T = {}; +export { type T };`, + } +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember12.ts b/tests/cases/fourslash/codeFixImportNonExportedMember12.ts new file mode 100644 index 0000000000000..25d030ba2585e --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember12.ts @@ -0,0 +1,22 @@ +/// + +// @module: esnext +// @filename: /a.ts +////type T1 = {}; +////type T2 = {}; +////export { type T1 }; + +// @filename: /b.ts +////import { T2 } from "./a"; + +goTo.file("/b.ts"); +verify.codeFix({ + description: [ts.Diagnostics.Export_0_from_module_1.message, "T2", "./a"], + index: 0, + newFileContent: { + "/a.ts": +`type T1 = {}; +type T2 = {}; +export { type T1, type T2 };`, + } +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember13.ts b/tests/cases/fourslash/codeFixImportNonExportedMember13.ts new file mode 100644 index 0000000000000..9db9a90e7f171 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember13.ts @@ -0,0 +1,23 @@ +/// + +// @module: esnext +// @isolatedModules: true +// @filename: /a.ts +////type T1 = {}; +////type T2 = {}; +////export type { T1 }; + +// @filename: /b.ts +////import { T2 } from "./a"; + +goTo.file("/b.ts"); +verify.codeFix({ + description: [ts.Diagnostics.Export_0_from_module_1.message, "T2", "./a"], + index: 0, + newFileContent: { + "/a.ts": +`type T1 = {}; +type T2 = {}; +export type { T1, T2 };`, + } +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember2.ts b/tests/cases/fourslash/codeFixImportNonExportedMember2.ts new file mode 100644 index 0000000000000..09df5a64b2238 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember2.ts @@ -0,0 +1,20 @@ +/// + +// @module: esnext +// @filename: /a.ts +////export declare function foo(): any; +////declare function bar(): any; + +// @filename: /b.ts +////import { bar } from "./a"; + +goTo.file("/b.ts"); +verify.codeFix({ + description: [ts.Diagnostics.Export_0_from_module_1.message, "bar", "./a"], + index: 0, + newFileContent: { + "/a.ts": +`export declare function foo(): any; +export declare function bar(): any;`, + } +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember3.ts b/tests/cases/fourslash/codeFixImportNonExportedMember3.ts new file mode 100644 index 0000000000000..1bd5dc6a9b0e3 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember3.ts @@ -0,0 +1,23 @@ +/// + +// @module: esnext +// @filename: /a.ts +////let foo = 1, bar = 1; +////export const baz = 1; + +// @filename: /b.ts +////import { bar } from "./a"; + +goTo.file("/b.ts"); +verify.codeFix({ + description: [ts.Diagnostics.Export_0_from_module_1.message, "bar", "./a"], + index: 0, + newFileContent: { + "/a.ts": +`let foo = 1, bar = 1; +export const baz = 1; + +export { bar }; +`, + } +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember4.ts b/tests/cases/fourslash/codeFixImportNonExportedMember4.ts new file mode 100644 index 0000000000000..58f73f536e5ed --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember4.ts @@ -0,0 +1,12 @@ +/// + +// @module: esnext +// @filename: /a.d.ts +////declare function foo(): any; +////declare function bar(): any; + +// @filename: /b.ts +////import { bar } from "./a"; + +goTo.file("/b.ts"); +verify.not.codeFixAvailable("fixImportNonExportedMember"); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember5.ts b/tests/cases/fourslash/codeFixImportNonExportedMember5.ts new file mode 100644 index 0000000000000..b2a6162e871a2 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember5.ts @@ -0,0 +1,12 @@ +/// + +// @moduleResolution: node +// @module: esnext +// @filename: /node_modules/foo/index.js +////function bar() {} + +// @filename: /b.ts +////import { bar } from "./foo"; + +goTo.file("/b.ts"); +verify.not.codeFixAvailable("fixImportNonExportedMember"); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember6.ts b/tests/cases/fourslash/codeFixImportNonExportedMember6.ts new file mode 100644 index 0000000000000..9594271bb34d0 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember6.ts @@ -0,0 +1,25 @@ +/// + +// @module: esnext +// @filename: /a.ts +////let a = 1, b = 1; +////type T = number; +////export type { T }; + +// @filename: /b.ts +////import { b } from "./a"; + +goTo.file("/b.ts"); +verify.codeFix({ + description: [ts.Diagnostics.Export_0_from_module_1.message, "b", "./a"], + index: 0, + newFileContent: { + "/a.ts": +`let a = 1, b = 1; +type T = number; +export type { T }; + +export { b }; +`, + } +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember7.ts b/tests/cases/fourslash/codeFixImportNonExportedMember7.ts new file mode 100644 index 0000000000000..4c30d26f732c8 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember7.ts @@ -0,0 +1,30 @@ +/// + +// @module: esnext +// @filename: /a.ts +////const a = 1 +////const b = 1; +////export { a, b }; +//// +////type T2 = number; +////type T1 = number; +////export type { T1 }; + +// @filename: /b.ts +////import { T2 } from "./a"; + +goTo.file("/b.ts"); +verify.codeFix({ + description: [ts.Diagnostics.Export_0_from_module_1.message, "T2", "./a"], + index: 0, + newFileContent: { + "/a.ts": +`const a = 1 +const b = 1; +export { a, b }; + +type T2 = number; +type T1 = number; +export type { T1, T2 };`, + } +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember8.ts b/tests/cases/fourslash/codeFixImportNonExportedMember8.ts new file mode 100644 index 0000000000000..2dc1c10015a02 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember8.ts @@ -0,0 +1,22 @@ +/// + +// @module: esnext +// @filename: /a.ts +////const a = 1; +////type T = number; +////export { a }; + +// @filename: /b.ts +////import { T } from "./a"; + +goTo.file("/b.ts"); +verify.codeFix({ + description: [ts.Diagnostics.Export_0_from_module_1.message, "T", "./a"], + index: 0, + newFileContent: { + "/a.ts": +`const a = 1; +type T = number; +export { a, T };`, + } +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember9.ts b/tests/cases/fourslash/codeFixImportNonExportedMember9.ts new file mode 100644 index 0000000000000..127718cb3ddf8 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember9.ts @@ -0,0 +1,26 @@ +/// + +// @module: esnext +// @filename: /a.ts +/////** +//// * foo +//// */ +////function foo() {} +////export {}; + +// @filename: /b.ts +////import { foo } from "./a"; + +goTo.file("/b.ts"); +verify.codeFix({ + description: [ts.Diagnostics.Export_0_from_module_1.message, "foo", "./a"], + index: 0, + newFileContent: { + "/a.ts": +`/** + * foo + */ +function foo() {} +export { foo };`, + } +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_all1.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_all1.ts new file mode 100644 index 0000000000000..e9936a8d7105a --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_all1.ts @@ -0,0 +1,22 @@ +/// + +// @module: esnext +// @filename: /a.ts +////declare function foo(): any; +////declare function bar(): any; +////export declare function baz(): any; + +// @filename: /b.ts +////import { foo, bar } from "./a"; + +goTo.file("/b.ts"); +verify.codeFixAll({ + fixId: "fixImportNonExportedMember", + fixAllDescription: ts.Diagnostics.Export_all_referenced_locals.message, + newFileContent: { + "/a.ts": +`export declare function foo(): any; +export declare function bar(): any; +export declare function baz(): any;` + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_all2.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_all2.ts new file mode 100644 index 0000000000000..5912d3dd131fd --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_all2.ts @@ -0,0 +1,24 @@ +/// + +// @module: esnext +// @filename: /a.ts +////declare function foo(): any; +////declare function bar(): any; +////declare function baz(): any; +////export { baz }; + +// @filename: /b.ts +////import { foo, bar } from "./a"; + +goTo.file("/b.ts"); +verify.codeFixAll({ + fixId: "fixImportNonExportedMember", + fixAllDescription: ts.Diagnostics.Export_all_referenced_locals.message, + newFileContent: { + "/a.ts": +`declare function foo(): any; +declare function bar(): any; +declare function baz(): any; +export { baz, foo, bar };` + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_all3.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_all3.ts new file mode 100644 index 0000000000000..56f41d93d5ed5 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_all3.ts @@ -0,0 +1,25 @@ +/// + +// @module: esnext +// @filename: /a.ts +////let a = 1, b = 1; +////let c = 1, d = 1; +////export const e = 1; + +// @filename: /b.ts +////import { b, d } from "./a"; + +goTo.file("/b.ts"); +verify.codeFixAll({ + fixId: "fixImportNonExportedMember", + fixAllDescription: ts.Diagnostics.Export_all_referenced_locals.message, + newFileContent: { + "/a.ts": +`let a = 1, b = 1; +let c = 1, d = 1; +export const e = 1; + +export { b, d }; +` + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_all4.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_all4.ts new file mode 100644 index 0000000000000..7f805c68e73ec --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_all4.ts @@ -0,0 +1,28 @@ +/// + +// @module: esnext +// @filename: /a.ts +////const a = 1; +////export const foo = 1; + +// @filename: /b.ts +////const b = 1; +////export const bar = 1; + +// @filename: /c.ts +////import { a } from "./a"; +////import { b } from "./b"; + +goTo.file("/c.ts"); +verify.codeFixAll({ + fixId: "fixImportNonExportedMember", + fixAllDescription: ts.Diagnostics.Export_all_referenced_locals.message, + newFileContent: { + "/a.ts": +`export const a = 1; +export const foo = 1;`, + "/b.ts": +`export const b = 1; +export const bar = 1;` + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_all5.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_all5.ts new file mode 100644 index 0000000000000..6597d39952d35 --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_all5.ts @@ -0,0 +1,79 @@ +/// + +// @module: esnext +// @filename: /a.ts +////let a = 1, b = 1, c = 1; +////export { b }; +//// +////type T2 = number; +////type T1 = number; +////export type { T2 }; + +// @filename: /b.ts +////let a = 1, b = 1, c = 1; +//// +////type T3 = number; +////type T4 = number; +////export type { T4 }; + +// @filename: /c.ts +////let a = 1, b = 1, c = 1; +//// +////type T5 = number; +////type T6 = number; +////export { a }; + +// @filename: /d.ts +////export const a = 1; +////let b = 1, c = 1, d = 1; +//// +////type T7 = number; +////type T8 = number; + +// @filename: /e.ts +////import { T1, a } from "./a"; +////import { T3, b } from "./b"; +////import { T5, c } from "./c"; +////import { T7, d } from "./d"; + +goTo.file("/e.ts"); +verify.codeFixAll({ + fixId: "fixImportNonExportedMember", + fixAllDescription: ts.Diagnostics.Export_all_referenced_locals.message, + newFileContent: { + "/a.ts": +`let a = 1, b = 1, c = 1; +export { b, a }; + +type T2 = number; +type T1 = number; +export type { T2, T1 };`, + + "/b.ts": +`let a = 1, b = 1, c = 1; + +type T3 = number; +type T4 = number; +export type { T4, T3 }; + +export { b }; +`, + + "/c.ts": +`let a = 1, b = 1, c = 1; + +type T5 = number; +type T6 = number; +export { a, c, T5 };`, + + "/d.ts": +`export const a = 1; +let b = 1, c = 1, d = 1; + +export type T7 = number; +type T8 = number; + +export { d }; +`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_all6.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_all6.ts new file mode 100644 index 0000000000000..cd10a7732fe2b --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_all6.ts @@ -0,0 +1,35 @@ +/// + +// @module: esnext +// @isolatedModules: true +// @filename: /a.ts +////type T1 = {}; +////const a = 1; +////const b = 1; +////export { a }; + +// @filename: /b.ts +////type T2 = {}; +////type T3 = {}; +////export type { T2 }; + +// @filename: /c.ts +////import { b, T1 } from "./a"; +////import { T3 } from "./b"; + +goTo.file("/c.ts"); +verify.codeFixAll({ + fixId: "fixImportNonExportedMember", + fixAllDescription: ts.Diagnostics.Export_all_referenced_locals.message, + newFileContent: { + "/a.ts": +`type T1 = {}; +const a = 1; +const b = 1; +export { a, b, type T1 };`, + "/b.ts": +`type T2 = {}; +type T3 = {}; +export type { T2, T3 };`, + }, +}); diff --git a/tests/cases/fourslash/codeFixImportNonExportedMember_all7.ts b/tests/cases/fourslash/codeFixImportNonExportedMember_all7.ts new file mode 100644 index 0000000000000..2bc48ab7b29ad --- /dev/null +++ b/tests/cases/fourslash/codeFixImportNonExportedMember_all7.ts @@ -0,0 +1,26 @@ +/// + +// @module: esnext +// @filename: /a.ts +////type T1 = {}; +////type T2 = {}; +////type T3 = {}; +////const a = 1; +////export { a, type T1 }; + +// @filename: /b.ts +////import { T2, T3 } from "./a"; + +goTo.file("/b.ts"); +verify.codeFixAll({ + fixId: "fixImportNonExportedMember", + fixAllDescription: ts.Diagnostics.Export_all_referenced_locals.message, + newFileContent: { + "/a.ts": +`type T1 = {}; +type T2 = {}; +type T3 = {}; +const a = 1; +export { a, type T1, type T2, type T3 };`, + }, +});