//! EcmaScript/TypeScript parser for the rust programming language.
//!
//! # Features
//!
//! ## Heavily tested
//!
//! Passes almost all tests from [tc39/test262][].
//!
//! ## Error reporting
//!
//! ```sh
//! error: 'implements', 'interface', 'let', 'package', 'private', 'protected',  'public', 'static', or 'yield' cannot be used as an identifier in strict mode
//!  --> invalid.js:3:10
//!   |
//! 3 | function yield() {
//!   |          ^^^^^
//! ```
//!
//! ## Error recovery
//!
//! The parser can recover from some parsing errors. For example, parser returns
//! `Ok(Module)` for the code below, while emitting error to handler.
//!
//! ```ts
//! const CONST = 9000 % 2;
//! const enum D {
//!     // Comma is required, but parser can recover because of the newline.
//!     d = 10
//!     g = CONST
//! }
//! ```
//!
//! # Example (lexer)
//!
//! See `lexer.rs` in examples directory.
//!
//! # Example (parser)
//!
//! ```
//! #[macro_use]
//! extern crate swc_common;
//! extern crate swc_ecma_parser;
//! use swc_common::sync::Lrc;
//! use swc_common::{
//!     errors::{ColorConfig, Handler},
//!     FileName, FilePathMapping, SourceMap,
//! };
//! use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax};
//!
//! fn main() {
//!     let cm: Lrc<SourceMap> = Default::default();
//!     let handler =
//!         Handler::with_tty_emitter(ColorConfig::Auto, true, false,
//!         Some(cm.clone()));
//!
//!     // Real usage
//!     // let fm = cm
//!     //     .load_file(Path::new("test.js"))
//!     //     .expect("failed to load test.js");
//!     let fm = cm.new_source_file(
//!         FileName::Custom("test.js".into()),
//!         "function foo() {}".into(),
//!     );
//!     let lexer = Lexer::new(
//!         // We want to parse ecmascript
//!         Syntax::Es(Default::default()),
//!         // JscTarget defaults to es5
//!         Default::default(),
//!         StringInput::from(&*fm),
//!         None,
//!     );
//!
//!     let mut parser = Parser::new_from(lexer);
//!
//!     for e in parser.take_errors() {
//!         e.into_diagnostic(&handler).emit();
//!     }
//!
//!     let _module = parser
//!         .parse_module()
//!         .map_err(|mut e| {
//!             // Unrecoverable fatal error occurred
//!             e.into_diagnostic(&handler).emit()
//!         })
//!         .expect("failed to parser module");
//! }
//! ```
//!
//! ## Known issues
//!
//! ### Null character after `\`
//!
//! Becuase [String] of rust should only contain valid utf-8 characters while
//! javascript allows non-utf8 chraceters, the parser stores invalid utf8
//! chracters in escpaed form.
//!
//! As a result, swc needs a way to distinguish invalid-utf8 code points and
//! input specified by the user. The parser stores a null chracter right after
//! `\\` for non-utf8 code points. Note that other parts of swc is aware of this
//! fact.
//!
//! Note that this can be changed at anytime with a breaking change.
//!
//! [tc39/test262]:https://github.com/tc39/test262

#![cfg_attr(test, feature(bench_black_box))]
#![cfg_attr(test, feature(test))]
#![deny(unused)]

pub use self::{
    lexer::input::{Input, StringInput},
    parser::*,
};
use serde::{Deserialize, Serialize};
pub use swc_ecma_ast::EsVersion as JscTarget;

#[macro_use]
mod macros;
pub mod error;
pub mod lexer;
mod parser;
pub mod token;

#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
#[serde(deny_unknown_fields, tag = "syntax")]
pub enum Syntax {
    /// Standard
    #[serde(rename = "ecmascript")]
    Es(EsConfig),
    /// This variant requires the cargo feature `typescript` to be enabled.
    #[cfg(feature = "typescript")]
    #[serde(rename = "typescript")]
    Typescript(TsConfig),
}

impl Default for Syntax {
    fn default() -> Self {
        Syntax::Es(Default::default())
    }
}

impl Syntax {
    pub fn import_assertions(self) -> bool {
        match self {
            Syntax::Es(EsConfig {
                import_assertions, ..
            })
            | Syntax::Typescript(TsConfig {
                import_assertions, ..
            }) => import_assertions,
        }
    }

    pub fn static_blocks(self) -> bool {
        match self {
            Syntax::Es(EsConfig {
                static_blocks: true,
                ..
            })
            | Syntax::Typescript(..) => true,
            _ => false,
        }
    }

    /// Should we parse jsx?
    pub fn jsx(self) -> bool {
        match self {
            Syntax::Es(EsConfig { jsx: true, .. })
            | Syntax::Typescript(TsConfig { tsx: true, .. }) => true,
            _ => false,
        }
    }

    pub const fn optional_chaining(self) -> bool {
        true
    }

    pub const fn dynamic_import(self) -> bool {
        true
    }

    pub fn fn_bind(self) -> bool {
        match self {
            Syntax::Es(EsConfig { fn_bind: true, .. }) => true,
            _ => false,
        }
    }

    pub const fn num_sep(self) -> bool {
        true
    }

    pub fn decorators(self) -> bool {
        match self {
            Syntax::Es(EsConfig {
                decorators: true, ..
            })
            | Syntax::Typescript(TsConfig {
                decorators: true, ..
            }) => true,
            _ => false,
        }
    }

