use crate::*;
use biome_js_syntax::{
AnyJsRoot, JsFileSource, JsLanguage, JsModule, JsScript, JsSyntaxNode, ModuleKind,
};
use biome_parser::event::Event;
use biome_parser::token_source::Trivia;
use biome_rowan::{AstNode, NodeCache};
use std::marker::PhantomData;
#[derive(Debug)]
pub struct Parse<T> {
root: JsSyntaxNode,
errors: Vec<ParseDiagnostic>,
_ty: PhantomData<T>,
}
impl<T> Parse<T> {
pub fn new_module(root: JsSyntaxNode, errors: Vec<ParseDiagnostic>) -> Parse<T> {
Self::new(root, errors)
}
pub fn new_script(root: JsSyntaxNode, errors: Vec<ParseDiagnostic>) -> Parse<T> {
Self::new(root, errors)
}
pub fn new(root: JsSyntaxNode, errors: Vec<ParseDiagnostic>) -> Parse<T> {
Parse {
root,
errors,
_ty: PhantomData,
}
}
pub fn cast<N: AstNode<Language = JsLanguage>>(self) -> Option<Parse<N>> {
if N::can_cast(self.syntax().kind()) {
Some(Parse::new(self.root, self.errors))
} else {
None
}
}
pub fn syntax(&self) -> JsSyntaxNode {
self.root.clone()
}
pub fn diagnostics(&self) -> &[ParseDiagnostic] {
self.errors.as_slice()
}
pub fn into_diagnostics(self) -> Vec<ParseDiagnostic> {
self.errors
}
pub fn has_errors(&self) -> bool {
self.errors.iter().any(|diagnostic| diagnostic.is_error())
}
}
impl<T: AstNode<Language = JsLanguage>> Parse<T> {
pub fn tree(&self) -> T {
self.try_tree().unwrap_or_else(|| {
panic!(
"Expected tree to be a {} but root is:\n{:#?}",
std::any::type_name::<T>(),
self.syntax()
)
})
}
pub fn try_tree(&self) -> Option<T> {
T::cast(self.syntax())
}
pub fn ok(self) -> Result<T, Vec<ParseDiagnostic>> {
if !self.errors.iter().any(|d| d.is_error()) {
Ok(self.tree())
} else {
Err(self.errors)
}
}
}
fn parse_common(
text: &str,
source_type: JsFileSource,
options: JsParserOptions,
) -> (Vec<Event<JsSyntaxKind>>, Vec<ParseDiagnostic>, Vec<Trivia>) {
let mut parser = JsParser::new(text, source_type, options);
syntax::program::parse(&mut parser);
let (events, trivia, errors) = parser.finish();
(events, errors, trivia)
}
pub fn parse_script(text: &str, options: JsParserOptions) -> Parse<JsScript> {
parse(
text,
JsFileSource::js_module().with_module_kind(ModuleKind::Script),
options,
)
.cast::<JsScript>()
.unwrap()
}
pub fn parse_module(text: &str, options: JsParserOptions) -> Parse<JsModule> {
parse(text, JsFileSource::js_module(), options)
.cast::<JsModule>()
.unwrap()
}
pub fn parse(text: &str, source_type: JsFileSource, options: JsParserOptions) -> Parse<AnyJsRoot> {
let mut cache = NodeCache::default();
parse_js_with_cache(text, source_type, options, &mut cache)
}
pub fn parse_js_with_cache(
text: &str,
source_type: JsFileSource,
options: JsParserOptions,
cache: &mut NodeCache,
) -> Parse<AnyJsRoot> {
tracing::debug_span!("parse").in_scope(move || {
let (events, errors, tokens) = parse_common(text, source_type, options);
let mut tree_sink = JsLosslessTreeSink::with_cache(text, &tokens, cache);
biome_parser::event::process(&mut tree_sink, events, errors);
let (green, parse_errors) = tree_sink.finish();
Parse::new(green, parse_errors)
})
}