feat(css/parser): Allow invalid line comments (#2443)

swc_css_parser:
 - Accept line comments with an option.
This commit is contained in:
Donny/강동윤 2021-10-15 23:21:33 +09:00 committed by GitHub
parent c2ce89c0fb
commit 7f04ef4715
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 629 additions and 51 deletions

8
Cargo.lock generated
View File

@ -2500,7 +2500,7 @@ dependencies = [
[[package]]
name = "swc_css"
version = "0.17.0"
version = "0.18.0"
dependencies = [
"swc_css_ast",
"swc_css_codegen",
@ -2522,7 +2522,7 @@ dependencies = [
[[package]]
name = "swc_css_codegen"
version = "0.15.0"
version = "0.16.0"
dependencies = [
"auto_impl",
"bitflags",
@ -2548,7 +2548,7 @@ dependencies = [
[[package]]
name = "swc_css_parser"
version = "0.17.0"
version = "0.18.0"
dependencies = [
"bitflags",
"lexical",
@ -3142,7 +3142,7 @@ dependencies = [
[[package]]
name = "swc_stylis"
version = "0.14.0"
version = "0.15.0"
dependencies = [
"swc_atoms 0.2.8",
"swc_common",

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.17.0"
version = "0.18.0"
[dependencies]
swc_css_ast = {version = "0.16.0", path = "./ast"}
swc_css_codegen = {version = "0.15.0", path = "./codegen"}
swc_css_parser = {version = "0.17.0", path = "./parser"}
swc_css_codegen = {version = "0.16.0", path = "./codegen"}
swc_css_parser = {version = "0.18.0", path = "./parser"}
swc_css_utils = {version = "0.13.0", path = "./utils/"}
swc_css_visit = {version = "0.15.0", path = "./visit"}

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_css_codegen"
repository = "https://github.com/swc-project/swc.git"
version = "0.15.0"
version = "0.16.0"
[dependencies]
auto_impl = "0.4.1"
@ -17,6 +17,6 @@ swc_css_ast = {version = "0.16.0", path = "../ast/"}
swc_css_codegen_macros = {version = "0.2.0", path = "macros/"}
[dev-dependencies]
swc_css_parser = {version = "0.17.0", path = "../parser"}
swc_css_parser = {version = "0.18.0", path = "../parser"}
swc_css_visit = {version = "0.15.0", path = "../visit"}
testing = {version = "0.14.0", path = "../../testing"}

View File

@ -19,8 +19,15 @@ fn parse_again(input: PathBuf) {
eprintln!("==== ==== Input ==== ====\n{}\n", fm.src);
let mut errors = vec![];
let mut stylesheet: Stylesheet =
parse_file(&fm, ParserConfig { parse_values: true }, &mut errors).unwrap();
let mut stylesheet: Stylesheet = parse_file(
&fm,
ParserConfig {
parse_values: true,
..Default::default()
},
&mut errors,
)
.unwrap();
for err in take(&mut errors) {
err.to_diagnostics(&handler).emit();
@ -37,12 +44,17 @@ fn parse_again(input: PathBuf) {
eprintln!("==== ==== Codegen ==== ====\n{}\n", css_str);
let new_fm = cm.new_source_file(FileName::Anon, css_str);
let mut parsed: Stylesheet =
parse_file(&new_fm, ParserConfig { parse_values: true }, &mut errors).map_err(
|err| {
err.to_diagnostics(&handler).emit();
},
)?;
let mut parsed: Stylesheet = parse_file(
&new_fm,
ParserConfig {
parse_values: true,
..Default::default()
},
&mut errors,
)
.map_err(|err| {
err.to_diagnostics(&handler).emit();
})?;
for err in errors {
err.to_diagnostics(&handler).emit();

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.17.0"
version = "0.18.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]

View File

@ -1,6 +1,6 @@
use crate::{
error::{Error, ErrorKind},
parser::{input::ParserInput, PResult},
parser::{input::ParserInput, PResult, ParserConfig},
};
use swc_atoms::{js_word, JsWord};
use swc_common::{input::Input, BytePos, Span};
@ -19,18 +19,20 @@ where
start_pos: BytePos,
/// Used to override last_pos
last_pos: Option<BytePos>,
config: ParserConfig,
}
impl<I> Lexer<I>
where
I: Input,
{
pub fn new(input: I) -> Self {
pub fn new(input: I, config: ParserConfig) -> Self {
let start_pos = input.last_pos();
Lexer {
input,
start_pos,
last_pos: None,
config,
}
}
}
@ -100,6 +102,15 @@ where
return self.read_token();
}
if self.config.allow_wrong_line_comments {
if self.input.is_byte(b'/') && self.input.peek() == Some('/') {
self.skip_line_comment()?;
self.start_pos = self.input.cur_pos();
return self.read_token();
}
}
macro_rules! try_delim {
($b:tt,$tok:tt) => {{
if self.input.eat_byte($b) {
@ -755,6 +766,15 @@ where
break;
}
if self.config.allow_wrong_line_comments {
if self.input.is_byte(b'/') && self.input.peek() == Some('/') {
self.skip_line_comment()?;
self.start_pos = self.input.cur_pos();
return Ok(());
}
}
Ok(())
}
@ -788,6 +808,29 @@ where
Err(ErrorKind::UnterminatedBlockComment)
}
fn skip_line_comment(&mut self) -> LexResult<()> {
debug_assert_eq!(self.input.cur(), Some('/'));
debug_assert_eq!(self.input.peek(), Some('/'));
self.input.bump();
self.input.bump();
debug_assert!(
self.config.allow_wrong_line_comments,
"Line comments are wrong and should be lexed only if it's explicitly requested"
);
while let Some(c) = self.input.cur() {
if is_newline(c) {
break;
}
self.input.bump();
}
Ok(())
}
}
#[inline(always)]

View File

@ -43,7 +43,7 @@ pub fn parse_str<'a, T>(
where
Parser<Lexer<StringInput<'a>>>: Parse<T>,
{
let lexer = Lexer::new(StringInput::new(src, start_pos, end_pos));
let lexer = Lexer::new(StringInput::new(src, start_pos, end_pos), config);
let mut parser = Parser::new(lexer, config);
let res = parser.parse();
@ -63,7 +63,7 @@ pub fn parse_file<'a, T>(
where
Parser<Lexer<StringInput<'a>>>: Parse<T>,
{
let lexer = Lexer::new(StringInput::from(fm));
let lexer = Lexer::new(StringInput::from(fm), config);
let mut parser = Parser::new(lexer, config);
let res = parser.parse();

View File

@ -24,6 +24,13 @@ pub type PResult<T> = Result<T, Error>;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ParserConfig {
pub parse_values: bool,
/// If this is `true`, **wrong** comments starting with `//` will be treated
/// as a comment.
///
/// This option exists because there are so many css-in-js tools and people
/// use `//` as a comment because it's javascript file.
pub allow_wrong_line_comments: bool,
}
#[derive(Debug, Default, Clone, Copy)]

View File

@ -27,9 +27,16 @@ impl Visit for AssertValid {
let mut errors = vec![];
let _selectors: Vec<ComplexSelector> =
parse_tokens(&s.args, ParserConfig { parse_values: true }, &mut errors)
.unwrap_or_else(|err| panic!("failed to parse tokens: {:?}\n{:?}", err, s.args));
let _selectors: Vec<ComplexSelector> = parse_tokens(
&s.args,
ParserConfig {
parse_values: true,
..Default::default()
},
&mut errors,
)
.unwrap_or_else(|err| panic!("failed to parse tokens: {:?}\n{:?}", err, s.args));
for err in errors {
panic!("{:?}", err);
@ -45,7 +52,7 @@ fn tokens_input(input: PathBuf) {
let fm = cm.load_file(&input).unwrap();
let tokens = {
let mut lexer = Lexer::new(SourceFileInput::from(&*fm));
let mut lexer = Lexer::new(SourceFileInput::from(&*fm), Default::default());
let mut tokens = vec![];
@ -59,9 +66,16 @@ fn tokens_input(input: PathBuf) {
};
let mut errors = vec![];
let ss: Stylesheet =
parse_tokens(&tokens, ParserConfig { parse_values: true }, &mut errors)
.expect("failed to parse tokens");
let ss: Stylesheet = parse_tokens(
&tokens,
ParserConfig {
parse_values: true,
..Default::default()
},
&mut errors,
)
.expect("failed to parse tokens");
for err in errors {
err.to_diagnostics(&handler).emit();
@ -78,16 +92,15 @@ fn tokens_input(input: PathBuf) {
.unwrap();
}
#[testing::fixture("tests/fixture/**/input.css")]
fn pass(input: PathBuf) {
fn test_pass(input: PathBuf, config: ParserConfig) {
eprintln!("Input: {}", input.display());
testing::run_test2(false, |cm, handler| {
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 lexer = Lexer::new(SourceFileInput::from(&*fm), config);
let mut parser = Parser::new(lexer, config);
let stylesheet = parser.parse_all();
@ -99,8 +112,8 @@ fn pass(input: PathBuf) {
actual_json.clone().compare_to_file(&ref_json_path).unwrap();
{
let mut lexer = Lexer::new(SourceFileInput::from(&*fm));
if !config.allow_wrong_line_comments {
let mut lexer = Lexer::new(SourceFileInput::from(&*fm), Default::default());
let mut tokens = Tokens {
span: Span::new(fm.start_pos, fm.end_pos, Default::default()),
tokens: vec![],
@ -123,9 +136,16 @@ fn pass(input: PathBuf) {
}
let mut errors = vec![];
let ss_tok: Stylesheet =
parse_tokens(&tokens, ParserConfig { parse_values: true }, &mut errors)
.expect("failed to parse token");
let ss_tok: Stylesheet = parse_tokens(
&tokens,
ParserConfig {
parse_values: true,
..Default::default()
},
&mut errors,
)
.expect("failed to parse token");
for err in errors {
err.to_diagnostics(&handler).emit();
@ -153,6 +173,29 @@ fn pass(input: PathBuf) {
.unwrap();
}
#[testing::fixture("tests/fixture/**/input.css")]
fn pass(input: PathBuf) {
test_pass(
input,
ParserConfig {
parse_values: true,
..Default::default()
},
)
}
#[testing::fixture("tests/line-comment/**/input.css")]
fn line_commetns(input: PathBuf) {
test_pass(
input,
ParserConfig {
parse_values: true,
allow_wrong_line_comments: true,
..Default::default()
},
)
}
#[testing::fixture("tests/recovery/**/input.css")]
fn recovery(input: PathBuf) {
eprintln!("Input: {}", input.display());
@ -168,9 +211,13 @@ fn recovery(input: PathBuf) {
let ref_json_path = input.parent().unwrap().join("output.json");
let config = ParserConfig {
parse_values: true,
allow_wrong_line_comments: false,
};
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 lexer = Lexer::new(SourceFileInput::from(&*fm), config);
let mut parser = Parser::new(lexer, config);
let stylesheet = parser.parse_all();
@ -183,7 +230,7 @@ fn recovery(input: PathBuf) {
actual_json.clone().compare_to_file(&ref_json_path).unwrap();
{
let mut lexer = Lexer::new(SourceFileInput::from(&*fm));
let mut lexer = Lexer::new(SourceFileInput::from(&*fm), Default::default());
let mut tokens = Tokens {
span: Span::new(fm.start_pos, fm.end_pos, Default::default()),
tokens: vec![],
@ -206,9 +253,15 @@ fn recovery(input: PathBuf) {
}
let mut errors = vec![];
let ss_tok: Stylesheet =
parse_tokens(&tokens, ParserConfig { parse_values: true }, &mut errors)
.expect("failed to parse token");
let ss_tok: Stylesheet = parse_tokens(
&tokens,
ParserConfig {
parse_values: true,
..Default::default()
},
&mut errors,
)
.expect("failed to parse token");
for err in errors {
err.to_diagnostics(&handler).emit();
@ -340,13 +393,19 @@ fn span(input: PathBuf) {
let dir = input.parent().unwrap().to_path_buf();
let output = testing::run_test2(false, |cm, handler| {
// Type annotation
if false {
return Ok(());
}
let config = ParserConfig {
parse_values: true,
..Default::default()
};
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 lexer = Lexer::new(SourceFileInput::from(&*fm), config);
let mut parser = Parser::new(lexer, config);
let stylesheet = parser.parse_all();
@ -382,9 +441,15 @@ fn fail(input: PathBuf) {
let stderr_path = input.parent().unwrap().join("output.stderr");
let stderr = testing::run_test2(false, |cm, handler| -> Result<(), _> {
let config = ParserConfig {
parse_values: true,
..Default::default()
};
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 lexer = Lexer::new(SourceFileInput::from(&*fm), config);
let mut parser = Parser::new(lexer, config);
let stylesheet = parser.parse_all();

View File

@ -0,0 +1,4 @@
// Line comment
foo {
color: red;
}

View File

@ -0,0 +1,101 @@
{
"type": "Stylesheet",
"span": {
"start": 15,
"end": 40,
"ctxt": 0
},
"rules": [
{
"type": "StyleRule",
"span": {
"start": 16,
"end": 39,
"ctxt": 0
},
"selectors": [
{
"type": "ComplexSelector",
"span": {
"start": 16,
"end": 19,
"ctxt": 0
},
"selectors": [
{
"type": "CompoundSelector",
"span": {
"start": 16,
"end": 19,
"ctxt": 0
},
"hasNestPrefix": false,
"combinator": null,
"typeSelector": {
"type": "NamespacedName",
"span": {
"start": 16,
"end": 19,
"ctxt": 0
},
"prefix": null,
"name": {
"type": "Text",
"span": {
"start": 16,
"end": 19,
"ctxt": 0
},
"value": "foo",
"raw": "foo"
}
},
"subclassSelectors": []
}
]
}
],
"block": {
"type": "DeclBlock",
"span": {
"start": 20,
"end": 39,
"ctxt": 0
},
"items": [
{
"type": "Property",
"span": {
"start": 26,
"end": 36,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 26,
"end": 31,
"ctxt": 0
},
"value": "color",
"raw": "color"
},
"values": [
{
"type": "Text",
"span": {
"start": 33,
"end": 36,
"ctxt": 0
},
"value": "red",
"raw": "red"
}
],
"important": null
}
]
}
}
]
}

View File

@ -0,0 +1,4 @@
foo {
// Line comment
color: red;
}

View File

@ -0,0 +1,101 @@
{
"type": "Stylesheet",
"span": {
"start": 0,
"end": 44,
"ctxt": 0
},
"rules": [
{
"type": "StyleRule",
"span": {
"start": 0,
"end": 43,
"ctxt": 0
},
"selectors": [
{
"type": "ComplexSelector",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"selectors": [
{
"type": "CompoundSelector",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"hasNestPrefix": false,
"combinator": null,
"typeSelector": {
"type": "NamespacedName",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"prefix": null,
"name": {
"type": "Text",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"value": "foo",
"raw": "foo"
}
},
"subclassSelectors": []
}
]
}
],
"block": {
"type": "DeclBlock",
"span": {
"start": 4,
"end": 43,
"ctxt": 0
},
"items": [
{
"type": "Property",
"span": {
"start": 30,
"end": 40,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 30,
"end": 35,
"ctxt": 0
},
"value": "color",
"raw": "color"
},
"values": [
{
"type": "Text",
"span": {
"start": 37,
"end": 40,
"ctxt": 0
},
"value": "red",
"raw": "red"
}
],
"important": null
}
]
}
}
]
}

View File

@ -0,0 +1,4 @@
foo {
color: red;
// Line comment
}

View File

@ -0,0 +1,101 @@
{
"type": "Stylesheet",
"span": {
"start": 0,
"end": 44,
"ctxt": 0
},
"rules": [
{
"type": "StyleRule",
"span": {
"start": 0,
"end": 43,
"ctxt": 0
},
"selectors": [
{
"type": "ComplexSelector",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"selectors": [
{
"type": "CompoundSelector",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"hasNestPrefix": false,
"combinator": null,
"typeSelector": {
"type": "NamespacedName",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"prefix": null,
"name": {
"type": "Text",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"value": "foo",
"raw": "foo"
}
},
"subclassSelectors": []
}
]
}
],
"block": {
"type": "DeclBlock",
"span": {
"start": 4,
"end": 43,
"ctxt": 0
},
"items": [
{
"type": "Property",
"span": {
"start": 10,
"end": 20,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 10,
"end": 15,
"ctxt": 0
},
"value": "color",
"raw": "color"
},
"values": [
{
"type": "Text",
"span": {
"start": 17,
"end": 20,
"ctxt": 0
},
"value": "red",
"raw": "red"
}
],
"important": null
}
]
}
}
]
}

View File

@ -0,0 +1,5 @@
foo {
color: red;
// Line comment
top: 0;
}

View File

@ -0,0 +1,131 @@
{
"type": "Stylesheet",
"span": {
"start": 0,
"end": 56,
"ctxt": 0
},
"rules": [
{
"type": "StyleRule",
"span": {
"start": 0,
"end": 55,
"ctxt": 0
},
"selectors": [
{
"type": "ComplexSelector",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"selectors": [
{
"type": "CompoundSelector",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"hasNestPrefix": false,
"combinator": null,
"typeSelector": {
"type": "NamespacedName",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"prefix": null,
"name": {
"type": "Text",
"span": {
"start": 0,
"end": 3,
"ctxt": 0
},
"value": "foo",
"raw": "foo"
}
},
"subclassSelectors": []
}
]
}
],
"block": {
"type": "DeclBlock",
"span": {
"start": 4,
"end": 55,
"ctxt": 0
},
"items": [
{
"type": "Property",
"span": {
"start": 10,
"end": 20,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 10,
"end": 15,
"ctxt": 0
},
"value": "color",
"raw": "color"
},
"values": [
{
"type": "Text",
"span": {
"start": 17,
"end": 20,
"ctxt": 0
},
"value": "red",
"raw": "red"
}
],
"important": null
},
{
"type": "Property",
"span": {
"start": 46,
"end": 52,
"ctxt": 0
},
"name": {
"type": "Text",
"span": {
"start": 46,
"end": 49,
"ctxt": 0
},
"value": "top",
"raw": "top"
},
"values": [
{
"type": "Number",
"span": {
"start": 51,
"end": 52,
"ctxt": 0
},
"value": 0.0
}
],
"important": null
}
]
}
}
]
}

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_stylis"
repository = "https://github.com/swc-project/swc.git"
version = "0.14.0"
version = "0.15.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -18,6 +18,6 @@ swc_css_utils = {version = "0.13.0", path = "../utils/"}
swc_css_visit = {version = "0.15.0", path = "../visit"}
[dev-dependencies]
swc_css_codegen = {version = "0.15.0", path = "../codegen"}
swc_css_parser = {version = "0.17.0", path = "../parser"}
swc_css_codegen = {version = "0.16.0", path = "../codegen"}
swc_css_parser = {version = "0.18.0", path = "../parser"}
testing = {version = "0.14.0", path = "../../testing"}