oxc_parser/
observation.rs1use oxc_ast::ast::{Comment, Program};
4use oxc_diagnostics::Diagnostics;
5use oxc_span::SourceType;
6
7use crate::{
8 ParseOptions, Parser, ParserReturn,
9 config::{NoTokensParserConfig, ParserConfig, RuntimeParserConfig, TokensParserConfig},
10};
11
12pub struct ProgramObservation<'a> {
28 parsed: ParserReturn<'a>,
29 source_text: &'a str,
30 source_type: SourceType,
31 options: ParseOptions,
32}
33
34pub struct AdmittedProgram<'p, 'a> {
55 observation: &'p ProgramObservation<'a>,
56}
57
58impl<'a> Parser<'a, NoTokensParserConfig> {
59 pub fn parse_observed(self) -> ProgramObservation<'a> {
80 observe_parser(self)
81 }
82}
83
84impl<'a> Parser<'a, TokensParserConfig> {
85 pub fn parse_observed(self) -> ProgramObservation<'a> {
87 observe_parser(self)
88 }
89}
90
91impl<'a> Parser<'a, RuntimeParserConfig> {
92 pub fn parse_observed(self) -> ProgramObservation<'a> {
94 observe_parser(self)
95 }
96}
97
98fn observe_parser<'a, C: ParserConfig>(parser: Parser<'a, C>) -> ProgramObservation<'a> {
100 let source_text = parser.source_text;
101 let source_type = parser.source_type;
102 let options = parser.options;
103 let parsed = parser.parse();
104 ProgramObservation { parsed, source_text, source_type, options }
105}
106
107impl<'a> ProgramObservation<'a> {
108 #[must_use]
110 pub fn admitted(&self) -> Option<AdmittedProgram<'_, 'a>> {
111 (!self.parsed.panicked
112 && !self.parsed.is_flow_language
113 && !self.parsed.diagnostics.has_errors())
114 .then_some(AdmittedProgram { observation: self })
115 }
116
117 #[must_use]
118 pub fn diagnostics(&self) -> &Diagnostics {
119 &self.parsed.diagnostics
120 }
121
122 #[must_use]
123 pub fn comments(&self) -> &[Comment] {
124 self.parsed.program.comments.as_slice()
125 }
126
127 #[must_use]
128 pub fn panicked(&self) -> bool {
129 self.parsed.panicked
130 }
131
132 #[must_use]
133 pub fn is_flow_language(&self) -> bool {
134 self.parsed.is_flow_language
135 }
136}
137
138impl<'p, 'a> AdmittedProgram<'p, 'a> {
139 #[must_use]
140 pub fn program(&self) -> &'p Program<'a> {
141 &self.observation.parsed.program
142 }
143
144 #[must_use]
146 pub fn source(&self) -> &'a str {
147 self.observation.source_text
148 }
149
150 #[must_use]
154 pub fn has_legacy_literals(&self) -> bool {
155 self.observation.parsed.has_legacy_literals
156 }
157
158 #[must_use]
160 pub fn source_type(&self) -> SourceType {
161 self.observation.source_type
162 }
163
164 #[must_use]
166 pub fn options(&self) -> ParseOptions {
167 self.observation.options
168 }
169}
170
171#[cfg(test)]
172mod legacy_literal_tests {
173 use super::*;
174 use oxc_allocator::Allocator;
175
176 #[test]
177 fn completed_tail_receipt_survives_committed_unambiguous_await_reparse() {
178 let arena = Allocator::default();
179 for literal in ["010", "'\\8'"] {
180 let source = format!("await /x/u; export {{}}; const value={literal};");
181 let parsed = Parser::new(&arena, &source, SourceType::unambiguous()).parse_observed();
182 let admitted = parsed.admitted().expect("completed original syntax");
183 assert!(admitted.has_legacy_literals(), "{literal}");
184 assert!(admitted.program().source_type.is_module());
185 }
186 }
187
188 #[test]
189 fn original_decoder_retains_legacy_facts_without_changing_syntax_admission() {
190 let arena = Allocator::default();
191 for literal in ["010", "08", "09.5", "'\\1'", "'\\8'", "'\\9'", "'\\00'"] {
192 let source = format!("const value={literal};");
193 let parsed = Parser::new(&arena, &source, SourceType::mjs()).parse_observed();
194 let admitted = parsed.admitted().expect("unchanged syntax admission");
195 assert!(admitted.has_legacy_literals(), "{literal}");
196 assert!(parsed.diagnostics().is_empty());
197 assert_eq!(admitted.source(), source);
198 }
199 for literal in [
200 "0",
201 "0o10",
202 "0x10",
203 "0b10",
204 "0.1",
205 "0e1",
206 "'雪'",
207 "'\\0'",
208 "'\\x01'",
209 "'\\u0001'",
210 "'\\\\1'",
211 "'\\🌸'",
212 ] {
213 let source = format!("const value={literal};");
214 let parsed = Parser::new(&arena, &source, SourceType::mjs()).parse_observed();
215 assert!(
216 !parsed.admitted().expect("actual admission").has_legacy_literals(),
217 "{literal}"
218 );
219 }
220 }
221}