Skip to content

Commit

Permalink
fix(css/parser): Fix recovery more for invalid component values in de…
Browse files Browse the repository at this point in the history
…claration value (#6560)
  • Loading branch information
alexander-akait committed Dec 1, 2022
1 parent 9a4fe89 commit db1eb48
Show file tree
Hide file tree
Showing 42 changed files with 3,563 additions and 505 deletions.
12 changes: 0 additions & 12 deletions crates/swc_css_parser/src/macros.rs
Expand Up @@ -166,16 +166,4 @@ macro_rules! tok {
(">") => {
swc_css_ast::Token::Delim { value: '>', .. }
};

("important") => {
ident_tok!("important")
};

("local") => {
ident_tok!("local")
};

("global") => {
ident_tok!("global")
};
}
63 changes: 33 additions & 30 deletions crates/swc_css_parser/src/parser/syntax/mod.rs
Expand Up @@ -623,53 +623,39 @@ where

// 5. As long as the next input token is anything other than an <EOF-token>,
// consume a component value and append it to the declaration’s value.
let mut is_valid_to_canonicalize = true;
let mut last_whitespaces = (0, 0, 0);
let mut exclamation_point_span = None;
let mut important_ident = None;

loop {
if is_one_of!(self, EOF) {
if important_ident.is_none() {
if let Some(span) = &exclamation_point_span {
// TODO improve me to `<declaration-value>`
self.errors.push(Error::new(
*span,
ErrorKind::Unexpected("'!' in <declaration-value>"),
));
}
}

if is!(self, EOF) {
break;
}

let component_value = self.parse_as::<ComponentValue>()?;

match &component_value {
// Optimization for step 5
ComponentValue::PreservedToken(
token_and_span @ TokenAndSpan {
token: Token::Ident { value, .. },
..
},
) if exclamation_point_span.is_some()
&& value.to_ascii_lowercase() == js_word!("important") =>
{
important_ident = Some(token_and_span.clone());
}
// Optimization for step 6
ComponentValue::PreservedToken(TokenAndSpan {
span,
token: Token::Delim { value: '!', .. },
..
}) => {
exclamation_point_span = Some(*span);
}) if is!(self, " ") || is_case_insensitive_ident!(self, "important") => {
if let Some(span) = &exclamation_point_span {
is_valid_to_canonicalize = false;

if important_ident.is_some() {
important_ident = None;
self.errors.push(Error::new(
*span,
ErrorKind::Unexpected("'!' in declaration value"),
));

important_ident = None;
last_whitespaces = (last_whitespaces.2, 0, 0);
}

exclamation_point_span = Some(*span);
}
// Optimization for step 6
ComponentValue::PreservedToken(TokenAndSpan {
token: Token::WhiteSpace { .. },
..
Expand All @@ -687,14 +673,31 @@ where
unreachable!();
}
},
ComponentValue::PreservedToken(
token_and_span @ TokenAndSpan {
token: Token::Ident { value, .. },
..
},
) if exclamation_point_span.is_some()
&& value.to_ascii_lowercase() == js_word!("important") =>
{
important_ident = Some(token_and_span.clone());
}
_ => {
if let Err(err) = self.validate_declaration_value(&component_value) {
is_valid_to_canonicalize = false;

self.errors.push(err);
}

last_whitespaces = (0, 0, 0);

if let Some(span) = &exclamation_point_span {
// TODO improve me to `<declaration-value>`
is_valid_to_canonicalize = false;

self.errors.push(Error::new(
*span,
ErrorKind::Unexpected("'!' in <declaration-value>"),
ErrorKind::Unexpected("'!' in declaration value"),
));

important_ident = None;
Expand Down Expand Up @@ -755,7 +758,7 @@ where
}

// Canonicalization against a grammar
if self.ctx.need_canonicalize {
if is_valid_to_canonicalize && self.ctx.need_canonicalize {
declaration = self.canonicalize_declaration_value(declaration)?;
}

Expand Down
79 changes: 79 additions & 0 deletions crates/swc_css_parser/src/parser/util.rs
Expand Up @@ -367,6 +367,85 @@ where
) -> PResult<Declaration> {
self.parse_according_to_grammar::<Declaration>(temporary_list, |parser| parser.parse_as())
}

// The <declaration-value> production matches any sequence of one or more
// tokens, so long as the sequence does not contain <bad-string-token>,
// <bad-url-token>, unmatched <)-token>, <]-token>, or <}-token>, or top-level
// <semicolon-token> tokens or <delim-token> tokens with a value of "!". It
// represents the entirety of what a valid declaration can have as its value.
pub(super) fn validate_declaration_value(
&mut self,
component_value: &ComponentValue,
) -> PResult<()> {
match component_value {
ComponentValue::PreservedToken(TokenAndSpan {
span,
token: Token::BadString { .. },
}) => {
return Err(Error::new(
*span,
ErrorKind::Unexpected("bad string in declaration value"),
));
}
ComponentValue::PreservedToken(TokenAndSpan {
span,
token: Token::BadUrl { .. },
}) => {
return Err(Error::new(
*span,
ErrorKind::Unexpected("bad url in declaration value"),
));
}
ComponentValue::PreservedToken(TokenAndSpan {
span,
token: Token::RParen,
}) => {
return Err(Error::new(
*span,
ErrorKind::Unexpected("')' in declaration value"),
));
}
ComponentValue::PreservedToken(TokenAndSpan {
span,
token: Token::RBracket,
}) => {
return Err(Error::new(
*span,
ErrorKind::Unexpected("']' in declaration value"),
));
}
ComponentValue::PreservedToken(TokenAndSpan {
span,
token: Token::RBrace,
}) => {
return Err(Error::new(
*span,
ErrorKind::Unexpected("'}' in declaration value"),
));
}
ComponentValue::PreservedToken(TokenAndSpan {
span,
token: Token::Semi,
}) => {
return Err(Error::new(
*span,
ErrorKind::Unexpected("';' in declaration value"),
));
}
ComponentValue::PreservedToken(TokenAndSpan {
span,
token: Token::Delim { value: '!' },
}) => {
return Err(Error::new(
*span,
ErrorKind::Unexpected("'!' in declaration value"),
));
}
_ => {}
}

Ok(())
}
}

pub(super) struct WithCtx<'w, I: 'w + ParserInput> {
Expand Down
78 changes: 0 additions & 78 deletions crates/swc_css_parser/src/parser/values_and_units/mod.rs
Expand Up @@ -116,84 +116,6 @@ where
Err(Error::new(span, ErrorKind::Expected("Declaration value")))
}

/// Parse value as <declaration-value>.
// pub(super) fn validate_declaration_value(&mut self) -> PResult<Vec<ComponentValue>> {
// let mut value = vec![];
// let mut balance_stack: Vec<Option<char>> = vec![];
//
// // The <declaration-value> production matches any sequence of one or more
// // tokens, so long as the sequence does not contain ...
// loop {
// if is!(self, EOF) {
// break;
// }
//
// match cur!(self) {
// // ... <bad-string-token>, <bad-url-token>,
// tok!("bad-string") | tok!("bad-url") => { break; },
//
// // ... unmatched <)-token>, <]-token>, or <}-token>,
// tok!(")") | tok!("]") | tok!("}") => {
// let value = match cur!(self) {
// tok!(")") => ')',
// tok!("]") => ']',
// tok!("}") => '}',
// _ => {
// unreachable!();
// }
// };
//
// let balance_close_type = match balance_stack.pop() {
// Some(v) => v,
// None => None,
// };
//
// if Some(value) != balance_close_type {
// break;
// }
// }
//
// tok!("function") | tok!("(") | tok!("[") | tok!("{") => {
// let value = match cur!(self) {
// tok!("function") | tok!("(") => ')',
// tok!("[") => ']',
// tok!("{") => '}',
// _ => {
// unreachable!();
// }
// };
//
// balance_stack.push(Some(value));
// }
//
// // ... or top-level <semicolon-token> tokens
// tok!(";") => {
// if balance_stack.is_empty() {
// break;
// }
// }
//
// // ... or <delim-token> tokens with a value of "!"
// tok!("!") => {
// if balance_stack.is_empty() {
// break;
// }
// }
//
// _ => {}
// }
//
// let token = self.input.bump();
//
// match token {
// Some(token) => value.push(ComponentValue::PreservedToken(token)),
// None => break,
// }
// }
//
// Ok(value)
// }

/// Parse value as <any-value>.
pub(super) fn parse_any_value(&mut self) -> PResult<Vec<TokenAndSpan>> {
let mut tokens = vec![];
Expand Down
1 change: 0 additions & 1 deletion crates/swc_css_parser/tests/fixture.rs
Expand Up @@ -587,7 +587,6 @@ fn span_visualizer_line_comment(input: PathBuf) {
"at-rule/page/without-page/input.css",
"function/calc/division/input.css",
"function/var/input.css",
"qualified-rule/only-block/input.css",
"whitespaces/input.css",
)
)]
Expand Down
3 changes: 3 additions & 0 deletions crates/swc_css_parser/tests/fixture/declaration/input.css
Expand Up @@ -22,6 +22,9 @@ div {
prop: center / 1em;
c\olor: red;
prop/**/: big;
prop: (;);
prop: [;];
prop: {;};
}

a { color: a/* ; */ b ; }
Expand Down

0 comments on commit db1eb48

Please sign in to comment.