mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Detect preference for Unode:-prefixed node core modules (#45080)
This commit is contained in:
@@ -886,6 +886,7 @@ namespace ts {
|
||||
let sourceFileToPackageName = new Map<Path, string>();
|
||||
// Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it.
|
||||
let redirectTargetsMap = createMultiMap<Path, string>();
|
||||
let usesUriStyleNodeCoreModules = false;
|
||||
|
||||
/**
|
||||
* map with
|
||||
@@ -1087,6 +1088,7 @@ namespace ts {
|
||||
getLibFileFromReference,
|
||||
sourceFileToPackageName,
|
||||
redirectTargetsMap,
|
||||
usesUriStyleNodeCoreModules,
|
||||
isEmittedFile,
|
||||
getConfigFileParsingDiagnostics,
|
||||
getResolvedModuleWithFailedLookupLocationsFromCache,
|
||||
@@ -1635,6 +1637,7 @@ namespace ts {
|
||||
|
||||
sourceFileToPackageName = oldProgram.sourceFileToPackageName;
|
||||
redirectTargetsMap = oldProgram.redirectTargetsMap;
|
||||
usesUriStyleNodeCoreModules = oldProgram.usesUriStyleNodeCoreModules;
|
||||
|
||||
return StructureIsReused.Completely;
|
||||
}
|
||||
@@ -2327,6 +2330,9 @@ namespace ts {
|
||||
// only through top - level external module names. Relative external module names are not permitted.
|
||||
if (moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text && (!inAmbientModule || !isExternalModuleNameRelative(moduleNameExpr.text))) {
|
||||
imports = append(imports, moduleNameExpr);
|
||||
if (!usesUriStyleNodeCoreModules && currentNodeModulesDepth === 0 && !file.isDeclarationFile) {
|
||||
usesUriStyleNodeCoreModules = startsWith(moduleNameExpr.text, "node:");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isModuleDeclaration(node)) {
|
||||
|
||||
@@ -3964,6 +3964,8 @@ namespace ts {
|
||||
/* @internal */ sourceFileToPackageName: ESMap<Path, string>;
|
||||
/** Set of all source files that some other source file redirects to. */
|
||||
/* @internal */ redirectTargetsMap: MultiMap<Path, string>;
|
||||
/** Whether any (non-external, non-declaration) source files use `node:`-prefixed module specifiers. */
|
||||
/* @internal */ readonly usesUriStyleNodeCoreModules: boolean;
|
||||
/** Is the file emitted file */
|
||||
/* @internal */ isEmittedFile(file: string): boolean;
|
||||
/* @internal */ getFileIncludeReasons(): MultiMap<Path, FileIncludeReason>;
|
||||
|
||||
@@ -1409,7 +1409,7 @@ namespace FourSlashInterface {
|
||||
"await",
|
||||
].map(keywordEntry);
|
||||
|
||||
export const undefinedVarEntry: ExpectedCompletionEntry = {
|
||||
export const undefinedVarEntry: ExpectedCompletionEntryObject = {
|
||||
name: "undefined",
|
||||
kind: "var",
|
||||
sortText: SortText.GlobalsOrKeywords
|
||||
@@ -1568,11 +1568,14 @@ namespace FourSlashInterface {
|
||||
];
|
||||
|
||||
export function globalsPlus(plus: readonly ExpectedCompletionEntry[]): readonly ExpectedCompletionEntry[] {
|
||||
const firstEntry = plus[0];
|
||||
const afterUndefined = typeof firstEntry !== "string" && firstEntry.sortText! > undefinedVarEntry.sortText!;
|
||||
return [
|
||||
globalThisEntry,
|
||||
...globalsVars,
|
||||
...plus,
|
||||
...afterUndefined ? ts.emptyArray : plus,
|
||||
undefinedVarEntry,
|
||||
...afterUndefined ? plus : ts.emptyArray,
|
||||
...globalKeywords];
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,9 @@ namespace ts.JsTyping {
|
||||
return availableVersion.compareTo(cachedTyping.version) <= 0;
|
||||
}
|
||||
|
||||
export const nodeCoreModuleList: readonly string[] = [
|
||||
const unprefixedNodeCoreModuleList = [
|
||||
"assert",
|
||||
"assert/strict",
|
||||
"async_hooks",
|
||||
"buffer",
|
||||
"child_process",
|
||||
@@ -39,14 +40,18 @@ namespace ts.JsTyping {
|
||||
"constants",
|
||||
"crypto",
|
||||
"dgram",
|
||||
"diagnostics_channel",
|
||||
"dns",
|
||||
"dns/promises",
|
||||
"domain",
|
||||
"events",
|
||||
"fs",
|
||||
"fs/promises",
|
||||
"http",
|
||||
"https",
|
||||
"http2",
|
||||
"inspector",
|
||||
"module",
|
||||
"net",
|
||||
"os",
|
||||
"path",
|
||||
@@ -57,17 +62,27 @@ namespace ts.JsTyping {
|
||||
"readline",
|
||||
"repl",
|
||||
"stream",
|
||||
"stream/promises",
|
||||
"string_decoder",
|
||||
"timers",
|
||||
"timers/promises",
|
||||
"tls",
|
||||
"trace_events",
|
||||
"tty",
|
||||
"url",
|
||||
"util",
|
||||
"util/types",
|
||||
"v8",
|
||||
"vm",
|
||||
"wasi",
|
||||
"worker_threads",
|
||||
"zlib"
|
||||
];
|
||||
|
||||
export const prefixedNodeCoreModuleList = unprefixedNodeCoreModuleList.map(name => `node:${name}`);
|
||||
|
||||
export const nodeCoreModuleList: readonly string[] = [...unprefixedNodeCoreModuleList, ...prefixedNodeCoreModuleList];
|
||||
|
||||
export const nodeCoreModules = new Set(nodeCoreModuleList);
|
||||
|
||||
export function nonRelativeModuleNameForTypingCache(moduleName: string) {
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace ts.codefix {
|
||||
|
||||
function getImportFixForSymbol(sourceFile: SourceFile, exportInfos: readonly SymbolExportInfo[], moduleSymbol: Symbol, symbolName: string, program: Program, position: number | undefined, preferTypeOnlyImport: boolean, useRequire: boolean, host: LanguageServiceHost, preferences: UserPreferences) {
|
||||
Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol), "Some exportInfo should match the specified moduleSymbol");
|
||||
return getBestFix(getImportFixes(exportInfos, symbolName, position, preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences), sourceFile, host, preferences);
|
||||
return getBestFix(getImportFixes(exportInfos, symbolName, position, preferTypeOnlyImport, useRequire, program, sourceFile, host, preferences), sourceFile, program, host, preferences);
|
||||
}
|
||||
|
||||
function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction {
|
||||
@@ -290,7 +290,7 @@ namespace ts.codefix {
|
||||
host,
|
||||
preferences,
|
||||
fromCacheOnly);
|
||||
const result = getBestFix(fixes, importingFile, host, preferences);
|
||||
const result = getBestFix(fixes, importingFile, program, host, preferences);
|
||||
return result && { ...result, computedWithoutCacheCount };
|
||||
}
|
||||
|
||||
@@ -506,15 +506,15 @@ namespace ts.codefix {
|
||||
const info = errorCode === Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code
|
||||
? getFixesInfoForUMDImport(context, symbolToken)
|
||||
: isIdentifier(symbolToken) ? getFixesInfoForNonUMDImport(context, symbolToken, useAutoImportProvider) : undefined;
|
||||
return info && { ...info, fixes: sortFixes(info.fixes, context.sourceFile, context.host, context.preferences) };
|
||||
return info && { ...info, fixes: sortFixes(info.fixes, context.sourceFile, context.program, context.host, context.preferences) };
|
||||
}
|
||||
|
||||
function sortFixes(fixes: readonly ImportFix[], sourceFile: SourceFile, host: LanguageServiceHost, preferences: UserPreferences): readonly ImportFix[] {
|
||||
function sortFixes(fixes: readonly ImportFix[], sourceFile: SourceFile, program: Program, host: LanguageServiceHost, preferences: UserPreferences): readonly ImportFix[] {
|
||||
const { allowsImportingSpecifier } = createPackageJsonImportFilter(sourceFile, preferences, host);
|
||||
return sort(fixes, (a, b) => compareValues(a.kind, b.kind) || compareModuleSpecifiers(a, b, allowsImportingSpecifier));
|
||||
return sort(fixes, (a, b) => compareValues(a.kind, b.kind) || compareModuleSpecifiers(a, b, sourceFile, program, allowsImportingSpecifier));
|
||||
}
|
||||
|
||||
function getBestFix<T extends ImportFix>(fixes: readonly T[], sourceFile: SourceFile, host: LanguageServiceHost, preferences: UserPreferences): T | undefined {
|
||||
function getBestFix<T extends ImportFix>(fixes: readonly T[], sourceFile: SourceFile, program: Program, host: LanguageServiceHost, preferences: UserPreferences): T | 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) {
|
||||
@@ -522,18 +522,25 @@ namespace ts.codefix {
|
||||
}
|
||||
const { allowsImportingSpecifier } = createPackageJsonImportFilter(sourceFile, preferences, host);
|
||||
return fixes.reduce((best, fix) =>
|
||||
compareModuleSpecifiers(fix, best, allowsImportingSpecifier) === Comparison.LessThan ? fix : best
|
||||
compareModuleSpecifiers(fix, best, sourceFile, program, allowsImportingSpecifier) === Comparison.LessThan ? fix : best
|
||||
);
|
||||
}
|
||||
|
||||
function compareModuleSpecifiers(a: ImportFix, b: ImportFix, allowsImportingSpecifier: (specifier: string) => boolean): Comparison {
|
||||
function compareModuleSpecifiers(a: ImportFix, b: ImportFix, importingFile: SourceFile, program: Program, allowsImportingSpecifier: (specifier: string) => boolean): Comparison {
|
||||
if (a.kind !== ImportFixKind.UseNamespace && b.kind !== ImportFixKind.UseNamespace) {
|
||||
return compareBooleans(allowsImportingSpecifier(a.moduleSpecifier), allowsImportingSpecifier(b.moduleSpecifier))
|
||||
|| compareNodeCoreModuleSpecifiers(a.moduleSpecifier, b.moduleSpecifier, importingFile, program)
|
||||
|| compareNumberOfDirectorySeparators(a.moduleSpecifier, b.moduleSpecifier);
|
||||
}
|
||||
return Comparison.EqualTo;
|
||||
}
|
||||
|
||||
function compareNodeCoreModuleSpecifiers(a: string, b: string, importingFile: SourceFile, 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;
|
||||
}
|
||||
|
||||
function getFixesInfoForUMDImport({ sourceFile, program, host, preferences }: CodeFixContextBase, token: Node): FixesInfo | undefined {
|
||||
const checker = program.getTypeChecker();
|
||||
const umdSymbol = getUmdSymbol(token, checker);
|
||||
|
||||
@@ -1959,6 +1959,10 @@ namespace ts.Completions {
|
||||
function isImportableExportInfo(info: SymbolExportInfo) {
|
||||
const moduleFile = tryCast(info.moduleSymbol.valueDeclaration, isSourceFile);
|
||||
if (!moduleFile) {
|
||||
const moduleName = stripQuotes(info.moduleSymbol.name);
|
||||
if (JsTyping.nodeCoreModules.has(moduleName) && startsWith(moduleName, "node:") !== shouldUseUriStyleNodeCoreModules(sourceFile, program)) {
|
||||
return false;
|
||||
}
|
||||
return packageJsonFilter
|
||||
? packageJsonFilter.allowsImportingAmbientModule(info.moduleSymbol, getModuleSpecifierResolutionHost(info.isFromPackageJson))
|
||||
: true;
|
||||
|
||||
@@ -3176,5 +3176,14 @@ namespace ts {
|
||||
return !!(getCombinedNodeFlagsAlwaysIncludeJSDoc(decl) & ModifierFlags.Deprecated);
|
||||
}
|
||||
|
||||
export function shouldUseUriStyleNodeCoreModules(file: SourceFile, program: Program): boolean {
|
||||
const decisionFromFile = firstDefined(file.imports, node => {
|
||||
if (JsTyping.nodeCoreModules.has(node.text)) {
|
||||
return startsWith(node.text, "node:");
|
||||
}
|
||||
});
|
||||
return decisionFromFile ?? program.usesUriStyleNodeCoreModules;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: commonjs
|
||||
|
||||
// @Filename: /node_modules/@types/node/index.d.ts
|
||||
//// declare module "fs" { function writeFile(): void }
|
||||
//// declare module "fs/promises" { function writeFile(): Promise<void> }
|
||||
//// declare module "node:fs" { export * from "fs"; }
|
||||
//// declare module "node:fs/promises" { export * from "fs/promises"; }
|
||||
|
||||
// @Filename: /index.ts
|
||||
//// write/**/
|
||||
|
||||
verify.completions({
|
||||
marker: "",
|
||||
exact: completion.globalsPlus([{
|
||||
name: "writeFile",
|
||||
source: "fs",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}, {
|
||||
name: "writeFile",
|
||||
source: "fs/promises",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}]),
|
||||
preferences: {
|
||||
includeCompletionsForModuleExports: true,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: commonjs
|
||||
|
||||
// @Filename: /node_modules/@types/node/index.d.ts
|
||||
//// declare module "fs" { function writeFile(): void }
|
||||
//// declare module "fs/promises" { function writeFile(): Promise<void> }
|
||||
//// declare module "node:fs" { export * from "fs"; }
|
||||
//// declare module "node:fs/promises" { export * from "fs/promises"; }
|
||||
|
||||
// @Filename: /other.ts
|
||||
//// import "node:fs/promises";
|
||||
|
||||
// @Filename: /index.ts
|
||||
//// write/**/
|
||||
|
||||
verify.completions({
|
||||
marker: "",
|
||||
exact: completion.globalsPlus([{
|
||||
name: "writeFile",
|
||||
source: "node:fs",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}, {
|
||||
name: "writeFile",
|
||||
source: "node:fs/promises",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}]),
|
||||
preferences: {
|
||||
includeCompletionsForModuleExports: true,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: commonjs
|
||||
|
||||
// @Filename: /node_modules/@types/node/index.d.ts
|
||||
//// declare module "path" { function join(...segments: readonly string[]): string; }
|
||||
//// declare module "node:path" { export * from "path"; }
|
||||
//// declare module "fs" { function writeFile(): void }
|
||||
//// declare module "fs/promises" { function writeFile(): Promise<void> }
|
||||
//// declare module "node:fs" { export * from "fs"; }
|
||||
//// declare module "node:fs/promises" { export * from "fs/promises"; }
|
||||
|
||||
// @Filename: /other.ts
|
||||
//// import "node:fs/promises";
|
||||
|
||||
// @Filename: /noPrefix.ts
|
||||
//// import "path";
|
||||
//// write/*noPrefix*/
|
||||
|
||||
// @Filename: /prefix.ts
|
||||
//// import "node:path";
|
||||
//// write/*prefix*/
|
||||
|
||||
// @Filename: /mixed1.ts
|
||||
//// import "path";
|
||||
//// import "node:path";
|
||||
//// write/*mixed1*/
|
||||
|
||||
// @Filename: /mixed2.ts
|
||||
//// import "node:path";
|
||||
//// import "path";
|
||||
//// write/*mixed2*/
|
||||
|
||||
verify.completions({
|
||||
marker: "noPrefix",
|
||||
exact: completion.globalsPlus([{
|
||||
name: "writeFile",
|
||||
source: "fs",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}, {
|
||||
name: "writeFile",
|
||||
source: "fs/promises",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}]),
|
||||
preferences: {
|
||||
includeCompletionsForModuleExports: true,
|
||||
},
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "prefix",
|
||||
exact: completion.globalsPlus([{
|
||||
name: "writeFile",
|
||||
source: "node:fs",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}, {
|
||||
name: "writeFile",
|
||||
source: "node:fs/promises",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}]),
|
||||
preferences: {
|
||||
includeCompletionsForModuleExports: true,
|
||||
},
|
||||
});
|
||||
|
||||
// We're doing as little work as possible to decide which module specifiers
|
||||
// to use, so we just take the *first* recognized node core module in the file
|
||||
// and copy its style.
|
||||
|
||||
verify.completions({
|
||||
marker: "mixed1",
|
||||
exact: completion.globalsPlus([{
|
||||
name: "writeFile",
|
||||
source: "fs",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}, {
|
||||
name: "writeFile",
|
||||
source: "fs/promises",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}]),
|
||||
preferences: {
|
||||
includeCompletionsForModuleExports: true,
|
||||
},
|
||||
});
|
||||
|
||||
verify.completions({
|
||||
marker: "mixed2",
|
||||
exact: completion.globalsPlus([{
|
||||
name: "writeFile",
|
||||
source: "node:fs",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}, {
|
||||
name: "writeFile",
|
||||
source: "node:fs/promises",
|
||||
hasAction: true,
|
||||
sortText: completion.SortText.AutoImportSuggestions
|
||||
}]),
|
||||
preferences: {
|
||||
includeCompletionsForModuleExports: true,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: commonjs
|
||||
|
||||
// @Filename: /node_modules/@types/node/index.d.ts
|
||||
//// declare module "fs" { function writeFile(): void }
|
||||
//// declare module "fs/promises" { function writeFile(): Promise<void> }
|
||||
//// declare module "node:fs" { export * from "fs"; }
|
||||
//// declare module "node:fs/promises" { export * from "fs/promises"; }
|
||||
|
||||
// @Filename: /index.ts
|
||||
//// writeFile/**/
|
||||
|
||||
verify.importFixModuleSpecifiers("", ["fs", "fs/promises", "node:fs", "node:fs/promises"]);
|
||||
@@ -0,0 +1,29 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: commonjs
|
||||
|
||||
// @Filename: /node_modules/@types/node/index.d.ts
|
||||
//// declare module "fs" { function writeFile(): void }
|
||||
//// declare module "fs/promises" { function writeFile(): Promise<void> }
|
||||
//// declare module "node:fs" { export * from "fs"; }
|
||||
//// declare module "node:fs/promises" { export * from "fs/promises"; }
|
||||
|
||||
// @Filename: /other.ts
|
||||
//// import "node:fs/promises";
|
||||
|
||||
// @Filename: /index.ts
|
||||
//// writeFile/**/
|
||||
|
||||
verify.importFixModuleSpecifiers("", ["node:fs", "node:fs/promises", "fs", "fs/promises"]);
|
||||
|
||||
goTo.file("/other.ts");
|
||||
edit.replaceLine(0, "\n");
|
||||
|
||||
goTo.file("/index.ts");
|
||||
verify.importFixModuleSpecifiers("", ["fs", "fs/promises", "node:fs", "node:fs/promises"]);
|
||||
|
||||
goTo.file("/other.ts");
|
||||
edit.replaceLine(0, `import "node:fs/promises";\n`);
|
||||
|
||||
goTo.file("/index.ts");
|
||||
verify.importFixModuleSpecifiers("", ["node:fs", "node:fs/promises", "fs", "fs/promises"]);
|
||||
@@ -0,0 +1,42 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @module: commonjs
|
||||
|
||||
// @Filename: /node_modules/@types/node/index.d.ts
|
||||
//// declare module "path" { function join(...segments: readonly string[]): string; }
|
||||
//// declare module "node:path" { export * from "path"; }
|
||||
//// declare module "fs" { function writeFile(): void }
|
||||
//// declare module "fs/promises" { function writeFile(): Promise<void> }
|
||||
//// declare module "node:fs" { export * from "fs"; }
|
||||
//// declare module "node:fs/promises" { export * from "fs/promises"; }
|
||||
|
||||
// @Filename: /other.ts
|
||||
//// import "node:fs/promises";
|
||||
|
||||
// @Filename: /noPrefix.ts
|
||||
//// import "path";
|
||||
//// writeFile/*noPrefix*/
|
||||
|
||||
// @Filename: /prefix.ts
|
||||
//// import "node:path";
|
||||
//// writeFile/*prefix*/
|
||||
|
||||
// @Filename: /mixed1.ts
|
||||
//// import "path";
|
||||
//// import "node:path";
|
||||
//// writeFile/*mixed1*/
|
||||
|
||||
// @Filename: /mixed2.ts
|
||||
//// import "node:path";
|
||||
//// import "path";
|
||||
//// writeFile/*mixed2*/
|
||||
|
||||
verify.importFixModuleSpecifiers("noPrefix", ["fs", "fs/promises", "node:fs", "node:fs/promises"]);
|
||||
verify.importFixModuleSpecifiers("prefix", ["node:fs", "node:fs/promises", "fs", "fs/promises"]);
|
||||
|
||||
// We're doing as little work as possible to decide which module specifiers
|
||||
// to use, so we just take the *first* recognized node core module in the file
|
||||
// and copy its style.
|
||||
|
||||
verify.importFixModuleSpecifiers("mixed1", ["fs", "fs/promises", "node:fs", "node:fs/promises"]);
|
||||
verify.importFixModuleSpecifiers("mixed2", ["node:fs", "node:fs/promises", "fs", "fs/promises"]);
|
||||
Reference in New Issue
Block a user