Skip to content

Commit b9642ab

Browse files
authored
fix(core): resolve aliased imports (#7121)
1 parent 6b541fb commit b9642ab

File tree

13 files changed

+612
-497
lines changed

13 files changed

+612
-497
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
Fixed [#7111](https://github.com/biomejs/biome/issues/7111): Imported symbols using aliases are now correctly recognised.

crates/biome_js_analyze/tests/quick_test.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,20 @@ fn project_layout_with_top_level_dependencies(dependencies: Dependencies) -> Arc
2626
#[test]
2727
fn quick_test() {
2828
const FILENAME: &str = "dummyFile.ts";
29-
const SOURCE: &str = r#"function head<T>(items: T[]) {
30-
if (items) { // This check is unnecessary
31-
return items[0].toUpperCase();
32-
}
33-
}"#;
29+
const SOURCE: &str = r#"import { sleep as alias } from "./sleep.ts";
30+
alias(100);"#;
3431

3532
let parsed = parse(SOURCE, JsFileSource::tsx(), JsParserOptions::default());
3633

3734
let mut fs = TemporaryFs::new("quick_test");
35+
fs.create_file("sleep.ts", "export const sleep = async (ms = 1000): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));");
3836
fs.create_file(FILENAME, SOURCE);
3937

4038
let file_path = Utf8PathBuf::from(format!("{}/{FILENAME}", fs.cli_path()));
4139

4240
let mut error_ranges: Vec<TextRange> = Vec::new();
4341
let options = AnalyzerOptions::default().with_file_path(file_path.clone());
44-
let rule_filter = RuleFilter::Rule("nursery", "noUnnecessaryConditions");
42+
let rule_filter = RuleFilter::Rule("nursery", "noFloatingPromises");
4543

4644
let dependencies = Dependencies(Box::new([("buffer".into(), "latest".into())]));
4745

crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/invalid.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { returnPromiseResult } from "./returnPromiseResult.ts";
2+
import { returnPromiseResult as returnAliasedPromiseResult } from "./returnPromiseResult.ts";
23

34
async function returnsPromise(): Promise<string> {
45
return "value";
@@ -335,6 +336,7 @@ async function testDestructuringAndCallingReturnsPromiseFromRest({
335336
import("some-module").then(() => {});
336337

337338
returnPromiseResult();
339+
returnAliasedPromiseResult();
338340

339341
function returnMaybePromise(): Promise<void> | undefined {
340342
if (!false) {

crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/invalid.ts.snap

Lines changed: 464 additions & 446 deletions
Large diffs are not rendered by default.

crates/biome_js_syntax/src/import_ext.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,15 @@ impl AnyJsNamedImportSpecifier {
189189
/// ```
190190
pub fn imported_name(&self) -> Option<JsSyntaxToken> {
191191
match self {
192-
specifier @ (Self::JsNamedImportSpecifier(_)
193-
| Self::JsShorthandNamedImportSpecifier(_)) => specifier
194-
.local_name()?
195-
.as_js_identifier_binding()?
196-
.name_token()
197-
.ok(),
198192
Self::JsBogusNamedImportSpecifier(_) => None,
193+
Self::JsNamedImportSpecifier(specifier) => {
194+
specifier.name().and_then(|name| name.value()).ok()
195+
}
196+
Self::JsShorthandNamedImportSpecifier(specifier) => {
197+
let imported_name = specifier.local_name().ok()?;
198+
let imported_name = imported_name.as_js_identifier_binding()?;
199+
imported_name.name_token().ok()
200+
}
199201
}
200202
}
201203

crates/biome_resolver/src/lib.rs

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -192,17 +192,12 @@ fn resolve_module_with_package_json(
192192

193193
// Initialise `type_roots` from the `tsconfig.json` if we have one.
194194
let initialise_type_roots = options.resolve_types && options.type_roots.is_auto();
195-
let type_roots: Option<Vec<&str>> = match initialise_type_roots {
196-
true => tsconfig
197-
.as_ref()
198-
.ok()
199-
.and_then(|tsconfig| tsconfig.compiler_options.type_roots.as_ref())
200-
.map(|type_roots| type_roots.iter().map(String::as_str).collect()),
201-
false => None,
202-
};
203195
let options = match initialise_type_roots {
204196
true => &options.with_type_roots_and_without_manifests(TypeRoots::from_optional_slice(
205-
type_roots.as_deref(),
197+
tsconfig
198+
.as_ref()
199+
.ok()
200+
.and_then(|tsconfig| tsconfig.compiler_options.type_roots.as_deref()),
206201
)),
207202
false => options,
208203
};
@@ -460,35 +455,32 @@ fn resolve_dependency(
460455
) -> Result<Utf8PathBuf, ResolveError> {
461456
let (package_name, subpath) = parse_package_specifier(specifier)?;
462457

463-
if let TypeRoots::Explicit(type_roots) = options.type_roots {
464-
for type_root in type_roots {
465-
let package_path = base_dir.join(type_root).join(package_name);
466-
match resolve_package_path(&package_path, subpath, fs, options) {
467-
Ok(path) => return Ok(path),
468-
Err(ResolveError::NotFound) => { /* continue */ }
469-
Err(error) => return Err(error),
470-
}
458+
for type_root in options.type_roots.explicit_roots() {
459+
let package_path = base_dir.join(type_root).join(package_name);
460+
match resolve_package_path(&package_path, subpath, fs, options) {
461+
Ok(path) => return Ok(path),
462+
Err(ResolveError::NotFound) => { /* continue */ }
463+
Err(error) => return Err(error),
464+
}
471465

472-
// FIXME: This is an incomplete approximation of how resolving
473-
// inside custom `typeRoots` should work. Besides packages,
474-
// type roots may contain individual `d.ts` files. Such files
475-
// don't even need to match the name of the package, because
476-
// they can do things such as
477-
// `declare module "whatever_package_name"`. But to get these
478-
// things to work reliably, we need to track **global** type
479-
// definitions first, so for now we'll assume a correlation
480-
// between package name and module name.
481-
for extension in options.extensions {
482-
if let Some(extension) = definition_extension_for_js_extension(extension) {
483-
let path = package_path.with_extension(extension);
484-
match fs.path_info(&path) {
485-
Ok(PathInfo::File) => return Ok(normalize_path(&path)),
486-
Ok(PathInfo::Symlink {
487-
canonicalized_target,
488-
}) => return Ok(canonicalized_target),
489-
_ => { /* continue */ }
490-
};
491-
}
466+
// FIXME: This is an incomplete approximation of how resolving inside
467+
// custom `typeRoots` should work. Besides packages, type roots
468+
// may contain individual `d.ts` files. Such files don't even
469+
// need to match the name of the package, because they can do
470+
// things such as `declare module "whatever_package_name"`. But
471+
// to get these things to work reliably, we need to track
472+
// **global** type definitions first, so for now we'll assume a
473+
// correlation between package name and module name.
474+
for extension in options.extensions {
475+
if let Some(extension) = definition_extension_for_js_extension(extension) {
476+
let path = package_path.with_extension(extension);
477+
match fs.path_info(&path) {
478+
Ok(PathInfo::File) => return Ok(normalize_path(&path)),
479+
Ok(PathInfo::Symlink {
480+
canonicalized_target,
481+
}) => return Ok(canonicalized_target),
482+
_ => { /* continue */ }
483+
};
492484
}
493485
}
494486
}
@@ -935,6 +927,12 @@ pub enum TypeRoots<'a> {
935927
/// Relative paths are resolved from the package path.
936928
Explicit(&'a [&'a str]),
937929

930+
/// Explicit list of directories to search.
931+
///
932+
/// Same as [`TypeRoots::Explicit`] except it references a slice of owned
933+
/// strings.
934+
ExplicitOwned(&'a [String]),
935+
938936
/// The default value to use if no `compilerOptions.typeRoots` field can be
939937
/// found in the `tsconfig.json`.
940938
///
@@ -944,18 +942,49 @@ pub enum TypeRoots<'a> {
944942
}
945943

946944
impl<'a> TypeRoots<'a> {
947-
const fn from_optional_slice(type_roots: Option<&'a [&'a str]>) -> Self {
945+
const fn from_optional_slice(type_roots: Option<&'a [String]>) -> Self {
948946
match type_roots {
949-
Some(type_roots) => Self::Explicit(type_roots),
947+
Some(type_roots) => Self::ExplicitOwned(type_roots),
950948
None => Self::TypesInNodeModules,
951949
}
952950
}
953951

952+
fn explicit_roots(&self) -> impl Iterator<Item = &str> {
953+
ExplicitTypeRootIterator {
954+
type_roots: self,
955+
index: 0,
956+
}
957+
}
958+
954959
const fn is_auto(self) -> bool {
955960
matches!(self, Self::Auto)
956961
}
957962
}
958963

964+
struct ExplicitTypeRootIterator<'a> {
965+
type_roots: &'a TypeRoots<'a>,
966+
index: usize,
967+
}
968+
969+
impl<'a> Iterator for ExplicitTypeRootIterator<'a> {
970+
type Item = &'a str;
971+
972+
fn next(&mut self) -> Option<Self::Item> {
973+
match self.type_roots {
974+
TypeRoots::Auto => None,
975+
TypeRoots::Explicit(items) => items.get(self.index).map(|root| {
976+
self.index += 1;
977+
*root
978+
}),
979+
TypeRoots::ExplicitOwned(items) => items.get(self.index).map(|root| {
980+
self.index += 1;
981+
root.as_str()
982+
}),
983+
TypeRoots::TypesInNodeModules => None,
984+
}
985+
}
986+
}
987+
959988
/// Reference-counted resolved path wrapped in a [Result] that may contain an
960989
/// error if the resolution failed.
961990
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]

crates/biome_resolver/tests/fixtures/resolver_cases_5/node_modules/sleep/dist/index.cjs

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_resolver/tests/fixtures/resolver_cases_5/node_modules/sleep/dist/index.d.ts

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_resolver/tests/fixtures/resolver_cases_5/node_modules/sleep/dist/index.js

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/biome_resolver/tests/fixtures/resolver_cases_5/node_modules/sleep/dist/src/index.d.ts

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)