Skip to content

Commit 3374158

Browse files
committed
Analyze dependencies in custom properties
Fixes parcel-bundler#81. Only absolute URLs are allowed since relative paths are ambiguous.
1 parent 660ec30 commit 3374158

File tree

10 files changed

+176
-29
lines changed

10 files changed

+176
-29
lines changed

node/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,14 @@ impl<'i> CompileError<'i> {
358358
#[cfg(not(target_arch = "wasm32"))]
359359
fn throw(self, ctx: CallContext, code: Option<&str>) -> napi::Result<JsUnknown> {
360360
let reason = self.reason();
361+
let data = match &self {
362+
CompileError::ParseError(Error { kind, .. }) => ctx.env.to_js_value(kind)?,
363+
CompileError::PrinterError(Error { kind, .. }) => ctx.env.to_js_value(kind)?,
364+
CompileError::MinifyError(Error { kind, .. }) => ctx.env.to_js_value(kind)?,
365+
CompileError::BundleError(Error { kind, .. }) => ctx.env.to_js_value(kind)?,
366+
_ => ctx.env.get_null()?.into_unknown()
367+
};
368+
361369
match self {
362370
CompileError::ParseError(Error { loc, .. }) |
363371
CompileError::PrinterError(Error { loc, .. }) |
@@ -382,6 +390,7 @@ impl<'i> CompileError<'i> {
382390
loc.set_named_property("column", col)?;
383391
obj.set_named_property("loc", loc)?;
384392
}
393+
obj.set_named_property("data", data)?;
385394
ctx.env.throw(obj)?;
386395
Ok(ctx.env.get_undefined()?.into_unknown())
387396
},

src/bundler.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use parcel_sourcemap::SourceMap;
2+
use serde::Serialize;
23
use crate::{rules::{Location, layer::{LayerBlockRule, LayerName}}, error::ErrorLocation};
34
use std::{fs, path::{Path, PathBuf}, sync::Mutex, collections::HashSet};
45
use rayon::prelude::*;
@@ -73,9 +74,9 @@ impl Drop for FileProvider {
7374
}
7475
}
7576

76-
#[derive(Debug)]
77+
#[derive(Debug, Serialize)]
7778
pub enum BundleErrorKind<'i> {
78-
IOError(std::io::Error),
79+
IOError(#[serde(skip)] std::io::Error),
7980
ParserError(ParserError<'i>),
8081
UnsupportedImportCondition,
8182
UnsupportedMediaBooleanLogic,

src/error.rs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use cssparser::{ParseError, ParseErrorKind, BasicParseErrorKind};
33
use crate::rules::Location;
44
use crate::properties::custom::Token;
55
use crate::values::string::CowArcStr;
6+
use serde::Serialize;
67

78
#[derive(Debug, PartialEq, Clone)]
89
pub struct Error<T> {
@@ -27,10 +28,11 @@ impl ErrorLocation {
2728
}
2829
}
2930

30-
#[derive(Debug, PartialEq)]
31+
#[derive(Debug, PartialEq, Serialize)]
32+
#[serde(tag = "type")]
3133
pub enum ParserError<'i> {
3234
/// An unexpected token was encountered.
33-
UnexpectedToken(Token<'i>),
35+
UnexpectedToken(#[serde(skip)] Token<'i>),
3436
/// The end of the input was encountered unexpectedly.
3537
EndOfInput,
3638
/// An `@` rule was encountered that was invalid.
@@ -102,9 +104,10 @@ impl<'i> ParserError<'i> {
102104
}
103105
}
104106

105-
#[derive(Debug, PartialEq)]
107+
#[derive(Debug, PartialEq, Serialize)]
108+
#[serde(tag = "type")]
106109
pub enum SelectorError<'i> {
107-
NoQualifiedNameInAttributeSelector(Token<'i>),
110+
NoQualifiedNameInAttributeSelector(#[serde(skip)] Token<'i>),
108111
EmptySelector,
109112
DanglingCombinator,
110113
NonCompoundSelector,
@@ -114,18 +117,18 @@ pub enum SelectorError<'i> {
114117
InvalidState,
115118
MissingNestingSelector,
116119
MissingNestingPrefix,
117-
UnexpectedTokenInAttributeSelector(Token<'i>),
118-
PseudoElementExpectedColon(Token<'i>),
119-
PseudoElementExpectedIdent(Token<'i>),
120-
NoIdentForPseudo(Token<'i>),
120+
UnexpectedTokenInAttributeSelector(#[serde(skip)] Token<'i>),
121+
PseudoElementExpectedColon(#[serde(skip)] Token<'i>),
122+
PseudoElementExpectedIdent(#[serde(skip)] Token<'i>),
123+
NoIdentForPseudo(#[serde(skip)] Token<'i>),
121124
UnsupportedPseudoClassOrElement(CowArcStr<'i>),
122125
UnexpectedIdent(CowArcStr<'i>),
123126
ExpectedNamespace(CowArcStr<'i>),
124-
ExpectedBarInAttr(Token<'i>),
125-
BadValueInAttr(Token<'i>),
126-
InvalidQualNameInAttr(Token<'i>),
127-
ExplicitNamespaceUnexpectedToken(Token<'i>),
128-
ClassNeedsIdent(Token<'i>),
127+
ExpectedBarInAttr(#[serde(skip)] Token<'i>),
128+
BadValueInAttr(#[serde(skip)] Token<'i>),
129+
InvalidQualNameInAttr(#[serde(skip)] Token<'i>),
130+
ExplicitNamespaceUnexpectedToken(#[serde(skip)] Token<'i>),
131+
ClassNeedsIdent(#[serde(skip)] Token<'i>),
129132
}
130133

131134
impl<'i> From<SelectorParseErrorKind<'i>> for SelectorError<'i> {
@@ -189,7 +192,8 @@ pub struct ErrorWithLocation<T> {
189192

190193
pub type MinifyError = ErrorWithLocation<MinifyErrorKind>;
191194

192-
#[derive(Debug, PartialEq)]
195+
#[derive(Debug, PartialEq, Serialize)]
196+
#[serde(tag = "type")]
193197
pub enum MinifyErrorKind {
194198
UnsupportedCustomMediaBooleanLogic { custom_media_loc: Location },
195199
CustomMediaNotDefined { name: String },
@@ -208,11 +212,13 @@ impl MinifyErrorKind {
208212

209213
pub type PrinterError = Error<PrinterErrorKind>;
210214

211-
#[derive(Debug)]
215+
#[derive(Debug, PartialEq, Serialize)]
216+
#[serde(tag = "type")]
212217
pub enum PrinterErrorKind {
213218
FmtError,
214219
InvalidComposesSelector,
215-
InvalidComposesNesting
220+
InvalidComposesNesting,
221+
AmbiguousUrlInCustomProperty { url: String }
216222
}
217223

218224
impl From<std::fmt::Error> for PrinterError {
@@ -229,7 +235,8 @@ impl PrinterErrorKind {
229235
match self {
230236
PrinterErrorKind::InvalidComposesSelector => "The `composes` property can only be used within a simple class selector.".into(),
231237
PrinterErrorKind::InvalidComposesNesting => "The `composes` property cannot be used within nested rules.".into(),
232-
PrinterErrorKind::FmtError => "Printer error".into()
238+
PrinterErrorKind::FmtError => "Printer error".into(),
239+
PrinterErrorKind::AmbiguousUrlInCustomProperty { .. } => "Ambiguous url() in custom property. Relative paths are resolved from the location the var() is used, not where the custom property is defined. Use an absolute URL instead.".into()
233240
}
234241
}
235242
}

src/lib.rs

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ mod context;
2323
#[cfg(test)]
2424
mod tests {
2525
use crate::dependencies::Dependency;
26-
use crate::error::{Error, MinifyErrorKind, ErrorLocation, ParserError};
26+
use crate::error::{Error, MinifyErrorKind, ErrorLocation, ParserError, PrinterErrorKind};
2727
use crate::properties::custom::Token;
2828
use crate::rules::CssRule;
2929
use crate::stylesheet::*;
@@ -13178,6 +13178,18 @@ mod tests {
1317813178
}
1317913179
}
1318013180

13181+
fn dep_error_test(source: &str, error: PrinterErrorKind) {
13182+
let stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions::default()).unwrap();
13183+
let res = stylesheet.to_css(PrinterOptions {
13184+
analyze_dependencies: true,
13185+
..PrinterOptions::default()
13186+
});
13187+
match res {
13188+
Err(e) => assert_eq!(e.kind, error),
13189+
_ => unreachable!()
13190+
}
13191+
}
13192+
1318113193
dep_test(
1318213194
".foo { background: image-set('./img12x.png', './img21x.png' 2x)}",
1318313195
".foo{background:image-set(\"hXFI8W\",\"5TkpBa\" 2x)}",
@@ -13195,6 +13207,61 @@ mod tests {
1319513207
("./img21x.png", "5TkpBa")
1319613208
]
1319713209
);
13210+
13211+
dep_test(
13212+
".foo { --test: url(/foo.png) }",
13213+
".foo{--test:url(\"lDnnrG\")}",
13214+
vec![
13215+
("/foo.png", "lDnnrG"),
13216+
]
13217+
);
13218+
13219+
dep_test(
13220+
".foo { --test: url(\"/foo.png\") }",
13221+
".foo{--test:url(\"lDnnrG\")}",
13222+
vec![
13223+
("/foo.png", "lDnnrG"),
13224+
]
13225+
);
13226+
13227+
dep_test(
13228+
".foo { --test: url(\"http://example.com/foo.png\") }",
13229+
".foo{--test:url(\"3X1zSW\")}",
13230+
vec![
13231+
("http://example.com/foo.png", "3X1zSW"),
13232+
]
13233+
);
13234+
13235+
dep_test(
13236+
".foo { --test: url(\"data:image/svg+xml;utf8,<svg></svg>\") }",
13237+
".foo{--test:url(\"-vl-rG\")}",
13238+
vec![
13239+
("data:image/svg+xml;utf8,<svg></svg>", "-vl-rG"),
13240+
]
13241+
);
13242+
13243+
dep_test(
13244+
".foo { background: url(\"foo.png\") var(--test) }",
13245+
".foo{background:url(\"Vwkwkq\") var(--test)}",
13246+
vec![
13247+
("foo.png", "Vwkwkq"),
13248+
]
13249+
);
13250+
13251+
dep_error_test(
13252+
".foo { --test: url(\"foo.png\") }",
13253+
PrinterErrorKind::AmbiguousUrlInCustomProperty { url: "foo.png".into() }
13254+
);
13255+
13256+
dep_error_test(
13257+
".foo { --test: url(foo.png) }",
13258+
PrinterErrorKind::AmbiguousUrlInCustomProperty { url: "foo.png".into() }
13259+
);
13260+
13261+
dep_error_test(
13262+
".foo { --test: url(./foo.png) }",
13263+
PrinterErrorKind::AmbiguousUrlInCustomProperty { url: "./foo.png".into() }
13264+
);
1319813265
}
1319913266

1320013267
#[test]

src/properties/custom.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ use crate::traits::{Parse, ToCss};
66
use crate::properties::PropertyId;
77
use crate::values::color::{CssColor, ColorFallbackKind};
88
use crate::values::length::serialize_dimension;
9+
use crate::values::url::Url;
910
use crate::vendor_prefix::VendorPrefix;
1011
use crate::targets::Browsers;
1112
use crate::prefixes::Feature;
12-
use crate::error::{ParserError, PrinterError};
13+
use crate::error::{ParserError, PrinterError, PrinterErrorKind};
1314

1415
#[derive(Debug, Clone, PartialEq)]
1516
pub struct CustomProperty<'i> {
@@ -48,7 +49,7 @@ impl<'i> UnparsedProperty<'i> {
4849
})
4950
}
5051

51-
pub fn get_prefixed(&self, targets: Option<Browsers>, feature: Feature) -> UnparsedProperty<'i> {
52+
pub(crate) fn get_prefixed(&self, targets: Option<Browsers>, feature: Feature) -> UnparsedProperty<'i> {
5253
let mut clone = self.clone();
5354
if self.property_id.prefix().contains(VendorPrefix::None) {
5455
if let Some(targets) = targets {
@@ -72,7 +73,8 @@ pub struct TokenList<'i>(pub Vec<TokenOrValue<'i>>);
7273
#[derive(Debug, Clone, PartialEq)]
7374
pub enum TokenOrValue<'i> {
7475
Token(Token<'i>),
75-
Color(CssColor)
76+
Color(CssColor),
77+
Url(Url<'i>)
7678
}
7779

7880
impl<'i> From<Token<'i>> for TokenOrValue<'i> {
@@ -131,6 +133,11 @@ impl<'i> TokenList<'i> {
131133
tokens.push(TokenOrValue::Color(color));
132134
last_is_delim = false;
133135
last_is_whitespace = false;
136+
} else if f == "url" {
137+
input.reset(&state);
138+
tokens.push(TokenOrValue::Url(Url::parse(input)?));
139+
last_is_delim = false;
140+
last_is_whitespace = false;
134141
} else {
135142
tokens.push(Token::Function(f).into());
136143
input.parse_nested_block(|input| {
@@ -150,6 +157,12 @@ impl<'i> TokenList<'i> {
150157
last_is_delim = false;
151158
last_is_whitespace = false;
152159
}
160+
Ok(&cssparser::Token::UnquotedUrl(_)) => {
161+
input.reset(&state);
162+
tokens.push(TokenOrValue::Url(Url::parse(input)?));
163+
last_is_delim = false;
164+
last_is_whitespace = false;
165+
}
153166
Ok(token @ &cssparser::Token::ParenthesisBlock) |
154167
Ok(token @ &cssparser::Token::SquareBracketBlock) |
155168
Ok(token @ &cssparser::Token::CurlyBracketBlock) => {
@@ -210,15 +223,23 @@ fn try_parse_color_token<'i, 't>(f: &CowArcStr<'i>, state: &ParserState, input:
210223
None
211224
}
212225

213-
impl<'i> ToCss for TokenList<'i> {
214-
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError> where W: std::fmt::Write {
226+
impl<'i> TokenList<'i> {
227+
pub(crate) fn to_css<W>(&self, dest: &mut Printer<W>, is_custom_property: bool) -> Result<(), PrinterError> where W: std::fmt::Write {
215228
if !dest.minify && self.0.len() == 1 && matches!(self.0.first(), Some(token) if token.is_whitespace()) {
216229
return Ok(())
217230
}
218231

219232
for (i, token_or_value) in self.0.iter().enumerate() {
220233
match token_or_value {
221234
TokenOrValue::Color(color) => color.to_css(dest)?,
235+
TokenOrValue::Url(url) => {
236+
if dest.dependencies.is_some() && is_custom_property && !url.is_absolute() {
237+
return Err(dest.error(PrinterErrorKind::AmbiguousUrlInCustomProperty {
238+
url: url.url.as_ref().to_owned()
239+
}, url.loc))
240+
}
241+
url.to_css(dest)?
242+
},
222243
TokenOrValue::Token(token) => {
223244
match token {
224245
Token::Delim(d) => {

src/properties/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,10 @@ macro_rules! define_properties {
390390
}
391391
)+
392392
Unparsed(unparsed) => {
393-
unparsed.value.to_css(dest)
393+
unparsed.value.to_css(dest, false)
394394
}
395395
Custom(custom) => {
396-
custom.value.to_css(dest)
396+
custom.value.to_css(dest, true)
397397
}
398398
Logical(logical) => {
399399
logical.to_css(dest)

src/rules/font_face.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ impl<'i> ToCss for FontFaceProperty<'i> {
411411
Custom(custom) => {
412412
dest.write_str(custom.name.as_ref())?;
413413
dest.delim(':', false)?;
414-
custom.value.to_css(dest)
414+
custom.value.to_css(dest, true)
415415
}
416416
}
417417
}

src/rules/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub mod custom_media;
1414
pub mod layer;
1515
pub mod property;
1616

17+
use serde::Serialize;
1718
use crate::values::string::CowArcStr;
1819
use media::MediaRule;
1920
use import::ImportRule;
@@ -51,7 +52,7 @@ pub(crate) struct StyleContext<'a, 'i> {
5152
pub parent: Option<&'a StyleContext<'a, 'i>>
5253
}
5354

54-
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
55+
#[derive(PartialEq, Eq, Debug, Clone, Copy, Serialize)]
5556
pub struct Location {
5657
/// The index of the source file within the source map.
5758
pub source_index: u32,

src/values/string.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use cssparser::CowRcStr;
2+
use serde::{Serialize, Serializer};
23
use std::marker::PhantomData;
34
use std::rc::Rc;
45
use std::sync::Arc;
@@ -209,3 +210,9 @@ impl<'a> fmt::Debug for CowArcStr<'a> {
209210
str::fmt(self, formatter)
210211
}
211212
}
213+
214+
impl<'a> Serialize for CowArcStr<'a> {
215+
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
216+
self.as_ref().serialize(serializer)
217+
}
218+
}

0 commit comments

Comments
 (0)