Skip to content

Commit 9c8d11b

Browse files
authored
Allow 'paths' without 'baseUrl' (#40101)
* Allow paths without baseUrl * Remove exception for leading * paths * Add comment, remove commented code * Update baselines * Remove unnecessary default * Fix test harness * Fix baseline * Resolve relative to host.getCurrentDirectory() with createProgram API
1 parent aa2756a commit 9c8d11b

26 files changed

+295
-22
lines changed

src/compiler/commandLineParser.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2509,6 +2509,13 @@ namespace ts {
25092509
parseOwnConfigOfJson(json, host, basePath, configFileName, errors) :
25102510
parseOwnConfigOfJsonSourceFile(sourceFile!, host, basePath, configFileName, errors);
25112511

2512+
if (ownConfig.options?.paths) {
2513+
// If we end up needing to resolve relative paths from 'paths' relative to
2514+
// the config file location, we'll need to know where that config file was.
2515+
// Since 'paths' can be inherited from an extended config in another directory,
2516+
// we wouldn't know which directory to use unless we store it here.
2517+
ownConfig.options.pathsBasePath = basePath;
2518+
}
25122519
if (ownConfig.extendedConfigPath) {
25132520
// copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios.
25142521
resolutionStack = resolutionStack.concat([resolvedPath]);

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3493,10 +3493,6 @@
34933493
"category": "Error",
34943494
"code": 5059
34953495
},
3496-
"Option 'paths' cannot be used without specifying '--baseUrl' option.": {
3497-
"category": "Error",
3498-
"code": 5060
3499-
},
35003496
"Pattern '{0}' can have at most one '*' character.": {
35013497
"category": "Error",
35023498
"code": 5061
@@ -3613,6 +3609,10 @@
36133609
"category": "Error",
36143610
"code": 5089
36153611
},
3612+
"Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?": {
3613+
"category": "Error",
3614+
"code": 5090
3615+
},
36163616

36173617
"Generates a sourcemap for each corresponding '.d.ts' file.": {
36183618
"category": "Message",

src/compiler/moduleNameResolver.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -788,13 +788,16 @@ namespace ts {
788788
}
789789

790790
function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) {
791-
const { baseUrl, paths } = state.compilerOptions;
792-
if (baseUrl && paths && !pathIsRelative(moduleName)) {
791+
const { baseUrl, paths, pathsBasePath } = state.compilerOptions;
792+
if (paths && !pathIsRelative(moduleName)) {
793793
if (state.traceEnabled) {
794-
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName);
794+
if (baseUrl) {
795+
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName);
796+
}
795797
trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName);
796798
}
797-
return tryLoadModuleUsingPaths(extensions, moduleName, baseUrl, paths, loader, /*onlyRecordFailures*/ false, state);
799+
const baseDirectory = baseUrl ?? Debug.checkDefined(pathsBasePath || state.host.getCurrentDirectory?.(), "Encountered 'paths' without a 'baseUrl', config file, or host 'getCurrentDirectory'.");
800+
return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, loader, /*onlyRecordFailures*/ false, state);
798801
}
799802
}
800803

