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
12mod default_export;
13pub use default_export::OriginalDefaultExport;
14
15pub struct ProgramObservation<'a> {
31 parsed: ParserReturn<'a>,
32 source_text: &'a str,
33 source_type: SourceType,
34 options: ParseOptions,
35}
36
37pub struct AdmittedProgram<'p, 'a> {
58 observation: &'p ProgramObservation<'a>,
59}
60
61impl<'a> Parser<'a, NoTokensParserConfig> {
62 pub fn parse_observed(self) -> ProgramObservation<'a> {
83 observe_parser(self)
84 }
85}
86
87impl<'a> Parser<'a, TokensParserConfig> {
88 pub fn parse_observed(self) -> ProgramObservation<'a> {
90 observe_parser(self)
91 }
92}
93
94impl<'a> Parser<'a, RuntimeParserConfig> {
95 pub fn parse_observed(self) -> ProgramObservation<'a> {
97 observe_parser(self)
98 }
99}
100
101fn observe_parser<'a, C: ParserConfig>(parser: Parser<'a, C>) -> ProgramObservation<'a> {
103 let source_text = parser.source_text;
104 let source_type = parser.source_type;
105 let options = parser.options;
106 let parsed = parser.parse();
107 ProgramObservation { parsed, source_text, source_type, options }
108}
109
110impl<'a> ProgramObservation<'a> {
111 #[must_use]
113 pub fn admitted(&self) -> Option<AdmittedProgram<'_, 'a>> {
114 (!self.parsed.panicked
115 && !self.parsed.is_flow_language
116 && !self.parsed.diagnostics.has_errors())
117 .then_some(AdmittedProgram { observation: self })
118 }
119
120 #[must_use]
121 pub fn diagnostics(&self) -> &Diagnostics {
122 &self.parsed.diagnostics
123 }
124
125 #[must_use]
126 pub fn comments(&self) -> &[Comment] {
127 self.parsed.program.comments.as_slice()
128 }
129
130 #[must_use]
131 pub fn panicked(&self) -> bool {
132 self.parsed.panicked
133 }
134
135 #[must_use]
136 pub fn is_flow_language(&self) -> bool {
137 self.parsed.is_flow_language
138 }
139}
140
141impl<'p, 'a> AdmittedProgram<'p, 'a> {
142 #[must_use]
143 pub fn program(&self) -> &'p Program<'a> {
144 &self.observation.parsed.program
145 }
146
147 #[must_use]
149 pub fn source(&self) -> &'a str {
150 self.observation.source_text
151 }
152
153 #[must_use]
157 pub fn has_legacy_literals(&self) -> bool {
158 self.observation.parsed.has_legacy_literals
159 }
160
161 #[must_use]
165 pub fn has_jsdoc_comments(&self) -> bool {
166 self.observation.parsed.has_jsdoc_comments
167 }
168
169 #[must_use]
171 pub fn source_type(&self) -> SourceType {
172 self.observation.source_type
173 }
174
175 #[must_use]
177 pub fn options(&self) -> ParseOptions {
178 self.observation.options
179 }
180}
181
182#[cfg(test)]
183mod legacy_literal_tests {
184 use super::*;
185 use oxc_allocator::Allocator;
186
187 #[test]
188 fn completed_tail_receipt_survives_committed_unambiguous_await_reparse() {
189 let arena = Allocator::default();
190 for literal in ["010", "'\\8'"] {
191 let source = format!("await /x/u; export {{}}; const value={literal};");
192 let parsed = Parser::new(&arena, &source, SourceType::unambiguous()).parse_observed();
193 let admitted = parsed.admitted().expect("completed original syntax");
194 assert!(admitted.has_legacy_literals(), "{literal}");
195 assert!(admitted.program().source_type.is_module());
196 }
197 }
198
199 #[test]
200 fn original_decoder_retains_legacy_facts_without_changing_syntax_admission() {
201 let arena = Allocator::default();
202 for literal in ["010", "08", "09.5", "'\\1'", "'\\8'", "'\\9'", "'\\00'"] {
203 let source = format!("const value={literal};");
204 let parsed = Parser::new(&arena, &source, SourceType::mjs()).parse_observed();
205 let admitted = parsed.admitted().expect("unchanged syntax admission");
206 assert!(admitted.has_legacy_literals(), "{literal}");
207 assert!(parsed.diagnostics().is_empty());
208 assert_eq!(admitted.source(), source);
209 }
210 for literal in [
211 "0",
212 "0o10",
213 "0x10",
214 "0b10",
215 "0.1",
216 "0e1",
217 "'雪'",
218 "'\\0'",
219 "'\\x01'",
220 "'\\u0001'",
221 "'\\\\1'",
222 "'\\🌸'",
223 ] {
224 let source = format!("const value={literal};");
225 let parsed = Parser::new(&arena, &source, SourceType::mjs()).parse_observed();
226 assert!(
227 !parsed.admitted().expect("actual admission").has_legacy_literals(),
228 "{literal}"
229 );
230 }
231 }
232}
233
234#[cfg(test)]
235mod jsdoc_tests;