From e7e5c17cbe146a2369f304ee11244cb896201732 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Wed, 9 Mar 2022 13:31:20 -0800 Subject: [PATCH] hack: resolve from outDir Proof of concept for resolving https://github.com/microsoft/TypeScript/issues/37378 Under the new proposed `compilerOptions.resolveFromOutDir` boolean, module resolution is attempted relative to the output folder. This is analogous to loading from the rootDirs, however it allows compilation where the output directory is configured on the command line rather than in the tsconfig.json. See the attached issue for context. TODO: - figure out what tests to add - reason about whether this interacts correctly with other related module resolution conditional logic - verify this works in some Bazel projects where the problem is observed --- src/compiler/diagnosticMessages.json | 12 +++++++++ src/compiler/moduleNameResolver.ts | 39 +++++++++++++++++++++++++++- src/compiler/types.ts | 1 + 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index f211d7aeb20fc..ab1e983818df6 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4433,6 +4433,10 @@ "category": "Message", "code": 6107 }, + "'resolveFromOutDir' option is set, using it to resolve relative module name '{0}'.": { + "category": "Message", + "code": 16107 + }, "Longest matching prefix for '{0}' is '{1}'.": { "category": "Message", "code": 6108 @@ -4441,6 +4445,10 @@ "category": "Message", "code": 6109 }, + "Loading '{0}' from the out dir '{1}', candidate location '{2}'.": { + "category": "Message", + "code": 16109 + }, "Trying other entries in 'rootDirs'.": { "category": "Message", "code": 6110 @@ -4449,6 +4457,10 @@ "category": "Message", "code": 6111 }, + "Module resolution using 'outDir' has failed.": { + "category": "Message", + "code": 16111 + }, "Do not emit 'use strict' directives in module output.": { "category": "Message", "code": 6112 diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index da2324cc74ecb..ac6681b7e7f77 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -1026,12 +1026,13 @@ namespace ts { type ResolutionKindSpecificLoader = (extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState) => Resolved | undefined; /** - * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'paths' and 'rootDirs' - they are used to + * Any module resolution kind can be augmented with optional settings: 'baseUrl', 'resolveFromOutDir', 'paths' and 'rootDirs' - they are used to * mitigate differences between design time structure of the project and its runtime counterpart so the same import name * can be resolved successfully by TypeScript compiler and runtime module loader. * If these settings are set then loading procedure will try to use them to resolve module name and it can of failure it will * fallback to standard resolution routine. * + * 'resolveFromOutDir': TODO document the semantics * - baseUrl - this setting controls how non-relative module names are resolved. If this setting is specified then non-relative * names will be resolved relative to baseUrl: i.e. if baseUrl is '/a/b' then candidate location to resolve module name 'c/d' will * be '/a/b/c/d' @@ -1088,6 +1089,9 @@ namespace ts { function tryLoadModuleUsingOptionalResolutionSettings(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { + const resolvedFromOutDir = tryLoadModuleUsingOutDirIfEligible(extensions, moduleName, containingDirectory, loader, state); + if (resolvedFromOutDir) return resolvedFromOutDir; + const resolved = tryLoadModuleUsingPathsIfEligible(extensions, moduleName, loader, state); if (resolved) return resolved.value; @@ -1114,6 +1118,39 @@ namespace ts { } } + function tryLoadModuleUsingOutDirIfEligible(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { + const { resolveFromOutDir, outDir } = state.compilerOptions; + if (!resolveFromOutDir) { + // FIXME: wire the new option through + // return undefined + } + if (!outDir) { + return undefined; + } + if (state.traceEnabled) { + trace(state.host, Diagnostics.resolveFromOutDir_option_is_set_using_it_to_resolve_relative_module_name_0, moduleName); + } + + let normalizedPrefix = normalizePath(outDir); + + let candidate = normalizePath(combinePaths(containingDirectory, moduleName)); + const suffix = candidate.substr(normalizedPrefix.length); + candidate = combinePaths(normalizePath(outDir), suffix); + if (state.traceEnabled) { + trace(state.host, Diagnostics.Loading_0_from_the_out_dir_1_candidate_location_2, suffix, outDir, candidate); + } + + const resolvedFileName = loader(extensions, candidate, !directoryProbablyExists(containingDirectory, state.host), state); + if (resolvedFileName) { + return resolvedFileName; + } + + if (state.traceEnabled) { + trace(state.host, Diagnostics.Module_resolution_using_outDir_has_failed); + } + return undefined; + } + function tryLoadModuleUsingRootDirs(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5ce2842fa8995..88ea91dcb95a6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6156,6 +6156,7 @@ namespace ts { project?: string; /* @internal */ pretty?: boolean; reactNamespace?: string; + resolveFromOutDir?: boolean; jsxFactory?: string; jsxFragmentFactory?: string; jsxImportSource?: string;