feat(es/quote): Implement quasi quoter partially (#3155)

This commit is contained in:
Donny/강동윤 2022-02-27 22:21:38 +09:00 committed by GitHub
parent cc2b753895
commit fe0ddcc54b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1106 additions and 10 deletions

26
Cargo.lock generated
View File

@ -3109,6 +3109,32 @@ dependencies = [
"testing",
]
[[package]]
name = "swc_ecma_quote"
version = "0.1.0"
dependencies = [
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_quote_macros",
]
[[package]]
name = "swc_ecma_quote_macros"
version = "0.1.0"
dependencies = [
"anyhow",
"pmutil",
"proc-macro2",
"quote",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_parser",
"swc_macros_common",
"syn",
]
[[package]]
name = "swc_ecma_transforms"
version = "0.124.0"

View File

@ -6,6 +6,7 @@ members = [
"crates/swc_css",
"crates/swc_ecmascript",
"crates/swc_ecma_lints",
"crates/swc_ecma_quote",
"crates/swc_estree_compat",
"crates/swc_plugin",
"crates/swc_plugin_macro",

View File

@ -12,9 +12,6 @@ version = "0.17.9"
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lib]
crate-type = ["lib", "dylib"]
[features]
concurrent = ["parking_lot"]
debug = []

View File

@ -238,12 +238,10 @@ impl<'a> Visit for DependencyCollector<'a> {
let kind = if node.is_type_only {
DependencyKind::ImportType
} else if node.is_export {
DependencyKind::ExportEquals
} else {
if node.is_export {
DependencyKind::ExportEquals
} else {
DependencyKind::ImportEquals
}
DependencyKind::ImportEquals
};
self.items.push(DependencyDescriptor {

View File

@ -11,6 +11,10 @@ use crate::{
};
impl<'a, I: Tokens> Parser<I> {
pub fn parse_pat(&mut self) -> PResult<Pat> {
self.parse_binding_pat_or_ident()
}
pub(super) fn parse_opt_binding_ident(&mut self) -> PResult<Option<BindingIdent>> {
trace_cur!(self, parse_opt_binding_ident);

View File

@ -8,6 +8,10 @@ use crate::error::SyntaxError;
mod module_item;
impl<'a, I: Tokens> Parser<I> {
pub fn parse_module_item(&mut self) -> PResult<ModuleItem> {
self.parse_stmt_like(true, true)
}
pub(super) fn parse_block_body<Type>(
&mut self,
mut allow_directives: bool,
@ -2171,13 +2175,13 @@ export default function waitUntil(callback, options = {}) {
#[test]
#[should_panic(expected = "Trailing comma is disallowed inside import(...) arguments")]
fn error_for_trailing_commma_inside_dynamic_import() {
fn error_for_trailing_comma_inside_dynamic_import() {
let src = "import('foo',)";
test_parser(src, Syntax::Es(Default::default()), |p| p.parse_expr());
}
#[test]
fn no_error_for_trailing_commma_inside_dynamic_import_with_import_assertions() {
fn no_error_for_trailing_comma_inside_dynamic_import_with_import_assertions() {
let src = "import('foo',)";
test_parser(
src,

View File

@ -0,0 +1,17 @@
[package]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
description = "Quasi quotation system for ecmascript"
documentation = "https://rustdoc.swc.rs/swc_ecma_quote/"
edition = "2021"
license = "Apache-2.0"
name = "swc_ecma_quote"
repository = "https://github.com/swc-project/swc.git"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
swc_atoms = {version = "0.2.9", path = "../swc_atoms"}
swc_common = {version = "0.17.9", path = "../swc_common"}
swc_ecma_ast = {version = "0.68.3", path = "../swc_ecma_ast"}
swc_ecma_quote_macros = {version = "0.1.0", path = "../swc_ecma_quote_macros"}

View File

@ -0,0 +1,68 @@
/// Not a public API.
#[doc(hidden)]
pub extern crate swc_common;
/// Not a public API.
#[doc(hidden)]
pub extern crate swc_ecma_ast;
/// Not a public API.
#[doc(hidden)]
pub extern crate swc_ecma_quote_macros;
/// # Supported output types
///
/// - `Expr`
/// - `Pat`
/// - `Stmt`
/// - `ModuleItem`
///
/// - Option<T> where T is supported type
/// - Box<T> where T is supported type
///
/// For example, `Box<Expr>` and `Option<Box<Expr>>` are supported.
///
/// # Variable substitution
///
/// (**Not implemented**)
///
/// If an identifier starts with `$`, it is substituted with the value of the
/// parameter passed.
///
/// e.g.
///
/// ```rust,ignore
/// quote!("const $name = 4;" as Stmt, name = private_ident!("ref"))
/// ```
///
///
/// # Examples
///
/// ## Quote a variable declaration
///
/// (**Not implemented**)
///
/// ```rust
/// use swc_ecma_ast::*;
/// use swc_ecma_quote::quote;
///
/// let stmt = quote!("const $name = 4;" as Stmt, name = private_ident!("ref"));
/// ```
#[macro_export]
macro_rules! quote {
($($tt:tt)*) => {{
$crate::swc_ecma_quote_macros::internal_quote!($($tt)*)
}};
}
/// Creates a `Box<Expr>` from the source code.
///
/// This is an alias for [quote], but without `as Box<Expr>`.
#[macro_export]
macro_rules! quote_expr {
($($tt1:tt)*) => {{
$crate::quote!($($tt1)* as Box<Expr>)
}};
($($tt1:tt)*, $($tt2:tt)*) => {{
$crate::quote!($($tt1)* as Box<Expr>, $($tt2)*)
}};
}

View File

@ -0,0 +1,6 @@
use swc_ecma_quote::quote_expr;
#[test]
fn quote_expr_call_1() {
let _expr = quote_expr!("call(arg1, typeof arg2, arg3)");
}

View File

@ -0,0 +1,26 @@
[package]
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
description = "Quasi quotation system for ecmascript"
documentation = "https://rustdoc.swc.rs/swc_ecma_quote_macros/"
edition = "2021"
license = "Apache-2.0"
name = "swc_ecma_quote_macros"
repository = "https://github.com/swc-project/swc.git"
version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
anyhow = "1"
pmutil = "0.5.1"
proc-macro2 = "1"
quote = "1"
swc_atoms = {version = "0.2.9", path = "../swc_atoms"}
swc_common = {version = "0.17.9", path = "../swc_common"}
swc_ecma_ast = {version = "0.68.3", path = "../swc_ecma_ast"}
swc_ecma_parser = {version = "0.91.1", path = "../swc_ecma_parser"}
swc_macros_common = {version = "0.3.3", path = "../swc_macros_common"}
syn = "1"

View File

@ -0,0 +1,71 @@
use swc_ecma_ast::*;
impl_struct!(
Class,
[
span,
decorators,
body,
super_class,
is_abstract,
type_params,
super_type_params,
implements
]
);
impl_struct!(
Constructor,
[span, key, params, body, accessibility, is_optional]
);
impl_struct!(
ClassMethod,
[
span,
key,
function,
kind,
is_static,
accessibility,
is_abstract,
is_optional,
is_override
]
);
impl_struct!(
PrivateMethod,
[
span,
key,
function,
kind,
is_static,
accessibility,
is_abstract,
is_optional,
is_override
]
);
impl_struct!(
ClassProp,
[
span,
key,
value,
type_ann,
is_static,
decorators,
accessibility,
is_abstract,
is_optional,
is_override,
readonly,
declare,
definite
]
);
impl_struct!(StaticBlock, [span, body]);

View File

@ -0,0 +1,11 @@
use swc_ecma_ast::*;
impl_enum!(
Decl,
[Class, Fn, Var, TsInterface, TsTypeAlias, TsEnum, TsModule]
);
impl_struct!(ClassDecl, [ident, class]);
impl_struct!(FnDecl, [ident, function]);
impl_struct!(VarDecl, [span, kind, declare, decls]);
impl_struct!(VarDeclarator, [span, name, init, definite]);

View File

@ -0,0 +1,79 @@
use pmutil::q;
use swc_ecma_ast::*;
macro_rules! impl_simple_enum {
($E:ident, [ $($v:ident),* ]) => {
impl crate::ast::ToCode for $E {
fn to_code(&self, _: &crate::ctxt::Ctx) -> syn::Expr {
match self {
$(
$E::$v => q!(
Vars {},
{ swc_ecma_ast::$E::$v }
)
.parse(),
)*
}
}
}
};
}
impl_simple_enum!(VarDeclKind, [Var, Const, Let]);
impl_simple_enum!(UnaryOp, [Minus, Plus, Bang, Tilde, TypeOf, Void, Delete]);
impl_simple_enum!(UpdateOp, [PlusPlus, MinusMinus]);
impl_simple_enum!(
AssignOp,
[
Assign,
AddAssign,
SubAssign,
MulAssign,
DivAssign,
ModAssign,
LShiftAssign,
RShiftAssign,
ZeroFillRShiftAssign,
BitOrAssign,
BitXorAssign,
BitAndAssign,
ExpAssign,
AndAssign,
OrAssign,
NullishAssign
]
);
impl_simple_enum!(
BinaryOp,
[
EqEq,
NotEq,
EqEqEq,
NotEqEq,
Lt,
LtEq,
Gt,
GtEq,
LShift,
RShift,
ZeroFillRShift,
Add,
Sub,
Mul,
Div,
Mod,
BitOr,
BitXor,
BitAnd,
LogicalOr,
LogicalAnd,
In,
InstanceOf,
Exp,
NullishCoalescing
]
);
impl_simple_enum!(Accessibility, [Public, Protected, Private]);
impl_simple_enum!(MethodKind, [Method, Getter, Setter]);
impl_simple_enum!(MetaPropKind, [NewTarget, ImportMeta]);

View File

@ -0,0 +1,136 @@
use swc_ecma_ast::*;
impl_enum!(PatOrExpr, [Pat, Expr]);
impl_enum!(
Expr,
[
This,
Array,
Object,
Fn,
Unary,
Update,
Bin,
Assign,
Member,
SuperProp,
Cond,
Call,
New,
Seq,
Ident,
Lit,
Tpl,
TaggedTpl,
Arrow,
Class,
Yield,
MetaProp,
Await,
Paren,
JSXMember,
JSXNamespacedName,
JSXEmpty,
JSXElement,
JSXFragment,
TsTypeAssertion,
TsConstAssertion,
TsNonNull,
TsAs,
TsInstantiation,
PrivateName,
OptChain,
Invalid
]
);
impl_struct!(ThisExpr, [span]);
impl_struct!(ArrayLit, [span, elems]);
impl_struct!(ObjectLit, [span, props]);
impl_struct!(FnExpr, [ident, function]);
impl_struct!(
ArrowExpr,
[
span,
params,
body,
is_async,
is_generator,
type_params,
return_type
]
);
impl_struct!(ClassExpr, [ident, class]);
impl_struct!(Tpl, [exprs, quasis]);
impl_struct!(UnaryExpr, [span, op, arg]);
impl_struct!(UpdateExpr, [span, op, prefix, arg]);
impl_struct!(BinExpr, [span, op, left, right]);
impl_struct!(AssignExpr, [span, op, left, right]);
impl_struct!(MemberExpr, [span, obj, prop]);
impl_struct!(SuperPropExpr, [span, obj, prop]);
impl_struct!(CondExpr, [span, test, cons, alt]);
impl_struct!(CallExpr, [span, callee, args, type_args]);
impl_struct!(ExprOrSpread, [spread, expr]);
impl_struct!(Super, [span]);
impl_struct!(Import, [span]);
impl_struct!(NewExpr, [span, callee, args, type_args]);
impl_struct!(SeqExpr, [span, exprs]);
impl_struct!(TaggedTpl, [span, tag, type_params, tpl]);
impl_struct!(YieldExpr, [span, arg, delegate]);
impl_struct!(MetaPropExpr, [span, kind]);
impl_struct!(AwaitExpr, [span, arg]);
impl_struct!(JSXMemberExpr, [obj, prop]);
impl_struct!(JSXNamespacedName, [ns, name]);
impl_struct!(JSXEmptyExpr, [span]);
impl_struct!(JSXElement, [span, opening, closing, children]);
impl_struct!(JSXFragment, [span, opening, closing, children]);
impl_struct!(OptChainExpr, [span, question_dot_token, base]);
impl_struct!(ParenExpr, [span, expr]);
impl_struct!(
Function,
[
params,
decorators,
span,
body,
is_generator,
is_async,
type_params,
return_type
]
);
impl_struct!(Decorator, [span, expr]);
impl_struct!(TplElement, [span, tail, cooked, raw]);
impl_struct!(
JSXOpeningElement,
[name, span, attrs, self_closing, type_args]
);
impl_struct!(JSXClosingElement, [name, span]);
impl_struct!(JSXOpeningFragment, [span]);
impl_struct!(JSXClosingFragment, [span]);
impl_struct!(SpreadElement, [dot3_token, expr]);
impl_struct!(JSXExprContainer, [span, expr]);
impl_struct!(JSXSpreadChild, [span, expr]);
impl_struct!(JSXAttr, [span, name, value]);
impl_enum!(
JSXAttrValue,
[Lit, JSXExprContainer, JSXElement, JSXFragment]
);
impl_enum!(JSXAttrName, [Ident, JSXNamespacedName]);
impl_enum!(JSXExpr, [Expr, JSXEmptyExpr]);
impl_struct!(OptCall, [span, callee, args, type_args]);
impl_enum!(Callee, [Super, Import, Expr]);

View File

@ -0,0 +1,26 @@
use pmutil::q;
use swc_ecma_ast::*;
use super::ToCode;
use crate::ctxt::Ctx;
impl ToCode for swc_ecma_ast::Ident {
fn to_code(&self, cx: &Ctx) -> syn::Expr {
// TODO: Check for variables.
q!(
Vars {
sym_value: self.sym.to_code(cx),
},
{
swc_ecma_quote::swc_ecma_ast::Ident::new(
sym_value,
swc_ecma_quote::swc_common::DUMMY_SP,
)
}
)
.parse()
}
}
impl_struct!(PrivateName, [span, id]);

View File

@ -0,0 +1,48 @@
use pmutil::q;
use proc_macro2::Span;
use swc_atoms::JsWord;
use swc_ecma_ast::*;
use syn::{ExprLit, LitBool, LitFloat};
use super::ToCode;
use crate::ctxt::Ctx;
fail_todo!(BigInt);
fail_todo!(JSXText);
impl_struct!(Str, [span, value, has_escape, kind]);
impl ToCode for StrKind {
fn to_code(&self, _: &Ctx) -> syn::Expr {
q!(Vars {}, { Default::default() }).parse()
}
}
impl_struct!(Bool, [span, value]);
impl_struct!(Null, [span]);
impl_struct!(Number, [span, value]);
impl_struct!(Regex, [span, exp, flags]);
impl ToCode for JsWord {
fn to_code(&self, _: &Ctx) -> syn::Expr {
q!(Vars { val: &**self }, { val.into() }).parse()
}
}
impl ToCode for bool {
fn to_code(&self, _: &Ctx) -> syn::Expr {
syn::Expr::Lit(ExprLit {
attrs: Default::default(),
lit: syn::Lit::Bool(LitBool::new(*self, Span::call_site())),
})
}
}
impl ToCode for f64 {
fn to_code(&self, _: &Ctx) -> syn::Expr {
syn::Expr::Lit(ExprLit {
attrs: Default::default(),
lit: syn::Lit::Float(LitFloat::new(&self.to_string(), Span::call_site())),
})
}
}

View File

@ -0,0 +1,192 @@
use pmutil::q;
use swc_common::Span;
use swc_ecma_ast::*;
use syn::ExprBlock;
use crate::ctxt::Ctx;
macro_rules! fail_todo {
($T:ty) => {
impl crate::ast::ToCode for $T {
fn to_code(&self, _: &crate::ctxt::Ctx) -> syn::Expr {
todo!("ToCode for {}", stringify!($T))
}
}
};
}
macro_rules! impl_enum {
($E:ident, [ $($v:ident),* ]) => {
impl crate::ast::ToCode for $E {
fn to_code(&self, cx: &crate::ctxt::Ctx) -> syn::Expr {
match self {
$(
$E::$v(inner) => pmutil::q!(
Vars {
val: crate::ast::ToCode::to_code(inner, cx),
},
{ swc_ecma_quote::swc_ecma_ast::$E::$v(val) }
)
.parse(),
)*
}
}
}
};
}
macro_rules! impl_struct {
(
$name:ident,
[ $($v:ident),* ]
) => {
impl crate::ast::ToCode for $name {
fn to_code(&self, cx: &crate::ctxt::Ctx) -> syn::Expr {
let mut builder = crate::builder::Builder::new(stringify!($name));
$(
builder.add(
stringify!($v),
crate::ast::ToCode::to_code(&self.$v, cx),
);
)*
syn::Expr::Struct(builder.build())
}
}
};
}
mod class;
mod decl;
mod enums;
mod expr;
mod id;
mod lit;
mod module_decl;
mod pat;
mod prop;
mod stmt;
mod typescript;
pub(crate) trait ToCode: 'static {
fn to_code(&self, cx: &Ctx) -> syn::Expr;
}
impl<T> ToCode for Box<T>
where
T: ?Sized + ToCode,
{
fn to_code(&self, cx: &Ctx) -> syn::Expr {
q!(
Vars {
inner: (**self).to_code(cx)
},
{ Box::new(inner) }
)
.parse()
}
}
/// TODO: Optimize
impl<T> ToCode for Option<T>
where
T: ToCode,
{
fn to_code(&self, cx: &Ctx) -> syn::Expr {
match self {
Some(inner) => q!(
Vars {
inner: inner.to_code(cx)
},
{ Some(inner) }
)
.parse(),
None => q!({ None }).parse(),
}
}
}
impl_struct!(Invalid, [span]);
impl ToCode for Span {
fn to_code(&self, _: &Ctx) -> syn::Expr {
q!({ swc_ecma_quote::swc_common::DUMMY_SP }).parse()
}
}
impl_enum!(ModuleItem, [ModuleDecl, Stmt]);
impl_enum!(Pat, [Ident, Array, Rest, Object, Assign, Invalid, Expr]);
impl_enum!(Lit, [Str, Bool, Null, Num, BigInt, Regex, JSXText]);
impl_enum!(
ClassMember,
[
Constructor,
Method,
PrivateMethod,
ClassProp,
PrivateProp,
TsIndexSignature,
Empty,
StaticBlock
]
);
impl_enum!(ObjectPatProp, [KeyValue, Assign, Rest]);
impl_enum!(PropName, [Ident, Str, Num, Computed, BigInt]);
impl_enum!(ParamOrTsParamProp, [TsParamProp, Param]);
impl_enum!(PropOrSpread, [Spread, Prop]);
impl_enum!(BlockStmtOrExpr, [BlockStmt, Expr]);
impl_enum!(MemberProp, [Ident, PrivateName, Computed]);
impl_enum!(SuperProp, [Ident, Computed]);
impl_enum!(JSXObject, [Ident, JSXMemberExpr]);
impl_enum!(
JSXElementChild,
[
JSXText,
JSXElement,
JSXExprContainer,
JSXFragment,
JSXSpreadChild
]
);
impl_enum!(OptChainBase, [Member, Call]);
impl_enum!(JSXElementName, [Ident, JSXMemberExpr, JSXNamespacedName]);
impl_enum!(JSXAttrOrSpread, [JSXAttr, SpreadElement]);
impl<T> ToCode for Vec<T>
where
T: ToCode,
{
fn to_code(&self, cx: &Ctx) -> syn::Expr {
let var_stmt = q!(Vars { len: self.len() }, {
let mut items = Vec::with_capacity(len);
})
.parse::<syn::Stmt>();
let mut stmts = vec![var_stmt];
for item in self {
stmts.push(syn::Stmt::Semi(
q!(
Vars {
item: item.to_code(cx)
},
{ items.push(item) }
)
.parse(),
Default::default(),
));
}
stmts.push(syn::Stmt::Expr(q!(Vars {}, { items }).parse()));
syn::Expr::Block(ExprBlock {
attrs: Default::default(),
label: Default::default(),
block: syn::Block {
brace_token: Default::default(),
stmts,
},
})
}
}

View File

@ -0,0 +1,39 @@
use swc_ecma_ast::*;
impl_enum!(
ModuleDecl,
[
Import,
ExportDecl,
ExportNamed,
ExportDefaultDecl,
ExportDefaultExpr,
ExportAll,
TsImportEquals,
TsExportAssignment,
TsNamespaceExport
]
);
impl_struct!(ImportDecl, [span, specifiers, src, type_only, asserts]);
impl_struct!(ExportDecl, [span, decl]);
impl_struct!(ExportDefaultDecl, [span, decl]);
impl_struct!(ExportDefaultExpr, [span, expr]);
impl_struct!(ExportAll, [span, src, asserts]);
impl_struct!(NamedExport, [span, specifiers, src, type_only, asserts]);
impl_enum!(ImportSpecifier, [Named, Default, Namespace]);
impl_struct!(ImportNamedSpecifier, [span, local, imported, is_type_only]);
impl_struct!(ImportDefaultSpecifier, [span, local]);
impl_struct!(ImportStarAsSpecifier, [span, local]);
impl_enum!(ExportSpecifier, [Named, Default, Namespace]);
impl_enum!(DefaultDecl, [Class, Fn, TsInterfaceDecl]);
impl_enum!(ModuleExportName, [Ident, Str]);
impl_struct!(ExportNamedSpecifier, [span, orig, exported, is_type_only]);
impl_struct!(ExportDefaultSpecifier, [exported]);
impl_struct!(ExportNamespaceSpecifier, [span, name]);

View File

@ -0,0 +1,8 @@
use swc_ecma_ast::*;
impl_struct!(BindingIdent, [id, type_ann]);
impl_struct!(ArrayPat, [span, elems, optional, type_ann]);
impl_struct!(ObjectPat, [span, props, optional, type_ann]);
impl_struct!(RestPat, [span, dot3_token, arg, type_ann]);
impl_struct!(AssignPat, [span, left, right, type_ann]);
impl_struct!(Param, [span, decorators, pat]);

View File

@ -0,0 +1,37 @@
use swc_ecma_ast::*;
impl_enum!(Prop, [Shorthand, KeyValue, Assign, Getter, Setter, Method]);
impl_struct!(
PrivateProp,
[
span,
key,
value,
type_ann,
is_static,
decorators,
computed,
accessibility,
is_abstract,
is_optional,
is_override,
readonly,
definite
]
);
impl_struct!(KeyValueProp, [key, value]);
impl_struct!(AssignProp, [key, value]);
impl_struct!(GetterProp, [span, key, type_ann, body]);
impl_struct!(SetterProp, [span, key, param, body]);
impl_struct!(MethodProp, [key, function]);
impl_struct!(KeyValuePatProp, [key, value]);
impl_struct!(AssignPatProp, [span, key, value]);
impl_struct!(ComputedPropName, [span, expr]);

View File

@ -0,0 +1,35 @@
use swc_ecma_ast::*;
impl_enum!(
Stmt,
[
Block, Empty, Debugger, With, Return, Labeled, Break, Continue, If, Switch, Throw, Try,
While, DoWhile, For, ForIn, ForOf, Decl, Expr
]
);
impl_struct!(EmptyStmt, [span]);
impl_struct!(BlockStmt, [span, stmts]);
impl_struct!(DebuggerStmt, [span]);
impl_struct!(WithStmt, [span, obj, body]);
impl_struct!(LabeledStmt, [span, label, body]);
impl_struct!(BreakStmt, [span, label]);
impl_struct!(ContinueStmt, [span, label]);
impl_struct!(IfStmt, [span, test, cons, alt]);
impl_struct!(SwitchStmt, [span, discriminant, cases]);
impl_struct!(ThrowStmt, [span, arg]);
impl_struct!(TryStmt, [span, block, handler, finalizer]);
impl_struct!(WhileStmt, [span, test, body]);
impl_struct!(DoWhileStmt, [span, test, body]);
impl_struct!(ForStmt, [span, init, test, update, body]);
impl_struct!(ForInStmt, [span, left, right, body]);
impl_struct!(ForOfStmt, [span, await_token, left, right, body]);
impl_struct!(ReturnStmt, [span, arg]);
impl_struct!(ExprStmt, [span, expr]);
impl_enum!(VarDeclOrExpr, [VarDecl, Expr]);
impl_enum!(VarDeclOrPat, [VarDecl, Pat]);
impl_struct!(SwitchCase, [span, test, cons]);
impl_struct!(CatchClause, [span, param, body]);

View File

@ -0,0 +1,21 @@
use swc_ecma_ast::*;
fail_todo!(TsInterfaceDecl);
fail_todo!(TsTypeAliasDecl);
fail_todo!(TsEnumDecl);
fail_todo!(TsModuleDecl);
fail_todo!(TsImportEqualsDecl);
fail_todo!(TsExportAssignment);
fail_todo!(TsNamespaceExportDecl);
fail_todo!(TsTypeAssertion);
fail_todo!(TsConstAssertion);
fail_todo!(TsNonNullExpr);
fail_todo!(TsAsExpr);
fail_todo!(TsInstantiation);
fail_todo!(TsType);
fail_todo!(TsTypeAnn);
fail_todo!(TsTypeParamInstantiation);
fail_todo!(TsTypeParamDecl);
fail_todo!(TsExprWithTypeArgs);
fail_todo!(TsIndexSignature);
fail_todo!(TsParamProp);

View File

@ -0,0 +1,38 @@
use proc_macro2::Span;
use syn::{punctuated::Punctuated, Expr, ExprStruct, FieldValue, Ident, Member, Token};
pub(crate) struct Builder {
type_name: syn::Ident,
fields: Punctuated<FieldValue, Token![,]>,
}
impl Builder {
pub fn new(ident: &str) -> Self {
Self {
type_name: Ident::new(ident, Span::call_site()),
fields: Default::default(),
}
}
pub fn add(&mut self, name: &str, value: Expr) {
self.fields.push(FieldValue {
attrs: Default::default(),
member: Member::Named(Ident::new(name, Span::call_site())),
colon_token: Some(Default::default()),
expr: value,
});
}
pub fn build(self) -> ExprStruct {
let type_name = self.type_name;
ExprStruct {
attrs: Default::default(),
brace_token: Default::default(),
path: syn::parse_quote!(swc_ecma_quote::swc_ecma_ast::#type_name),
fields: self.fields,
dot2_token: Default::default(),
rest: Default::default(),
}
}
}

View File

@ -0,0 +1,20 @@
#![allow(unused)]
use swc_common::collections::AHashMap;
use swc_ecma_ast::Ident;
#[derive(Debug)]
pub(crate) struct Ctx {
pub(crate) vars: Vars,
}
#[derive(Debug)]
pub struct VarData {
/// How many times this variable should be cloned. 0 for variables used only
/// once.
clone: usize,
ident: Ident,
}
pub type Vars = AHashMap<String, VarData>;

View File

@ -0,0 +1,54 @@
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Token,
};
pub(super) struct QuoteInput {
pub src: syn::LitStr,
#[allow(unused)]
pub as_token: Token![as],
pub output_type: syn::Type,
pub vars: Option<(Token![,], Punctuated<QuoteVar, Token![,]>)>,
}
#[allow(unused)]
pub(super) struct QuoteVar {
pub name: syn::Ident,
#[allow(unused)]
pub eq_token: Token![=],
pub value: syn::Expr,
}
impl Parse for QuoteInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let src = input.parse()?;
let as_token = input.parse()?;
let output_type = input.parse()?;
let vars = if input.is_empty() {
None
} else {
let comma_token = input.parse()?;
let vars = Punctuated::parse_terminated(input)?;
Some((comma_token, vars))
};
Ok(Self {
src,
as_token,
output_type,
vars,
})
}
}
impl Parse for QuoteVar {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
name: input.parse()?,
eq_token: input.parse()?,
value: input.parse()?,
})
}
}

View File

@ -0,0 +1,40 @@
extern crate proc_macro;
use std::collections::HashMap;
use quote::ToTokens;
use crate::{ast::ToCode, ctxt::Ctx, input::QuoteInput, ret_type::parse_input_type};
mod ast;
mod builder;
mod ctxt;
mod input;
mod ret_type;
/// Don't invoke this macro directly, use the `quote!` macro from
/// `swc_ecma_quote` instead.
#[proc_macro]
pub fn internal_quote(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let QuoteInput {
src,
as_token: _,
output_type,
vars,
} = syn::parse::<QuoteInput>(input).expect("failed to parse input to quote!()");
let ret_type =
parse_input_type(&src.value(), &output_type).expect("failed to parse input type");
let vars = vars.map(|v| v.1);
let map = HashMap::default();
if let Some(vars) = vars {
if !vars.is_empty() {
todo!("quote! macro does not support variables yet")
}
}
let cx = Ctx { vars: map };
ret_type.to_code(&cx).to_token_stream().into()
}

View File

@ -0,0 +1,94 @@
use std::any::type_name;
use anyhow::{anyhow, bail, Context, Error};
use swc_common::{sync::Lrc, FileName, SourceMap};
use swc_ecma_ast::EsVersion;
use swc_ecma_parser::{lexer::Lexer, PResult, Parser, StringInput};
use syn::{GenericArgument, PathArguments, Type};
use crate::{ast::ToCode, ctxt::Ctx};
/// Storage for `dyn ToCode`.The first `Box`, which is required to store `dyn
/// ToCode`, is ignored.
pub struct BoxWrapper(Box<dyn ToCode>);
impl ToCode for BoxWrapper {
fn to_code(&self, cx: &Ctx) -> syn::Expr {
(*self.0).to_code(cx)
}
}
pub(crate) fn parse_input_type(input_str: &str, ty: &Type) -> Result<BoxWrapper, Error> {
if let Some(ty) = extract_generic("Box", ty) {
let node = parse_input_type(input_str, ty).context("failed to parse `T` in Box<T>")?;
return Ok(BoxWrapper(Box::new(Box::new(node))));
}
if let Some(ty) = extract_generic("Option", ty) {
if input_str.is_empty() {
return Ok(BoxWrapper(Box::new(None::<swc_ecma_ast::Expr>)));
}
let node = parse_input_type(input_str, ty).context("failed to parse `T` in Option<T>")?;
return Ok(BoxWrapper(Box::new(Some(node))));
}
if let Type::Path(p) = ty {
if let Some(ident) = p.path.get_ident() {
match &*ident.to_string() {
"Expr" => return parse(input_str, &mut |p| p.parse_expr().map(|v| *v)),
"Pat" => return parse(input_str, &mut |p| p.parse_pat()),
"Stmt" => return parse(input_str, &mut |p| p.parse_stmt(true)),
"ModuleItem" => return parse(input_str, &mut |p| p.parse_module_item()),
_ => {}
}
}
}
bail!("Unknown quote type: {:?}", ty);
}
fn parse<T>(
input_str: &str,
op: &mut dyn FnMut(&mut Parser<Lexer<StringInput>>) -> PResult<T>,
) -> Result<BoxWrapper, Error>
where
T: ToCode,
{
let cm = Lrc::new(SourceMap::default());
let fm = cm.new_source_file(FileName::Anon, input_str.to_string());
let lexer = Lexer::new(
Default::default(),
EsVersion::Es2020,
StringInput::from(&*fm),
None,
);
let mut parser = Parser::new_from(lexer);
op(&mut parser)
.map_err(|err| anyhow!("{:?}", err))
.with_context(|| format!("failed to parse input as `{}`", type_name::<T>()))
.map(|val| BoxWrapper(Box::new(val)))
}
fn extract_generic<'a>(name: &str, ty: &'a Type) -> Option<&'a Type> {
if let Type::Path(p) = ty {
let last = p.path.segments.last().unwrap();
if !last.arguments.is_empty() && last.ident == name {
match &last.arguments {
PathArguments::AngleBracketed(tps) => {
let arg = tps.args.first().unwrap();
match arg {
GenericArgument::Type(arg) => return Some(arg),
_ => unimplemented!("generic parameter other than type"),
}
}
_ => unimplemented!("Box() -> T or Box without a type parameter"),
}
}
}
None
}