feat(css/parser): Implement more error recovery (#2316)

swc_css_parser:
 - Recover from wrong properties like `flex-basis: __styled-jsx-placeholder__2%;`.
This commit is contained in:
Donny/강동윤 2021-09-29 14:38:49 +09:00 committed by GitHub
parent 2c50cde8de
commit ce40ff73a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 942 additions and 5 deletions

4
Cargo.lock generated
View File

@ -2477,7 +2477,7 @@ dependencies = [
[[package]]
name = "swc_css"
version = "0.8.1"
version = "0.8.2"
dependencies = [
"swc_css_ast",
"swc_css_codegen",
@ -2525,7 +2525,7 @@ dependencies = [
[[package]]
name = "swc_css_parser"
version = "0.8.0"
version = "0.8.1"
dependencies = [
"bitflags",
"lexical",

View File

@ -6,11 +6,11 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_css"
repository = "https://github.com/swc-project/swc.git"
version = "0.8.1"
version = "0.8.2"
[dependencies]
swc_css_ast = {version = "0.7.0", path = "./ast"}
swc_css_codegen = {version = "0.6.0", path = "./codegen"}
swc_css_parser = {version = "0.8.0", path = "./parser"}
swc_css_parser = {version = "0.8.1", path = "./parser"}
swc_css_utils = {version = "0.4.0", path = "./utils/"}
swc_css_visit = {version = "0.6.0", path = "./visit"}

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_css_parser"
repository = "https://github.com/swc-project/swc.git"
version = "0.8.0"
version = "0.8.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]

View File

@ -38,6 +38,8 @@ struct Ctx {
is_in_delimited_value: bool,
allow_at_selctor: bool,
recover_from_property_value: bool,
}
#[derive(Debug)]

View File

@ -90,6 +90,7 @@ where
let (values, mut last_pos) = {
let ctx = Ctx {
allow_operation_in_value: false,
recover_from_property_value: true,
..self.ctx
};
self.with_ctx(ctx).parse_property_values()?

View File

@ -23,6 +23,7 @@ where
pub(super) fn parse_property_values(&mut self) -> PResult<(Vec<Value>, BytePos)> {
let mut values = vec![];
let mut state = self.input.state();
let start_pos = self.input.cur_span()?.lo;
let mut hi = self.input.last_pos()?;
loop {
@ -43,6 +44,23 @@ where
state = self.input.state();
if !eat!(self, " ") {
if self.ctx.recover_from_property_value
&& !is_one_of!(self, EOF, ";", "}", "!", ")")
{
self.input.reset(&state);
let mut tokens = vec![];
while !is_one_of!(self, EOF, ";", "}", "!", ")") {
tokens.extend(self.input.bump()?);
}
let v = Value::Lazy(Tokens {
span: span!(self, start_pos),
tokens,
});
return Ok((vec![v], hi));
}
break;
}
}

View File

@ -153,6 +153,97 @@ fn pass(input: PathBuf) {
.unwrap();
}
#[testing::fixture("tests/recovery/**/input.css")]
fn recovery(input: PathBuf) {
eprintln!("Input: {}", input.display());
let stderr_path = input.parent().unwrap().join("output.swc-stderr");
let mut errored = false;
let stderr = testing::run_test2(false, |cm, handler| {
if false {
// For type inference
return Ok(());
}
let ref_json_path = input.parent().unwrap().join("output.json");
let fm = cm.load_file(&input).unwrap();
let lexer = Lexer::new(SourceFileInput::from(&*fm));
let mut parser = Parser::new(lexer, ParserConfig { parse_values: true });
let stylesheet = parser.parse_all();
match stylesheet {
Ok(stylesheet) => {
let actual_json = serde_json::to_string_pretty(&stylesheet)
.map(NormalizedOutput::from)
.expect("failed to serialize stylesheet");
actual_json.clone().compare_to_file(&ref_json_path).unwrap();
{
let mut lexer = Lexer::new(SourceFileInput::from(&*fm));
let mut tokens = Tokens {
span: Span::new(fm.start_pos, fm.end_pos, Default::default()),
tokens: vec![],
};
loop {
let res = lexer.next();
match res {
Ok(t) => {
tokens.tokens.push(t);
}
Err(e) => {
if matches!(e.kind(), ErrorKind::Eof) {
break;
}
panic!("failed to lex tokens: {:?}", e)
}
}
}
let mut errors = vec![];
let ss_tok: Stylesheet =
parse_tokens(&tokens, ParserConfig { parse_values: true }, &mut errors)
.expect("failed to parse token");
for err in errors {
err.to_diagnostics(&handler).emit();
}
let json_from_tokens = serde_json::to_string_pretty(&ss_tok)
.map(NormalizedOutput::from)
.expect("failed to serialize stylesheet from tokens");
assert_eq!(actual_json, json_from_tokens);
}
Err(())
}
Err(err) => {
let mut d = err.to_diagnostics(&handler);
d.note(&format!("current token = {}", parser.dump_cur()));
d.emit();
errored = true;
Err(())
}
}
})
.unwrap_err();
if errored {
panic!("Parser should recover, but failed with {}", stderr);
}
stderr.compare_to_file(&stderr_path).unwrap();
}
struct SpanVisualizer<'a> {
handler: &'a Handler,
}

View File

@ -0,0 +1,24 @@
.removed-214123 {
flex-wrap: wrap;
margin: __styled-jsx-placeholder__0;
box-sizing: border-box;
}
.removed-214123 :global(> .removed-214123-item) {
padding: __styled-jsx-placeholder__1;
flex-grow: 0;
flex-basis: __styled-jsx-placeholder__2%;
min-width: 0;
}
@media screen {
.removed-214123 :global(> .removed-214123-item) {
flex-basis: __styled-jsx-placeholder__3%;
}
}
@media screen {
.removed-214123 :global(> .removed-214123-item) {
flex-basis: __styled-jsx-placeholder__4%;
}
}

View File

@ -0,0 +1,801 @@
{
"type": "Stylesheet",
"span": {
"start": 0,
"end": 546,
"ctxt": 0
},
"rules": [
{
"type": "StyleRule",
"span": {
"start": 0,
"end": 109,
"ctxt": 0
},
"selectors": [
{
"type": "ComplexSelector",
"span": {
"start": 0,
"end": 15,
"ctxt": 0
},
"selectors": [
{
"type": "CompoundSelector",
"span": {
"start": 0,
"end": 15,
"ctxt": 0
},
"hasNestPrefix": false,
"combinator": null,
"typeSelector": null,
"subclassSelectors": [
{
"type": "ClassSelector",
"span": {
"start": 0,
"end": 15,
"ctxt": 0
},
"text": {
"type": "Text",
"span": {
"start": 1,
"end": 15,
"ctxt": 0
},
"value": "removed-214123"
}
}
]
}
]
}
],
"block": {
"type": "DeclBlock",
"span": {
"start": 16,
"end": 109,
"ctxt": 0
},
"items": [
{
"type": "Property",
"span": {
"start": 22,
"end": 37,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 22,
"end": 31,
"ctxt": 0
},
"value": "flex-wrap"
},
"values": [
{
"type": "Text",
"span": {
"start": 33,
"end": 37,
"ctxt": 0
},
"value": "wrap"
}
],
"important": null
},
{
"type": "Property",
"span": {
"start": 43,
"end": 78,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 43,
"end": 49,
"ctxt": 0
},
"value": "margin"
},
"values": [
{
"type": "Text",
"span": {
"start": 51,
"end": 78,
"ctxt": 0
},
"value": "__styled-jsx-placeholder__0"
}
],
"important": null
},
{
"type": "Property",
"span": {
"start": 84,
"end": 106,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 84,
"end": 94,
"ctxt": 0
},
"value": "box-sizing"
},
"values": [
{
"type": "Text",
"span": {
"start": 96,
"end": 106,
"ctxt": 0
},
"value": "border-box"
}
],
"important": null
}
]
}
},
{
"type": "StyleRule",
"span": {
"start": 111,
"end": 286,
"ctxt": 0
},
"selectors": [
{
"type": "ComplexSelector",
"span": {
"start": 111,
"end": 158,
"ctxt": 0
},
"selectors": [
{
"type": "CompoundSelector",
"span": {
"start": 111,
"end": 126,
"ctxt": 0
},
"hasNestPrefix": false,
"combinator": null,
"typeSelector": null,
"subclassSelectors": [
{
"type": "ClassSelector",
"span": {
"start": 111,
"end": 126,
"ctxt": 0
},
"text": {
"type": "Text",
"span": {
"start": 112,
"end": 126,
"ctxt": 0
},
"value": "removed-214123"
}
}
]
},
{
"type": "CompoundSelector",
"span": {
"start": 127,
"end": 158,
"ctxt": 0
},
"hasNestPrefix": false,
"combinator": null,
"typeSelector": null,
"subclassSelectors": [
{
"type": "PseudoSelector",
"span": {
"start": 127,
"end": 158,
"ctxt": 0
},
"isElement": false,
"name": {
"type": "Text",
"span": {
"start": 128,
"end": 134,
"ctxt": 0
},
"value": "global"
},
"args": {
"type": "Tokens",
"span": {
"start": 135,
"end": 157,
"ctxt": 0
},
"tokens": [
{
"span": {
"start": 135,
"end": 136,
"ctxt": 0
},
"token": "GreaterThan"
},
{
"span": {
"start": 136,
"end": 137,
"ctxt": 0
},
"token": "WhiteSpace"
},
{
"span": {
"start": 137,
"end": 138,
"ctxt": 0
},
"token": "Dot"
},
{
"span": {
"start": 138,
"end": 157,
"ctxt": 0
},
"token": {
"Ident": "removed-214123-item"
}
}
]
}
}
]
}
]
}
],
"block": {
"type": "DeclBlock",
"span": {
"start": 159,
"end": 286,
"ctxt": 0
},
"items": [
{
"type": "Property",
"span": {
"start": 165,
"end": 201,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 165,
"end": 172,
"ctxt": 0
},
"value": "padding"
},
"values": [
{
"type": "Text",
"span": {
"start": 174,
"end": 201,
"ctxt": 0
},
"value": "__styled-jsx-placeholder__1"
}
],
"important": null
},
{
"type": "Property",
"span": {
"start": 207,
"end": 219,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 207,
"end": 216,
"ctxt": 0
},
"value": "flex-grow"
},
"values": [
{
"type": "Number",
"span": {
"start": 218,
"end": 219,
"ctxt": 0
},
"value": 0.0
}
],
"important": null
},
{
"type": "Property",
"span": {
"start": 225,
"end": 264,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 225,
"end": 235,
"ctxt": 0
},
"value": "flex-basis"
},
"values": [
{
"type": "Tokens",
"span": {
"start": 236,
"end": 265,
"ctxt": 0
},
"tokens": [
{
"span": {
"start": 264,
"end": 265,
"ctxt": 0
},
"token": "Percent"
}
]
}
],
"important": null
},
{
"type": "Property",
"span": {
"start": 271,
"end": 283,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 271,
"end": 280,
"ctxt": 0
},
"value": "min-width"
},
"values": [
{
"type": "Number",
"span": {
"start": 282,
"end": 283,
"ctxt": 0
},
"value": 0.0
}
],
"important": null
}
]
}
},
{
"type": "MediaRule",
"span": {
"start": 288,
"end": 415,
"ctxt": 0
},
"query": {
"type": "Text",
"span": {
"start": 295,
"end": 301,
"ctxt": 0
},
"value": "screen"
},
"rules": [
{
"type": "StyleRule",
"span": {
"start": 308,
"end": 413,
"ctxt": 0
},
"selectors": [
{
"type": "ComplexSelector",
"span": {
"start": 308,
"end": 355,
"ctxt": 0
},
"selectors": [
{
"type": "CompoundSelector",
"span": {
"start": 308,
"end": 323,
"ctxt": 0
},
"hasNestPrefix": false,
"combinator": null,
"typeSelector": null,
"subclassSelectors": [
{
"type": "ClassSelector",
"span": {
"start": 308,
"end": 323,
"ctxt": 0
},
"text": {
"type": "Text",
"span": {
"start": 309,
"end": 323,
"ctxt": 0
},
"value": "removed-214123"
}
}
]
},
{
"type": "CompoundSelector",
"span": {
"start": 324,
"end": 355,
"ctxt": 0
},
"hasNestPrefix": false,
"combinator": null,
"typeSelector": null,
"subclassSelectors": [
{
"type": "PseudoSelector",
"span": {
"start": 324,
"end": 355,
"ctxt": 0
},
"isElement": false,
"name": {
"type": "Text",
"span": {
"start": 325,
"end": 331,
"ctxt": 0
},
"value": "global"
},
"args": {
"type": "Tokens",
"span": {
"start": 332,
"end": 354,
"ctxt": 0
},
"tokens": [
{
"span": {
"start": 332,
"end": 333,
"ctxt": 0
},
"token": "GreaterThan"
},
{
"span": {
"start": 333,
"end": 334,
"ctxt": 0
},
"token": "WhiteSpace"
},
{
"span": {
"start": 334,
"end": 335,
"ctxt": 0
},
"token": "Dot"
},
{
"span": {
"start": 335,
"end": 354,
"ctxt": 0
},
"token": {
"Ident": "removed-214123-item"
}
}
]
}
}
]
}
]
}
],
"block": {
"type": "DeclBlock",
"span": {
"start": 356,
"end": 413,
"ctxt": 0
},
"items": [
{
"type": "Property",
"span": {
"start": 366,
"end": 405,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 366,
"end": 376,
"ctxt": 0
},
"value": "flex-basis"
},
"values": [
{
"type": "Tokens",
"span": {
"start": 377,
"end": 406,
"ctxt": 0
},
"tokens": [
{
"span": {
"start": 405,
"end": 406,
"ctxt": 0
},
"token": "Percent"
}
]
}
],
"important": null
}
]
}
}
]
},
{
"type": "MediaRule",
"span": {
"start": 417,
"end": 546,
"ctxt": 0
},
"query": {
"type": "Text",
"span": {
"start": 424,
"end": 430,
"ctxt": 0
},
"value": "screen"
},
"rules": [
{
"type": "StyleRule",
"span": {
"start": 437,
"end": 542,
"ctxt": 0
},
"selectors": [
{
"type": "ComplexSelector",
"span": {
"start": 437,
"end": 484,
"ctxt": 0
},
"selectors": [
{
"type": "CompoundSelector",
"span": {
"start": 437,
"end": 452,
"ctxt": 0
},
"hasNestPrefix": false,
"combinator": null,
"typeSelector": null,
"subclassSelectors": [
{
"type": "ClassSelector",
"span": {
"start": 437,
"end": 452,
"ctxt": 0
},
"text": {
"type": "Text",
"span": {
"start": 438,
"end": 452,
"ctxt": 0
},
"value": "removed-214123"
}
}
]
},
{
"type": "CompoundSelector",
"span": {
"start": 453,
"end": 484,
"ctxt": 0
},
"hasNestPrefix": false,
"combinator": null,
"typeSelector": null,
"subclassSelectors": [
{
"type": "PseudoSelector",
"span": {
"start": 453,
"end": 484,
"ctxt": 0
},
"isElement": false,
"name": {
"type": "Text",
"span": {
"start": 454,
"end": 460,
"ctxt": 0
},
"value": "global"
},
"args": {
"type": "Tokens",
"span": {
"start": 461,
"end": 483,
"ctxt": 0
},
"tokens": [
{
"span": {
"start": 461,
"end": 462,
"ctxt": 0
},
"token": "GreaterThan"
},
{
"span": {
"start": 462,
"end": 463,
"ctxt": 0
},
"token": "WhiteSpace"
},
{
"span": {
"start": 463,
"end": 464,
"ctxt": 0
},
"token": "Dot"
},
{
"span": {
"start": 464,
"end": 483,
"ctxt": 0
},
"token": {
"Ident": "removed-214123-item"
}
}
]
}
}
]
}
]
}
],
"block": {
"type": "DeclBlock",
"span": {
"start": 485,
"end": 542,
"ctxt": 0
},
"items": [
{
"type": "Property",
"span": {
"start": 495,
"end": 534,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 495,
"end": 505,
"ctxt": 0
},
"value": "flex-basis"
},
"values": [
{
"type": "Tokens",
"span": {
"start": 506,
"end": 535,
"ctxt": 0
},
"tokens": [
{
"span": {
"start": 534,
"end": 535,
"ctxt": 0
},
"token": "Percent"
}
]
}
],
"important": null
}
]
}
}
]
}
]
}