Allow ImportAdder to insert imports into a new file

This commit is contained in:
Andrew Branch
2023-04-05 16:02:39 -07:00
committed by navya9singh
parent 68d8be4f1d
commit fab885d440
16 changed files with 189 additions and 113 deletions

View File

@@ -31,6 +31,7 @@ import {
flatten,
forEach,
forEachAncestorDirectory,
FutureSourceFile,
getBaseFileName,
GetCanonicalFileName,
getConditions,
@@ -126,7 +127,7 @@ interface Preferences {
function getPreferences(
{ importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences,
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
importingSourceFile: SourceFile | FutureSourceFile,
oldImportSpecifier?: string,
): Preferences {
const preferredEnding = getPreferredEnding();
@@ -212,7 +213,7 @@ export function getModuleSpecifier(
/** @internal */
export function getNodeModulesPackageName(
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
importingSourceFile: SourceFile | FutureSourceFile,
nodeModulesFileName: string,
host: ModuleSpecifierResolutionHost,
preferences: UserPreferences,
@@ -243,14 +244,14 @@ function getModuleSpecifierWorker(
/** @internal */
export function tryGetModuleSpecifiersFromCache(
moduleSymbol: Symbol,
importingSourceFile: SourceFile,
importingSourceFilePath: Path,
host: ModuleSpecifierResolutionHost,
userPreferences: UserPreferences,
options: ModuleSpecifierOptions = {},
): readonly string[] | undefined {
return tryGetModuleSpecifiersFromCacheWorker(
moduleSymbol,
importingSourceFile,
importingSourceFilePath,
host,
userPreferences,
options)[0];
@@ -258,7 +259,7 @@ export function tryGetModuleSpecifiersFromCache(
function tryGetModuleSpecifiersFromCacheWorker(
moduleSymbol: Symbol,
importingSourceFile: SourceFile,
importingSourceFilePath: Path,
host: ModuleSpecifierResolutionHost,
userPreferences: UserPreferences,
options: ModuleSpecifierOptions = {},
@@ -269,7 +270,7 @@ function tryGetModuleSpecifiersFromCacheWorker(
}
const cache = host.getModuleSpecifierCache?.();
const cached = cache?.get(importingSourceFile.path, moduleSourceFile.path, userPreferences, options);
const cached = cache?.get(importingSourceFilePath, moduleSourceFile.path, userPreferences, options);
return [cached?.moduleSpecifiers, moduleSourceFile, cached?.modulePaths, cache];
}
@@ -303,7 +304,7 @@ export function getModuleSpecifiersWithCacheInfo(
moduleSymbol: Symbol,
checker: TypeChecker,
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
importingSourceFile: SourceFile | FutureSourceFile,
host: ModuleSpecifierResolutionHost,
userPreferences: UserPreferences,
options: ModuleSpecifierOptions = {},
@@ -315,7 +316,7 @@ export function getModuleSpecifiersWithCacheInfo(
// eslint-disable-next-line prefer-const
let [specifiers, moduleSourceFile, modulePaths, cache] = tryGetModuleSpecifiersFromCacheWorker(
moduleSymbol,
importingSourceFile,
importingSourceFile.path,
host,
userPreferences,
options
@@ -333,14 +334,14 @@ export function getModuleSpecifiersWithCacheInfo(
function computeModuleSpecifiers(
modulePaths: readonly ModulePath[],
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
importingSourceFile: SourceFile | FutureSourceFile,
host: ModuleSpecifierResolutionHost,
userPreferences: UserPreferences,
options: ModuleSpecifierOptions = {},
): readonly string[] {
const info = getInfo(importingSourceFile.path, host);
const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile);
const existingSpecifier = forEach(modulePaths, modulePath => forEach(
const existingSpecifier = importingSourceFile.kind && forEach(modulePaths, modulePath => forEach(
host.getFileIncludeReasons().get(toPath(modulePath.path, host.getCurrentDirectory(), info.getCanonicalFileName)),
reason => {
if (reason.kind !== FileIncludeKind.Import || reason.file !== importingSourceFile.path) return undefined;
@@ -877,7 +878,7 @@ function tryGetModuleNameFromRootDirs(rootDirs: readonly string[], moduleFileNam
return processEnding(shortest, allowedEndings, compilerOptions);
}
function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile, host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ResolutionMode): string | undefined {
function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCanonicalFileName, sourceDirectory }: Info, importingSourceFile: SourceFile | FutureSourceFile, host: ModuleSpecifierResolutionHost, options: CompilerOptions, userPreferences: UserPreferences, packageNameOnly?: boolean, overrideMode?: ResolutionMode): string | undefined {
if (!host.fileExists || !host.readFile) {
return undefined;
}

View File

@@ -4220,6 +4220,15 @@ export interface SourceFileLike {
getPositionOfLineAndCharacter?(line: number, character: number, allowEdits?: true): number;
}
/** @internal */
export interface FutureSourceFile {
readonly kind?: undefined;
readonly fileName: string;
readonly path: Path;
readonly impliedNodeFormat?: ResolutionMode;
readonly commonJsModuleIndicator?: boolean;
readonly externalModuleIndicator?: boolean;
}
/** @internal */
export interface RedirectInfo {

View File

@@ -157,6 +157,7 @@ import {
FunctionDeclaration,
FunctionExpression,
FunctionLikeDeclaration,
FutureSourceFile,
GetAccessorDeclaration,
getBaseFileName,
GetCanonicalFileName,
@@ -9160,7 +9161,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 | FutureSourceFile): ModuleSpecifierEnding {
if (preference === "js" || resolutionMode === ModuleKind.ESNext) {
// Extensions are explicitly requested or required. Now choose between .js and .ts.
if (!shouldAllowImportingTsExtension(compilerOptions)) {
@@ -9185,27 +9186,30 @@ 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.kind && usesExtensionsOnImports(sourceFile) ? ModuleSpecifierEnding.JsExtension : ModuleSpecifierEnding.Minimal;
}
return inferPreference();
function inferPreference() {
let usesJsExtensions = false;
const specifiers = sourceFile.imports.length ? sourceFile.imports.map(i => i.text) :
isSourceFileJS(sourceFile) ? getRequiresAtTopOfFile(sourceFile).map(r => r.arguments[0].text) :
emptyArray;
for (const specifier of specifiers) {
if (pathIsRelative(specifier)) {
if (hasTSFileExtension(specifier)) {
return ModuleSpecifierEnding.TsExtension;
}
if (hasJSFileExtension(specifier)) {
usesJsExtensions = true;
if (sourceFile.kind) {
let usesJsExtensions = false;
const specifiers = sourceFile.imports.length ? sourceFile.imports.map(i => i.text) :
isSourceFileJS(sourceFile) ? getRequiresAtTopOfFile(sourceFile).map(r => r.arguments[0].text) :
emptyArray;
for (const specifier of specifiers) {
if (pathIsRelative(specifier)) {
if (hasTSFileExtension(specifier)) {
return ModuleSpecifierEnding.TsExtension;
}
if (hasJSFileExtension(specifier)) {
usesJsExtensions = true;
}
}
}
return usesJsExtensions ? ModuleSpecifierEnding.JsExtension : ModuleSpecifierEnding.Minimal;
}
return usesJsExtensions ? ModuleSpecifierEnding.JsExtension : ModuleSpecifierEnding.Minimal;
return ModuleSpecifierEnding.Minimal;
}
}

View File

@@ -21,7 +21,7 @@ import {
FunctionExpression,
getEmitScriptTarget,
getNameOfDeclaration,
getQuotePreference,
getQuotePreferenceFromFile,
getTokenAtPosition,
idText,
isAccessExpression,
@@ -211,7 +211,7 @@ function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, po
// f.x = expr
if (isAccessExpression(memberDeclaration) && (isFunctionExpression(assignmentExpr) || isArrowFunction(assignmentExpr))) {
const quotePreference = getQuotePreference(sourceFile, preferences);
const quotePreference = getQuotePreferenceFromFile(sourceFile, preferences);
const name = tryGetPropertyName(memberDeclaration, compilerOptions, quotePreference);
if (name) {
createFunctionLikeExpressionMember(members, assignmentExpr, name);

View File

@@ -27,7 +27,7 @@ import {
FunctionExpression,
getEmitScriptTarget,
getModeForUsageLocation,
getQuotePreference,
getQuotePreferenceFromFile,
getResolvedModule,
getSynthesizedDeepClone,
getSynthesizedDeepClones,
@@ -87,10 +87,10 @@ registerCodeFix({
getCodeActions(context) {
const { sourceFile, program, preferences } = context;
const changes = textChanges.ChangeTracker.with(context, changes => {
const moduleExportsChangedToDefault = convertFileToEsModule(sourceFile, program.getTypeChecker(), changes, getEmitScriptTarget(program.getCompilerOptions()), getQuotePreference(sourceFile, preferences));
const moduleExportsChangedToDefault = convertFileToEsModule(sourceFile, program.getTypeChecker(), changes, getEmitScriptTarget(program.getCompilerOptions()), getQuotePreferenceFromFile(sourceFile, preferences));
if (moduleExportsChangedToDefault) {
for (const importingFile of program.getSourceFiles()) {
fixImportOfModuleExports(importingFile, sourceFile, changes, getQuotePreference(importingFile, preferences));
fixImportOfModuleExports(importingFile, sourceFile, changes, getQuotePreferenceFromFile(importingFile, preferences));
}
}
});

View File

@@ -33,7 +33,7 @@ import {
getNodeId,
getObjectFlags,
getOrUpdate,
getQuotePreference,
getQuotePreferenceFromFile,
getSourceFileOfNode,
getTokenAtPosition,
hasAbstractModifier,
@@ -597,7 +597,7 @@ function addEnumMemberDeclaration(changes: textChanges.ChangeTracker, checker: T
}
function addFunctionDeclaration(changes: textChanges.ChangeTracker, context: CodeFixContextBase, info: FunctionInfo | SignatureInfo) {
const quotePreference = getQuotePreference(context.sourceFile, context.preferences);
const quotePreference = getQuotePreferenceFromFile(context.sourceFile, context.preferences);
const importAdder = createImportAdder(context.sourceFile, context.program, context.preferences, context.host);
const functionDeclaration = info.kind === InfoKind.Function
? createSignatureDeclarationFromCallExpression(SyntaxKind.FunctionDeclaration, context, importAdder, info.call, idText(info.token), info.modifierFlags, info.parentDeclaration)
@@ -614,7 +614,7 @@ function addFunctionDeclaration(changes: textChanges.ChangeTracker, context: Cod
function addJsxAttributes(changes: textChanges.ChangeTracker, context: CodeFixContextBase, info: JsxAttributesInfo) {
const importAdder = createImportAdder(context.sourceFile, context.program, context.preferences, context.host);
const quotePreference = getQuotePreference(context.sourceFile, context.preferences);
const quotePreference = getQuotePreferenceFromFile(context.sourceFile, context.preferences);
const checker = context.program.getTypeChecker();
const jsxAttributesNode = info.parentDeclaration.attributes;
const hasSpreadAttribute = some(jsxAttributesNode.properties, isJsxSpreadAttribute);
@@ -634,7 +634,7 @@ function addJsxAttributes(changes: textChanges.ChangeTracker, context: CodeFixCo
function addObjectLiteralProperties(changes: textChanges.ChangeTracker, context: CodeFixContextBase, info: ObjectLiteralInfo) {
const importAdder = createImportAdder(context.sourceFile, context.program, context.preferences, context.host);
const quotePreference = getQuotePreference(context.sourceFile, context.preferences);
const quotePreference = getQuotePreferenceFromFile(context.sourceFile, context.preferences);
const target = getEmitScriptTarget(context.program.getCompilerOptions());
const checker = context.program.getTypeChecker();
const props = map(info.properties, prop => {

View File

@@ -8,7 +8,7 @@ import {
findAncestor,
getEmitModuleKind,
getNamespaceDeclarationNode,
getQuotePreference,
getQuotePreferenceFromFile,
getSourceFileOfNode,
getTokenAtPosition,
ImportDeclaration,
@@ -39,7 +39,7 @@ function getCodeFixesForImportDeclaration(context: CodeFixContext, node: ImportD
const variations: CodeFixAction[] = [];
// import Bluebird from "bluebird";
variations.push(createAction(context, sourceFile, node, makeImport(namespace.name, /*namedImports*/ undefined, node.moduleSpecifier, getQuotePreference(sourceFile, context.preferences))));
variations.push(createAction(context, sourceFile, node, makeImport(namespace.name, /*namedImports*/ undefined, node.moduleSpecifier, getQuotePreferenceFromFile(sourceFile, context.preferences))));
if (getEmitModuleKind(opts) === ModuleKind.CommonJS) {
// import Bluebird = require("bluebird");

View File

@@ -2,7 +2,7 @@ import {
cast,
Diagnostics,
factory,
getQuotePreference,
getQuotePreferenceFromFile,
getTokenAtPosition,
isPropertyAccessChain,
isPropertyAccessExpression,
@@ -37,7 +37,7 @@ registerCodeFix({
});
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: PropertyAccessExpression, preferences: UserPreferences): void {
const quotePreference = getQuotePreference(sourceFile, preferences);
const quotePreference = getQuotePreferenceFromFile(sourceFile, preferences);
const argumentsExpression = factory.createStringLiteral(node.name.text, quotePreference === QuotePreference.Single);
changes.replaceNode(
sourceFile,

View File

@@ -27,7 +27,7 @@ import {
getModuleSpecifierResolverHost,
getNameForExportedSymbol,
getNameOfDeclaration,
getQuotePreference,
getQuotePreferenceFromFile,
getSetAccessorValueParameter,
getSynthesizedDeepClone,
getTokenAtPosition,
@@ -205,7 +205,7 @@ export function addNewNodeForMemberSymbol(
const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration));
const optional = !!(symbol.flags & SymbolFlags.Optional);
const ambient = !!(enclosingDeclaration.flags & NodeFlags.Ambient) || isAmbient;
const quotePreference = getQuotePreference(sourceFile, preferences);
const quotePreference = getQuotePreferenceFromFile(sourceFile, preferences);
switch (kind) {
case SyntaxKind.PropertySignature:
@@ -467,7 +467,7 @@ export function createSignatureDeclarationFromCallExpression(
modifierFlags: ModifierFlags,
contextNode: Node
): MethodDeclaration | FunctionDeclaration | MethodSignature {
const quotePreference = getQuotePreference(context.sourceFile, context.preferences);
const quotePreference = getQuotePreferenceFromFile(context.sourceFile, context.preferences);
const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions());
const tracker = getNoopSymbolTrackerWithResolver(context);
const checker = context.program.getTypeChecker();

View File

@@ -31,6 +31,7 @@ import {
flatMapIterator,
forEachExternalModuleToImportFrom,
formatting,
FutureSourceFile,
getAllowSyntheticDefaultImports,
getBaseFileName,
getDefaultExportInfoWorker,
@@ -46,11 +47,13 @@ import {
getNodeId,
getQuoteFromPreference,
getQuotePreference,
getQuotePreferenceFromFile,
getSourceFileOfNode,
getSymbolId,
getTokenAtPosition,
getTypeKeywordOfTypeOnlyImport,
getUniqueSymbolId,
hasJSFileExtension,
hostGetCanonicalFileName,
Identifier,
ImportClause,
@@ -61,7 +64,6 @@ import {
ImportsNotUsedAsValues,
insertImports,
InternalSymbolName,
isExternalModule,
isExternalModuleReference,
isIdentifier,
isIdentifierPart,
@@ -208,7 +210,7 @@ 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[] = [];
@@ -232,7 +234,7 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
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 exportInfo = getAllExportInfoForSymbol(sourceFile.path, 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);
if (fix) {
@@ -346,14 +348,17 @@ function createImportAdderWorker(sourceFile: SourceFile, program: Program, useAu
}
function writeFixes(changeTracker: textChanges.ChangeTracker) {
const quotePreference = getQuotePreference(sourceFile, preferences);
const quotePreference = sourceFile.kind ? getQuotePreferenceFromFile(sourceFile, preferences) : getQuotePreference(preferences);
for (const fix of addToNamespace) {
Debug.assert(sourceFile.kind, "Cannot add to an existing import in a non-existent file.");
addNamespaceQualifier(changeTracker, sourceFile, fix);
}
for (const fix of importType) {
Debug.assert(sourceFile.kind, "Cannot add to an existing import in a non-existent file.");
addImportType(changeTracker, sourceFile, fix, quotePreference);
}
addToExisting.forEach(({ importClauseOrBindingPattern, defaultImport, namedImports }) => {
Debug.assert(sourceFile.kind, "Cannot add to an existing import in a non-existent file.");
doAddExistingFix(
changeTracker,
sourceFile,
@@ -509,14 +514,14 @@ export function getImportCompletionAction(
if (exportMapKey) {
// The new way: `exportMapKey` should be in the `data` of each auto-import completion entry and
// sent back when asking for details.
exportInfos = getExportInfoMap(sourceFile, host, program, preferences, cancellationToken).get(sourceFile.path, exportMapKey);
exportInfos = getExportInfoMap(sourceFile.path, host, program, preferences, cancellationToken).get(sourceFile.path, exportMapKey);
Debug.assertIsDefined(exportInfos, "Some exportInfo should match the specified exportMapKey");
}
else {
// The old way, kept alive for super old editors that don't give us `data` back.
exportInfos = pathIsBareSpecifier(stripQuotes(moduleSymbol.name))
? [getSingleExportInfoForSymbol(targetSymbol, symbolName, moduleSymbol, program, host)]
: getAllExportInfoForSymbol(sourceFile, targetSymbol, symbolName, moduleSymbol, isJsxTagName, program, host, preferences, cancellationToken);
: getAllExportInfoForSymbol(sourceFile.path, targetSymbol, symbolName, moduleSymbol, isJsxTagName, program, host, preferences, cancellationToken);
Debug.assertIsDefined(exportInfos, "Some exportInfo should match the specified symbol / moduleSymbol");
}
@@ -545,7 +550,7 @@ export function getPromoteTypeOnlyCompletionAction(sourceFile: SourceFile, symbo
return fix && codeFixActionToCodeAction(codeActionForFix({ host, formatContext, preferences }, sourceFile, symbolName, fix, includeSymbolNameInDescription, compilerOptions, preferences));
}
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);
}
@@ -554,10 +559,10 @@ 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(importingFilePath: Path, 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 => {
return getExportInfoMap(importingFilePath, host, program, preferences, cancellationToken)
.search(importingFilePath, preferCapitalized, name => name === symbolName, info => {
if (skipAlias(info[0].symbol, getChecker(info[0].isFromPackageJson)) === symbol && info.some(i => i.moduleSymbol === moduleSymbol || i.symbol.parent === moduleSymbol)) {
return info;
}
@@ -591,16 +596,16 @@ function getImportFixes(
isValidTypeOnlyUseSite: boolean,
useRequire: boolean,
program: Program,
sourceFile: SourceFile,
sourceFile: SourceFile | FutureSourceFile,
host: LanguageServiceHost,
preferences: UserPreferences,
importMap = createExistingImportMap(program.getTypeChecker(), sourceFile, program.getCompilerOptions()),
importMap = sourceFile.kind && createExistingImportMap(program.getTypeChecker(), sourceFile, program.getCompilerOptions()),
fromCacheOnly?: boolean,
): { computedWithoutCacheCount: number, fixes: readonly ImportFixWithModuleSpecifier[] } {
const checker = program.getTypeChecker();
const existingImports = flatMap(exportInfos, importMap.getImportsForExportInfo);
const useNamespace = usagePosition !== undefined && tryUseExistingNamespaceImport(existingImports, usagePosition);
const addToExisting = tryAddToExistingImport(existingImports, isValidTypeOnlyUseSite, checker, program.getCompilerOptions());
const existingImports = importMap && flatMap(exportInfos, importMap.getImportsForExportInfo);
const useNamespace = existingImports && usagePosition !== undefined && tryUseExistingNamespaceImport(existingImports, usagePosition);
const addToExisting = existingImports && tryAddToExistingImport(existingImports, isValidTypeOnlyUseSite, checker, program.getCompilerOptions());
if (addToExisting) {
// Don't bother providing an action to add a new import if we can add to an existing one.
return {
@@ -787,9 +792,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 (sourceFile.kind && !isSourceFileJS(sourceFile) || !sourceFile.kind && !hasJSFileExtension(sourceFile.fileName)) {
return false;
}
@@ -820,7 +825,7 @@ function createGetChecker(program: Program, host: LanguageServiceHost) {
function getNewImportFixes(
program: Program,
sourceFile: SourceFile,
sourceFile: SourceFile | FutureSourceFile,
usagePosition: number | undefined,
isValidTypeOnlyUseSite: boolean,
useRequire: boolean,
@@ -829,14 +834,14 @@ function getNewImportFixes(
preferences: UserPreferences,
fromCacheOnly?: boolean,
): { computedWithoutCacheCount: number, fixes: readonly (FixAddNewImport | FixAddJsdocTypeImport)[] } {
const isJs = isSourceFileJS(sourceFile);
const isJs = sourceFile.kind ? isSourceFileJS(sourceFile) : 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) => ({ moduleSpecifiers: moduleSpecifiers.tryGetModuleSpecifiersFromCache(moduleSymbol, sourceFile.path, moduleSpecifierResolutionHost, preferences), computedWithoutCache: false })
: (moduleSymbol: Symbol, checker: TypeChecker) => moduleSpecifiers.getModuleSpecifiersWithCacheInfo(moduleSymbol, checker, compilerOptions, sourceFile, moduleSpecifierResolutionHost, preferences);
let computedWithoutCacheCount = 0;
@@ -892,9 +897,9 @@ function getNewImportFixes(
function getFixesForAddImport(
exportInfos: readonly SymbolExportInfo[],
existingImports: readonly FixAddToExistingImportInfo[],
existingImports: readonly FixAddToExistingImportInfo[] | undefined,
program: Program,
sourceFile: SourceFile,
sourceFile: SourceFile | FutureSourceFile,
usagePosition: number | undefined,
isValidTypeOnlyUseSite: boolean,
useRequire: boolean,
@@ -958,7 +963,7 @@ 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 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) {
@@ -982,7 +987,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,
@@ -1003,7 +1008,7 @@ function compareModuleSpecifiers(
// This can produce false positives or negatives if re-exports cross into sibling directories
// (e.g. `export * from "../whatever"`) or are not named "index" (we don't even try to consider
// this if we're in a resolution mode where you can't drop trailing "/index" from paths).
function isFixPossiblyReExportingImportingFile(fix: ImportFixWithModuleSpecifier, importingFile: SourceFile, compilerOptions: CompilerOptions, toPath: (fileName: string) => Path): boolean {
function isFixPossiblyReExportingImportingFile(fix: ImportFixWithModuleSpecifier, importingFile: SourceFile | FutureSourceFile, compilerOptions: CompilerOptions, toPath: (fileName: string) => Path): boolean {
if (fix.isReExport &&
fix.exportInfo?.moduleFileName &&
getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.Node10 &&
@@ -1019,7 +1024,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;
@@ -1064,7 +1069,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;
@@ -1078,7 +1083,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;
@@ -1090,8 +1095,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 (importingFile.kind ? isInJSFile(importingFile) : hasJSFileExtension(importingFile.fileName)) {
return importingFile.externalModuleIndicator || forceImportKeyword ? ImportKind.Namespace : ImportKind.CommonJS;
}
return ImportKind.CommonJS;
case ModuleKind.System:
@@ -1206,9 +1211,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 = importingFile.kind ? isInJSFile(importingFile) : 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) {
@@ -1217,17 +1222,19 @@ 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) {
// `import foo` parses as an ImportEqualsDeclaration even though it could be an ImportDeclaration
if (isImportEqualsDeclaration(statement) && !nodeIsMissing(statement.moduleReference)) {
return ImportKind.CommonJS;
if (importingFile.kind) {
for (const statement of importingFile.statements) {
// `import foo` parses as an ImportEqualsDeclaration even though it could be an ImportDeclaration
if (isImportEqualsDeclaration(statement) && !nodeIsMissing(statement.moduleReference)) {
return ImportKind.CommonJS;
}
}
}
// 4. We have no precedent to go on, so just use a default import if
@@ -1243,7 +1250,7 @@ function codeActionForFix(context: textChanges.TextChangesContext, sourceFile: S
return createCodeFixAction(importFixName, changes, diag, importFixId, Diagnostics.Add_all_missing_imports);
}
function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile: SourceFile, symbolName: string, fix: ImportFix, includeSymbolNameInDescription: boolean, compilerOptions: CompilerOptions, preferences: UserPreferences): DiagnosticOrDiagnosticAndArguments {
const quotePreference = getQuotePreference(sourceFile, preferences);
const quotePreference = getQuotePreferenceFromFile(sourceFile, preferences);
switch (fix.kind) {
case ImportFixKind.UseNamespace:
addNamespaceQualifier(changes, sourceFile, fix);

View File

@@ -2,7 +2,7 @@ import {
AnyImportSyntax,
Diagnostics,
Expression,
getQuotePreference,
getQuotePreferenceFromFile,
getTokenAtPosition,
Identifier,
isExternalModuleReference,
@@ -57,5 +57,5 @@ function getInfo(sourceFile: SourceFile, pos: number): Info | undefined {
}
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, info: Info, preferences: UserPreferences): void {
changes.replaceNode(sourceFile, info.importNode, makeImport(info.name, /*namedImports*/ undefined, info.moduleSpecifier, getQuotePreference(sourceFile, preferences)));
changes.replaceNode(sourceFile, info.importNode, makeImport(info.name, /*namedImports*/ undefined, info.moduleSpecifier, getQuotePreferenceFromFile(sourceFile, preferences)));
}

View File

@@ -96,7 +96,7 @@ import {
getNewLineKind,
getNewLineOrDefaultFromHost,
getPropertyNameForPropertyNameNode,
getQuotePreference,
getQuotePreferenceFromFile,
getReplacementSpanForContextToken,
getRootDeclaration,
getSourceFileOfModule,
@@ -790,7 +790,7 @@ function continuePreviousIncompleteResponse(
const touchNode = getTouchingPropertyName(file, position);
const lowerCaseTokenText = location.text.toLowerCase();
const exportMap = getExportInfoMap(file, host, program, preferences, cancellationToken);
const exportMap = getExportInfoMap(file.path, host, program, preferences, cancellationToken);
const newEntries = resolvingModuleSpecifiers(
"continuePreviousIncompleteResponse",
host,
@@ -1105,7 +1105,7 @@ function getJSDocParamAnnotation(
const inferredType = checker.getTypeAtLocation(initializer.parent);
if (!(inferredType.flags & (TypeFlags.Any | TypeFlags.Void))) {
const sourceFile = initializer.getSourceFile();
const quotePreference = getQuotePreference(sourceFile, preferences);
const quotePreference = getQuotePreferenceFromFile(sourceFile, preferences);
const builderFlags = (quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : NodeBuilderFlags.None);
const typeNode = checker.typeToTypeNode(inferredType, findAncestor(initializer, isFunctionLike), builderFlags);
if (typeNode) {
@@ -1353,7 +1353,7 @@ function getExhaustiveCaseSnippets(
const tracker = newCaseClauseTracker(checker, clauses);
const target = getEmitScriptTarget(options);
const quotePreference = getQuotePreference(sourceFile, preferences);
const quotePreference = getQuotePreferenceFromFile(sourceFile, preferences);
const importAdder = codefix.createImportAdder(sourceFile, program, preferences, host);
const elements: Expression[] = [];
for (const type of switchType.types as LiteralType[]) {
@@ -2048,7 +2048,7 @@ function createObjectLiteralMethod(
const declaration = declarations[0];
const name = getSynthesizedDeepClone(getNameOfDeclaration(declaration), /*includeTrivia*/ false) as PropertyName;
const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration));
const quotePreference = getQuotePreference(sourceFile, preferences);
const quotePreference = getQuotePreferenceFromFile(sourceFile, preferences);
const builderFlags = NodeBuilderFlags.OmitThisParameter | (quotePreference === QuotePreference.Single ? NodeBuilderFlags.UseSingleQuotesForStringLiteralType : NodeBuilderFlags.None);
switch (declaration.kind) {
@@ -3747,7 +3747,7 @@ function getCompletionData(
"";
const moduleSpecifierCache = host.getModuleSpecifierCache?.();
const exportInfo = getExportInfoMap(sourceFile, host, program, preferences, cancellationToken);
const exportInfo = getExportInfoMap(sourceFile.path, host, program, preferences, cancellationToken);
const packageJsonAutoImportProvider = host.getPackageJsonAutoImportProvider?.();
const packageJsonFilter = detailsEntryId ? undefined : createPackageJsonImportFilter(sourceFile, preferences, host);
resolvingModuleSpecifiers(

View File

@@ -451,7 +451,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(importingFilePath: Path, 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
@@ -463,7 +463,7 @@ export function getExportInfoMap(importingFile: SourceFile, host: LanguageServic
getGlobalTypingsCacheLocation: () => host.getGlobalTypingsCacheLocation?.(),
});
if (cache.isUsableByFile(importingFile.path)) {
if (cache.isUsableByFile(importingFilePath)) {
host.log?.("getExportInfoMap: cache hit");
return cache;
}
@@ -481,7 +481,7 @@ export function getExportInfoMap(importingFile: SourceFile, host: LanguageServic
// can cause it to happen: see 'completionsImport_mergedReExport.ts'
if (defaultInfo && isImportableSymbol(defaultInfo.symbol, checker)) {
cache.add(
importingFile.path,
importingFilePath,
defaultInfo.symbol,
defaultInfo.exportKind === ExportKind.Default ? InternalSymbolName.Default : InternalSymbolName.ExportEquals,
moduleSymbol,
@@ -493,7 +493,7 @@ export function getExportInfoMap(importingFile: SourceFile, host: LanguageServic
checker.forEachExportAndPropertyOfModule(moduleSymbol, (exported, key) => {
if (exported !== defaultInfo?.symbol && isImportableSymbol(exported, checker) && addToSeen(seenExports, key)) {
cache.add(
importingFile.path,
importingFilePath,
exported,
key,
moduleSymbol,

View File

@@ -46,7 +46,7 @@ import {
getLocaleSpecificMessage,
getModifiers,
getPropertySymbolFromBindingElement,
getQuotePreference,
getQuotePreferenceFromFile,
getRangesWhere,
getRefactorContextSpan,
getRelativePathFromFile,
@@ -280,7 +280,7 @@ function getNewStatementsAndRemoveFromOldFile(
}
const useEsModuleSyntax = !!oldFile.externalModuleIndicator;
const quotePreference = getQuotePreference(oldFile, preferences);
const quotePreference = getQuotePreferenceFromFile(oldFile, preferences);
const importsFromNewFile = createOldFileImportsFromNewFile(oldFile, usage.oldFileImportsFromNewFile, newFilename, program, host, useEsModuleSyntax, quotePreference);
if (importsFromNewFile) {
insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true, preferences);

View File

@@ -23,6 +23,7 @@ import {
DeclarationStatement,
EmitHint,
EmitTextWriter,
emptyArray,
endsWith,
Expression,
factory,
@@ -40,6 +41,7 @@ import {
formatting,
FunctionDeclaration,
FunctionExpression,
FutureSourceFile,
getAncestor,
getFirstNonSpaceCharacterPosition,
getFormatCodeSettingsForWriting,
@@ -337,6 +339,12 @@ interface ChangeText extends BaseChange {
readonly text: string;
}
interface NewFileInsertion {
readonly fileName: string;
readonly oldFile?: SourceFile;
readonly statements: readonly (Statement | SyntaxKind.NewLineTrivia)[];
}
function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd): TextRange {
return { pos: getAdjustedStartPosition(sourceFile, startNode, options), end: getAdjustedEndPosition(sourceFile, endNode, options) };
}
@@ -480,7 +488,7 @@ export function isThisTypeAnnotatable(containingFunction: SignatureDeclaration):
/** @internal */
export class ChangeTracker {
private readonly changes: Change[] = [];
private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: readonly (Statement | SyntaxKind.NewLineTrivia)[] }[] = [];
private newFileChanges?: NewFileInsertion[];
private readonly classesWithNodesInsertedAtStart = new Map<number, { readonly node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, readonly sourceFile: SourceFile }>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray<TypeParameterDeclaration> }[] = [];
@@ -600,28 +608,42 @@ export class ChangeTracker {
this.replaceRangeWithNodes(sourceFile, createRange(pos), newNodes, options);
}
public insertNodeAtTopOfFile(sourceFile: SourceFile, newNode: Statement, blankLineBetween: boolean): void {
public insertNodeAtTopOfFile(sourceFile: SourceFile | FutureSourceFile, newNode: Statement, blankLineBetween: boolean): void {
this.insertAtTopOfFile(sourceFile, newNode, blankLineBetween);
}
public insertNodesAtTopOfFile(sourceFile: SourceFile, newNodes: readonly Statement[], blankLineBetween: boolean): void {
public insertNodesAtTopOfFile(sourceFile: SourceFile | FutureSourceFile, newNodes: readonly Statement[], blankLineBetween: boolean): void {
this.insertAtTopOfFile(sourceFile, newNodes, blankLineBetween);
}
private insertAtTopOfFile(sourceFile: SourceFile, insert: Statement | readonly Statement[], blankLineBetween: boolean): void {
const pos = getInsertionPositionAtSourceFileTop(sourceFile);
private insertAtTopOfFile(sourceFile: SourceFile | FutureSourceFile, insert: Statement | readonly Statement[], blankLineBetween: boolean): void {
const pos = sourceFile.kind ? getInsertionPositionAtSourceFileTop(sourceFile) : 0;
const options = {
prefix: pos === 0 ? undefined : this.newLineCharacter,
suffix: (isLineBreak(sourceFile.text.charCodeAt(pos)) ? "" : this.newLineCharacter) + (blankLineBetween ? this.newLineCharacter : ""),
suffix: (sourceFile.kind && isLineBreak(sourceFile.text.charCodeAt(pos)) ? "" : this.newLineCharacter) + (blankLineBetween ? this.newLineCharacter : ""),
};
if (isArray(insert)) {
this.insertNodesAt(sourceFile, pos, insert, options);
if (sourceFile.kind) {
this.insertNodesAt(sourceFile, pos, insert, options);
}
else {
this.insertStatementsInNewFile(sourceFile.fileName, insert);
}
}
else {
this.insertNodeAt(sourceFile, pos, insert, options);
if (sourceFile.kind) {
this.insertNodeAt(sourceFile, pos, insert, options);
}
else {
this.insertStatementsInNewFile(sourceFile.fileName, [insert]);
}
}
}
private insertStatementsInNewFile(fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], oldFile?: SourceFile): void {
(this.newFileChanges ??= []).push({ fileName, statements, oldFile });
}
public insertFirstParameter(sourceFile: SourceFile, parameters: NodeArray<ParameterDeclaration>, newParam: ParameterDeclaration): void {
const p0 = firstOrUndefined(parameters);
if (p0) {
@@ -1128,14 +1150,14 @@ export class ChangeTracker {
this.finishDeleteDeclarations();
this.finishClassesWithNodesInsertedAtStart();
const changes = changesToText.getTextChangesFromChanges(this.changes, this.newLineCharacter, this.formatContext, validate);
for (const { oldFile, fileName, statements } of this.newFiles) {
for (const { oldFile, fileName, statements } of this.newFileChanges ?? emptyArray) {
changes.push(changesToText.newFileChanges(oldFile, fileName, statements, this.newLineCharacter, this.formatContext));
}
return changes;
}
public createNewFile(oldFile: SourceFile | undefined, fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[]): void {
this.newFiles.push({ oldFile, fileName, statements });
this.insertStatementsInNewFile(fileName, statements, oldFile);
}
}

View File

@@ -85,11 +85,13 @@ import {
FunctionDeclaration,
FunctionExpression,
FunctionLikeDeclaration,
FutureSourceFile,
getAssignmentDeclarationKind,
getCombinedNodeFlagsAlwaysIncludeJSDoc,
getDirectoryPath,
getEmitScriptTarget,
getExternalModuleImportEqualsDeclarationExpression,
getImpliedNodeFormatForFile,
getIndentString,
getJSDocEnumTag,
getLastChild,
@@ -267,6 +269,8 @@ import {
ModifierFlags,
ModuleDeclaration,
ModuleInstanceState,
ModuleKind,
ModuleResolutionHost,
ModuleResolutionKind,
ModuleSpecifierResolutionHost,
moduleSpecifiers,
@@ -350,6 +354,7 @@ import {
textSpanEnd,
Token,
tokenToString,
toPath,
tryCast,
Type,
TypeChecker,
@@ -2472,9 +2477,14 @@ export function quotePreferenceFromString(str: StringLiteral, sourceFile: Source
}
/** @internal */
export function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference {
export function getQuotePreference(preferences: UserPreferences): QuotePreference {
return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double;
}
/** @internal */
export function getQuotePreferenceFromFile(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference {
if (preferences.quotePreference && preferences.quotePreference !== "auto") {
return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double;
return getQuotePreference(preferences);
}
else {
// ignore synthetic import added when importHelpers: true
@@ -2561,17 +2571,18 @@ 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 existingImportStatements = sourceFile.kind ? filter(sourceFile.statements, importKindPredicate) : undefined;
let sortKind = isArray(imports) ? OrganizeImports.detectImportDeclarationSorting(imports, preferences) : SortKind.Both;
const comparer = OrganizeImports.getOrganizeImportsComparer(preferences, sortKind === SortKind.CaseInsensitive);
const sortedNewImports = isArray(imports) ? stableSort(imports, (a, b) => OrganizeImports.compareImportsOrRequireStatements(a, b, comparer)) : [imports];
if (!existingImportStatements.length) {
if (!existingImportStatements?.length) {
changes.insertNodesAtTopOfFile(sourceFile, sortedNewImports, blankLineBetween);
}
else if (existingImportStatements && (sortKind = OrganizeImports.detectImportDeclarationSorting(existingImportStatements, preferences))) {
Debug.assert(sourceFile.kind, "Cannot have existing import statements in a non-existent source file.");
const comparer = OrganizeImports.getOrganizeImportsComparer(preferences, sortKind === SortKind.CaseInsensitive);
for (const newImport of sortedNewImports) {
const insertionIndex = OrganizeImports.getImportDeclarationInsertionIndex(existingImportStatements, newImport, comparer);
@@ -2590,6 +2601,7 @@ export function insertImports(changes: textChanges.ChangeTracker, sourceFile: So
else {
const lastExistingImport = lastOrUndefined(existingImportStatements);
if (lastExistingImport) {
Debug.assert(sourceFile.kind, "Cannot have existing import statements in a non-existent source file.");
changes.insertNodesAfter(sourceFile, lastExistingImport, sortedNewImports);
}
else {
@@ -3322,7 +3334,7 @@ export function getContextualTypeFromParent(node: Expression, checker: TypeCheck
/** @internal */
export function quote(sourceFile: SourceFile, preferences: UserPreferences, text: string): string {
// Editors can pass in undefined or empty string - we want to infer the preference in those cases.
const quotePreference = getQuotePreference(sourceFile, preferences);
const quotePreference = getQuotePreferenceFromFile(sourceFile, preferences);
const quoted = JSON.stringify(text);
return quotePreference === QuotePreference.Single ? `'${stripQuotes(quoted).replace(/'/g, "\\'").replace(/\\"/g, '"')}'` : quoted;
}
@@ -3683,7 +3695,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);
@@ -3783,7 +3795,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 (fromFile.kind && isSourceFileJS(fromFile) && JsTyping.nodeCoreModules.has(moduleSpecifier)) {
if (usesNodeCoreModules === undefined) {
usesNodeCoreModules = consumesNodeCoreModules(fromFile);
}
@@ -4041,8 +4053,8 @@ export function isDeprecatedDeclaration(decl: Declaration) {
}
/** @internal */
export function shouldUseUriStyleNodeCoreModules(file: SourceFile, program: Program): boolean {
const decisionFromFile = firstDefined(file.imports, node => {
export function shouldUseUriStyleNodeCoreModules(file: SourceFile | FutureSourceFile, program: Program): boolean {
const decisionFromFile = file.kind && firstDefined(file.imports, node => {
if (JsTyping.nodeCoreModules.has(node.text)) {
return startsWith(node.text, "node:");
}
@@ -4064,6 +4076,27 @@ export function diagnosticToString(diag: DiagnosticOrDiagnosticAndArguments): st
: getLocaleSpecificMessage(diag);
}
/** @internal */
export function createFutureSourceFile(
fileName: string,
program: Program,
host: ModuleResolutionHost,
moduleSyntax?: ModuleKind.CommonJS | ModuleKind.ESNext,
): FutureSourceFile {
const path = toPath(fileName, /*basePath*/ undefined, program.getCanonicalFileName);
const impliedNodeFormat = getImpliedNodeFormatForFile(path, program.getPackageJsonInfoCache?.(), host, program.getCompilerOptions());
if (moduleSyntax === ModuleKind.CommonJS && impliedNodeFormat === ModuleKind.ESNext) {
Debug.fail("ES Modules cannot contain CommonJS syntax");
}
return {
fileName,
path,
commonJsModuleIndicator: moduleSyntax === ModuleKind.CommonJS,
externalModuleIndicator: moduleSyntax === ModuleKind.ESNext,
impliedNodeFormat,
};
}
/**
* Get format code settings for a code writing context (e.g. when formatting text changes or completions code).
*