Add user preference for preferring type-only auto imports (#56090)

This commit is contained in:
Andrew Branch
2023-10-20 14:52:18 -07:00
committed by GitHub
parent 6424e181d4
commit aef4e468d9
7 changed files with 97 additions and 11 deletions
+1
View File
@@ -10014,6 +10014,7 @@ export interface UserPreferences {
readonly interactiveInlayHints?: boolean;
readonly allowRenameOfImportPath?: boolean;
readonly autoImportFileExcludePatterns?: string[];
readonly preferTypeOnlyAutoImports?: boolean;
readonly organizeImportsIgnoreCase?: "auto" | boolean;
readonly organizeImportsCollation?: "ordinal" | "unicode";
readonly organizeImportsLocale?: string;
+7 -3
View File
@@ -3347,12 +3347,16 @@ export class TestState {
this.verifyRangeIs(expectedText, includeWhiteSpace);
}
public verifyCodeFixAll({ fixId, fixAllDescription, newFileContent, commands: expectedCommands }: FourSlashInterface.VerifyCodeFixAllOptions): void {
const fixWithId = ts.find(this.getCodeFixes(this.activeFile.fileName), a => a.fixId === fixId);
public verifyCodeFixAll({ fixId, fixAllDescription, newFileContent, commands: expectedCommands, preferences }: FourSlashInterface.VerifyCodeFixAllOptions): void {
if (this.testType === FourSlashTestType.Server && preferences) {
this.configure(preferences);
}
const fixWithId = ts.find(this.getCodeFixes(this.activeFile.fileName, /*errorCode*/ undefined, preferences), a => a.fixId === fixId);
ts.Debug.assert(fixWithId !== undefined, "No available code fix has the expected id. Fix All is not available if there is only one potentially fixable diagnostic present.", () => `Expected '${fixId}'. Available actions:\n${ts.mapDefined(this.getCodeFixes(this.activeFile.fileName), a => `${a.fixName} (${a.fixId || "no fix id"})`).join("\n")}`);
ts.Debug.assertEqual(fixWithId.fixAllDescription, fixAllDescription);
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings, ts.emptyOptions);
const { changes, commands } = this.languageService.getCombinedCodeFix({ type: "file", fileName: this.activeFile.fileName }, fixId, this.formatCodeSettings, preferences || ts.emptyOptions);
assert.deepEqual<readonly {}[] | undefined>(commands, expectedCommands);
this.verifyNewContent({ newFileContent }, changes);
}
+1
View File
@@ -1869,6 +1869,7 @@ export interface VerifyCodeFixAllOptions {
fixAllDescription: string;
newFileContent: NewFileContent;
commands: readonly {}[];
preferences?: ts.UserPreferences;
}
export interface VerifyRefactorOptions {
+14 -7
View File
@@ -389,6 +389,7 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
namedImports && arrayFrom(namedImports.entries(), ([name, addAsTypeOnly]) => ({ addAsTypeOnly, name })),
namespaceLikeImport,
compilerOptions,
preferences,
);
newDeclarations = combine(newDeclarations, declarations);
});
@@ -1348,6 +1349,7 @@ function codeActionForFixWorker(
namedImports,
namespaceLikeImport,
program.getCompilerOptions(),
preferences,
),
/*blankLineBetween*/ true,
preferences,
@@ -1505,7 +1507,7 @@ function doAddExistingFix(
const newSpecifiers = stableSort(
namedImports.map(namedImport =>
factory.createImportSpecifier(
(!clause.isTypeOnly || promoteFromTypeOnly) && needsTypeOnly(namedImport),
(!clause.isTypeOnly || promoteFromTypeOnly) && shouldUseTypeOnly(namedImport, preferences),
/*propertyName*/ undefined,
factory.createIdentifier(namedImport.name),
)
@@ -1604,6 +1606,10 @@ function needsTypeOnly({ addAsTypeOnly }: { addAsTypeOnly: AddAsTypeOnly; }): bo
return addAsTypeOnly === AddAsTypeOnly.Required;
}
function shouldUseTypeOnly(info: { addAsTypeOnly: AddAsTypeOnly; }, preferences: UserPreferences): boolean {
return needsTypeOnly(info) || !!preferences.preferTypeOnlyAutoImports && info.addAsTypeOnly !== AddAsTypeOnly.NotAllowed;
}
function getNewImports(
moduleSpecifier: string,
quotePreference: QuotePreference,
@@ -1611,6 +1617,7 @@ function getNewImports(
namedImports: readonly Import[] | undefined,
namespaceLikeImport: Import & { importKind: ImportKind.CommonJS | ImportKind.Namespace; } | undefined,
compilerOptions: CompilerOptions,
preferences: UserPreferences,
): AnyImportSyntax | readonly AnyImportSyntax[] {
const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
let statements: AnyImportSyntax | readonly AnyImportSyntax[] | undefined;
@@ -1618,18 +1625,18 @@ function getNewImports(
// `verbatimModuleSyntax` should prefer top-level `import type` -
// even though it's not an error, it would add unnecessary runtime emit.
const topLevelTypeOnly = (!defaultImport || needsTypeOnly(defaultImport)) && every(namedImports, needsTypeOnly) ||
compilerOptions.verbatimModuleSyntax &&
(compilerOptions.verbatimModuleSyntax || preferences.preferTypeOnlyAutoImports) &&
defaultImport?.addAsTypeOnly !== AddAsTypeOnly.NotAllowed &&
!some(namedImports, i => i.addAsTypeOnly === AddAsTypeOnly.NotAllowed);
statements = combine(
statements,
makeImport(
defaultImport && factory.createIdentifier(defaultImport.name),
namedImports?.map(({ addAsTypeOnly, name }) =>
namedImports?.map(namedImport =>
factory.createImportSpecifier(
!topLevelTypeOnly && addAsTypeOnly === AddAsTypeOnly.Required,
!topLevelTypeOnly && shouldUseTypeOnly(namedImport, preferences),
/*propertyName*/ undefined,
factory.createIdentifier(name),
factory.createIdentifier(namedImport.name),
)
),
moduleSpecifier,
@@ -1643,14 +1650,14 @@ function getNewImports(
const declaration = namespaceLikeImport.importKind === ImportKind.CommonJS
? factory.createImportEqualsDeclaration(
/*modifiers*/ undefined,
needsTypeOnly(namespaceLikeImport),
shouldUseTypeOnly(namespaceLikeImport, preferences),
factory.createIdentifier(namespaceLikeImport.name),
factory.createExternalModuleReference(quotedModuleSpecifier),
)
: factory.createImportDeclaration(
/*modifiers*/ undefined,
factory.createImportClause(
needsTypeOnly(namespaceLikeImport),
shouldUseTypeOnly(namespaceLikeImport, preferences),
/*name*/ undefined,
factory.createNamespaceImport(factory.createIdentifier(namespaceLikeImport.name)),
),
+1
View File
@@ -8742,6 +8742,7 @@ declare namespace ts {
readonly interactiveInlayHints?: boolean;
readonly allowRenameOfImportPath?: boolean;
readonly autoImportFileExcludePatterns?: string[];
readonly preferTypeOnlyAutoImports?: boolean;
readonly organizeImportsIgnoreCase?: "auto" | boolean;
readonly organizeImportsCollation?: "ordinal" | "unicode";
readonly organizeImportsLocale?: string;
@@ -0,0 +1,71 @@
// @module: esnext
// @moduleResolution: bundler
// @Filename: /a.ts
//// export class A {}
//// export class B {}
// @Filename: /b.ts
//// let x: A/*b*/;
// @Filename: /c.ts
//// import { A } from "./a";
//// new A();
//// let x: B/*c*/;
// @Filename: /d.ts
//// new A();
//// let x: B;
// @Filename: /ns.ts
//// export * as default from "./a";
// @Filename: /e.ts
//// let x: /*e*/ns.A;
goTo.marker("b");
verify.importFixAtPosition([
`import type { A } from "./a";
let x: A;`],
/*errorCode*/ undefined,
{
preferTypeOnlyAutoImports: true,
}
);
goTo.marker("c");
verify.importFixAtPosition([
`import { A, type B } from "./a";
new A();
let x: B;`],
/*errorCode*/ undefined,
{
preferTypeOnlyAutoImports: true,
}
);
goTo.file("/d.ts");
verify.codeFixAll({
fixId: "fixMissingImport",
fixAllDescription: "Add all missing imports",
newFileContent:
`import { A, type B } from "./a";
new A();
let x: B;`,
preferences: {
preferTypeOnlyAutoImports: true,
},
});
goTo.marker("e");
verify.importFixAtPosition([
`import type ns from "./ns";
let x: ns.A;`],
/*errorCode*/ undefined,
{
preferTypeOnlyAutoImports: true,
}
);
+2 -1
View File
@@ -363,7 +363,7 @@ declare namespace FourSlashInterface {
docCommentTemplateAt(markerName: string | FourSlashInterface.Marker, expectedOffset: number, expectedText: string, options?: VerifyDocCommentTemplateOptions): void;
noDocCommentTemplateAt(markerName: string | FourSlashInterface.Marker): void;
rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void;
codeFixAll(options: { fixId: string, fixAllDescription: string, newFileContent: NewFileContent, commands?: {}[] }): void;
codeFixAll(options: { fixId: string, fixAllDescription: string, newFileContent: NewFileContent, commands?: {}[], preferences?: UserPreferences }): void;
fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, actionName: string, formattingOptions?: FormatCodeOptions): void;
rangeIs(expectedText: string, includeWhiteSpace?: boolean): void;
fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, formattingOptions?: FormatCodeOptions): void;
@@ -663,6 +663,7 @@ declare namespace FourSlashInterface {
readonly providePrefixAndSuffixTextForRename?: boolean;
readonly allowRenameOfImportPath?: boolean;
readonly autoImportFileExcludePatterns?: readonly string[];
readonly preferTypeOnlyAutoImports?: boolean;
readonly organizeImportsIgnoreCase?: "auto" | boolean;
readonly organizeImportsCollation?: "unicode" | "ordinal";
readonly organizeImportsLocale?: string;