    pub const fn class_private_methods(self) -> bool {
        true
    }

    pub const fn class_private_props(self) -> bool {
        true
    }

    pub const fn class_props(self) -> bool {
        true
    }

    pub fn decorators_before_export(self) -> bool {
        match self {
            Syntax::Es(EsConfig {
                decorators_before_export: true,
                ..
            })
            | Syntax::Typescript(..) => true,
            _ => false,
        }
    }

    /// Should we pare typescript?
    #[cfg(not(feature = "typescript"))]
    pub const fn typescript(self) -> bool {
        false
    }

    /// Should we pare typescript?
    #[cfg(feature = "typescript")]
    pub const fn typescript(self) -> bool {
        match self {
            Syntax::Typescript(..) => true,
            _ => false,
        }
    }

    pub fn export_default_from(self) -> bool {
        match self {
            Syntax::Es(EsConfig {
                export_default_from: true,
                ..
            }) => true,
            _ => false,
        }
    }

    /// `true`
    pub const fn export_namespace_from(self) -> bool {
        true
    }

    /// `true`
    pub const fn nullish_coalescing(self) -> bool {
        true
    }

    pub const fn import_meta(self) -> bool {
        true
    }

    pub const fn top_level_await(self) -> bool {
        true
    }

    pub fn dts(self) -> bool {
        match self {
            Syntax::Typescript(t) => t.dts,
            _ => false,
        }
    }

    pub fn private_in_object(self) -> bool {
        match self {
            Syntax::Es(EsConfig {
                private_in_object, ..
            }) => private_in_object,
            _ => false,
        }
    }

    pub(crate) fn early_errors(self) -> bool {
        match self {
            Syntax::Typescript(t) => !t.no_early_errors,
            Syntax::Es(..) => true,
        }
    }
}

#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct TsConfig {
    #[serde(default)]
    pub tsx: bool,

    #[serde(default)]
    pub decorators: bool,

    #[serde(default)]
    pub dynamic_import: bool,

    /// `.d.ts`
    #[serde(skip, default)]
    pub dts: bool,

    #[serde(skip, default)]
    pub no_early_errors: bool,

    /// Stage 3.
    #[serde(default)]
    pub import_assertions: bool,
}

#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct EsConfig {
    #[serde(default)]
    pub jsx: bool,
    /// Support numeric separator.
    /// Stage 3.
    #[serde(rename = "numericSeparator")]
    #[serde(default)]
    pub num_sep: bool,

    #[serde(rename = "classPrivateProperty")]
    #[serde(default)]
    pub class_private_props: bool,

    #[serde(rename = "privateMethod")]
    #[serde(default)]
    pub class_private_methods: bool,

    #[serde(rename = "classProperty")]
    #[serde(default)]
    pub class_props: bool,

    /// Support function bind expression.
    #[serde(rename = "functionBind")]
    #[serde(default)]
    pub fn_bind: bool,

    /// Enable decorators.
    #[serde(default)]
    pub decorators: bool,

    /// babel: `decorators.decoratorsBeforeExport`
    ///
    /// Effective only if `decorator` is true.
    #[serde(rename = "decoratorsBeforeExport")]
    #[serde(default)]
    pub decorators_before_export: bool,

    #[serde(default)]
    pub export_default_from: bool,

    #[serde(default)]
    pub export_namespace_from: bool,

    #[serde(default)]
    pub dynamic_import: bool,

    /// Stage 3.
    #[serde(default)]
    pub nullish_coalescing: bool,

    #[serde(default)]
    pub optional_chaining: bool,

    /// Stage 3.
    #[serde(default)]
    pub import_meta: bool,

    /// Stage 3.
    #[serde(default)]
    pub top_level_await: bool,

    /// Stage 3.
    #[serde(default)]
    pub import_assertions: bool,

    #[serde(default, rename = "staticBlocks")]
    pub static_blocks: bool,

    #[serde(default, rename = "privateInObject")]
    pub private_in_object: bool,
}

/// Syntactic context.
#[derive(Debug, Clone, Copy, Default)]
pub struct Context {
    /// Is in module code?
    module: bool,
    can_be_module: bool,
    strict: bool,
    include_in_expr: bool,
    /// If true, await expression is parsed, and "await" is treated as a
    /// keyword.
    in_async: bool,
    /// If true, yield expression is parsed, and "yield" is treated as a
    /// keyword.
    in_generator: bool,

    is_continue_allowed: bool,
    is_break_allowed: bool,

    in_type: bool,
    /// Typescript extension.
    in_declare: bool,

    /// If true, `:` should not be treated as a type annotation.
    in_cond_expr: bool,
    is_direct_child_of_cond: bool,

    in_function: bool,

    in_parameters: bool,

    has_super_class: bool,

    in_property_name: bool,

    in_forced_jsx_context: bool,

    /// If true, `:` should not be treated as a type annotation.
    dont_parse_colon_as_type_ann: bool,

    /// See: https://github.com/swc-project/swc/issues/2264
    dont_store_comments: bool,
}

#[cfg(test)]
fn with_test_sess<F, Ret>(src: &str, f: F) -> Result<Ret, ::testing::StdErr>
where
    F: FnOnce(&swc_common::errors::Handler, StringInput<'_>) -> Result<Ret, ()>,
{
    use swc_common::FileName;

    ::testing::run_test(false, |cm, handler| {
        let fm = cm.new_source_file(FileName::Real("testing".into()), src.into());

        f(handler, (&*fm).into())
    })
}
