use oxc_ast::ast::{Comment, Program};
use oxc_diagnostics::Diagnostics;
use oxc_span::SourceType;
use crate::{
ParseOptions, Parser, ParserReturn,
config::{NoTokensParserConfig, ParserConfig, RuntimeParserConfig, TokensParserConfig},
};
pub struct ProgramObservation<'a> {
parsed: ParserReturn<'a>,
source_text: &'a str,
source_type: SourceType,
options: ParseOptions,
}
pub struct AdmittedProgram<'p, 'a> {
observation: &'p ProgramObservation<'a>,
}
impl<'a> Parser<'a, NoTokensParserConfig> {
pub fn parse_observed(self) -> ProgramObservation<'a> {
observe_parser(self)
}
}
impl<'a> Parser<'a, TokensParserConfig> {
pub fn parse_observed(self) -> ProgramObservation<'a> {
observe_parser(self)
}
}
impl<'a> Parser<'a, RuntimeParserConfig> {
pub fn parse_observed(self) -> ProgramObservation<'a> {
observe_parser(self)
}
}
fn observe_parser<'a, C: ParserConfig>(parser: Parser<'a, C>) -> ProgramObservation<'a> {
let source_text = parser.source_text;
let source_type = parser.source_type;
let options = parser.options;
let parsed = parser.parse();
ProgramObservation { parsed, source_text, source_type, options }
}
impl<'a> ProgramObservation<'a> {
#[must_use]
pub fn admitted(&self) -> Option<AdmittedProgram<'_, 'a>> {
(!self.parsed.panicked
&& !self.parsed.is_flow_language
&& !self.parsed.diagnostics.has_errors())
.then_some(AdmittedProgram { observation: self })
}
#[must_use]
pub fn diagnostics(&self) -> &Diagnostics {
&self.parsed.diagnostics
}
#[must_use]
pub fn comments(&self) -> &[Comment] {
self.parsed.program.comments.as_slice()
}
#[must_use]
pub fn panicked(&self) -> bool {
self.parsed.panicked
}
#[must_use]
pub fn is_flow_language(&self) -> bool {
self.parsed.is_flow_language
}
}
impl<'p, 'a> AdmittedProgram<'p, 'a> {
#[must_use]
pub fn program(&self) -> &'p Program<'a> {
&self.observation.parsed.program
}
#[must_use]
pub fn source(&self) -> &'a str {
self.observation.source_text
}
#[must_use]
pub fn has_legacy_literals(&self) -> bool {
self.observation.parsed.has_legacy_literals
}
#[must_use]
pub fn source_type(&self) -> SourceType {
self.observation.source_type
}
#[must_use]
pub fn options(&self) -> ParseOptions {
self.observation.options
}
}
#[cfg(test)]
mod legacy_literal_tests {
use super::*;
use oxc_allocator::Allocator;
#[test]
fn completed_tail_receipt_survives_committed_unambiguous_await_reparse() {
let arena = Allocator::default();
for literal in ["010", "'\\8'"] {
let source = format!("await /x/u; export {{}}; const value={literal};");
let parsed = Parser::new(&arena, &source, SourceType::unambiguous()).parse_observed();
let admitted = parsed.admitted().expect("completed original syntax");
assert!(admitted.has_legacy_literals(), "{literal}");
assert!(admitted.program().source_type.is_module());
}
}
#[test]
fn original_decoder_retains_legacy_facts_without_changing_syntax_admission() {
let arena = Allocator::default();
for literal in ["010", "08", "09.5", "'\\1'", "'\\8'", "'\\9'", "'\\00'"] {
let source = format!("const value={literal};");
let parsed = Parser::new(&arena, &source, SourceType::mjs()).parse_observed();
let admitted = parsed.admitted().expect("unchanged syntax admission");
assert!(admitted.has_legacy_literals(), "{literal}");
assert!(parsed.diagnostics().is_empty());
assert_eq!(admitted.source(), source);
}
for literal in [
"0",
"0o10",
"0x10",
"0b10",
"0.1",
"0e1",
"'雪'",
"'\\0'",
"'\\x01'",
"'\\u0001'",
"'\\\\1'",
"'\\🌸'",
] {
let source = format!("const value={literal};");
let parsed = Parser::new(&arena, &source, SourceType::mjs()).parse_observed();
assert!(
!parsed.admitted().expect("actual admission").has_legacy_literals(),
"{literal}"
);
}
}
}