From 299f2742aabd0d88e40c0855dd18752f283a31ac Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Sat, 1 Jul 2023 07:06:22 +0200 Subject: [PATCH 1/4] refactor: Move definitions closer to usage --- packages/core/src/multiCompilerHost.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/core/src/multiCompilerHost.ts b/packages/core/src/multiCompilerHost.ts index 0ef4c55..175b644 100644 --- a/packages/core/src/multiCompilerHost.ts +++ b/packages/core/src/multiCompilerHost.ts @@ -33,15 +33,8 @@ export interface MultiCompilerHost { } export function createMultiCompilerHost(fs: Package): MultiCompilerHost { - const useCaseSensitiveFileNames = () => false; const getCanonicalFileName = ts.createGetCanonicalFileName(false); - const getCurrentDirectory = () => "/"; - const getNewLine = () => "\n"; - const getDefaultLibFileName = () => "/node_modules/typescript/lib/lib.d.ts"; const toPath = (fileName: string) => ts.toPath(fileName, "/", getCanonicalFileName); - const writeFile = () => { - throw new Error("Not implemented"); - }; const languageVersion = ts.ScriptTarget.Latest; const traceCollector = createTraceCollector(); const compilerOptions: Record = { @@ -231,12 +224,14 @@ export function createMultiCompilerHost(fs: Package): MultiCompilerHost { sourceFileCache.set(path, sourceFile); return sourceFile; }, - getDefaultLibFileName, - getCurrentDirectory, - writeFile, + getDefaultLibFileName: () => "/node_modules/typescript/lib/lib.d.ts", + getCurrentDirectory: () => "/", + writeFile: () => { + throw new Error("Not implemented"); + }, getCanonicalFileName, - useCaseSensitiveFileNames, - getNewLine, + useCaseSensitiveFileNames: () => false, + getNewLine: () => "\n", trace: traceCollector.trace, resolveModuleNameLiterals(moduleLiterals, containingFile, _redirectedReference, options, containingSourceFile) { return moduleLiterals.map( From 604dd8aa4639dc61d730da79ab104ed2a2b9f640 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Sat, 1 Jul 2023 07:18:31 +0200 Subject: [PATCH 2/4] refactor: Remove unused methods from MultiCompilerHost interface --- packages/core/src/multiCompilerHost.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/core/src/multiCompilerHost.ts b/packages/core/src/multiCompilerHost.ts index 175b644..ce023a3 100644 --- a/packages/core/src/multiCompilerHost.ts +++ b/packages/core/src/multiCompilerHost.ts @@ -9,11 +9,6 @@ export interface ResolveModuleNameResult { export interface MultiCompilerHost { getSourceFile(fileName: string, moduleResolution?: ResolutionOption): ts.SourceFile | undefined; - getImpliedNodeFormatForFile( - fileName: string, - moduleResolution: ResolutionOption - ): ts.ModuleKind.ESNext | ts.ModuleKind.CommonJS | undefined; - getPackageScopeForPath(fileName: string): ts.PackageJsonInfo | undefined; getModuleKindForFile(fileName: string, moduleResolution: "node16"): ModuleKind; getModuleKindForFile(fileName: string, moduleResolution: ResolutionOption): ModuleKind | undefined; resolveModuleName( @@ -90,8 +85,6 @@ export function createMultiCompilerHost(fs: Package): MultiCompilerHost { return { getSourceFile, - getImpliedNodeFormatForFile, - getPackageScopeForPath, getModuleKindForFile, resolveModuleName, createProgram, From db9517e4f34ddce777182d18a8d5a9199bb0571d Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Sat, 1 Jul 2023 08:45:50 +0200 Subject: [PATCH 3/4] refactor: Move private functions down --- packages/core/src/multiCompilerHost.ts | 50 +++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/core/src/multiCompilerHost.ts b/packages/core/src/multiCompilerHost.ts index ce023a3..eba2b92 100644 --- a/packages/core/src/multiCompilerHost.ts +++ b/packages/core/src/multiCompilerHost.ts @@ -95,31 +95,6 @@ export function createMultiCompilerHost(fs: Package): MultiCompilerHost { return compilerHosts[moduleResolution].getSourceFile(fileName, languageVersion); } - function getImpliedNodeFormatForFile( - fileName: string, - moduleResolution: ResolutionOption - ): ts.ModuleKind.ESNext | ts.ModuleKind.CommonJS | undefined { - return ts.getImpliedNodeFormatForFile( - toPath(fileName), - moduleResolutionCaches[moduleResolution][0].getPackageJsonInfoCache(), - compilerHosts[moduleResolution], - compilerOptions[moduleResolution] - ); - } - - function getPackageScopeForPath(fileName: string): ts.PackageJsonInfo | undefined { - // Which compiler options get used here is irrelevant. - // Use the node16 cache because package.json it should be a hit. - return ts.getPackageScopeForPath( - fileName, - ts.getTemporaryModuleResolutionState( - moduleResolutionCaches.node16[0].getPackageJsonInfoCache(), - compilerHosts.node16, - compilerOptions.node16 - ) - ); - } - function getModuleKindForFile(fileName: string, moduleResolution: "node16"): ModuleKind; function getModuleKindForFile(fileName: string, moduleResolution: ResolutionOption): ModuleKind | undefined; function getModuleKindForFile(fileName: string, moduleResolution: ResolutionOption): ModuleKind | undefined { @@ -241,6 +216,31 @@ export function createMultiCompilerHost(fs: Package): MultiCompilerHost { }; } + function getImpliedNodeFormatForFile( + fileName: string, + moduleResolution: ResolutionOption + ): ts.ModuleKind.ESNext | ts.ModuleKind.CommonJS | undefined { + return ts.getImpliedNodeFormatForFile( + toPath(fileName), + moduleResolutionCaches[moduleResolution][0].getPackageJsonInfoCache(), + compilerHosts[moduleResolution], + compilerOptions[moduleResolution] + ); + } + + function getPackageScopeForPath(fileName: string): ts.PackageJsonInfo | undefined { + // Which compiler options get used here is irrelevant. + // Use the node16 cache because package.json it should be a hit. + return ts.getPackageScopeForPath( + fileName, + ts.getTemporaryModuleResolutionState( + moduleResolutionCaches.node16[0].getPackageJsonInfoCache(), + compilerHosts.node16, + compilerOptions.node16 + ) + ); + } + function createTraceCollector() { const traces: string[] = []; return { From 28f8c43283f1779dd908403655726b4c8ea4b21f Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Sat, 1 Jul 2023 08:50:39 +0200 Subject: [PATCH 4/4] refactor: Split up MultiCompilerHost The logic of the MultiCompilerHost almost only operated on one CompilerHost plus respective CompilerOptions and caches, so it makes sense to encapsulate this logic for one instance of these properties, instead of requiring each of these properities to be a Record and having to index them all the time. The resulting code is less repetitive and more readable. --- packages/core/src/checkPackage.ts | 32 ++- .../checks/entrypointResolutionProblems.ts | 8 +- packages/core/src/checks/fileProblems.ts | 6 +- .../src/checks/resolutionBasedFileProblems.ts | 11 +- packages/core/src/multiCompilerHost.ts | 231 +++++++----------- 5 files changed, 122 insertions(+), 166 deletions(-) diff --git a/packages/core/src/checkPackage.ts b/packages/core/src/checkPackage.ts index c615fa0..ebb331a 100644 --- a/packages/core/src/checkPackage.ts +++ b/packages/core/src/checkPackage.ts @@ -3,7 +3,7 @@ import { getEntrypointResolutionProblems } from "./checks/entrypointResolutionPr import { getFileProblems } from "./checks/fileProblems.js"; import { getResolutionBasedFileProblems } from "./checks/resolutionBasedFileProblems.js"; import type { Package } from "./createPackage.js"; -import { createMultiCompilerHost, type MultiCompilerHost } from "./multiCompilerHost.js"; +import { createCompilerHosts, type CompilerHosts, CompilerHostWrapper } from "./multiCompilerHost.js"; import type { CheckResult, EntrypointInfo, EntrypointResolutionAnalysis, Resolution, ResolutionKind } from "./types.js"; export async function checkPackage(pkg: Package): Promise { @@ -20,11 +20,11 @@ export async function checkPackage(pkg: Package): Promise { return { packageName, packageVersion, types }; } - const host = createMultiCompilerHost(pkg); - const entrypointResolutions = getEntrypointInfo(packageName, pkg, host); - const entrypointResolutionProblems = getEntrypointResolutionProblems(entrypointResolutions, host); - const resolutionBasedFileProblems = getResolutionBasedFileProblems(packageName, entrypointResolutions, host); - const fileProblems = getFileProblems(entrypointResolutions, host); + const hosts = createCompilerHosts(pkg); + const entrypointResolutions = getEntrypointInfo(packageName, pkg, hosts); + const entrypointResolutionProblems = getEntrypointResolutionProblems(entrypointResolutions, hosts); + const resolutionBasedFileProblems = getResolutionBasedFileProblems(packageName, entrypointResolutions, hosts); + const fileProblems = getFileProblems(entrypointResolutions, hosts); return { packageName, @@ -62,7 +62,7 @@ function getProxyDirectories(rootDir: string, fs: Package) { .filter((f) => f !== "./"); } -function getEntrypointInfo(packageName: string, fs: Package, host: MultiCompilerHost): Record { +function getEntrypointInfo(packageName: string, fs: Package, hosts: CompilerHosts): Record { const packageJson = JSON.parse(fs.readFile(`/node_modules/${packageName}/package.json`)); const subpaths = getSubpaths(packageJson.exports); const entrypoints = subpaths.length ? subpaths : ["."]; @@ -72,10 +72,10 @@ function getEntrypointInfo(packageName: string, fs: Package, host: MultiCompiler const result: Record = {}; for (const entrypoint of entrypoints) { const resolutions: Record = { - node10: getEntrypointResolution(packageName, "node10", entrypoint, host), - "node16-cjs": getEntrypointResolution(packageName, "node16-cjs", entrypoint, host), - "node16-esm": getEntrypointResolution(packageName, "node16-esm", entrypoint, host), - bundler: getEntrypointResolution(packageName, "bundler", entrypoint, host), + node10: getEntrypointResolution(packageName, hosts.node10, "node10", entrypoint), + "node16-cjs": getEntrypointResolution(packageName, hosts.node16, "node16-cjs", entrypoint), + "node16-esm": getEntrypointResolution(packageName, hosts.node16, "node16-esm", entrypoint), + bundler: getEntrypointResolution(packageName, hosts.bundler, "bundler", entrypoint), }; result[entrypoint] = { subpath: entrypoint, @@ -89,16 +89,15 @@ function getEntrypointInfo(packageName: string, fs: Package, host: MultiCompiler function getEntrypointResolution( packageName: string, + host: CompilerHostWrapper, resolutionKind: ResolutionKind, - entrypoint: string, - host: MultiCompilerHost + entrypoint: string ): EntrypointResolutionAnalysis { if (entrypoint.includes("*")) { return { name: entrypoint, resolutionKind, isWildcard: true }; } const moduleSpecifier = packageName + entrypoint.substring(1); // remove leading . before slash const importingFileName = resolutionKind === "node16-esm" ? "/index.mts" : "/index.ts"; - const moduleResolution = resolutionKind === "node10" ? "node10" : resolutionKind === "bundler" ? "bundler" : "node16"; const resolutionMode = resolutionKind === "node16-esm" ? ts.ModuleKind.ESNext : ts.ModuleKind.CommonJS; const resolution = tryResolve(); @@ -107,7 +106,7 @@ function getEntrypointResolution( const files = resolution ? host - .createProgram(moduleResolution, [resolution.fileName]) + .createProgram([resolution.fileName]) .getSourceFiles() .map((f) => f.fileName) : undefined; @@ -124,7 +123,6 @@ function getEntrypointResolution( const { resolution, trace } = host.resolveModuleName( moduleSpecifier, importingFileName, - moduleResolution, resolutionMode, noDtsResolution ); @@ -135,7 +133,7 @@ function getEntrypointResolution( return { fileName, - moduleKind: host.getModuleKindForFile(fileName, moduleResolution), + moduleKind: host.getModuleKindForFile(fileName), isJson: resolution.resolvedModule.extension === ts.Extension.Json, isTypeScript: ts.hasTSFileExtension(resolution.resolvedModule.resolvedFileName), trace, diff --git a/packages/core/src/checks/entrypointResolutionProblems.ts b/packages/core/src/checks/entrypointResolutionProblems.ts index d0b2380..65387f1 100644 --- a/packages/core/src/checks/entrypointResolutionProblems.ts +++ b/packages/core/src/checks/entrypointResolutionProblems.ts @@ -1,11 +1,11 @@ import ts from "typescript"; import type { EntrypointInfo, EntrypointResolutionProblem } from "../types.js"; -import type { MultiCompilerHost } from "../multiCompilerHost.js"; +import type { CompilerHosts } from "../multiCompilerHost.js"; import { resolvedThroughFallback, visitResolutions } from "../utils.js"; export function getEntrypointResolutionProblems( entrypointResolutions: Record, - host: MultiCompilerHost + hosts: CompilerHosts ): EntrypointResolutionProblem[] { const problems: EntrypointResolutionProblem[] = []; visitResolutions(entrypointResolutions, (result, entrypoint) => { @@ -71,12 +71,12 @@ export function getEntrypointResolutionProblems( } if (resolutionKind === "node16-esm" && resolution && implementationResolution) { - const typesSourceFile = host.getSourceFile(resolution.fileName, "node16"); + const typesSourceFile = hosts.node16.getSourceFile(resolution.fileName); if (typesSourceFile) { ts.bindSourceFile(typesSourceFile, { target: ts.ScriptTarget.Latest, allowJs: true, checkJs: true }); } const typesExports = typesSourceFile?.symbol?.exports; - const jsSourceFile = typesExports && host.getSourceFile(implementationResolution.fileName, "node16"); + const jsSourceFile = typesExports && hosts.node16.getSourceFile(implementationResolution.fileName); if (jsSourceFile) { ts.bindSourceFile(jsSourceFile, { target: ts.ScriptTarget.Latest, allowJs: true, checkJs: true }); } diff --git a/packages/core/src/checks/fileProblems.ts b/packages/core/src/checks/fileProblems.ts index 7ba0747..193bd1a 100644 --- a/packages/core/src/checks/fileProblems.ts +++ b/packages/core/src/checks/fileProblems.ts @@ -1,11 +1,11 @@ import ts from "typescript"; -import type { MultiCompilerHost } from "../multiCompilerHost.js"; +import type { CompilerHosts } from "../multiCompilerHost.js"; import type { EntrypointInfo, FileProblem } from "../types.js"; import { isDefined } from "../utils.js"; export function getFileProblems( entrypointResolutions: Record, - host: MultiCompilerHost + hosts: CompilerHosts ): FileProblem[] { const problems: FileProblem[] = []; const visibleFiles = new Set( @@ -18,7 +18,7 @@ export function getFileProblems( for (const fileName of visibleFiles) { if (ts.hasJSFileExtension(fileName)) { - const sourceFile = host.getSourceFile(fileName, "node16")!; + const sourceFile = hosts.node16.getSourceFile(fileName)!; if ( !sourceFile.externalModuleIndicator && sourceFile.commonJsModuleIndicator && diff --git a/packages/core/src/checks/resolutionBasedFileProblems.ts b/packages/core/src/checks/resolutionBasedFileProblems.ts index 5357afc..cdc08ed 100644 --- a/packages/core/src/checks/resolutionBasedFileProblems.ts +++ b/packages/core/src/checks/resolutionBasedFileProblems.ts @@ -1,12 +1,12 @@ import ts from "typescript"; -import type { MultiCompilerHost } from "../multiCompilerHost.js"; +import type { CompilerHosts } from "../multiCompilerHost.js"; import type { EntrypointInfo, ResolutionBasedFileProblem } from "../types.js"; import { allResolutionOptions, getResolutionKinds } from "../utils.js"; export function getResolutionBasedFileProblems( packageName: string, entrypointResolutions: Record, - host: MultiCompilerHost + hosts: CompilerHosts ): ResolutionBasedFileProblem[] { const result: ResolutionBasedFileProblem[] = []; for (const resolutionOption of allResolutionOptions) { @@ -24,7 +24,8 @@ export function getResolutionBasedFileProblems( ); for (const fileName of visibleFiles) { - const sourceFile = host.getSourceFile(fileName, resolutionOption)!; + const host = hosts[resolutionOption]; + const sourceFile = host.getSourceFile(fileName)!; if (sourceFile.imports) { for (const moduleSpecifier of sourceFile.imports) { @@ -52,7 +53,7 @@ export function getResolutionBasedFileProblems( pos: moduleSpecifier.pos, end: moduleSpecifier.end, resolutionMode, - trace: host.getTrace(resolutionOption, fileName, moduleSpecifier.text, resolutionMode)!, + trace: host.getTrace(fileName, moduleSpecifier.text, resolutionMode)!, }); } } @@ -67,7 +68,7 @@ export function getResolutionBasedFileProblems( // for looking for a JS file. if (resolutionOption === "node16") { if (ts.hasJSFileExtension(fileName)) { - const expectedModuleKind = host.getModuleKindForFile(fileName, resolutionOption); + const expectedModuleKind = host.getModuleKindForFile(fileName); const syntaxImpliedModuleKind = sourceFile.externalModuleIndicator ? ts.ModuleKind.ESNext : sourceFile.commonJsModuleIndicator diff --git a/packages/core/src/multiCompilerHost.ts b/packages/core/src/multiCompilerHost.ts index eba2b92..fc33d2f 100644 --- a/packages/core/src/multiCompilerHost.ts +++ b/packages/core/src/multiCompilerHost.ts @@ -1,5 +1,5 @@ import ts from "typescript"; -import type { ModuleKind, ResolutionOption } from "./types.js"; +import type { ModuleKind } from "./types.js"; import type { Package } from "./createPackage.js"; export interface ResolveModuleNameResult { @@ -7,98 +7,57 @@ export interface ResolveModuleNameResult { trace: string[]; } -export interface MultiCompilerHost { - getSourceFile(fileName: string, moduleResolution?: ResolutionOption): ts.SourceFile | undefined; - getModuleKindForFile(fileName: string, moduleResolution: "node16"): ModuleKind; - getModuleKindForFile(fileName: string, moduleResolution: ResolutionOption): ModuleKind | undefined; - resolveModuleName( - moduleName: string, - containingFile: string, - moduleResolution: ResolutionOption, - resolutionMode?: ts.ModuleKind.ESNext | ts.ModuleKind.CommonJS, - noDtsResolution?: boolean - ): ResolveModuleNameResult; - getTrace( - moduleResolution: ResolutionOption, - fromFileName: string, - moduleName: string, - resolutionMode: ts.ModuleKind.ESNext | ts.ModuleKind.CommonJS | undefined - ): string[] | undefined; - createProgram(moduleResolution: ResolutionOption, rootNames: string[]): ts.Program; +export interface CompilerHosts { + node10: CompilerHostWrapper; + node16: CompilerHostWrapper; + bundler: CompilerHostWrapper; } -export function createMultiCompilerHost(fs: Package): MultiCompilerHost { - const getCanonicalFileName = ts.createGetCanonicalFileName(false); - const toPath = (fileName: string) => ts.toPath(fileName, "/", getCanonicalFileName); - const languageVersion = ts.ScriptTarget.Latest; - const traceCollector = createTraceCollector(); - const compilerOptions: Record = { - node10: { - moduleResolution: ts.ModuleResolutionKind.NodeJs, - module: ts.ModuleKind.CommonJS, - target: ts.ScriptTarget.Latest, - resolveJsonModule: true, - traceResolution: true, - }, - node16: { - moduleResolution: ts.ModuleResolutionKind.Node16, - module: ts.ModuleKind.Node16, - target: ts.ScriptTarget.Latest, - resolveJsonModule: true, - traceResolution: true, - }, - bundler: { - moduleResolution: ts.ModuleResolutionKind.Bundler, - module: ts.ModuleKind.ESNext, +export function createCompilerHosts(fs: Package): CompilerHosts { + return { + node10: new CompilerHostWrapper(fs, ts.ModuleResolutionKind.Node10, ts.ModuleKind.CommonJS), + node16: new CompilerHostWrapper(fs, ts.ModuleResolutionKind.Node16, ts.ModuleKind.Node16), + bundler: new CompilerHostWrapper(fs, ts.ModuleResolutionKind.Bundler, ts.ModuleKind.ESNext), + }; +} + +const getCanonicalFileName = ts.createGetCanonicalFileName(false); +const toPath = (fileName: string) => ts.toPath(fileName, "/", getCanonicalFileName); + +export class CompilerHostWrapper { + private compilerHost: ts.CompilerHost; + private compilerOptions: ts.CompilerOptions; + private normalModuleResolutionCache: ts.ModuleResolutionCache; + private noDtsResolutionModuleResolutionCache: ts.ModuleResolutionCache; + + private traceCache: Record> = {}; + private traceCollector: TraceCollector = new TraceCollector(); + + private languageVersion = ts.ScriptTarget.Latest; + + constructor(fs: Package, moduleResolution: ts.ModuleResolutionKind, moduleKind: ts.ModuleKind) { + this.compilerOptions = { + moduleResolution, + module: moduleKind, target: ts.ScriptTarget.Latest, resolveJsonModule: true, traceResolution: true, - }, - }; - const moduleResolutionCaches: Record< - ResolutionOption, - [normal: ts.ModuleResolutionCache, noDtsResolution: ts.ModuleResolutionCache] - > = { - node10: [ - ts.createModuleResolutionCache("/", getCanonicalFileName, compilerOptions.node10), - ts.createModuleResolutionCache("/", getCanonicalFileName, compilerOptions.node10), - ], - node16: [ - ts.createModuleResolutionCache("/", getCanonicalFileName, compilerOptions.node16), - ts.createModuleResolutionCache("/", getCanonicalFileName, compilerOptions.node16), - ], - bundler: [ - ts.createModuleResolutionCache("/", getCanonicalFileName, compilerOptions.bundler), - ts.createModuleResolutionCache("/", getCanonicalFileName, compilerOptions.bundler), - ], - }; - const compilerHosts: Record = { - node10: createCompilerHost("node10"), - node16: createCompilerHost("node16"), - bundler: createCompilerHost("bundler"), - }; - const traceCache: Record>> = { - node10: {}, - node16: {}, - bundler: {}, - }; - - return { - getSourceFile, - getModuleKindForFile, - resolveModuleName, - createProgram, - getTrace, - }; + }; + this.normalModuleResolutionCache = ts.createModuleResolutionCache("/", getCanonicalFileName, this.compilerOptions); + this.noDtsResolutionModuleResolutionCache = ts.createModuleResolutionCache( + "/", + getCanonicalFileName, + this.compilerOptions + ); + this.compilerHost = this.createCompilerHost(fs); + } - function getSourceFile(fileName: string, moduleResolution: ResolutionOption = "bundler"): ts.SourceFile | undefined { - return compilerHosts[moduleResolution].getSourceFile(fileName, languageVersion); + getSourceFile(fileName: string): ts.SourceFile | undefined { + return this.compilerHost.getSourceFile(fileName, this.languageVersion); } - function getModuleKindForFile(fileName: string, moduleResolution: "node16"): ModuleKind; - function getModuleKindForFile(fileName: string, moduleResolution: ResolutionOption): ModuleKind | undefined; - function getModuleKindForFile(fileName: string, moduleResolution: ResolutionOption): ModuleKind | undefined { - const kind = getImpliedNodeFormatForFile(fileName, moduleResolution); + getModuleKindForFile(fileName: string): ModuleKind | undefined { + const kind = this.getImpliedNodeFormatForFile(fileName); if (kind) { const extension = ts.getAnyExtensionFromPath(fileName); const isExtension = @@ -108,7 +67,7 @@ export function createMultiCompilerHost(fs: Package): MultiCompilerHost { extension === ts.Extension.Mjs || extension === ts.Extension.Mts || extension === ts.Extension.Dmts; - const reasonPackageJsonInfo = isExtension ? undefined : getPackageScopeForPath(fileName); + const reasonPackageJsonInfo = isExtension ? undefined : this.getPackageScopeForPath(fileName); const reasonFileName = isExtension ? fileName : reasonPackageJsonInfo @@ -123,28 +82,26 @@ export function createMultiCompilerHost(fs: Package): MultiCompilerHost { } } - function resolveModuleName( + resolveModuleName( moduleName: string, containingFile: string, - moduleResolution: ResolutionOption, resolutionMode?: ts.ModuleKind.ESNext | ts.ModuleKind.CommonJS, noDtsResolution?: boolean ): ResolveModuleNameResult { - traceCollector.clear(); - const options = compilerOptions[moduleResolution]; + this.traceCollector.clear(); const resolution = ts.resolveModuleName( moduleName, containingFile, - noDtsResolution ? { ...options, noDtsResolution } : options, - compilerHosts[moduleResolution], - moduleResolutionCaches[moduleResolution][+!!noDtsResolution], + noDtsResolution ? { ...this.compilerOptions, noDtsResolution } : this.compilerOptions, + this.compilerHost, + noDtsResolution ? this.noDtsResolutionModuleResolutionCache : this.normalModuleResolutionCache, /*redirectedReference*/ undefined, resolutionMode ); - const trace = traceCollector.read(); + const trace = this.traceCollector.read(); const moduleKey = `${resolutionMode ?? 1}:${moduleName}`; - if (!traceCache[moduleResolution][containingFile]?.[moduleKey]) { - (traceCache[moduleResolution][containingFile] ??= {})[moduleKey] = trace; + if (!this.traceCache[containingFile]?.[moduleKey]) { + (this.traceCache[containingFile] ??= {})[moduleKey] = trace; } return { resolution, @@ -152,24 +109,23 @@ export function createMultiCompilerHost(fs: Package): MultiCompilerHost { }; } - function getTrace( - moduleResolution: ResolutionOption, + getTrace( fromFileName: string, moduleSpecifier: string, - resolutionMode: ts.ModuleKind.ESNext | ts.ModuleKind.CommonJS + resolutionMode: ts.ModuleKind.ESNext | ts.ModuleKind.CommonJS | undefined ): string[] | undefined { - return traceCache[moduleResolution][fromFileName]?.[`${resolutionMode ?? 1}:${moduleSpecifier}`]; + return this.traceCache[fromFileName]?.[`${resolutionMode ?? 1}:${moduleSpecifier}`]; } - function createProgram(moduleResolution: ResolutionOption, rootNames: string[]): ts.Program { + createProgram(rootNames: string[]): ts.Program { return ts.createProgram({ rootNames, - options: compilerOptions[moduleResolution], - host: compilerHosts[moduleResolution], + options: this.compilerOptions, + host: this.compilerHost, }); } - function createCompilerHost(moduleResolution: ResolutionOption): ts.CompilerHost { + private createCompilerHost(fs: Package): ts.CompilerHost { const sourceFileCache = new Map(); return { ...fs, @@ -184,8 +140,8 @@ export function createMultiCompilerHost(fs: Package): MultiCompilerHost { fileName, content, { - languageVersion, - impliedNodeFormat: getImpliedNodeFormatForFile(fileName, moduleResolution), + languageVersion: this.languageVersion, + impliedNodeFormat: this.getImpliedNodeFormatForFile(fileName), }, /*setParentNodes*/ true ); @@ -200,14 +156,19 @@ export function createMultiCompilerHost(fs: Package): MultiCompilerHost { getCanonicalFileName, useCaseSensitiveFileNames: () => false, getNewLine: () => "\n", - trace: traceCollector.trace, - resolveModuleNameLiterals(moduleLiterals, containingFile, _redirectedReference, options, containingSourceFile) { + trace: this.traceCollector.trace, + resolveModuleNameLiterals: ( + moduleLiterals, + containingFile, + _redirectedReference, + options, + containingSourceFile + ) => { return moduleLiterals.map( (literal) => - resolveModuleName( + this.resolveModuleName( literal.text, containingFile, - moduleResolution, ts.getModeForUsageLocation(containingSourceFile, literal), options.noDtsResolution ).resolution @@ -216,44 +177,40 @@ export function createMultiCompilerHost(fs: Package): MultiCompilerHost { }; } - function getImpliedNodeFormatForFile( - fileName: string, - moduleResolution: ResolutionOption - ): ts.ModuleKind.ESNext | ts.ModuleKind.CommonJS | undefined { + private getImpliedNodeFormatForFile(fileName: string): ts.ModuleKind.ESNext | ts.ModuleKind.CommonJS | undefined { return ts.getImpliedNodeFormatForFile( toPath(fileName), - moduleResolutionCaches[moduleResolution][0].getPackageJsonInfoCache(), - compilerHosts[moduleResolution], - compilerOptions[moduleResolution] + this.normalModuleResolutionCache.getPackageJsonInfoCache(), + this.compilerHost, + this.compilerOptions ); } - function getPackageScopeForPath(fileName: string): ts.PackageJsonInfo | undefined { - // Which compiler options get used here is irrelevant. - // Use the node16 cache because package.json it should be a hit. + private getPackageScopeForPath(fileName: string): ts.PackageJsonInfo | undefined { return ts.getPackageScopeForPath( fileName, ts.getTemporaryModuleResolutionState( - moduleResolutionCaches.node16[0].getPackageJsonInfoCache(), - compilerHosts.node16, - compilerOptions.node16 + // TODO: consider always using the node16 cache because package.json should be a hit + this.normalModuleResolutionCache.getPackageJsonInfoCache(), + this.compilerHost, + this.compilerOptions ) ); } +} - function createTraceCollector() { - const traces: string[] = []; - return { - trace: (message: string) => traces.push(message), - read: () => { - const result = traces.slice(); - clear(); - return result; - }, - clear, - }; - function clear() { - traces.length = 0; - } +class TraceCollector { + private traces: string[] = []; + + trace = (message: string) => { + this.traces.push(message); + }; + read() { + const result = this.traces.slice(); + this.clear(); + return result; + } + clear() { + this.traces.length = 0; } }