From a3a13d76fa201ff4db1a7f356dad9a7ff639bc3c Mon Sep 17 00:00:00 2001 From: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com> Date: Wed, 26 Jul 2023 23:32:45 +0300 Subject: [PATCH] feat(rome_js_parser): CSS lexer #4682 (#4724) --- Cargo.lock | 1 + .../src/generated/node_factory.rs | 15 ++- .../src/generated/syntax_factory.rs | 25 +++- crates/rome_css_factory/src/lib.rs | 2 +- crates/rome_css_parser/Cargo.toml | 1 + crates/rome_css_parser/src/lexer/mod.rs | 11 +- crates/rome_css_parser/src/lib.rs | 116 ++++++++++++++++ crates/rome_css_parser/src/parser.rs | 63 +++++++++ crates/rome_css_parser/src/syntax.rs | 13 ++ crates/rome_css_parser/src/token_source.rs | 117 ++++++++++++++++ .../tests/css_test_suite/ok/empty.css | 0 .../tests/css_test_suite/ok/empty.css.snap | 31 +++++ crates/rome_css_parser/tests/spec_test.rs | 125 ++++++++++++++++++ crates/rome_css_parser/tests/spec_tests.rs | 8 ++ crates/rome_css_syntax/src/generated/kind.rs | 4 +- .../rome_css_syntax/src/generated/macros.rs | 8 +- crates/rome_css_syntax/src/generated/nodes.rs | 97 ++++++++++++-- .../src/generated/nodes_mut.rs | 14 ++ crates/rome_css_syntax/src/lib.rs | 1 + crates/rome_json_parser/src/parser.rs | 26 +--- crates/rome_json_parser/src/token_source.rs | 26 ++-- crates/rome_json_syntax/src/lib.rs | 6 + crates/rome_rowan/src/syntax/trivia.rs | 10 +- xtask/codegen/css.ungram | 6 +- xtask/codegen/src/css_kinds_src.rs | 1 + 25 files changed, 661 insertions(+), 66 deletions(-) create mode 100644 crates/rome_css_parser/src/parser.rs create mode 100644 crates/rome_css_parser/src/syntax.rs create mode 100644 crates/rome_css_parser/src/token_source.rs create mode 100644 crates/rome_css_parser/tests/css_test_suite/ok/empty.css create mode 100644 crates/rome_css_parser/tests/css_test_suite/ok/empty.css.snap create mode 100644 crates/rome_css_parser/tests/spec_test.rs create mode 100644 crates/rome_css_parser/tests/spec_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 03bc90419ef..1e910542dc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1754,6 +1754,7 @@ dependencies = [ "quickcheck", "quickcheck_macros", "rome_console", + "rome_css_factory", "rome_css_syntax", "rome_diagnostics", "rome_js_unicode_table", diff --git a/crates/rome_css_factory/src/generated/node_factory.rs b/crates/rome_css_factory/src/generated/node_factory.rs index 4777e7bda5f..1d2240bce38 100644 --- a/crates/rome_css_factory/src/generated/node_factory.rs +++ b/crates/rome_css_factory/src/generated/node_factory.rs @@ -576,6 +576,15 @@ pub fn css_ratio(numerator: CssNumber, denominator: CssNumber) -> CssRatio { ], )) } +pub fn css_root(rules: CssRuleList, eof_token: SyntaxToken) -> CssRoot { + CssRoot::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_ROOT, + [ + Some(SyntaxElement::Node(rules.into_syntax())), + Some(SyntaxElement::Token(eof_token)), + ], + )) +} pub fn css_rule(prelude: CssSelectorList, block: CssBlock) -> CssRule { CssRule::unwrap_cast(SyntaxNode::new_detached( CssSyntaxKind::CSS_RULE, @@ -779,13 +788,13 @@ where .map(|item| Some(item.into_syntax().into())), )) } -pub fn css_root(items: I) -> CssRoot +pub fn css_rule_list(items: I) -> CssRuleList where I: IntoIterator, I::IntoIter: ExactSizeIterator, { - CssRoot::unwrap_cast(SyntaxNode::new_detached( - CssSyntaxKind::CSS_ROOT, + CssRuleList::unwrap_cast(SyntaxNode::new_detached( + CssSyntaxKind::CSS_RULE_LIST, items .into_iter() .map(|item| Some(item.into_syntax().into())), diff --git a/crates/rome_css_factory/src/generated/syntax_factory.rs b/crates/rome_css_factory/src/generated/syntax_factory.rs index 95c24e09a68..bb174c5ddd9 100644 --- a/crates/rome_css_factory/src/generated/syntax_factory.rs +++ b/crates/rome_css_factory/src/generated/syntax_factory.rs @@ -1154,6 +1154,29 @@ impl SyntaxFactory for CssSyntaxFactory { } slots.into_node(CSS_RATIO, children) } + CSS_ROOT => { + let mut elements = (&children).into_iter(); + let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); + let mut current_element = elements.next(); + if let Some(element) = ¤t_element { + if CssRuleList::can_cast(element.kind()) { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if let Some(element) = ¤t_element { + if element.kind() == T![EOF] { + slots.mark_present(); + current_element = elements.next(); + } + } + slots.next_slot(); + if current_element.is_some() { + return RawSyntaxNode::new(CSS_ROOT.to_bogus(), children.into_iter().map(Some)); + } + slots.into_node(CSS_ROOT, children) + } CSS_RULE => { let mut elements = (&children).into_iter(); let mut slots: RawNodeSlots<2usize> = RawNodeSlots::default(); @@ -1395,7 +1418,7 @@ impl SyntaxFactory for CssSyntaxFactory { CSS_PARAMETER_LIST => { Self::make_node_list_syntax(kind, children, CssParameter::can_cast) } - CSS_ROOT => Self::make_node_list_syntax(kind, children, AnyCssRule::can_cast), + CSS_RULE_LIST => Self::make_node_list_syntax(kind, children, AnyCssRule::can_cast), CSS_SELECTOR_LIST => Self::make_separated_list_syntax( kind, children, diff --git a/crates/rome_css_factory/src/lib.rs b/crates/rome_css_factory/src/lib.rs index e6673ec38b8..16a8de7b562 100644 --- a/crates/rome_css_factory/src/lib.rs +++ b/crates/rome_css_factory/src/lib.rs @@ -1,4 +1,4 @@ -use crate::generated::CssSyntaxFactory; +pub use crate::generated::CssSyntaxFactory; use rome_css_syntax::CssLanguage; use rome_rowan::TreeBuilder; diff --git a/crates/rome_css_parser/Cargo.toml b/crates/rome_css_parser/Cargo.toml index 44fa3c75c83..5354fafa477 100644 --- a/crates/rome_css_parser/Cargo.toml +++ b/crates/rome_css_parser/Cargo.toml @@ -13,6 +13,7 @@ version = "0.0.1" [dependencies] rome_console = { workspace = true } rome_css_syntax = { workspace = true } +rome_css_factory = { workspace = true } rome_diagnostics = { workspace = true } rome_js_unicode_table = { workspace = true } rome_parser = { workspace = true } diff --git a/crates/rome_css_parser/src/lexer/mod.rs b/crates/rome_css_parser/src/lexer/mod.rs index 55b070050bf..e7f03d09c30 100644 --- a/crates/rome_css_parser/src/lexer/mod.rs +++ b/crates/rome_css_parser/src/lexer/mod.rs @@ -1,9 +1,8 @@ //! An extremely fast, lookup table based, СSS lexer which yields SyntaxKind tokens used by the rome-css parser. -#![allow(dead_code)] - #[rustfmt::skip] mod tests; +use crate::CssParserOptions; use rome_css_syntax::{CssSyntaxKind, CssSyntaxKind::*, TextLen, TextRange, TextSize, T}; use rome_js_unicode_table::{is_id_continue, is_id_start, lookup_byte, Dispatch::*}; use rome_parser::diagnostic::ParseDiagnostic; @@ -35,6 +34,8 @@ pub(crate) struct Lexer<'src> { position: usize, diagnostics: Vec, + + config: CssParserOptions, } impl<'src> Lexer<'src> { @@ -44,9 +45,15 @@ impl<'src> Lexer<'src> { source, position: 0, diagnostics: vec![], + config: CssParserOptions::default(), } } + pub(crate) fn with_config(mut self, config: CssParserOptions) -> Self { + self.config = config; + self + } + /// Returns the source code pub fn source(&self) -> &'src str { self.source diff --git a/crates/rome_css_parser/src/lib.rs b/crates/rome_css_parser/src/lib.rs index 027ef38d7e4..6a8d1c8b668 100644 --- a/crates/rome_css_parser/src/lib.rs +++ b/crates/rome_css_parser/src/lib.rs @@ -1,4 +1,120 @@ //! Extremely fast, lossless, and error tolerant CSS Parser. +use crate::parser::CssParser; + +use crate::syntax::parse_root; +pub use parser::CssParserOptions; +use rome_css_factory::CssSyntaxFactory; +use rome_css_syntax::{CssLanguage, CssRoot, CssSyntaxNode}; +pub use rome_parser::prelude::*; +use rome_parser::tree_sink::LosslessTreeSink; +use rome_rowan::{AstNode, NodeCache}; + mod lexer; +mod parser; mod prelude; +mod syntax; +mod token_source; + +pub(crate) type CssLosslessTreeSink<'source> = + LosslessTreeSink<'source, CssLanguage, CssSyntaxFactory>; + +pub fn parse_css(source: &str, options: CssParserOptions) -> CssParse { + let mut cache = NodeCache::default(); + parse_css_with_cache(source, &mut cache, options) +} + +/// Parses the provided string as CSS program using the provided node cache. +pub fn parse_css_with_cache( + source: &str, + cache: &mut NodeCache, + config: CssParserOptions, +) -> CssParse { + tracing::debug_span!("parse").in_scope(move || { + let mut parser = CssParser::new(source, config); + + parse_root(&mut parser); + + let (events, diagnostics, trivia) = parser.finish(); + + let mut tree_sink = CssLosslessTreeSink::with_cache(source, &trivia, cache); + rome_parser::event::process(&mut tree_sink, events, diagnostics); + let (green, diagnostics) = tree_sink.finish(); + + CssParse::new(green, diagnostics) + }) +} + +/// A utility struct for managing the result of a parser job +#[derive(Debug)] +pub struct CssParse { + root: CssSyntaxNode, + diagnostics: Vec, +} + +impl CssParse { + pub fn new(root: CssSyntaxNode, diagnostics: Vec) -> CssParse { + CssParse { root, diagnostics } + } + + /// The syntax node represented by this Parse result + /// + /// ``` + /// # use rome_css_parser::parse_css; + /// # use rome_css_syntax::CssSyntaxKind; + /// # use rome_rowan::{AstNode, AstNodeList, SyntaxError}; + /// + /// # fn main() -> Result<(), SyntaxError> { + /// use rome_css_syntax::CssSyntaxKind; + /// use rome_css_parser::CssParserOptions; + /// let parse = parse_css(r#""#, CssParserOptions::default()); + /// + /// let root_value = parse.tree().rules(); + /// + /// assert_eq!(root_value.syntax().kind(), CssSyntaxKind::CSS_RULE_LIST); + /// + /// # Ok(()) + /// # } + /// ``` + pub fn syntax(&self) -> CssSyntaxNode { + self.root.clone() + } + + /// Get the diagnostics which occurred when parsing + pub fn diagnostics(&self) -> &[ParseDiagnostic] { + &self.diagnostics + } + + /// Get the diagnostics which occurred when parsing + pub fn into_diagnostics(self) -> Vec { + self.diagnostics + } + + /// Returns [true] if the parser encountered some errors during the parsing. + pub fn has_errors(&self) -> bool { + self.diagnostics + .iter() + .any(|diagnostic| diagnostic.is_error()) + } + + /// Convert this parse result into a typed AST node. + /// + /// # Panics + /// Panics if the node represented by this parse result mismatches. + pub fn tree(&self) -> CssRoot { + CssRoot::unwrap_cast(self.syntax()) + } +} + +#[cfg(test)] +mod tests { + use crate::{parse_css, CssParserOptions}; + + #[test] + fn parser_smoke_test() { + let src = r#" +"#; + + let _css = parse_css(src, CssParserOptions::default()); + } +} diff --git a/crates/rome_css_parser/src/parser.rs b/crates/rome_css_parser/src/parser.rs new file mode 100644 index 00000000000..36312aea023 --- /dev/null +++ b/crates/rome_css_parser/src/parser.rs @@ -0,0 +1,63 @@ +use crate::token_source::CssTokenSource; +use rome_css_syntax::CssSyntaxKind; +use rome_parser::diagnostic::merge_diagnostics; +use rome_parser::event::Event; +use rome_parser::prelude::*; +use rome_parser::token_source::Trivia; +use rome_parser::ParserContext; + +pub(crate) struct CssParser<'source> { + context: ParserContext, + source: CssTokenSource<'source>, +} + +#[derive(Default, Debug, Clone, Copy)] +pub struct CssParserOptions { + pub allow_single_line_comments: bool, +} + +impl CssParserOptions { + pub fn with_allow_single_line_comments(mut self) -> Self { + self.allow_single_line_comments = true; + self + } +} + +impl<'source> CssParser<'source> { + pub fn new(source: &'source str, config: CssParserOptions) -> Self { + Self { + context: ParserContext::default(), + source: CssTokenSource::from_str(source, config), + } + } + + pub fn finish(self) -> (Vec>, Vec, Vec) { + let (trivia, lexer_diagnostics) = self.source.finish(); + let (events, parse_diagnostics) = self.context.finish(); + + let diagnostics = merge_diagnostics(lexer_diagnostics, parse_diagnostics); + + (events, diagnostics, trivia) + } +} + +impl<'source> Parser for CssParser<'source> { + type Kind = CssSyntaxKind; + type Source = CssTokenSource<'source>; + + fn context(&self) -> &ParserContext { + &self.context + } + + fn context_mut(&mut self) -> &mut ParserContext { + &mut self.context + } + + fn source(&self) -> &Self::Source { + &self.source + } + + fn source_mut(&mut self) -> &mut Self::Source { + &mut self.source + } +} diff --git a/crates/rome_css_parser/src/syntax.rs b/crates/rome_css_parser/src/syntax.rs new file mode 100644 index 00000000000..7f92f21444d --- /dev/null +++ b/crates/rome_css_parser/src/syntax.rs @@ -0,0 +1,13 @@ +use crate::parser::CssParser; +use rome_css_syntax::CssSyntaxKind::*; +use rome_parser::Parser; + +pub(crate) fn parse_root(p: &mut CssParser) { + let m = p.start(); + + let rules = p.start(); + + rules.complete(p, CSS_RULE_LIST); + + m.complete(p, CSS_ROOT); +} diff --git a/crates/rome_css_parser/src/token_source.rs b/crates/rome_css_parser/src/token_source.rs new file mode 100644 index 00000000000..102b54811bb --- /dev/null +++ b/crates/rome_css_parser/src/token_source.rs @@ -0,0 +1,117 @@ +use crate::lexer::{Lexer, Token}; +use crate::CssParserOptions; +use rome_css_syntax::CssSyntaxKind::{EOF, TOMBSTONE}; +use rome_css_syntax::{CssSyntaxKind, TextRange}; +use rome_parser::diagnostic::ParseDiagnostic; +use rome_parser::prelude::TokenSource; +use rome_parser::token_source::Trivia; +use rome_rowan::TriviaPieceKind; + +pub(crate) struct CssTokenSource<'source> { + lexer: Lexer<'source>, + trivia: Vec, + current: CssSyntaxKind, + current_range: TextRange, + preceding_line_break: bool, + config: CssParserOptions, +} + +impl<'source> CssTokenSource<'source> { + pub fn from_str(source: &'source str, config: CssParserOptions) -> Self { + let lexer = Lexer::from_str(source).with_config(config); + + let mut source = Self { + lexer, + trivia: Vec::new(), + current: TOMBSTONE, + current_range: TextRange::default(), + preceding_line_break: false, + config, + }; + + source.next_non_trivia_token(true); + source + } + + fn next_non_trivia_token(&mut self, first_token: bool) { + let mut trailing = !first_token; + self.preceding_line_break = false; + + while let Some(token) = self.lexer.next_token() { + let trivia_kind = TriviaPieceKind::try_from(token.kind()); + + match trivia_kind { + Err(_) => { + self.set_current_token(token); + // Not trivia + break; + } + Ok(trivia_kind) + if trivia_kind.is_single_line_comment() + && !self.config.allow_single_line_comments => + { + self.set_current_token(token); + + // Not trivia + break; + } + Ok(trivia_kind) => { + if trivia_kind.is_newline() { + trailing = false; + self.preceding_line_break = true; + } + + self.trivia + .push(Trivia::new(trivia_kind, token.range(), trailing)); + } + } + } + } + + fn set_current_token(&mut self, token: Token) { + self.current = token.kind(); + self.current_range = token.range() + } +} + +impl<'source> TokenSource for CssTokenSource<'source> { + type Kind = CssSyntaxKind; + + fn current(&self) -> Self::Kind { + self.current + } + + fn current_range(&self) -> TextRange { + self.current_range + } + + fn text(&self) -> &str { + self.lexer.source() + } + + fn has_preceding_line_break(&self) -> bool { + self.preceding_line_break + } + + fn bump(&mut self) { + if self.current != EOF { + self.next_non_trivia_token(false) + } + } + + fn skip_as_trivia(&mut self) { + if self.current() != EOF { + self.trivia.push(Trivia::new( + TriviaPieceKind::Skipped, + self.current_range(), + false, + )); + + self.next_non_trivia_token(false) + } + } + + fn finish(self) -> (Vec, Vec) { + (self.trivia, self.lexer.finish()) + } +} diff --git a/crates/rome_css_parser/tests/css_test_suite/ok/empty.css b/crates/rome_css_parser/tests/css_test_suite/ok/empty.css new file mode 100644 index 00000000000..e69de29bb2d diff --git a/crates/rome_css_parser/tests/css_test_suite/ok/empty.css.snap b/crates/rome_css_parser/tests/css_test_suite/ok/empty.css.snap new file mode 100644 index 00000000000..35ced7bd9bc --- /dev/null +++ b/crates/rome_css_parser/tests/css_test_suite/ok/empty.css.snap @@ -0,0 +1,31 @@ +--- +source: crates/rome_css_parser/tests/spec_test.rs +expression: snapshot +--- + +## Input + +```css + +``` + + +## AST + +``` +CssRoot { + rules: CssRuleList [], + eof_token: EOF@0..0 "" [] [], +} +``` + +## CST + +``` +0: CSS_ROOT@0..0 + 0: CSS_RULE_LIST@0..0 + 1: EOF@0..0 "" [] [] + +``` + + diff --git a/crates/rome_css_parser/tests/spec_test.rs b/crates/rome_css_parser/tests/spec_test.rs new file mode 100644 index 00000000000..bf20f13e7dd --- /dev/null +++ b/crates/rome_css_parser/tests/spec_test.rs @@ -0,0 +1,125 @@ +use rome_console::fmt::{Formatter, Termcolor}; +use rome_console::markup; +use rome_css_parser::{parse_css, CssParserOptions}; +use rome_diagnostics::display::PrintDiagnostic; +use rome_diagnostics::termcolor; +use rome_diagnostics::DiagnosticExt; +use rome_rowan::SyntaxKind; +use std::fmt::Write; +use std::fs; +use std::path::Path; + +#[derive(Copy, Clone)] +pub enum ExpectedOutcome { + Pass, + Fail, + Undefined, +} + +pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_str: &str) { + let outcome = match outcome_str { + "ok" => ExpectedOutcome::Pass, + "error" => ExpectedOutcome::Fail, + "undefined" => ExpectedOutcome::Undefined, + "allow_single_line_comments" => ExpectedOutcome::Pass, + _ => panic!("Invalid expected outcome {outcome_str}"), + }; + + let test_case_path = Path::new(test_case); + + let file_name = test_case_path + .file_name() + .expect("Expected test to have a file name") + .to_str() + .expect("File name to be valid UTF8"); + + let content = fs::read_to_string(test_case_path) + .expect("Expected test path to be a readable file in UTF8 encoding"); + + let parse_config = CssParserOptions { + allow_single_line_comments: outcome_str == "allow_single_line_comments", + }; + let parsed = parse_css(&content, parse_config); + let formatted_ast = format!("{:#?}", parsed.tree()); + + let mut snapshot = String::new(); + writeln!(snapshot, "\n## Input\n\n```css\n{content}\n```\n\n").unwrap(); + + writeln!( + snapshot, + r#"## AST + +``` +{formatted_ast} +``` + +## CST + +``` +{:#?} +``` +"#, + parsed.syntax() + ) + .unwrap(); + + let diagnostics = parsed.diagnostics(); + if !diagnostics.is_empty() { + let mut diagnostics_buffer = termcolor::Buffer::no_color(); + + let termcolor = &mut Termcolor(&mut diagnostics_buffer); + let mut formatter = Formatter::new(termcolor); + + for diagnostic in diagnostics { + let error = diagnostic + .clone() + .with_file_path(file_name) + .with_file_source_code(&content); + + formatter + .write_markup(markup! { + {PrintDiagnostic::verbose(&error)} + }) + .expect("failed to emit diagnostic"); + } + + let formatted_diagnostics = + std::str::from_utf8(diagnostics_buffer.as_slice()).expect("non utf8 in error buffer"); + + if matches!(outcome, ExpectedOutcome::Pass) { + panic!("Expected no errors to be present in a test case that is expected to pass but the following diagnostics are present:\n{formatted_diagnostics}") + } + + writeln!(snapshot, "## Diagnostics\n\n```").unwrap(); + snapshot.write_str(formatted_diagnostics).unwrap(); + + writeln!(snapshot, "```\n").unwrap(); + } + + match outcome { + ExpectedOutcome::Pass => { + let missing_required = formatted_ast.contains("missing (required)"); + if missing_required + || parsed + .syntax() + .descendants() + .any(|node| node.kind().is_bogus()) + { + panic!("Parsed tree of a 'OK' test case should not contain any missing required children or bogus nodes"); + } + } + ExpectedOutcome::Fail => { + if parsed.diagnostics().is_empty() { + panic!("Failing test must have diagnostics"); + } + } + _ => {} + } + + insta::with_settings!({ + prepend_module_to_snapshot => false, + snapshot_path => &test_directory, + }, { + insta::assert_snapshot!(file_name, snapshot); + }); +} diff --git a/crates/rome_css_parser/tests/spec_tests.rs b/crates/rome_css_parser/tests/spec_tests.rs new file mode 100644 index 00000000000..117649508a9 --- /dev/null +++ b/crates/rome_css_parser/tests/spec_tests.rs @@ -0,0 +1,8 @@ +#![allow(non_snake_case)] + +mod spec_test; + +mod ok { + //! Tests that must pass according to the CSS specification + tests_macros::gen_tests! {"tests/css_test_suite/ok/*.css", crate::spec_test::run, "ok"} +} diff --git a/crates/rome_css_syntax/src/generated/kind.rs b/crates/rome_css_syntax/src/generated/kind.rs index 437d21fd95c..d3a60fbf132 100644 --- a/crates/rome_css_syntax/src/generated/kind.rs +++ b/crates/rome_css_syntax/src/generated/kind.rs @@ -212,6 +212,7 @@ pub enum CssSyntaxKind { COMMENT, MULTILINE_COMMENT, CSS_ROOT, + CSS_RULE_LIST, CSS_ID_SELECTOR_PATTERN, CSS_RULE, CSS_SELECTOR_LIST, @@ -288,7 +289,8 @@ impl CssSyntaxKind { } pub const fn is_list(self) -> bool { match self { - CSS_SELECTOR_LIST + CSS_RULE_LIST + | CSS_SELECTOR_LIST | CSS_ANY_SELECTOR_PATTERN_LIST | CSS_AT_KEYFRAMES_ITEM_LIST | CSS_AT_MEDIA_QUERY_LIST diff --git a/crates/rome_css_syntax/src/generated/macros.rs b/crates/rome_css_syntax/src/generated/macros.rs index 167faa840cb..55cb257d6a2 100644 --- a/crates/rome_css_syntax/src/generated/macros.rs +++ b/crates/rome_css_syntax/src/generated/macros.rs @@ -166,6 +166,10 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::CssRatio::new_unchecked(node) }; $body } + $crate::CssSyntaxKind::CSS_ROOT => { + let $pattern = unsafe { $crate::CssRoot::new_unchecked(node) }; + $body + } $crate::CssSyntaxKind::CSS_RULE => { let $pattern = unsafe { $crate::CssRule::new_unchecked(node) }; $body @@ -232,8 +236,8 @@ macro_rules! map_syntax_node { let $pattern = unsafe { $crate::CssParameterList::new_unchecked(node) }; $body } - $crate::CssSyntaxKind::CSS_ROOT => { - let $pattern = unsafe { $crate::CssRoot::new_unchecked(node) }; + $crate::CssSyntaxKind::CSS_RULE_LIST => { + let $pattern = unsafe { $crate::CssRuleList::new_unchecked(node) }; $body } $crate::CssSyntaxKind::CSS_SELECTOR_LIST => { diff --git a/crates/rome_css_syntax/src/generated/nodes.rs b/crates/rome_css_syntax/src/generated/nodes.rs index 95fa7f107db..d95be1361f9 100644 --- a/crates/rome_css_syntax/src/generated/nodes.rs +++ b/crates/rome_css_syntax/src/generated/nodes.rs @@ -1526,6 +1526,43 @@ pub struct CssRatioFields { pub denominator: SyntaxResult, } #[derive(Clone, PartialEq, Eq, Hash)] +pub struct CssRoot { + pub(crate) syntax: SyntaxNode, +} +impl CssRoot { + #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] + #[doc = r""] + #[doc = r" # Safety"] + #[doc = r" This function must be guarded with a call to [AstNode::can_cast]"] + #[doc = r" or a match on [SyntaxNode::kind]"] + #[inline] + pub const unsafe fn new_unchecked(syntax: SyntaxNode) -> Self { Self { syntax } } + pub fn as_fields(&self) -> CssRootFields { + CssRootFields { + rules: self.rules(), + eof_token: self.eof_token(), + } + } + pub fn rules(&self) -> CssRuleList { support::list(&self.syntax, 0usize) } + pub fn eof_token(&self) -> SyntaxResult { + support::required_token(&self.syntax, 1usize) + } +} +#[cfg(feature = "serde")] +impl Serialize for CssRoot { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.as_fields().serialize(serializer) + } +} +#[cfg_attr(feature = "serde", derive(Serialize))] +pub struct CssRootFields { + pub rules: CssRuleList, + pub eof_token: SyntaxResult, +} +#[derive(Clone, PartialEq, Eq, Hash)] pub struct CssRule { pub(crate) syntax: SyntaxNode, } @@ -3259,6 +3296,35 @@ impl From for SyntaxNode { impl From for SyntaxElement { fn from(n: CssRatio) -> SyntaxElement { n.syntax.into() } } +impl AstNode for CssRoot { + type Language = Language; + const KIND_SET: SyntaxKindSet = + SyntaxKindSet::from_raw(RawSyntaxKind(CSS_ROOT as u16)); + fn can_cast(kind: SyntaxKind) -> bool { kind == CSS_ROOT } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } + fn into_syntax(self) -> SyntaxNode { self.syntax } +} +impl std::fmt::Debug for CssRoot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CssRoot") + .field("rules", &self.rules()) + .field("eof_token", &support::DebugSyntaxResult(self.eof_token())) + .finish() + } +} +impl From for SyntaxNode { + fn from(n: CssRoot) -> SyntaxNode { n.syntax } +} +impl From for SyntaxElement { + fn from(n: CssRoot) -> SyntaxElement { n.syntax.into() } +} impl AstNode for CssRule { type Language = Language; const KIND_SET: SyntaxKindSet = @@ -4258,6 +4324,11 @@ impl std::fmt::Display for CssRatio { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for CssRoot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for CssRule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) @@ -4825,10 +4896,10 @@ impl IntoIterator for CssParameterList { fn into_iter(self) -> Self::IntoIter { self.iter() } } #[derive(Clone, Eq, PartialEq, Hash)] -pub struct CssRoot { +pub struct CssRuleList { syntax_list: SyntaxList, } -impl CssRoot { +impl CssRuleList { #[doc = r" Create an AstNode from a SyntaxNode without checking its kind"] #[doc = r""] #[doc = r" # Safety"] @@ -4841,14 +4912,14 @@ impl CssRoot { } } } -impl AstNode for CssRoot { +impl AstNode for CssRuleList { type Language = Language; const KIND_SET: SyntaxKindSet = - SyntaxKindSet::from_raw(RawSyntaxKind(CSS_ROOT as u16)); - fn can_cast(kind: SyntaxKind) -> bool { kind == CSS_ROOT } - fn cast(syntax: SyntaxNode) -> Option { + SyntaxKindSet::from_raw(RawSyntaxKind(CSS_RULE_LIST as u16)); + fn can_cast(kind: SyntaxKind) -> bool { kind == CSS_RULE_LIST } + fn cast(syntax: SyntaxNode) -> Option { if Self::can_cast(syntax.kind()) { - Some(CssRoot { + Some(CssRuleList { syntax_list: syntax.into_list(), }) } else { @@ -4859,7 +4930,7 @@ impl AstNode for CssRoot { fn into_syntax(self) -> SyntaxNode { self.syntax_list.into_node() } } #[cfg(feature = "serde")] -impl Serialize for CssRoot { +impl Serialize for CssRuleList { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -4871,24 +4942,24 @@ impl Serialize for CssRoot { seq.end() } } -impl AstNodeList for CssRoot { +impl AstNodeList for CssRuleList { type Language = Language; type Node = AnyCssRule; fn syntax_list(&self) -> &SyntaxList { &self.syntax_list } fn into_syntax_list(self) -> SyntaxList { self.syntax_list } } -impl Debug for CssRoot { +impl Debug for CssRuleList { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str("CssRoot ")?; + f.write_str("CssRuleList ")?; f.debug_list().entries(self.iter()).finish() } } -impl IntoIterator for &CssRoot { +impl IntoIterator for &CssRuleList { type Item = AnyCssRule; type IntoIter = AstNodeListIterator; fn into_iter(self) -> Self::IntoIter { self.iter() } } -impl IntoIterator for CssRoot { +impl IntoIterator for CssRuleList { type Item = AnyCssRule; type IntoIter = AstNodeListIterator; fn into_iter(self) -> Self::IntoIter { self.iter() } diff --git a/crates/rome_css_syntax/src/generated/nodes_mut.rs b/crates/rome_css_syntax/src/generated/nodes_mut.rs index 89cf4fb888c..2c46106d1e3 100644 --- a/crates/rome_css_syntax/src/generated/nodes_mut.rs +++ b/crates/rome_css_syntax/src/generated/nodes_mut.rs @@ -691,6 +691,20 @@ impl CssRatio { ) } } +impl CssRoot { + pub fn with_rules(self, element: CssRuleList) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(0usize..=0usize, once(Some(element.into_syntax().into()))), + ) + } + pub fn with_eof_token(self, element: SyntaxToken) -> Self { + Self::unwrap_cast( + self.syntax + .splice_slots(1usize..=1usize, once(Some(element.into()))), + ) + } +} impl CssRule { pub fn with_prelude(self, element: CssSelectorList) -> Self { Self::unwrap_cast( diff --git a/crates/rome_css_syntax/src/lib.rs b/crates/rome_css_syntax/src/lib.rs index 5046bfdfb21..37957f7aa19 100644 --- a/crates/rome_css_syntax/src/lib.rs +++ b/crates/rome_css_syntax/src/lib.rs @@ -96,6 +96,7 @@ impl TryFrom for TriviaPieceKind { CssSyntaxKind::NEWLINE => Ok(TriviaPieceKind::Newline), CssSyntaxKind::WHITESPACE => Ok(TriviaPieceKind::Whitespace), CssSyntaxKind::COMMENT => Ok(TriviaPieceKind::SingleLineComment), + CssSyntaxKind::MULTILINE_COMMENT => Ok(TriviaPieceKind::MultiLineComment), _ => unreachable!("Not Trivia"), } } else { diff --git a/crates/rome_json_parser/src/parser.rs b/crates/rome_json_parser/src/parser.rs index 58842128238..05eb0058e82 100644 --- a/crates/rome_json_parser/src/parser.rs +++ b/crates/rome_json_parser/src/parser.rs @@ -1,6 +1,6 @@ use crate::token_source::JsonTokenSource; use rome_json_syntax::JsonSyntaxKind; -use rome_parser::diagnostic::{expected_token, merge_diagnostics}; +use rome_parser::diagnostic::merge_diagnostics; use rome_parser::event::Event; use rome_parser::prelude::*; use rome_parser::token_source::Trivia; @@ -9,7 +9,6 @@ use rome_parser::ParserContext; pub(crate) struct JsonParser<'source> { context: ParserContext, source: JsonTokenSource<'source>, - config: JsonParserOptions, } #[derive(Default, Debug, Clone, Copy)] @@ -29,7 +28,6 @@ impl<'source> JsonParser<'source> { Self { context: ParserContext::default(), source: JsonTokenSource::from_str(source, config), - config, } } @@ -68,26 +66,4 @@ impl<'source> Parser for JsonParser<'source> { fn source_mut(&mut self) -> &mut Self::Source { &mut self.source } - - /// Try to eat a specific token kind, if the kind is not there then adds an error to the events stack. - fn expect(&mut self, kind: Self::Kind) -> bool { - if self.eat(kind) { - true - } else { - if self.config.allow_comments { - while matches!( - self.cur(), - JsonSyntaxKind::COMMENT | JsonSyntaxKind::MULTILINE_COMMENT - ) { - self.bump_any(); - } - } - if self.eat(kind) { - return true; - } - - self.error(expected_token(kind)); - false - } - } } diff --git a/crates/rome_json_parser/src/token_source.rs b/crates/rome_json_parser/src/token_source.rs index 4d1c4546fac..f84b3209a19 100644 --- a/crates/rome_json_parser/src/token_source.rs +++ b/crates/rome_json_parser/src/token_source.rs @@ -1,4 +1,4 @@ -use crate::lexer::Lexer; +use crate::lexer::{Lexer, Token}; use crate::JsonParserOptions; use rome_json_syntax::JsonSyntaxKind::{EOF, TOMBSTONE}; use rome_json_syntax::{JsonSyntaxKind, TextRange}; @@ -42,19 +42,12 @@ impl<'source> JsonTokenSource<'source> { match trivia_kind { Err(_) => { - if self.config.allow_comments && token.kind().is_comments() { - let trivia_kind = match token.kind() { - JsonSyntaxKind::COMMENT => TriviaPieceKind::SingleLineComment, - JsonSyntaxKind::MULTILINE_COMMENT => TriviaPieceKind::MultiLineComment, - _ => unreachable!(), - }; - self.trivia - .push(Trivia::new(trivia_kind, token.range(), trailing)); - continue; - } - - self.current = token.kind(); - self.current_range = token.range(); + self.set_current_token(token); + // Not trivia + break; + } + Ok(trivia_kind) if trivia_kind.is_comment() && !self.config.allow_comments => { + self.set_current_token(token); // Not trivia break; @@ -71,6 +64,11 @@ impl<'source> JsonTokenSource<'source> { } } } + + fn set_current_token(&mut self, token: Token) { + self.current = token.kind(); + self.current_range = token.range() + } } impl<'source> TokenSource for JsonTokenSource<'source> { diff --git a/crates/rome_json_syntax/src/lib.rs b/crates/rome_json_syntax/src/lib.rs index 9d8790cbb9c..73586748e3e 100644 --- a/crates/rome_json_syntax/src/lib.rs +++ b/crates/rome_json_syntax/src/lib.rs @@ -100,6 +100,12 @@ impl TryFrom for TriviaPieceKind { JsonSyntaxKind::WHITESPACE => Ok(TriviaPieceKind::Whitespace), _ => unreachable!("Not Trivia"), } + } else if value.is_comments() { + match value { + JsonSyntaxKind::COMMENT => Ok(TriviaPieceKind::SingleLineComment), + JsonSyntaxKind::MULTILINE_COMMENT => Ok(TriviaPieceKind::MultiLineComment), + _ => unreachable!("Not Comment"), + } } else { Err(()) } diff --git a/crates/rome_rowan/src/syntax/trivia.rs b/crates/rome_rowan/src/syntax/trivia.rs index b585c053cf3..6a01bddd098 100644 --- a/crates/rome_rowan/src/syntax/trivia.rs +++ b/crates/rome_rowan/src/syntax/trivia.rs @@ -28,6 +28,10 @@ impl TriviaPieceKind { matches!(self, TriviaPieceKind::Whitespace) } + pub const fn is_comment(&self) -> bool { + self.is_single_line_comment() || self.is_multiline_comment() + } + pub const fn is_single_line_comment(&self) -> bool { matches!(self, TriviaPieceKind::SingleLineComment) } @@ -683,7 +687,7 @@ impl std::fmt::Debug for SyntaxTrivia { /// use rome_rowan::{trim_leading_trivia_pieces, RawSyntaxToken, SyntaxToken, TriviaPiece}; /// /// let token = SyntaxToken::::new_detached( -/// RawLanguageKind::LET_TOKEN, +/// RawLanguageKind::LET_TOKEN, /// "\n\t /*c*/ let \t", /// [TriviaPiece::newline(1), TriviaPiece::whitespace(2), TriviaPiece::multi_line_comment(5), TriviaPiece::whitespace(1)], /// [TriviaPiece::whitespace(2)] @@ -719,7 +723,7 @@ pub fn trim_leading_trivia_pieces( /// use rome_rowan::{trim_trailing_trivia_pieces, RawSyntaxToken, SyntaxToken, TriviaPiece}; /// /// let token = SyntaxToken::::new_detached( -/// RawLanguageKind::LET_TOKEN, +/// RawLanguageKind::LET_TOKEN, /// "\t/*c*/\n\t let ", /// [TriviaPiece::whitespace(1), TriviaPiece::multi_line_comment(5), TriviaPiece::newline(1), TriviaPiece::whitespace(2)], /// [TriviaPiece::whitespace(1)], @@ -759,7 +763,7 @@ pub fn trim_trailing_trivia_pieces( /// use rome_rowan::{chain_trivia_pieces, RawSyntaxToken, SyntaxToken, TriviaPiece, TriviaPieceKind}; /// /// let first_token = SyntaxToken::::new_detached( -/// RawLanguageKind::LET_TOKEN, +/// RawLanguageKind::LET_TOKEN, /// "\n\t let \t\t", /// [TriviaPiece::whitespace(3)], /// [TriviaPiece::whitespace(3)] diff --git a/xtask/codegen/css.ungram b/xtask/codegen/css.ungram index 91412c4c8af..702936ec36b 100644 --- a/xtask/codegen/css.ungram +++ b/xtask/codegen/css.ungram @@ -38,7 +38,11 @@ SyntaxElement = SyntaxElement CssBogus = SyntaxElement* -CssRoot = AnyCssRule* +CssRoot = + rules: CssRuleList + eof: 'EOF' + +CssRuleList = AnyCssRule* AnyCssRule = CssRule diff --git a/xtask/codegen/src/css_kinds_src.rs b/xtask/codegen/src/css_kinds_src.rs index 143c3705efb..b25e75fe989 100644 --- a/xtask/codegen/src/css_kinds_src.rs +++ b/xtask/codegen/src/css_kinds_src.rs @@ -212,6 +212,7 @@ pub const CSS_KINDS_SRC: KindsSrc = KindsSrc { ], nodes: &[ "CSS_ROOT", + "CSS_RULE_LIST", "CSS_ID_SELECTOR_PATTERN", "CSS_RULE", "CSS_SELECTOR_LIST",