mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Type-only import specifiers (#45998)
* Parse type-only import specifiers
* Add type-only export specifiers
* Update transform and emit
* Update checking
* Fix elision when combined with importsNotUsedAsValues=preserve
* Accept baselines
* Add test
* WIP auto imports updates
* First auto-imports test working
* More auto-import tests
* Fix auto imports of type-only exports
* Add test for promoting type-only import
* Sort import/export specifiers by type-onlyness
* Update completions for `import { type |`
* Update other completions tests
* Respect organize imports sorting when promoting type-only to regular while adding a specifier
* Fix comment mistakes
* Update src/services/codefixes/importFixes.ts
Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>
* Rearrange some order of assignments in parser
* Split huge if statement
* Remove redundant check
* Update new transformer
* Fix import statement completions
* Fix type keyword completions good grief
* Fix last tests
Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>
This commit is contained in:
co-authored by
Daniel Rosenwasser
parent
26aef89a72
commit
e160bc8c0d
@@ -294,7 +294,7 @@ namespace ts.codefix {
|
||||
*/
|
||||
const newNodes = [
|
||||
makeConst(/*modifiers*/ undefined, rename, assignment.right),
|
||||
makeExportDeclaration([factory.createExportSpecifier(rename, text)]),
|
||||
makeExportDeclaration([factory.createExportSpecifier(/*isTypeOnly*/ false, rename, text)]),
|
||||
];
|
||||
changes.replaceNodeWithNodes(sourceFile, assignment.parent, newNodes);
|
||||
}
|
||||
@@ -317,7 +317,7 @@ namespace ts.codefix {
|
||||
return makeExportDeclaration(/*exportClause*/ undefined, moduleSpecifier);
|
||||
}
|
||||
function reExportDefault(moduleSpecifier: string): ExportDeclaration {
|
||||
return makeExportDeclaration([factory.createExportSpecifier(/*propertyName*/ undefined, "default")], moduleSpecifier);
|
||||
return makeExportDeclaration([factory.createExportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, "default")], moduleSpecifier);
|
||||
}
|
||||
|
||||
function convertExportsPropertyAssignment({ left, right, parent }: BinaryExpression & { left: PropertyAccessExpression }, sourceFile: SourceFile, changes: textChanges.ChangeTracker): void {
|
||||
@@ -480,7 +480,7 @@ namespace ts.codefix {
|
||||
}
|
||||
|
||||
const namedBindings = namedBindingsNames.size === 0 ? undefined : arrayFrom(mapIterator(namedBindingsNames.entries(), ([propertyName, idName]) =>
|
||||
factory.createImportSpecifier(propertyName === idName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(idName))));
|
||||
factory.createImportSpecifier(/*isTypeOnly*/ false, propertyName === idName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(idName))));
|
||||
if (!namedBindings) {
|
||||
// If it was unused, ensure that we at least import *something*.
|
||||
needDefaultImport = true;
|
||||
@@ -573,7 +573,7 @@ namespace ts.codefix {
|
||||
}
|
||||
|
||||
function makeImportSpecifier(propertyName: string | undefined, name: string): ImportSpecifier {
|
||||
return factory.createImportSpecifier(propertyName !== undefined && propertyName !== name ? factory.createIdentifier(propertyName) : undefined, factory.createIdentifier(name));
|
||||
return factory.createImportSpecifier(/*isTypeOnly*/ false, propertyName !== undefined && propertyName !== name ? factory.createIdentifier(propertyName) : undefined, factory.createIdentifier(name));
|
||||
}
|
||||
|
||||
function makeConst(modifiers: readonly Modifier[] | undefined, name: string | BindingName, init: Expression): VariableStatement {
|
||||
|
||||
@@ -532,6 +532,6 @@ namespace ts.codefix {
|
||||
}
|
||||
|
||||
export function importSymbols(importAdder: ImportAdder, symbols: readonly Symbol[]) {
|
||||
symbols.forEach(s => importAdder.addImportFromExportedSymbol(s, /*usageIsTypeOnly*/ true));
|
||||
symbols.forEach(s => importAdder.addImportFromExportedSymbol(s, /*isValidTypeOnlyUseSite*/ true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ namespace ts.codefix {
|
||||
registerCodeFix({
|
||||
errorCodes,
|
||||
getCodeActions(context) {
|
||||
const { errorCode, preferences, sourceFile, span } = context;
|
||||
const { errorCode, preferences, sourceFile, span, program } = context;
|
||||
const info = getFixesInfo(context, errorCode, span.start, /*useAutoImportProvider*/ true);
|
||||
if (!info) return undefined;
|
||||
const { fixes, symbolName } = info;
|
||||
const quotePreference = getQuotePreference(sourceFile, preferences);
|
||||
return fixes.map(fix => codeActionForFix(context, sourceFile, symbolName, fix, quotePreference));
|
||||
return fixes.map(fix => codeActionForFix(context, sourceFile, symbolName, fix, quotePreference, program.getCompilerOptions()));
|
||||
},
|
||||
fixIds: [importFixId],
|
||||
getAllCodeActions: context => {
|
||||
@@ -34,7 +34,7 @@ namespace ts.codefix {
|
||||
|
||||
export interface ImportAdder {
|
||||
addImportFromDiagnostic: (diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) => void;
|
||||
addImportFromExportedSymbol: (exportedSymbol: Symbol, usageIsTypeOnly?: boolean) => void;
|
||||
addImportFromExportedSymbol: (exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean) => void;
|
||||
writeFixes: (changeTracker: textChanges.ChangeTracker) => void;
|
||||
}
|
||||
|
||||
@@ -42,14 +42,23 @@ namespace ts.codefix {
|
||||
return createImportAdderWorker(sourceFile, program, /*useAutoImportProvider*/ false, preferences, host);
|
||||
}
|
||||
|
||||
interface AddToExistingState {
|
||||
readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern;
|
||||
defaultImport: Import | undefined;
|
||||
readonly namedImports: ESMap<string, AddAsTypeOnly>;
|
||||
}
|
||||
|
||||
function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAutoImportProvider: boolean, preferences: UserPreferences, host: LanguageServiceHost): ImportAdder {
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
// Namespace fixes don't conflict, so just build a list.
|
||||
const addToNamespace: FixUseNamespaceImport[] = [];
|
||||
const importType: FixUseImportType[] = [];
|
||||
// Keys are import clause node IDs.
|
||||
const addToExisting = new Map<string, { readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern, defaultImport: string | undefined; readonly namedImports: string[], canUseTypeOnlyImport: boolean }>();
|
||||
const newImports = new Map<string, Mutable<ImportsCollection & { useRequire: boolean }>>();
|
||||
const importType: FixAddJsdocTypeImport[] = [];
|
||||
/** Keys are import clause node IDs. */
|
||||
const addToExisting = new Map<string, AddToExistingState>();
|
||||
|
||||
type NewImportsKey = `${0 | 1}|${string}`;
|
||||
/** Use `getNewImportEntry` for access */
|
||||
const newImports = new Map<NewImportsKey, Mutable<ImportsCollection & { useRequire: boolean }>>();
|
||||
return { addImportFromDiagnostic, addImportFromExportedSymbol, writeFixes };
|
||||
|
||||
function addImportFromDiagnostic(diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) {
|
||||
@@ -58,15 +67,14 @@ namespace ts.codefix {
|
||||
addImport(info);
|
||||
}
|
||||
|
||||
function addImportFromExportedSymbol(exportedSymbol: Symbol, usageIsTypeOnly?: boolean) {
|
||||
function addImportFromExportedSymbol(exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean) {
|
||||
const moduleSymbol = Debug.checkDefined(exportedSymbol.parent);
|
||||
const symbolName = getNameForExportedSymbol(exportedSymbol, getEmitScriptTarget(compilerOptions));
|
||||
const checker = program.getTypeChecker();
|
||||
const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker));
|
||||
const exportInfos = getAllReExportingModules(sourceFile, symbol, moduleSymbol, symbolName, host, program, preferences, useAutoImportProvider);
|
||||
const preferTypeOnlyImport = !!usageIsTypeOnly && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error;
|
||||
const useRequire = shouldUseRequire(sourceFile, program);
|
||||
const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, preferTypeOnlyImport, useRequire, host, preferences);
|
||||
const fix = getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, /*position*/ undefined, !!isValidTypeOnlyUseSite, useRequire, host, preferences);
|
||||
if (fix) {
|
||||
addImport({ fixes: [fix], symbolName });
|
||||
}
|
||||
@@ -79,47 +87,47 @@ namespace ts.codefix {
|
||||
case ImportFixKind.UseNamespace:
|
||||
addToNamespace.push(fix);
|
||||
break;
|
||||
case ImportFixKind.ImportType:
|
||||
case ImportFixKind.JsdocTypeImport:
|
||||
importType.push(fix);
|
||||
break;
|
||||
case ImportFixKind.AddToExisting: {
|
||||
const { importClauseOrBindingPattern, importKind, canUseTypeOnlyImport } = fix;
|
||||
const { importClauseOrBindingPattern, importKind, addAsTypeOnly } = fix;
|
||||
const key = String(getNodeId(importClauseOrBindingPattern));
|
||||
let entry = addToExisting.get(key);
|
||||
if (!entry) {
|
||||
addToExisting.set(key, entry = { importClauseOrBindingPattern, defaultImport: undefined, namedImports: [], canUseTypeOnlyImport });
|
||||
addToExisting.set(key, entry = { importClauseOrBindingPattern, defaultImport: undefined, namedImports: new Map() });
|
||||
}
|
||||
if (importKind === ImportKind.Named) {
|
||||
pushIfUnique(entry.namedImports, symbolName);
|
||||
const prevValue = entry?.namedImports.get(symbolName);
|
||||
entry.namedImports.set(symbolName, reduceAddAsTypeOnlyValues(prevValue, addAsTypeOnly));
|
||||
}
|
||||
else {
|
||||
Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName, "(Add to Existing) Default import should be missing or match symbolName");
|
||||
entry.defaultImport = symbolName;
|
||||
Debug.assert(entry.defaultImport === undefined || entry.defaultImport.name === symbolName, "(Add to Existing) Default import should be missing or match symbolName");
|
||||
entry.defaultImport = {
|
||||
name: symbolName,
|
||||
addAsTypeOnly: reduceAddAsTypeOnlyValues(entry.defaultImport?.addAsTypeOnly, addAsTypeOnly),
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ImportFixKind.AddNew: {
|
||||
const { moduleSpecifier, importKind, useRequire, typeOnly } = fix;
|
||||
let entry = newImports.get(moduleSpecifier);
|
||||
if (!entry) {
|
||||
newImports.set(moduleSpecifier, entry = { namedImports: [], namespaceLikeImport: undefined, typeOnly, useRequire });
|
||||
}
|
||||
else {
|
||||
// An import clause can only be type-only if every import fix contributing to it can be type-only.
|
||||
entry.typeOnly = entry.typeOnly && typeOnly;
|
||||
}
|
||||
const { moduleSpecifier, importKind, useRequire, addAsTypeOnly } = fix;
|
||||
const entry = getNewImportEntry(moduleSpecifier, importKind, useRequire, addAsTypeOnly);
|
||||
Debug.assert(entry.useRequire === useRequire, "(Add new) Tried to add an `import` and a `require` for the same module");
|
||||
|
||||
switch (importKind) {
|
||||
case ImportKind.Default:
|
||||
Debug.assert(entry.defaultImport === undefined || entry.defaultImport === symbolName, "(Add new) Default import should be missing or match symbolName");
|
||||
entry.defaultImport = symbolName;
|
||||
Debug.assert(entry.defaultImport === undefined || entry.defaultImport.name === symbolName, "(Add new) Default import should be missing or match symbolName");
|
||||
entry.defaultImport = { name: symbolName, addAsTypeOnly: reduceAddAsTypeOnlyValues(entry.defaultImport?.addAsTypeOnly, addAsTypeOnly) };
|
||||
break;
|
||||
case ImportKind.Named:
|
||||
pushIfUnique(entry.namedImports || (entry.namedImports = []), symbolName);
|
||||
const prevValue = (entry.namedImports ||= new Map()).get(symbolName);
|
||||
entry.namedImports.set(symbolName, reduceAddAsTypeOnlyValues(prevValue, addAsTypeOnly));
|
||||
break;
|
||||
case ImportKind.CommonJS:
|
||||
case ImportKind.Namespace:
|
||||
Debug.assert(entry.namespaceLikeImport === undefined || entry.namespaceLikeImport.name === symbolName, "Namespacelike import shoudl be missing or match symbolName");
|
||||
entry.namespaceLikeImport = { importKind, name: symbolName };
|
||||
entry.namespaceLikeImport = { importKind, name: symbolName, addAsTypeOnly };
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -127,6 +135,52 @@ namespace ts.codefix {
|
||||
default:
|
||||
Debug.assertNever(fix, `fix wasn't never - got kind ${(fix as ImportFix).kind}`);
|
||||
}
|
||||
|
||||
function reduceAddAsTypeOnlyValues(prevValue: AddAsTypeOnly | undefined, newValue: AddAsTypeOnly): AddAsTypeOnly {
|
||||
// `NotAllowed` overrides `Required` because one addition of a new import might be required to be type-only
|
||||
// because of `--importsNotUsedAsValues=error`, but if a second addition of the same import is `NotAllowed`
|
||||
// to be type-only, the reason the first one was `Required` - the unused runtime dependency - is now moot.
|
||||
// Alternatively, if one addition is `Required` because it has no value meaning under `--preserveValueImports`
|
||||
// and `--isolatedModules`, it should be impossible for another addition to be `NotAllowed` since that would
|
||||
// mean a type is being referenced in a value location.
|
||||
return Math.max(prevValue ?? 0, newValue);
|
||||
}
|
||||
|
||||
function getNewImportEntry(moduleSpecifier: string, importKind: ImportKind, useRequire: boolean, addAsTypeOnly: AddAsTypeOnly): Mutable<ImportsCollection & { useRequire: boolean }> {
|
||||
// A default import that requires type-only makes the whole import type-only.
|
||||
// (We could add `default` as a named import, but that style seems undesirable.)
|
||||
// Under `--preserveValueImports` and `--importsNotUsedAsValues=error`, if a
|
||||
// module default-exports a type but named-exports some values (weird), you would
|
||||
// have to use a type-only default import and non-type-only named imports. These
|
||||
// require two separate import declarations, so we build this into the map key.
|
||||
const typeOnlyKey = newImportsKey(moduleSpecifier, /*topLevelTypeOnly*/ true);
|
||||
const nonTypeOnlyKey = newImportsKey(moduleSpecifier, /*topLevelTypeOnly*/ false);
|
||||
const typeOnlyEntry = newImports.get(typeOnlyKey);
|
||||
const nonTypeOnlyEntry = newImports.get(nonTypeOnlyKey);
|
||||
const newEntry: ImportsCollection & { useRequire: boolean } = {
|
||||
defaultImport: undefined,
|
||||
namedImports: undefined,
|
||||
namespaceLikeImport: undefined,
|
||||
useRequire
|
||||
};
|
||||
if (importKind === ImportKind.Default && addAsTypeOnly === AddAsTypeOnly.Required) {
|
||||
if (typeOnlyEntry) return typeOnlyEntry;
|
||||
newImports.set(typeOnlyKey, newEntry);
|
||||
return newEntry;
|
||||
}
|
||||
if (addAsTypeOnly === AddAsTypeOnly.Allowed && (typeOnlyEntry || nonTypeOnlyEntry)) {
|
||||
return (typeOnlyEntry || nonTypeOnlyEntry)!;
|
||||
}
|
||||
if (nonTypeOnlyEntry) {
|
||||
return nonTypeOnlyEntry;
|
||||
}
|
||||
newImports.set(nonTypeOnlyKey, newEntry);
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
function newImportsKey(moduleSpecifier: string, topLevelTypeOnly: boolean): NewImportsKey {
|
||||
return `${topLevelTypeOnly ? 1 : 0}|${moduleSpecifier}`;
|
||||
}
|
||||
}
|
||||
|
||||
function writeFixes(changeTracker: textChanges.ChangeTracker) {
|
||||
@@ -137,14 +191,27 @@ namespace ts.codefix {
|
||||
for (const fix of importType) {
|
||||
addImportType(changeTracker, sourceFile, fix, quotePreference);
|
||||
}
|
||||
addToExisting.forEach(({ importClauseOrBindingPattern, defaultImport, namedImports, canUseTypeOnlyImport }) => {
|
||||
doAddExistingFix(changeTracker, sourceFile, importClauseOrBindingPattern, defaultImport, namedImports, canUseTypeOnlyImport);
|
||||
addToExisting.forEach(({ importClauseOrBindingPattern, defaultImport, namedImports }) => {
|
||||
doAddExistingFix(
|
||||
changeTracker,
|
||||
sourceFile,
|
||||
importClauseOrBindingPattern,
|
||||
defaultImport,
|
||||
arrayFrom(namedImports.entries(), ([name, addAsTypeOnly]) => ({ addAsTypeOnly, name })),
|
||||
compilerOptions);
|
||||
});
|
||||
|
||||
let newDeclarations: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[] | undefined;
|
||||
newImports.forEach(({ useRequire, ...imports }, moduleSpecifier) => {
|
||||
newImports.forEach(({ useRequire, defaultImport, namedImports, namespaceLikeImport }, key) => {
|
||||
const moduleSpecifier = key.slice(2); // From `${0 | 1}|${moduleSpecifier}` format
|
||||
const getDeclarations = useRequire ? getNewRequires : getNewImports;
|
||||
newDeclarations = combine(newDeclarations, getDeclarations(moduleSpecifier, quotePreference, imports));
|
||||
const declarations = getDeclarations(
|
||||
moduleSpecifier,
|
||||
quotePreference,
|
||||
defaultImport,
|
||||
namedImports && arrayFrom(namedImports.entries(), ([name, addAsTypeOnly]) => ({ addAsTypeOnly, name })),
|
||||
namespaceLikeImport);
|
||||
newDeclarations = combine(newDeclarations, declarations);
|
||||
});
|
||||
if (newDeclarations) {
|
||||
insertImports(changeTracker, sourceFile, newDeclarations, /*blankLineBetween*/ true);
|
||||
@@ -153,16 +220,25 @@ namespace ts.codefix {
|
||||
}
|
||||
|
||||
// Sorted with the preferred fix coming first.
|
||||
const enum ImportFixKind { UseNamespace, ImportType, AddToExisting, AddNew }
|
||||
type ImportFix = FixUseNamespaceImport | FixUseImportType | FixAddToExistingImport | FixAddNewImport;
|
||||
const enum ImportFixKind { UseNamespace, JsdocTypeImport, AddToExisting, AddNew }
|
||||
// These should not be combined as bitflags, but are given powers of 2 values to
|
||||
// easily detect conflicts between `NotAllowed` and `Required` by giving them a unique sum.
|
||||
// They're also ordered in terms of increasing priority for a fix-all scenario (see
|
||||
// `reduceAddAsTypeOnlyValues`).
|
||||
const enum AddAsTypeOnly {
|
||||
Allowed = 1 << 0,
|
||||
Required = 1 << 1,
|
||||
NotAllowed = 1 << 2,
|
||||
}
|
||||
type ImportFix = FixUseNamespaceImport | FixAddJsdocTypeImport | FixAddToExistingImport | FixAddNewImport;
|
||||
interface FixUseNamespaceImport {
|
||||
readonly kind: ImportFixKind.UseNamespace;
|
||||
readonly namespacePrefix: string;
|
||||
readonly position: number;
|
||||
readonly moduleSpecifier: string;
|
||||
}
|
||||
interface FixUseImportType {
|
||||
readonly kind: ImportFixKind.ImportType;
|
||||
interface FixAddJsdocTypeImport {
|
||||
readonly kind: ImportFixKind.JsdocTypeImport;
|
||||
readonly moduleSpecifier: string;
|
||||
readonly position: number;
|
||||
readonly exportInfo: SymbolExportInfo;
|
||||
@@ -172,13 +248,13 @@ namespace ts.codefix {
|
||||
readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern;
|
||||
readonly moduleSpecifier: string;
|
||||
readonly importKind: ImportKind.Default | ImportKind.Named;
|
||||
readonly canUseTypeOnlyImport: boolean;
|
||||
readonly addAsTypeOnly: AddAsTypeOnly;
|
||||
}
|
||||
interface FixAddNewImport {
|
||||
readonly kind: ImportFixKind.AddNew;
|
||||
readonly moduleSpecifier: string;
|
||||
readonly importKind: ImportKind;
|
||||
readonly typeOnly: boolean;
|
||||
readonly addAsTypeOnly: AddAsTypeOnly;
|
||||
readonly useRequire: boolean;
|
||||
readonly exportInfo?: SymbolExportInfo;
|
||||
}
|
||||
@@ -187,6 +263,8 @@ namespace ts.codefix {
|
||||
interface FixAddToExistingImportInfo {
|
||||
readonly declaration: AnyImportOrRequire;
|
||||
readonly importKind: ImportKind;
|
||||
readonly targetFlags: SymbolFlags;
|
||||
readonly symbol: Symbol;
|
||||
}
|
||||
|
||||
export function getImportCompletionAction(
|
||||
@@ -205,14 +283,22 @@ namespace ts.codefix {
|
||||
? [getSymbolExportInfoForSymbol(exportedSymbol, moduleSymbol, program, host)]
|
||||
: getAllReExportingModules(sourceFile, exportedSymbol, moduleSymbol, symbolName, host, program, preferences, /*useAutoImportProvider*/ true);
|
||||
const useRequire = shouldUseRequire(sourceFile, program);
|
||||
const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && !isSourceFileJS(sourceFile) && isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
|
||||
const fix = Debug.checkDefined(getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, preferTypeOnlyImport, useRequire, host, preferences));
|
||||
return { moduleSpecifier: fix.moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) };
|
||||
const isValidTypeOnlyUseSite = isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
|
||||
const fix = Debug.checkDefined(getImportFixForSymbol(sourceFile, exportInfos, moduleSymbol, symbolName, program, position, isValidTypeOnlyUseSite, useRequire, host, preferences));
|
||||
return {
|
||||
moduleSpecifier: fix.moduleSpecifier,
|
||||
codeAction: codeFixActionToCodeAction(codeActionForFix(
|
||||
{ host, formatContext, preferences },
|
||||
sourceFile,
|
||||
symbolName,
|
||||
fix,
|
||||
getQuotePreference(sourceFile, preferences), compilerOptions))
|
||||
};
|
||||
}
|
||||
|
||||
function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, preferTypeOnlyImport: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
|
||||
function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, isValidTypeOnlyUseSite: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
|
||||
Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol || info.symbol.parent === moduleSymbol), "Some exportInfo should match the specified moduleSymbol");
|
||||
return getBestFix(getImportFixes(exportInfos, symbolName, position, preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences), sourceFile, program, host, preferences);
|
||||
return getBestFix(getImportFixes(exportInfos, symbolName, position, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences), sourceFile, program, host, preferences);
|
||||
}
|
||||
|
||||
function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction {
|
||||
@@ -284,7 +370,7 @@ namespace ts.codefix {
|
||||
program,
|
||||
importingFile,
|
||||
/*position*/ undefined,
|
||||
/*preferTypeOnlyImport*/ false,
|
||||
/*isValidTypeOnlyUseSite*/ false,
|
||||
/*useRequire*/ false,
|
||||
exportInfo,
|
||||
host,
|
||||
@@ -294,16 +380,12 @@ namespace ts.codefix {
|
||||
return result && { ...result, computedWithoutCacheCount };
|
||||
}
|
||||
|
||||
function isTypeOnlyPosition(sourceFile: SourceFile, position: number) {
|
||||
return isValidTypeOnlyAliasUseSite(getTokenAtPosition(sourceFile, position));
|
||||
}
|
||||
|
||||
function getImportFixes(
|
||||
exportInfos: readonly SymbolExportInfo[],
|
||||
symbolName: string,
|
||||
/** undefined only for missing JSX namespace */
|
||||
position: number | undefined,
|
||||
preferTypeOnlyImport: boolean,
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
useRequire: boolean,
|
||||
program: Program,
|
||||
sourceFile: SourceFile,
|
||||
@@ -313,9 +395,9 @@ namespace ts.codefix {
|
||||
const checker = program.getTypeChecker();
|
||||
const existingImports = flatMap(exportInfos, info => getExistingImportDeclarations(info, checker, sourceFile, program.getCompilerOptions()));
|
||||
const useNamespace = position === undefined ? undefined : tryUseExistingNamespaceImport(existingImports, symbolName, position, checker);
|
||||
const addToExisting = tryAddToExistingImport(existingImports, position !== undefined && isTypeOnlyPosition(sourceFile, position));
|
||||
const addToExisting = tryAddToExistingImport(existingImports, isValidTypeOnlyUseSite, checker, program.getCompilerOptions());
|
||||
// Don't bother providing an action to add a new import if we can add to an existing one.
|
||||
const addImport = addToExisting ? [addToExisting] : getFixesForAddImport(exportInfos, existingImports, program, sourceFile, position, preferTypeOnlyImport, useRequire, host, preferences);
|
||||
const addImport = addToExisting ? [addToExisting] : getFixesForAddImport(exportInfos, existingImports, program, sourceFile, position, isValidTypeOnlyUseSite, useRequire, host, preferences);
|
||||
return [...(useNamespace ? [useNamespace] : emptyArray), ...addImport];
|
||||
}
|
||||
|
||||
@@ -371,37 +453,88 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function tryAddToExistingImport(existingImports: readonly FixAddToExistingImportInfo[], canUseTypeOnlyImport: boolean): FixAddToExistingImport | undefined {
|
||||
return firstDefined(existingImports, ({ declaration, importKind }): FixAddToExistingImport | undefined => {
|
||||
if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) return undefined;
|
||||
function getAddAsTypeOnly(
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
isForNewImportDeclaration: boolean,
|
||||
symbol: Symbol,
|
||||
targetFlags: SymbolFlags,
|
||||
checker: TypeChecker,
|
||||
compilerOptions: CompilerOptions
|
||||
) {
|
||||
if (!isValidTypeOnlyUseSite) {
|
||||
// Can't use a type-only import if the usage is an emitting position
|
||||
return AddAsTypeOnly.NotAllowed;
|
||||
}
|
||||
if (isForNewImportDeclaration && compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error) {
|
||||
// Not writing a (top-level) type-only import here would create an error because the runtime dependency is unnecessary
|
||||
return AddAsTypeOnly.Required;
|
||||
}
|
||||
if (compilerOptions.isolatedModules && compilerOptions.preserveValueImports &&
|
||||
(!(targetFlags & SymbolFlags.Value) || !!checker.getTypeOnlyAliasDeclaration(symbol))
|
||||
) {
|
||||
// A type-only import is required for this symbol if under these settings if the symbol will
|
||||
// be erased, which will happen if the target symbol is purely a type or if it was exported/imported
|
||||
// as type-only already somewhere between this import and the target.
|
||||
return AddAsTypeOnly.Required;
|
||||
}
|
||||
return AddAsTypeOnly.Allowed;
|
||||
}
|
||||
|
||||
function tryAddToExistingImport(existingImports: readonly FixAddToExistingImportInfo[], isValidTypeOnlyUseSite: boolean, checker: TypeChecker, compilerOptions: CompilerOptions): FixAddToExistingImport | undefined {
|
||||
return firstDefined(existingImports, ({ declaration, importKind, symbol, targetFlags }): FixAddToExistingImport | undefined => {
|
||||
if (importKind === ImportKind.CommonJS || importKind === ImportKind.Namespace || declaration.kind === SyntaxKind.ImportEqualsDeclaration) {
|
||||
// These kinds of imports are not combinable with anything
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (declaration.kind === SyntaxKind.VariableDeclaration) {
|
||||
return (importKind === ImportKind.Named || importKind === ImportKind.Default) && declaration.name.kind === SyntaxKind.ObjectBindingPattern
|
||||
? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: declaration.name, importKind, moduleSpecifier: declaration.initializer.arguments[0].text, canUseTypeOnlyImport: false }
|
||||
? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: declaration.name, importKind, moduleSpecifier: declaration.initializer.arguments[0].text, addAsTypeOnly: AddAsTypeOnly.NotAllowed }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
const { importClause } = declaration;
|
||||
if (!importClause || !isStringLiteralLike(declaration.moduleSpecifier)) return undefined;
|
||||
const { name, namedBindings } = importClause;
|
||||
// A type-only import may not have both a default and named imports, so the only way a name can
|
||||
// be added to an existing type-only import is adding a named import to existing named bindings.
|
||||
if (importClause.isTypeOnly && !(importKind === ImportKind.Named && namedBindings)) return undefined;
|
||||
return importKind === ImportKind.Default && !name || importKind === ImportKind.Named && (!namedBindings || namedBindings.kind === SyntaxKind.NamedImports)
|
||||
? { kind: ImportFixKind.AddToExisting, importClauseOrBindingPattern: importClause, importKind, moduleSpecifier: declaration.moduleSpecifier.text, canUseTypeOnlyImport }
|
||||
: undefined;
|
||||
|
||||
// N.B. we don't have to figure out whether to use the main program checker
|
||||
// or the AutoImportProvider checker because we're adding to an existing import; the existence of
|
||||
// the import guarantees the symbol came from the main program.
|
||||
const addAsTypeOnly = getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ false, symbol, targetFlags, checker, compilerOptions);
|
||||
|
||||
if (importKind === ImportKind.Default && (
|
||||
name || // Cannot add a default import to a declaration that already has one
|
||||
addAsTypeOnly === AddAsTypeOnly.Required && namedBindings // Cannot add a default import as type-only if the import already has named bindings
|
||||
)) return undefined;
|
||||
if (
|
||||
importKind === ImportKind.Named &&
|
||||
namedBindings?.kind === SyntaxKind.NamespaceImport // Cannot add a named import to a declaration that has a namespace import
|
||||
) return undefined;
|
||||
|
||||
return {
|
||||
kind: ImportFixKind.AddToExisting,
|
||||
importClauseOrBindingPattern: importClause,
|
||||
importKind,
|
||||
moduleSpecifier: declaration.moduleSpecifier.text,
|
||||
addAsTypeOnly,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getExistingImportDeclarations({ moduleSymbol, exportKind, targetFlags }: SymbolExportInfo, checker: TypeChecker, importingFile: SourceFile, compilerOptions: CompilerOptions): readonly FixAddToExistingImportInfo[] {
|
||||
function getExistingImportDeclarations({ moduleSymbol, exportKind, targetFlags, symbol }: SymbolExportInfo, checker: TypeChecker, importingFile: SourceFile, compilerOptions: CompilerOptions): readonly FixAddToExistingImportInfo[] {
|
||||
// Can't use an es6 import for a type in JS.
|
||||
if (!(targetFlags & SymbolFlags.Value) && isSourceFileJS(importingFile)) return emptyArray;
|
||||
const importKind = getImportKind(importingFile, exportKind, compilerOptions);
|
||||
return mapDefined(importingFile.imports, (moduleSpecifier): FixAddToExistingImportInfo | undefined => {
|
||||
const i = importFromModuleSpecifier(moduleSpecifier);
|
||||
if (isRequireVariableDeclaration(i.parent)) {
|
||||
return checker.resolveExternalModuleName(moduleSpecifier) === moduleSymbol ? { declaration: i.parent, importKind } : undefined;
|
||||
return checker.resolveExternalModuleName(moduleSpecifier) === moduleSymbol ? { declaration: i.parent, importKind, symbol, targetFlags } : undefined;
|
||||
}
|
||||
if (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration) {
|
||||
return checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind } : undefined;
|
||||
return checker.getSymbolAtLocation(moduleSpecifier) === moduleSymbol ? { declaration: i, importKind, symbol, targetFlags } : undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -437,35 +570,38 @@ namespace ts.codefix {
|
||||
program: Program,
|
||||
sourceFile: SourceFile,
|
||||
position: number | undefined,
|
||||
preferTypeOnlyImport: boolean,
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
useRequire: boolean,
|
||||
moduleSymbols: readonly SymbolExportInfo[],
|
||||
host: LanguageServiceHost,
|
||||
preferences: UserPreferences,
|
||||
fromCacheOnly?: boolean,
|
||||
): { computedWithoutCacheCount: number, fixes: readonly (FixAddNewImport | FixUseImportType)[] } {
|
||||
): { computedWithoutCacheCount: number, fixes: readonly (FixAddNewImport | FixAddJsdocTypeImport)[] } {
|
||||
const isJs = isSourceFileJS(sourceFile);
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const moduleSpecifierResolutionHost = createModuleSpecifierResolutionHost(program, host);
|
||||
const checker = program.getTypeChecker();
|
||||
const getChecker = memoizeOne((isFromPackageJson: boolean) => isFromPackageJson ? host.getPackageJsonAutoImportProvider!()!.getTypeChecker() : program.getTypeChecker());
|
||||
const getModuleSpecifiers = fromCacheOnly
|
||||
? (moduleSymbol: Symbol) => ({ moduleSpecifiers: moduleSpecifiers.tryGetModuleSpecifiersFromCache(moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences), computedWithoutCache: false })
|
||||
: (moduleSymbol: Symbol) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences);
|
||||
: (moduleSymbol: Symbol, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences);
|
||||
|
||||
let computedWithoutCacheCount = 0;
|
||||
const fixes = flatMap(moduleSymbols, exportInfo => {
|
||||
const { computedWithoutCache, moduleSpecifiers } = getModuleSpecifiers(exportInfo.moduleSymbol);
|
||||
const checker = getChecker(exportInfo.isFromPackageJson);
|
||||
const { computedWithoutCache, moduleSpecifiers } = getModuleSpecifiers(exportInfo.moduleSymbol, checker);
|
||||
const importedSymbolHasValueMeaning = !!(exportInfo.targetFlags & SymbolFlags.Value);
|
||||
const addAsTypeOnly = getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ true, exportInfo.symbol, exportInfo.targetFlags, checker, compilerOptions);
|
||||
computedWithoutCacheCount += computedWithoutCache ? 1 : 0;
|
||||
return moduleSpecifiers?.map((moduleSpecifier): FixAddNewImport | FixUseImportType =>
|
||||
return moduleSpecifiers?.map((moduleSpecifier): FixAddNewImport | FixAddJsdocTypeImport =>
|
||||
// `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
|
||||
!(exportInfo.targetFlags & SymbolFlags.Value) && isJs && position !== undefined
|
||||
? { kind: ImportFixKind.ImportType, moduleSpecifier, position, exportInfo }
|
||||
!importedSymbolHasValueMeaning && isJs && position !== undefined
|
||||
? { kind: ImportFixKind.JsdocTypeImport, moduleSpecifier, position, exportInfo }
|
||||
: {
|
||||
kind: ImportFixKind.AddNew,
|
||||
moduleSpecifier,
|
||||
importKind: getImportKind(sourceFile, exportInfo.exportKind, compilerOptions),
|
||||
useRequire,
|
||||
typeOnly: preferTypeOnlyImport,
|
||||
addAsTypeOnly,
|
||||
exportInfo,
|
||||
}
|
||||
);
|
||||
@@ -480,20 +616,29 @@ namespace ts.codefix {
|
||||
program: Program,
|
||||
sourceFile: SourceFile,
|
||||
position: number | undefined,
|
||||
preferTypeOnlyImport: boolean,
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
useRequire: boolean,
|
||||
host: LanguageServiceHost,
|
||||
preferences: UserPreferences,
|
||||
): readonly (FixAddNewImport | FixUseImportType)[] {
|
||||
const existingDeclaration = firstDefined(existingImports, info => newImportInfoFromExistingSpecifier(info, preferTypeOnlyImport, useRequire));
|
||||
return existingDeclaration ? [existingDeclaration] : getNewImportFixes(program, sourceFile, position, preferTypeOnlyImport, useRequire, exportInfos, host, preferences).fixes;
|
||||
): readonly (FixAddNewImport | FixAddJsdocTypeImport)[] {
|
||||
const existingDeclaration = firstDefined(existingImports, info => newImportInfoFromExistingSpecifier(info, isValidTypeOnlyUseSite, useRequire, program.getTypeChecker(), program.getCompilerOptions()));
|
||||
return existingDeclaration ? [existingDeclaration] : getNewImportFixes(program, sourceFile, position, isValidTypeOnlyUseSite, useRequire, exportInfos, host, preferences).fixes;
|
||||
}
|
||||
|
||||
function newImportInfoFromExistingSpecifier({ declaration, importKind }: FixAddToExistingImportInfo, preferTypeOnlyImport: boolean, useRequire: boolean): FixAddNewImport | undefined {
|
||||
function newImportInfoFromExistingSpecifier(
|
||||
{ declaration, importKind, symbol, targetFlags }: FixAddToExistingImportInfo,
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
useRequire: boolean,
|
||||
checker: TypeChecker,
|
||||
compilerOptions: CompilerOptions
|
||||
): FixAddNewImport | undefined {
|
||||
const moduleSpecifier = tryGetModuleSpecifierFromDeclaration(declaration);
|
||||
return moduleSpecifier
|
||||
? { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, typeOnly: preferTypeOnlyImport, useRequire }
|
||||
: undefined;
|
||||
if (moduleSpecifier) {
|
||||
const addAsTypeOnly = useRequire
|
||||
? AddAsTypeOnly.NotAllowed
|
||||
: getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ true, symbol, targetFlags, checker, compilerOptions);
|
||||
return { kind: ImportFixKind.AddNew, moduleSpecifier, importKind, addAsTypeOnly, useRequire };
|
||||
}
|
||||
}
|
||||
|
||||
interface FixesInfo { readonly fixes: readonly ImportFix[]; readonly symbolName: string; }
|
||||
@@ -545,7 +690,7 @@ namespace ts.codefix {
|
||||
const symbolName = umdSymbol.name;
|
||||
const exportInfos: readonly SymbolExportInfo[] = [{ symbol: umdSymbol, moduleSymbol: symbol, moduleFileName: undefined, exportKind: ExportKind.UMD, targetFlags: symbol.flags, isFromPackageJson: false }];
|
||||
const useRequire = shouldUseRequire(sourceFile, program);
|
||||
const fixes = getImportFixes(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, /*preferTypeOnlyImport*/ false, useRequire, program, sourceFile, host, preferences);
|
||||
const fixes = getImportFixes(exportInfos, symbolName, isIdentifier(token) ? token.getStart(sourceFile) : undefined, /*isValidTypeOnlyUseSite*/ false, useRequire, program, sourceFile, host, preferences);
|
||||
return { fixes, symbolName };
|
||||
}
|
||||
function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined {
|
||||
@@ -612,11 +757,11 @@ namespace ts.codefix {
|
||||
// "default" is a keyword and not a legal identifier for the import, so we don't expect it here
|
||||
Debug.assert(symbolName !== InternalSymbolName.Default, "'default' isn't a legal identifier and couldn't occur here");
|
||||
|
||||
const preferTypeOnlyImport = compilerOptions.importsNotUsedAsValues === ImportsNotUsedAsValues.Error && isValidTypeOnlyAliasUseSite(symbolToken);
|
||||
const isValidTypeOnlyUseSite = isValidTypeOnlyAliasUseSite(symbolToken);
|
||||
const useRequire = shouldUseRequire(sourceFile, program);
|
||||
const exportInfos = getExportInfos(symbolName, getMeaningFromLocation(symbolToken), cancellationToken, sourceFile, program, useAutoImportProvider, host, preferences);
|
||||
const fixes = arrayFrom(flatMapIterator(exportInfos.entries(), ([_, exportInfos]) =>
|
||||
getImportFixes(exportInfos, symbolName, symbolToken.getStart(sourceFile), preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences)));
|
||||
getImportFixes(exportInfos, symbolName, symbolToken.getStart(sourceFile), isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences)));
|
||||
return { fixes, symbolName };
|
||||
}
|
||||
|
||||
@@ -707,34 +852,44 @@ namespace ts.codefix {
|
||||
return allowSyntheticDefaults ? ImportKind.Default : ImportKind.CommonJS;
|
||||
}
|
||||
|
||||
function codeActionForFix(context: textChanges.TextChangesContext, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference): CodeFixAction {
|
||||
function codeActionForFix(context: textChanges.TextChangesContext, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference, compilerOptions: CompilerOptions): CodeFixAction {
|
||||
let diag!: DiagnosticAndArguments;
|
||||
const changes = textChanges.ChangeTracker.with(context, tracker => {
|
||||
diag = codeActionForFixWorker(tracker, sourceFile, symbolName, fix, quotePreference);
|
||||
diag = codeActionForFixWorker(tracker, sourceFile, symbolName, fix, quotePreference, compilerOptions);
|
||||
});
|
||||
return createCodeFixAction(importFixName, changes, diag, importFixId, Diagnostics.Add_all_missing_imports);
|
||||
}
|
||||
function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference): DiagnosticAndArguments {
|
||||
function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference, compilerOptions: CompilerOptions): DiagnosticAndArguments {
|
||||
switch (fix.kind) {
|
||||
case ImportFixKind.UseNamespace:
|
||||
addNamespaceQualifier(changes, sourceFile, fix);
|
||||
return [Diagnostics.Change_0_to_1, symbolName, `${fix.namespacePrefix}.${symbolName}`];
|
||||
case ImportFixKind.ImportType:
|
||||
case ImportFixKind.JsdocTypeImport:
|
||||
addImportType(changes, sourceFile, fix, quotePreference);
|
||||
return [Diagnostics.Change_0_to_1, symbolName, getImportTypePrefix(fix.moduleSpecifier, quotePreference) + symbolName];
|
||||
case ImportFixKind.AddToExisting: {
|
||||
const { importClauseOrBindingPattern, importKind, canUseTypeOnlyImport, moduleSpecifier } = fix;
|
||||
doAddExistingFix(changes, sourceFile, importClauseOrBindingPattern, importKind === ImportKind.Default ? symbolName : undefined, importKind === ImportKind.Named ? [symbolName] : emptyArray, canUseTypeOnlyImport);
|
||||
const { importClauseOrBindingPattern, importKind, addAsTypeOnly, moduleSpecifier } = fix;
|
||||
doAddExistingFix(
|
||||
changes,
|
||||
sourceFile,
|
||||
importClauseOrBindingPattern,
|
||||
importKind === ImportKind.Default ? { name: symbolName, addAsTypeOnly } : undefined,
|
||||
importKind === ImportKind.Named ? [{ name: symbolName, addAsTypeOnly }] : emptyArray,
|
||||
compilerOptions);
|
||||
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
|
||||
return [importKind === ImportKind.Default ? Diagnostics.Add_default_import_0_to_existing_import_declaration_from_1 : Diagnostics.Add_0_to_existing_import_declaration_from_1, symbolName, moduleSpecifierWithoutQuotes]; // you too!
|
||||
return [
|
||||
importKind === ImportKind.Default ? Diagnostics.Add_default_import_0_to_existing_import_declaration_from_1 : Diagnostics.Add_0_to_existing_import_declaration_from_1,
|
||||
symbolName,
|
||||
moduleSpecifierWithoutQuotes
|
||||
]; // you too!
|
||||
}
|
||||
case ImportFixKind.AddNew: {
|
||||
const { importKind, moduleSpecifier, typeOnly, useRequire } = fix;
|
||||
const { importKind, moduleSpecifier, addAsTypeOnly, useRequire } = fix;
|
||||
const getDeclarations = useRequire ? getNewRequires : getNewImports;
|
||||
const importsCollection = importKind === ImportKind.Default ? { defaultImport: symbolName, typeOnly } :
|
||||
importKind === ImportKind.Named ? { namedImports: [symbolName], typeOnly } :
|
||||
{ namespaceLikeImport: { importKind, name: symbolName }, typeOnly };
|
||||
insertImports(changes, sourceFile, getDeclarations(moduleSpecifier, quotePreference, importsCollection), /*blankLineBetween*/ true);
|
||||
const defaultImport: Import | undefined = importKind === ImportKind.Default ? { name: symbolName, addAsTypeOnly } : undefined;
|
||||
const namedImports: Import[] | undefined = importKind === ImportKind.Named ? [{ name: symbolName, addAsTypeOnly }] : undefined;
|
||||
const namespaceLikeImport = importKind === ImportKind.Namespace || importKind === ImportKind.CommonJS ? { importKind, name: symbolName, addAsTypeOnly } : undefined;
|
||||
insertImports(changes, sourceFile, getDeclarations(moduleSpecifier, quotePreference, defaultImport, namedImports, namespaceLikeImport), /*blankLineBetween*/ true);
|
||||
return [importKind === ImportKind.Default ? Diagnostics.Import_default_0_from_module_1 : Diagnostics.Import_0_from_module_1, symbolName, moduleSpecifier];
|
||||
}
|
||||
default:
|
||||
@@ -742,32 +897,55 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
function doAddExistingFix(changes: textChanges.ChangeTracker, sourceFile: SourceFile, clause: ImportClause | ObjectBindingPattern, defaultImport: string | undefined, namedImports: readonly string[], canUseTypeOnlyImport: boolean): void {
|
||||
function doAddExistingFix(
|
||||
changes: textChanges.ChangeTracker,
|
||||
sourceFile: SourceFile,
|
||||
clause: ImportClause | ObjectBindingPattern,
|
||||
defaultImport: Import | undefined,
|
||||
namedImports: readonly Import[],
|
||||
compilerOptions: CompilerOptions,
|
||||
): void {
|
||||
if (clause.kind === SyntaxKind.ObjectBindingPattern) {
|
||||
if (defaultImport) {
|
||||
addElementToBindingPattern(clause, defaultImport, "default");
|
||||
addElementToBindingPattern(clause, defaultImport.name, "default");
|
||||
}
|
||||
for (const specifier of namedImports) {
|
||||
addElementToBindingPattern(clause, specifier, /*propertyName*/ undefined);
|
||||
addElementToBindingPattern(clause, specifier.name, /*propertyName*/ undefined);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const convertTypeOnlyToRegular = !canUseTypeOnlyImport && clause.isTypeOnly;
|
||||
const promoteFromTypeOnly = clause.isTypeOnly && some([defaultImport, ...namedImports], i => i?.addAsTypeOnly === AddAsTypeOnly.NotAllowed);
|
||||
const existingSpecifiers = clause.namedBindings && tryCast(clause.namedBindings, isNamedImports)?.elements;
|
||||
// If we are promoting from a type-only import and `--isolatedModules` and `--preserveValueImports`
|
||||
// are enabled, we need to make every existing import specifier type-only. It may be possible that
|
||||
// some of them don't strictly need to be marked type-only (if they have a value meaning and are
|
||||
// never used in an emitting position). These are allowed to be imported without being type-only,
|
||||
// but the user has clearly already signified that they don't need them to be present at runtime
|
||||
// by placing them in a type-only import. So, just mark each specifier as type-only.
|
||||
const convertExistingToTypeOnly = promoteFromTypeOnly && compilerOptions.preserveValueImports && compilerOptions.isolatedModules;
|
||||
|
||||
if (defaultImport) {
|
||||
Debug.assert(!clause.name, "Cannot add a default import to an import clause that already has one");
|
||||
changes.insertNodeAt(sourceFile, clause.getStart(sourceFile), factory.createIdentifier(defaultImport), { suffix: ", " });
|
||||
changes.insertNodeAt(sourceFile, clause.getStart(sourceFile), factory.createIdentifier(defaultImport.name), { suffix: ", " });
|
||||
}
|
||||
|
||||
if (namedImports.length) {
|
||||
const existingSpecifiers = clause.namedBindings && cast(clause.namedBindings, isNamedImports).elements;
|
||||
const newSpecifiers = stableSort(
|
||||
namedImports.map(name => factory.createImportSpecifier(/*propertyName*/ undefined, factory.createIdentifier(name))),
|
||||
namedImports.map(namedImport => factory.createImportSpecifier(
|
||||
(!clause.isTypeOnly || promoteFromTypeOnly) && needsTypeOnly(namedImport),
|
||||
/*propertyName*/ undefined,
|
||||
factory.createIdentifier(namedImport.name))),
|
||||
OrganizeImports.compareImportOrExportSpecifiers);
|
||||
|
||||
if (existingSpecifiers?.length && OrganizeImports.importSpecifiersAreSorted(existingSpecifiers)) {
|
||||
for (const spec of newSpecifiers) {
|
||||
const insertionIndex = OrganizeImports.getImportSpecifierInsertionIndex(existingSpecifiers, spec);
|
||||
// Organize imports puts type-only import specifiers last, so if we're
|
||||
// adding a non-type-only specifier and converting all the other ones to
|
||||
// type-only, there's no need to ask for the insertion index - it's 0.
|
||||
const insertionIndex = convertExistingToTypeOnly && !spec.isTypeOnly
|
||||
? 0
|
||||
: OrganizeImports.getImportSpecifierInsertionIndex(existingSpecifiers, spec);
|
||||
const prevSpecifier = (clause.namedBindings as NamedImports).elements[insertionIndex - 1];
|
||||
if (prevSpecifier) {
|
||||
changes.insertNodeInListAfter(sourceFile, prevSpecifier, spec);
|
||||
@@ -799,8 +977,13 @@ namespace ts.codefix {
|
||||
}
|
||||
}
|
||||
|
||||
if (convertTypeOnlyToRegular) {
|
||||
if (promoteFromTypeOnly) {
|
||||
changes.delete(sourceFile, getTypeKeywordOfTypeOnlyImport(clause, sourceFile));
|
||||
if (convertExistingToTypeOnly && existingSpecifiers) {
|
||||
for (const specifier of existingSpecifiers) {
|
||||
changes.insertModifierBefore(sourceFile, SyntaxKind.TypeKeyword, specifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addElementToBindingPattern(bindingPattern: ObjectBindingPattern, name: string, propertyName: string | undefined) {
|
||||
@@ -818,7 +1001,7 @@ namespace ts.codefix {
|
||||
changes.insertText(sourceFile, position, namespacePrefix + ".");
|
||||
}
|
||||
|
||||
function addImportType(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { moduleSpecifier, position }: FixUseImportType, quotePreference: QuotePreference): void {
|
||||
function addImportType(changes: textChanges.ChangeTracker, sourceFile: SourceFile, { moduleSpecifier, position }: FixAddJsdocTypeImport, quotePreference: QuotePreference): void {
|
||||
changes.insertText(sourceFile, position, getImportTypePrefix(moduleSpecifier, quotePreference));
|
||||
}
|
||||
|
||||
@@ -827,37 +1010,60 @@ namespace ts.codefix {
|
||||
return `import(${quote}${moduleSpecifier}${quote}).`;
|
||||
}
|
||||
|
||||
interface Import {
|
||||
readonly name: string;
|
||||
readonly addAsTypeOnly: AddAsTypeOnly;
|
||||
}
|
||||
|
||||
interface ImportsCollection {
|
||||
readonly typeOnly: boolean;
|
||||
readonly defaultImport?: string;
|
||||
readonly namedImports?: string[];
|
||||
readonly defaultImport?: Import;
|
||||
readonly namedImports?: ESMap<string, AddAsTypeOnly>;
|
||||
readonly namespaceLikeImport?: {
|
||||
readonly importKind: ImportKind.CommonJS | ImportKind.Namespace;
|
||||
readonly name: string;
|
||||
readonly addAsTypeOnly: AddAsTypeOnly;
|
||||
};
|
||||
}
|
||||
function getNewImports(moduleSpecifier: string, quotePreference: QuotePreference, imports: ImportsCollection): AnyImportSyntax | readonly AnyImportSyntax[] {
|
||||
|
||||
function needsTypeOnly({ addAsTypeOnly }: { addAsTypeOnly: AddAsTypeOnly }): boolean {
|
||||
return addAsTypeOnly === AddAsTypeOnly.Required;
|
||||
}
|
||||
|
||||
function getNewImports(
|
||||
moduleSpecifier: string,
|
||||
quotePreference: QuotePreference,
|
||||
defaultImport: Import | undefined,
|
||||
namedImports: readonly Import[] | undefined,
|
||||
namespaceLikeImport: Import & { importKind: ImportKind.CommonJS | ImportKind.Namespace } | undefined
|
||||
): AnyImportSyntax | readonly AnyImportSyntax[] {
|
||||
const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
|
||||
let statements: AnyImportSyntax | readonly AnyImportSyntax[] | undefined;
|
||||
if (imports.defaultImport !== undefined || imports.namedImports?.length) {
|
||||
if (defaultImport !== undefined || namedImports?.length) {
|
||||
const topLevelTypeOnly = (!defaultImport || needsTypeOnly(defaultImport)) && every(namedImports, needsTypeOnly);
|
||||
statements = combine(statements, makeImport(
|
||||
imports.defaultImport === undefined ? undefined : factory.createIdentifier(imports.defaultImport),
|
||||
imports.namedImports?.map(n => factory.createImportSpecifier(/*propertyName*/ undefined, factory.createIdentifier(n))), moduleSpecifier, quotePreference, imports.typeOnly));
|
||||
defaultImport && factory.createIdentifier(defaultImport.name),
|
||||
namedImports?.map(({ addAsTypeOnly, name }) => factory.createImportSpecifier(
|
||||
!topLevelTypeOnly && addAsTypeOnly === AddAsTypeOnly.Required,
|
||||
/*propertyName*/ undefined,
|
||||
factory.createIdentifier(name))),
|
||||
moduleSpecifier,
|
||||
quotePreference,
|
||||
topLevelTypeOnly));
|
||||
}
|
||||
const { namespaceLikeImport, typeOnly } = imports;
|
||||
|
||||
if (namespaceLikeImport) {
|
||||
const declaration = namespaceLikeImport.importKind === ImportKind.CommonJS
|
||||
? factory.createImportEqualsDeclaration(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
typeOnly,
|
||||
needsTypeOnly(namespaceLikeImport),
|
||||
factory.createIdentifier(namespaceLikeImport.name),
|
||||
factory.createExternalModuleReference(quotedModuleSpecifier))
|
||||
: factory.createImportDeclaration(
|
||||
/*decorators*/ undefined,
|
||||
/*modifiers*/ undefined,
|
||||
factory.createImportClause(
|
||||
typeOnly,
|
||||
needsTypeOnly(namespaceLikeImport),
|
||||
/*name*/ undefined,
|
||||
factory.createNamespaceImport(factory.createIdentifier(namespaceLikeImport.name))),
|
||||
quotedModuleSpecifier,
|
||||
@@ -867,21 +1073,21 @@ namespace ts.codefix {
|
||||
return Debug.checkDefined(statements);
|
||||
}
|
||||
|
||||
function getNewRequires(moduleSpecifier: string, quotePreference: QuotePreference, imports: ImportsCollection): RequireVariableStatement | readonly RequireVariableStatement[] {
|
||||
function getNewRequires(moduleSpecifier: string, quotePreference: QuotePreference, defaultImport: Import | undefined, namedImports: readonly Import[] | undefined, namespaceLikeImport: Import | undefined): RequireVariableStatement | readonly RequireVariableStatement[] {
|
||||
const quotedModuleSpecifier = makeStringLiteral(moduleSpecifier, quotePreference);
|
||||
let statements: RequireVariableStatement | readonly RequireVariableStatement[] | undefined;
|
||||
// const { default: foo, bar, etc } = require('./mod');
|
||||
if (imports.defaultImport || imports.namedImports?.length) {
|
||||
const bindingElements = imports.namedImports?.map(name => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name)) || [];
|
||||
if (imports.defaultImport) {
|
||||
bindingElements.unshift(factory.createBindingElement(/*dotDotDotToken*/ undefined, "default", imports.defaultImport));
|
||||
if (defaultImport || namedImports?.length) {
|
||||
const bindingElements = namedImports?.map(({ name }) => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name)) || [];
|
||||
if (defaultImport) {
|
||||
bindingElements.unshift(factory.createBindingElement(/*dotDotDotToken*/ undefined, "default", defaultImport.name));
|
||||
}
|
||||
const declaration = createConstEqualsRequireDeclaration(factory.createObjectBindingPattern(bindingElements), quotedModuleSpecifier);
|
||||
statements = combine(statements, declaration);
|
||||
}
|
||||
// const foo = require('./mod');
|
||||
if (imports.namespaceLikeImport) {
|
||||
const declaration = createConstEqualsRequireDeclaration(imports.namespaceLikeImport.name, quotedModuleSpecifier);
|
||||
if (namespaceLikeImport) {
|
||||
const declaration = createConstEqualsRequireDeclaration(namespaceLikeImport.name, quotedModuleSpecifier);
|
||||
statements = combine(statements, declaration);
|
||||
}
|
||||
return Debug.checkDefined(statements);
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace ts.codefix {
|
||||
if (!isIdentifier(element.name) || element.initializer) {
|
||||
return undefined;
|
||||
}
|
||||
importSpecifiers.push(factory.createImportSpecifier(tryCast(element.propertyName, isIdentifier), element.name));
|
||||
importSpecifiers.push(factory.createImportSpecifier(/*isTypeOnly*/ false, tryCast(element.propertyName, isIdentifier), element.name));
|
||||
}
|
||||
|
||||
if (importSpecifiers.length) {
|
||||
|
||||
+186
-55
@@ -149,6 +149,7 @@ namespace ts.Completions {
|
||||
FunctionLikeBodyKeywords, // Keywords at function like body
|
||||
TypeAssertionKeywords,
|
||||
TypeKeywords,
|
||||
TypeKeyword, // Literally just `type`
|
||||
Last = TypeKeywords
|
||||
}
|
||||
|
||||
@@ -287,7 +288,7 @@ namespace ts.Completions {
|
||||
case CompletionDataKind.JsDocParameterName:
|
||||
return jsdocCompletionInfo(JsDoc.getJSDocParameterNameCompletions(completionData.tag));
|
||||
case CompletionDataKind.Keywords:
|
||||
return specificKeywordCompletionInfo(completionData.keywords);
|
||||
return specificKeywordCompletionInfo(completionData.keywordCompletions, completionData.isNewIdentifierLocation);
|
||||
default:
|
||||
return Debug.assertNever(completionData);
|
||||
}
|
||||
@@ -368,20 +369,39 @@ namespace ts.Completions {
|
||||
return { isGlobalCompletion: false, isMemberCompletion: false, isNewIdentifierLocation: false, entries };
|
||||
}
|
||||
|
||||
function specificKeywordCompletionInfo(keywords: readonly SyntaxKind[]): CompletionInfo {
|
||||
function keywordToCompletionEntry(keyword: TokenSyntaxKind) {
|
||||
return {
|
||||
name: tokenToString(keyword)!,
|
||||
kind: ScriptElementKind.keyword,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
sortText: SortText.GlobalsOrKeywords,
|
||||
};
|
||||
}
|
||||
|
||||
function specificKeywordCompletionInfo(entries: readonly CompletionEntry[], isNewIdentifierLocation: boolean): CompletionInfo {
|
||||
return {
|
||||
isGlobalCompletion: false,
|
||||
isMemberCompletion: false,
|
||||
isNewIdentifierLocation: false,
|
||||
entries: keywords.map(k => ({
|
||||
name: tokenToString(k)!,
|
||||
kind: ScriptElementKind.keyword,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
sortText: SortText.GlobalsOrKeywords,
|
||||
})),
|
||||
isNewIdentifierLocation,
|
||||
entries: entries.slice(),
|
||||
};
|
||||
}
|
||||
|
||||
function keywordCompletionData(keywordFilters: KeywordCompletionFilters, filterOutTsOnlyKeywords: boolean, isNewIdentifierLocation: boolean): Request {
|
||||
return {
|
||||
kind: CompletionDataKind.Keywords,
|
||||
keywordCompletions: getKeywordCompletions(keywordFilters, filterOutTsOnlyKeywords),
|
||||
isNewIdentifierLocation,
|
||||
};
|
||||
}
|
||||
|
||||
function keywordFiltersFromSyntaxKind(keywordCompletion: TokenSyntaxKind): KeywordCompletionFilters {
|
||||
switch (keywordCompletion) {
|
||||
case SyntaxKind.TypeKeyword: return KeywordCompletionFilters.TypeKeyword;
|
||||
default: Debug.fail("Unknown mapping from SyntaxKind to KeywordCompletionFilters");
|
||||
}
|
||||
}
|
||||
|
||||
function getOptionalReplacementSpan(location: Node | undefined) {
|
||||
// StringLiteralLike locations are handled separately in stringCompletions.ts
|
||||
return location?.kind === SyntaxKind.Identifier ? createTextSpanFromNode(location) : undefined;
|
||||
@@ -390,6 +410,7 @@ namespace ts.Completions {
|
||||
function completionInfoFromData(sourceFile: SourceFile, typeChecker: TypeChecker, compilerOptions: CompilerOptions, log: Log, completionData: CompletionData, preferences: UserPreferences): CompletionInfo | undefined {
|
||||
const {
|
||||
symbols,
|
||||
contextToken,
|
||||
completionKind,
|
||||
isInSnippetScope,
|
||||
isNewIdentifierLocation,
|
||||
@@ -422,7 +443,8 @@ namespace ts.Completions {
|
||||
const uniqueNames = getCompletionEntriesFromSymbols(
|
||||
symbols,
|
||||
entries,
|
||||
/* contextToken */ undefined,
|
||||
/*replacementToken*/ undefined,
|
||||
contextToken,
|
||||
location,
|
||||
sourceFile,
|
||||
typeChecker,
|
||||
@@ -450,7 +472,8 @@ namespace ts.Completions {
|
||||
getCompletionEntriesFromSymbols(
|
||||
symbols,
|
||||
entries,
|
||||
/* contextToken */ undefined,
|
||||
/*replacementToken*/ undefined,
|
||||
contextToken,
|
||||
location,
|
||||
sourceFile,
|
||||
typeChecker,
|
||||
@@ -591,6 +614,7 @@ namespace ts.Completions {
|
||||
function createCompletionEntry(
|
||||
symbol: Symbol,
|
||||
sortText: SortText,
|
||||
replacementToken: Node | undefined,
|
||||
contextToken: Node | undefined,
|
||||
location: Node,
|
||||
sourceFile: SourceFile,
|
||||
@@ -602,13 +626,12 @@ namespace ts.Completions {
|
||||
propertyAccessToConvert: PropertyAccessExpression | undefined,
|
||||
isJsxInitializer: IsJsxInitializer | undefined,
|
||||
importCompletionNode: Node | undefined,
|
||||
isTypeOnlyImport: boolean,
|
||||
useSemicolons: boolean,
|
||||
options: CompilerOptions,
|
||||
preferences: UserPreferences,
|
||||
): CompletionEntry | undefined {
|
||||
let insertText: string | undefined;
|
||||
let replacementSpan = getReplacementSpanForContextToken(contextToken);
|
||||
let replacementSpan = getReplacementSpanForContextToken(replacementToken);
|
||||
let data: CompletionEntryData | undefined;
|
||||
let isSnippet: true | undefined;
|
||||
let sourceDisplay;
|
||||
@@ -662,7 +685,7 @@ namespace ts.Completions {
|
||||
if (originIsResolvedExport(origin)) {
|
||||
sourceDisplay = [textPart(origin.moduleSpecifier)];
|
||||
if (importCompletionNode) {
|
||||
({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importCompletionNode, isTypeOnlyImport, origin, useSemicolons, options, preferences));
|
||||
({ insertText, replacementSpan } = getInsertTextAndReplacementSpanForImportCompletion(name, importCompletionNode, contextToken, origin, useSemicolons, options, preferences));
|
||||
isSnippet = preferences.includeCompletionsWithSnippetText ? true : undefined;
|
||||
}
|
||||
}
|
||||
@@ -747,9 +770,9 @@ namespace ts.Completions {
|
||||
};
|
||||
}
|
||||
|
||||
function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, isTypeOnly: boolean | undefined, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) {
|
||||
function getInsertTextAndReplacementSpanForImportCompletion(name: string, importCompletionNode: Node, contextToken: Node | undefined, origin: SymbolOriginInfoResolvedExport, useSemicolons: boolean, options: CompilerOptions, preferences: UserPreferences) {
|
||||
const sourceFile = importCompletionNode.getSourceFile();
|
||||
const replacementSpan = createTextSpanFromNode(importCompletionNode, sourceFile);
|
||||
const replacementSpan = createTextSpanFromNode(findAncestor(importCompletionNode, or(isImportDeclaration, isImportEqualsDeclaration)) || importCompletionNode, sourceFile);
|
||||
const quotedModuleSpecifier = quote(sourceFile, preferences, origin.moduleSpecifier);
|
||||
const exportKind =
|
||||
origin.isDefaultExport ? ExportKind.Default :
|
||||
@@ -757,13 +780,16 @@ namespace ts.Completions {
|
||||
ExportKind.Named;
|
||||
const tabStop = preferences.includeCompletionsWithSnippetText ? "$1" : "";
|
||||
const importKind = codefix.getImportKind(sourceFile, exportKind, options, /*forceImportKeyword*/ true);
|
||||
const typeOnlyPrefix = isTypeOnly ? ` ${tokenToString(SyntaxKind.TypeKeyword)} ` : " ";
|
||||
const isTopLevelTypeOnly = tryCast(importCompletionNode, isImportDeclaration)?.importClause?.isTypeOnly || tryCast(importCompletionNode, isImportEqualsDeclaration)?.isTypeOnly;
|
||||
const isImportSpecifierTypeOnly = couldBeTypeOnlyImportSpecifier(importCompletionNode, contextToken);
|
||||
const topLevelTypeOnlyText = isTopLevelTypeOnly ? ` ${tokenToString(SyntaxKind.TypeKeyword)} ` : " ";
|
||||
const importSpecifierTypeOnlyText = isImportSpecifierTypeOnly ? `${tokenToString(SyntaxKind.TypeKeyword)} ` : "";
|
||||
const suffix = useSemicolons ? ";" : "";
|
||||
switch (importKind) {
|
||||
case ImportKind.CommonJS: return { replacementSpan, insertText: `import${typeOnlyPrefix}${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` };
|
||||
case ImportKind.Default: return { replacementSpan, insertText: `import${typeOnlyPrefix}${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` };
|
||||
case ImportKind.Namespace: return { replacementSpan, insertText: `import${typeOnlyPrefix}* as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` };
|
||||
case ImportKind.Named: return { replacementSpan, insertText: `import${typeOnlyPrefix}{ ${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` };
|
||||
case ImportKind.CommonJS: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} = require(${quotedModuleSpecifier})${suffix}` };
|
||||
case ImportKind.Default: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}${escapeSnippetText(name)}${tabStop} from ${quotedModuleSpecifier}${suffix}` };
|
||||
case ImportKind.Namespace: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}* as ${escapeSnippetText(name)} from ${quotedModuleSpecifier}${suffix}` };
|
||||
case ImportKind.Named: return { replacementSpan, insertText: `import${topLevelTypeOnlyText}{ ${importSpecifierTypeOnlyText}${escapeSnippetText(name)}${tabStop} } from ${quotedModuleSpecifier}${suffix}` };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -795,6 +821,7 @@ namespace ts.Completions {
|
||||
export function getCompletionEntriesFromSymbols(
|
||||
symbols: readonly Symbol[],
|
||||
entries: Push<CompletionEntry>,
|
||||
replacementToken: Node | undefined,
|
||||
contextToken: Node | undefined,
|
||||
location: Node,
|
||||
sourceFile: SourceFile,
|
||||
@@ -816,7 +843,6 @@ namespace ts.Completions {
|
||||
const start = timestamp();
|
||||
const variableDeclaration = getVariableDeclaration(location);
|
||||
const useSemicolons = probablyUsesSemicolons(sourceFile);
|
||||
const isTypeOnlyImport = !!importCompletionNode && isTypeOnlyImportOrExportDeclaration(location.parent);
|
||||
// Tracks unique names.
|
||||
// Value is set to false for global variables or completions from external module exports, because we can have multiple of those;
|
||||
// true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports.
|
||||
@@ -836,6 +862,7 @@ namespace ts.Completions {
|
||||
const entry = createCompletionEntry(
|
||||
symbol,
|
||||
sortText,
|
||||
replacementToken,
|
||||
contextToken,
|
||||
location,
|
||||
sourceFile,
|
||||
@@ -847,7 +874,6 @@ namespace ts.Completions {
|
||||
propertyAccessToConvert,
|
||||
isJsxInitializer,
|
||||
importCompletionNode,
|
||||
isTypeOnlyImport,
|
||||
useSemicolons,
|
||||
compilerOptions,
|
||||
preferences
|
||||
@@ -1050,7 +1076,7 @@ namespace ts.Completions {
|
||||
case CompletionDataKind.JsDocParameterName:
|
||||
return JsDoc.getJSDocParameterNameCompletionDetails(name);
|
||||
case CompletionDataKind.Keywords:
|
||||
return request.keywords.indexOf(stringToToken(name)!) > -1 ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined;
|
||||
return some(request.keywordCompletions, c => c.name === name) ? createSimpleDetails(name, ScriptElementKind.keyword, SymbolDisplayPartKind.keyword) : undefined;
|
||||
default:
|
||||
return Debug.assertNever(request);
|
||||
}
|
||||
@@ -1107,7 +1133,7 @@ namespace ts.Completions {
|
||||
): CodeActionsAndSourceDisplay {
|
||||
if (data?.moduleSpecifier) {
|
||||
const { contextToken, previousToken } = getRelevantTokens(position, sourceFile);
|
||||
if (previousToken && getImportCompletionNode(contextToken || previousToken)) {
|
||||
if (previousToken && getImportStatementCompletionInfo(contextToken || previousToken).replacementNode) {
|
||||
// Import statement completion: 'import c|'
|
||||
return { codeActions: undefined, sourceDisplay: [textPart(data.moduleSpecifier)] };
|
||||
}
|
||||
@@ -1164,6 +1190,7 @@ namespace ts.Completions {
|
||||
readonly symbolToOriginInfoMap: SymbolOriginInfoMap;
|
||||
readonly recommendedCompletion: Symbol | undefined;
|
||||
readonly previousToken: Node | undefined;
|
||||
readonly contextToken: Node | undefined;
|
||||
readonly isJsxInitializer: IsJsxInitializer;
|
||||
readonly insideJsDocTagTypeExpression: boolean;
|
||||
readonly symbolToSortTextIdMap: SymbolSortTextIdMap;
|
||||
@@ -1176,7 +1203,7 @@ namespace ts.Completions {
|
||||
type Request =
|
||||
| { readonly kind: CompletionDataKind.JsDocTagName | CompletionDataKind.JsDocTag }
|
||||
| { readonly kind: CompletionDataKind.JsDocParameterName, tag: JSDocParameterTag }
|
||||
| { readonly kind: CompletionDataKind.Keywords, keywords: readonly SyntaxKind[] };
|
||||
| { readonly kind: CompletionDataKind.Keywords, keywordCompletions: readonly CompletionEntry[], isNewIdentifierLocation: boolean };
|
||||
|
||||
export const enum CompletionKind {
|
||||
ObjectPropertyDeclaration,
|
||||
@@ -1332,6 +1359,7 @@ namespace ts.Completions {
|
||||
start = timestamp();
|
||||
// The decision to provide completion depends on the contextToken, which is determined through the previousToken.
|
||||
// Note: 'previousToken' (and thus 'contextToken') can be undefined if we are the beginning of the file
|
||||
const isJsOnlyLocation = !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile);
|
||||
const tokens = getRelevantTokens(position, sourceFile);
|
||||
const previousToken = tokens.previousToken!;
|
||||
let contextToken = tokens.contextToken!;
|
||||
@@ -1350,23 +1378,35 @@ namespace ts.Completions {
|
||||
let isJsxIdentifierExpected = false;
|
||||
let importCompletionNode: Node | undefined;
|
||||
let location = getTouchingPropertyName(sourceFile, position);
|
||||
let keywordFilters = KeywordCompletionFilters.None;
|
||||
let isNewIdentifierLocation = false;
|
||||
|
||||
if (contextToken) {
|
||||
const importCompletionCandidate = getImportCompletionNode(contextToken);
|
||||
if (importCompletionCandidate === SyntaxKind.FromKeyword) {
|
||||
return { kind: CompletionDataKind.Keywords, keywords: [SyntaxKind.FromKeyword] };
|
||||
const importStatementCompletion = getImportStatementCompletionInfo(contextToken);
|
||||
isNewIdentifierLocation = importStatementCompletion.isNewIdentifierLocation;
|
||||
if (importStatementCompletion.keywordCompletion) {
|
||||
if (importStatementCompletion.isKeywordOnlyCompletion) {
|
||||
return {
|
||||
kind: CompletionDataKind.Keywords,
|
||||
keywordCompletions: [keywordToCompletionEntry(importStatementCompletion.keywordCompletion)],
|
||||
isNewIdentifierLocation,
|
||||
};
|
||||
}
|
||||
keywordFilters = keywordFiltersFromSyntaxKind(importStatementCompletion.keywordCompletion);
|
||||
}
|
||||
// Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier`
|
||||
// added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature
|
||||
// is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients
|
||||
// to opt in with the `includeCompletionsForImportStatements` user preference.
|
||||
if (importCompletionCandidate && preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) {
|
||||
importCompletionNode = importCompletionCandidate;
|
||||
if (importStatementCompletion.replacementNode && preferences.includeCompletionsForImportStatements && preferences.includeCompletionsWithInsertText) {
|
||||
// Import statement completions use `insertText`, and also require the `data` property of `CompletionEntryIdentifier`
|
||||
// added in TypeScript 4.3 to be sent back from the client during `getCompletionEntryDetails`. Since this feature
|
||||
// is not backward compatible with older clients, the language service defaults to disabling it, allowing newer clients
|
||||
// to opt in with the `includeCompletionsForImportStatements` user preference.
|
||||
importCompletionNode = importStatementCompletion.replacementNode;
|
||||
}
|
||||
// Bail out if this is a known invalid completion location
|
||||
if (!importCompletionNode && isCompletionListBlocker(contextToken)) {
|
||||
log("Returning an empty list because completion was requested in an invalid position.");
|
||||
return undefined;
|
||||
return keywordFilters
|
||||
? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierDefinitionLocation())
|
||||
: undefined;
|
||||
}
|
||||
|
||||
let parent = contextToken.parent;
|
||||
@@ -1495,10 +1535,8 @@ namespace ts.Completions {
|
||||
|
||||
const semanticStart = timestamp();
|
||||
let completionKind = CompletionKind.None;
|
||||
let isNewIdentifierLocation = false;
|
||||
let isNonContextualObjectLiteral = false;
|
||||
let hasUnresolvedAutoImports = false;
|
||||
let keywordFilters = KeywordCompletionFilters.None;
|
||||
// This also gets mutated in nested-functions after the return
|
||||
let symbols: Symbol[] = [];
|
||||
const symbolToOriginInfoMap: SymbolOriginInfoMap = [];
|
||||
@@ -1533,7 +1571,9 @@ namespace ts.Completions {
|
||||
// global symbols in scope. These results should be valid for either language as
|
||||
// the set of symbols that can be referenced from this location.
|
||||
if (!tryGetGlobalSymbols()) {
|
||||
return undefined;
|
||||
return keywordFilters
|
||||
? keywordCompletionData(keywordFilters, isJsOnlyLocation, isNewIdentifierLocation)
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1558,6 +1598,7 @@ namespace ts.Completions {
|
||||
symbolToOriginInfoMap,
|
||||
recommendedCompletion,
|
||||
previousToken,
|
||||
contextToken,
|
||||
isJsxInitializer,
|
||||
insideJsDocTagTypeExpression,
|
||||
symbolToSortTextIdMap,
|
||||
@@ -1971,10 +2012,18 @@ namespace ts.Completions {
|
||||
return;
|
||||
}
|
||||
|
||||
const moduleSpecifierCache = host.getModuleSpecifierCache?.();
|
||||
const lowerCaseTokenText = previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() : "";
|
||||
const exportInfo = getExportInfoMap(sourceFile, host, program, cancellationToken);
|
||||
// import { type | -> token text should be blank
|
||||
const isAfterTypeOnlyImportSpecifierModifier = previousToken === contextToken
|
||||
&& importCompletionNode
|
||||
&& couldBeTypeOnlyImportSpecifier(importCompletionNode, contextToken);
|
||||
|
||||
const lowerCaseTokenText =
|
||||
isAfterTypeOnlyImportSpecifierModifier ? "" :
|
||||
previousToken && isIdentifier(previousToken) ? previousToken.text.toLowerCase() :
|
||||
"";
|
||||
|
||||
const moduleSpecifierCache = host.getModuleSpecifierCache?.();
|
||||
const exportInfo = getExportInfoMap(sourceFile, host, program, cancellationToken);
|
||||
const packageJsonAutoImportProvider = host.getPackageJsonAutoImportProvider?.();
|
||||
const packageJsonFilter = detailsEntryId ? undefined : createPackageJsonImportFilter(sourceFile, preferences, host);
|
||||
resolvingModuleSpecifiers(
|
||||
@@ -2130,6 +2179,7 @@ namespace ts.Completions {
|
||||
|
||||
case SyntaxKind.ModuleKeyword: // module |
|
||||
case SyntaxKind.NamespaceKeyword: // namespace |
|
||||
case SyntaxKind.ImportKeyword: // import |
|
||||
return true;
|
||||
|
||||
case SyntaxKind.DotToken:
|
||||
@@ -2292,22 +2342,42 @@ namespace ts.Completions {
|
||||
* Relevant symbols are stored in the captured 'symbols' variable.
|
||||
*/
|
||||
function tryGetImportOrExportClauseCompletionSymbols(): GlobalsSearch {
|
||||
// `import { |` or `import { a as 0, | }`
|
||||
const namedImportsOrExports = contextToken && (contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken)
|
||||
? tryCast(contextToken.parent, isNamedImportsOrExports) : undefined;
|
||||
if (!contextToken) return GlobalsSearch.Continue;
|
||||
|
||||
// `import { |` or `import { a as 0, | }` or `import { type | }`
|
||||
const namedImportsOrExports =
|
||||
contextToken.kind === SyntaxKind.OpenBraceToken || contextToken.kind === SyntaxKind.CommaToken ? tryCast(contextToken.parent, isNamedImportsOrExports) :
|
||||
isTypeKeywordTokenOrIdentifier(contextToken) ? tryCast(contextToken.parent.parent, isNamedImportsOrExports) : undefined;
|
||||
|
||||
if (!namedImportsOrExports) return GlobalsSearch.Continue;
|
||||
|
||||
// We can at least offer `type` at `import { |`
|
||||
if (!isTypeKeywordTokenOrIdentifier(contextToken)) {
|
||||
keywordFilters = KeywordCompletionFilters.TypeKeyword;
|
||||
}
|
||||
|
||||
// try to show exported member for imported/re-exported module
|
||||
const { moduleSpecifier } = namedImportsOrExports.kind === SyntaxKind.NamedImports ? namedImportsOrExports.parent.parent : namedImportsOrExports.parent;
|
||||
if (!moduleSpecifier) return namedImportsOrExports.kind === SyntaxKind.NamedImports ? GlobalsSearch.Fail : GlobalsSearch.Continue;
|
||||
if (!moduleSpecifier) {
|
||||
isNewIdentifierLocation = true;
|
||||
return namedImportsOrExports.kind === SyntaxKind.NamedImports ? GlobalsSearch.Fail : GlobalsSearch.Continue;
|
||||
}
|
||||
const moduleSpecifierSymbol = typeChecker.getSymbolAtLocation(moduleSpecifier); // TODO: GH#18217
|
||||
if (!moduleSpecifierSymbol) return GlobalsSearch.Fail;
|
||||
if (!moduleSpecifierSymbol) {
|
||||
isNewIdentifierLocation = true;
|
||||
return GlobalsSearch.Fail;
|
||||
}
|
||||
|
||||
completionKind = CompletionKind.MemberLike;
|
||||
isNewIdentifierLocation = false;
|
||||
const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol);
|
||||
const existing = new Set((namedImportsOrExports.elements as NodeArray<ImportOrExportSpecifier>).filter(n => !isCurrentlyEditingNode(n)).map(n => (n.propertyName || n.name).escapedText));
|
||||
symbols = concatenate(symbols, exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName)));
|
||||
const uniques = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName));
|
||||
symbols = concatenate(symbols, uniques);
|
||||
if (!uniques.length) {
|
||||
// If there's nothing else to import, don't offer `type` either
|
||||
keywordFilters = KeywordCompletionFilters.None;
|
||||
}
|
||||
return GlobalsSearch.Success;
|
||||
}
|
||||
|
||||
@@ -2597,6 +2667,16 @@ namespace ts.Completions {
|
||||
case SyntaxKind.SetKeyword:
|
||||
return !isFromObjectTypeDeclaration(contextToken);
|
||||
|
||||
case SyntaxKind.Identifier:
|
||||
if (containingNodeKind === SyntaxKind.ImportSpecifier &&
|
||||
contextToken === (parent as ImportSpecifier).name &&
|
||||
(contextToken as Identifier).text === "type"
|
||||
) {
|
||||
// import { type | }
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case SyntaxKind.ClassKeyword:
|
||||
case SyntaxKind.EnumKeyword:
|
||||
case SyntaxKind.InterfaceKeyword:
|
||||
@@ -2606,9 +2686,12 @@ namespace ts.Completions {
|
||||
case SyntaxKind.LetKeyword:
|
||||
case SyntaxKind.ConstKeyword:
|
||||
case SyntaxKind.InferKeyword:
|
||||
case SyntaxKind.TypeKeyword: // type htm|
|
||||
return true;
|
||||
|
||||
case SyntaxKind.TypeKeyword:
|
||||
// import { type foo| }
|
||||
return containingNodeKind !== SyntaxKind.ImportSpecifier;
|
||||
|
||||
case SyntaxKind.AsteriskToken:
|
||||
return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent);
|
||||
}
|
||||
@@ -3014,6 +3097,8 @@ namespace ts.Completions {
|
||||
return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword;
|
||||
case KeywordCompletionFilters.TypeKeywords:
|
||||
return isTypeKeyword(kind);
|
||||
case KeywordCompletionFilters.TypeKeyword:
|
||||
return kind === SyntaxKind.TypeKeyword;
|
||||
default:
|
||||
return Debug.assertNever(keywordFilter);
|
||||
}
|
||||
@@ -3311,36 +3396,81 @@ namespace ts.Completions {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getImportCompletionNode(contextToken: Node) {
|
||||
interface ImportStatementCompletionInfo {
|
||||
isKeywordOnlyCompletion: boolean;
|
||||
keywordCompletion: TokenSyntaxKind | undefined;
|
||||
isNewIdentifierLocation: boolean;
|
||||
replacementNode: ImportEqualsDeclaration | ImportDeclaration | ImportSpecifier | Token<SyntaxKind.ImportKeyword> | undefined;
|
||||
}
|
||||
|
||||
function getImportStatementCompletionInfo(contextToken: Node): ImportStatementCompletionInfo {
|
||||
let keywordCompletion: TokenSyntaxKind | undefined;
|
||||
let isKeywordOnlyCompletion = false;
|
||||
const candidate = getCandidate();
|
||||
return candidate === SyntaxKind.FromKeyword || candidate && rangeIsOnSingleLine(candidate, candidate.getSourceFile()) ? candidate : undefined;
|
||||
return {
|
||||
isKeywordOnlyCompletion,
|
||||
keywordCompletion,
|
||||
isNewIdentifierLocation: !!(candidate || keywordCompletion === SyntaxKind.TypeKeyword),
|
||||
replacementNode: candidate && rangeIsOnSingleLine(candidate, candidate.getSourceFile())
|
||||
? candidate
|
||||
: undefined
|
||||
};
|
||||
|
||||
function getCandidate() {
|
||||
const parent = contextToken.parent;
|
||||
if (isImportEqualsDeclaration(parent)) {
|
||||
keywordCompletion = contextToken.kind === SyntaxKind.TypeKeyword ? undefined : SyntaxKind.TypeKeyword;
|
||||
return isModuleSpecifierMissingOrEmpty(parent.moduleReference) ? parent : undefined;
|
||||
}
|
||||
if (couldBeTypeOnlyImportSpecifier(parent, contextToken) && canCompleteFromNamedBindings(parent.parent)) {
|
||||
return parent;
|
||||
}
|
||||
if (isNamedImports(parent) || isNamespaceImport(parent)) {
|
||||
if (isModuleSpecifierMissingOrEmpty(parent.parent.parent.moduleSpecifier) && (isNamespaceImport(parent) || parent.elements.length < 2) && !parent.parent.name) {
|
||||
if (!parent.parent.isTypeOnly && (
|
||||
contextToken.kind === SyntaxKind.OpenBraceToken ||
|
||||
contextToken.kind === SyntaxKind.ImportKeyword ||
|
||||
contextToken.kind === SyntaxKind.CommaToken
|
||||
)) {
|
||||
keywordCompletion = SyntaxKind.TypeKeyword;
|
||||
}
|
||||
|
||||
if (canCompleteFromNamedBindings(parent)) {
|
||||
// At `import { ... } |` or `import * as Foo |`, the only possible completion is `from`
|
||||
return contextToken.kind === SyntaxKind.CloseBraceToken || contextToken.kind === SyntaxKind.Identifier
|
||||
? SyntaxKind.FromKeyword
|
||||
: parent.parent.parent;
|
||||
if (contextToken.kind === SyntaxKind.CloseBraceToken || contextToken.kind === SyntaxKind.Identifier) {
|
||||
isKeywordOnlyCompletion = true;
|
||||
keywordCompletion = SyntaxKind.FromKeyword;
|
||||
}
|
||||
else {
|
||||
return parent.parent.parent;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
if (isImportKeyword(contextToken) && isSourceFile(parent)) {
|
||||
// A lone import keyword with nothing following it does not parse as a statement at all
|
||||
keywordCompletion = SyntaxKind.TypeKeyword;
|
||||
return contextToken as Token<SyntaxKind.ImportKeyword>;
|
||||
}
|
||||
if (isImportKeyword(contextToken) && isImportDeclaration(parent)) {
|
||||
// `import s| from`
|
||||
keywordCompletion = SyntaxKind.TypeKeyword;
|
||||
return isModuleSpecifierMissingOrEmpty(parent.moduleSpecifier) ? parent : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function couldBeTypeOnlyImportSpecifier(importSpecifier: Node, contextToken: Node | undefined): importSpecifier is ImportSpecifier {
|
||||
return isImportSpecifier(importSpecifier)
|
||||
&& (importSpecifier.isTypeOnly || contextToken === importSpecifier.name && isTypeKeywordTokenOrIdentifier(contextToken));
|
||||
}
|
||||
|
||||
function canCompleteFromNamedBindings(namedBindings: NamedImportBindings) {
|
||||
return isModuleSpecifierMissingOrEmpty(namedBindings.parent.parent.moduleSpecifier)
|
||||
&& (isNamespaceImport(namedBindings) || namedBindings.elements.length < 2)
|
||||
&& !namedBindings.parent.name;
|
||||
}
|
||||
|
||||
function isModuleSpecifierMissingOrEmpty(specifier: ModuleReference | Expression) {
|
||||
if (nodeIsMissing(specifier)) return true;
|
||||
return !tryCast(isExternalModuleReference(specifier) ? specifier.expression : specifier, isStringLiteralLike)?.text;
|
||||
@@ -3426,3 +3556,4 @@ namespace ts.Completions {
|
||||
return charCode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -232,7 +232,7 @@ namespace ts.OrganizeImports {
|
||||
else {
|
||||
for (const defaultImport of defaultImports) {
|
||||
newImportSpecifiers.push(
|
||||
factory.createImportSpecifier(factory.createIdentifier("default"), defaultImport.importClause!.name!)); // TODO: GH#18217
|
||||
factory.createImportSpecifier(/*isTypeOnly*/ false, factory.createIdentifier("default"), defaultImport.importClause!.name!)); // TODO: GH#18217
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,7 +416,8 @@ namespace ts.OrganizeImports {
|
||||
}
|
||||
|
||||
export function compareImportOrExportSpecifiers<T extends ImportOrExportSpecifier>(s1: T, s2: T) {
|
||||
return compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name)
|
||||
return compareBooleans(s1.isTypeOnly, s2.isTypeOnly)
|
||||
|| compareIdentifiers(s1.propertyName || s1.name, s2.propertyName || s2.name)
|
||||
|| compareIdentifiers(s1.name, s2.name);
|
||||
}
|
||||
|
||||
|
||||
@@ -252,10 +252,10 @@ namespace ts.refactor {
|
||||
}
|
||||
|
||||
function makeImportSpecifier(propertyName: string, name: string): ImportSpecifier {
|
||||
return factory.createImportSpecifier(propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name));
|
||||
return factory.createImportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name));
|
||||
}
|
||||
|
||||
function makeExportSpecifier(propertyName: string, name: string): ExportSpecifier {
|
||||
return factory.createExportSpecifier(propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name));
|
||||
return factory.createExportSpecifier(/*isTypeOnly*/ false, propertyName === name ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace ts.refactor {
|
||||
|
||||
const importSpecifiers: ImportSpecifier[] = [];
|
||||
exportNameToImportName.forEach((name, propertyName) => {
|
||||
importSpecifiers.push(factory.createImportSpecifier(name === propertyName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)));
|
||||
importSpecifiers.push(factory.createImportSpecifier(/*isTypeOnly*/ false, name === propertyName ? undefined : factory.createIdentifier(propertyName), factory.createIdentifier(name)));
|
||||
});
|
||||
|
||||
const importDecl = toConvert.parent.parent;
|
||||
@@ -191,7 +191,7 @@ namespace ts.refactor {
|
||||
changes.replaceNode(sourceFile, toConvert, factory.createNamespaceImport(factory.createIdentifier(namespaceImportName)));
|
||||
if (neededNamedImports.size) {
|
||||
const newNamedImports: ImportSpecifier[] = arrayFrom(neededNamedImports.values()).map(element =>
|
||||
factory.createImportSpecifier(element.propertyName && factory.createIdentifier(element.propertyName.text), factory.createIdentifier(element.name.text)));
|
||||
factory.createImportSpecifier(element.isTypeOnly, element.propertyName && factory.createIdentifier(element.propertyName.text), factory.createIdentifier(element.name.text)));
|
||||
changes.insertNodeAfter(sourceFile, toConvert.parent.parent, updateImport(importDecl, /*defaultImportName*/ undefined, newNamedImports));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ namespace ts.refactor {
|
||||
function makeImportOrRequire(defaultImport: Identifier | undefined, imports: readonly string[], path: string, useEs6Imports: boolean, quotePreference: QuotePreference): AnyImportOrRequireStatement | undefined {
|
||||
path = ensurePathIsNonModuleName(path);
|
||||
if (useEs6Imports) {
|
||||
const specifiers = imports.map(i => factory.createImportSpecifier(/*propertyName*/ undefined, factory.createIdentifier(i)));
|
||||
const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i)));
|
||||
return makeImportIfNecessary(defaultImport, specifiers, path, quotePreference);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace ts.Completions.StringCompletions {
|
||||
completion.symbols,
|
||||
entries,
|
||||
contextToken,
|
||||
contextToken,
|
||||
sourceFile,
|
||||
sourceFile,
|
||||
checker,
|
||||
|
||||
@@ -1795,6 +1795,10 @@ namespace ts {
|
||||
return node.kind === SyntaxKind.TypeKeyword;
|
||||
}
|
||||
|
||||
export function isTypeKeywordTokenOrIdentifier(node: Node) {
|
||||
return isTypeKeywordToken(node) || isIdentifier(node) && node.text === "type";
|
||||
}
|
||||
|
||||
/** True if the symbol is for an external module, as opposed to a namespace. */
|
||||
export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean {
|
||||
return !!(moduleSymbol.flags & SymbolFlags.Module) && moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote;
|
||||
|
||||
Reference in New Issue
Block a user