Simplify handling of node:-prefixed modules in auto-imports (#59702)

This commit is contained in:
Andrew Branch
2024-08-21 16:43:21 -07:00
committed by GitHub
parent f6ec916313
commit a5eec2485f
16 changed files with 943 additions and 191 deletions
@@ -11,8 +11,8 @@ import {
InstallPackageAction,
isExternalModuleNameRelative,
isStringLiteral,
JsTyping,
LanguageServiceHost,
nodeCoreModules,
parsePackageName,
SourceFile,
tryCast,
@@ -71,6 +71,6 @@ function tryGetImportedPackageName(sourceFile: SourceFile, pos: number): string
function getTypesPackageNameToInstall(packageName: string, host: LanguageServiceHost, diagCode: number): string | undefined {
return diagCode === errorCodeCannotFindModule
? (JsTyping.nodeCoreModules.has(packageName) ? "@types/node" : undefined)
? (nodeCoreModules.has(packageName) ? "@types/node" : undefined)
: (host.isKnownTypesPackageName?.(packageName) ? getTypesPackageName(packageName) : undefined);
}
+2 -6
View File
@@ -35,7 +35,6 @@ import {
ExportKind,
ExportMapInfoKey,
factory,
fileContainsPackageImport,
findAncestor,
first,
firstDefined,
@@ -84,7 +83,7 @@ import {
isExternalModuleReference,
isFullSourceFile,
isIdentifier,
isImportableFile,
isImportable,
isImportDeclaration,
isImportEqualsDeclaration,
isIntrinsicJsxName,
@@ -1542,10 +1541,7 @@ function getExportInfos(
});
function addSymbol(moduleSymbol: Symbol, toFile: SourceFile | undefined, exportedSymbol: Symbol, exportKind: ExportKind, program: Program, isFromPackageJson: boolean): void {
const moduleSpecifierResolutionHost = getModuleSpecifierResolutionHost(isFromPackageJson);
if (
toFile && isImportableFile(program, fromFile, toFile, preferences, packageJsonFilter, moduleSpecifierResolutionHost, moduleSpecifierCache) ||
(!toFile && packageJsonFilter.allowsImportingAmbientModule(moduleSymbol, moduleSpecifierResolutionHost) || fileContainsPackageImport(fromFile, stripQuotes(moduleSymbol.name)))
) {
if (isImportable(program, fromFile, toFile, moduleSymbol, preferences, packageJsonFilter, moduleSpecifierResolutionHost, moduleSpecifierCache)) {
const checker = program.getTypeChecker();
originalSymbolToExportInfos.add(getUniqueSymbolId(exportedSymbol, checker).toString(), { symbol: exportedSymbol, moduleSymbol, moduleFileName: toFile?.fileName, exportKind, targetFlags: skipAlias(exportedSymbol, checker).flags, isFromPackageJson });
}
+6 -17
View File
@@ -64,7 +64,6 @@ import {
Expression,
ExpressionWithTypeArguments,
factory,
fileContainsPackageImport,
filter,
find,
findAncestor,
@@ -175,7 +174,7 @@ import {
isIdentifierPart,
isIdentifierStart,
isIdentifierText,
isImportableFile,
isImportable,
isImportAttributes,
isImportDeclaration,
isImportEqualsDeclaration,
@@ -274,7 +273,6 @@ import {
JSDocTypedefTag,
JSDocTypeExpression,
JSDocTypeTag,
JsTyping,
JsxAttribute,
JsxAttributes,
JsxClosingElement,
@@ -344,7 +342,6 @@ import {
SemanticMeaning,
setEmitFlags,
setSnippetElement,
shouldUseUriStyleNodeCoreModules,
SignatureHelp,
SignatureKind,
singleElementArray,
@@ -4221,19 +4218,11 @@ function getCompletionData(
);
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?.allowsImportingAmbientModule(info.moduleSymbol, getModuleSpecifierResolutionHost(info.isFromPackageJson)) ?? true)
|| fileContainsPackageImport(sourceFile, moduleName);
}
return isImportableFile(
return isImportable(
info.isFromPackageJson ? packageJsonAutoImportProvider! : program,
sourceFile,
moduleFile,
tryCast(info.moduleSymbol.valueDeclaration, isSourceFile),
info.moduleSymbol,
preferences,
packageJsonFilter,
getModuleSpecifierResolutionHost(info.isFromPackageJson),
@@ -4371,7 +4360,7 @@ function getCompletionData(
// dprint-ignore
switch (tokenKind) {
case SyntaxKind.CommaToken:
switch (containingNodeKind) {
switch (containingNodeKind) {
case SyntaxKind.CallExpression: // func( a, |
case SyntaxKind.NewExpression: { // new C(a, |
const expression = (contextToken.parent as CallExpression | NewExpression).expression;
@@ -4454,7 +4443,7 @@ function getCompletionData(
}
case SyntaxKind.TemplateHead:
return {
return {
defaultCommitCharacters: allCommitCharacters,
isNewIdentifierLocation: containingNodeKind === SyntaxKind.TemplateExpression // `aa ${|
};
+31 -16
View File
@@ -38,12 +38,14 @@ import {
ModuleSpecifierResolutionHost,
moduleSpecifiers,
moduleSymbolToValidIdentifier,
nodeCoreModules,
nodeModulesPathPart,
PackageJsonImportFilter,
Path,
pathContainsNodeModules,
Program,
ScriptTarget,
shouldUseUriStyleNodeCoreModules,
skipAlias,
SourceFile,
startsWith,
@@ -362,48 +364,61 @@ export function createCacheableExportInfoMap(host: CacheableExportInfoMapHost):
}
/** @internal */
export function isImportableFile(
export function isImportable(
program: Program,
from: SourceFile,
to: SourceFile,
fromFile: SourceFile,
toFile: SourceFile | undefined,
toModule: Symbol,
preferences: UserPreferences,
packageJsonFilter: PackageJsonImportFilter | undefined,
moduleSpecifierResolutionHost: ModuleSpecifierResolutionHost,
moduleSpecifierCache: ModuleSpecifierCache | undefined,
): boolean {
if (from === to) return false;
const cachedResult = moduleSpecifierCache?.get(from.path, to.path, preferences, {});
if (!toFile) {
// Ambient module
let useNodePrefix;
const moduleName = stripQuotes(toModule.name);
if (nodeCoreModules.has(moduleName) && (useNodePrefix = shouldUseUriStyleNodeCoreModules(fromFile, program)) !== undefined) {
return useNodePrefix === startsWith(moduleName, "node:");
}
return !packageJsonFilter
|| packageJsonFilter.allowsImportingAmbientModule(toModule, moduleSpecifierResolutionHost)
|| fileContainsPackageImport(fromFile, moduleName);
}
Debug.assertIsDefined(toFile);
if (fromFile === toFile) return false;
const cachedResult = moduleSpecifierCache?.get(fromFile.path, toFile.path, preferences, {});
if (cachedResult?.isBlockedByPackageJsonDependencies !== undefined) {
return !cachedResult.isBlockedByPackageJsonDependencies || !!cachedResult.packageName && fileContainsPackageImport(from, cachedResult.packageName);
return !cachedResult.isBlockedByPackageJsonDependencies || !!cachedResult.packageName && fileContainsPackageImport(fromFile, cachedResult.packageName);
}
const getCanonicalFileName = hostGetCanonicalFileName(moduleSpecifierResolutionHost);
const globalTypingsCache = moduleSpecifierResolutionHost.getGlobalTypingsCacheLocation?.();
const hasImportablePath = !!moduleSpecifiers.forEachFileNameOfModule(
from.fileName,
to.fileName,
fromFile.fileName,
toFile.fileName,
moduleSpecifierResolutionHost,
/*preferSymlinks*/ false,
toPath => {
const toFile = program.getSourceFile(toPath);
const file = program.getSourceFile(toPath);
// Determine to import using toPath only if toPath is what we were looking at
// or there doesnt exist the file in the program by the symlink
return (toFile === to || !toFile) &&
isImportablePath(from.fileName, toPath, getCanonicalFileName, globalTypingsCache);
return (file === toFile || !file) &&
isImportablePath(fromFile.fileName, toPath, getCanonicalFileName, globalTypingsCache);
},
);
if (packageJsonFilter) {
const importInfo = hasImportablePath ? packageJsonFilter.getSourceFileInfo(to, moduleSpecifierResolutionHost) : undefined;
moduleSpecifierCache?.setBlockedByPackageJsonDependencies(from.path, to.path, preferences, {}, importInfo?.packageName, !importInfo?.importable);
return !!importInfo?.importable || !!importInfo?.packageName && fileContainsPackageImport(from, importInfo.packageName);
const importInfo = hasImportablePath ? packageJsonFilter.getSourceFileInfo(toFile, moduleSpecifierResolutionHost) : undefined;
moduleSpecifierCache?.setBlockedByPackageJsonDependencies(fromFile.path, toFile.path, preferences, {}, importInfo?.packageName, !importInfo?.importable);
return !!importInfo?.importable || hasImportablePath && !!importInfo?.packageName && fileContainsPackageImport(fromFile, importInfo.packageName);
}
return hasImportablePath;
}
/** @internal */
export function fileContainsPackageImport(sourceFile: SourceFile, packageName: string) {
function fileContainsPackageImport(sourceFile: SourceFile, packageName: string) {
return sourceFile.imports && sourceFile.imports.some(i => i.text === packageName || i.text.startsWith(packageName + "/"));
}
+15 -8
View File
@@ -62,6 +62,7 @@ import {
equateStringsCaseInsensitive,
equateStringsCaseSensitive,
escapeString,
exclusivelyPrefixedNodeCoreModules,
ExportAssignment,
ExportDeclaration,
Expression,
@@ -271,7 +272,6 @@ import {
JSDocLinkDisplayPart,
JSDocLinkPlain,
JSDocTypedefTag,
JsTyping,
JsxEmit,
JsxOpeningLikeElement,
JsxTagNameExpression,
@@ -297,6 +297,7 @@ import {
Node,
NodeArray,
NodeBuilderFlags,
nodeCoreModules,
NodeFlags,
nodeIsMissing,
nodeIsPresent,
@@ -3851,7 +3852,7 @@ export function createPackageJsonImportFilter(fromFile: SourceFile | FutureSourc
// 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 (isFullSourceFile(fromFile) && isSourceFileJS(fromFile) && JsTyping.nodeCoreModules.has(moduleSpecifier)) {
if (isFullSourceFile(fromFile) && isSourceFileJS(fromFile) && nodeCoreModules.has(moduleSpecifier)) {
if (usesNodeCoreModules === undefined) {
usesNodeCoreModules = consumesNodeCoreModules(fromFile);
}
@@ -3896,7 +3897,7 @@ export function createPackageJsonImportFilter(fromFile: SourceFile | FutureSourc
/** @internal */
export function consumesNodeCoreModules(sourceFile: SourceFile): boolean {
return some(sourceFile.imports, ({ text }) => JsTyping.nodeCoreModules.has(text));
return some(sourceFile.imports, ({ text }) => nodeCoreModules.has(text));
}
/** @internal */
@@ -4123,12 +4124,18 @@ export function isDeprecatedDeclaration(decl: Declaration) {
}
/** @internal */
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:");
export function shouldUseUriStyleNodeCoreModules(file: SourceFile | FutureSourceFile, program: Program): boolean | undefined {
let decisionFromFile;
for (const node of file.imports) {
if (nodeCoreModules.has(node.text) && !exclusivelyPrefixedNodeCoreModules.has(node.text)) {
if (startsWith(node.text, "node:")) {
return true;
}
else {
decisionFromFile = false;
}
}
});
}
return decisionFromFile ?? program.usesUriStyleNodeCoreModules;
}