Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(css/parser): recovery more for non valid component values in declaration value #6560

Merged
merged 8 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 0 additions & 12 deletions crates/swc_css_parser/src/macros.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ div {
prop: center / 1em;
c\olor: red;
prop/**/: big;
prop: (;);
prop: [;];
prop: {;};
}

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