Support code-fix-all for importFixes (#25137)

* Support code-fix-all for importFixes

* Change description

* Update API (#25283)
This commit is contained in:
Andy 2018-07-03 15:19:15 -07:00 committed by GitHub
parent 726412cf6a
commit 064ecd449e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 369 additions and 268 deletions

View File

@ -531,7 +531,7 @@ namespace ts {
return result;
}
export function flatMapIterator<T, U>(iter: Iterator<T>, mapfn: (x: T) => U[] | Iterator<U> | undefined): Iterator<U> {
export function flatMapIterator<T, U>(iter: Iterator<T>, mapfn: (x: T) => ReadonlyArray<U> | Iterator<U> | undefined): Iterator<U> {
const first = iter.next();
if (first.done) {
return emptyIterator;
@ -1418,7 +1418,9 @@ namespace ts {
return typeof text === "string";
}
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined {
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined;
export function tryCast<T>(value: T, test: (value: T) => boolean): T | undefined;
export function tryCast<T>(value: T, test: (value: T) => boolean): T | undefined {
return value !== undefined && test(value) ? value : undefined;
}

View File

@ -4474,5 +4474,9 @@
"Add missing enum member '{0}'": {
"category": "Message",
"code": 95063
},
"Add all missing imports": {
"category": "Message",
"code": 95064
}
}

View File

@ -4312,8 +4312,8 @@ namespace ts {
return !!forEachAncestorDirectory(directory, d => callback(d) ? true : undefined);
}
export function isUMDExportSymbol(symbol: Symbol | undefined) {
return symbol && symbol.declarations && symbol.declarations[0] && isNamespaceExportDeclaration(symbol.declarations[0]);
export function isUMDExportSymbol(symbol: Symbol | undefined): boolean {
return !!symbol && !!symbol.declarations && !!symbol.declarations[0] && isNamespaceExportDeclaration(symbol.declarations[0]);
}
export function showModuleSpecifier({ moduleSpecifier }: ImportDeclaration): string {
@ -8106,4 +8106,6 @@ namespace ts {
return findBestPatternMatch(patterns, _ => _, candidate);
}
export type Mutable<T extends object> = { -readonly [K in keyof T]: T[K] };
}

View File

@ -2643,7 +2643,7 @@ Actual: ${stringify(fullActual)}`);
}
const range = ts.firstOrUndefined(ranges);
const codeFixes = this.getCodeFixes(fileName, errorCode, preferences).filter(f => f.fixId === undefined); // TODO: GH#20315 filter out those that use the import fix ID;
const codeFixes = this.getCodeFixes(fileName, errorCode, preferences).filter(f => f.fixId === ts.codefix.importFixId);
if (codeFixes.length === 0) {
if (expectedTextArray.length !== 0) {

View File

@ -1,9 +1,9 @@
/* @internal */
namespace ts {
export interface CodeFixRegistration {
errorCodes: number[];
errorCodes: ReadonlyArray<number>;
getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined;
fixIds?: string[];
fixIds?: ReadonlyArray<string>;
getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions;
}
@ -27,7 +27,7 @@ namespace ts {
const errorCodeToFixes = createMultiMap<CodeFixRegistration>();
const fixIdToRegistration = createMap<CodeFixRegistration>();
type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string];
export type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string];
function diagnosticToString(diag: DiagnosticAndArguments): string {
return isArray(diag)
? formatStringFromArgs(getLocaleSpecificMessage(diag[0]), diag.slice(1) as ReadonlyArray<string>)
@ -89,7 +89,7 @@ namespace ts {
return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands);
}
export function eachDiagnostic({ program, sourceFile, cancellationToken }: CodeFixAllContext, errorCodes: number[], cb: (diag: DiagnosticWithLocation) => void): void {
export function eachDiagnostic({ program, sourceFile, cancellationToken }: CodeFixAllContext, errorCodes: ReadonlyArray<number>, cb: (diag: DiagnosticWithLocation) => void): void {
for (const diag of program.getSemanticDiagnostics(sourceFile, cancellationToken).concat(computeSuggestionDiagnostics(sourceFile, program, cancellationToken))) {
if (contains(errorCodes, diag.code)) {
cb(diag as DiagnosticWithLocation);

View File

@ -1,58 +1,120 @@
/* @internal */
namespace ts.codefix {
import ChangeTracker = textChanges.ChangeTracker;
export const importFixId = "fixMissingImport";
const errorCodes: ReadonlyArray<number> = [
Diagnostics.Cannot_find_name_0.code,
Diagnostics.Cannot_find_name_0_Did_you_mean_1.code,
Diagnostics.Cannot_find_namespace_0.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,
];
registerCodeFix({
errorCodes: [
Diagnostics.Cannot_find_name_0.code,
Diagnostics.Cannot_find_name_0_Did_you_mean_1.code,
Diagnostics.Cannot_find_namespace_0.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,
],
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
fixIds: [],
getAllCodeActions: notImplemented,
errorCodes,
getCodeActions(context) {
const { errorCode, preferences, sourceFile, span } = context;
const info = getFixesInfo(context, errorCode, span.start);
if (!info) return undefined;
const { fixes, symbolName } = info;
const quotePreference = getQuotePreference(sourceFile, preferences);
return fixes.map(fix => codeActionForFix(context, sourceFile, symbolName, fix, quotePreference));
},
fixIds: [importFixId],
getAllCodeActions: context => {
const { sourceFile, preferences } = context;
// Namespace fixes don't conflict, so just build a list.
const addToNamespace: FixUseNamespaceImport[] = [];
// Keys are import clause node IDs.
const addToExisting = createMap<{ readonly importClause: ImportClause, defaultImport: string | undefined; readonly namedImports: string[] }>();
// Keys are module specifiers.
const newImports = createMap<Mutable<ImportsCollection>>();
eachDiagnostic(context, errorCodes, diag => {
const info = getFixesInfo(context, diag.code, diag.start);
if (!info || !info.fixes.length) return;
const { fixes, symbolName } = info;
const fix = first(fixes);
switch (fix.kind) {
case ImportFixKind.UseNamespace:
addToNamespace.push(fix);
break;
case ImportFixKind.AddToExisting: {
const { importClause, importKind } = fix;
const key = String(getNodeId(importClause));
let entry = addToExisting.get(key);
if (!entry) {
addToExisting.set(key, entry = { importClause, defaultImport: undefined, namedImports: [] });
}
if (importKind === ImportKind.Named) {
pushIfUnique(entry.namedImports, symbolName);
}
else {
Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName);
entry.defaultImport = symbolName;
}
break;
}
case ImportFixKind.AddNew: {
const { moduleSpecifier, importKind } = fix;
let entry = newImports.get(moduleSpecifier);
if (!entry) {
newImports.set(moduleSpecifier, entry = { defaultImport: undefined, namedImports: [], namespaceLikeImport: undefined });
}
switch (importKind) {
case ImportKind.Default:
Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName);
entry.defaultImport = symbolName;
break;
case ImportKind.Named:
pushIfUnique(entry.namedImports, symbolName);
break;
case ImportKind.Equals:
case ImportKind.Namespace:
Debug.assert(entry.namespaceLikeImport === undefined || entry.namespaceLikeImport.name === symbolName);
entry.namespaceLikeImport = { importKind, name: symbolName };
break;
}
break;
}
default:
Debug.assertNever(fix);
}
});
return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => {
for (const fix of addToNamespace) {
addNamespaceQualifier(changes, sourceFile, fix);
}
addToExisting.forEach(({ importClause, defaultImport, namedImports }) => {
doAddExistingFix(changes, sourceFile, importClause, defaultImport, namedImports);
});
const quotePreference = getQuotePreference(sourceFile, preferences);
newImports.forEach((imports, moduleSpecifier) => {
addNewImports(changes, sourceFile, moduleSpecifier, quotePreference, imports);
});
}));
},
});
interface SymbolContext extends textChanges.TextChangesContext {
sourceFile: SourceFile;
symbolName: string;
// Sorted with the preferred fix coming first.
const enum ImportFixKind { UseNamespace, AddToExisting, AddNew }
type ImportFix = FixUseNamespaceImport | FixAddToExistingImport | FixAddNewImport;
interface FixUseNamespaceImport {
readonly kind: ImportFixKind.UseNamespace;
readonly namespacePrefix: string;
readonly symbolToken: Identifier;
}
interface ImportCodeFixContext extends SymbolContext {
symbolToken: Node | undefined;
program: Program;
checker: TypeChecker;
compilerOptions: CompilerOptions;
getCanonicalFileName: GetCanonicalFileName;
preferences: UserPreferences;
interface FixAddToExistingImport {
readonly kind: ImportFixKind.AddToExisting;
readonly importClause: ImportClause;
readonly importKind: ImportKind.Default | ImportKind.Named;
}
function createCodeAction(descriptionDiagnostic: DiagnosticMessage, diagnosticArgs: [string, string], changes: FileTextChanges[]): CodeFixAction {
// TODO: GH#20315
return createCodeFixActionNoFixId("import", changes, [descriptionDiagnostic, ...diagnosticArgs] as [DiagnosticMessage, string, string]);
}
function convertToImportCodeFixContext(context: CodeFixContext, symbolToken: Node, symbolName: string): ImportCodeFixContext {
const { program } = context;
const checker = program.getTypeChecker();
return {
host: context.host,
formatContext: context.formatContext,
sourceFile: context.sourceFile,
program,
checker,
compilerOptions: program.getCompilerOptions(),
getCanonicalFileName: createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(context.host)),
symbolName,
symbolToken,
preferences: context.preferences,
};
interface FixAddNewImport {
readonly kind: ImportFixKind.AddNew;
readonly moduleSpecifier: string;
readonly importKind: ImportKind;
}
const enum ImportKind {
@ -69,17 +131,11 @@ namespace ts.codefix {
}
/** Information needed to augment an existing import declaration. */
interface ExistingImportInfo {
interface FixAddToExistingImportInfo {
readonly declaration: AnyImportSyntax;
readonly importKind: ImportKind;
}
/** Information needed to create a new import declaration. */
interface NewImportInfo {
readonly moduleSpecifier: string;
readonly importKind: ImportKind;
}
export function getImportCompletionAction(
exportedSymbol: Symbol,
moduleSymbol: Symbol,
@ -88,10 +144,8 @@ namespace ts.codefix {
host: LanguageServiceHost,
program: Program,
checker: TypeChecker,
compilerOptions: CompilerOptions,
allSourceFiles: ReadonlyArray<SourceFile>,
formatContext: formatting.FormatContext,
getCanonicalFileName: GetCanonicalFileName,
symbolToken: Node | undefined,
preferences: UserPreferences,
): { readonly moduleSpecifier: string, readonly codeAction: CodeAction } {
@ -99,8 +153,8 @@ namespace ts.codefix {
Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol));
// We sort the best codefixes first, so taking `first` is best for completions.
const moduleSpecifier = first(getNewImportInfos(program, sourceFile, exportInfos, host, preferences)).moduleSpecifier;
const ctx: ImportCodeFixContext = { host, program, checker, compilerOptions, sourceFile, formatContext, symbolName, getCanonicalFileName, symbolToken, preferences };
return { moduleSpecifier, codeAction: first(getCodeActionsForImport(exportInfos, ctx)) };
const fix = first(getFixForImport(exportInfos, symbolName, symbolToken, program, sourceFile, host, preferences));
return { moduleSpecifier, codeAction: codeActionForFix({ host, formatContext }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences)) };
}
function getAllReExportingModules(exportedSymbol: Symbol, exportingModuleSymbol: Symbol, symbolName: string, sourceFile: SourceFile, checker: TypeChecker, allSourceFiles: ReadonlyArray<SourceFile>): ReadonlyArray<SymbolExportInfo> {
const result: SymbolExportInfo[] = [];
@ -120,26 +174,25 @@ namespace ts.codefix {
return result;
}
function getCodeActionsForImport(exportInfos: ReadonlyArray<SymbolExportInfo>, context: ImportCodeFixContext): CodeFixAction[] {
const result: CodeFixAction[] = [];
getCodeActionsForImport_separateExistingAndNew(exportInfos, context, result, result);
return result;
function getFixForImport(
exportInfos: ReadonlyArray<SymbolExportInfo>,
symbolName: string,
symbolToken: Node | undefined,
program: Program,
sourceFile: SourceFile,
host: LanguageServiceHost,
preferences: UserPreferences,
): ReadonlyArray<ImportFix> {
const checker = program.getTypeChecker();
const existingImports = flatMap(exportInfos, info => getExistingImportDeclarations(info, checker, sourceFile));
const useNamespace = tryUseExistingNamespaceImport(existingImports, symbolName, symbolToken, checker);
const addToExisting = tryAddToExistingImport(existingImports);
// Don't bother providing an action to add a new import if we can add to an existing one.
const addImport = addToExisting ? [addToExisting] : getCodeActionsForAddImport(exportInfos, existingImports, program, sourceFile, host, preferences);
return [...(useNamespace ? [useNamespace] : emptyArray), ...addImport];
}
function getCodeActionsForImport_separateExistingAndNew(exportInfos: ReadonlyArray<SymbolExportInfo>, context: ImportCodeFixContext, useExisting: Push<CodeFixAction>, addNew: Push<CodeFixAction>): void {
const existingImports = flatMap(exportInfos, info => getExistingImportDeclarations(info, context.checker, context.sourceFile));
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 {
function tryUseExistingNamespaceImport(existingImports: ReadonlyArray<FixAddToExistingImportInfo>, symbolName: string, symbolToken: Node | undefined, checker: TypeChecker): FixUseNamespaceImport | undefined {
// It is possible that multiple import statements with the same specifier exist in the file.
// e.g.
//
@ -152,25 +205,26 @@ namespace ts.codefix {
// 1. change "member3" to "ns.member3"
// 2. add "member3" to the second import statement's import list
// and it is up to the user to decide which one fits best.
return !symbolToken || !isIdentifier(symbolToken) ? undefined : firstDefined(existingImports, ({ declaration }) => {
return !symbolToken || !isIdentifier(symbolToken) ? undefined : firstDefined(existingImports, ({ declaration }): FixUseNamespaceImport | undefined => {
const namespace = getNamespaceImportName(declaration);
if (namespace) {
const moduleSymbol = namespace && checker.getAliasedSymbol(checker.getSymbolAtLocation(namespace)!);
if (moduleSymbol && moduleSymbol.exports!.has(escapeLeadingUnderscores(context.symbolName))) {
return getCodeActionForUseExistingNamespaceImport(namespace.text, context, symbolToken);
const moduleSymbol = checker.getAliasedSymbol(checker.getSymbolAtLocation(namespace)!);
if (moduleSymbol && moduleSymbol.exports!.has(escapeLeadingUnderscores(symbolName))) {
return { kind: ImportFixKind.UseNamespace, namespacePrefix: namespace.text, symbolToken };
}
}
});
}
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 tryAddToExistingImport(existingImports: ReadonlyArray<FixAddToExistingImportInfo>): FixAddToExistingImport | undefined {
return firstDefined(existingImports, ({ declaration, importKind }): FixAddToExistingImport | undefined => {
if (declaration.kind !== SyntaxKind.ImportDeclaration) return undefined;
const { importClause } = declaration;
if (!importClause) return undefined;
const { name, namedBindings } = importClause;
return importKind === ImportKind.Default && !name || importKind === ImportKind.Named && (!namedBindings || namedBindings.kind === SyntaxKind.NamedImports)
? { kind: ImportFixKind.AddToExisting, importClause, importKind }
: undefined;
});
}
@ -184,180 +238,82 @@ namespace ts.codefix {
}
}
function getExistingImportDeclarations({ moduleSymbol, importKind }: SymbolExportInfo, checker: TypeChecker, { imports }: SourceFile): ReadonlyArray<ExistingImportInfo> {
return mapDefined<StringLiteralLike, ExistingImportInfo>(imports, moduleSpecifier => {
function getExistingImportDeclarations({ moduleSymbol, importKind }: SymbolExportInfo, checker: TypeChecker, { imports }: SourceFile): ReadonlyArray<FixAddToExistingImportInfo> {
return mapDefined<StringLiteralLike, FixAddToExistingImportInfo>(imports, moduleSpecifier => {
const i = importFromModuleSpecifier(moduleSpecifier);
return (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration)
&& checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind } : undefined;
});
}
function getCodeActionForNewImport(context: SymbolContext & { preferences: UserPreferences }, { moduleSpecifier, importKind }: NewImportInfo): CodeFixAction {
const { sourceFile, symbolName, preferences } = context;
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
const quotedModuleSpecifier = makeStringLiteral(moduleSpecifierWithoutQuotes, getQuotePreference(sourceFile, preferences));
const importDecl = importKind !== ImportKind.Equals
? createImportDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createImportClauseOfKind(importKind, symbolName),
quotedModuleSpecifier)
: createImportEqualsDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createIdentifier(symbolName),
createExternalModuleReference(quotedModuleSpecifier));
const changes = ChangeTracker.with(context, t => insertImport(t, sourceFile, importDecl));
// if this file doesn't have any import statements, insert an import statement and then insert a new line
// between the only import statement and user code. Otherwise just insert the statement because chances
// are there are already a new line separating code and import statements.
return createCodeAction(Diagnostics.Import_0_from_module_1, [symbolName, moduleSpecifierWithoutQuotes], changes);
}
function createImportClauseOfKind(kind: ImportKind.Default | ImportKind.Named | ImportKind.Namespace, symbolName: string) {
const id = createIdentifier(symbolName);
switch (kind) {
case ImportKind.Default:
return createImportClause(id, /*namedBindings*/ undefined);
case ImportKind.Namespace:
return createImportClause(/*name*/ undefined, createNamespaceImport(id));
case ImportKind.Named:
return createImportClause(/*name*/ undefined, createNamedImports([createImportSpecifier(/*propertyName*/ undefined, id)]));
default:
Debug.assertNever(kind);
}
}
function getNewImportInfos(
program: Program,
sourceFile: SourceFile,
moduleSymbols: ReadonlyArray<SymbolExportInfo>,
host: LanguageServiceHost,
preferences: UserPreferences,
): ReadonlyArray<NewImportInfo> {
const choicesForEachExportingModule = flatMap<SymbolExportInfo, NewImportInfo[]>(moduleSymbols, ({ moduleSymbol, importKind }) => {
): ReadonlyArray<FixAddNewImport> {
const choicesForEachExportingModule = flatMap<SymbolExportInfo, ReadonlyArray<FixAddNewImport>>(moduleSymbols, ({ moduleSymbol, importKind }) => {
const modulePathsGroups = moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences);
return modulePathsGroups.map(group => group.map(moduleSpecifier => ({ moduleSpecifier, importKind })));
return modulePathsGroups.map(group => group.map((moduleSpecifier): FixAddNewImport => ({ kind: ImportFixKind.AddNew, moduleSpecifier, importKind })));
});
// Sort to keep the shortest paths first, but keep [relativePath, importRelativeToBaseUrl] groups together
return flatten<NewImportInfo>(choicesForEachExportingModule.sort((a, b) => first(a).moduleSpecifier.length - first(b).moduleSpecifier.length));
return flatten<FixAddNewImport>(choicesForEachExportingModule.sort((a, b) => first(a).moduleSpecifier.length - first(b).moduleSpecifier.length));
}
function getCodeActionsForAddImport(
exportInfos: ReadonlyArray<SymbolExportInfo>,
ctx: ImportCodeFixContext,
existingImports: ReadonlyArray<ExistingImportInfo>,
addNew: Push<CodeFixAction>,
): void {
existingImports: ReadonlyArray<FixAddToExistingImportInfo>,
program: Program,
sourceFile: SourceFile,
host: LanguageServiceHost,
preferences: UserPreferences,
): ReadonlyArray<FixAddNewImport> {
const existingDeclaration = firstDefined(existingImports, newImportInfoFromExistingSpecifier);
const newImportInfos = existingDeclaration
? [existingDeclaration]
: getNewImportInfos(ctx.program, ctx.sourceFile, exportInfos, ctx.host, ctx.preferences);
for (const info of newImportInfos) {
addNew.push(getCodeActionForNewImport(ctx, info));
}
return existingDeclaration ? [existingDeclaration] : getNewImportInfos(program, sourceFile, exportInfos, host, preferences);
}
function newImportInfoFromExistingSpecifier({ declaration, importKind }: ExistingImportInfo): NewImportInfo | undefined {
function newImportInfoFromExistingSpecifier({ declaration, importKind }: FixAddToExistingImportInfo): FixAddNewImport | undefined {
const expression = declaration.kind === SyntaxKind.ImportDeclaration
? declaration.moduleSpecifier
: declaration.moduleReference.kind === SyntaxKind.ExternalModuleReference
? declaration.moduleReference.expression
: undefined;
return expression && isStringLiteral(expression) ? { moduleSpecifier: expression.text, importKind } : undefined;
return expression && isStringLiteral(expression) ? { kind: ImportFixKind.AddNew, moduleSpecifier: expression.text, importKind } : undefined;
}
function tryUpdateExistingImport(context: SymbolContext, importClause: ImportClause | ImportEqualsDeclaration, importKind: ImportKind): FileTextChanges[] | undefined {
const { symbolName, sourceFile } = context;
const { name } = importClause;
const { namedBindings } = (importClause.kind !== SyntaxKind.ImportEqualsDeclaration && importClause) as ImportClause; // TODO: GH#18217
switch (importKind) {
case ImportKind.Default:
return name ? undefined : ChangeTracker.with(context, t =>
t.replaceNode(sourceFile, importClause, createImportClause(createIdentifier(symbolName), namedBindings)));
case ImportKind.Named: {
const newImportSpecifier = createImportSpecifier(/*propertyName*/ undefined, createIdentifier(symbolName));
if (namedBindings && namedBindings.kind === SyntaxKind.NamedImports && namedBindings.elements.length !== 0) {
// There are already named imports; add another.
return ChangeTracker.with(context, t => t.insertNodeInListAfter(
sourceFile,
namedBindings.elements[namedBindings.elements.length - 1],
newImportSpecifier));
}
if (!namedBindings || namedBindings.kind === SyntaxKind.NamedImports && namedBindings.elements.length === 0) {
return ChangeTracker.with(context, t =>
t.replaceNode(sourceFile, importClause, createImportClause(name, createNamedImports([newImportSpecifier]))));
}
return undefined;
}
case ImportKind.Namespace:
return namedBindings ? undefined : ChangeTracker.with(context, t =>
t.replaceNode(sourceFile, importClause, createImportClause(name, createNamespaceImport(createIdentifier(symbolName)))));
case ImportKind.Equals:
return undefined;
default:
Debug.assertNever(importKind);
}
interface FixesInfo { readonly fixes: ReadonlyArray<ImportFix>; readonly symbolName: string; }
function getFixesInfo(context: CodeFixContextBase, errorCode: number, pos: number): FixesInfo | undefined {
const symbolToken = getTokenAtPosition(context.sourceFile, pos);
const info = errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code
? getFixesInfoForUMDImport(context, symbolToken)
: getFixesInfoForNonUMDImport(context, symbolToken);
return info && { ...info, fixes: sort(info.fixes, (a, b) => a.kind - b.kind) };
}
function getCodeActionForUseExistingNamespaceImport(namespacePrefix: string, context: SymbolContext, symbolToken: Identifier): CodeFixAction {
const { symbolName, sourceFile } = context;
function getFixesInfoForUMDImport({ sourceFile, program, host, preferences }: CodeFixContextBase, token: Node): FixesInfo | undefined {
const checker = program.getTypeChecker();
const umdSymbol = getUmdSymbol(token, checker);
if (!umdSymbol) return undefined;
const symbol = checker.getAliasedSymbol(umdSymbol);
const symbolName = umdSymbol.name;
const exportInfos: ReadonlyArray<SymbolExportInfo> = [{ moduleSymbol: symbol, importKind: getUmdImportKind(program.getCompilerOptions()) }];
const fixes = getFixForImport(exportInfos, symbolName, token, program, sourceFile, host, preferences);
return { fixes, symbolName };
}
function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined {
// try the identifier to see if it is the umd symbol
const umdSymbol = isIdentifier(token) ? checker.getSymbolAtLocation(token) : undefined;
if (isUMDExportSymbol(umdSymbol)) return umdSymbol;
/**
* Cases:
* import * as ns from "mod"
* import default, * as ns from "mod"
* import ns = require("mod")
*
* Because there is no import list, we alter the reference to include the
* namespace instead of altering the import declaration. For example, "foo" would
* become "ns.foo"
*/
const changes = ChangeTracker.with(context, tracker =>
tracker.replaceNode(sourceFile, symbolToken, createPropertyAccess(createIdentifier(namespacePrefix), symbolToken)));
return createCodeAction(Diagnostics.Change_0_to_1, [symbolName, `${namespacePrefix}.${symbolName}`], changes);
// The error wasn't for the symbolAtLocation, it was for the JSX tag itself, which needs access to e.g. `React`.
const { parent } = token;
return (isJsxOpeningLikeElement(parent) && parent.tagName === token) || isJsxOpeningFragment(parent)
? tryCast(checker.resolveName(checker.getJsxNamespace(parent), isJsxOpeningLikeElement(parent) ? token : parent, SymbolFlags.Value, /*excludeGlobals*/ false), isUMDExportSymbol)
: undefined;
}
function getActionsForUMDImport(context: CodeFixContext): CodeFixAction[] | undefined {
const token = getTokenAtPosition(context.sourceFile, context.span.start);
const checker = context.program.getTypeChecker();
let umdSymbol: Symbol | undefined;
if (isIdentifier(token)) {
// try the identifier to see if it is the umd symbol
umdSymbol = checker.getSymbolAtLocation(token);
}
if (!isUMDExportSymbol(umdSymbol)) {
// The error wasn't for the symbolAtLocation, it was for the JSX tag itself, which needs access to e.g. `React`.
const parent = token.parent;
const isNodeOpeningLikeElement = isJsxOpeningLikeElement(parent);
if ((isJsxOpeningLikeElement && (<JsxOpeningLikeElement>parent).tagName === token) || parent.kind === SyntaxKind.JsxOpeningFragment) {
umdSymbol = checker.resolveName(checker.getJsxNamespace(parent),
isNodeOpeningLikeElement ? (<JsxOpeningLikeElement>parent).tagName : parent, SymbolFlags.Value, /*excludeGlobals*/ false);
}
}
if (isUMDExportSymbol(umdSymbol)) {
const symbol = checker.getAliasedSymbol(umdSymbol!);
if (symbol) {
return getCodeActionsForImport([{ moduleSymbol: symbol, importKind: getUmdImportKind(context.program.getCompilerOptions()) }],
convertToImportCodeFixContext(context, token, umdSymbol!.name));
}
}
return undefined;
}
function getUmdImportKind(compilerOptions: CompilerOptions) {
function getUmdImportKind(compilerOptions: CompilerOptions): ImportKind {
// Import a synthetic `default` if enabled.
if (getAllowSyntheticDefaultImports(compilerOptions)) {
return ImportKind.Default;
@ -381,11 +337,9 @@ namespace ts.codefix {
}
}
function getActionsForNonUMDImport(context: CodeFixContext): CodeFixAction[] | undefined {
function getFixesInfoForNonUMDImport({ sourceFile, program, cancellationToken, host, preferences }: CodeFixContextBase, symbolToken: Node): FixesInfo | undefined {
// This will always be an Identifier, since the diagnostics we fix only fail on identifiers.
const { sourceFile, span, program, cancellationToken } = context;
const checker = program.getTypeChecker();
const symbolToken = getTokenAtPosition(sourceFile, span.start);
// If we're at `<Foo/>`, we must check if `Foo` is already in scope, and if so, get an import for `React` instead.
const symbolName = isJsxOpeningLikeElement(symbolToken.parent)
&& symbolToken.parent.tagName === symbolToken
@ -394,17 +348,22 @@ namespace ts.codefix {
: isIdentifier(symbolToken) ? symbolToken.text : undefined;
if (!symbolName) return undefined;
// "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 !== InternalSymbolName.Default);
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];
const fixes = arrayFrom(flatMapIterator(getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, checker, program).entries(), ([_, exportInfos]) =>
getFixForImport(exportInfos, symbolName, symbolToken, program, sourceFile, host, preferences)));
return { fixes, symbolName };
}
function getExportInfos(symbolName: string, currentTokenMeaning: SemanticMeaning, cancellationToken: CancellationToken, sourceFile: SourceFile, checker: TypeChecker, program: Program): ReadonlyMap<ReadonlyArray<SymbolExportInfo>> {
// Returns a map from an exported symbol's ID to a list of every way it's (re-)exported.
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.
// Maps symbol id to info for modules providing that symbol (original export + re-exports).
const originalSymbolToExportInfos = createMultiMap<SymbolExportInfo>();
@ -422,14 +381,14 @@ namespace ts.codefix {
localSymbol && localSymbol.escapedName === symbolName ||
getEscapedNameForExportDefault(defaultExport) === symbolName ||
moduleSymbolToValidIdentifier(moduleSymbol, program.getCompilerOptions().target!) === symbolName
) && checkSymbolHasMeaning(localSymbol || defaultExport, currentTokenMeaning)) {
) && symbolHasMeaning(localSymbol || defaultExport, currentTokenMeaning)) {
addSymbol(moduleSymbol, localSymbol || defaultExport, ImportKind.Default);
}
}
// check exports with the same name
const exportSymbolWithIdenticalName = checker.tryGetMemberInModuleExportsAndProperties(symbolName, moduleSymbol);
if (exportSymbolWithIdenticalName && checkSymbolHasMeaning(exportSymbolWithIdenticalName, currentTokenMeaning)) {
if (exportSymbolWithIdenticalName && symbolHasMeaning(exportSymbolWithIdenticalName, currentTokenMeaning)) {
addSymbol(moduleSymbol, exportSymbolWithIdenticalName, ImportKind.Named);
}
@ -450,7 +409,88 @@ namespace ts.codefix {
return originalSymbolToExportInfos;
}
function checkSymbolHasMeaning({ declarations }: Symbol, meaning: SemanticMeaning): boolean {
function codeActionForFix(context: textChanges.TextChangesContext, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference): CodeFixAction {
let diag!: DiagnosticAndArguments;
const changes = textChanges.ChangeTracker.with(context, tracker => {
diag = codeActionForFixWorker(tracker, sourceFile, symbolName, fix, quotePreference);
});
return createCodeFixAction("import", changes, diag, importFixId, Diagnostics.Add_all_missing_imports);
}
function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference): DiagnosticAndArguments {
switch (fix.kind) {
case ImportFixKind.UseNamespace:
addNamespaceQualifier(changes, sourceFile, fix);
return [Diagnostics.Change_0_to_1, symbolName, `${fix.namespacePrefix}.${symbolName}`];
case ImportFixKind.AddToExisting: {
const { importClause, importKind } = fix;
doAddExistingFix(changes, sourceFile, importClause, importKind === ImportKind.Default ? symbolName : undefined, importKind === ImportKind.Named ? [symbolName] : emptyArray);
const moduleSpecifierWithoutQuotes = stripQuotes(importClause.parent.moduleSpecifier.getText());
return [Diagnostics.Add_0_to_existing_import_declaration_from_1, symbolName, moduleSpecifierWithoutQuotes];
}
case ImportFixKind.AddNew: {
const { importKind, moduleSpecifier } = fix;
addNewImports(changes, sourceFile, moduleSpecifier, quotePreference, importKind === ImportKind.Default ? { defaultImport: symbolName, namedImports: emptyArray, namespaceLikeImport: undefined }
: importKind === ImportKind.Named ? { defaultImport: undefined, namedImports: [symbolName], namespaceLikeImport: undefined }
: { defaultImport: undefined, namedImports: emptyArray, namespaceLikeImport: { importKind, name: symbolName } });
return [Diagnostics.Import_0_from_module_1, symbolName, moduleSpecifier];
}
default:
return Debug.assertNever(fix);
}
}
function doAddExistingFix(changes: textChanges.ChangeTracker, sourceFile: SourceFile, clause: ImportClause, defaultImport: string | undefined, namedImports: ReadonlyArray<string>): void {
if (defaultImport) {
Debug.assert(!clause.name);
changes.insertNodeAt(sourceFile, clause.getStart(sourceFile), createIdentifier(defaultImport), { suffix: ", " });
}
if (namedImports.length) {
const specifiers = namedImports.map(name => createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name)));
if (clause.namedBindings && cast(clause.namedBindings, isNamedImports).elements.length) {
for (const spec of specifiers) {
changes.insertNodeInListAfter(sourceFile, last(cast(clause.namedBindings, isNamedImports).elements), spec);
}
}
else {
if (specifiers.length) {
const namedImports = createNamedImports(specifiers);
if (clause.namedBindings) {
changes.replaceNode(sourceFile, clause.namedBindings, namedImports);
}
else {
changes.insertNodeAfter(sourceFile, Debug.assertDefined(clause.name), namedImports);
}
}
}
}
}
function addNamespaceQualifier(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { namespacePrefix, symbolToken }: FixUseNamespaceImport): void {
changes.replaceNode(sourceFile, symbolToken, createPropertyAccess(createIdentifier(namespacePrefix), symbolToken));
}
interface ImportsCollection {
readonly defaultImport: string | undefined;
readonly namedImports: string[];
readonly namespaceLikeImport: { readonly importKind: ImportKind.Equals | ImportKind.Namespace, readonly name: string } | undefined;
}
function addNewImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, moduleSpecifier: string, quotePreference: QuotePreference, { defaultImport, namedImports, namespaceLikeImport }: ImportsCollection): void {
const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
if (defaultImport !== undefined || namedImports.length) {
insertImport(changes, sourceFile,
makeImport(
defaultImport === undefined ? undefined : createIdentifier(defaultImport),
namedImports.map(n => createImportSpecifier(/*propertyName*/ undefined, createIdentifier(n))), moduleSpecifier, quotePreference));
}
if (namespaceLikeImport) {
insertImport(changes, sourceFile, namespaceLikeImport.importKind === ImportKind.Equals
? createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createIdentifier(namespaceLikeImport.name), createExternalModuleReference(quotedModuleSpecifier))
: createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClause(/*name*/ undefined, createNamespaceImport(createIdentifier(namespaceLikeImport.name))), quotedModuleSpecifier));
}
}
function symbolHasMeaning({ declarations }: Symbol, meaning: SemanticMeaning): boolean {
return some(declarations, decl => !!(getMeaningFromDeclaration(decl) & meaning));
}

View File

@ -549,7 +549,6 @@ namespace ts.Completions {
entryId: CompletionEntryIdentifier,
host: LanguageServiceHost,
formatContext: formatting.FormatContext,
getCanonicalFileName: GetCanonicalFileName,
preferences: UserPreferences,
cancellationToken: CancellationToken,
): CompletionEntryDetails | undefined {
@ -583,7 +582,7 @@ namespace ts.Completions {
}
case "symbol": {
const { symbol, location, symbolToOriginInfoMap, previousToken } = symbolCompletion;
const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, previousToken, formatContext, getCanonicalFileName, program.getSourceFiles(), preferences);
const { codeActions, sourceDisplay } = getCompletionEntryCodeActionsAndSourceDisplay(symbolToOriginInfoMap, symbol, program, typeChecker, host, compilerOptions, sourceFile, previousToken, formatContext, program.getSourceFiles(), preferences);
return createCompletionDetailsForSymbol(symbol, typeChecker, sourceFile, location!, cancellationToken, codeActions, sourceDisplay); // TODO: GH#18217
}
case "literal": {
@ -645,7 +644,6 @@ namespace ts.Completions {
sourceFile: SourceFile,
previousToken: Node | undefined,
formatContext: formatting.FormatContext,
getCanonicalFileName: GetCanonicalFileName,
allSourceFiles: ReadonlyArray<SourceFile>,
preferences: UserPreferences,
): CodeActionsAndSourceDisplay {
@ -664,10 +662,8 @@ namespace ts.Completions {
host,
program,
checker,
compilerOptions,
allSourceFiles,
formatContext,
getCanonicalFileName,
previousToken,
preferences);
return { sourceDisplay: [textPart(moduleSpecifier)], codeActions: [codeAction] };

View File

@ -1424,7 +1424,6 @@ namespace ts {
{ name, source },
host,
(formattingOptions && formatting.getFormatContext(formattingOptions))!, // TODO: GH#18217
getCanonicalFileName,
preferences,
cancellationToken,
);

View File

@ -290,7 +290,7 @@ namespace ts.textChanges {
return this.replaceNode(sourceFile, oldNode, newNode, { suffix });
}
private insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}) {
public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}) {
this.replaceRange(sourceFile, createTextRange(pos), newNode, options);
}
@ -478,6 +478,7 @@ namespace ts.textChanges {
case SyntaxKind.VariableDeclaration:
case SyntaxKind.StringLiteral:
case SyntaxKind.Identifier:
return { prefix: ", " };
case SyntaxKind.PropertyAssignment:

View File

@ -132,7 +132,7 @@ declare namespace ts {
*/
function flatMap<T, U>(array: ReadonlyArray<T>, mapfn: (x: T, i: number) => U | ReadonlyArray<U> | undefined): U[];
function flatMap<T, U>(array: ReadonlyArray<T> | undefined, mapfn: (x: T, i: number) => U | ReadonlyArray<U> | undefined): U[] | undefined;
function flatMapIterator<T, U>(iter: Iterator<T>, mapfn: (x: T) => U[] | Iterator<U> | undefined): Iterator<U>;
function flatMapIterator<T, U>(iter: Iterator<T>, mapfn: (x: T) => ReadonlyArray<U> | Iterator<U> | undefined): Iterator<U>;
/**
* Maps an array. If the mapped value is an array, it is spread into the result.
* Avoids allocation if all elements map to themselves.
@ -347,6 +347,7 @@ declare namespace ts {
*/
function isString(text: any): text is string;
function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined;
function tryCast<T>(value: T, test: (value: T) => boolean): T | undefined;
function cast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut;
/** Does nothing. */
function noop(_?: {} | null | undefined): void;
@ -5937,6 +5938,7 @@ declare namespace ts {
Convert_default_export_to_named_export: DiagnosticMessage;
Convert_named_export_to_default_export: DiagnosticMessage;
Add_missing_enum_member_0: DiagnosticMessage;
Add_all_missing_imports: DiagnosticMessage;
};
}
declare namespace ts {
@ -6619,7 +6621,7 @@ declare namespace ts {
function getObjectFlags(type: Type): ObjectFlags;
function typeHasCallOrConstructSignatures(type: Type, checker: TypeChecker): boolean;
function forSomeAncestorDirectory(directory: string, callback: (directory: string) => boolean): boolean;
function isUMDExportSymbol(symbol: Symbol | undefined): boolean | undefined;
function isUMDExportSymbol(symbol: Symbol | undefined): boolean;
function showModuleSpecifier({ moduleSpecifier }: ImportDeclaration): string;
function getLastChild(node: Node): Node | undefined;
/** Add a value to a set, and return true if it wasn't already present. */
@ -7385,6 +7387,9 @@ declare namespace ts {
* (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.)
*/
function matchPatternOrExact(patternStrings: ReadonlyArray<string>, candidate: string): string | Pattern | undefined;
type Mutable<T extends object> = {
-readonly [K in keyof T]: T[K];
};
}
declare namespace ts {
function createNode(kind: SyntaxKind, pos?: number, end?: number): Node;
@ -10980,7 +10985,7 @@ declare namespace ts.Completions {
name: string;
source?: string;
}
function getCompletionEntryDetails(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier, host: LanguageServiceHost, formatContext: formatting.FormatContext, getCanonicalFileName: GetCanonicalFileName, preferences: UserPreferences, cancellationToken: CancellationToken): CompletionEntryDetails | undefined;
function getCompletionEntryDetails(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier, host: LanguageServiceHost, formatContext: formatting.FormatContext, preferences: UserPreferences, cancellationToken: CancellationToken): CompletionEntryDetails | undefined;
function getCompletionEntrySymbol(program: Program, log: Log, sourceFile: SourceFile, position: number, entryId: CompletionEntryIdentifier): Symbol | undefined;
}
declare namespace ts.DocumentHighlights {
@ -11572,7 +11577,7 @@ declare namespace ts.textChanges {
replaceNodeRangeWithNodes(sourceFile: SourceFile, startNode: Node, endNode: Node, newNodes: ReadonlyArray<Node>, options?: ReplaceWithMultipleNodesOptions & ConfigurableStartEnd): this;
private nextCommaToken;
replacePropertyAssignment(sourceFile: SourceFile, oldNode: PropertyAssignment, newNode: PropertyAssignment): this;
private insertNodeAt;
insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options?: InsertNodeOptions): void;
private insertNodesAt;
insertNodeAtTopOfFile(sourceFile: SourceFile, newNode: Statement, blankLineBetween: boolean): void;
insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, blankLineBetween?: boolean): void;
@ -11624,9 +11629,9 @@ declare namespace ts.textChanges {
}
declare namespace ts {
interface CodeFixRegistration {
errorCodes: number[];
errorCodes: ReadonlyArray<number>;
getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined;
fixIds?: string[];
fixIds?: ReadonlyArray<string>;
getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions;
}
interface CodeFixContextBase extends textChanges.TextChangesContext {
@ -11653,7 +11658,7 @@ declare namespace ts {
function createCombinedCodeActions(changes: FileTextChanges[], commands?: CodeActionCommand[]): CombinedCodeActions;
function createFileTextChanges(fileName: string, textChanges: TextChange[]): FileTextChanges;
function codeFixAll(context: CodeFixAllContext, errorCodes: number[], use: (changes: textChanges.ChangeTracker, error: DiagnosticWithLocation, commands: Push<CodeActionCommand>) => void): CombinedCodeActions;
function eachDiagnostic({ program, sourceFile, cancellationToken }: CodeFixAllContext, errorCodes: number[], cb: (diag: DiagnosticWithLocation) => void): void;
function eachDiagnostic({ program, sourceFile, cancellationToken }: CodeFixAllContext, errorCodes: ReadonlyArray<number>, cb: (diag: DiagnosticWithLocation) => void): void;
}
}
declare namespace ts {
@ -11694,7 +11699,8 @@ declare namespace ts.codefix {
declare namespace ts.codefix {
}
declare namespace ts.codefix {
function getImportCompletionAction(exportedSymbol: Symbol, moduleSymbol: Symbol, sourceFile: SourceFile, symbolName: string, host: LanguageServiceHost, program: Program, checker: TypeChecker, compilerOptions: CompilerOptions, allSourceFiles: ReadonlyArray<SourceFile>, formatContext: formatting.FormatContext, getCanonicalFileName: GetCanonicalFileName, symbolToken: Node | undefined, preferences: UserPreferences): {
const importFixId = "fixMissingImport";
function getImportCompletionAction(exportedSymbol: Symbol, moduleSymbol: Symbol, sourceFile: SourceFile, symbolName: string, host: LanguageServiceHost, program: Program, checker: TypeChecker, allSourceFiles: ReadonlyArray<SourceFile>, formatContext: formatting.FormatContext, symbolToken: Node | undefined, preferences: UserPreferences): {
readonly moduleSpecifier: string;
readonly codeAction: CodeAction;
};

View File

@ -9,7 +9,7 @@
goTo.marker("");
verify.completionListContains({ name: "foo", source: "/a" }, "function foo(): void", "", "function", /*spanIndex*/ undefined, /*hasAction*/ true, {
includeExternalModuleExports: true,
includeCompletionsForModuleExports: true,
sourceDisplay: "./a",
});

View File

@ -0,0 +1,51 @@
/// <reference path='fourslash.ts' />
// @Filename: /a.ts
////export default function ad() {}
////export const a0 = 0;
// @Filename: /b.ts
////export default function bd() {}
////export const b0 = 0;
// @Filename: /c.ts
////export default function cd() {}
////export const c0 = 0;
// @Filename: /d.ts
////export default function dd() {}
////export const d0 = 0;
////export const d1 = 1;
// @Filename: /e.d.ts
////declare function e(): void;
////export = e;
// @Filename: /user.ts
////import * as b from "./b";
////import { } from "./c";
////import dd from "./d";
////
////ad; ad; a0; a0;
////bd; bd; b0; b0;
////cd; cd; c0; c0;
////dd; dd; d0; d0; d1; d1;
////e; e;
goTo.file("/user.ts");
verify.codeFixAll({
fixId: "fixMissingImport",
fixAllDescription: "Add all missing imports",
newFileContent:
// TODO: GH#25135 (should import 'e')
`import bd, * as b from "./b";
import cd, { c0 } from "./c";
import dd, { d0, d1 } from "./d";
import ad, { a0 } from "./a";
ad; ad; a0; a0;
bd; bd; b.b0; b.b0;
cd; cd; c0; c0;
dd; dd; d0; d0; d1; d1;
e; e;`,
});