mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-11 19:27:35 -06:00
Refactor named imports to default instead of namespace when esModuleInterop is on and module is an export= (#47744)
This commit is contained in:
parent
8ddead50eb
commit
9c3b41d3cc
@ -1252,11 +1252,11 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getOwnValues<T>(sparseArray: T[]): T[] {
|
||||
export function getOwnValues<T>(collection: MapLike<T> | T[]): T[] {
|
||||
const values: T[] = [];
|
||||
for (const key in sparseArray) {
|
||||
if (hasOwnProperty.call(sparseArray, key)) {
|
||||
values.push(sparseArray[key]);
|
||||
for (const key in collection) {
|
||||
if (hasOwnProperty.call(collection, key)) {
|
||||
values.push((collection as MapLike<T>)[key]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7123,6 +7123,10 @@
|
||||
"category": "Message",
|
||||
"code": 95169
|
||||
},
|
||||
"Convert named imports to default import": {
|
||||
"category": "Message",
|
||||
"code": 95170
|
||||
},
|
||||
|
||||
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
|
||||
"category": "Error",
|
||||
|
||||
@ -2,46 +2,48 @@
|
||||
namespace ts.refactor {
|
||||
const refactorName = "Convert import";
|
||||
|
||||
const namespaceToNamedAction = {
|
||||
name: "Convert namespace import to named imports",
|
||||
description: Diagnostics.Convert_namespace_import_to_named_imports.message,
|
||||
kind: "refactor.rewrite.import.named",
|
||||
};
|
||||
const namedToNamespaceAction = {
|
||||
name: "Convert named imports to namespace import",
|
||||
description: Diagnostics.Convert_named_imports_to_namespace_import.message,
|
||||
kind: "refactor.rewrite.import.namespace",
|
||||
const actions = {
|
||||
[ImportKind.Named]: {
|
||||
name: "Convert namespace import to named imports",
|
||||
description: Diagnostics.Convert_namespace_import_to_named_imports.message,
|
||||
kind: "refactor.rewrite.import.named",
|
||||
},
|
||||
[ImportKind.Namespace]: {
|
||||
name: "Convert named imports to namespace import",
|
||||
description: Diagnostics.Convert_named_imports_to_namespace_import.message,
|
||||
kind: "refactor.rewrite.import.namespace",
|
||||
},
|
||||
[ImportKind.Default]: {
|
||||
name: "Convert named imports to default import",
|
||||
description: Diagnostics.Convert_named_imports_to_default_import.message,
|
||||
kind: "refactor.rewrite.import.default",
|
||||
},
|
||||
};
|
||||
|
||||
registerRefactor(refactorName, {
|
||||
kinds: [
|
||||
namespaceToNamedAction.kind,
|
||||
namedToNamespaceAction.kind
|
||||
],
|
||||
kinds: getOwnValues(actions).map(a => a.kind),
|
||||
getAvailableActions: function getRefactorActionsToConvertBetweenNamedAndNamespacedImports(context): readonly ApplicableRefactorInfo[] {
|
||||
const info = getImportToConvert(context, context.triggerReason === "invoked");
|
||||
const info = getImportConversionInfo(context, context.triggerReason === "invoked");
|
||||
if (!info) return emptyArray;
|
||||
|
||||
if (!isRefactorErrorInfo(info)) {
|
||||
const namespaceImport = info.kind === SyntaxKind.NamespaceImport;
|
||||
const action = namespaceImport ? namespaceToNamedAction : namedToNamespaceAction;
|
||||
const action = actions[info.convertTo];
|
||||
return [{ name: refactorName, description: action.description, actions: [action] }];
|
||||
}
|
||||
|
||||
if (context.preferences.provideRefactorNotApplicableReason) {
|
||||
return [
|
||||
{ name: refactorName, description: namespaceToNamedAction.description,
|
||||
actions: [{ ...namespaceToNamedAction, notApplicableReason: info.error }] },
|
||||
{ name: refactorName, description: namedToNamespaceAction.description,
|
||||
actions: [{ ...namedToNamespaceAction, notApplicableReason: info.error }] }
|
||||
];
|
||||
return getOwnValues(actions).map(action => ({
|
||||
name: refactorName,
|
||||
description: action.description,
|
||||
actions: [{ ...action, notApplicableReason: info.error }]
|
||||
}));
|
||||
}
|
||||
|
||||
return emptyArray;
|
||||
},
|
||||
getEditsForAction: function getRefactorEditsToConvertBetweenNamedAndNamespacedImports(context, actionName): RefactorEditInfo {
|
||||
Debug.assert(actionName === namespaceToNamedAction.name || actionName === namedToNamespaceAction.name, "Unexpected action name");
|
||||
const info = getImportToConvert(context);
|
||||
Debug.assert(some(getOwnValues(actions), action => action.name === actionName), "Unexpected action name");
|
||||
const info = getImportConversionInfo(context);
|
||||
Debug.assert(info && !isRefactorErrorInfo(info), "Expected applicable refactor info");
|
||||
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, t, info));
|
||||
return { edits, renameFilename: undefined, renameLocation: undefined };
|
||||
@ -49,7 +51,12 @@ namespace ts.refactor {
|
||||
});
|
||||
|
||||
// Can convert imports of the form `import * as m from "m";` or `import d, { x, y } from "m";`.
|
||||
function getImportToConvert(context: RefactorContext, considerPartialSpans = true): NamedImportBindings | RefactorErrorInfo | undefined {
|
||||
type ImportConversionInfo =
|
||||
| { convertTo: ImportKind.Default, import: NamedImports }
|
||||
| { convertTo: ImportKind.Namespace, import: NamedImports }
|
||||
| { convertTo: ImportKind.Named, import: NamespaceImport };
|
||||
|
||||
function getImportConversionInfo(context: RefactorContext, considerPartialSpans = true): ImportConversionInfo | RefactorErrorInfo | undefined {
|
||||
const { file } = context;
|
||||
const span = getRefactorContextSpan(context);
|
||||
const token = getTokenAtPosition(file, span.start);
|
||||
@ -69,16 +76,25 @@ namespace ts.refactor {
|
||||
return { error: getLocaleSpecificMessage(Diagnostics.Could_not_find_namespace_import_or_named_imports) };
|
||||
}
|
||||
|
||||
return importClause.namedBindings;
|
||||
if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) {
|
||||
return { convertTo: ImportKind.Named, import: importClause.namedBindings };
|
||||
}
|
||||
const compilerOptions = context.program.getCompilerOptions();
|
||||
const shouldUseDefault = getAllowSyntheticDefaultImports(compilerOptions)
|
||||
&& isExportEqualsModule(importClause.parent.moduleSpecifier, context.program.getTypeChecker());
|
||||
|
||||
return shouldUseDefault
|
||||
? { convertTo: ImportKind.Default, import: importClause.namedBindings }
|
||||
: { convertTo: ImportKind.Namespace, import: importClause.namedBindings };
|
||||
}
|
||||
|
||||
function doChange(sourceFile: SourceFile, program: Program, changes: textChanges.ChangeTracker, toConvert: NamedImportBindings): void {
|
||||
function doChange(sourceFile: SourceFile, program: Program, changes: textChanges.ChangeTracker, info: ImportConversionInfo): void {
|
||||
const checker = program.getTypeChecker();
|
||||
if (toConvert.kind === SyntaxKind.NamespaceImport) {
|
||||
doChangeNamespaceToNamed(sourceFile, checker, changes, toConvert, getAllowSyntheticDefaultImports(program.getCompilerOptions()));
|
||||
if (info.convertTo === ImportKind.Named) {
|
||||
doChangeNamespaceToNamed(sourceFile, checker, changes, info.import, getAllowSyntheticDefaultImports(program.getCompilerOptions()));
|
||||
}
|
||||
else {
|
||||
doChangeNamedToNamespace(sourceFile, checker, changes, toConvert);
|
||||
doChangeNamedToNamespaceOrDefault(sourceFile, checker, changes, info.import, info.convertTo === ImportKind.Default);
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +153,7 @@ namespace ts.refactor {
|
||||
return isPropertyAccessExpression(propertyAccessOrQualifiedName) ? propertyAccessOrQualifiedName.expression : propertyAccessOrQualifiedName.left;
|
||||
}
|
||||
|
||||
function doChangeNamedToNamespace(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, toConvert: NamedImports): void {
|
||||
function doChangeNamedToNamespaceOrDefault(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, toConvert: NamedImports, shouldUseDefault: boolean) {
|
||||
const importDecl = toConvert.parent.parent;
|
||||
const { moduleSpecifier } = importDecl;
|
||||
|
||||
@ -188,7 +204,9 @@ namespace ts.refactor {
|
||||
});
|
||||
}
|
||||
|
||||
changes.replaceNode(sourceFile, toConvert, factory.createNamespaceImport(factory.createIdentifier(namespaceImportName)));
|
||||
changes.replaceNode(sourceFile, toConvert, shouldUseDefault
|
||||
? factory.createIdentifier(namespaceImportName)
|
||||
: factory.createNamespaceImport(factory.createIdentifier(namespaceImportName)));
|
||||
if (neededNamedImports.size) {
|
||||
const newNamedImports: ImportSpecifier[] = arrayFrom(neededNamedImports.values()).map(element =>
|
||||
factory.createImportSpecifier(element.isTypeOnly, element.propertyName && factory.createIdentifier(element.propertyName.text), factory.createIdentifier(element.name.text)));
|
||||
@ -196,6 +214,13 @@ namespace ts.refactor {
|
||||
}
|
||||
}
|
||||
|
||||
function isExportEqualsModule(moduleSpecifier: Expression, checker: TypeChecker) {
|
||||
const externalModule = checker.resolveExternalModuleName(moduleSpecifier);
|
||||
if (!externalModule) return false;
|
||||
const exportEquals = checker.resolveExternalModuleSymbol(externalModule);
|
||||
return externalModule !== exportEquals;
|
||||
}
|
||||
|
||||
function updateImport(old: ImportDeclaration, defaultImportName: Identifier | undefined, elements: readonly ImportSpecifier[] | undefined): ImportDeclaration {
|
||||
return factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined,
|
||||
factory.createImportClause(/*isTypeOnly*/ false, defaultImportName, elements && elements.length ? factory.createNamedImports(elements) : undefined), old.moduleSpecifier, /*assertClause*/ undefined);
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @esModuleInterop: true
|
||||
|
||||
// @Filename: /process.d.ts
|
||||
//// declare module "process" {
|
||||
//// interface Process {
|
||||
//// pid: number;
|
||||
//// addListener(event: string, listener: (...args: any[]) => void): void;
|
||||
//// }
|
||||
//// var process: Process;
|
||||
//// export = process;
|
||||
//// }
|
||||
|
||||
// @Filename: /url.d.ts
|
||||
//// declare module "url" {
|
||||
//// export function parse(urlStr: string): any;
|
||||
//// }
|
||||
|
||||
// @Filename: /index.ts
|
||||
//// [|import { pid, addListener } from "process";|]
|
||||
//// addListener("message", (m) => {
|
||||
//// console.log(pid);
|
||||
//// });
|
||||
|
||||
// @Filename: /a.ts
|
||||
//// [|import { parse } from "url";|]
|
||||
//// parse("https://www.typescriptlang.org");
|
||||
|
||||
goTo.selectRange(test.ranges()[0]);
|
||||
edit.applyRefactor({
|
||||
refactorName: "Convert import",
|
||||
actionName: "Convert named imports to default import",
|
||||
actionDescription: "Convert named imports to default import",
|
||||
newContent: `import process from "process";
|
||||
process.addListener("message", (m) => {
|
||||
console.log(process.pid);
|
||||
});`,
|
||||
});
|
||||
verify.not.refactorAvailable("Convert import", "Convert named imports to namespace import");
|
||||
|
||||
goTo.selectRange(test.ranges()[1]);
|
||||
edit.applyRefactor({
|
||||
refactorName: "Convert import",
|
||||
actionName: "Convert named imports to namespace import",
|
||||
actionDescription: "Convert named imports to namespace import",
|
||||
newContent: `import * as url from "url";
|
||||
url.parse("https://www.typescriptlang.org");`,
|
||||
});
|
||||
verify.not.refactorAvailable("Convert import", "Convert named imports to default import");
|
||||
Loading…
x
Reference in New Issue
Block a user