mirror of
https://github.com/swc-project/swc.git
synced 2024-12-26 15:12:08 +03:00
465 lines
11 KiB
Rust
465 lines
11 KiB
Rust
use inflector::Inflector;
|
|
use is_macro::Is;
|
|
use serde::{Deserialize, Serialize};
|
|
use swc_atoms::{js_word, JsWord};
|
|
use swc_cached::regex::CachedRegex;
|
|
use swc_common::{Span, Spanned, DUMMY_SP};
|
|
use swc_ecma_ast::*;
|
|
use swc_ecma_transforms_base::feature::FeatureFlag;
|
|
use swc_ecma_utils::{
|
|
is_valid_prop_ident, member_expr, private_ident, quote_ident, quote_str, ExprFactory,
|
|
FunctionFactory,
|
|
};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
pub struct Config {
|
|
#[serde(default)]
|
|
pub allow_top_level_this: bool,
|
|
#[serde(default)]
|
|
pub strict: bool,
|
|
#[serde(default = "default_strict_mode")]
|
|
pub strict_mode: bool,
|
|
#[serde(default)]
|
|
pub lazy: Lazy,
|
|
#[serde(default)]
|
|
pub import_interop: Option<ImportInterop>,
|
|
#[serde(default)]
|
|
/// Note: deprecated
|
|
pub no_interop: bool,
|
|
#[serde(default)]
|
|
pub ignore_dynamic: bool,
|
|
#[serde(default)]
|
|
pub preserve_import_meta: bool,
|
|
}
|
|
|
|
impl Default for Config {
|
|
fn default() -> Self {
|
|
Config {
|
|
allow_top_level_this: false,
|
|
strict: false,
|
|
strict_mode: default_strict_mode(),
|
|
lazy: Lazy::default(),
|
|
import_interop: None,
|
|
no_interop: false,
|
|
ignore_dynamic: false,
|
|
preserve_import_meta: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
const fn default_strict_mode() -> bool {
|
|
true
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Is, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub enum ImportInterop {
|
|
#[serde(alias = "babel")]
|
|
Swc,
|
|
Node,
|
|
None,
|
|
}
|
|
|
|
impl From<bool> for ImportInterop {
|
|
fn from(no_interop: bool) -> Self {
|
|
if no_interop {
|
|
ImportInterop::None
|
|
} else {
|
|
ImportInterop::Swc
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Config {
|
|
#[inline(always)]
|
|
pub fn import_interop(&self) -> ImportInterop {
|
|
self.import_interop
|
|
.unwrap_or_else(|| self.no_interop.into())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
pub struct LazyObjectConfig {
|
|
pub patterns: Vec<CachedRegex>,
|
|
}
|
|
|
|
impl LazyObjectConfig {
|
|
pub fn is_lazy(&self, src: &JsWord) -> bool {
|
|
self.patterns.iter().any(|pat| pat.is_match(src))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(untagged, deny_unknown_fields, rename_all = "camelCase")]
|
|
pub enum Lazy {
|
|
Bool(bool),
|
|
List(Vec<JsWord>),
|
|
Object(LazyObjectConfig),
|
|
}
|
|
|
|
impl Lazy {
|
|
pub fn is_lazy(&self, src: &JsWord) -> bool {
|
|
match *self {
|
|
Lazy::Bool(false) => false,
|
|
Lazy::Bool(true) => !src.starts_with('.'),
|
|
Lazy::List(ref srcs) => srcs.contains(src),
|
|
Lazy::Object(ref object) => object.is_lazy(src),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Lazy {
|
|
fn default() -> Self {
|
|
Lazy::Bool(false)
|
|
}
|
|
}
|
|
|
|
pub(super) fn local_name_for_src(src: &JsWord) -> JsWord {
|
|
if !src.contains('/') {
|
|
return format!("_{}", src.to_camel_case()).into();
|
|
}
|
|
|
|
let src = src
|
|
.starts_with("@swc/helpers/")
|
|
.then(|| src.strip_suffix(".mjs"))
|
|
.flatten()
|
|
.unwrap_or(src);
|
|
|
|
format!("_{}", src.split('/').last().unwrap().to_camel_case()).into()
|
|
}
|
|
|
|
/// Creates
|
|
///
|
|
///```js
|
|
///
|
|
/// Object.defineProperty(target, prop_name, {
|
|
/// ...props
|
|
/// });
|
|
/// ```
|
|
pub(super) fn object_define_property(
|
|
target: ExprOrSpread,
|
|
prop_name: ExprOrSpread,
|
|
descriptor: ExprOrSpread,
|
|
) -> Expr {
|
|
member_expr!(DUMMY_SP, Object.defineProperty)
|
|
.as_call(DUMMY_SP, vec![target, prop_name, descriptor])
|
|
}
|
|
|
|
/// Creates
|
|
///
|
|
///```js
|
|
///
|
|
/// Object.defineProperty(exports, '__esModule', {
|
|
/// value: true
|
|
/// });
|
|
/// ```
|
|
pub(super) fn define_es_module(exports: Ident) -> Stmt {
|
|
object_define_property(
|
|
exports.as_arg(),
|
|
quote_str!("__esModule").as_arg(),
|
|
ObjectLit {
|
|
span: DUMMY_SP,
|
|
props: vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
|
|
key: PropName::Ident(quote_ident!("value")),
|
|
value: true.into(),
|
|
})))],
|
|
}
|
|
.as_arg(),
|
|
)
|
|
.into_stmt()
|
|
}
|
|
|
|
pub(super) fn clone_first_use_strict(stmts: &[ModuleItem]) -> Option<Stmt> {
|
|
if stmts.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
stmts.iter().find_map(|item| match item {
|
|
ModuleItem::Stmt(stmt @ Stmt::Expr(ExprStmt { expr, .. })) => match **expr {
|
|
Expr::Lit(Lit::Str(Str { ref value, .. })) if value == "use strict" => {
|
|
Some(stmt.clone())
|
|
}
|
|
_ => None,
|
|
},
|
|
_ => None,
|
|
})
|
|
}
|
|
|
|
pub(super) fn use_strict() -> Stmt {
|
|
Lit::Str(quote_str!("use strict")).into_stmt()
|
|
}
|
|
|
|
/// Private `_exports` ident.
|
|
pub(super) struct Exports(pub Ident);
|
|
|
|
impl Default for Exports {
|
|
fn default() -> Self {
|
|
Exports(private_ident!("_exports"))
|
|
}
|
|
}
|
|
|
|
pub(crate) fn object_define_enumerable(
|
|
target: ExprOrSpread,
|
|
prop_name: ExprOrSpread,
|
|
prop: PropOrSpread,
|
|
) -> Expr {
|
|
object_define_property(
|
|
target,
|
|
prop_name,
|
|
ObjectLit {
|
|
span: DUMMY_SP,
|
|
props: vec![
|
|
PropOrSpread::Prop(Box::new(
|
|
KeyValueProp {
|
|
key: quote_ident!("enumerable").into(),
|
|
value: Box::new(true.into()),
|
|
}
|
|
.into(),
|
|
)),
|
|
prop,
|
|
],
|
|
}
|
|
.as_arg(),
|
|
)
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! caniuse {
|
|
($feature_set:ident . $feature:ident) => {
|
|
$feature_set.intersects(swc_ecma_transforms_base::feature::FeatureFlag::$feature)
|
|
};
|
|
}
|
|
|
|
/// ```javascript
|
|
/// function _esmExport(target, all) {
|
|
/// for (var name in all)Object.defineProperty(target, name, { get: all[name], enumerable: true });
|
|
/// }
|
|
/// ```
|
|
pub(crate) fn esm_export() -> Function {
|
|
let target = private_ident!("target");
|
|
let all = private_ident!("all");
|
|
let name = private_ident!("name");
|
|
|
|
let getter = KeyValueProp {
|
|
key: quote_ident!("get").into(),
|
|
value: Box::new(all.clone().computed_member(Expr::from(name.clone()))),
|
|
};
|
|
|
|
let body = object_define_enumerable(
|
|
target.clone().as_arg(),
|
|
name.clone().as_arg(),
|
|
PropOrSpread::Prop(Box::new(Prop::KeyValue(getter))),
|
|
)
|
|
.into_stmt();
|
|
|
|
let for_in_stmt: Stmt = ForInStmt {
|
|
span: DUMMY_SP,
|
|
left: VarDecl {
|
|
span: DUMMY_SP,
|
|
kind: VarDeclKind::Var,
|
|
declare: false,
|
|
decls: vec![VarDeclarator {
|
|
span: DUMMY_SP,
|
|
name: name.into(),
|
|
init: None,
|
|
definite: false,
|
|
}],
|
|
}
|
|
.into(),
|
|
right: Box::new(all.clone().into()),
|
|
body: Box::new(body),
|
|
}
|
|
.into();
|
|
|
|
Function {
|
|
params: vec![target.into(), all.into()],
|
|
decorators: Default::default(),
|
|
span: DUMMY_SP,
|
|
body: Some(BlockStmt {
|
|
span: DUMMY_SP,
|
|
stmts: vec![for_in_stmt],
|
|
}),
|
|
is_generator: false,
|
|
is_async: false,
|
|
type_params: None,
|
|
return_type: None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn emit_export_stmts(
|
|
features: FeatureFlag,
|
|
exports: Ident,
|
|
mut prop_list: Vec<ObjPropKeyIdent>,
|
|
) -> Vec<Stmt> {
|
|
let features = &features;
|
|
let support_arrow = caniuse!(features.ArrowFunctions);
|
|
let support_shorthand = caniuse!(features.ShorthandProperties);
|
|
|
|
let prop_auto = if support_arrow {
|
|
prop_arrow
|
|
} else if support_shorthand {
|
|
prop_method
|
|
} else {
|
|
prop_function
|
|
};
|
|
|
|
match prop_list.len() {
|
|
0 | 1 => prop_list
|
|
.pop()
|
|
.map(|obj_prop| {
|
|
object_define_enumerable(
|
|
exports.as_arg(),
|
|
quote_str!(obj_prop.span(), obj_prop.key()).as_arg(),
|
|
prop_auto((js_word!("get"), DUMMY_SP, obj_prop.2.clone()).into()).into(),
|
|
)
|
|
.into_stmt()
|
|
})
|
|
.into_iter()
|
|
.collect(),
|
|
_ => {
|
|
let props = prop_list
|
|
.into_iter()
|
|
.map(prop_auto)
|
|
.map(From::from)
|
|
.collect();
|
|
let obj_lit = ObjectLit {
|
|
span: DUMMY_SP,
|
|
props,
|
|
};
|
|
|
|
let esm_export_ident = private_ident!("_export");
|
|
|
|
vec![
|
|
Stmt::Decl(Decl::Fn(
|
|
esm_export().into_fn_decl(esm_export_ident.clone()),
|
|
)),
|
|
esm_export_ident
|
|
.as_call(DUMMY_SP, vec![exports.as_arg(), obj_lit.as_arg()])
|
|
.into_stmt(),
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn prop_name(key: &str, span: Span) -> IdentOrStr {
|
|
if is_valid_prop_ident(key) {
|
|
IdentOrStr::Ident(quote_ident!(span, key))
|
|
} else {
|
|
IdentOrStr::Str(quote_str!(span, key))
|
|
}
|
|
}
|
|
|
|
pub(crate) enum IdentOrStr {
|
|
Ident(Ident),
|
|
Str(Str),
|
|
}
|
|
|
|
impl From<IdentOrStr> for PropName {
|
|
fn from(val: IdentOrStr) -> Self {
|
|
match val {
|
|
IdentOrStr::Ident(i) => Self::Ident(i),
|
|
IdentOrStr::Str(s) => Self::Str(s),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<IdentOrStr> for MemberProp {
|
|
fn from(val: IdentOrStr) -> Self {
|
|
match val {
|
|
IdentOrStr::Ident(i) => Self::Ident(i),
|
|
IdentOrStr::Str(s) => Self::Computed(ComputedPropName {
|
|
span: DUMMY_SP,
|
|
expr: s.into(),
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// {
|
|
/// "key": ident,
|
|
/// }
|
|
pub(crate) struct ObjPropKeyIdent(JsWord, Span, Ident);
|
|
|
|
impl From<((JsWord, Span), Ident)> for ObjPropKeyIdent {
|
|
fn from(((key, span), ident): ((JsWord, Span), Ident)) -> Self {
|
|
Self(key, span, ident)
|
|
}
|
|
}
|
|
|
|
impl From<(JsWord, Span, Ident)> for ObjPropKeyIdent {
|
|
fn from((key, span, ident): (JsWord, Span, Ident)) -> Self {
|
|
Self(key, span, ident)
|
|
}
|
|
}
|
|
|
|
impl Spanned for ObjPropKeyIdent {
|
|
fn span(&self) -> Span {
|
|
self.1
|
|
}
|
|
}
|
|
|
|
impl ObjPropKeyIdent {
|
|
pub fn key(&self) -> &JsWord {
|
|
&self.0
|
|
}
|
|
|
|
pub fn into_expr(self) -> Expr {
|
|
self.2.into()
|
|
}
|
|
}
|
|
|
|
/// ```javascript
|
|
/// {
|
|
/// key: () => expr,
|
|
/// }
|
|
/// ```
|
|
pub(crate) fn prop_arrow(prop: ObjPropKeyIdent) -> Prop {
|
|
let key = prop_name(prop.key(), prop.span()).into();
|
|
|
|
KeyValueProp {
|
|
key,
|
|
value: Box::new(prop.into_expr().into_lazy_arrow(Default::default()).into()),
|
|
}
|
|
.into()
|
|
}
|
|
|
|
/// ```javascript
|
|
/// {
|
|
/// key() {
|
|
/// return expr;
|
|
/// },
|
|
/// }
|
|
/// ```
|
|
pub(crate) fn prop_method(prop: ObjPropKeyIdent) -> Prop {
|
|
let key = prop_name(prop.key(), prop.span()).into();
|
|
|
|
prop.into_expr()
|
|
.into_lazy_fn(Default::default())
|
|
.into_method_prop(key)
|
|
.into()
|
|
}
|
|
|
|
/// ```javascript
|
|
/// {
|
|
/// key: function() {
|
|
/// return expr;
|
|
/// },
|
|
/// }
|
|
/// ```
|
|
pub(crate) fn prop_function(prop: ObjPropKeyIdent) -> Prop {
|
|
let key = prop_name(prop.key(), prop.span()).into();
|
|
|
|
KeyValueProp {
|
|
key,
|
|
value: Box::new(
|
|
prop.into_expr()
|
|
.into_lazy_fn(Default::default())
|
|
.into_fn_expr(None)
|
|
.into(),
|
|
),
|
|
}
|
|
.into()
|
|
}
|