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
This commit is contained in:
Andrew Branch
2020-09-11 12:58:40 -07:00
committed by GitHub
parent aa2756a5d7
commit 9c8d11b5ed
26 changed files with 295 additions and 22 deletions
+7
View File
@@ -2509,6 +2509,13 @@ namespace ts {
parseOwnConfigOfJson(json, host, basePath, configFileName, errors) :
parseOwnConfigOfJsonSourceFile(sourceFile!, host, basePath, configFileName, errors);
if (ownConfig.options?.paths) {
// If we end up needing to resolve relative paths from 'paths' relative to
// the config file location, we'll need to know where that config file was.
// Since 'paths' can be inherited from an extended config in another directory,
// we wouldn't know which directory to use unless we store it here.
ownConfig.options.pathsBasePath = basePath;
}
if (ownConfig.extendedConfigPath) {
// copy the resolution stack so it is never reused between branches in potential diamond-problem scenarios.
resolutionStack = resolutionStack.concat([resolvedPath]);
+4 -4
View File
@@ -3493,10 +3493,6 @@
"category": "Error",
"code": 5059
},
"Option 'paths' cannot be used without specifying '--baseUrl' option.": {
"category": "Error",
"code": 5060
},
"Pattern '{0}' can have at most one '*' character.": {
"category": "Error",
"code": 5061
@@ -3613,6 +3609,10 @@
"category": "Error",
"code": 5089
},
"Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?": {
"category": "Error",
"code": 5090
},
"Generates a sourcemap for each corresponding '.d.ts' file.": {
"category": "Message",
+8 -4
View File
@@ -788,13 +788,16 @@ namespace ts {
}
function tryLoadModuleUsingPathsIfEligible(extensions: Extensions, moduleName: string, loader: ResolutionKindSpecificLoader, state: ModuleResolutionState) {
const { baseUrl, paths } = state.compilerOptions;
if (baseUrl && paths && !pathIsRelative(moduleName)) {
const { baseUrl, paths, pathsBasePath } = state.compilerOptions;
if (paths && !pathIsRelative(moduleName)) {
if (state.traceEnabled) {
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName);
if (baseUrl) {
trace(state.host, Diagnostics.baseUrl_option_is_set_to_0_using_this_value_to_resolve_non_relative_module_name_1, baseUrl, moduleName);
}
trace(state.host, Diagnostics.paths_option_is_specified_looking_for_a_pattern_to_match_module_name_0, moduleName);
}
return tryLoadModuleUsingPaths(extensions, moduleName, baseUrl, paths, loader, /*onlyRecordFailures*/ false, state);
const baseDirectory = baseUrl ?? Debug.checkDefined(pathsBasePath || state.host.getCurrentDirectory?.(), "Encountered 'paths' without a 'baseUrl', config file, or host 'getCurrentDirectory'.");
return tryLoadModuleUsingPaths(extensions, moduleName, baseDirectory, paths, loader, /*onlyRecordFailures*/ false, state);
}
}
@@ -1368,6 +1371,7 @@ namespace ts {
}
const resolved = forEach(paths[matchedPatternText], subst => {
const path = matchedStar ? subst.replace("*", matchedStar) : subst;
// When baseUrl is not specified, the command line parser resolves relative paths to the config file location.
const candidate = normalizePath(combinePaths(baseDirectory, path));
if (state.traceEnabled) {
trace(state.host, Diagnostics.Trying_substitution_0_candidate_module_location_Colon_1, subst, path);
+4 -5
View File
@@ -2994,10 +2994,6 @@ namespace ts {
}
}
if (options.paths && options.baseUrl === undefined) {
createDiagnosticForOptionName(Diagnostics.Option_paths_cannot_be_used_without_specifying_baseUrl_option, "paths");
}
if (options.composite) {
if (options.declaration === false) {
createDiagnosticForOptionName(Diagnostics.Composite_projects_may_not_disable_declaration_emit, "declaration");
@@ -3056,6 +3052,9 @@ namespace ts {
if (!hasZeroOrOneAsteriskCharacter(subst)) {
createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Substitution_0_in_pattern_1_can_have_at_most_one_Asterisk_character, subst, key);
}
if (!options.baseUrl && !pathIsRelative(subst) && !pathIsAbsolute(subst)) {
createDiagnosticForOptionPathKeyValue(key, i, Diagnostics.Non_relative_paths_are_not_allowed_when_baseUrl_is_not_set_Did_you_forget_a_leading_Slash);
}
}
else {
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 {
});
}
function createDiagnosticForOptionPathKeyValue(key: string, valueIndex: number, message: DiagnosticMessage, arg0: string | number, arg1: string | number, arg2?: string | number) {
function createDiagnosticForOptionPathKeyValue(key: string, valueIndex: number, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number) {
let needCompilerDiagnostic = true;
const pathsSyntax = getOptionPathsSyntax();
for (const pathProp of pathsSyntax) {
+2
View File
@@ -5787,6 +5787,8 @@ namespace ts {
outDir?: string;
outFile?: string;
paths?: MapLike<string[]>;
/** The directory of the config file that specified 'paths'. Used to resolve relative paths when 'baseUrl' is absent. */
/*@internal*/ pathsBasePath?: string;
/*@internal*/ plugins?: PluginImport[];
preserveConstEnums?: boolean;
preserveSymlinks?: boolean;
+1 -1
View File
@@ -256,7 +256,7 @@ namespace compiler {
if (compilerOptions.skipDefaultLibCheck === undefined) compilerOptions.skipDefaultLibCheck = true;
if (compilerOptions.noErrorTruncation === undefined) compilerOptions.noErrorTruncation = true;
const preProgram = ts.length(rootFiles) < 100 ? ts.createProgram(rootFiles || [], { ...compilerOptions, traceResolution: false }, host) : undefined;
const preProgram = ts.length(rootFiles) < 100 ? ts.createProgram(rootFiles || [], { ...compilerOptions, configFile: compilerOptions.configFile, traceResolution: false }, host) : undefined;
const preErrors = preProgram && ts.getPreEmitDiagnostics(preProgram);
const program = ts.createProgram(rootFiles || [], compilerOptions, host);
+16
View File
@@ -204,4 +204,20 @@ namespace ts {
assert.isEmpty(program.getSemanticDiagnostics());
});
});
describe("unittests:: programApi:: CompilerOptions relative paths", () => {
it("resolves relative paths by getCurrentDirectory", () => {
const main = new documents.TextDocument("/main.ts", "import \"module\";");
const mod = new documents.TextDocument("/lib/module.ts", "declare const foo: any;");
const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" });
const program = createProgram(["./main.ts"], {
paths: { "*": ["./lib/*"] }
}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
assert.isEmpty(program.getConfigFileParsingDiagnostics());
assert.isEmpty(program.getGlobalDiagnostics());
assert.isEmpty(program.getSemanticDiagnostics());
});
});
}
@@ -0,0 +1,23 @@
c:/root/tsconfig.json(5,17): error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
c:/root/tsconfig.json(6,17): error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
==== c:/root/tsconfig.json (2 errors) ====
{
"compilerOptions": {
"paths": {
"*": [
"*",
~~~
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
"generated/*"
~~~~~~~~~~~~~
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
]
}
}
}
==== c:/root/f1.ts (0 errors) ====
export var x = 1;
@@ -0,0 +1,11 @@
//// [f1.ts]
export var x = 1;
//// [f1.js]
define(["require", "exports"], function (require, exports) {
"use strict";
exports.__esModule = true;
exports.x = void 0;
exports.x = 1;
});
@@ -0,0 +1,4 @@
=== c:/root/f1.ts ===
export var x = 1;
>x : Symbol(x, Decl(f1.ts, 0, 10))
@@ -0,0 +1,5 @@
=== c:/root/f1.ts ===
export var x = 1;
>x : number
>1 : 1
@@ -1,16 +1,18 @@
c:/root/tsconfig.json(4,9): error TS5060: Option 'paths' cannot be used without specifying '--baseUrl' option.
c:/root/tsconfig.json(5,17): error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
c:/root/tsconfig.json(6,17): error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
==== c:/root/tsconfig.json (1 errors) ====
// paths should error in the absence of baseurl
==== c:/root/tsconfig.json (2 errors) ====
{
"compilerOptions": {
"paths": {
~~~~~~~
!!! error TS5060: Option 'paths' cannot be used without specifying '--baseUrl' option.
"*": [
"*",
~~~
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
"generated/*"
~~~~~~~~~~~~~
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
]
}
}
@@ -0,0 +1,24 @@
//// [tests/cases/compiler/pathMappingInheritedBaseUrl.ts] ////
//// [tsconfig.base.json]
{
"compilerOptions": {
"baseUrl": "."
}
}
//// [index.ts]
export const p1 = 0;
//// [index.ts]
import { p1 } from "p1";
//// [index.js]
"use strict";
exports.__esModule = true;
exports.p1 = void 0;
exports.p1 = 0;
//// [index.js]
"use strict";
exports.__esModule = true;
@@ -0,0 +1,17 @@
//// [tests/cases/compiler/pathMappingWithoutBaseUrl1.ts] ////
//// [index.ts]
export const p1 = 0;
//// [index.ts]
import { p1 } from "p1";
//// [index.js]
"use strict";
exports.__esModule = true;
exports.p1 = void 0;
exports.p1 = 0;
//// [index.js]
"use strict";
exports.__esModule = true;
@@ -0,0 +1,26 @@
//// [tests/cases/compiler/pathMappingWithoutBaseUrl2.ts] ////
//// [tsconfig.base.json]
{
"compilerOptions": {
"paths": {
"p1": ["./lib/p1"]
}
}
}
//// [index.ts]
export const p1 = 0;
//// [index.ts]
import { p1 } from "p1";
//// [index.js]
"use strict";
exports.__esModule = true;
exports.p1 = void 0;
exports.p1 = 0;
//// [index.js]
"use strict";
exports.__esModule = true;
@@ -0,0 +1,25 @@
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 './'?
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 './'?
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 './'?
==== tests/cases/compiler/tsconfig.json (3 errors) ====
{
"compilerOptions": {
"traceResolution": true,
"paths": {
"@interface/*": ["src/interface/*"],
~~~~~~~~~~~~~~~~~
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
"@blah": ["blah"],
~~~~~~
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
"@humbug/*": ["*/generated"]
~~~~~~~~~~~~~
!!! error TS5090: Non-relative paths are not allowed when 'baseUrl' is not set. Did you forget a leading './'?
}
}
}
==== tests/cases/compiler/src/main.ts (0 errors) ====
import 'someModule';
@@ -0,0 +1,7 @@
//// [main.ts]
import 'someModule';
//// [main.js]
"use strict";
exports.__esModule = true;
require("someModule");
@@ -0,0 +1,21 @@
[
"======== Resolving module 'someModule' from 'tests/cases/compiler/src/main.ts'. ========",
"Module resolution kind is not specified, using 'NodeJs'.",
"'paths' option is specified, looking for a pattern to match module name 'someModule'.",
"Loading module 'someModule' from 'node_modules' folder, target file type 'TypeScript'.",
"Directory 'tests/cases/compiler/src/node_modules' does not exist, skipping all lookups in it.",
"Directory 'tests/cases/compiler/node_modules' does not exist, skipping all lookups in it.",
"Directory 'tests/cases/node_modules' does not exist, skipping all lookups in it.",
"Directory 'tests/node_modules' does not exist, skipping all lookups in it.",
"Directory 'node_modules' does not exist, skipping all lookups in it.",
"Directory '/node_modules' does not exist, skipping all lookups in it.",
"'paths' option is specified, looking for a pattern to match module name 'someModule'.",
"Loading module 'someModule' from 'node_modules' folder, target file type 'JavaScript'.",
"Directory 'tests/cases/compiler/src/node_modules' does not exist, skipping all lookups in it.",
"Directory 'tests/cases/compiler/node_modules' does not exist, skipping all lookups in it.",
"Directory 'tests/cases/node_modules' does not exist, skipping all lookups in it.",
"Directory 'tests/node_modules' does not exist, skipping all lookups in it.",
"Directory 'node_modules' does not exist, skipping all lookups in it.",
"Directory '/node_modules' does not exist, skipping all lookups in it.",
"======== Module name 'someModule' was not resolved. ========"
]
@@ -179,6 +179,7 @@ a_1.X;
"./*"
]
},
"pathsBasePath": "/src",
"listFiles": true,
"configFilePath": "./tsconfig.b.json"
},
@@ -1,8 +1,6 @@
// @module: amd
// @traceResolution: true
// paths should error in the absence of baseurl
// @filename: c:/root/tsconfig.json
{
"compilerOptions": {
@@ -1,7 +1,6 @@
// @module: commonjs
// @traceResolution: true
// paths should error in the absence of baseurl
// @filename: c:/root/tsconfig.json
{
"compilerOptions": {
@@ -0,0 +1,25 @@
// @noTypesAndSymbols: true
// @Filename: /other/tsconfig.base.json
{
"compilerOptions": {
"baseUrl": "."
}
}
// @Filename: /project/tsconfig.json
{
"extends": "../other/tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"paths": {
"p1": ["./lib/p1"]
}
}
}
// @Filename: /other/lib/p1/index.ts
export const p1 = 0;
// @Filename: /project/index.ts
import { p1 } from "p1";
@@ -0,0 +1,17 @@
// @noTypesAndSymbols: true
// @Filename: /project/tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"paths": {
"p1": ["./lib/p1"]
}
}
}
// @Filename: /project/lib/p1/index.ts
export const p1 = 0;
// @Filename: /project/index.ts
import { p1 } from "p1";
@@ -0,0 +1,24 @@
// @noTypesAndSymbols: true
// @Filename: /other/tsconfig.base.json
{
"compilerOptions": {
"paths": {
"p1": ["./lib/p1"]
}
}
}
// @Filename: /project/tsconfig.json
{
"extends": "../other/tsconfig.base.json",
"compilerOptions": {
"module": "commonjs"
}
}
// @Filename: /other/lib/p1/index.ts
export const p1 = 0;
// @Filename: /project/index.ts
import { p1 } from "p1";
+15
View File
@@ -0,0 +1,15 @@
// @noTypesAndSymbols: true
// @Filename: tsconfig.json
{
"compilerOptions": {
"traceResolution": true,
"paths": {
"@interface/*": ["src/interface/*"],
"@blah": ["blah"],
"@humbug/*": ["*/generated"]
}
}
}
// @filename: src/main.ts
import 'someModule';