feat(rslint_parser): Add missing support to parser & tree sink
Add support for the parser to emit missing events for children that weren't present in the source and add them as empty slots in the tree sink.
MODIFY
crates/rome_rowan/src/api.rs
crates/rome_rowan/src/api.rs
@@ -119,6 +119,14 @@ impl<L: Language> SyntaxNode<L> {
SyntaxNode::from(cursor::SyntaxNode::new_root(green))
}
+ /// Returns the element stored in the slot with the given index. Returns [None] if the slot is empty.
+ ///
+ /// ## Panics
+ /// If the slot index is out of bounds
+ pub fn element_in_slot(&self, slot: u32) -> Option<SyntaxElement<L>> {
+ self.raw.element_in_slot(slot).map(SyntaxElement::from)
+ }
+
pub fn kind(&self) -> L::Kind {
L::kind_from_raw(self.raw.kind())
}

MODIFY
crates/rome_rowan/src/cursor.rs
crates/rome_rowan/src/cursor.rs
@@ -609,6 +609,23 @@ impl SyntaxNode {
self.data().offset()
}
+ pub(crate) fn element_in_slot(&self, slot_index: u32) -> Option<SyntaxElement> {
+ let slot = self
+ .green_ref()
+ .slots()
+ .nth(slot_index as usize)
+ .expect("Slot index out of bounds");
+
+ slot.as_ref().map(|element| {
+ SyntaxElement::new(
+ element,
+ self.clone(),
+ slot_index,
+ self.offset() + slot.rel_offset(),
+ )
+ })
+ }
+
#[inline]
pub fn text_range(&self) -> TextRange {
self.data().text_range()

MODIFY
crates/rslint_parser/src/event.rs
crates/rslint_parser/src/event.rs
@@ -39,6 +39,10 @@ pub enum Event {
range: Range<usize>,
},
+ /// Missing child element, either because the child is optional and wasn't present in the source
+ /// or a required child is missing because of a syntax error
+ Missing,
+
MultipleTokens {
amount: u8,
kind: SyntaxKind,
@@ -104,6 +108,7 @@ pub fn process(sink: &mut impl TreeSink, mut events: Vec<Event>, errors: Vec<Par
}
}
Event::Finish { .. } => sink.finish_node(),
+ Event::Missing => sink.missing(),
Event::Token { kind, .. } => {
sink.token(kind);
}

MODIFY
crates/rslint_parser/src/lib.rs
crates/rslint_parser/src/lib.rs
@@ -130,6 +130,9 @@ pub trait TreeSink {
/// branch as current.
fn finish_node(&mut self);
+ /// Expected a token or child node that wasn't present and adds it to the current branch.
+ fn missing(&mut self);
+
/// Emit errors
fn errors(&mut self, errors: Vec<ParserError>);

MODIFY
crates/rslint_parser/src/lossless_tree_sink.rs
crates/rslint_parser/src/lossless_tree_sink.rs
@@ -63,6 +63,16 @@ impl<'a> TreeSink for LosslessTreeSink<'a> {
self.do_token(kind, len);
}
+ fn missing(&mut self) {
+ match mem::replace(&mut self.state, State::Normal) {
+ State::PendingStart => unreachable!(),
+ State::PendingFinish => self.inner.finish_node(),
+ State::Normal => (),
+ }
+
+ self.inner.missing();
+ }
+
// TODO: Attach comment whitespace to nodes
fn start_node(&mut self, kind: SyntaxKind) {
match mem::replace(&mut self.state, State::Normal) {

MODIFY
crates/rslint_parser/src/lossy_tree_sink.rs
crates/rslint_parser/src/lossy_tree_sink.rs
@@ -56,6 +56,16 @@ impl<'a> TreeSink for LossyTreeSink<'a> {
self.do_token(kind, len, false);
}
+ fn missing(&mut self) {
+ match mem::replace(&mut self.state, State::Normal) {
+ State::PendingStart => unreachable!(),
+ State::PendingFinish => self.inner.finish_node(),
+ State::Normal => (),
+ }
+
+ self.inner.missing();
+ }
+
fn start_node(&mut self, kind: SyntaxKind) {
match mem::replace(&mut self.state, State::Normal) {
State::PendingStart => {

MODIFY
crates/rslint_parser/src/parser.rs
crates/rslint_parser/src/parser.rs
@@ -285,6 +285,12 @@ impl<'t> Parser<'t> {
self.push_event(Event::Token { kind, range });
}
+ /// Inserts a marker for a missing child element because the child is optional and wasn't present in the source
+ /// or it's a mandatory child that wasn't present in the source because of a syntax error.
+ pub fn missing(&mut self) {
+ self.push_event(Event::Missing);
+ }
+
fn push_event(&mut self, event: Event) {
self.events.push(event)
}
@@ -307,7 +313,7 @@ impl<'t> Parser<'t> {
.expect("Parser source and tokens mismatch")
}
- /// Try to eat a specific token kind, if the kind is not there then add an error to the events stack.
+ /// Try to eat a specific token kind, if the kind is not there then add a missing marker and add an error to the events stack.
pub fn expect(&mut self, kind: SyntaxKind) -> bool {
if self.eat(kind) {
true
@@ -331,6 +337,7 @@ impl<'t> Parser<'t> {
.primary(self.cur_tok().range, "unexpected")
};
+ self.missing();
self.error(err);
false
}

MODIFY
crates/rslint_parser/src/tests.rs
crates/rslint_parser/src/tests.rs
-use crate::{ast::Module, parse_module, Parse, ParserError};
+use crate::ast::ArgList;
+use crate::{ast::Module, parse_module, AstNode, Parse, ParserError};
use expect_test::expect_file;
use rslint_errors::{file::SimpleFiles, Emitter};
+use rslint_syntax::SyntaxKind;
use std::fs;
use std::panic::catch_unwind;
use std::path::{Path, PathBuf};
@@ -14,6 +16,29 @@ fn parser_smoke_test() {
assert!(parse_module(src, 0).ok().is_ok());
}
+#[test]
+fn parser_missing_smoke_test() {
+ let src = r#"
+ console.log("Hello world";
+ "#;
+
+ let module = parse_module(src, 0);
+
+ let arg_list = module
+ .syntax()
+ .descendants()
+ .find_map(ArgList::cast)
+ .unwrap();
+
+ let opening = arg_list.syntax.element_in_slot(0);
+ let list = arg_list.syntax.element_in_slot(1);
+ let closing = arg_list.syntax().element_in_slot(2);
+
+ assert_eq!(opening.map(|o| o.to_string()), Some(String::from("(")));
+ assert_eq!(list.map(|l| l.kind()), Some(SyntaxKind::LIST));
+ assert_eq!(closing, None);
+}
+
fn test_data_dir() -> PathBuf {
project_dir().join("rslint_parser/test_data")
}

feat(rslint_parser): Add missing support to parser & tree sink (#1763)

Add support for the parser to emit missing events for children that weren't present in the source and add them as empty slots in the tree sink.