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)),
),