Skip to content

Commit

Permalink
test: add more tests about v-on handler
Browse files Browse the repository at this point in the history
  • Loading branch information
phoenix-ru committed Mar 6, 2024
1 parent aeecf3a commit 1615463
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 42 deletions.
8 changes: 2 additions & 6 deletions crates/fervid_transform/src/template/ast_transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -742,9 +742,9 @@ impl VisitMut for Node {
#[cfg(test)]
mod tests {
use fervid_core::{ElementKind, Node, PatchHints, VForDirective, VueDirectives};
use swc_core::{common::DUMMY_SP, ecma::ast::Expr};
use swc_core::common::DUMMY_SP;

use crate::test_utils::{parser::parse_javascript_expr, to_str};
use crate::test_utils::{js, to_str};

use super::*;

Expand Down Expand Up @@ -1436,8 +1436,4 @@ mod tests {
} if tag_name == "h3"
));
}

fn js(raw: &str) -> Box<Expr> {
parse_javascript_expr(raw, 0, Default::default()).unwrap().0
}
}
45 changes: 41 additions & 4 deletions crates/fervid_transform/src/template/expr_transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use swc_core::{
ecma::{
ast::{
AssignExpr, AssignOp, CallExpr, Callee, CondExpr, Expr, ExprOrSpread, Ident,
KeyValueProp, Lit, MemberExpr, MemberProp, Null, Pat, PatOrExpr, Prop, PropName,
PropOrSpread,
KeyValueProp, Lit, MemberExpr, MemberProp, Null, ObjectLit, Pat, PatOrExpr, Prop,
PropName, PropOrSpread,
},
visit::{VisitMut, VisitMutWith},
},
Expand Down Expand Up @@ -231,6 +231,23 @@ impl<'s> VisitMut for TransformVisitor<'s> {
is_reassignable,
);
} else {
// LHS may be a `Pat::Ident`, e.g. in `foo = 0`
// In this case we need to use an `Expr::Ident`.
// I intentionally use `match` in case there are other arms that need same logic
match assign_expr.left {
PatOrExpr::Pat(ref mut pat) => match **pat {
Pat::Ident(ref ident) => {
*pat =
Box::new(Pat::Expr(Box::new(Expr::Ident(ident.id.to_owned()))));
}

_ => {}
},

_ => {}
}

// Process as usual
assign_expr.left.visit_mut_with(self);
}

Expand All @@ -256,6 +273,26 @@ impl<'s> VisitMut for TransformVisitor<'s> {
return;
}

// Regular functions need params collection as well
Expr::Fn(fn_expr) => {
let old_len = self.local_vars.len();
let func = &mut fn_expr.function;

// Add the temporary variables
self.local_vars.reserve(func.params.len());
for param in func.params.iter() {
extract_variables_from_pat(&param.pat, &mut self.local_vars, true);
}

// Transform the function body
func.body.visit_mut_with(self);

// Clear the temporary variables
self.local_vars.drain(old_len..);

return;
}

// Identifier is what we need for the rest of the function
Expr::Ident(ident_expr) => ident_expr,

