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
1 change: 1 addition & 0 deletions Cargo.lock

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

104 changes: 91 additions & 13 deletions crates/css-module-lexer/src/dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ impl ScanContext {
#[derive(Debug)]
struct ImportData<'s> {
start: Pos,
magic_comments: Option<&'s str>,
prelude: ImportPrelude<'s>,
url: Option<&'s str>,
url_flags: TokenFlags,
Expand All @@ -134,6 +135,7 @@ impl ImportData<'_> {
pub fn new(start: Pos) -> Self {
Self {
start,
magic_comments: None,
prelude: ImportPrelude::default(),
url: None,
url_flags: TokenFlags::ascii(),
Expand Down Expand Up @@ -328,6 +330,7 @@ impl BalancedStack {
struct BalancedItem {
kind: BalancedItemKind,
range: Range,
magic_comments: Option<Range>,
}

impl BalancedItem {
Expand All @@ -343,27 +346,31 @@ impl BalancedItem {
Self {
kind,
range: Range::new(start, end),
magic_comments: None,
}
}

pub fn new_normalized(name: &str, start: Pos, end: Pos) -> Self {
Self {
kind: BalancedItemKind::new(name),
range: Range::new(start, end),
magic_comments: None,
}
}

pub fn new_other(start: Pos, end: Pos) -> Self {
Self {
kind: BalancedItemKind::Other,
range: Range::new(start, end),
magic_comments: None,
}
}

pub fn new_curly(start: Pos, end: Pos) -> Self {
Self {
kind: BalancedItemKind::Curly,
range: Range::new(start, end),
magic_comments: None,
}
}
}
Expand Down Expand Up @@ -417,6 +424,29 @@ impl BalancedItemKind {
}
}

fn preceding_comment_range(input: &str) -> Option<Range> {
let bytes = input.as_bytes();
let mut cursor = bytes.len();
let end = cursor as Pos;
let mut start = None;

loop {
while cursor > 0 && is_css_space_byte(bytes[cursor - 1]) {
cursor -= 1;
}
if cursor < 2 || &bytes[cursor - 2..cursor] != b"*/" {
break;
}
let Some(comment_start) = input[..cursor - 2].rfind("/*") else {
break;
};
start = Some(comment_start as Pos);
cursor = comment_start;
}

start.map(|start| Range::new(start, end))
}

fn trivia_only(input: &str) -> bool {
if input.is_empty() {
return false;
Expand Down Expand Up @@ -3081,6 +3111,12 @@ impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
}

impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
fn magic_comments_before(lexer: &DependencyLexer<'s>, start: Pos) -> Option<&'s str> {
let input = lexer.slice(0, start)?;
let range = preceding_comment_range(input)?;
lexer.slice(range.start, range.end)
}

fn handle_comment(
&mut self,
lexer: &mut DependencyLexer<'s>,
Expand Down Expand Up @@ -3116,6 +3152,14 @@ impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
flags: TokenFlags,
) -> Option<()> {
let value = lexer.slice(content_start, content_end)?;
let can_be_dependency = match &self.scope {
Scope::InAtImport(import_data) => !import_data.in_supports(),
Scope::InBlock => true,
_ => false,
};
let magic_comments = can_be_dependency
.then(|| Self::magic_comments_before(lexer, start))
.flatten();
match self.scope {
Scope::InAtImport(ref mut import_data) => {
if import_data.in_supports() {
Expand All @@ -3136,12 +3180,16 @@ impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
import_data.url = Some(value);
import_data.url_flags = flags;
import_data.url_range = Some(Range::new(start, end));
import_data.magic_comments = magic_comments;
}
Scope::InBlock => {
self.dependency_context.push_dependency(Dependency::Url {
request: value,
range: Range::new(start, end),
kind: UrlRangeKind::Function,
magic_comments,
});
}
Scope::InBlock => self.dependency_context.push_dependency(Dependency::Url {
request: value,
range: Range::new(start, end),
kind: UrlRangeKind::Function,
}),
_ => {}
}
Some(())
Expand All @@ -3154,15 +3202,36 @@ impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
end: Pos,
flags: TokenFlags,
) -> Option<()> {
let inside_url = matches!(
self.balanced.last(),
Some(last) if matches!(last.kind, BalancedItemKind::Url)
);
let can_be_dependency = match &self.scope {
Scope::InAtImport(import_data) => {
!import_data.in_supports() && (inside_url || import_data.url.is_none())
}
Scope::InBlock => matches!(
self.balanced.last(),
Some(last) if matches!(last.kind, BalancedItemKind::Url | BalancedItemKind::ImageSet)
),
_ => false,
};
let mut magic_comments = can_be_dependency
.then(|| Self::magic_comments_before(lexer, start))
.flatten();
if magic_comments.is_none()
&& let Some(range) = self.balanced.last().and_then(|item| item.magic_comments)
{
magic_comments = lexer.slice(range.start, range.end);
Comment on lines +3222 to +3225

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retain function-level ignore across inner comments

When a quoted URL has a function-level ignore comment followed by any ordinary comment inside the function, such as /* webpackIgnore: true */ url(/* note */ "./missing.png"), magic_comments_before returns the inner comment, so this is_none() fallback never restores the ignore comment saved on the balanced url( item. The parser consequently finds no ignore option and attempts to resolve the missing asset; combine the function-level and argument-level comment groups, or fall back when the inner group contains no applicable ignore directive.

Useful? React with 👍 / 👎.

}
match self.scope {
Scope::InAtImport(ref mut import_data) => {
let inside_url = matches!(
self.balanced.last(),
Some(last) if matches!(last.kind, BalancedItemKind::Url)
);

// Do not parse URLs in `supports(...)` and other strings if we already have a URL
if import_data.in_supports() || (!inside_url && import_data.url.is_some()) {
// Do not parse URLs in `supports(...)`.
if import_data.in_supports() {
return Some(());
}
// Do not parse other strings if we already have a URL.
if !inside_url && import_data.url.is_some() {
return Some(());
}

Expand All @@ -3179,6 +3248,7 @@ impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
let value = lexer.slice(start + 1, end - 1)?;
import_data.url = Some(value);
import_data.url_flags = flags;
import_data.magic_comments = magic_comments;
// For url("inside_url") url_range will determined in right_parenthesis
if !inside_url {
import_data.prelude.push(ImportPreludeNode::Url {
Expand Down Expand Up @@ -3218,6 +3288,7 @@ impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
request: value,
range: Range::new(start, end),
kind,
magic_comments,
});
}
_ => {}
Expand Down Expand Up @@ -3441,6 +3512,7 @@ impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
layer,
supports,
media,
import_data.magic_comments,
);
self.scope = Scope::TopLevel;
}
Expand Down Expand Up @@ -3497,10 +3569,14 @@ impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
} else {
lowercase_ascii_keyword(name, &mut normalized)
};
let item = normalized_name.map_or_else(
let mut item = normalized_name.map_or_else(
|| BalancedItem::new_other(start, end),
|name| BalancedItem::new_normalized(name, start, end),
);
if normalized_name == Some("url(") {
item.magic_comments = preceding_comment_range(stream.slice_trusted(0, start));
}
let magic_comments = item.magic_comments;
let at_import_top_level =
matches!(self.scope, Scope::InAtImport(_)) && self.balanced.is_empty();
self.balanced.push(item, self.mode_data.as_mut());
Expand All @@ -3510,6 +3586,8 @@ impl<'s, W: HandleWarning<'s>> LexDependencies<'s, W> {
import_data.prelude.push(ImportPreludeNode::Url {
range: Range::new(start, end),
});
import_data.magic_comments =
magic_comments.map(|range| stream.slice_trusted(range.start, range.end));
} else if at_import_top_level && normalized_name == Some("layer(") {
import_data.prelude.push(ImportPreludeNode::Layer {
range: Range::new(start, end),
Expand Down
4 changes: 4 additions & 0 deletions crates/css-module-lexer/src/dependency_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,13 @@ pub enum Dependency<'s> {
request: &'s str,
range: Range,
kind: UrlRangeKind,
magic_comments: Option<&'s str>,
},
Import {
request: &'s str,
range: Range,
attributes: DependencyIndex<ImportAttributes<'s>>,
magic_comments: Option<&'s str>,
},
ICSSImportUrl {
name: &'s str,
Expand Down Expand Up @@ -403,6 +405,7 @@ impl<'s> DependencyContext<'s> {
layer: Option<&'s str>,
supports: Option<&'s str>,
media: Option<&'s str>,
magic_comments: Option<&'s str>,
) {
let attributes = DependencyIndex::from_index(self.import_attributes.len());
self
Expand All @@ -412,6 +415,7 @@ impl<'s> DependencyContext<'s> {
request,
range,
attributes,
magic_comments,
});
}

Expand Down
2 changes: 2 additions & 0 deletions crates/css-module-lexer/tests/support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub(crate) fn assert_url_dependency(
request: req,
range,
kind: k,
..
} = dependency
else {
panic!("unexpected dependency");
Expand Down Expand Up @@ -65,6 +66,7 @@ pub(crate) fn import_dependency<'context, 's>(
request,
range,
attributes,
..
} = &context[dependency_index]
else {
panic!("unexpected dependency");
Expand Down
51 changes: 26 additions & 25 deletions crates/rspack_plugin_css/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,32 @@ repository = "https://github.com/web-infra-dev/rspack"
version.workspace = true

[dependencies]
async-trait = { workspace = true }
atomic_refcell = { workspace = true }
concat-string = { workspace = true }
cow-utils = { workspace = true }
css-module-lexer = { workspace = true }
cssparser = { workspace = true }
heck = { workspace = true }
once_cell = { workspace = true }
regex = { workspace = true }
rspack_cacheable = { workspace = true }
rspack_collections = { workspace = true }
rspack_core = { workspace = true }
rspack_error = { workspace = true }
rspack_hash = { workspace = true }
rspack_hook = { workspace = true }
rspack_parallel = { workspace = true }
rspack_plugin_runtime = { workspace = true }
rspack_util = { workspace = true }
rustc-hash = { workspace = true }
serde_json = { workspace = true }
simd-json = { workspace = true }
smol_str = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
urlencoding = { workspace = true }
async-trait = { workspace = true }
atomic_refcell = { workspace = true }
concat-string = { workspace = true }
cow-utils = { workspace = true }
css-module-lexer = { workspace = true }
cssparser = { workspace = true }
heck = { workspace = true }
once_cell = { workspace = true }
regex = { workspace = true }
rspack_cacheable = { workspace = true }
rspack_collections = { workspace = true }
rspack_core = { workspace = true }
rspack_error = { workspace = true }
rspack_hash = { workspace = true }
rspack_hook = { workspace = true }
rspack_parallel = { workspace = true }
rspack_plugin_javascript = { workspace = true }
rspack_plugin_runtime = { workspace = true }
rspack_util = { workspace = true }
rustc-hash = { workspace = true }
serde_json = { workspace = true }
simd-json = { workspace = true }
smol_str = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
urlencoding = { workspace = true }

[package.metadata.cargo-shear]
ignored = ["tokio"]
Expand Down
Loading
Loading