mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-04 21:53:42 -06:00
Expand auto-import API to work on non-existent files and symbols (#58093)
This commit is contained in:
parent
7a4cbfa7ea
commit
5c55ce1ba2
@ -32,6 +32,7 @@ import {
|
||||
flatten,
|
||||
forEach,
|
||||
forEachAncestorDirectory,
|
||||
FutureSourceFile,
|
||||
getBaseFileName,
|
||||
GetCanonicalFileName,
|
||||
getConditions,
|
||||
@ -65,6 +66,7 @@ import {
|
||||
isDeclarationFileName,
|
||||
isExternalModuleAugmentation,
|
||||
isExternalModuleNameRelative,
|
||||
isFullSourceFile,
|
||||
isMissingPackageJsonInfo,
|
||||
isModuleBlock,
|
||||
isModuleDeclaration,
|
||||
@ -141,7 +143,7 @@ export interface ModuleSpecifierPreferences {
|
||||
export function getModuleSpecifierPreferences(
|
||||
{ importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences,
|
||||
compilerOptions: CompilerOptions,
|
||||
importingSourceFile: SourceFile,
|
||||
importingSourceFile: Pick<SourceFile, "fileName" | "impliedNodeFormat">,
|
||||
oldImportSpecifier?: string,
|
||||
): ModuleSpecifierPreferences {
|
||||
const filePreferredEnding = getPreferredEnding();
|
||||
@ -197,7 +199,7 @@ export function getModuleSpecifierPreferences(
|
||||
importModuleSpecifierEnding,
|
||||
resolutionMode ?? importingSourceFile.impliedNodeFormat,
|
||||
compilerOptions,
|
||||
importingSourceFile,
|
||||
isFullSourceFile(importingSourceFile) ? importingSourceFile : undefined,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -230,7 +232,7 @@ export function updateModuleSpecifier(
|
||||
/** @internal */
|
||||
export function getModuleSpecifier(
|
||||
compilerOptions: CompilerOptions,
|
||||
importingSourceFile: SourceFile,
|
||||
importingSourceFile: SourceFile | FutureSourceFile,
|
||||
importingSourceFileName: string,
|
||||
toFileName: string,
|
||||
host: ModuleSpecifierResolutionHost,
|
||||
@ -242,7 +244,7 @@ export function getModuleSpecifier(
|
||||
/** @internal */
|
||||
export function getNodeModulesPackageName(
|
||||
compilerOptions: CompilerOptions,
|
||||
importingSourceFile: SourceFile,
|
||||
importingSourceFile: SourceFile | FutureSourceFile,
|
||||
nodeModulesFileName: string,
|
||||
host: ModuleSpecifierResolutionHost,
|
||||
preferences: UserPreferences,
|
||||
@ -255,7 +257,7 @@ export function getNodeModulesPackageName(
|
||||
|
||||
function getModuleSpecifierWorker(
|
||||
compilerOptions: CompilerOptions,
|
||||
importingSourceFile: SourceFile,
|
||||
importingSourceFile: SourceFile | FutureSourceFile,
|
||||
importingSourceFileName: string,
|
||||
toFileName: string,
|
||||
host: ModuleSpecifierResolutionHost,
|
||||
@ -272,7 +274,7 @@ function getModuleSpecifierWorker(
|
||||
/** @internal */
|
||||
export function tryGetModuleSpecifiersFromCache(
|
||||
moduleSymbol: Symbol,
|
||||
importingSourceFile: SourceFile,
|
||||
importingSourceFile: SourceFile | FutureSourceFile,
|
||||
host: ModuleSpecifierResolutionHost,
|
||||
userPreferences: UserPreferences,
|
||||
options: ModuleSpecifierOptions = {},
|
||||
@ -288,7 +290,7 @@ export function tryGetModuleSpecifiersFromCache(
|
||||
|
||||
function tryGetModuleSpecifiersFromCacheWorker(
|
||||
moduleSymbol: Symbol,
|
||||
importingSourceFile: SourceFile,
|
||||
importingSourceFile: SourceFile | FutureSourceFile,
|
||||
host: ModuleSpecifierResolutionHost,
|
||||
userPreferences: UserPreferences,
|
||||
options: ModuleSpecifierOptions = {},
|
||||
@ -334,7 +336,7 @@ export function getModuleSpecifiersWithCacheInfo(
|
||||
moduleSymbol: Symbol,
|
||||
checker: TypeChecker,
|
||||
compilerOptions: CompilerOptions,
|
||||
importingSourceFile: SourceFile,
|
||||
importingSourceFile: SourceFile | FutureSourceFile,
|
||||
host: ModuleSpecifierResolutionHost,
|
||||
userPreferences: UserPreferences,
|
||||
options: ModuleSpecifierOptions = {},
|
||||
@ -370,10 +372,30 @@ export function getModuleSpecifiersWithCacheInfo(
|
||||
return { moduleSpecifiers: result, computedWithoutCache };
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getLocalModuleSpecifierBetweenFileNames(
|
||||
importingFile: Pick<SourceFile, "fileName" | "impliedNodeFormat">,
|
||||
targetFileName: string,
|
||||
compilerOptions: CompilerOptions,
|
||||
host: ModuleSpecifierResolutionHost,
|
||||
options: ModuleSpecifierOptions = {},
|
||||
): string {
|
||||
const info = getInfo(importingFile.fileName, host);
|
||||
const importMode = options.overrideImportMode ?? importingFile.impliedNodeFormat;
|
||||
return getLocalModuleSpecifier(
|
||||
targetFileName,
|
||||
info,
|
||||
compilerOptions,
|
||||
host,
|
||||
importMode,
|
||||
getModuleSpecifierPreferences({}, compilerOptions, importingFile),
|
||||
);
|
||||
}
|
||||
|
||||
function computeModuleSpecifiers(
|
||||
modulePaths: readonly ModulePath[],
|
||||
compilerOptions: CompilerOptions,
|
||||
importingSourceFile: SourceFile,
|
||||
importingSourceFile: SourceFile | FutureSourceFile,
|
||||
host: ModuleSpecifierResolutionHost,
|
||||
userPreferences: UserPreferences,
|
||||
options: ModuleSpecifierOptions = {},
|
||||
@ -381,7 +403,7 @@ function computeModuleSpecifiers(
|
||||
): readonly string[] {
|
||||
const info = getInfo(importingSourceFile.fileName, host);
|
||||
const preferences = getModuleSpecifierPreferences(userPreferences, compilerOptions, importingSourceFile);
|
||||
const existingSpecifier = forEach(modulePaths, modulePath =>
|
||||
const existingSpecifier = isFullSourceFile(importingSourceFile) && forEach(modulePaths, modulePath =>
|
||||
forEach(
|
||||
host.getFileIncludeReasons().get(toPath(modulePath.path, host.getCurrentDirectory(), info.getCanonicalFileName)),
|
||||
reason => {
|
||||
@ -1014,7 +1036,7 @@ function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileNam
|
||||
return processEnding(shortest, allowedEndings, compilerOptions);
|
||||
}
|
||||
|
||||
function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, canonicalSourceDirectory }: Info, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ResolutionMode): string | undefined {
|
||||
function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, canonicalSourceDirectory }: Info, importingSourceFile: SourceFile | FutureSourceFile, host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ResolutionMode): string | undefined {
|
||||
if (!host.fileExists || !host.readFile) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -4217,6 +4217,18 @@ export interface SourceFileLike {
|
||||
getPositionOfLineAndCharacter?(line: number, character: number, allowEdits?: true): number;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface FutureSourceFile {
|
||||
readonly path: Path;
|
||||
readonly fileName: string;
|
||||
readonly impliedNodeFormat?: ResolutionMode;
|
||||
readonly packageJsonScope?: PackageJsonInfo;
|
||||
readonly externalModuleIndicator?: true | undefined;
|
||||
readonly commonJsModuleIndicator?: true | undefined;
|
||||
readonly statements: readonly never[];
|
||||
readonly imports: readonly never[];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface RedirectInfo {
|
||||
/** Source file this redirects to. */
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
AmpersandAmpersandEqualsToken,
|
||||
AnyImportOrBareOrAccessedRequire,
|
||||
AnyImportOrReExport,
|
||||
AnyImportOrRequireStatement,
|
||||
AnyImportSyntax,
|
||||
AnyValidImportOrReExport,
|
||||
append,
|
||||
@ -1985,6 +1986,11 @@ export function isAnyImportOrBareOrAccessedRequire(node: Node): node is AnyImpor
|
||||
return isAnyImportSyntax(node) || isVariableDeclarationInitializedToBareOrAccessedRequire(node);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isAnyImportOrRequireStatement(node: Node): node is AnyImportOrRequireStatement {
|
||||
return isAnyImportSyntax(node) || isRequireVariableStatement(node);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isLateVisibilityPaintedStatement(node: Node): node is LateVisibilityPaintedStatement {
|
||||
switch (node.kind) {
|
||||
@ -3461,6 +3467,11 @@ export function isInternalModuleImportEqualsDeclaration(node: Node): node is Imp
|
||||
return node.kind === SyntaxKind.ImportEqualsDeclaration && (node as ImportEqualsDeclaration).moduleReference.kind !== SyntaxKind.ExternalModuleReference;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isFullSourceFile(sourceFile: object): sourceFile is SourceFile {
|
||||
return (sourceFile as Partial<SourceFile>)?.kind === SyntaxKind.SourceFile;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isSourceFileJS(file: SourceFile): boolean {
|
||||
return isInJSFile(file);
|
||||
@ -9576,7 +9587,7 @@ export function usesExtensionsOnImports({ imports }: SourceFile, hasExtension: (
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getModuleSpecifierEndingPreference(preference: UserPreferences["importModuleSpecifierEnding"], resolutionMode: ResolutionMode, compilerOptions: CompilerOptions, sourceFile: SourceFile): ModuleSpecifierEnding {
|
||||
export function getModuleSpecifierEndingPreference(preference: UserPreferences["importModuleSpecifierEnding"], resolutionMode: ResolutionMode, compilerOptions: CompilerOptions, sourceFile?: SourceFile): ModuleSpecifierEnding {
|
||||
const moduleResolution = getEmitModuleResolutionKind(compilerOptions);
|
||||
const moduleResolutionIsNodeNext = ModuleResolutionKind.Node16 <= moduleResolution && moduleResolution <= ModuleResolutionKind.NodeNext;
|
||||
if (preference === "js" || resolutionMode === ModuleKind.ESNext && moduleResolutionIsNodeNext) {
|
||||
@ -9603,22 +9614,22 @@ export function getModuleSpecifierEndingPreference(preference: UserPreferences["
|
||||
// accurately, and more importantly, literally nobody wants `Index` and its existence is a mystery.
|
||||
if (!shouldAllowImportingTsExtension(compilerOptions)) {
|
||||
// If .ts imports are not valid, we only need to see one .js import to go with that.
|
||||
return usesExtensionsOnImports(sourceFile) ? ModuleSpecifierEnding.JsExtension : ModuleSpecifierEnding.Minimal;
|
||||
return sourceFile && usesExtensionsOnImports(sourceFile) ? ModuleSpecifierEnding.JsExtension : ModuleSpecifierEnding.Minimal;
|
||||
}
|
||||
|
||||
return inferPreference();
|
||||
|
||||
function inferPreference() {
|
||||
let usesJsExtensions = false;
|
||||
const specifiers = sourceFile.imports.length ? sourceFile.imports :
|
||||
isSourceFileJS(sourceFile) ? getRequiresAtTopOfFile(sourceFile).map(r => r.arguments[0]) :
|
||||
const specifiers = sourceFile?.imports.length ? sourceFile.imports :
|
||||
sourceFile && isSourceFileJS(sourceFile) ? getRequiresAtTopOfFile(sourceFile).map(r => r.arguments[0]) :
|
||||
emptyArray;
|
||||
for (const specifier of specifiers) {
|
||||
if (pathIsRelative(specifier.text)) {
|
||||
if (
|
||||
moduleResolutionIsNodeNext &&
|
||||
resolutionMode === ModuleKind.CommonJS &&
|
||||
getModeForUsageLocation(sourceFile, specifier, compilerOptions) === ModuleKind.ESNext
|
||||
getModeForUsageLocation(sourceFile!, specifier, compilerOptions) === ModuleKind.ESNext
|
||||
) {
|
||||
// We're trying to decide a preference for a CommonJS module specifier, but looking at an ESM import.
|
||||
continue;
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
AnyImportOrRequireStatement,
|
||||
AnyImportSyntax,
|
||||
arrayFrom,
|
||||
BindingElement,
|
||||
CancellationToken,
|
||||
cast,
|
||||
changeAnyExtension,
|
||||
@ -15,6 +16,7 @@ import {
|
||||
compareValues,
|
||||
Comparison,
|
||||
CompilerOptions,
|
||||
createFutureSourceFile,
|
||||
createModuleSpecifierResolutionHost,
|
||||
createMultiMap,
|
||||
createPackageJsonImportFilter,
|
||||
@ -27,12 +29,15 @@ import {
|
||||
ExportKind,
|
||||
ExportMapInfoKey,
|
||||
factory,
|
||||
findAncestor,
|
||||
first,
|
||||
firstDefined,
|
||||
flatMap,
|
||||
flatMapIterator,
|
||||
forEachExternalModuleToImportFrom,
|
||||
formatting,
|
||||
FutureSourceFile,
|
||||
FutureSymbolExportInfo,
|
||||
getAllowSyntheticDefaultImports,
|
||||
getBaseFileName,
|
||||
getDefaultExportInfoWorker,
|
||||
@ -45,32 +50,34 @@ import {
|
||||
getMeaningFromDeclaration,
|
||||
getMeaningFromLocation,
|
||||
getNameForExportedSymbol,
|
||||
getNodeId,
|
||||
getOutputExtension,
|
||||
getQuoteFromPreference,
|
||||
getQuotePreference,
|
||||
getSourceFileOfNode,
|
||||
getSymbolId,
|
||||
getSynthesizedDeepClone,
|
||||
getTokenAtPosition,
|
||||
getTokenPosOfNode,
|
||||
getTypeKeywordOfTypeOnlyImport,
|
||||
getUniqueSymbolId,
|
||||
hasJSFileExtension,
|
||||
hostGetCanonicalFileName,
|
||||
Identifier,
|
||||
ImportClause,
|
||||
ImportEqualsDeclaration,
|
||||
importFromModuleSpecifier,
|
||||
ImportKind,
|
||||
ImportSpecifier,
|
||||
insertImports,
|
||||
InternalSymbolName,
|
||||
isExternalModule,
|
||||
isExternalModuleReference,
|
||||
isFullSourceFile,
|
||||
isIdentifier,
|
||||
isIdentifierPart,
|
||||
isIdentifierStart,
|
||||
isImportableFile,
|
||||
isImportDeclaration,
|
||||
isImportEqualsDeclaration,
|
||||
isInJSFile,
|
||||
isIntrinsicJsxName,
|
||||
isJSDocImportTag,
|
||||
isJsxClosingElement,
|
||||
@ -79,6 +86,7 @@ import {
|
||||
isJSXTagName,
|
||||
isNamedImports,
|
||||
isNamespaceImport,
|
||||
isRequireVariableStatement,
|
||||
isSourceFileJS,
|
||||
isStringANonContextualKeyword,
|
||||
isStringLiteral,
|
||||
@ -101,6 +109,7 @@ import {
|
||||
MultiMap,
|
||||
Mutable,
|
||||
NamedImports,
|
||||
NamespaceImport,
|
||||
Node,
|
||||
NodeFlags,
|
||||
nodeIsMissing,
|
||||
@ -114,6 +123,7 @@ import {
|
||||
QuotePreference,
|
||||
removeFileExtension,
|
||||
removeSuffix,
|
||||
RequireOrImportCall,
|
||||
RequireVariableStatement,
|
||||
sameMap,
|
||||
ScriptTarget,
|
||||
@ -140,6 +150,7 @@ import {
|
||||
TypeChecker,
|
||||
TypeOnlyAliasDeclaration,
|
||||
UserPreferences,
|
||||
VariableDeclarationInitializedTo,
|
||||
} from "../_namespaces/ts";
|
||||
import {
|
||||
createCodeFixAction,
|
||||
@ -200,6 +211,14 @@ registerCodeFix({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* The node kinds that may be the declaration of an alias symbol imported/required from an external module.
|
||||
* `ImportClause` is the declaration for a syntactic default import. `VariableDeclaration` is the declaration
|
||||
* for a non-destructured `require` call.
|
||||
* @internal
|
||||
*/
|
||||
export type ImportOrRequireAliasDeclaration = ImportEqualsDeclaration | ImportClause | ImportSpecifier | NamespaceImport | VariableDeclarationInitializedTo<RequireOrImportCall> | BindingElement;
|
||||
|
||||
/**
|
||||
* Computes multiple import additions to a file and writes them to a ChangeTracker.
|
||||
*
|
||||
@ -208,12 +227,16 @@ registerCodeFix({
|
||||
export interface ImportAdder {
|
||||
hasFixes(): boolean;
|
||||
addImportFromDiagnostic: (diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) => void;
|
||||
addImportFromExportedSymbol: (exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean) => void;
|
||||
addImportFromExportedSymbol: (exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean, referenceImport?: ImportOrRequireAliasDeclaration) => void;
|
||||
addImportForNonExistentExport: (exportName: string, exportingFileName: string, exportKind: ExportKind, exportedMeanings: SymbolFlags, isImportUsageValidAsTypeOnly: boolean) => void;
|
||||
addImportForUnresolvedIdentifier: (context: CodeFixContextBase, symbolToken: Identifier, useAutoImportProvider: boolean) => void;
|
||||
addVerbatimImport: (declaration: AnyImportOrRequireStatement | ImportOrRequireAliasDeclaration) => void;
|
||||
removeExistingImport: (declaration: ImportOrRequireAliasDeclaration) => void;
|
||||
writeFixes: (changeTracker: textChanges.ChangeTracker, oldFileQuotePreference?: QuotePreference) => void;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function createImportAdder(sourceFile: SourceFile, program: Program, preferences: UserPreferences, host: LanguageServiceHost, cancellationToken?: CancellationToken): ImportAdder {
|
||||
export function createImportAdder(sourceFile: SourceFile | FutureSourceFile, program: Program, preferences: UserPreferences, host: LanguageServiceHost, cancellationToken?: CancellationToken): ImportAdder {
|
||||
return createImportAdderWorker(sourceFile, program, /*useAutoImportProvider*/ false, preferences, host, cancellationToken);
|
||||
}
|
||||
|
||||
@ -223,18 +246,29 @@ interface AddToExistingState {
|
||||
readonly namedImports: Map<string, AddAsTypeOnly>;
|
||||
}
|
||||
|
||||
function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAutoImportProvider: boolean, preferences: UserPreferences, host: LanguageServiceHost, cancellationToken: CancellationToken | undefined): ImportAdder {
|
||||
function createImportAdderWorker(sourceFile: SourceFile | FutureSourceFile, program: Program, useAutoImportProvider: boolean, preferences: UserPreferences, host: LanguageServiceHost, cancellationToken: CancellationToken | undefined): ImportAdder {
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
// Namespace fixes don't conflict, so just build a list.
|
||||
const addToNamespace: FixUseNamespaceImport[] = [];
|
||||
const importType: FixAddJsdocTypeImport[] = [];
|
||||
/** Keys are import clause node IDs. */
|
||||
const addToExisting = new Map<string, AddToExistingState>();
|
||||
const addToExisting = new Map<ImportClause | ObjectBindingPattern, AddToExistingState>();
|
||||
const removeExisting = new Set<ImportOrRequireAliasDeclaration>();
|
||||
const verbatimImports = new Set<AnyImportOrRequireStatement | ImportOrRequireAliasDeclaration>();
|
||||
|
||||
type NewImportsKey = `${0 | 1}|${string}`;
|
||||
/** Use `getNewImportEntry` for access */
|
||||
const newImports = new Map<NewImportsKey, Mutable<ImportsCollection & { useRequire: boolean; }>>();
|
||||
return { addImportFromDiagnostic, addImportFromExportedSymbol, writeFixes, hasFixes };
|
||||
return { addImportFromDiagnostic, addImportFromExportedSymbol, writeFixes, hasFixes, addImportForUnresolvedIdentifier, addImportForNonExistentExport, removeExistingImport, addVerbatimImport };
|
||||
|
||||
function addVerbatimImport(declaration: AnyImportOrRequireStatement | ImportOrRequireAliasDeclaration) {
|
||||
verbatimImports.add(declaration);
|
||||
}
|
||||
|
||||
function addImportForUnresolvedIdentifier(context: CodeFixContextBase, symbolToken: Identifier, useAutoImportProvider: boolean) {
|
||||
const info = getFixInfosWithoutDiagnostic(context, symbolToken, useAutoImportProvider);
|
||||
if (!info || !info.length) return;
|
||||
addImport(first(info));
|
||||
}
|
||||
|
||||
function addImportFromDiagnostic(diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) {
|
||||
const info = getFixInfos(context, diagnostic.code, diagnostic.start, useAutoImportProvider);
|
||||
@ -242,19 +276,89 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
|
||||
addImport(first(info));
|
||||
}
|
||||
|
||||
function addImportFromExportedSymbol(exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean) {
|
||||
function addImportFromExportedSymbol(exportedSymbol: Symbol, isValidTypeOnlyUseSite?: boolean, referenceImport?: ImportOrRequireAliasDeclaration) {
|
||||
const moduleSymbol = Debug.checkDefined(exportedSymbol.parent);
|
||||
const symbolName = getNameForExportedSymbol(exportedSymbol, getEmitScriptTarget(compilerOptions));
|
||||
const checker = program.getTypeChecker();
|
||||
const symbol = checker.getMergedSymbol(skipAlias(exportedSymbol, checker));
|
||||
const exportInfo = getAllExportInfoForSymbol(sourceFile, symbol, symbolName, moduleSymbol, /*preferCapitalized*/ false, program, host, preferences, cancellationToken);
|
||||
const useRequire = shouldUseRequire(sourceFile, program);
|
||||
const fix = getImportFixForSymbol(sourceFile, Debug.checkDefined(exportInfo), program, /*position*/ undefined, !!isValidTypeOnlyUseSite, useRequire, host, preferences);
|
||||
let fix = getImportFixForSymbol(sourceFile, Debug.checkDefined(exportInfo), program, /*position*/ undefined, !!isValidTypeOnlyUseSite, useRequire, host, preferences);
|
||||
if (fix) {
|
||||
addImport({ fix, symbolName, errorIdentifierText: undefined });
|
||||
const localName = tryCast(referenceImport?.name, isIdentifier)?.text ?? symbolName;
|
||||
if (
|
||||
referenceImport
|
||||
&& isTypeOnlyImportDeclaration(referenceImport)
|
||||
&& (fix.kind === ImportFixKind.AddNew || fix.kind === ImportFixKind.AddToExisting)
|
||||
&& fix.addAsTypeOnly === AddAsTypeOnly.Allowed
|
||||
) {
|
||||
// Copy the type-only status from the reference import
|
||||
fix = { ...fix, addAsTypeOnly: AddAsTypeOnly.Required };
|
||||
}
|
||||
addImport({ fix, symbolName: localName ?? symbolName, errorIdentifierText: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
function addImportForNonExistentExport(exportName: string, exportingFileName: string, exportKind: ExportKind, exportedMeanings: SymbolFlags, isImportUsageValidAsTypeOnly: boolean) {
|
||||
const exportingSourceFile = program.getSourceFile(exportingFileName);
|
||||
const useRequire = shouldUseRequire(sourceFile, program);
|
||||
if (exportingSourceFile && exportingSourceFile.symbol) {
|
||||
const { fixes } = getImportFixes(
|
||||
[{
|
||||
exportKind,
|
||||
isFromPackageJson: false,
|
||||
moduleFileName: exportingFileName,
|
||||
moduleSymbol: exportingSourceFile.symbol,
|
||||
targetFlags: exportedMeanings,
|
||||
}],
|
||||
/*usagePosition*/ undefined,
|
||||
isImportUsageValidAsTypeOnly,
|
||||
useRequire,
|
||||
program,
|
||||
sourceFile,
|
||||
host,
|
||||
preferences,
|
||||
);
|
||||
if (fixes.length) {
|
||||
addImport({ fix: fixes[0], symbolName: exportName, errorIdentifierText: exportName });
|
||||
}
|
||||
}
|
||||
else {
|
||||
// File does not exist yet or has no exports, so all imports added will be "new"
|
||||
const futureExportingSourceFile = createFutureSourceFile(exportingFileName, ModuleKind.ESNext, program, host);
|
||||
const moduleSpecifier = moduleSpecifiers.getLocalModuleSpecifierBetweenFileNames(
|
||||
sourceFile,
|
||||
exportingFileName,
|
||||
compilerOptions,
|
||||
createModuleSpecifierResolutionHost(program, host),
|
||||
);
|
||||
const importKind = getImportKind(futureExportingSourceFile, exportKind, compilerOptions);
|
||||
const addAsTypeOnly = getAddAsTypeOnly(
|
||||
isImportUsageValidAsTypeOnly,
|
||||
/*isForNewImportDeclaration*/ true,
|
||||
/*symbol*/ undefined,
|
||||
exportedMeanings,
|
||||
program.getTypeChecker(),
|
||||
compilerOptions,
|
||||
);
|
||||
const fix: FixAddNewImport = {
|
||||
kind: ImportFixKind.AddNew,
|
||||
moduleSpecifier,
|
||||
importKind,
|
||||
addAsTypeOnly,
|
||||
useRequire,
|
||||
};
|
||||
addImport({ fix, symbolName: exportName, errorIdentifierText: exportName });
|
||||
}
|
||||
}
|
||||
|
||||
function removeExistingImport(declaration: ImportOrRequireAliasDeclaration) {
|
||||
if (declaration.kind === SyntaxKind.ImportClause) {
|
||||
Debug.assertIsDefined(declaration.name, "ImportClause should have a name if it's being removed");
|
||||
}
|
||||
removeExisting.add(declaration);
|
||||
}
|
||||
|
||||
function addImport(info: FixInfo) {
|
||||
const { fix, symbolName } = info;
|
||||
switch (fix.kind) {
|
||||
@ -266,10 +370,9 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
|
||||
break;
|
||||
case ImportFixKind.AddToExisting: {
|
||||
const { importClauseOrBindingPattern, importKind, addAsTypeOnly } = fix;
|
||||
const key = String(getNodeId(importClauseOrBindingPattern));
|
||||
let entry = addToExisting.get(key);
|
||||
let entry = addToExisting.get(importClauseOrBindingPattern);
|
||||
if (!entry) {
|
||||
addToExisting.set(key, entry = { importClauseOrBindingPattern, defaultImport: undefined, namedImports: new Map() });
|
||||
addToExisting.set(importClauseOrBindingPattern, entry = { importClauseOrBindingPattern, defaultImport: undefined, namedImports: new Map() });
|
||||
}
|
||||
if (importKind === ImportKind.Named) {
|
||||
const prevValue = entry?.namedImports.get(symbolName);
|
||||
@ -362,7 +465,7 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
|
||||
|
||||
function writeFixes(changeTracker: textChanges.ChangeTracker, oldFileQuotePreference?: QuotePreference) {
|
||||
let quotePreference: QuotePreference;
|
||||
if (sourceFile.imports.length === 0 && oldFileQuotePreference !== undefined) {
|
||||
if (isFullSourceFile(sourceFile) && sourceFile.imports.length === 0 && oldFileQuotePreference !== undefined) {
|
||||
// If the target file has no imports, we must use the same quote preference as the file we are importing from.
|
||||
quotePreference = oldFileQuotePreference;
|
||||
}
|
||||
@ -370,18 +473,102 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
|
||||
quotePreference = getQuotePreference(sourceFile, preferences);
|
||||
}
|
||||
for (const fix of addToNamespace) {
|
||||
addNamespaceQualifier(changeTracker, sourceFile, fix);
|
||||
// Any modifications to existing syntax imply SourceFile already exists
|
||||
addNamespaceQualifier(changeTracker, sourceFile as SourceFile, fix);
|
||||
}
|
||||
for (const fix of importType) {
|
||||
addImportType(changeTracker, sourceFile, fix, quotePreference);
|
||||
// Any modifications to existing syntax imply SourceFile already exists
|
||||
addImportType(changeTracker, sourceFile as SourceFile, fix, quotePreference);
|
||||
}
|
||||
let importSpecifiersToRemoveWhileAdding: Set<ImportSpecifier | BindingElement> | undefined;
|
||||
if (removeExisting.size) {
|
||||
Debug.assert(isFullSourceFile(sourceFile), "Cannot remove imports from a future source file");
|
||||
const importDeclarationsWithRemovals = new Set(mapDefined([...removeExisting], d => findAncestor(d, isImportDeclaration)!));
|
||||
const variableDeclarationsWithRemovals = new Set(mapDefined([...removeExisting], d => findAncestor(d, isVariableDeclarationInitializedToRequire)!));
|
||||
const emptyImportDeclarations = [...importDeclarationsWithRemovals].filter(d =>
|
||||
// nothing added to the import declaration
|
||||
!addToExisting.has(d.importClause!) &&
|
||||
// no default, or default is being removed
|
||||
(!d.importClause?.name || removeExisting.has(d.importClause)) &&
|
||||
// no namespace import, or namespace import is being removed
|
||||
(!tryCast(d.importClause?.namedBindings, isNamespaceImport) || removeExisting.has(d.importClause!.namedBindings as NamespaceImport)) &&
|
||||
// no named imports, or all named imports are being removed
|
||||
(!tryCast(d.importClause?.namedBindings, isNamedImports) || every((d.importClause!.namedBindings as NamedImports).elements, e => removeExisting.has(e)))
|
||||
);
|
||||
const emptyVariableDeclarations = [...variableDeclarationsWithRemovals].filter(d =>
|
||||
// no binding elements being added to the variable declaration
|
||||
(d.name.kind !== SyntaxKind.ObjectBindingPattern || !addToExisting.has(d.name)) &&
|
||||
// no binding elements, or all binding elements are being removed
|
||||
(d.name.kind !== SyntaxKind.ObjectBindingPattern || every(d.name.elements, e => removeExisting.has(e)))
|
||||
);
|
||||
const namedBindingsToDelete = [...importDeclarationsWithRemovals].filter(d =>
|
||||
// has named bindings
|
||||
d.importClause?.namedBindings &&
|
||||
// is not being fully removed
|
||||
emptyImportDeclarations.indexOf(d) === -1 &&
|
||||
// is not gaining named imports
|
||||
!addToExisting.get(d.importClause)?.namedImports &&
|
||||
// all named imports are being removed
|
||||
(d.importClause.namedBindings.kind === SyntaxKind.NamespaceImport || every(d.importClause.namedBindings.elements, e => removeExisting.has(e)))
|
||||
);
|
||||
for (const declaration of [...emptyImportDeclarations, ...emptyVariableDeclarations]) {
|
||||
changeTracker.delete(sourceFile, declaration);
|
||||
}
|
||||
for (const declaration of namedBindingsToDelete) {
|
||||
changeTracker.replaceNode(
|
||||
sourceFile,
|
||||
declaration.importClause!,
|
||||
factory.updateImportClause(
|
||||
declaration.importClause!,
|
||||
declaration.importClause!.isTypeOnly,
|
||||
declaration.importClause!.name,
|
||||
/*namedBindings*/ undefined,
|
||||
),
|
||||
);
|
||||
}
|
||||
for (const declaration of removeExisting) {
|
||||
const importDeclaration = findAncestor(declaration, isImportDeclaration);
|
||||
if (
|
||||
importDeclaration &&
|
||||
emptyImportDeclarations.indexOf(importDeclaration) === -1 &&
|
||||
namedBindingsToDelete.indexOf(importDeclaration) === -1
|
||||
) {
|
||||
if (declaration.kind === SyntaxKind.ImportClause) {
|
||||
changeTracker.delete(sourceFile, declaration.name!);
|
||||
}
|
||||
else {
|
||||
Debug.assert(declaration.kind === SyntaxKind.ImportSpecifier, "NamespaceImport should have been handled earlier");
|
||||
if (addToExisting.get(importDeclaration.importClause!)?.namedImports) {
|
||||
// Handle combined inserts/deletes in `doAddExistingFix`
|
||||
(importSpecifiersToRemoveWhileAdding ??= new Set()).add(declaration);
|
||||
}
|
||||
else {
|
||||
changeTracker.delete(sourceFile, declaration);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (declaration.kind === SyntaxKind.BindingElement) {
|
||||
if (addToExisting.get(declaration.parent as ObjectBindingPattern)?.namedImports) {
|
||||
// Handle combined inserts/deletes in `doAddExistingFix`
|
||||
(importSpecifiersToRemoveWhileAdding ??= new Set()).add(declaration);
|
||||
}
|
||||
else {
|
||||
changeTracker.delete(sourceFile, declaration);
|
||||
}
|
||||
}
|
||||
else if (declaration.kind === SyntaxKind.ImportEqualsDeclaration) {
|
||||
changeTracker.delete(sourceFile, declaration);
|
||||
}
|
||||
}
|
||||
}
|
||||
addToExisting.forEach(({ importClauseOrBindingPattern, defaultImport, namedImports }) => {
|
||||
doAddExistingFix(
|
||||
changeTracker,
|
||||
sourceFile,
|
||||
sourceFile as SourceFile,
|
||||
importClauseOrBindingPattern,
|
||||
defaultImport,
|
||||
arrayFrom(namedImports.entries(), ([name, addAsTypeOnly]) => ({ addAsTypeOnly, name })),
|
||||
importSpecifiersToRemoveWhileAdding,
|
||||
preferences,
|
||||
);
|
||||
});
|
||||
@ -401,13 +588,84 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
|
||||
);
|
||||
newDeclarations = combine(newDeclarations, declarations);
|
||||
});
|
||||
newDeclarations = combine(newDeclarations, getCombinedVerbatimImports());
|
||||
if (newDeclarations) {
|
||||
insertImports(changeTracker, sourceFile, newDeclarations, /*blankLineBetween*/ true, preferences);
|
||||
}
|
||||
}
|
||||
|
||||
function getCombinedVerbatimImports(): AnyImportOrRequireStatement[] | undefined {
|
||||
if (!verbatimImports.size) return undefined;
|
||||
const importDeclarations = new Set(mapDefined([...verbatimImports], d => findAncestor(d, isImportDeclaration)));
|
||||
const requireStatements = new Set(mapDefined([...verbatimImports], d => findAncestor(d, isRequireVariableStatement)));
|
||||
return [
|
||||
...mapDefined([...verbatimImports], d =>
|
||||
d.kind === SyntaxKind.ImportEqualsDeclaration
|
||||
? getSynthesizedDeepClone(d, /*includeTrivia*/ true)
|
||||
: undefined),
|
||||
...[...importDeclarations].map(d => {
|
||||
if (verbatimImports.has(d)) {
|
||||
return getSynthesizedDeepClone(d, /*includeTrivia*/ true);
|
||||
}
|
||||
return getSynthesizedDeepClone(
|
||||
factory.updateImportDeclaration(
|
||||
d,
|
||||
d.modifiers,
|
||||
d.importClause && factory.updateImportClause(
|
||||
d.importClause,
|
||||
d.importClause.isTypeOnly,
|
||||
verbatimImports.has(d.importClause) ? d.importClause.name : undefined,
|
||||
verbatimImports.has(d.importClause.namedBindings as NamespaceImport)
|
||||
? d.importClause.namedBindings as NamespaceImport :
|
||||
tryCast(d.importClause.namedBindings, isNamedImports)?.elements.some(e => verbatimImports.has(e))
|
||||
? factory.updateNamedImports(
|
||||
d.importClause.namedBindings as NamedImports,
|
||||
(d.importClause.namedBindings as NamedImports).elements.filter(e => verbatimImports.has(e)),
|
||||
)
|
||||
: undefined,
|
||||
),
|
||||
d.moduleSpecifier,
|
||||
d.attributes,
|
||||
),
|
||||
/*includeTrivia*/ true,
|
||||
);
|
||||
}),
|
||||
...[...requireStatements].map(s => {
|
||||
if (verbatimImports.has(s)) {
|
||||
return getSynthesizedDeepClone(s, /*includeTrivia*/ true);
|
||||
}
|
||||
return getSynthesizedDeepClone(
|
||||
factory.updateVariableStatement(
|
||||
s,
|
||||
s.modifiers,
|
||||
factory.updateVariableDeclarationList(
|
||||
s.declarationList,
|
||||
mapDefined(s.declarationList.declarations, d => {
|
||||
if (verbatimImports.has(d)) {
|
||||
return d;
|
||||
}
|
||||
return factory.updateVariableDeclaration(
|
||||
d,
|
||||
d.name.kind === SyntaxKind.ObjectBindingPattern
|
||||
? factory.updateObjectBindingPattern(
|
||||
d.name,
|
||||
d.name.elements.filter(e => verbatimImports.has(e)),
|
||||
) : d.name,
|
||||
d.exclamationToken,
|
||||
d.type,
|
||||
d.initializer,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
/*includeTrivia*/ true,
|
||||
) as RequireVariableStatement;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
function hasFixes() {
|
||||
return addToNamespace.length > 0 || importType.length > 0 || addToExisting.size > 0 || newImports.size > 0;
|
||||
return addToNamespace.length > 0 || importType.length > 0 || addToExisting.size > 0 || newImports.size > 0 || verbatimImports.size > 0 || removeExisting.size > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -422,7 +680,7 @@ export interface ImportSpecifierResolver {
|
||||
position: number,
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
fromCacheOnly?: boolean,
|
||||
): { exportInfo?: SymbolExportInfo; moduleSpecifier: string; computedWithoutCacheCount: number; } | undefined;
|
||||
): { exportInfo?: SymbolExportInfo | FutureSymbolExportInfo; moduleSpecifier: string; computedWithoutCacheCount: number; } | undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -436,7 +694,7 @@ export function createImportSpecifierResolver(importingFile: SourceFile, program
|
||||
position: number,
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
fromCacheOnly?: boolean,
|
||||
): { exportInfo?: SymbolExportInfo; moduleSpecifier: string; computedWithoutCacheCount: number; } | undefined {
|
||||
): { exportInfo?: SymbolExportInfo | FutureSymbolExportInfo; moduleSpecifier: string; computedWithoutCacheCount: number; } | undefined {
|
||||
const { fixes, computedWithoutCacheCount } = getImportFixes(
|
||||
exportInfo,
|
||||
position,
|
||||
@ -477,7 +735,7 @@ type ImportFixWithModuleSpecifier = FixUseNamespaceImport | FixAddJsdocTypeImpor
|
||||
// Properties are be undefined if fix is derived from an existing import
|
||||
interface ImportFixBase {
|
||||
readonly isReExport?: boolean;
|
||||
readonly exportInfo?: SymbolExportInfo;
|
||||
readonly exportInfo?: SymbolExportInfo | FutureSymbolExportInfo;
|
||||
readonly moduleSpecifier: string;
|
||||
}
|
||||
interface Qualification {
|
||||
@ -491,7 +749,7 @@ interface FixAddJsdocTypeImport extends ImportFixBase {
|
||||
readonly kind: ImportFixKind.JsdocTypeImport;
|
||||
readonly usagePosition: number;
|
||||
readonly isReExport: boolean;
|
||||
readonly exportInfo: SymbolExportInfo;
|
||||
readonly exportInfo: SymbolExportInfo | FutureSymbolExportInfo;
|
||||
}
|
||||
interface FixAddToExistingImport extends ImportFixBase {
|
||||
readonly kind: ImportFixKind.AddToExisting;
|
||||
@ -516,7 +774,7 @@ interface FixAddToExistingImportInfo {
|
||||
readonly declaration: AnyImportOrRequire;
|
||||
readonly importKind: ImportKind;
|
||||
readonly targetFlags: SymbolFlags;
|
||||
readonly symbol: Symbol;
|
||||
readonly symbol?: Symbol;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -584,7 +842,7 @@ export function getPromoteTypeOnlyCompletionAction(sourceFile: SourceFile, symbo
|
||||
));
|
||||
}
|
||||
|
||||
function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], program: Program, position: number | undefined, isValidTypeOnlyUseSite: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
|
||||
function getImportFixForSymbol(sourceFile: SourceFile | FutureSourceFile, exportInfos: readonly SymbolExportInfo[], program: Program, position: number | undefined, isValidTypeOnlyUseSite: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
|
||||
const packageJsonImportFilter = createPackageJsonImportFilter(sourceFile, preferences, host);
|
||||
return getBestFix(getImportFixes(exportInfos, position, isValidTypeOnlyUseSite, useRequire, program, sourceFile, host, preferences).fixes, sourceFile, program, packageJsonImportFilter, host);
|
||||
}
|
||||
@ -593,7 +851,7 @@ function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAc
|
||||
return { description, changes, commands };
|
||||
}
|
||||
|
||||
function getAllExportInfoForSymbol(importingFile: SourceFile, symbol: Symbol, symbolName: string, moduleSymbol: Symbol, preferCapitalized: boolean, program: Program, host: LanguageServiceHost, preferences: UserPreferences, cancellationToken: CancellationToken | undefined): readonly SymbolExportInfo[] | undefined {
|
||||
function getAllExportInfoForSymbol(importingFile: SourceFile | FutureSourceFile, symbol: Symbol, symbolName: string, moduleSymbol: Symbol, preferCapitalized: boolean, program: Program, host: LanguageServiceHost, preferences: UserPreferences, cancellationToken: CancellationToken | undefined): readonly SymbolExportInfo[] | undefined {
|
||||
const getChecker = createGetChecker(program, host);
|
||||
return getExportInfoMap(importingFile, host, program, preferences, cancellationToken)
|
||||
.search(importingFile.path, preferCapitalized, name => name === symbolName, info => {
|
||||
@ -624,20 +882,24 @@ function getSingleExportInfoForSymbol(symbol: Symbol, symbolName: string, module
|
||||
}
|
||||
}
|
||||
|
||||
function isFutureSymbolExportInfoArray(info: readonly SymbolExportInfo[] | readonly FutureSymbolExportInfo[]): info is readonly FutureSymbolExportInfo[] {
|
||||
return info[0].symbol === undefined;
|
||||
}
|
||||
|
||||
function getImportFixes(
|
||||
exportInfos: readonly SymbolExportInfo[],
|
||||
exportInfos: readonly SymbolExportInfo[] | readonly FutureSymbolExportInfo[],
|
||||
usagePosition: number | undefined,
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
useRequire: boolean,
|
||||
program: Program,
|
||||
sourceFile: SourceFile,
|
||||
sourceFile: SourceFile | FutureSourceFile,
|
||||
host: LanguageServiceHost,
|
||||
preferences: UserPreferences,
|
||||
importMap = createExistingImportMap(program.getTypeChecker(), sourceFile, program.getCompilerOptions()),
|
||||
importMap = isFullSourceFile(sourceFile) ? createExistingImportMap(program.getTypeChecker(), sourceFile, program.getCompilerOptions()) : undefined,
|
||||
fromCacheOnly?: boolean,
|
||||
): { computedWithoutCacheCount: number; fixes: readonly ImportFixWithModuleSpecifier[]; } {
|
||||
const checker = program.getTypeChecker();
|
||||
const existingImports = flatMap(exportInfos, importMap.getImportsForExportInfo);
|
||||
const existingImports = importMap && !isFutureSymbolExportInfoArray(exportInfos) ? flatMap(exportInfos, importMap.getImportsForExportInfo) : emptyArray;
|
||||
const useNamespace = usagePosition !== undefined && tryUseExistingNamespaceImport(existingImports, usagePosition);
|
||||
const addToExisting = tryAddToExistingImport(existingImports, isValidTypeOnlyUseSite, checker, program.getCompilerOptions());
|
||||
if (addToExisting) {
|
||||
@ -706,7 +968,7 @@ function getNamespaceLikeImportText(declaration: AnyImportOrRequire) {
|
||||
function getAddAsTypeOnly(
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
isForNewImportDeclaration: boolean,
|
||||
symbol: Symbol,
|
||||
symbol: Symbol | undefined,
|
||||
targetFlags: SymbolFlags,
|
||||
checker: TypeChecker,
|
||||
compilerOptions: CompilerOptions,
|
||||
@ -716,6 +978,7 @@ function getAddAsTypeOnly(
|
||||
return AddAsTypeOnly.NotAllowed;
|
||||
}
|
||||
if (
|
||||
symbol &&
|
||||
compilerOptions.verbatimModuleSyntax &&
|
||||
(!(targetFlags & SymbolFlags.Value) || !!checker.getTypeOnlyAliasDeclaration(symbol))
|
||||
) {
|
||||
@ -834,9 +1097,9 @@ function createExistingImportMap(checker: TypeChecker, importingFile: SourceFile
|
||||
};
|
||||
}
|
||||
|
||||
function shouldUseRequire(sourceFile: SourceFile, program: Program): boolean {
|
||||
function shouldUseRequire(sourceFile: SourceFile | FutureSourceFile, program: Program): boolean {
|
||||
// 1. TypeScript files don't use require variable declarations
|
||||
if (!isSourceFileJS(sourceFile)) {
|
||||
if (!hasJSFileExtension(sourceFile.fileName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -872,29 +1135,29 @@ function createGetChecker(program: Program, host: LanguageServiceHost) {
|
||||
|
||||
function getNewImportFixes(
|
||||
program: Program,
|
||||
sourceFile: SourceFile,
|
||||
sourceFile: SourceFile | FutureSourceFile,
|
||||
usagePosition: number | undefined,
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
useRequire: boolean,
|
||||
exportInfo: readonly SymbolExportInfo[],
|
||||
exportInfo: readonly (SymbolExportInfo | FutureSymbolExportInfo)[],
|
||||
host: LanguageServiceHost,
|
||||
preferences: UserPreferences,
|
||||
fromCacheOnly?: boolean,
|
||||
): { computedWithoutCacheCount: number; fixes: readonly (FixAddNewImport | FixAddJsdocTypeImport)[]; } {
|
||||
const isJs = isSourceFileJS(sourceFile);
|
||||
const isJs = hasJSFileExtension(sourceFile.fileName);
|
||||
const compilerOptions = program.getCompilerOptions();
|
||||
const moduleSpecifierResolutionHost = createModuleSpecifierResolutionHost(program, host);
|
||||
const getChecker = createGetChecker(program, host);
|
||||
const moduleResolution = getEmitModuleResolutionKind(compilerOptions);
|
||||
const rejectNodeModulesRelativePaths = moduleResolutionUsesNodeModules(moduleResolution);
|
||||
const getModuleSpecifiers = fromCacheOnly
|
||||
? (moduleSymbol: Symbol) => ({ moduleSpecifiers: moduleSpecifiers.tryGetModuleSpecifiersFromCache(moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences), computedWithoutCache: false })
|
||||
: (moduleSymbol: Symbol, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences, /*options*/ undefined, /*forAutoImport*/ true);
|
||||
? (exportInfo: SymbolExportInfo | FutureSymbolExportInfo) => ({ moduleSpecifiers: moduleSpecifiers.tryGetModuleSpecifiersFromCache(exportInfo.moduleSymbol, sourceFile, moduleSpecifierResolutionHost, preferences), computedWithoutCache: false })
|
||||
: (exportInfo: SymbolExportInfo | FutureSymbolExportInfo, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(exportInfo.moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences, /*options*/ undefined, /*forAutoImport*/ true);
|
||||
|
||||
let computedWithoutCacheCount = 0;
|
||||
const fixes = flatMap(exportInfo, (exportInfo, i) => {
|
||||
const checker = getChecker(exportInfo.isFromPackageJson);
|
||||
const { computedWithoutCache, moduleSpecifiers } = getModuleSpecifiers(exportInfo.moduleSymbol, checker);
|
||||
const { computedWithoutCache, moduleSpecifiers } = getModuleSpecifiers(exportInfo, checker);
|
||||
const importedSymbolHasValueMeaning = !!(exportInfo.targetFlags & SymbolFlags.Value);
|
||||
const addAsTypeOnly = getAddAsTypeOnly(isValidTypeOnlyUseSite, /*isForNewImportDeclaration*/ true, exportInfo.symbol, exportInfo.targetFlags, checker, compilerOptions);
|
||||
computedWithoutCacheCount += computedWithoutCache ? 1 : 0;
|
||||
@ -944,10 +1207,10 @@ function getNewImportFixes(
|
||||
}
|
||||
|
||||
function getFixesForAddImport(
|
||||
exportInfos: readonly SymbolExportInfo[],
|
||||
exportInfos: readonly SymbolExportInfo[] | readonly FutureSymbolExportInfo[],
|
||||
existingImports: readonly FixAddToExistingImportInfo[],
|
||||
program: Program,
|
||||
sourceFile: SourceFile,
|
||||
sourceFile: SourceFile | FutureSourceFile,
|
||||
usagePosition: number | undefined,
|
||||
isValidTypeOnlyUseSite: boolean,
|
||||
useRequire: boolean,
|
||||
@ -1011,7 +1274,13 @@ function sortFixInfo(fixes: readonly (FixInfo & { fix: ImportFixWithModuleSpecif
|
||||
compareModuleSpecifiers(a.fix, b.fix, sourceFile, program, packageJsonImportFilter.allowsImportingSpecifier, _toPath));
|
||||
}
|
||||
|
||||
function getBestFix(fixes: readonly ImportFixWithModuleSpecifier[], sourceFile: SourceFile, program: Program, packageJsonImportFilter: PackageJsonImportFilter, host: LanguageServiceHost): ImportFixWithModuleSpecifier | undefined {
|
||||
function getFixInfosWithoutDiagnostic(context: CodeFixContextBase, symbolToken: Identifier, useAutoImportProvider: boolean): readonly FixInfo[] | undefined {
|
||||
const info = getFixesInfoForNonUMDImport(context, symbolToken, useAutoImportProvider);
|
||||
const packageJsonImportFilter = createPackageJsonImportFilter(context.sourceFile, context.preferences, context.host);
|
||||
return info && sortFixInfo(info, context.sourceFile, context.program, packageJsonImportFilter, context.host);
|
||||
}
|
||||
|
||||
function getBestFix(fixes: readonly ImportFixWithModuleSpecifier[], sourceFile: SourceFile | FutureSourceFile, program: Program, packageJsonImportFilter: PackageJsonImportFilter, host: LanguageServiceHost): ImportFixWithModuleSpecifier | undefined {
|
||||
if (!some(fixes)) return;
|
||||
// These will always be placed first if available, and are better than other kinds
|
||||
if (fixes[0].kind === ImportFixKind.UseNamespace || fixes[0].kind === ImportFixKind.AddToExisting) {
|
||||
@ -1035,7 +1304,7 @@ function getBestFix(fixes: readonly ImportFixWithModuleSpecifier[], sourceFile:
|
||||
function compareModuleSpecifiers(
|
||||
a: ImportFixWithModuleSpecifier,
|
||||
b: ImportFixWithModuleSpecifier,
|
||||
importingFile: SourceFile,
|
||||
importingFile: SourceFile | FutureSourceFile,
|
||||
program: Program,
|
||||
allowsImportingSpecifier: (specifier: string) => boolean,
|
||||
toPath: (fileName: string) => Path,
|
||||
@ -1044,8 +1313,8 @@ function compareModuleSpecifiers(
|
||||
return compareBooleans(allowsImportingSpecifier(b.moduleSpecifier), allowsImportingSpecifier(a.moduleSpecifier))
|
||||
|| compareNodeCoreModuleSpecifiers(a.moduleSpecifier, b.moduleSpecifier, importingFile, program)
|
||||
|| compareBooleans(
|
||||
isFixPossiblyReExportingImportingFile(a, importingFile, program.getCompilerOptions(), toPath),
|
||||
isFixPossiblyReExportingImportingFile(b, importingFile, program.getCompilerOptions(), toPath),
|
||||
isFixPossiblyReExportingImportingFile(a, importingFile.path, toPath),
|
||||
isFixPossiblyReExportingImportingFile(b, importingFile.path, toPath),
|
||||
)
|
||||
|| compareNumberOfDirectorySeparators(a.moduleSpecifier, b.moduleSpecifier);
|
||||
}
|
||||
@ -1056,14 +1325,14 @@ function compareModuleSpecifiers(
|
||||
// E.g., do not `import { Foo } from ".."` when you could `import { Foo } from "../Foo"`.
|
||||
// This can produce false positives or negatives if re-exports cross into sibling directories
|
||||
// (e.g. `export * from "../whatever"`) or are not named "index".
|
||||
function isFixPossiblyReExportingImportingFile(fix: ImportFixWithModuleSpecifier, importingFile: SourceFile, compilerOptions: CompilerOptions, toPath: (fileName: string) => Path): boolean {
|
||||
function isFixPossiblyReExportingImportingFile(fix: ImportFixWithModuleSpecifier, importingFilePath: Path, toPath: (fileName: string) => Path): boolean {
|
||||
if (
|
||||
fix.isReExport &&
|
||||
fix.exportInfo?.moduleFileName &&
|
||||
isIndexFileName(fix.exportInfo.moduleFileName)
|
||||
) {
|
||||
const reExportDir = toPath(getDirectoryPath(fix.exportInfo.moduleFileName));
|
||||
return startsWith(importingFile.path, reExportDir);
|
||||
return startsWith(importingFilePath, reExportDir);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1072,7 +1341,7 @@ function isIndexFileName(fileName: string) {
|
||||
return getBaseFileName(fileName, [".js", ".jsx", ".d.ts", ".ts", ".tsx"], /*ignoreCase*/ true) === "index";
|
||||
}
|
||||
|
||||
function compareNodeCoreModuleSpecifiers(a: string, b: string, importingFile: SourceFile, program: Program): Comparison {
|
||||
function compareNodeCoreModuleSpecifiers(a: string, b: string, importingFile: SourceFile | FutureSourceFile, program: Program): Comparison {
|
||||
if (startsWith(a, "node:") && !startsWith(b, "node:")) return shouldUseUriStyleNodeCoreModules(importingFile, program) ? Comparison.LessThan : Comparison.GreaterThan;
|
||||
if (startsWith(b, "node:") && !startsWith(a, "node:")) return shouldUseUriStyleNodeCoreModules(importingFile, program) ? Comparison.GreaterThan : Comparison.LessThan;
|
||||
return Comparison.EqualTo;
|
||||
@ -1117,7 +1386,7 @@ function getUmdSymbol(token: Node, checker: TypeChecker): Symbol | undefined {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function getImportKind(importingFile: SourceFile, exportKind: ExportKind, compilerOptions: CompilerOptions, forceImportKeyword?: boolean): ImportKind {
|
||||
export function getImportKind(importingFile: SourceFile | FutureSourceFile, exportKind: ExportKind, compilerOptions: CompilerOptions, forceImportKeyword?: boolean): ImportKind {
|
||||
if (compilerOptions.verbatimModuleSyntax && (getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS || importingFile.impliedNodeFormat === ModuleKind.CommonJS)) {
|
||||
// TODO: if the exporting file is ESM under nodenext, or `forceImport` is given in a JS file, this is impossible
|
||||
return ImportKind.CommonJS;
|
||||
@ -1136,7 +1405,7 @@ export function getImportKind(importingFile: SourceFile, exportKind: ExportKind,
|
||||
}
|
||||
}
|
||||
|
||||
function getUmdImportKind(importingFile: SourceFile, compilerOptions: CompilerOptions, forceImportKeyword: boolean): ImportKind {
|
||||
function getUmdImportKind(importingFile: SourceFile | FutureSourceFile, compilerOptions: CompilerOptions, forceImportKeyword: boolean): ImportKind {
|
||||
// Import a synthetic `default` if enabled.
|
||||
if (getAllowSyntheticDefaultImports(compilerOptions)) {
|
||||
return ImportKind.Default;
|
||||
@ -1148,8 +1417,8 @@ function getUmdImportKind(importingFile: SourceFile, compilerOptions: CompilerOp
|
||||
case ModuleKind.AMD:
|
||||
case ModuleKind.CommonJS:
|
||||
case ModuleKind.UMD:
|
||||
if (isInJSFile(importingFile)) {
|
||||
return isExternalModule(importingFile) || forceImportKeyword ? ImportKind.Namespace : ImportKind.CommonJS;
|
||||
if (hasJSFileExtension(importingFile.fileName)) {
|
||||
return importingFile.externalModuleIndicator || forceImportKeyword ? ImportKind.Namespace : ImportKind.CommonJS;
|
||||
}
|
||||
return ImportKind.CommonJS;
|
||||
case ModuleKind.System:
|
||||
@ -1265,9 +1534,9 @@ function getExportInfos(
|
||||
return originalSymbolToExportInfos;
|
||||
}
|
||||
|
||||
function getExportEqualsImportKind(importingFile: SourceFile, compilerOptions: CompilerOptions, forceImportKeyword: boolean): ImportKind {
|
||||
function getExportEqualsImportKind(importingFile: SourceFile | FutureSourceFile, compilerOptions: CompilerOptions, forceImportKeyword: boolean): ImportKind {
|
||||
const allowSyntheticDefaults = getAllowSyntheticDefaultImports(compilerOptions);
|
||||
const isJS = isInJSFile(importingFile);
|
||||
const isJS = hasJSFileExtension(importingFile.fileName);
|
||||
// 1. 'import =' will not work in es2015+ TS files, so the decision is between a default
|
||||
// and a namespace import, based on allowSyntheticDefaultImports/esModuleInterop.
|
||||
if (!isJS && getEmitModuleKind(compilerOptions) >= ModuleKind.ES2015) {
|
||||
@ -1276,14 +1545,14 @@ function getExportEqualsImportKind(importingFile: SourceFile, compilerOptions: C
|
||||
// 2. 'import =' will not work in JavaScript, so the decision is between a default import,
|
||||
// a namespace import, and const/require.
|
||||
if (isJS) {
|
||||
return isExternalModule(importingFile) || forceImportKeyword
|
||||
return importingFile.externalModuleIndicator || forceImportKeyword
|
||||
? allowSyntheticDefaults ? ImportKind.Default : ImportKind.Namespace
|
||||
: ImportKind.CommonJS;
|
||||
}
|
||||
// 3. At this point the most correct choice is probably 'import =', but people
|
||||
// really hate that, so look to see if the importing file has any precedent
|
||||
// on how to handle it.
|
||||
for (const statement of importingFile.statements) {
|
||||
for (const statement of importingFile.statements ?? emptyArray) {
|
||||
// `import foo` parses as an ImportEqualsDeclaration even though it could be an ImportDeclaration
|
||||
if (isImportEqualsDeclaration(statement) && !nodeIsMissing(statement.moduleReference)) {
|
||||
return ImportKind.CommonJS;
|
||||
@ -1334,6 +1603,7 @@ function codeActionForFixWorker(
|
||||
importClauseOrBindingPattern,
|
||||
importKind === ImportKind.Default ? { name: symbolName, addAsTypeOnly } : undefined,
|
||||
importKind === ImportKind.Named ? [{ name: symbolName, addAsTypeOnly }] : emptyArray,
|
||||
/*removeExistingImportSpecifiers*/ undefined,
|
||||
preferences,
|
||||
);
|
||||
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
|
||||
@ -1474,9 +1744,25 @@ function doAddExistingFix(
|
||||
clause: ImportClause | ObjectBindingPattern,
|
||||
defaultImport: Import | undefined,
|
||||
namedImports: readonly Import[],
|
||||
removeExistingImportSpecifiers: Set<ImportSpecifier | BindingElement> | undefined,
|
||||
preferences: UserPreferences,
|
||||
): void {
|
||||
if (clause.kind === SyntaxKind.ObjectBindingPattern) {
|
||||
if (removeExistingImportSpecifiers && clause.elements.some(e => removeExistingImportSpecifiers.has(e))) {
|
||||
// If we're both adding and removing elements, just replace and reprint the whole
|
||||
// node. The change tracker doesn't understand all the operations and can insert or
|
||||
// leave behind stray commas.
|
||||
changes.replaceNode(
|
||||
sourceFile,
|
||||
clause,
|
||||
factory.createObjectBindingPattern([
|
||||
...clause.elements.filter(e => !removeExistingImportSpecifiers.has(e)),
|
||||
...defaultImport ? [factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ "default", defaultImport.name)] : emptyArray,
|
||||
...namedImports.map(i => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, i.name)),
|
||||
]),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (defaultImport) {
|
||||
addElementToBindingPattern(clause, defaultImport.name, "default");
|
||||
}
|
||||
@ -1508,6 +1794,19 @@ function doAddExistingFix(
|
||||
specifierComparer,
|
||||
);
|
||||
|
||||
if (removeExistingImportSpecifiers) {
|
||||
// If we're both adding and removing specifiers, just replace and reprint the whole
|
||||
// node. The change tracker doesn't understand all the operations and can insert or
|
||||
// leave behind stray commas.
|
||||
changes.replaceNode(
|
||||
sourceFile,
|
||||
clause.namedBindings!,
|
||||
factory.updateNamedImports(
|
||||
clause.namedBindings as NamedImports,
|
||||
stableSort([...existingSpecifiers!.filter(s => !removeExistingImportSpecifiers.has(s)), ...newSpecifiers], specifierComparer),
|
||||
),
|
||||
);
|
||||
}
|
||||
// The sorting preference computed earlier may or may not have validated that these particular
|
||||
// import specifiers are sorted. If they aren't, `getImportSpecifierInsertionIndex` will return
|
||||
// nonsense. So if there are existing specifiers, even if we know the sorting preference, we
|
||||
@ -1515,7 +1814,7 @@ function doAddExistingFix(
|
||||
// to do a sorted insertion.
|
||||
|
||||
// changed to check if existing specifiers are sorted
|
||||
if (existingSpecifiers?.length && isSorted !== false) {
|
||||
else if (existingSpecifiers?.length && isSorted !== false) {
|
||||
// if we're promoting the clause from type-only, we need to transform the existing imports before attempting to insert the new named imports
|
||||
const transformedExistingSpecifiers = (promoteFromTypeOnly && existingSpecifiers) ? factory.updateNamedImports(
|
||||
clause.namedBindings as NamedImports,
|
||||
|
||||
@ -73,6 +73,7 @@ import {
|
||||
forEach,
|
||||
formatting,
|
||||
FunctionLikeDeclaration,
|
||||
FutureSymbolExportInfo,
|
||||
getAllSuperTypeNodes,
|
||||
getAncestor,
|
||||
getCombinedLocalAndExportSymbolFlags,
|
||||
@ -609,7 +610,7 @@ interface ModuleSpecifierResolutionContext {
|
||||
}
|
||||
|
||||
type ModuleSpecifierResolutionResult = "skipped" | "failed" | {
|
||||
exportInfo?: SymbolExportInfo;
|
||||
exportInfo?: SymbolExportInfo | FutureSymbolExportInfo;
|
||||
moduleSpecifier: string;
|
||||
};
|
||||
|
||||
@ -4079,20 +4080,20 @@ function getCompletionData(
|
||||
// it should be identical regardless of which one is used. During the subsequent
|
||||
// `CompletionEntryDetails` request, we'll get all the ExportInfos again and pick
|
||||
// the best one based on the module specifier it produces.
|
||||
let exportInfo = info[0], moduleSpecifier;
|
||||
let exportInfo: SymbolExportInfo | FutureSymbolExportInfo = info[0], moduleSpecifier;
|
||||
if (result !== "skipped") {
|
||||
({ exportInfo = info[0], moduleSpecifier } = result);
|
||||
}
|
||||
|
||||
const isDefaultExport = exportInfo.exportKind === ExportKind.Default;
|
||||
const symbol = isDefaultExport && getLocalSymbolForExportDefault(exportInfo.symbol) || exportInfo.symbol;
|
||||
const symbol = isDefaultExport && getLocalSymbolForExportDefault(Debug.checkDefined(exportInfo.symbol)) || Debug.checkDefined(exportInfo.symbol);
|
||||
|
||||
pushAutoImportSymbol(symbol, {
|
||||
kind: moduleSpecifier ? SymbolOriginInfoKind.ResolvedExport : SymbolOriginInfoKind.Export,
|
||||
moduleSpecifier,
|
||||
symbolName,
|
||||
exportMapKey,
|
||||
exportName: exportInfo.exportKind === ExportKind.ExportEquals ? InternalSymbolName.ExportEquals : exportInfo.symbol.name,
|
||||
exportName: exportInfo.exportKind === ExportKind.ExportEquals ? InternalSymbolName.ExportEquals : Debug.checkDefined(exportInfo.symbol).name,
|
||||
fileName: exportInfo.moduleFileName,
|
||||
isDefaultExport,
|
||||
moduleSymbol: exportInfo.moduleSymbol,
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
firstDefined,
|
||||
forEachAncestorDirectory,
|
||||
forEachEntry,
|
||||
FutureSourceFile,
|
||||
getBaseFileName,
|
||||
GetCanonicalFileName,
|
||||
getDirectoryPath,
|
||||
@ -90,6 +91,12 @@ export interface SymbolExportInfo {
|
||||
isFromPackageJson: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* ExportInfo for an export that does not exist yet, so does not have a symbol.
|
||||
*/
|
||||
export type FutureSymbolExportInfo = Omit<SymbolExportInfo, "symbol"> & { readonly symbol?: undefined; };
|
||||
|
||||
interface CachedSymbolExportInfo {
|
||||
// Used to rehydrate `symbol` and `moduleSymbol` when transient
|
||||
id: number;
|
||||
@ -477,7 +484,7 @@ function forEachExternalModule(checker: TypeChecker, allSourceFiles: readonly So
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getExportInfoMap(importingFile: SourceFile, host: LanguageServiceHost, program: Program, preferences: UserPreferences, cancellationToken: CancellationToken | undefined): ExportInfoMap {
|
||||
export function getExportInfoMap(importingFile: SourceFile | FutureSourceFile, host: LanguageServiceHost, program: Program, preferences: UserPreferences, cancellationToken: CancellationToken | undefined): ExportInfoMap {
|
||||
const start = timestamp();
|
||||
// Pulling the AutoImportProvider project will trigger its updateGraph if pending,
|
||||
// which will invalidate the export map cache if things change, so pull it before
|
||||
|
||||
@ -655,7 +655,7 @@ export class ChangeTracker {
|
||||
this.insertNodesAt(sourceFile, pos, insert, options);
|
||||
}
|
||||
|
||||
private insertStatementsInNewFile(fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], oldFile?: SourceFile): void {
|
||||
public insertStatementsInNewFile(fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], oldFile?: SourceFile): void {
|
||||
if (!this.newFileChanges) {
|
||||
this.newFileChanges = createMultiMap<string, NewFileInsertion>();
|
||||
}
|
||||
|
||||
@ -90,6 +90,7 @@ import {
|
||||
FunctionDeclaration,
|
||||
FunctionExpression,
|
||||
FunctionLikeDeclaration,
|
||||
FutureSourceFile,
|
||||
getAssignmentDeclarationKind,
|
||||
getCombinedNodeFlagsAlwaysIncludeJSDoc,
|
||||
getDirectoryPath,
|
||||
@ -97,6 +98,7 @@ import {
|
||||
getEmitScriptTarget,
|
||||
getExternalModuleImportEqualsDeclarationExpression,
|
||||
getImpliedNodeFormatForFile,
|
||||
getImpliedNodeFormatForFileWorker,
|
||||
getIndentString,
|
||||
getJSDocEnumTag,
|
||||
getLastChild,
|
||||
@ -106,6 +108,7 @@ import {
|
||||
getModuleInstanceState,
|
||||
getNameOfDeclaration,
|
||||
getNodeId,
|
||||
getOriginalNode,
|
||||
getPackageNameFromTypesPackageName,
|
||||
getPathComponents,
|
||||
getRootDeclaration,
|
||||
@ -168,6 +171,7 @@ import {
|
||||
isFileLevelUniqueName,
|
||||
isForInStatement,
|
||||
isForOfStatement,
|
||||
isFullSourceFile,
|
||||
isFunctionBlock,
|
||||
isFunctionDeclaration,
|
||||
isFunctionExpression,
|
||||
@ -281,6 +285,7 @@ import {
|
||||
ModuleDeclaration,
|
||||
ModuleInstanceState,
|
||||
ModuleKind,
|
||||
ModuleResolutionHost,
|
||||
ModuleResolutionKind,
|
||||
ModuleSpecifierResolutionHost,
|
||||
moduleSpecifiers,
|
||||
@ -2533,13 +2538,13 @@ export function quotePreferenceFromString(str: StringLiteral, sourceFile: Source
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference {
|
||||
export function getQuotePreference(sourceFile: SourceFile | FutureSourceFile, preferences: UserPreferences): QuotePreference {
|
||||
if (preferences.quotePreference && preferences.quotePreference !== "auto") {
|
||||
return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double;
|
||||
}
|
||||
else {
|
||||
// ignore synthetic import added when importHelpers: true
|
||||
const firstModuleSpecifier = sourceFile.imports &&
|
||||
const firstModuleSpecifier = isFullSourceFile(sourceFile) && sourceFile.imports &&
|
||||
find(sourceFile.imports, n => isStringLiteral(n) && !nodeIsSynthesized(n.parent)) as StringLiteral;
|
||||
return firstModuleSpecifier ? quotePreferenceFromString(firstModuleSpecifier, sourceFile) : QuotePreference.Double;
|
||||
}
|
||||
@ -2627,16 +2632,28 @@ export function findModifier(node: Node, kind: Modifier["kind"]): Modifier | und
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function insertImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile, imports: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[], blankLineBetween: boolean, preferences: UserPreferences): void {
|
||||
export function insertImports(changes: textChanges.ChangeTracker, sourceFile: SourceFile | FutureSourceFile, imports: AnyImportOrRequireStatement | readonly AnyImportOrRequireStatement[], blankLineBetween: boolean, preferences: UserPreferences): void {
|
||||
const decl = isArray(imports) ? imports[0] : imports;
|
||||
const importKindPredicate: (node: Node) => node is AnyImportOrRequireStatement = decl.kind === SyntaxKind.VariableStatement ? isRequireVariableStatement : isAnyImportSyntax;
|
||||
const existingImportStatements = filter(sourceFile.statements, importKindPredicate);
|
||||
const { comparer, isSorted } = OrganizeImports.getOrganizeImportsStringComparerWithDetection(existingImportStatements, preferences);
|
||||
const sortedNewImports = isArray(imports) ? stableSort(imports, (a, b) => OrganizeImports.compareImportsOrRequireStatements(a, b, comparer)) : [imports];
|
||||
if (!existingImportStatements.length) {
|
||||
changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween);
|
||||
if (!existingImportStatements?.length) {
|
||||
if (isFullSourceFile(sourceFile)) {
|
||||
changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween);
|
||||
}
|
||||
else {
|
||||
for (const newImport of sortedNewImports) {
|
||||
// Insert one at a time to send correct original source file for accurate text reuse
|
||||
// when some imports are cloned from existing ones in other files.
|
||||
changes.insertStatementsInNewFile(sourceFile.fileName, [newImport], getOriginalNode(newImport)?.getSourceFile());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (existingImportStatements && isSorted) {
|
||||
|
||||
Debug.assert(isFullSourceFile(sourceFile));
|
||||
if (existingImportStatements && isSorted) {
|
||||
for (const newImport of sortedNewImports) {
|
||||
const insertionIndex = OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport, comparer);
|
||||
if (insertionIndex === 0) {
|
||||
@ -3761,7 +3778,7 @@ export interface PackageJsonImportFilter {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function createPackageJsonImportFilter(fromFile: SourceFile, preferences: UserPreferences, host: LanguageServiceHost): PackageJsonImportFilter {
|
||||
export function createPackageJsonImportFilter(fromFile: SourceFile | FutureSourceFile, preferences: UserPreferences, host: LanguageServiceHost): PackageJsonImportFilter {
|
||||
const packageJsons = (
|
||||
(host.getPackageJsonsVisibleToFile && host.getPackageJsonsVisibleToFile(fromFile.fileName)) || getPackageJsonsVisibleToFile(fromFile.fileName, host)
|
||||
).filter(p => p.parseable);
|
||||
@ -3860,7 +3877,7 @@ export function createPackageJsonImportFilter(fromFile: SourceFile, preferences:
|
||||
// from Node core modules or not. We can start by seeing if the user is actually using
|
||||
// any node core modules, as opposed to simply having @types/node accidentally as a
|
||||
// dependency of a dependency.
|
||||
if (isSourceFileJS(fromFile) && JsTyping.nodeCoreModules.has(moduleSpecifier)) {
|
||||
if (isFullSourceFile(fromFile) && isSourceFileJS(fromFile) && JsTyping.nodeCoreModules.has(moduleSpecifier)) {
|
||||
if (usesNodeCoreModules === undefined) {
|
||||
usesNodeCoreModules = consumesNodeCoreModules(fromFile);
|
||||
}
|
||||
@ -4121,7 +4138,7 @@ export function isDeprecatedDeclaration(decl: Declaration) {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function shouldUseUriStyleNodeCoreModules(file: SourceFile, program: Program): boolean {
|
||||
export function shouldUseUriStyleNodeCoreModules(file: SourceFile | FutureSourceFile, program: Program): boolean {
|
||||
const decisionFromFile = firstDefined(file.imports, node => {
|
||||
if (JsTyping.nodeCoreModules.has(node.text)) {
|
||||
return startsWith(node.text, "node:");
|
||||
@ -4293,3 +4310,23 @@ export function isBlockLike(node: Node): node is BlockLike {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function createFutureSourceFile(fileName: string, syntaxModuleIndicator: ModuleKind.ESNext | ModuleKind.CommonJS | undefined, program: Program, moduleResolutionHost: ModuleResolutionHost): FutureSourceFile {
|
||||
const result = getImpliedNodeFormatForFileWorker(fileName, program.getPackageJsonInfoCache?.(), moduleResolutionHost, program.getCompilerOptions());
|
||||
let impliedNodeFormat, packageJsonScope;
|
||||
if (typeof result === "object") {
|
||||
impliedNodeFormat = result.impliedNodeFormat;
|
||||
packageJsonScope = result.packageJsonScope;
|
||||
}
|
||||
return {
|
||||
path: toPath(fileName, program.getCurrentDirectory(), program.getCanonicalFileName),
|
||||
fileName,
|
||||
externalModuleIndicator: syntaxModuleIndicator === ModuleKind.ESNext ? true : undefined,
|
||||
commonJsModuleIndicator: syntaxModuleIndicator === ModuleKind.CommonJS ? true : undefined,
|
||||
impliedNodeFormat,
|
||||
packageJsonScope,
|
||||
statements: emptyArray,
|
||||
imports: emptyArray,
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user