Minor cleanups in importFixes (#23995)
This commit is contained in:
parent
ac0657a5d7
commit
ee8337d8e4
2 changed files with 53 additions and 64 deletions
|
@ -965,7 +965,8 @@ namespace ts {
|
||||||
export function append<T>(to: T[], value: T | undefined): T[];
|
export function append<T>(to: T[], value: T | undefined): T[];
|
||||||
export function append<T>(to: T[] | undefined, value: T): T[];
|
export function append<T>(to: T[] | undefined, value: T): T[];
|
||||||
export function append<T>(to: T[] | undefined, value: T | undefined): T[] | undefined;
|
export function append<T>(to: T[] | undefined, value: T | undefined): T[] | undefined;
|
||||||
export function append<T>(to: T[] | undefined, value: T | undefined): T[] | undefined {
|
export function append<T>(to: Push<T>, value: T | undefined): void;
|
||||||
|
export function append<T>(to: T[], value: T | undefined): T[] | undefined {
|
||||||
if (value === undefined) return to;
|
if (value === undefined) return to;
|
||||||
if (to === undefined) return [value];
|
if (to === undefined) return [value];
|
||||||
to.push(value);
|
to.push(value);
|
||||||
|
|
|
@ -10,15 +10,14 @@ namespace ts.codefix {
|
||||||
Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code,
|
Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code,
|
||||||
Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here.code,
|
Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here.code,
|
||||||
],
|
],
|
||||||
getCodeActions: getImportCodeActions,
|
getCodeActions: context => context.errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code
|
||||||
|
? getActionsForUMDImport(context)
|
||||||
|
: getActionsForNonUMDImport(context),
|
||||||
// TODO: GH#20315
|
// TODO: GH#20315
|
||||||
fixIds: [],
|
fixIds: [],
|
||||||
getAllCodeActions: notImplemented,
|
getAllCodeActions: notImplemented,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Map from module Id to an array of import declarations in that module.
|
|
||||||
type ImportDeclarationMap = ExistingImportInfo[][];
|
|
||||||
|
|
||||||
interface SymbolContext extends textChanges.TextChangesContext {
|
interface SymbolContext extends textChanges.TextChangesContext {
|
||||||
sourceFile: SourceFile;
|
sourceFile: SourceFile;
|
||||||
symbolName: string;
|
symbolName: string;
|
||||||
|
@ -30,7 +29,6 @@ namespace ts.codefix {
|
||||||
checker: TypeChecker;
|
checker: TypeChecker;
|
||||||
compilerOptions: CompilerOptions;
|
compilerOptions: CompilerOptions;
|
||||||
getCanonicalFileName: GetCanonicalFileName;
|
getCanonicalFileName: GetCanonicalFileName;
|
||||||
cachedImportDeclarations?: ImportDeclarationMap;
|
|
||||||
preferences: UserPreferences;
|
preferences: UserPreferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +48,6 @@ namespace ts.codefix {
|
||||||
program,
|
program,
|
||||||
checker,
|
checker,
|
||||||
compilerOptions: program.getCompilerOptions(),
|
compilerOptions: program.getCompilerOptions(),
|
||||||
cachedImportDeclarations: [],
|
|
||||||
getCanonicalFileName: createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(context.host)),
|
getCanonicalFileName: createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(context.host)),
|
||||||
symbolName,
|
symbolName,
|
||||||
symbolToken,
|
symbolToken,
|
||||||
|
@ -130,8 +127,19 @@ namespace ts.codefix {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCodeActionsForImport_separateExistingAndNew(exportInfos: ReadonlyArray<SymbolExportInfo>, context: ImportCodeFixContext, useExisting: Push<CodeFixAction>, addNew: Push<CodeFixAction>): void {
|
function getCodeActionsForImport_separateExistingAndNew(exportInfos: ReadonlyArray<SymbolExportInfo>, context: ImportCodeFixContext, useExisting: Push<CodeFixAction>, addNew: Push<CodeFixAction>): void {
|
||||||
const existingImports = flatMap(exportInfos, info =>
|
const existingImports = flatMap(exportInfos, info => getExistingImportDeclarations(info, context.checker, context.sourceFile));
|
||||||
getImportDeclarations(info, context.checker, context.sourceFile, context.cachedImportDeclarations));
|
|
||||||
|
append(useExisting, tryUseExistingNamespaceImport(existingImports, context, context.symbolToken, context.checker));
|
||||||
|
const addToExisting = tryAddToExistingImport(existingImports, context);
|
||||||
|
|
||||||
|
if (addToExisting) {
|
||||||
|
useExisting.push(addToExisting);
|
||||||
|
}
|
||||||
|
else { // Don't bother providing an action to add a new import if we can add to an existing one.
|
||||||
|
getCodeActionsForAddImport(exportInfos, context, existingImports, addNew);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function tryUseExistingNamespaceImport(existingImports: ReadonlyArray<ExistingImportInfo>, context: SymbolContext, symbolToken: Node | undefined, checker: TypeChecker): CodeFixAction | undefined {
|
||||||
// It is possible that multiple import statements with the same specifier exist in the file.
|
// It is possible that multiple import statements with the same specifier exist in the file.
|
||||||
// e.g.
|
// e.g.
|
||||||
//
|
//
|
||||||
|
@ -144,18 +152,26 @@ namespace ts.codefix {
|
||||||
// 1. change "member3" to "ns.member3"
|
// 1. change "member3" to "ns.member3"
|
||||||
// 2. add "member3" to the second import statement's import list
|
// 2. add "member3" to the second import statement's import list
|
||||||
// and it is up to the user to decide which one fits best.
|
// and it is up to the user to decide which one fits best.
|
||||||
if (context.symbolToken && isIdentifier(context.symbolToken)) {
|
return !symbolToken || !isIdentifier(symbolToken) ? undefined : firstDefined(existingImports, ({ declaration }) => {
|
||||||
for (const { declaration } of existingImports) {
|
const namespace = getNamespaceImportName(declaration);
|
||||||
const namespace = getNamespaceImportName(declaration);
|
if (namespace) {
|
||||||
if (namespace) {
|
const moduleSymbol = namespace && checker.getAliasedSymbol(checker.getSymbolAtLocation(namespace)!);
|
||||||
const moduleSymbol = context.checker.getAliasedSymbol(context.checker.getSymbolAtLocation(namespace)!);
|
if (moduleSymbol && moduleSymbol.exports!.has(escapeLeadingUnderscores(context.symbolName))) {
|
||||||
if (moduleSymbol && moduleSymbol.exports!.has(escapeLeadingUnderscores(context.symbolName))) {
|
return getCodeActionForUseExistingNamespaceImport(namespace.text, context, symbolToken);
|
||||||
useExisting.push(getCodeActionForUseExistingNamespaceImport(namespace.text, context, context.symbolToken));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
getCodeActionsForAddImport(exportInfos, context, existingImports, useExisting, addNew);
|
}
|
||||||
|
function tryAddToExistingImport(existingImports: ReadonlyArray<ExistingImportInfo>, context: SymbolContext): CodeFixAction | undefined {
|
||||||
|
return firstDefined(existingImports, ({ declaration, importKind }) => {
|
||||||
|
if (declaration.kind === SyntaxKind.ImportDeclaration && declaration.importClause) {
|
||||||
|
const changes = tryUpdateExistingImport(context, declaration.importClause, importKind);
|
||||||
|
if (changes) {
|
||||||
|
const moduleSpecifierWithoutQuotes = stripQuotes(declaration.moduleSpecifier.getText());
|
||||||
|
return createCodeAction(Diagnostics.Add_0_to_existing_import_declaration_from_1, [context.symbolName, moduleSpecifierWithoutQuotes], changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNamespaceImportName(declaration: AnyImportSyntax): Identifier | undefined {
|
function getNamespaceImportName(declaration: AnyImportSyntax): Identifier | undefined {
|
||||||
|
@ -168,18 +184,12 @@ namespace ts.codefix {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(anhans): This doesn't seem important to cache... just use an iterator instead of creating a new array?
|
function getExistingImportDeclarations({ moduleSymbol, importKind }: SymbolExportInfo, checker: TypeChecker, { imports }: SourceFile): ReadonlyArray<ExistingImportInfo> {
|
||||||
function getImportDeclarations({ moduleSymbol, importKind }: SymbolExportInfo, checker: TypeChecker, { imports }: SourceFile, cachedImportDeclarations: ImportDeclarationMap = []): ReadonlyArray<ExistingImportInfo> {
|
return mapDefined<StringLiteralLike, ExistingImportInfo>(imports, moduleSpecifier => {
|
||||||
const moduleSymbolId = getUniqueSymbolId(moduleSymbol, checker);
|
const i = importFromModuleSpecifier(moduleSpecifier);
|
||||||
let cached = cachedImportDeclarations[moduleSymbolId];
|
return (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration)
|
||||||
if (!cached) {
|
&& checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind } : undefined;
|
||||||
cached = cachedImportDeclarations[moduleSymbolId] = mapDefined<StringLiteralLike, ExistingImportInfo>(imports, moduleSpecifier => {
|
});
|
||||||
const i = importFromModuleSpecifier(moduleSpecifier);
|
|
||||||
return (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration)
|
|
||||||
&& checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind } : undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return cached;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCodeActionForNewImport(context: SymbolContext & { preferences: UserPreferences }, { moduleSpecifier, importKind }: NewImportInfo): CodeFixAction {
|
function getCodeActionForNewImport(context: SymbolContext & { preferences: UserPreferences }, { moduleSpecifier, importKind }: NewImportInfo): CodeFixAction {
|
||||||
|
@ -258,23 +268,8 @@ namespace ts.codefix {
|
||||||
exportInfos: ReadonlyArray<SymbolExportInfo>,
|
exportInfos: ReadonlyArray<SymbolExportInfo>,
|
||||||
ctx: ImportCodeFixContext,
|
ctx: ImportCodeFixContext,
|
||||||
existingImports: ReadonlyArray<ExistingImportInfo>,
|
existingImports: ReadonlyArray<ExistingImportInfo>,
|
||||||
useExisting: Push<CodeFixAction>,
|
|
||||||
addNew: Push<CodeFixAction>,
|
addNew: Push<CodeFixAction>,
|
||||||
): void {
|
): void {
|
||||||
const fromExistingImport = firstDefined(existingImports, ({ declaration, importKind }) => {
|
|
||||||
if (declaration.kind === SyntaxKind.ImportDeclaration && declaration.importClause) {
|
|
||||||
const changes = tryUpdateExistingImport(ctx, (isImportClause(declaration.importClause) && declaration.importClause || undefined)!, importKind); // TODO: GH#18217
|
|
||||||
if (changes) {
|
|
||||||
const moduleSpecifierWithoutQuotes = stripQuotes(declaration.moduleSpecifier.getText());
|
|
||||||
return createCodeAction(Diagnostics.Add_0_to_existing_import_declaration_from_1, [ctx.symbolName, moduleSpecifierWithoutQuotes], changes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (fromExistingImport) {
|
|
||||||
useExisting.push(fromExistingImport);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingDeclaration = firstDefined(existingImports, newImportInfoFromExistingSpecifier);
|
const existingDeclaration = firstDefined(existingImports, newImportInfoFromExistingSpecifier);
|
||||||
const newImportInfos = existingDeclaration
|
const newImportInfos = existingDeclaration
|
||||||
? [existingDeclaration]
|
? [existingDeclaration]
|
||||||
|
@ -348,12 +343,6 @@ namespace ts.codefix {
|
||||||
return createCodeAction(Diagnostics.Change_0_to_1, [symbolName, `${namespacePrefix}.${symbolName}`], changes);
|
return createCodeAction(Diagnostics.Change_0_to_1, [symbolName, `${namespacePrefix}.${symbolName}`], changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImportCodeActions(context: CodeFixContext): CodeFixAction[] | undefined {
|
|
||||||
return context.errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code
|
|
||||||
? getActionsForUMDImport(context)
|
|
||||||
: getActionsForNonUMDImport(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActionsForUMDImport(context: CodeFixContext): CodeFixAction[] | undefined {
|
function getActionsForUMDImport(context: CodeFixContext): CodeFixAction[] | undefined {
|
||||||
const token = getTokenAtPosition(context.sourceFile, context.span.start, /*includeJsDocComment*/ false);
|
const token = getTokenAtPosition(context.sourceFile, context.span.start, /*includeJsDocComment*/ false);
|
||||||
const checker = context.program.getTypeChecker();
|
const checker = context.program.getTypeChecker();
|
||||||
|
@ -422,11 +411,18 @@ namespace ts.codefix {
|
||||||
? checker.getJsxNamespace()
|
? checker.getJsxNamespace()
|
||||||
: isIdentifier(symbolToken) ? symbolToken.text : undefined;
|
: isIdentifier(symbolToken) ? symbolToken.text : undefined;
|
||||||
if (!symbolName) return undefined;
|
if (!symbolName) return undefined;
|
||||||
|
|
||||||
// "default" is a keyword and not a legal identifier for the import, so we don't expect it here
|
// "default" is a keyword and not a legal identifier for the import, so we don't expect it here
|
||||||
Debug.assert(symbolName !== "default");
|
Debug.assert(symbolName !== "default");
|
||||||
const currentTokenMeaning = getMeaningFromLocation(symbolToken);
|
|
||||||
|
|
||||||
|
const addToExistingDeclaration: CodeFixAction[] = [];
|
||||||
|
const addNewDeclaration: CodeFixAction[] = [];
|
||||||
|
getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, checker, program).forEach(exportInfos => {
|
||||||
|
getCodeActionsForImport_separateExistingAndNew(exportInfos, convertToImportCodeFixContext(context, symbolToken, symbolName), addToExistingDeclaration, addNewDeclaration);
|
||||||
|
});
|
||||||
|
return [...addToExistingDeclaration, ...addNewDeclaration];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExportInfos(symbolName: string, currentTokenMeaning: SemanticMeaning, cancellationToken: CancellationToken, sourceFile: SourceFile, checker: TypeChecker, program: Program): ReadonlyMap<ReadonlyArray<SymbolExportInfo>> {
|
||||||
// For each original symbol, keep all re-exports of that symbol together so we can call `getCodeActionsForImport` on the whole group at once.
|
// For each original symbol, keep all re-exports of that symbol together so we can call `getCodeActionsForImport` on the whole group at once.
|
||||||
// Maps symbol id to info for modules providing that symbol (original export + re-exports).
|
// Maps symbol id to info for modules providing that symbol (original export + re-exports).
|
||||||
const originalSymbolToExportInfos = createMultiMap<SymbolExportInfo>();
|
const originalSymbolToExportInfos = createMultiMap<SymbolExportInfo>();
|
||||||
|
@ -464,20 +460,12 @@ namespace ts.codefix {
|
||||||
}
|
}
|
||||||
else if (isExportSpecifier(declaration)) {
|
else if (isExportSpecifier(declaration)) {
|
||||||
Debug.assert(declaration.name.escapedText === InternalSymbolName.Default);
|
Debug.assert(declaration.name.escapedText === InternalSymbolName.Default);
|
||||||
if (declaration.propertyName) {
|
return declaration.propertyName && declaration.propertyName.escapedText;
|
||||||
return declaration.propertyName.escapedText;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return originalSymbolToExportInfos;
|
||||||
const addToExistingDeclaration: CodeFixAction[] = [];
|
|
||||||
const addNewDeclaration: CodeFixAction[] = [];
|
|
||||||
originalSymbolToExportInfos.forEach(exportInfos => {
|
|
||||||
getCodeActionsForImport_separateExistingAndNew(exportInfos, convertToImportCodeFixContext(context, symbolToken, symbolName), addToExistingDeclaration, addNewDeclaration);
|
|
||||||
});
|
|
||||||
return [...addToExistingDeclaration, ...addNewDeclaration];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkSymbolHasMeaning({ declarations }: Symbol, meaning: SemanticMeaning): boolean {
|
function checkSymbolHasMeaning({ declarations }: Symbol, meaning: SemanticMeaning): boolean {
|
||||||
|
|
Loading…
Reference in a new issue