Only look up package.json type if module is node16/nodenext or file is in node_modules (#58825)

Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com>
This commit is contained in:
Andrew Branch
2024-07-17 09:23:51 -07:00
committed by GitHub
co-authored by Jake Bailey
parent f37482cd16
commit a9139bfdfe
90 changed files with 3082 additions and 355 deletions
+143 -27
View File
@@ -175,6 +175,7 @@ import {
ImportClause,
ImportDeclaration,
ImportOrExportSpecifier,
importSyntaxAffectsModuleResolution,
InternalEmitFlags,
inverseJsxOptionMap,
isAmbientModule,
@@ -840,9 +841,11 @@ export function flattenDiagnosticMessageText(diag: string | DiagnosticMessageCha
* @internal
*/
export interface SourceFileImportsList {
/** @internal */ imports: SourceFile["imports"];
/** @internal */ moduleAugmentations: SourceFile["moduleAugmentations"];
imports: SourceFile["imports"];
moduleAugmentations: SourceFile["moduleAugmentations"];
impliedNodeFormat?: ResolutionMode;
fileName: string;
packageJsonScope?: SourceFile["packageJsonScope"];
}
/**
@@ -866,7 +869,7 @@ export function getModeForFileReference(ref: FileReference | string, containingF
* should be the options of the referenced project, not the referencing project.
*/
export function getModeForResolutionAtIndex(file: SourceFile, index: number, compilerOptions: CompilerOptions): ResolutionMode;
/** @internal */
/** @internal @knipignore */
// eslint-disable-next-line @typescript-eslint/unified-signatures
export function getModeForResolutionAtIndex(file: SourceFileImportsList, index: number, compilerOptions: CompilerOptions): ResolutionMode;
export function getModeForResolutionAtIndex(file: SourceFileImportsList, index: number, compilerOptions?: CompilerOptions): ResolutionMode {
@@ -888,22 +891,47 @@ export function isExclusivelyTypeOnlyImportOrExport(decl: ImportDeclaration | Ex
/**
* Use `program.getModeForUsageLocation`, which retrieves the correct `compilerOptions`, instead of this function whenever possible.
* Calculates the final resolution mode for a given module reference node. This is the resolution mode explicitly provided via import
* attributes, if present, or the syntax the usage would have if emitted to JavaScript. In `--module node16` or `nodenext`, this may
* depend on the file's `impliedNodeFormat`. In `--module preserve`, it depends only on the input syntax of the reference. In other
* `module` modes, when overriding import attributes are not provided, this function returns `undefined`, as the result would have no
* impact on module resolution, emit, or type checking.
* Calculates the final resolution mode for a given module reference node. This function only returns a result when module resolution
* settings allow differing resolution between ESM imports and CJS requires, or when a mode is explicitly provided via import attributes,
* which cause an `import` or `require` condition to be used during resolution regardless of module resolution settings. In absence of
* overriding attributes, and in modes that support differing resolution, the result indicates the syntax the usage would emit to JavaScript.
* Some examples:
*
* ```ts
* // tsc foo.mts --module nodenext
* import {} from "mod";
* // Result: ESNext - the import emits as ESM due to `impliedNodeFormat` set by .mts file extension
*
* // tsc foo.cts --module nodenext
* import {} from "mod";
* // Result: CommonJS - the import emits as CJS due to `impliedNodeFormat` set by .cts file extension
*
* // tsc foo.ts --module preserve --moduleResolution bundler
* import {} from "mod";
* // Result: ESNext - the import emits as ESM due to `--module preserve` and `--moduleResolution bundler`
* // supports conditional imports/exports
*
* // tsc foo.ts --module preserve --moduleResolution node10
* import {} from "mod";
* // Result: undefined - the import emits as ESM due to `--module preserve`, but `--moduleResolution node10`
* // does not support conditional imports/exports
*
* // tsc foo.ts --module commonjs --moduleResolution node10
* import type {} from "mod" with { "resolution-mode": "import" };
* // Result: ESNext - conditional imports/exports always supported with "resolution-mode" attribute
* ```
*
* @param file The file the import or import-like reference is contained within
* @param usage The module reference string
* @param compilerOptions The compiler options for the program that owns the file. If the file belongs to a referenced project, the compiler options
* should be the options of the referenced project, not the referencing project.
* @returns The final resolution mode of the import
*/
export function getModeForUsageLocation(file: { impliedNodeFormat?: ResolutionMode; }, usage: StringLiteralLike, compilerOptions: CompilerOptions) {
export function getModeForUsageLocation(file: SourceFile, usage: StringLiteralLike, compilerOptions: CompilerOptions) {
return getModeForUsageLocationWorker(file, usage, compilerOptions);
}
function getModeForUsageLocationWorker(file: { impliedNodeFormat?: ResolutionMode; }, usage: StringLiteralLike, compilerOptions?: CompilerOptions) {
function getModeForUsageLocationWorker(file: Pick<SourceFile, "fileName" | "impliedNodeFormat" | "packageJsonScope">, usage: StringLiteralLike, compilerOptions?: CompilerOptions) {
if (isImportDeclaration(usage.parent) || isExportDeclaration(usage.parent) || isJSDocImportTag(usage.parent)) {
const isTypeOnly = isExclusivelyTypeOnlyImportOrExport(usage.parent);
if (isTypeOnly) {
@@ -919,20 +947,36 @@ function getModeForUsageLocationWorker(file: { impliedNodeFormat?: ResolutionMod
return override;
}
}
if (compilerOptions && getEmitModuleKind(compilerOptions) === ModuleKind.Preserve) {
return (usage.parent.parent && isImportEqualsDeclaration(usage.parent.parent) || isRequireCall(usage.parent, /*requireStringLiteralLikeArgument*/ false))
? ModuleKind.CommonJS
: ModuleKind.ESNext;
if (compilerOptions && importSyntaxAffectsModuleResolution(compilerOptions)) {
return getEmitSyntaxForUsageLocationWorker(file, usage, compilerOptions);
}
if (file.impliedNodeFormat === undefined) return undefined;
if (file.impliedNodeFormat !== ModuleKind.ESNext) {
// in cjs files, import call expressions are esm format, otherwise everything is cjs
return isImportCall(walkUpParenthesizedExpressions(usage.parent)) ? ModuleKind.ESNext : ModuleKind.CommonJS;
}
function getEmitSyntaxForUsageLocationWorker(file: Pick<SourceFile, "fileName" | "impliedNodeFormat" | "packageJsonScope">, usage: StringLiteralLike, compilerOptions?: CompilerOptions): ResolutionMode {
if (!compilerOptions) {
// This should always be provided, but we try to fail somewhat
// gracefully to allow projects like ts-node time to update.
return undefined;
}
// in esm files, import=require statements are cjs format, otherwise everything is esm
// imports are only parent'd up to their containing declaration/expression, so access farther parents with care
const exprParentParent = walkUpParenthesizedExpressions(usage.parent)?.parent;
return exprParentParent && isImportEqualsDeclaration(exprParentParent) ? ModuleKind.CommonJS : ModuleKind.ESNext;
if (exprParentParent && isImportEqualsDeclaration(exprParentParent) || isRequireCall(usage.parent, /*requireStringLiteralLikeArgument*/ false)) {
return ModuleKind.CommonJS;
}
if (isImportCall(walkUpParenthesizedExpressions(usage.parent))) {
return shouldTransformImportCallWorker(file, compilerOptions) ? ModuleKind.CommonJS : ModuleKind.ESNext;
}
// If we're in --module preserve on an input file, we know that an import
// is an import. But if this is a declaration file, we'd prefer to use the
// impliedNodeFormat. Since we want things to be consistent between the two,
// we need to issue errors when the user writes ESM syntax in a definitely-CJS
// file, until/unless declaration emit can indicate a true ESM import. On the
// other hand, writing CJS syntax in a definitely-ESM file is fine, since declaration
// emit preserves the CJS syntax.
const fileEmitMode = getEmitModuleFormatOfFileWorker(file, compilerOptions);
return fileEmitMode === ModuleKind.CommonJS ? ModuleKind.CommonJS :
emitModuleKindIsNonNodeESM(fileEmitMode) || fileEmitMode === ModuleKind.Preserve ? ModuleKind.ESNext :
undefined;
}
/** @internal */
@@ -1028,7 +1072,7 @@ function getTypeReferenceResolutionName<T extends FileReference | string>(entry:
const typeReferenceResolutionNameAndModeGetter: ResolutionNameAndModeGetter<FileReference | string, SourceFile | undefined> = {
getName: getTypeReferenceResolutionName,
getMode: (entry, file) => getModeForFileReference(entry, file?.impliedNodeFormat),
getMode: (entry, file, compilerOptions) => getModeForFileReference(entry, file && getDefaultResolutionModeForFileWorker(file, compilerOptions)),
};
/** @internal */
@@ -1358,6 +1402,7 @@ export function getImpliedNodeFormatForFileWorker(
default:
return undefined;
}
function lookupFromPackageJson(): Partial<CreateSourceFileOptions> {
const state = getTemporaryModuleResolutionState(packageJsonInfoCache, host, options);
const packageJsonLocations: string[] = [];
@@ -1927,6 +1972,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
isSourceFileFromExternalLibrary,
isSourceFileDefaultLibrary,
getModeForUsageLocation,
getEmitSyntaxForUsageLocation,
getModeForResolutionAtIndex,
getSourceFileFromReference,
getLibFileFromReference,
@@ -1955,6 +2001,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
forEachResolvedProjectReference,
isSourceOfProjectReferenceRedirect,
getRedirectReferenceForResolutionFromSourceOfProject,
getCompilerOptionsForFile,
getDefaultResolutionModeForFile,
getEmitModuleFormatOfFile,
getImpliedNodeFormatForEmit,
shouldTransformImportCall,
emitBuildInfo,
fileExists,
readFile,
@@ -2669,6 +2720,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
getSymlinkCache,
writeFile: writeFileCallback || writeFile,
isEmitBlocked,
shouldTransformImportCall,
getEmitModuleFormatOfFile,
getDefaultResolutionModeForFile,
getModeForResolutionAtIndex,
readFile: f => host.readFile(f),
fileExists: f => {
// Use local caches
@@ -3955,11 +4010,15 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
// store resolved type directive on the file
const fileName = ref.fileName;
resolutionsInFile.set(fileName, getModeForFileReference(ref, file.impliedNodeFormat), resolvedTypeReferenceDirective);
const mode = ref.resolutionMode || file.impliedNodeFormat;
const mode = ref.resolutionMode || getDefaultResolutionModeForFile(file);
processTypeReferenceDirective(fileName, mode, resolvedTypeReferenceDirective, { kind: FileIncludeKind.TypeReferenceDirective, file: file.path, index });
}
}
function getCompilerOptionsForFile(file: SourceFile): CompilerOptions {
return getRedirectReferenceForResolution(file)?.commandLine.options || options;
}
function processTypeReferenceDirective(
typeReferenceDirective: string,
mode: ResolutionMode,
@@ -4074,7 +4133,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
const resolutions = resolvedModulesProcessing?.get(file.path) ||
resolveModuleNamesReusingOldState(moduleNames, file);
Debug.assert(resolutions.length === moduleNames.length);
const optionsForFile = getRedirectReferenceForResolution(file)?.commandLine.options || options;
const optionsForFile = getCompilerOptionsForFile(file);
const resolutionsInFile = createModeAwareCache<ResolutionWithFailedLookupLocations>();
(resolvedModules ??= new Map()).set(file.path, resolutionsInFile);
for (let index = 0; index < moduleNames.length; index++) {
@@ -4664,7 +4723,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
else {
reasons?.forEach(processReason);
redirectInfo = file && explainIfFileIsRedirectAndImpliedFormat(file);
redirectInfo = file && explainIfFileIsRedirectAndImpliedFormat(file, getCompilerOptionsForFile(file));
}
if (fileProcessingReason) processReason(fileProcessingReason);
@@ -5098,13 +5157,70 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
function getModeForUsageLocation(file: SourceFile, usage: StringLiteralLike): ResolutionMode {
const optionsForFile = getRedirectReferenceForResolution(file)?.commandLine.options || options;
return getModeForUsageLocationWorker(file, usage, optionsForFile);
return getModeForUsageLocationWorker(file, usage, getCompilerOptionsForFile(file));
}
function getEmitSyntaxForUsageLocation(file: SourceFile, usage: StringLiteralLike): ResolutionMode {
return getEmitSyntaxForUsageLocationWorker(file, usage, getCompilerOptionsForFile(file));
}
function getModeForResolutionAtIndex(file: SourceFile, index: number): ResolutionMode {
return getModeForUsageLocation(file, getModuleNameStringLiteralAt(file, index));
}
function getDefaultResolutionModeForFile(sourceFile: SourceFile): ResolutionMode {
return getDefaultResolutionModeForFileWorker(sourceFile, getCompilerOptionsForFile(sourceFile));
}
function getImpliedNodeFormatForEmit(sourceFile: SourceFile): ResolutionMode {
return getImpliedNodeFormatForEmitWorker(sourceFile, getCompilerOptionsForFile(sourceFile));
}
function getEmitModuleFormatOfFile(sourceFile: SourceFile): ModuleKind {
return getEmitModuleFormatOfFileWorker(sourceFile, getCompilerOptionsForFile(sourceFile));
}
function shouldTransformImportCall(sourceFile: SourceFile): boolean {
return shouldTransformImportCallWorker(sourceFile, getCompilerOptionsForFile(sourceFile));
}
}
function shouldTransformImportCallWorker(sourceFile: Pick<SourceFile, "fileName" | "impliedNodeFormat" | "packageJsonScope">, options: CompilerOptions): boolean {
const moduleKind = getEmitModuleKind(options);
if (ModuleKind.Node16 <= moduleKind && moduleKind <= ModuleKind.NodeNext || moduleKind === ModuleKind.Preserve) {
return false;
}
return getEmitModuleFormatOfFileWorker(sourceFile, options) < ModuleKind.ES2015;
}
/** @internal Prefer `program.getEmitModuleFormatOfFile` when possible. */
export function getEmitModuleFormatOfFileWorker(sourceFile: Pick<SourceFile, "fileName" | "impliedNodeFormat" | "packageJsonScope">, options: CompilerOptions): ModuleKind {
return getImpliedNodeFormatForEmitWorker(sourceFile, options) ?? getEmitModuleKind(options);
}
/** @internal Prefer `program.getImpliedNodeFormatForEmit` when possible. */
export function getImpliedNodeFormatForEmitWorker(sourceFile: Pick<SourceFile, "fileName" | "impliedNodeFormat" | "packageJsonScope">, options: CompilerOptions): ResolutionMode {
const moduleKind = getEmitModuleKind(options);
if (ModuleKind.Node16 <= moduleKind && moduleKind <= ModuleKind.NodeNext) {
return sourceFile.impliedNodeFormat;
}
if (
sourceFile.impliedNodeFormat === ModuleKind.CommonJS
&& (sourceFile.packageJsonScope?.contents.packageJsonContent.type === "commonjs"
|| fileExtensionIsOneOf(sourceFile.fileName, [Extension.Cjs, Extension.Cts]))
) {
return ModuleKind.CommonJS;
}
if (
sourceFile.impliedNodeFormat === ModuleKind.ESNext
&& (sourceFile.packageJsonScope?.contents.packageJsonContent.type === "module"
|| fileExtensionIsOneOf(sourceFile.fileName, [Extension.Mjs, Extension.Mts]))
) {
return ModuleKind.ESNext;
}
return undefined;
}
/** @internal Prefer `program.getDefaultResolutionModeForFile` when possible. */
export function getDefaultResolutionModeForFileWorker(sourceFile: Pick<SourceFile, "fileName" | "impliedNodeFormat" | "packageJsonScope">, options: CompilerOptions): ResolutionMode {
return importSyntaxAffectsModuleResolution(options) ? getImpliedNodeFormatForEmitWorker(sourceFile, options) : undefined;
}
interface HostForUseSourceOfProjectReferenceRedirect {