swc_ecma_parser/
lib.rs

1//! EcmaScript/TypeScript parser for the rust programming language.
2//!
3//! # Features
4//!
5//! ## Heavily tested
6//!
7//! Passes almost all tests from [tc39/test262][].
8//!
9//! ## Error reporting
10//!
11//! ```sh
12//! error: 'implements', 'interface', 'let', 'package', 'private', 'protected',  'public', 'static', or 'yield' cannot be used as an identifier in strict mode
13//!  --> invalid.js:3:10
14//!   |
15//! 3 | function yield() {
16//!   |          ^^^^^
17//! ```
18//!
19//! ## Error recovery
20//!
21//! The parser can recover from some parsing errors. For example, parser returns
22//! `Ok(Module)` for the code below, while emitting error to handler.
23//!
24//! ```ts
25//! const CONST = 9000 % 2;
26//! const enum D {
27//!     // Comma is required, but parser can recover because of the newline.
28//!     d = 10
29//!     g = CONST
30//! }
31//! ```
32//!
33//! # Example (lexer)
34//!
35//! See `lexer.rs` in examples directory.
36//!
37//! # Example (parser)
38//!
39//! ```
40//! #[macro_use]
41//! extern crate swc_common;
42//! extern crate swc_ecma_parser;
43//! use swc_common::sync::Lrc;
44//! use swc_common::{
45//!     errors::{ColorConfig, Handler},
46//!     FileName, FilePathMapping, SourceMap,
47//! };
48//! use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax};
49//!
50//! fn main() {
51//!     let cm: Lrc<SourceMap> = Default::default();
52//!     let handler =
53//!         Handler::with_tty_emitter(ColorConfig::Auto, true, false,
54//!         Some(cm.clone()));
55//!
56//!     // Real usage
57//!     // let fm = cm
58//!     //     .load_file(Path::new("test.js"))
59//!     //     .expect("failed to load test.js");
60//!     let fm = cm.new_source_file(
61//!         FileName::Custom("test.js".into()).into(),
62//!         "function foo() {}",
63//!     );
64//!     let lexer = Lexer::new(
65//!         // We want to parse ecmascript
66//!         Syntax::Es(Default::default()),
67//!         // EsVersion defaults to es5
68//!         Default::default(),
69//!         StringInput::from(&*fm),
70//!         None,
71//!     );
72//!
73//!     let mut parser = Parser::new_from(lexer);
74//!
75//!     for e in parser.take_errors() {
76//!         e.into_diagnostic(&handler).emit();
77//!     }
78//!
79//!     let _module = parser
80//!         .parse_module()
81//!         .map_err(|mut e| {
82//!             // Unrecoverable fatal error occurred
83//!             e.into_diagnostic(&handler).emit()
84//!         })
85//!         .expect("failed to parser module");
86//! }
87//! ```
88//!
89//! ## Cargo features
90//!
91//! ### `typescript`
92//!
93//! Enables typescript parser.
94//!
95//! ### `verify`
96//!
97//! Verify more errors, using `swc_ecma_visit`.
98//!
99//! ## Known issues
100//!
101//! ### Null character after `\`
102//!
103//! Because [String] of rust should only contain valid utf-8 characters while
104//! javascript allows non-utf8 characters, the parser stores invalid utf8
105//! characters in escaped form.
106//!
107//! As a result, swc needs a way to distinguish invalid-utf8 code points and
108//! input specified by the user. The parser stores a null character right after
109//! `\\` for non-utf8 code points. Note that other parts of swc is aware of this
110//! fact.
111//!
112//! Note that this can be changed at anytime with a breaking change.
113//!
114//! [tc39/test262]:https://github.com/tc39/test262
115
116#![cfg_attr(docsrs, feature(doc_cfg))]
117#![cfg_attr(test, feature(test))]
118#![deny(clippy::all)]
119#![deny(unused)]
120#![allow(unexpected_cfgs)]
121#![allow(clippy::nonminimal_bool)]
122#![allow(clippy::too_many_arguments)]
123#![allow(clippy::unnecessary_unwrap)]
124#![allow(clippy::vec_box)]
125#![allow(clippy::wrong_self_convention)]
126#![allow(clippy::match_like_matches_macro)]
127
128#[cfg(feature = "unstable")]
129pub mod unstable {
130    //! This module expose tokens related to the `swc_ecma_parser::lexer`.
131    //!
132    //! Unlike the tokens re-exported from `swc_ecma_lexer`, the token kinds
133    //! defined in the `swc_ecma_parser` here are non-strict for higher
134    //! performance.
135    //!
136    //! Although it's marked as unstable, we can ensure that we will not
137    //! introduce too many breaking changes. And we also encourage the
138    //! applications to migrate to the lexer and tokens in terms of
139    //! the performance.
140    //!
141    //! Also see the dicussion https://github.com/swc-project/swc/discussions/10683
142    pub use crate::lexer::{
143        capturing::Capturing,
144        token::{NextTokenAndSpan, Token, TokenAndSpan, TokenValue},
145    };
146}
147
148use error::Error;
149use swc_common::{comments::Comments, input::SourceFileInput, SourceFile};
150use swc_ecma_ast::*;
151
152mod context;
153pub mod error;
154mod legacy;
155pub mod lexer;
156mod parser;
157mod syntax;
158
159pub use context::Context;
160pub use legacy::token;
161pub use lexer::Lexer;
162pub use parser::*;
163pub use swc_common::input::{Input, StringInput};
164pub use syntax::{EsSyntax, Syntax, SyntaxFlags, TsSyntax};
165
166#[cfg(test)]
167fn with_test_sess<F, Ret>(src: &str, f: F) -> Result<Ret, ::testing::StdErr>
168where
169    F: FnOnce(&swc_common::errors::Handler, StringInput<'_>) -> Result<Ret, ()>,
170{
171    use swc_common::FileName;
172
173    ::testing::run_test(false, |cm, handler| {
174        let fm = cm.new_source_file(FileName::Real("testing".into()).into(), src.to_string());
175
176        f(handler, (&*fm).into())
177    })
178}
179
180pub fn with_file_parser<T>(
181    fm: &SourceFile,
182    syntax: Syntax,
183    target: EsVersion,
184    comments: Option<&dyn Comments>,
185    recovered_errors: &mut Vec<Error>,
186    op: impl for<'aa> FnOnce(&mut Parser<self::Lexer>) -> PResult<T>,
187) -> PResult<T> {
188    let lexer = self::Lexer::new(syntax, target, SourceFileInput::from(fm), comments);
189    let mut p = Parser::new_from(lexer);
190    let ret = op(&mut p);
191
192    recovered_errors.append(&mut p.take_errors());
193
194    ret
195}
196
197macro_rules! expose {
198    (
199        $name:ident,
200        $T:ty,
201        $($t:tt)*
202    ) => {
203        /// Note: This is recommended way to parse a file.
204        ///
205        /// This is an alias for [Parser], [Lexer] and [SourceFileInput], but
206        /// instantiation of generics occur in `swc_ecma_parser` crate.
207        pub fn $name(
208            fm: &SourceFile,
209            syntax: Syntax,
210            target: EsVersion,
211            comments: Option<&dyn Comments>,
212            recovered_errors: &mut Vec<Error>,
213        ) -> PResult<$T> {
214            with_file_parser(fm, syntax, target, comments, recovered_errors, $($t)*)
215        }
216    };
217}
218
219expose!(parse_file_as_expr, Box<Expr>, |p| {
220    // This allow to parse `import.meta`
221    let ctx = p.ctx();
222    p.set_ctx(ctx.union(Context::CanBeModule));
223    p.parse_expr()
224});
225expose!(parse_file_as_module, Module, |p| { p.parse_module() });
226expose!(parse_file_as_script, Script, |p| { p.parse_script() });
227expose!(parse_file_as_commonjs, Script, |p| { p.parse_commonjs() });
228expose!(parse_file_as_program, Program, |p| { p.parse_program() });
229
230#[inline(always)]
231#[cfg(any(
232    target_arch = "wasm32",
233    target_arch = "arm",
234    not(feature = "stacker"),
235    // miri does not work with stacker
236    miri
237))]
238fn maybe_grow<R, F: FnOnce() -> R>(_red_zone: usize, _stack_size: usize, callback: F) -> R {
239    callback()
240}
241
242#[inline(always)]
243#[cfg(all(
244    not(any(target_arch = "wasm32", target_arch = "arm", miri)),
245    feature = "stacker"
246))]
247fn maybe_grow<R, F: FnOnce() -> R>(red_zone: usize, stack_size: usize, callback: F) -> R {
248    stacker::maybe_grow(red_zone, stack_size, callback)
249}