From ce40ff73a73891fb1b41aedf1f6a26ce867ed7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Wed, 29 Sep 2021 14:38:49 +0900 Subject: [PATCH] feat(css/parser): Implement more error recovery (#2316) swc_css_parser: - Recover from wrong properties like `flex-basis: __styled-jsx-placeholder__2%;`. --- Cargo.lock | 4 +- css/Cargo.toml | 4 +- css/parser/Cargo.toml | 2 +- css/parser/src/parser/mod.rs | 2 + css/parser/src/parser/style_rule.rs | 1 + css/parser/src/parser/value/mod.rs | 18 + css/parser/tests/fixture.rs | 91 ++ .../tests/recovery/styled-jsx/1/input.css | 24 + .../tests/recovery/styled-jsx/1/output.json | 801 ++++++++++++++++++ 9 files changed, 942 insertions(+), 5 deletions(-) create mode 100644 css/parser/tests/recovery/styled-jsx/1/input.css create mode 100644 css/parser/tests/recovery/styled-jsx/1/output.json diff --git a/Cargo.lock b/Cargo.lock index bea67bd5428..b7997aad557 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/css/Cargo.toml b/css/Cargo.toml index 60959a03406..f9d471c53a7 100644 --- a/css/Cargo.toml +++ b/css/Cargo.toml @@ -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"} diff --git a/css/parser/Cargo.toml b/css/parser/Cargo.toml index 718df966d29..9f6a154aa8a 100644 --- a/css/parser/Cargo.toml +++ b/css/parser/Cargo.toml @@ -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] diff --git a/css/parser/src/parser/mod.rs b/css/parser/src/parser/mod.rs index a90593d8869..4f849a8b17f 100644 --- a/css/parser/src/parser/mod.rs +++ b/css/parser/src/parser/mod.rs @@ -38,6 +38,8 @@ struct Ctx { is_in_delimited_value: bool, allow_at_selctor: bool, + + recover_from_property_value: bool, } #[derive(Debug)] diff --git a/css/parser/src/parser/style_rule.rs b/css/parser/src/parser/style_rule.rs index 249a3be78f9..fff08f37605 100644 --- a/css/parser/src/parser/style_rule.rs +++ b/css/parser/src/parser/style_rule.rs @@ -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()? diff --git a/css/parser/src/parser/value/mod.rs b/css/parser/src/parser/value/mod.rs index 6b651c7329c..87f7e526a33 100644 --- a/css/parser/src/parser/value/mod.rs +++ b/css/parser/src/parser/value/mod.rs @@ -23,6 +23,7 @@ where pub(super) fn parse_property_values(&mut self) -> PResult<(Vec, 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; } } diff --git a/css/parser/tests/fixture.rs b/css/parser/tests/fixture.rs index 054ce506fe3..c6e7035f3ce 100644 --- a/css/parser/tests/fixture.rs +++ b/css/parser/tests/fixture.rs @@ -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, } diff --git a/css/parser/tests/recovery/styled-jsx/1/input.css b/css/parser/tests/recovery/styled-jsx/1/input.css new file mode 100644 index 00000000000..a1f0af76c02 --- /dev/null +++ b/css/parser/tests/recovery/styled-jsx/1/input.css @@ -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%; + } + } \ No newline at end of file diff --git a/css/parser/tests/recovery/styled-jsx/1/output.json b/css/parser/tests/recovery/styled-jsx/1/output.json new file mode 100644 index 00000000000..3f6392dbf58 --- /dev/null +++ b/css/parser/tests/recovery/styled-jsx/1/output.json @@ -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 + } + ] + } + } + ] + } + ] +}