1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
//! Utilities for high level parsing of js code.
use crate::{
ast::{Expr, Module, Script},
*,
};
use rslint_errors::Severity;
use std::marker::PhantomData;
/// A utility struct for managing the result of a parser job
#[derive(Debug)]
pub struct Parse<T> {
green: GreenNode,
errors: Vec<ParserError>,
_ty: PhantomData<fn() -> T>,
}
impl<T> Clone for Parse<T> {
fn clone(&self) -> Parse<T> {
Parse {
green: self.green.clone(),
errors: self.errors.clone(),
_ty: PhantomData,
}
}
}
impl<T> Parse<T> {
pub fn new(green: GreenNode, errors: Vec<ParserError>) -> Parse<T> {
Parse {
green,
errors,
_ty: PhantomData,
}
}
/// Consume the parse result and get its green node.
/// This is useful for multithreaded accesses to the tree as
/// syntax nodes are not sync but green nodes are.
pub fn green(self) -> GreenNode {
self.green
}
/// The syntax node represented by this Parse result
///
/// ```
/// use rslint_parser::{parse_text, ast::IfStmt, SyntaxNodeExt, SyntaxKind, AstNode};
///
/// let parse = parse_text(
/// "
/// if (a > 5) {
/// /* something */
/// }
/// ", 0);
///
/// // The first child of the root syntax node (Script) is the if statement.
/// let if_stmt = parse.syntax().first_child().unwrap();
///
/// assert_eq!(if_stmt.kind(), SyntaxKind::IF_STMT);
///
/// // The if statement node is untyped, we must first cast it to a typed ast node
/// // to be able to get properties of it in an easy way.
/// assert_eq!(if_stmt.to::<IfStmt>().condition().unwrap().syntax().text(), "(a > 5)");
/// ```
pub fn syntax(&self) -> SyntaxNode {
SyntaxNode::new_root(self.green.clone())
}
/// Get the errors which ocurred when parsing
pub fn errors(&self) -> &[ParserError] {
&*self.errors
}
}
impl<T: AstNode> Parse<T> {
/// Convert the result to an untyped SyntaxNode parse.
#[allow(clippy::wrong_self_convention)]
pub fn to_syntax(self) -> Parse<SyntaxNode> {
Parse {
green: self.green,
errors: self.errors,
_ty: PhantomData,
}
}
/// Convert this parse result into a typed AST node.
///
/// # Panics
/// Panics if the node represented by this parse result mismatches.
pub fn tree(&self) -> T {
T::cast(self.syntax()).unwrap()
}
/// Try to convert this parse's untyped syntax node into an AST node.
pub fn try_tree(&self) -> Option<T> {
T::cast(self.syntax())
}
/// Convert this parse into a result
pub fn ok(self) -> Result<T, Vec<ParserError>> {
if !self.errors.iter().any(|d| d.severity == Severity::Error) {
Ok(self.tree())
} else {
Err(self.errors)
}
}
}
/// Run the rslint_lexer lexer to turn source code into tokens and errors produced by the lexer
pub fn tokenize(text: &str, file_id: usize) -> (Vec<rslint_lexer::Token>, Vec<ParserError>) {
let mut tokens = Vec::new();
let mut errors = Vec::new();
for (tok, error) in rslint_lexer::Lexer::from_str(text, file_id) {
tokens.push(tok);
if let Some(err) = error {
errors.push(err)
}
}
(tokens, errors)
}
fn parse_common(
text: &str,
file_id: usize,
syntax: Syntax,
) -> (Vec<Event>, Vec<ParserError>, Vec<rslint_lexer::Token>) {
let (tokens, mut errors) = tokenize(text, file_id);
let tok_source = TokenSource::new(text, &tokens);
let mut parser = crate::Parser::new(tok_source, file_id, syntax);
crate::syntax::program::parse(&mut parser);
let (events, p_errs) = parser.finish();
errors.extend(p_errs);
(events, errors, tokens)
}
/// Parse text into a [`Parse`](Parse) which can then be turned into an untyped root [`SyntaxNode`](SyntaxNode).
/// Or turned into a typed [`Script`](Script) with [`tree`](Parse::tree).
///
/// ```
/// use rslint_parser::{ast::BracketExpr, parse_text, AstNode, SyntaxToken, SyntaxNodeExt, util};
///
/// let parse = parse_text("foo. bar[2]", 0);
/// // The untyped syntax node of `foo.bar[2]`, the root node is `Script`.
/// let untyped_expr_node = parse.syntax().first_child().unwrap();
///
/// // SyntaxNodes can be turned into a nice string representation.
/// println!("{:#?}", untyped_expr_node);
///
/// // You can then cast syntax nodes into a typed AST node.
/// let typed_ast_node = BracketExpr::cast(untyped_expr_node.first_child().unwrap().to_owned()).unwrap();
///
/// // Everything on every ast node is optional because of error recovery.
/// let prop = dbg!(typed_ast_node.prop()).unwrap();
///
/// // You can then go back to an untyped SyntaxNode and get its range, text, parents, children, etc.
/// assert_eq!(prop.syntax().text(), "2");
///
/// // Util has a function for yielding all tokens of a node.
/// let tokens = untyped_expr_node.tokens();
///
/// assert_eq!(&util::concat_tokens(&tokens), "foo. bar[2]")
/// ```
pub fn parse_text(text: &str, file_id: usize) -> Parse<Script> {
let (events, errors, tokens) = parse_common(text, file_id, Syntax::default());
let mut tree_sink = LosslessTreeSink::new(text, &tokens);
crate::process(&mut tree_sink, events, errors);
let (green, parse_errors) = tree_sink.finish();
Parse::new(green, parse_errors)
}
/// Lossly parse text into a [`Parse`](Parse) which can then be turned into an untyped root [`SyntaxNode`](SyntaxNode).
/// Or turned into a typed [`Script`](Script) with [`tree`](Parse::tree).
///
/// Unlike [`parse_text`], the final parse result includes no whitespace, it does however include errors.
///
/// Note however that the ranges and text of nodes still includes whitespace! Therefore you should trim text before rendering it.
/// The [`util`](crate::util) module has utility functions for dealing with this easily.
///
/// ```
/// use rslint_parser::{ast::BracketExpr, parse_text_lossy, AstNode, SyntaxToken, SyntaxNodeExt, util};
///
/// let parse = parse_text_lossy("foo. bar[2]", 0);
/// // The untyped syntax node of `foo.bar[2]`, the root node is `Script`.
/// let untyped_expr_node = parse.syntax().first_child().unwrap();
///
/// // SyntaxNodes can be turned into a nice string representation.
/// println!("{:#?}", untyped_expr_node);
///
/// // You can then cast syntax nodes into a typed AST node.
/// let typed_ast_node = BracketExpr::cast(untyped_expr_node.first_child().unwrap()).unwrap();
///
/// // Everything on every ast node is optional because of error recovery.
/// let prop = typed_ast_node.prop().unwrap();
///
/// // You can then go back to an untyped SyntaxNode and get its range, text, parents, children, etc.
/// assert_eq!(prop.syntax().text(), "2");
///
/// // Util has a function for yielding all tokens of a node.
/// let tokens = untyped_expr_node.tokens();
///
/// // End result does not include whitespace because the parsing is lossy in this case
/// assert_eq!(&util::concat_tokens(&tokens), "foo.bar[2]")
/// ```
pub fn parse_text_lossy(text: &str, file_id: usize) -> Parse<Script> {
let (events, errors, tokens) = parse_common(text, file_id, Syntax::default());
let mut tree_sink = LossyTreeSink::new(text, &tokens);
crate::process(&mut tree_sink, events, errors);
let (green, parse_errors) = tree_sink.finish();
Parse::new(green, parse_errors)
}
/// Same as [`parse_text_lossy`] but configures the parser to parse an ECMAScript module instead of a Script
pub fn parse_module_lossy(text: &str, file_id: usize) -> Parse<Module> {
let (events, errors, tokens) = parse_common(text, file_id, Syntax::default().module());
let mut tree_sink = LossyTreeSink::new(text, &tokens);
crate::process(&mut tree_sink, events, errors);
let (green, parse_errors) = tree_sink.finish();
Parse::new(green, parse_errors)
}
/// Same as [`parse_text`] but configures the parser to parse an ECMAScript module instead of a script
pub fn parse_module(text: &str, file_id: usize) -> Parse<Module> {
let (events, errors, tokens) = parse_common(text, file_id, Syntax::default().module());
let mut tree_sink = LosslessTreeSink::new(text, &tokens);
crate::process(&mut tree_sink, events, errors);
let (green, parse_errors) = tree_sink.finish();
Parse::new(green, parse_errors)
}
/// Losslessly Parse text into an expression [`Parse`](Parse) which can then be turned into an untyped root [`SyntaxNode`](SyntaxNode).
/// Or turned into a typed [`Expr`](Expr) with [`tree`](Parse::tree).
pub fn parse_expr(text: &str, file_id: usize) -> Parse<Expr> {
let (tokens, mut errors) = tokenize(text, file_id);
let tok_source = TokenSource::new(text, &tokens);
let mut parser = crate::Parser::new(tok_source, file_id, Syntax::default());
crate::syntax::expr::expr(&mut parser);
let (events, p_diags) = parser.finish();
errors.extend(p_diags);
let mut tree_sink = LosslessTreeSink::new(text, &tokens);
crate::process(&mut tree_sink, events, errors);
let (green, parse_errors) = tree_sink.finish();
Parse::new(green, parse_errors)
}
pub fn parse_with_syntax(text: &str, file_id: usize, syntax: Syntax) -> Parse<()> {
let (events, errors, tokens) = parse_common(text, file_id, syntax);
let mut tree_sink = LosslessTreeSink::new(text, &tokens);
crate::process(&mut tree_sink, events, errors);
let (green, parse_errors) = tree_sink.finish();
Parse::new(green, parse_errors)
}