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() {}".into(),
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(clippy::nonminimal_bool)]
121#![allow(clippy::too_many_arguments)]
122#![allow(clippy::unnecessary_unwrap)]
123#![allow(clippy::vec_box)]
124#![allow(clippy::wrong_self_convention)]
125#![allow(clippy::match_like_matches_macro)]
126
127use error::Error;
128use lexer::Lexer;
129use serde::{Deserialize, Serialize};
130pub use swc_common::input::{Input, StringInput};
131use swc_common::{comments::Comments, input::SourceFileInput, SourceFile};
132use swc_ecma_ast::*;
133
134pub use self::parser::*;
135
136#[macro_use]
137mod macros;
138#[macro_use]
139pub mod token;
140pub mod error;
141pub mod lexer;
142mod parser;
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
145#[serde(deny_unknown_fields, tag = "syntax")]
146pub enum Syntax {
147    /// Standard
148    #[serde(rename = "ecmascript")]
149    Es(EsSyntax),
150    /// This variant requires the cargo feature `typescript` to be enabled.
151    #[cfg(feature = "typescript")]
152    #[cfg_attr(docsrs, doc(cfg(feature = "typescript")))]
153    #[serde(rename = "typescript")]
154    Typescript(TsSyntax),
155}
156
157impl Default for Syntax {
158    fn default() -> Self {
159        Syntax::Es(Default::default())
160    }
161}
162
163impl Syntax {
164    fn auto_accessors(self) -> bool {
165        match self {
166            Syntax::Es(EsSyntax {
167                auto_accessors: true,
168                ..
169            }) => true,
170            #[cfg(feature = "typescript")]
171            Syntax::Typescript(_) => true,
172            _ => false,
173        }
174    }
175
176    pub fn import_attributes(self) -> bool {
177        match self {
178            Syntax::Es(EsSyntax {
179                import_attributes, ..
180            }) => import_attributes,
181            #[cfg(feature = "typescript")]
182            Syntax::Typescript(_) => true,
183        }
184    }
185
186    /// Should we parse jsx?
187    pub fn jsx(self) -> bool {
188        match self {
189            Syntax::Es(EsSyntax { jsx: true, .. }) => true,
190            #[cfg(feature = "typescript")]
191            Syntax::Typescript(TsSyntax { tsx: true, .. }) => true,
192            _ => false,
193        }
194    }
195
196    pub fn fn_bind(self) -> bool {
197        matches!(self, Syntax::Es(EsSyntax { fn_bind: true, .. }))
198    }
199
200    pub fn decorators(self) -> bool {
201        match self {
202            Syntax::Es(EsSyntax {
203                decorators: true, ..
204            }) => true,
205            #[cfg(feature = "typescript")]
206            Syntax::Typescript(TsSyntax {
207                decorators: true, ..
208            }) => true,
209            _ => false,
210        }
211    }
212
213    pub fn decorators_before_export(self) -> bool {
214        match self {
215            Syntax::Es(EsSyntax {
216                decorators_before_export: true,
217                ..
218            }) => true,
219            #[cfg(feature = "typescript")]
220            Syntax::Typescript(..) => true,
221            _ => false,
222        }
223    }
224
225    /// Should we parse typescript?
226    #[cfg(not(feature = "typescript"))]
227    pub const fn typescript(self) -> bool {
228        false
229    }
230
231    /// Should we parse typescript?
232    #[cfg(feature = "typescript")]
233    pub const fn typescript(self) -> bool {
234        matches!(self, Syntax::Typescript(..))
235    }
236
237    pub fn export_default_from(self) -> bool {
238        matches!(
239            self,
240            Syntax::Es(EsSyntax {
241                export_default_from: true,
242                ..
243            })
244        )
245    }
246
247    pub fn dts(self) -> bool {
248        match self {
249            #[cfg(feature = "typescript")]
250            Syntax::Typescript(t) => t.dts,
251            _ => false,
252        }
253    }
254
255    pub(crate) fn allow_super_outside_method(self) -> bool {
256        match self {
257            Syntax::Es(EsSyntax {
258                allow_super_outside_method,
259                ..
260            }) => allow_super_outside_method,
261            #[cfg(feature = "typescript")]
262            Syntax::Typescript(_) => true,
263        }
264    }
265
266    pub(crate) fn allow_return_outside_function(self) -> bool {
267        match self {
268            Syntax::Es(EsSyntax {
269                allow_return_outside_function,
270                ..
271            }) => allow_return_outside_function,
272            #[cfg(feature = "typescript")]
273            Syntax::Typescript(_) => false,
274        }
275    }
276
277    pub(crate) fn early_errors(self) -> bool {
278        match self {
279            #[cfg(feature = "typescript")]
280            Syntax::Typescript(t) => !t.no_early_errors,
281            Syntax::Es(..) => true,
282        }
283    }
284
285    fn disallow_ambiguous_jsx_like(self) -> bool {
286        match self {
287            #[cfg(feature = "typescript")]
288            Syntax::Typescript(t) => t.disallow_ambiguous_jsx_like,
289            _ => false,
290        }
291    }
292
293    pub fn explicit_resource_management(&self) -> bool {
294        match self {
295            Syntax::Es(EsSyntax {
296                explicit_resource_management: using_decl,
297                ..
298            }) => *using_decl,
299            #[cfg(feature = "typescript")]
300            Syntax::Typescript(_) => true,
301        }
302    }
303}
304
305#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
306#[serde(rename_all = "camelCase")]
307pub struct TsSyntax {
308    #[serde(default)]
309    pub tsx: bool,
310
311    #[serde(default)]
312    pub decorators: bool,
313
314    /// `.d.ts`
315    #[serde(skip, default)]
316    pub dts: bool,
317
318    #[serde(skip, default)]
319    pub no_early_errors: bool,
320
321    /// babel: `disallowAmbiguousJSXLike`
322    /// Even when JSX parsing is not enabled, this option disallows using syntax
323    /// that would be ambiguous with JSX (`<X> y` type assertions and
324    /// `<X>()=>{}` type arguments)
325    /// see: https://babeljs.io/docs/en/babel-plugin-transform-typescript#disallowambiguousjsxlike
326    #[serde(skip, default)]
327    pub disallow_ambiguous_jsx_like: bool,
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
331#[serde(rename_all = "camelCase")]
332pub struct EsSyntax {
333    #[serde(default)]
334    pub jsx: bool,
335
336    /// Support function bind expression.
337    #[serde(rename = "functionBind")]
338    #[serde(default)]
339    pub fn_bind: bool,
340
341    /// Enable decorators.
342    #[serde(default)]
343    pub decorators: bool,
344
345    /// babel: `decorators.decoratorsBeforeExport`
346    ///
347    /// Effective only if `decorator` is true.
348    #[serde(rename = "decoratorsBeforeExport")]
349    #[serde(default)]
350    pub decorators_before_export: bool,
351
352    #[serde(default)]
353    pub export_default_from: bool,
354
355    /// Stage 3.
356    #[serde(default, alias = "importAssertions")]
357    pub import_attributes: bool,
358
359    #[serde(default, rename = "allowSuperOutsideMethod")]
360    pub allow_super_outside_method: bool,
361
362    #[serde(default, rename = "allowReturnOutsideFunction")]
363    pub allow_return_outside_function: bool,
364
365    #[serde(default)]
366    pub auto_accessors: bool,
367
368    #[serde(default)]
369    pub explicit_resource_management: bool,
370}
371
372/// Syntactic context.
373#[derive(Debug, Clone, Copy, Default)]
374pub struct Context {
375    /// `true` while backtracking
376    ignore_error: bool,
377
378    /// Is in module code?
379    module: bool,
380    can_be_module: bool,
381    strict: bool,
382
383    expr_ctx: ExpressionContext,
384
385    include_in_expr: bool,
386    /// If true, await expression is parsed, and "await" is treated as a
387    /// keyword.
388    in_async: bool,
389    /// If true, yield expression is parsed, and "yield" is treated as a
390    /// keyword.
391    in_generator: bool,
392
393    /// If true, await is treated as a keyword.
394    in_static_block: bool,
395
396    is_continue_allowed: bool,
397    is_break_allowed: bool,
398
399    in_type: bool,
400    /// Typescript extension.
401    should_not_lex_lt_or_gt_as_type: bool,
402    /// Typescript extension.
403    in_declare: bool,
404
405    /// If true, `:` should not be treated as a type annotation.
406    in_cond_expr: bool,
407    will_expect_colon_for_cond: bool,
408
409    in_class: bool,
410
411    in_class_field: bool,
412
413    in_function: bool,
414
415    /// This indicates current scope or the scope out of arrow function is
416    /// function declaration or function expression or not.
417    inside_non_arrow_function_scope: bool,
418
419    in_parameters: bool,
420
421    has_super_class: bool,
422
423    in_property_name: bool,
424
425    in_forced_jsx_context: bool,
426
427    // If true, allow super.x and super[x]
428    allow_direct_super: bool,
429
430    ignore_else_clause: bool,
431
432    disallow_conditional_types: bool,
433
434    allow_using_decl: bool,
435}
436
437#[derive(Debug, Clone, Copy, Default)]
438struct ExpressionContext {
439    // TODO:
440    // - include_in
441    for_loop_init: bool,
442    for_await_loop_init: bool,
443}
444
445#[cfg(test)]
446fn with_test_sess<F, Ret>(src: &str, f: F) -> Result<Ret, ::testing::StdErr>
447where
448    F: FnOnce(&swc_common::errors::Handler, StringInput<'_>) -> Result<Ret, ()>,
449{
450    use swc_common::FileName;
451
452    ::testing::run_test(false, |cm, handler| {
453        let fm = cm.new_source_file(FileName::Real("testing".into()).into(), src.into());
454
455        f(handler, (&*fm).into())
456    })
457}
458
459pub fn with_file_parser<T>(
460    fm: &SourceFile,
461    syntax: Syntax,
462    target: EsVersion,
463    comments: Option<&dyn Comments>,
464    recovered_errors: &mut Vec<Error>,
465    op: impl for<'aa> FnOnce(&mut Parser<Lexer>) -> PResult<T>,
466) -> PResult<T> {
467    let lexer = Lexer::new(syntax, target, SourceFileInput::from(fm), comments);
468    let mut p = Parser::new_from(lexer);
469    let ret = op(&mut p);
470
471    recovered_errors.append(&mut p.take_errors());
472
473    ret
474}
475
476macro_rules! expose {
477    (
478        $name:ident,
479        $T:ty,
480        $($t:tt)*
481    ) => {
482        /// Note: This is recommended way to parse a file.
483        ///
484        /// This is an alias for [Parser], [Lexer] and [SourceFileInput], but
485        /// instantiation of generics occur in `swc_ecma_parser` crate.
486        pub fn $name(
487            fm: &SourceFile,
488            syntax: Syntax,
489            target: EsVersion,
490            comments: Option<&dyn Comments>,
491            recovered_errors: &mut Vec<Error>,
492        ) -> PResult<$T> {
493            with_file_parser(fm, syntax, target, comments, recovered_errors, $($t)*)
494        }
495    };
496}
497
498expose!(parse_file_as_expr, Box<Expr>, |p| {
499    // This allow to parse `import.meta`
500    p.input().ctx.can_be_module = true;
501    p.parse_expr()
502});
503expose!(parse_file_as_module, Module, |p| { p.parse_module() });
504expose!(parse_file_as_script, Script, |p| { p.parse_script() });
505expose!(parse_file_as_program, Program, |p| { p.parse_program() });
506
507#[inline(always)]
508#[cfg(any(
509    target_arch = "wasm32",
510    target_arch = "arm",
511    not(feature = "stacker"),
512    // miri does not work with stacker
513    miri
514))]
515fn maybe_grow<R, F: FnOnce() -> R>(_red_zone: usize, _stack_size: usize, callback: F) -> R {
516    callback()
517}
518
519#[inline(always)]
520#[cfg(all(
521    not(any(target_arch = "wasm32", target_arch = "arm", miri)),
522    feature = "stacker"
523))]
524fn maybe_grow<R, F: FnOnce() -> R>(red_zone: usize, stack_size: usize, callback: F) -> R {
525    stacker::maybe_grow(red_zone, stack_size, callback)
526}