oxc/crates/oxc_parser/src/ts/declaration.rs

139 lines
4.5 KiB
Rust

use crate::{lexer::Kind, Parser};
impl<'a> Parser<'a> {
/// Check if the parser is at a start of a declaration
fn at_start_of_ts_declaration_worker(&mut self) -> bool {
loop {
match self.cur_kind() {
Kind::Var | Kind::Let | Kind::Const | Kind::Function | Kind::Class | Kind::Enum => {
return true;
}
Kind::Interface | Kind::Type => {
self.bump_any();
return self.cur_kind().is_binding_identifier()
&& !self.cur_token().is_on_new_line;
}
Kind::Module | Kind::Namespace => {
self.bump_any();
return !self.cur_token().is_on_new_line
&& (self.cur_kind().is_binding_identifier()
|| self.cur_kind() == Kind::Str);
}
Kind::Abstract
| Kind::Accessor
| Kind::Async
| Kind::Declare
| Kind::Private
| Kind::Protected
| Kind::Public
| Kind::Readonly => {
self.bump_any();
if self.cur_token().is_on_new_line {
return false;
}
}
Kind::Global => {
self.bump_any();
return matches!(self.cur_kind(), Kind::Ident | Kind::LCurly | Kind::Export);
}
Kind::Import => {
self.bump_any();
return matches!(self.cur_kind(), Kind::Str | Kind::Star | Kind::LCurly)
|| self.cur_kind().is_identifier();
}
Kind::Export => {
self.bump_any();
let kind = if self.cur_kind() == Kind::Type {
self.peek_kind()
} else {
self.cur_kind()
};
// This allows constructs like
// `export *`, `export default`, `export {}`, `export = {}` along with all
// export [declaration]
if matches!(
kind,
Kind::Eq | Kind::Star | Kind::Default | Kind::LCurly | Kind::At | Kind::As
) {
return true;
}
// falls through to check next token
}
Kind::Static => {
self.bump_any();
}
_ => {
return false;
}
}
}
}
pub(crate) fn at_start_of_ts_declaration(&mut self) -> bool {
self.lookahead(Self::at_start_of_ts_declaration_worker)
}
}
#[cfg(test)]
mod test_is_declaration {
use oxc_allocator::Allocator;
use oxc_span::SourceType;
use super::*;
fn run_check(source: &str, expected: bool) {
let alloc = Allocator::default();
let source_type = SourceType::default().with_typescript(true);
let mut parser = Parser::new(&alloc, source, source_type);
// Get the parser to the first token.
parser.bump_any();
assert_eq!(expected, parser.at_start_of_ts_declaration());
}
#[test]
fn test_lexical_declaration() {
run_check("const a = 1", true);
run_check("let a = 1", true);
}
#[test]
fn test_combined_modifier() {
// The order of modifiers shouldn't matter
let source = "abstract async function a() { return 123; }";
let source2 = "async abstract class C{}";
run_check(source, true);
run_check(source2, true);
}
#[test]
fn test_contextual_keyword() {
// Here abstract should not be parsed as starting a declaration
run_check("abstract = 1", false);
run_check("private = 'abc'", false);
run_check("abstract\nclass A {}", false);
}
#[test]
fn test_export() {
run_check("export = {}", true);
run_check("export *", true);
// modifiers can be combined with expory
run_check("abstract export type T", true);
}
#[test]
fn test_declare_module() {
run_check("declare module 'external1' {}", true);
}
#[test]
fn test_const_enum() {
run_check("const enum A {}", true);
}
#[test]
fn test_type_alias() {
run_check("type string = I", true);
run_check("type void = I", false);
}
}