-
-
Notifications
You must be signed in to change notification settings - Fork 938
feat(resolver): support baseUrl in tsconfig.json
#7263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ffcbe88
e57e171
b6f4560
afba8bc
bcace8e
b134da1
b99716b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| --- | ||
| "@biomejs/biome": minor | ||
| --- | ||
|
|
||
| Biome's resolver now supports `baseUrl` if specified in `tsconfig.json`. | ||
|
|
||
| #### Example | ||
|
|
||
| Given the following file structure: | ||
|
|
||
| **`tsconfig.json`** | ||
| ```json | ||
| { | ||
| "compilerOptions": { | ||
| "baseUrl": "./src", | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **`src/foo.ts`** | ||
| ```ts | ||
| export function foo() {} | ||
| ``` | ||
|
|
||
| In this scenario, `import { foo } from "foo";` should work regardless of the | ||
| location of the file containing the `import` statement. | ||
|
|
||
| Fixes [#6432](https://github.com/biomejs/biome/issues/6432). | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -39,14 +39,23 @@ pub struct TsConfigJson { | |||||||||||||||||||
| impl Manifest for TsConfigJson { | ||||||||||||||||||||
| type Language = JsonLanguage; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| fn deserialize_manifest(root: &LanguageRoot<Self::Language>) -> Deserialized<Self> { | ||||||||||||||||||||
| deserialize_from_json_ast::<Self>(root, "") | ||||||||||||||||||||
| fn deserialize_manifest( | ||||||||||||||||||||
| root: &LanguageRoot<Self::Language>, | ||||||||||||||||||||
| path: &Utf8Path, | ||||||||||||||||||||
| ) -> Deserialized<Self> { | ||||||||||||||||||||
| let deserialized = deserialize_from_json_ast::<Self>(root, ""); | ||||||||||||||||||||
| let (mut tsconfig, errors) = deserialized.consume(); | ||||||||||||||||||||
| if let Some(manifest) = tsconfig.as_mut() { | ||||||||||||||||||||
| manifest.initialise_paths(path); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| Deserialized::new(tsconfig, errors) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| fn read_manifest(fs: &dyn biome_fs::FileSystem, path: &Utf8Path) -> Deserialized<Self> { | ||||||||||||||||||||
| match fs.read_file_from_path(path) { | ||||||||||||||||||||
| Ok(content) => { | ||||||||||||||||||||
| let (manifest, errors) = Self::parse(true, path, &content); | ||||||||||||||||||||
| let (manifest, errors) = Self::parse(path, &content); | ||||||||||||||||||||
| Deserialized::new(Some(manifest), errors) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| Err(error) => Deserialized::new(None, vec![Error::from(error)]), | ||||||||||||||||||||
|
|
@@ -55,7 +64,7 @@ impl Manifest for TsConfigJson { | |||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| impl TsConfigJson { | ||||||||||||||||||||
| fn parse(root: bool, path: &Utf8Path, json: &str) -> (Self, Vec<Error>) { | ||||||||||||||||||||
| fn parse(path: &Utf8Path, json: &str) -> (Self, Vec<Error>) { | ||||||||||||||||||||
| let (tsconfig, diagnostics) = deserialize_from_json_str( | ||||||||||||||||||||
| json, | ||||||||||||||||||||
| JsonParserOptions::default() | ||||||||||||||||||||
|
|
@@ -66,34 +75,52 @@ impl TsConfigJson { | |||||||||||||||||||
| .consume(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| let mut tsconfig: Self = tsconfig.unwrap_or_default(); | ||||||||||||||||||||
| tsconfig.root = root; | ||||||||||||||||||||
| tsconfig.path = path.to_path_buf(); | ||||||||||||||||||||
| tsconfig.initialise_paths(path); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| (tsconfig, diagnostics) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /// Initialises the paths stored in the manifest. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// `path` must be an absolute path to the `tsconfig.json` file itself. | ||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does it mean to be absolute to a file? Can we add a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It means the path is absolute, i.e. not relative. Added a |
||||||||||||||||||||
| fn initialise_paths(&mut self, path: &Utf8Path) { | ||||||||||||||||||||
| // Some tests that use UNIX paths are not recognised as absolute on | ||||||||||||||||||||
| // Windows... | ||||||||||||||||||||
| #[cfg(not(target_os = "windows"))] | ||||||||||||||||||||
| debug_assert!(path.is_absolute()); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| self.root = true; // For now we only support root configs. | ||||||||||||||||||||
|
|
||||||||||||||||||||
| self.path = path.to_path_buf(); | ||||||||||||||||||||
| let directory = path.parent(); | ||||||||||||||||||||
| if let Some(base_url) = tsconfig.compiler_options.base_url { | ||||||||||||||||||||
| tsconfig.compiler_options.base_url = | ||||||||||||||||||||
| if let Some(base_url) = self.compiler_options.base_url.as_ref() { | ||||||||||||||||||||
| self.compiler_options.base_url = | ||||||||||||||||||||
| directory.map(|dir| normalize_path(&dir.join(base_url))); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| if tsconfig.compiler_options.paths.is_some() { | ||||||||||||||||||||
| tsconfig.compiler_options.paths_base = | ||||||||||||||||||||
| tsconfig.compiler_options.base_url.as_ref().map_or_else( | ||||||||||||||||||||
| || directory.map_or_else(Default::default, Utf8Path::to_path_buf), | ||||||||||||||||||||
| Clone::clone, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| if self.compiler_options.paths.is_some() { | ||||||||||||||||||||
| self.compiler_options.paths_base = self.compiler_options.base_url.as_ref().map_or_else( | ||||||||||||||||||||
| || directory.map_or_else(Default::default, Utf8Path::to_path_buf), | ||||||||||||||||||||
| Clone::clone, | ||||||||||||||||||||
| ); | ||||||||||||||||||||
| } | ||||||||||||||||||||
| (tsconfig, diagnostics) | ||||||||||||||||||||
| } | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| #[derive(Clone, Debug, Default, Deserializable)] | ||||||||||||||||||||
| pub struct CompilerOptions { | ||||||||||||||||||||
| /// https://www.typescriptlang.org/tsconfig/#baseUrl | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// The base URL is normalised to an absolute path after parsing. | ||||||||||||||||||||
| pub base_url: Option<Utf8PathBuf>, | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
Comment on lines
+111
to
115
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainEnsure JSON key maps to base_url (baseUrl) during deserialisation Unless the derive macro auto-maps camelCase to snake_case, Apply this diff: /// https://www.typescriptlang.org/tsconfig/#baseUrl
///
/// The base URL is normalised to an absolute path after parsing.
- pub base_url: Option<Utf8PathBuf>,
+ #[deserializable(rename = "baseUrl")]
+ pub base_url: Option<Utf8PathBuf>,To confirm whether auto-mapping is already handled, run: 🏁 Script executed: #!/bin/bash
# Inspect how camelCase keys are handled elsewhere and if baseUrl mapping exists
rg -n -C2 --type=rust 'Deserializable.*rename'
rg -n -C3 --type=rust '\bcompiler_options\b'
rg -n -C2 --type=rust '"baseUrl"|base_url'Length of output: 9285 I want to double-check how 🏁 Script executed: #!/bin/bash
# Inspect TsConfigJson derive and serde attributes
rg -n '#\[derive' crates/biome_package/src/node_js_package/tsconfig_json.rs
rg -n 'rename_all' -C2 --type=rust crates/biome_package/src/node_js_package/tsconfig_json.rs
rg -n 'serde' -C2 --type=rust crates/biome_package/src/node_js_package/tsconfig_json.rsLength of output: 458 Let’s peek at the top of tsconfig_json.rs to see which derive and import paths 🏁 Script executed: #!/bin/bash
sed -n '1,30p' crates/biome_package/src/node_js_package/tsconfig_json.rsLength of output: 1081 Explicitly rename The /// The base URL is normalised to an absolute path after parsing.
- pub base_url: Option<Utf8PathBuf>,
+ #[deserializable(rename = "baseUrl")]
+ pub base_url: Option<Utf8PathBuf>,📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||
| /// Path aliases. | ||||||||||||||||||||
| pub paths: Option<CompilerOptionsPathsMap>, | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /// The actual base from where path aliases are resolved. | ||||||||||||||||||||
| /// | ||||||||||||||||||||
| /// The base URL is normalised to an absolute path. | ||||||||||||||||||||
| #[deserializable(skip)] | ||||||||||||||||||||
| paths_base: Utf8PathBuf, | ||||||||||||||||||||
| pub paths_base: Utf8PathBuf, | ||||||||||||||||||||
|
|
||||||||||||||||||||
| /// See: https://www.typescriptlang.org/tsconfig/#typeRoots | ||||||||||||||||||||
| #[deserializable(rename = "typeRoots")] | ||||||||||||||||||||
|
|
||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a paragraph that explains the example below? That's the format that we try with the docs. E.g. "Given the following file structure ..., when importing a
"foo"fromindex.ts, Biome will automatically pick upsrc/foo.ts"