Skip to content

Commit

Permalink
hack: resolve from outDir
Browse files Browse the repository at this point in the history
Proof of concept for resolving microsoft#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
  • Loading branch information
alexeagle authored and Greg Magolan committed Mar 10, 2022
1 parent ea4791d commit 0afea27
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 1 deletion.
8 changes: 8 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,14 @@ namespace ts {
category: Diagnostics.Modules,
description: Diagnostics.Specify_multiple_folders_that_act_like_Slashnode_modules_Slash_types
},
{
name: "resolveFromOutDir",
type: "boolean",
affectsModuleResolution: true,
category: Diagnostics.Modules,
description: Diagnostics.Allow_resolving_files_relative_to_the_output_directory,
defaultValueDescription: false
},
{
name: "types",
type: "list",
Expand Down
17 changes: 17 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -5721,6 +5733,11 @@
"category": "Message",
"code": 6718
},
"Allow resolving files relative to the output directory.": {
"category": "Message",
"code": 6719
},

"Default catch clause variables as 'unknown' instead of 'any'.": {
"category": "Message",
"code": 6803
Expand Down
51 changes: 50 additions & 1 deletion src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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;

Expand All @@ -1114,6 +1118,51 @@ namespace ts {
}
}

function tryLoadModuleUsingOutDirIfEligible(extensions: Extensions, moduleName: string, containingDirectory: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState): Resolved | undefined {
const { baseUrl, resolveFromOutDir, outDir, rootDir } = state.compilerOptions;
if (!resolveFromOutDir) {
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);
}

// COMMENT FOR REVIEWER: Is there a more robust way to determine the base directory here?
var baseDirectory = baseUrl;
if (!baseDirectory && state.host.getCurrentDirectory) {
baseDirectory = state.host.getCurrentDirectory();
}
if (!baseDirectory) {
return undefined;
}

// COMMENT FOR REVIEWER: I've seen rootDir be relative path and and absolute path so
// handling both cases here to come up with an absolute normalizedPrefix path
var normalizedPrefix = rootDir && ts.startsWith(rootDir, ts.directorySeparator) ?
ts.normalizePath(rootDir) :
ts.normalizePath(ts.combinePaths(baseDirectory, rootDir));
var candidate = ts.normalizePath(ts.combinePaths(containingDirectory, moduleName));

// COMMENT FOR REVIEWER: No ts.relativePath() function that I could find. Is there one
// somewhere that I'm not aware of?
var suffix = require("path").relative(normalizedPrefix, candidate);
candidate = ts.normalizePath(ts.combinePaths(baseDirectory, 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 {

Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6156,6 +6156,7 @@ namespace ts {
project?: string;
/* @internal */ pretty?: boolean;
reactNamespace?: string;
resolveFromOutDir?: boolean;
jsxFactory?: string;
jsxFragmentFactory?: string;
jsxImportSource?: string;
Expand Down

0 comments on commit 0afea27

Please sign in to comment.