Skip to content

Commit 48e991a

Browse files
committed
feat: substitute path that starts with ${configDir}/ in tsconfig.compilerOptions.paths
closes #129 NOTE: All tests cases are just a head replacement of `${configDir}`, so we are constrained as such.
1 parent ee22b66 commit 48e991a

File tree

5 files changed

+70
-10
lines changed

5 files changed

+70
-10
lines changed

Diff for: fixtures/tsconfig/cases/paths_template_variable/foo.js

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "../../tsconfig_template_variable.json"
3+
}

Diff for: fixtures/tsconfig/tsconfig_template_variable.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"paths": {
4+
"foo": ["${configDir}/foo.js"]
5+
}
6+
}
7+
}

Diff for: src/tests/tsconfig_paths.rs

+25
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,31 @@ fn test_paths_and_base_url() {
168168
}
169169
}
170170

171+
// Template variable ${configDir} for substitution of config files directory path
172+
// https://github.com/microsoft/TypeScript/pull/58042
173+
#[test]
174+
fn test_template_variable() {
175+
let f = super::fixture_root().join("tsconfig");
176+
let f2 = f.join("cases").join("paths_template_variable");
177+
178+
#[rustfmt::skip]
179+
let pass = [
180+
(f2.clone(), "foo", f2.join("foo.js")),
181+
];
182+
183+
for (dir, request, expected) in pass {
184+
let resolver = Resolver::new(ResolveOptions {
185+
tsconfig: Some(TsconfigOptions {
186+
config_file: f2.join("tsconfig.json"),
187+
references: TsconfigReferences::Auto,
188+
}),
189+
..ResolveOptions::default()
190+
});
191+
let resolved_path = resolver.resolve(&dir, request).map(|f| f.full_path());
192+
assert_eq!(resolved_path, Ok(expected), "{request} {dir:?}");
193+
}
194+
}
195+
171196
#[cfg(not(target_os = "windows"))] // MemoryFS's path separator is always `/` so the test will not pass in windows.
172197
mod windows_test {
173198
use std::path::{Path, PathBuf};

Diff for: src/tsconfig.rs

+35-10
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use std::{
33
sync::Arc,
44
};
55

6-
use crate::PathUtil;
76
use serde::Deserialize;
87
use typescript_tsconfig_json::{CompilerOptionsPathsMap, ExtendsField};
98

9+
use crate::PathUtil;
10+
1011
#[derive(Debug, Deserialize)]
1112
#[serde(rename_all = "camelCase")]
1213
pub struct TsConfig {
@@ -71,24 +72,25 @@ impl TsConfig {
7172
Ok(tsconfig)
7273
}
7374

74-
/// Directory to `package.json`
75+
fn base_path(&self) -> &Path {
76+
self.compiler_options
77+
.base_url
78+
.as_ref()
79+
.map_or_else(|| self.directory(), |path| path.as_ref())
80+
}
81+
82+
/// Directory to `tsconfig.json`
7583
///
7684
/// # Panics
7785
///
78-
/// * When the package.json path is misconfigured.
86+
/// * When the `tsconfig.json` path is misconfigured.
7987
pub fn directory(&self) -> &Path {
8088
debug_assert!(self.path.file_name().is_some());
8189
self.path.parent().unwrap()
8290
}
8391

84-
fn base_path(&self) -> &Path {
85-
self.compiler_options
86-
.base_url
87-
.as_ref()
88-
.map_or_else(|| self.directory(), |path| path.as_ref())
89-
}
90-
9192
pub fn extend_tsconfig(&mut self, tsconfig: &Self) {
93+
let dir = self.directory().to_path_buf();
9294
let compiler_options = &mut self.compiler_options;
9395
if compiler_options.paths.is_none() {
9496
compiler_options.paths_base = compiler_options
@@ -100,6 +102,29 @@ impl TsConfig {
100102
if compiler_options.base_url.is_none() {
101103
compiler_options.base_url = tsconfig.compiler_options.base_url.clone();
102104
}
105+
if let Some(paths) = &mut compiler_options.paths {
106+
Self::normalize_paths(&dir, paths);
107+
}
108+
}
109+
110+
fn normalize_paths(directory: &Path, paths: &mut CompilerOptionsPathsMap) {
111+
for paths in paths.values_mut() {
112+
for path in paths {
113+
Self::substitute_template_variable(directory, path);
114+
}
115+
}
116+
}
117+
118+
/// Template variable `${configDir}` for substitution of config files directory path
119+
///
120+
/// NOTE: All tests cases are just a head replacement of `${configDir}`, so we are constrained as such.
121+
///
122+
/// See <https://github.com/microsoft/TypeScript/pull/58042>
123+
fn substitute_template_variable(directory: &Path, path: &mut String) {
124+
const TEMPLATE_VARIABLE: &str = "${configDir}/";
125+
if let Some(stripped_path) = path.strip_prefix(TEMPLATE_VARIABLE) {
126+
*path = directory.join(stripped_path).to_string_lossy().to_string();
127+
}
103128
}
104129

105130
pub fn resolve(&self, path: &Path, specifier: &str) -> Vec<PathBuf> {

0 commit comments

Comments
 (0)