@@ -1368,6 +1371,7 @@ namespace ts {
13681371
}
13691372
const resolved = forEach(paths[matchedPatternText], subst => {
13701373
const path = matchedStar ? subst.replace("*", matchedStar) : subst;
1374+
// When baseUrl is not specified, the command line parser resolves relative paths to the config file location.
13711375
const candidate = normalizePath(combinePaths(baseDirectory, path));
13721376
if (state.traceEnabled) {
13731377
trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path);

src/compiler/program.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2994,10 +2994,6 @@ namespace ts {
29942994
}
29952995
}
29962996

2997-
if (options.paths && options.baseUrl === undefined) {
2998-
createDiagnosticForOptionName(Diagnostics.Option_paths_cannot_be_used_without_specifying_baseUrl_option, "paths");
2999-
}
3000-
30012997
if (options.composite) {
30022998
if (options.declaration === false) {
30032999
createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_declaration_emit, "declaration");
@@ -3056,6 +3052,9 @@ namespace ts {
30563052
if (!hasZeroOrOneAsteriskCharacter(subst)) {
30573053
createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_in_pattern_1_can_have_at_most_one_Asterisk_character, subst, key);
30583054
}
3055+
if (!options.baseUrl && !pathIsRelative(subst) && !pathIsAbsolute(subst)) {
3056+
createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Non_relative_paths_are_not_allowed_when_baseUrl_is_not_set_Did_you_forget_a_leading_Slash);
3057+
}
30593058
}
30603059
else {
30613060
createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_for_pattern_1_has_incorrect_type_expected_string_got_2, subst, key, typeOfSubst);
@@ -3339,7 +3338,7 @@ namespace ts {
33393338
});
33403339
}
33413340

