mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-20 01:56:37 +03:00
Sampled contrast: url() is a token, not a suffix of an identifier
The sheet url rewrite and its comment scan both matched `url(` inside a longer identifier, so `myurl(a /* url(x.png) */)` read as an unquoted url and the comment inside it as url content. Both now require a non-identifier byte before `url(`, the way the tokenizer does; the regex re-emits that byte unchanged. A test pins a custom function wrapping a comment beside a real url on the same rule. AI assistance: Claude Code (Claude Fable 5.1), on maintainer instruction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
4c5f3974b3
commit
634b579b63
@@ -145,8 +145,10 @@ pub fn collect_static_css_text(
|
||||
// whitespace, quotes, parens, braces, or semicolons (CSS syntax), so a
|
||||
// stray `url(` can never pair with a `)` in a later rule.
|
||||
static CSS_URL_RE: Lazy<Regex> = Lazy::new(|| {
|
||||
// The leading group keeps `url(` from matching inside a longer
|
||||
// identifier such as `myurl(`; the closure emits it unchanged.
|
||||
Regex::new(
|
||||
r#"(?i)url\(\s*(?:"((?:[^"\\\n\r]|\\.)*)"|'((?:[^'\\\n\r]|\\.)*)'|([^)"'(\s{};]*))\s*\)"#,
|
||||
r#"(?i)(^|[^A-Za-z0-9_-])url\(\s*(?:"((?:[^"\\\n\r]|\\.)*)"|'((?:[^'\\\n\r]|\\.)*)'|([^)"'(\s{};]*))\s*\)"#,
|
||||
)
|
||||
.expect("CSS_URL_RE")
|
||||
});
|
||||
@@ -177,6 +179,12 @@ pub fn rewrite_sheet_urls(css: &str, sheet_dir: &str, page_dir: &str) -> String
|
||||
/// `url()` is content, not a comment opener, and an unclosed comment runs to
|
||||
/// the end of the sheet. Every span boundary sits on an ASCII byte, so the
|
||||
/// spans are valid `str` indices.
|
||||
/// A byte that can continue a CSS identifier, so `myurl(` is a custom
|
||||
/// function and not the `url(` token.
|
||||
fn is_ident_byte(b: u8) -> bool {
|
||||
b.is_ascii_alphanumeric() || b == b'-' || b == b'_' || b >= 0x80
|
||||
}
|
||||
|
||||
fn comment_spans(css: &str) -> Vec<(usize, usize)> {
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum State {
|
||||
@@ -207,6 +215,7 @@ fn comment_spans(css: &str) -> Vec<(usize, usize)> {
|
||||
&& css[i..]
|
||||
.get(..4)
|
||||
.is_some_and(|s| s.eq_ignore_ascii_case("url("))
|
||||
&& !(i > 0 && is_ident_byte(bytes[i - 1]))
|
||||
{
|
||||
i += 4;
|
||||
while i < bytes.len() && bytes[i].is_ascii_whitespace() {
|
||||
@@ -249,7 +258,8 @@ fn rewrite_code_urls(css: &str, sheet_dir: &str, page_dir: &str) -> String {
|
||||
CSS_URL_RE
|
||||
.replace_all(css, |caps: ®ex::Captures| {
|
||||
let whole = caps.get(0).map(|m| m.as_str()).unwrap_or("");
|
||||
let (target, quote) = match (caps.get(1), caps.get(2), caps.get(3)) {
|
||||
let before = caps.get(1).map(|m| m.as_str()).unwrap_or("");
|
||||
let (target, quote) = match (caps.get(2), caps.get(3), caps.get(4)) {
|
||||
(Some(m), _, _) => (m.as_str(), "\""),
|
||||
(_, Some(m), _) => (m.as_str(), "'"),
|
||||
(_, _, Some(m)) => (js::trim(m.as_str()), ""),
|
||||
@@ -282,7 +292,7 @@ fn rewrite_code_urls(css: &str, sheet_dir: &str, page_dir: &str) -> String {
|
||||
.chars()
|
||||
.any(|c| c.is_whitespace() || matches!(c, '(' | ')' | '"' | '\''));
|
||||
let quote = if needs_quotes { "\"" } else { quote };
|
||||
format!("url({quote}{relative}{suffix}{quote})")
|
||||
format!("{before}url({quote}{relative}{suffix}{quote})")
|
||||
})
|
||||
.into_owned()
|
||||
}
|
||||
|
||||
@@ -406,4 +406,12 @@ fn linked_sheet_urls_are_rewritten_page_relative() {
|
||||
out.contains("/* real url( */ .n { background: url(css/n.png) }"),
|
||||
"{out}"
|
||||
);
|
||||
// `myurl(` is a custom function, not the url token: its comment stays a
|
||||
// comment and its argument is not rewritten.
|
||||
let custom = ".q { mask: myurl(a /* url(x.png) */); background: url(q.png) }";
|
||||
let out = rewrite_sheet_urls(custom, "/site/css", "/site");
|
||||
assert_eq!(
|
||||
out,
|
||||
".q { mask: myurl(a /* url(x.png) */); background: url(css/q.png) }"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user