Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions crates/biome_css_syntax/src/import_ext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::{AnyCssImportUrl, AnyCssUrlValue};
use biome_rowan::TokenText;

impl AnyCssImportUrl {
/// Returns the inner text of specifier:
///
/// ## Examples
///
/// ```
/// use biome_css_factory::make;
/// use biome_css_syntax::AnyCssImportUrl;
/// ```
pub fn inner_string_text(&self) -> Option<TokenText> {
match self {
Self::CssString(css) => css.inner_string_text().ok(),
Self::CssUrlFunction(url_function) => {
url_function.value().and_then(|value| match value {
AnyCssUrlValue::CssString(css) => css.inner_string_text().ok(),
AnyCssUrlValue::CssUrlValueRaw(raw) => raw
.value_token()
.ok()
.map(|token| token.token_text_trimmed()),
})
}
}
}
}
1 change: 1 addition & 0 deletions crates/biome_css_syntax/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#[macro_use]
mod file_source;
mod generated;
mod import_ext;
pub mod selector_ext;
pub mod stmt_ext;
mod string_ext;
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_js_analyze/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ where
.map(|(path, manifest)| (path, Arc::new(manifest)));

let type_resolver = module_graph
.module_info_for_path(file_path.as_ref())
.js_module_info_for_path(file_path.as_ref())
.map(|module_info| ModuleResolver::for_module(module_info, module_graph.clone()))
.map(Arc::new);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ fn get_deprecated_imports_from_module_source(
target_path: &Utf8Path,
module_graph: &ModuleGraph,
) -> Vec<NoDeprecatedImportsState> {
let Some(module_info) = module_graph.module_info_for_path(target_path) else {
let Some(module_info) = module_graph.js_module_info_for_path(target_path) else {
return Vec::new();
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,6 @@ fn get_unresolved_imports_from_module_source(
fn has_exported_symbol(import_name: &Text, options: &GetUnresolvedImportsOptions) -> bool {
options
.target_info
.find_exported_symbol(options.module_graph, import_name.text())
.find_js_exported_symbol(options.module_graph, import_name.text())
.is_some()
}
2 changes: 1 addition & 1 deletion crates/biome_js_analyze/src/services/module_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl ModuleGraphService {
}

pub fn module_info_for_path(&self, path: &Utf8Path) -> Option<JsModuleInfo> {
self.0.module_info_for_path(path)
self.0.js_module_info_for_path(path)
}

pub fn project_layout(&self) -> &ProjectLayout {
Expand Down
12 changes: 10 additions & 2 deletions crates/biome_lsp/src/server.tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3838,7 +3838,11 @@ async fn should_open_and_update_nested_files() -> Result<()> {
result
.data
.get(fs.working_directory.join("src").join("a.js").as_str())
.map(|module_info| module_info.static_import_paths.clone()),
.map(|module_info| module_info
.as_js_module_info()
.unwrap()
.static_import_paths
.clone()),
Some(BTreeMap::from([("foo".to_string(), "foo".to_string())]))
);

Expand All @@ -3865,7 +3869,11 @@ async fn should_open_and_update_nested_files() -> Result<()> {
result
.data
.get(fs.working_directory.join("src").join("a.js").as_str())
.map(|module_info| module_info.static_import_paths.clone()),
.map(|module_info| module_info
.as_js_module_info()
.unwrap()
.static_import_paths
.clone()),
Some(BTreeMap::from([("bar".to_string(), "bar".to_string())]))
);

Expand Down
2 changes: 2 additions & 0 deletions crates/biome_module_graph/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ workspace = true

[dependencies]
biome_console = { workspace = true }
biome_css_semantic = { workspace = true }
biome_css_syntax = { workspace = true }
biome_diagnostics = { workspace = true }
biome_formatter = { workspace = true }
biome_fs = { workspace = true }
Expand Down
1 change: 0 additions & 1 deletion crates/biome_module_graph/benches/module_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ fn bench_index_d_ts(bencher: Bencher, name: &str) {
&fs,
&ProjectLayout::default(),
&[(&path, root)],
&[],
);
divan::black_box(&module_graph);
});
Expand Down
102 changes: 102 additions & 0 deletions crates/biome_module_graph/src/css_module_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
mod visitor;

use biome_resolver::ResolvedPath;
use biome_rowan::Text;
use indexmap::IndexMap;
use std::collections::BTreeSet;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;

pub(crate) use visitor::CssModuleVisitor;

/// Information restricted to a single module in the [ModuleGraph].
#[derive(Clone, Debug)]
pub struct CssModuleInfo(pub(super) Arc<CssModuleInfoInner>);

impl Deref for CssModuleInfo {
type Target = CssModuleInfoInner;

fn deref(&self) -> &Self::Target {
self.0.as_ref()
}
}

impl CssModuleInfo {
pub(crate) fn new(imports: CssImports) -> Self {
let info = CssModuleInfoInner { imports };
Self(Arc::new(info))
}

pub(crate) fn dump(&self) -> SerializedCssModuleInfo {
SerializedCssModuleInfo {
imports: self
.0
.imports
.iter()
.map(|(_, static_import)| static_import.specifier.to_string())
.collect(),
}
}
}

#[derive(Clone, Debug)]
pub struct CssModuleInfoInner {
/// Map of all static imports found in the module.
///
/// Maps from the local imported name to a [JsImport] with the absolute path
/// it resolves to. The resolved path may be looked up as key in the
/// [ModuleGraph::data] map, although it is not required to exist
/// (for instance, if the path is outside the project's scope).
///
/// Note that re-exports may introduce additional dependencies, because they
/// import another module and immediately re-export from that module.
/// Re-exports are tracked as part of [Self::exports] and
/// [Self::blanket_reexports].
pub imports: CssImports,
Comment on lines +44 to +55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Doc comments reference JS-specific concepts.

The documentation mentions JsImport, Self::exports, and Self::blanket_reexports which don't exist for CSS modules. These appear to be copy-pasted from the JS module info.

     /// Map of all static imports found in the module.
     ///
-    /// Maps from the local imported name to a [JsImport] with the absolute path
+    /// Maps from the import specifier to a [CssImport] with the absolute path
     /// it resolves to. The resolved path may be looked up as key in the
     /// [ModuleGraph::data] map, although it is not required to exist
     /// (for instance, if the path is outside the project's scope).
-    ///
-    /// Note that re-exports may introduce additional dependencies, because they
-    /// import another module and immediately re-export from that module.
-    /// Re-exports are tracked as part of [Self::exports] and
-    /// [Self::blanket_reexports].
     pub imports: CssImports,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Map of all static imports found in the module.
///
/// Maps from the local imported name to a [JsImport] with the absolute path
/// it resolves to. The resolved path may be looked up as key in the
/// [ModuleGraph::data] map, although it is not required to exist
/// (for instance, if the path is outside the project's scope).
///
/// Note that re-exports may introduce additional dependencies, because they
/// import another module and immediately re-export from that module.
/// Re-exports are tracked as part of [Self::exports] and
/// [Self::blanket_reexports].
pub imports: CssImports,
/// Map of all static imports found in the module.
///
/// Maps from the import specifier to a [CssImport] with the absolute path
/// it resolves to. The resolved path may be looked up as key in the
/// [ModuleGraph::data] map, although it is not required to exist
/// (for instance, if the path is outside the project's scope).
pub imports: CssImports,

}

#[derive(Debug, Default, Clone)]
pub struct CssImports(pub(crate) IndexMap<Text, CssImport>);

impl Deref for CssImports {
type Target = IndexMap<Text, CssImport>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for CssImports {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

/// Represents an import to one or more symbols from an external path.
///
/// It could point to any kind of resource, such as JavaScript files, CSS files,
/// images, and so on.
#[derive(Clone, Debug, PartialEq, Hash, Eq)]
pub struct CssImport {
/// The specifier for the imported as it appeared in the source text.
pub specifier: Text,

/// Absolute path of the resource being imported, if it can be resolved.
///
/// If the import statement referred to a package dependency, the path will
/// point towards the resolved entry point of the package.
///
/// If `None`, import resolution failed.
pub resolved_path: ResolvedPath,
}

#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct SerializedCssModuleInfo {
/// Map of all static imports found in the module.
///
/// Maps from the local imported name to the absolute path it resolves to.
pub imports: BTreeSet<String>,
}
76 changes: 76 additions & 0 deletions crates/biome_module_graph/src/css_module_info/visitor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use crate::css_module_info::{CssImport, CssImports, CssModuleInfo};
use crate::module_graph::ModuleGraphFsProxy;
use biome_css_syntax::{AnyCssImportUrl, CssRoot};
use biome_resolver::{ResolveOptions, ResolvedPath, resolve};
use biome_rowan::{AstNode, Text, WalkEvent};
use camino::Utf8Path;
use std::ops::DerefMut;

pub const SUPPORTED_EXTENSIONS: &[&str] = &["css"];

pub(crate) struct CssModuleVisitor<'a> {
root: CssRoot,
directory: &'a Utf8Path,
fs_proxy: &'a ModuleGraphFsProxy<'a>,
}

impl<'a> CssModuleVisitor<'a> {
pub(crate) fn new(
root: CssRoot,
directory: &'a Utf8Path,
fs_proxy: &'a ModuleGraphFsProxy,
) -> Self {
Self {
root,
directory,
fs_proxy,
}
}

pub(crate) fn visit(self) -> CssModuleInfo {
let mut imports = CssImports::default();
let iter = self.root.syntax().preorder();
for event in iter {
match event {
WalkEvent::Enter(node) => {
if let Some(node) = AnyCssImportUrl::cast(node) {
self.visit_any_css_import_url(node, &mut imports);
}
}
WalkEvent::Leave(_) => {}
}
}

CssModuleInfo::new(imports)
}

fn visit_any_css_import_url(&self, node: AnyCssImportUrl, imports: &mut CssImports) {
let Some(specifier) = node.inner_string_text() else {
return;
};

let resolved_path = self.resolved_path_from_specifier(&specifier);

let text: Text = specifier.into();
imports.deref_mut().insert(
text.clone(),
CssImport {
specifier: text,
resolved_path,
},
);
}

fn resolved_path_from_specifier(&self, specifier: &str) -> ResolvedPath {
let options = ResolveOptions {
assume_relative: true,
condition_names: &[],
default_files: &[],
extensions: SUPPORTED_EXTENSIONS,
extension_aliases: &[],
..Default::default()
};
let resolved_path = resolve(specifier, self.directory, self.fs_proxy, &options);
ResolvedPath::new(resolved_path)
}
}
2 changes: 1 addition & 1 deletion crates/biome_module_graph/src/js_module_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl JsModuleInfo {
/// Finds an exported symbol by `name`, using the `module_graph` to
/// lookup re-exports if necessary.
#[inline]
pub fn find_exported_symbol(
pub fn find_js_exported_symbol(
&self,
module_graph: &ModuleGraph,
name: &str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ impl ModuleResolver {
Entry::Occupied(entry) => Some(*entry.get()),
Entry::Vacant(entry) => {
let path = entry.key().as_path()?;
let module_info = self.module_graph.module_info_for_path(path)?;
let module_info = self.module_graph.js_module_info_for_path(path)?;
let module_id = ModuleId::new(self.modules.len());
self.modules.push(module_info);
self.types
Expand Down
6 changes: 5 additions & 1 deletion crates/biome_module_graph/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![deny(clippy::use_self)]

mod css_module_info;
mod diagnostics;
mod format_module_graph;
mod js_module_info;
Expand All @@ -8,9 +9,12 @@ mod module_graph;
pub use biome_js_type_info::ImportSymbol;
pub use biome_resolver::ResolvedPath;

pub use css_module_info::{CssImport, CssImports, CssModuleInfo};
pub use diagnostics::ModuleDiagnostic;
pub use js_module_info::{
JsExport, JsImport, JsImportPath, JsImportPhase, JsModuleInfo, JsModuleInfoDiagnostic,
JsOwnExport, JsReexport, ModuleResolver, SerializedJsModuleInfo,
};
pub use module_graph::{ModuleDependencies, ModuleGraph, SUPPORTED_EXTENSIONS};
pub use module_graph::{
ModuleDependencies, ModuleGraph, ModuleInfo, SUPPORTED_EXTENSIONS, SerializedModuleInfo,
};
Loading
Loading