Detect preference for Unode:-prefixed node core modules (#45080)

This commit is contained in:
Andrew Branch
2021-07-19 09:56:24 -07:00
committed by GitHub
parent ddbd829ea1
commit 36225c3260
13 changed files with 313 additions and 11 deletions
+6
View File
@@ -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)) {
+2
View File
@@ -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>;
+5 -2
View File
@@ -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];
}
+16 -1
View File
@@ -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) {
+15 -8
View File
@@ -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);
+4
View File
@@ -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;
+9
View File
@@ -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"]);