Expand Down Expand Up @@ -374,15 +411,15 @@ impl<'s> VisitMut for TransformVisitor<'s> {
// }
// }

fn visit_mut_member_expr(&mut self, n: &mut swc_core::ecma::ast::MemberExpr) {
fn visit_mut_member_expr(&mut self, n: &mut MemberExpr) {
if n.obj.is_ident() {
n.obj.visit_mut_with(self)
} else {
n.visit_mut_children_with(self);
}
}

fn visit_mut_object_lit(&mut self, n: &mut swc_core::ecma::ast::ObjectLit) {
fn visit_mut_object_lit(&mut self, n: &mut ObjectLit) {
for prop in n.props.iter_mut() {
match prop {
PropOrSpread::Prop(ref mut prop) => {
Expand Down
168 changes: 161 additions & 7 deletions crates/fervid_transform/src/template/v_on.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use fervid_core::{fervid_atom, FervidAtom, StrOrExpr, VOnDirective};
use fervid_core::{fervid_atom, BindingTypes, FervidAtom, StrOrExpr, VOnDirective};
use swc_core::{
common::DUMMY_SP,
ecma::ast::{
Expand Down Expand Up @@ -32,19 +32,21 @@ impl TemplateVisitor<'_> {
// 1. Check the handler shape
let mut is_member_or_paren = false;
let mut is_non_null_or_opt_chain = false;
let mut is_unresolved_ident = false;
let mut is_non_const_ident = false;
let mut needs_event = false;

match handler.as_ref() {
// This is always as-is
Expr::Fn(_) | Expr::Arrow(_) => {}

// This is either as-is (if known) or `(...args) => _ctx.smth && _ctx.smth(...args)`
// This is either as-is (if const) or `(...args) => _ctx.smth && _ctx.smth(...args)`
Expr::Ident(ident) => {
is_unresolved_ident = matches!(
is_non_const_ident = !matches!(
self.bindings_helper
.get_var_binding_type(scope_to_use, &ident.sym),
fervid_core::BindingTypes::Unresolved
BindingTypes::SetupConst
| BindingTypes::LiteralConst
| BindingTypes::SetupReactiveConst
);
}

Expand Down Expand Up @@ -107,7 +109,7 @@ impl TemplateVisitor<'_> {
.transform_expr(&mut handler, scope_to_use);

// 4. Wrap in `(...args)` arrow if needed
if is_unresolved_ident || is_member_or_paren || is_non_null_or_opt_chain {
if is_non_const_ident || is_member_or_paren || is_non_null_or_opt_chain {
handler = wrap_in_args_arrow(handler, !is_non_null_or_opt_chain);
}

Expand Down Expand Up @@ -243,7 +245,11 @@ fn wrap_in_args_arrow(mut expr: Box<Expr>, needs_check: bool) -> Box<Expr> {

#[cfg(test)]
mod tests {
use fervid_core::fervid_atom;
use fervid_core::{
fervid_atom, BindingTypes, BindingsHelper, SetupBinding, TemplateGenerationMode,
};

use crate::test_utils::{to_str, ts};

use super::*;

Expand Down Expand Up @@ -285,4 +291,152 @@ mod tests {
test!("click", "onClick");
test!("multi-word-event", "onMultiWordEvent");
}

#[test]
fn it_transforms_handler() {
// `const foo = ref()`
// `function func() {}`
let mut bindings_helper = BindingsHelper::default();
bindings_helper
.setup_bindings
.push(SetupBinding(fervid_atom!("foo"), BindingTypes::SetupRef));
bindings_helper
.setup_bindings
.push(SetupBinding(fervid_atom!("func"), BindingTypes::SetupConst));
bindings_helper.template_generation_mode = TemplateGenerationMode::Inline;

let mut template_visitor = TemplateVisitor {
bindings_helper: &mut bindings_helper,
current_scope: 0,
v_for_scope: false,
};

// @evt="$in"
macro_rules! test {
($in: literal, $expected: literal) => {
let mut v_on = VOnDirective {
event: Some("evt".into()),
handler: Some(ts($in)),
modifiers: vec![],
span: DUMMY_SP,
};
template_visitor.transform_v_on(&mut v_on, 0);
assert_eq!($expected, to_str(&v_on.handler.expect("should exist")));
};
}

// Explicit and known ref

// Arrow
test!("() => foo = 2", "()=>foo.value=2");
test!("$event => foo = 2", "$event=>foo.value=2");
test!("$event => foo = $event", "$event=>foo.value=$event");
test!("v => foo = v", "v=>foo.value=v");
test!("(v1, v2) => foo += v1 * v2", "(v1,v2)=>foo.value+=v1*v2");

// Function
test!("function () { foo = 2 }", "function(){foo.value=2;}");
test!(
"function assignFoo () { foo = 2 }",
"function assignFoo(){foo.value=2;}"
);
test!(
"function ($event) { foo = 2 }",
"function($event){foo.value=2;}"
);
test!(
"function ($event) { foo = $event }",
"function($event){foo.value=$event;}"
);
test!("function (v) { foo = v }", "function(v){foo.value=v;}");
test!(
"function (v1, v2) { foo += v1 * v2 }",
"function(v1,v2){foo.value+=v1*v2;}"
);

// Implicit $event
test!("foo = 2", "$event=>foo.value=2");
test!("foo = 2", "$event=>foo.value=2");
test!("foo = $event", "$event=>foo.value=$event");

// Different handler expressions:

// resolved binding
test!("func", "func");

// unresolved binding
test!("bar", "(...args)=>_ctx.bar&&_ctx.bar(...args)");

// member expr
test!(
"foo.bar",
"(...args)=>foo.value.bar&&foo.value.bar(...args)"
);
test!("bar.baz", "(...args)=>_ctx.bar.baz&&_ctx.bar.baz(...args)");

// paren expr
test!("(foo)", "(...args)=>(foo.value)&&(foo.value)(...args)");
test!("(bar)", "(...args)=>(_ctx.bar)&&(_ctx.bar)(...args)");

// ts non-null
test!("foo!", "(...args)=>foo.value!(...args)");
test!("bar!", "(...args)=>_ctx.bar!(...args)");

// optional chaining
test!("foo?.bar", "(...args)=>foo.value?.bar(...args)");
test!("bar?.baz", "(...args)=>_ctx.bar?.baz(...args)");

// call
test!("func()", "$event=>func()");
test!("func($event)", "$event=>func($event)");
test!("foo($event)", "$event=>foo.value($event)");

// array
test!("[foo, bar]", "$event=>[foo.value,_ctx.bar]");

// this
test!("this", "$event=>this");

// object
// FIXME this is a bug in SWC stringifier (it should add parens):
// test!("{}", "$event=>({})");
test!("{}", "$event=>{}");

// unary
test!("!foo", "$event=>!foo.value");

// binary
test!("foo || bar", "$event=>foo.value||_ctx.bar");

// update
test!("foo++", "$event=>foo.value++");

// assign
test!("foo += bar", "$event=>foo.value+=_ctx.bar");

// new
test!("new func", "$event=>new func");
test!("new foo", "$event=>new foo.value");
test!("new bar", "$event=>new _ctx.bar");

// seq
test!("foo, bar", "$event=>foo.value,_ctx.bar");

// condition
test!("foo ? bar : baz", "$event=>foo.value?_ctx.bar:_ctx.baz");

// literal
test!("123.45", "$event=>123.45");
test!("'foo'", "$event=>\"foo\"");
test!("true", "$event=>true");

// template
test!("`bar ${baz}`", "$event=>`bar ${_ctx.baz}`");

// tagged template
test!("foo`bar ${baz}`", "$event=>foo.value`bar ${_ctx.baz}`");

// class
test!("class FooBar {}", "$event=>class FooBar{}");
}
}
23 changes: 0 additions & 23 deletions crates/fervid_transform/src/test_utils/js_stringify.rs

This file was deleted.

36 changes: 34 additions & 2 deletions crates/fervid_transform/src/test_utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,36 @@
mod js_stringify;
pub mod parser;

pub use js_stringify::to_str;
use swc_core::common::{sync::Lrc, SourceMap};
use swc_core::ecma::ast::Expr;
use swc_ecma_codegen::{text_writer::JsWriter, Emitter, Node};

use self::parser::{parse_javascript_expr, parse_typescript_expr};

pub fn js(raw: &str) -> Box<Expr> {
parse_javascript_expr(raw, 0, Default::default()).unwrap().0
}

pub fn ts(raw: &str) -> Box<Expr> {
parse_typescript_expr(raw, 0, Default::default()).unwrap().0
}

pub fn to_str(swc_node: &impl Node) -> String {
// Emitting the result requires some setup with SWC
let cm: Lrc<SourceMap> = Default::default();
let mut buff: Vec<u8> = Vec::with_capacity(128);
let writer: JsWriter<&mut Vec<u8>> = JsWriter::new(cm.clone(), "\n", &mut buff, None);

let mut emitter_cfg = swc_ecma_codegen::Config::default();
emitter_cfg.minify = true;

let mut emitter = Emitter {
cfg: emitter_cfg,
comments: None,
wr: writer,
cm,
};

let _ = swc_node.emit_with(&mut emitter);

String::from_utf8(buff).unwrap()
}
25 changes: 25 additions & 0 deletions crates/fervid_transform/src/test_utils/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,28 @@ pub fn parse_javascript_expr(

parser.parse_expr().map(|module| (module, comments))
}

pub fn parse_typescript_expr(
input: &str,
span_start: u32,
ts_config: TsConfig,
) -> Result<(Box<Expr>, SingleThreadedComments), swc_ecma_parser::error::Error> {
let span = Span::new(
BytePos(span_start),
BytePos(span_start + input.len() as u32),
SyntaxContext::empty(),
);

let comments = SingleThreadedComments::default();

let lexer = Lexer::new(
Syntax::Typescript(ts_config),
EsVersion::EsNext,
StringInput::new(input, span.lo, span.hi),
Some(&comments),
);

let mut parser = Parser::new_from(lexer);

parser.parse_expr().map(|module| (module, comments))
}

0 comments on commit 1615463

Please sign in to comment.