3342-
function createDiagnosticForOptionPathKeyValue(key: string, valueIndex: number, message: DiagnosticMessage, arg0: string | number, arg1: string | number, arg2?: string | number) {
3341+
function createDiagnosticForOptionPathKeyValue(key: string, valueIndex: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number) {
33433342
let needCompilerDiagnostic = true;
33443343
const pathsSyntax = getOptionPathsSyntax();
33453344
for (const pathProp of pathsSyntax) {

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5787,6 +5787,8 @@ namespace ts {
57875787
outDir?: string;
57885788
outFile?: string;
57895789
paths?: MapLike<string[]>;
5790+
/** The directory of the config file that specified 'paths'. Used to resolve relative paths when 'baseUrl' is absent. */
5791+
/*@internal*/ pathsBasePath?: string;
57905792
/*@internal*/ plugins?: PluginImport[];
57915793
preserveConstEnums?: boolean;
57925794
preserveSymlinks?: boolean;

src/harness/compilerImpl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ namespace compiler {
256256
if (compilerOptions.skipDefaultLibCheck === undefined) compilerOptions.skipDefaultLibCheck = true;
257257
if (compilerOptions.noErrorTruncation === undefined) compilerOptions.noErrorTruncation = true;
258258

259-
const preProgram = ts.length(rootFiles) < 100 ? ts.createProgram(rootFiles || [], { ...compilerOptions, traceResolution: false }, host) : undefined;
259+
const preProgram = ts.length(rootFiles) < 100 ? ts.createProgram(rootFiles || [], { ...compilerOptions, configFile: compilerOptions.configFile, traceResolution: false }, host) : undefined;
260260
const preErrors = preProgram && ts.getPreEmitDiagnostics(preProgram);
261261

262262
const program = ts.createProgram(rootFiles || [], compilerOptions, host);

src/testRunner/unittests/programApi.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,20 @@ namespace ts {
204204
assert.isEmpty(program.getSemanticDiagnostics());
205205
});
206206
});
207+
208+
describe("unittests:: programApi:: CompilerOptions relative paths", () => {
209+
it("resolves relative paths by getCurrentDirectory", () => {
210+
const main = new documents.TextDocument("/main.ts", "import \"module\";");
211+
const mod = new documents.TextDocument("/lib/module.ts", "declare const foo: any;");
212+
213+
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" });
214+
const program = createProgram(["./main.ts"], {
215+
paths: { "*": ["./lib/*"] }
216+
}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
217+
218+
assert.isEmpty(program.getConfigFileParsingDiagnostics());
219+
assert.isEmpty(program.getGlobalDiagnostics());
220+
assert.isEmpty(program.getSemanticDiagnostics());
221+
});
222+
});
207223
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
c:/root/tsconfig.json(5,17): error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
2+
c:/root/tsconfig.json(6,17): error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
3+
4+
5+
==== c:/root/tsconfig.json (2 errors) ====
6+
{
7+
"compilerOptions": {
8+
"paths": {
9+
"*": [
10+
"*",
11+
~~~
12+
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
13+
"generated/*"
14+
~~~~~~~~~~~~~
15+
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
16+
]
17+
}
18+
}
19+
}
20+
21+
==== c:/root/f1.ts (0 errors) ====
22+
export var x = 1;
23+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//// [f1.ts]
2+
export var x = 1;
3+
4+
5+
//// [f1.js]
6+
define(["require", "exports"], function (require, exports) {
7+
"use strict";
8+
exports.__esModule = true;
9+
exports.x = void 0;
10+
exports.x = 1;
11+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
=== c:/root/f1.ts ===
2+
export var x = 1;
3+
>x : Symbol(x, Decl(f1.ts, 0, 10))
4+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
=== c:/root/f1.ts ===
2+
export var x = 1;
3+
>x : number
4+
>1 : 1
5+

tests/baselines/reference/pathMappingBasedModuleResolution1_node.errors.txt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
c:/root/tsconfig.json(4,9): error TS5060: Option 'paths' cannot be used without specifying '--baseUrl' option.
1+
c:/root/tsconfig.json(5,17): error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
2+
c:/root/tsconfig.json(6,17): error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
23

34

4-
==== c:/root/tsconfig.json (1 errors) ====
5-
// paths should error in the absence of baseurl
5+
==== c:/root/tsconfig.json (2 errors) ====
66
{
77
"compilerOptions": {
88
"paths": {
9-
~~~~~~~
10-
!!! error TS5060: Option 'paths' cannot be used without specifying '--baseUrl' option.
119
"*": [
1210
"*",
11+
~~~
12+
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
1313
"generated/*"
14+
~~~~~~~~~~~~~
15+
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
1416
]
1517
}
1618
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//// [tests/cases/compiler/pathMappingInheritedBaseUrl.ts] ////
2+
3+
//// [tsconfig.base.json]
4+
{
5+
"compilerOptions": {
6+
"baseUrl": "."
7+
}
8+
}
9+
10+
//// [index.ts]
11+
export const p1 = 0;
12+
13+
//// [index.ts]
14+
import { p1 } from "p1";
15+
16+
17+
//// [index.js]
18+
"use strict";
19+
exports.__esModule = true;
20+
exports.p1 = void 0;
21+
exports.p1 = 0;
22+
//// [index.js]
23+
"use strict";
24+
exports.__esModule = true;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//// [tests/cases/compiler/pathMappingWithoutBaseUrl1.ts] ////
2+
3+
//// [index.ts]
4+
export const p1 = 0;
5+
6+
//// [index.ts]
7+
import { p1 } from "p1";
8+
9+
10+
//// [index.js]
11+
"use strict";
12+
exports.__esModule = true;
13+
exports.p1 = void 0;
14+
exports.p1 = 0;
15+
//// [index.js]
16+
"use strict";
17+
exports.__esModule = true;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [tests/cases/compiler/pathMappingWithoutBaseUrl2.ts] ////
2+
3+
//// [tsconfig.base.json]
4+
{
5+
"compilerOptions": {
6+
"paths": {
7+
"p1": ["./lib/p1"]
8+
}
9+
}
10+
}
11+
12+
//// [index.ts]
13+
export const p1 = 0;
14+
15+
//// [index.ts]
16+
import { p1 } from "p1";
17+
18+
19+
//// [index.js]
20+
"use strict";
21+
exports.__esModule = true;
22+
exports.p1 = void 0;
23+
exports.p1 = 0;
24+
//// [index.js]
25+
"use strict";
26+
exports.__esModule = true;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
tests/cases/compiler/tsconfig.json(5,26): error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
2+
tests/cases/compiler/tsconfig.json(6,19): error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
3+
tests/cases/compiler/tsconfig.json(7,23): error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
4+
5+
6+
==== tests/cases/compiler/tsconfig.json (3 errors) ====
7+
{
8+
"compilerOptions": {
9+
"traceResolution": true,
10+
"paths": {
11+
"@interface/*": ["src/interface/*"],
12+
~~~~~~~~~~~~~~~~~
13+
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
14+
"@blah": ["blah"],
15+
~~~~~~
16+
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
17+
"@humbug/*": ["*/generated"]
18+
~~~~~~~~~~~~~
19+
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
20+
}
21+
}
22+
}
23+
24+
==== tests/cases/compiler/src/main.ts (0 errors) ====
25+
import 'someModule';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//// [main.ts]
2+
import 'someModule';
3+
4+
//// [main.js]
5+
"use strict";
6+
exports.__esModule = true;
7+
require("someModule");
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[
2+
"======== Resolving module 'someModule' from 'tests/cases/compiler/src/main.ts'. ========",
3+
"Module resolution kind is not specified, using 'NodeJs'.",
4+
"'paths' option is specified, looking for a pattern to match module name 'someModule'.",
5+
"Loading module 'someModule' from 'node_modules' folder, target file type 'TypeScript'.",
6+
"Directory 'tests/cases/compiler/src/node_modules' does not exist, skipping all lookups in it.",
7+
"Directory 'tests/cases/compiler/node_modules' does not exist, skipping all lookups in it.",
8+
"Directory 'tests/cases/node_modules' does not exist, skipping all lookups in it.",
9+
"Directory 'tests/node_modules' does not exist, skipping all lookups in it.",
10+
"Directory 'node_modules' does not exist, skipping all lookups in it.",
11+
"Directory '/node_modules' does not exist, skipping all lookups in it.",
12+
"'paths' option is specified, looking for a pattern to match module name 'someModule'.",
13+
"Loading module 'someModule' from 'node_modules' folder, target file type 'JavaScript'.",
14+
"Directory 'tests/cases/compiler/src/node_modules' does not exist, skipping all lookups in it.",
15+
"Directory 'tests/cases/compiler/node_modules' does not exist, skipping all lookups in it.",
16+
"Directory 'tests/cases/node_modules' does not exist, skipping all lookups in it.",
17+
"Directory 'tests/node_modules' does not exist, skipping all lookups in it.",
18+
"Directory 'node_modules' does not exist, skipping all lookups in it.",
19+
"Directory '/node_modules' does not exist, skipping all lookups in it.",
20+
"======== Module name 'someModule' was not resolved. ========"
21+
]

tests/baselines/reference/tsbuild/transitiveReferences/initial-build/builds-correctly.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ a_1.X;
179179
"./*"
180180
]
181181
},
182+
"pathsBasePath": "/src",
182183
"listFiles": true,
183184
"configFilePath": "./tsconfig.b.json"
184185
},

tests/cases/compiler/pathMappingBasedModuleResolution1_classic.ts renamed to tests/cases/compiler/pathMappingBasedModuleResolution1_amd.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// @module: amd
22
// @traceResolution: true
33

4-
// paths should error in the absence of baseurl
5-
64
// @filename: c:/root/tsconfig.json
75
{
86
"compilerOptions": {

tests/cases/compiler/pathMappingBasedModuleResolution1_node.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// @module: commonjs
22
// @traceResolution: true
33

4-
// paths should error in the absence of baseurl
54
// @filename: c:/root/tsconfig.json
65
{
76
"compilerOptions": {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// @noTypesAndSymbols: true
2+
3+
// @Filename: /other/tsconfig.base.json
4+
{
5+
"compilerOptions": {
6+
"baseUrl": "."
7+
}
8+
}
9+
10+
// @Filename: /project/tsconfig.json
11+
{
12+
"extends": "../other/tsconfig.base.json",
13+
"compilerOptions": {
14+
"module": "commonjs",
15+
"paths": {
16+
"p1": ["./lib/p1"]
17+
}
18+
}
19+
}
20+
21+
// @Filename: /other/lib/p1/index.ts
22+
export const p1 = 0;
23+
24+
// @Filename: /project/index.ts
25+
import { p1 } from "p1";

0 commit comments

Comments
 (0)