Skip to content

Commit

Permalink
feat(#19): template assignments transform
Browse files Browse the repository at this point in the history
  • Loading branch information
phoenix-ru committed Mar 23, 2024
1 parent a95db91 commit 676293e
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 13 deletions.
8 changes: 7 additions & 1 deletion crates/fervid_parser/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use fervid_core::{
AttributeOrBinding, FervidAtom, StrOrExpr, VBindDirective, VCustomDirective, VForDirective,
VModelDirective, VOnDirective, VSlotDirective, VueDirectives,
};
use swc_core::common::{BytePos, Span};
use swc_core::{common::{BytePos, Span}, ecma::ast::Expr};
use swc_ecma_parser::Syntax;
use swc_html_ast::Attribute;

Expand Down Expand Up @@ -349,6 +349,12 @@ impl SfcParser<'_, '_, '_> {

match self.parse_expr(&value, ts!(), span) {
Ok(model_binding) => {
// v-model value must be a valid JavaScript member expression
if !matches!(*model_binding, Expr::Member(_) | Expr::Ident(_)) {
// TODO Report an error
bail!();
}

let directives = get_directives!();
directives.v_model.push(VModelDirective {
argument,
Expand Down
48 changes: 44 additions & 4 deletions crates/fervid_transform/src/template/expr_transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use fervid_core::{
StrOrExpr, TemplateGenerationMode, VModelDirective, VueImports,
};
use swc_core::{
common::{Span, DUMMY_SP},
common::DUMMY_SP,
ecma::{
ast::{
ArrayLit, ArrayPat, AssignExpr, AssignOp, AssignTarget, AssignTargetPat, BindingIdent,
Expand All @@ -29,6 +29,14 @@ struct TransformVisitor<'s> {
/// In ({ x } = y)
is_in_destructure_assign: bool,

/// In LHS of x = y
is_in_assign_target: bool,

/// COMPAT: `v-on` and `v-model` look differently at how to transform `SetupMaybeRef`
/// `v-on` simply assigns to it: `maybe.value = 1`
/// `v-model` does a check: `isRef(maybe) ? maybe.value = 1 : null`
is_v_model_transform: bool,

// `SetupBinding` instead of `FervidAtom` to easier interface with `extract_variables_from_pat`
local_vars: Vec<SetupBinding>,

Expand Down Expand Up @@ -75,7 +83,9 @@ impl BindingsHelperTransform for BindingsHelper {
bindings_helper: self,
has_js_bindings: false,
is_inline,
is_in_assign_target: false,
is_in_destructure_assign: false,
is_v_model_transform: false,
local_vars: Vec::new(),
update_expr_helper: None,
should_consume_update_expr: false,
Expand Down Expand Up @@ -113,7 +123,25 @@ impl BindingsHelperTransform for BindingsHelper {
));

// 2. Transform handler
self.transform_expr(&mut handler, scope_to_use);
{
let is_inline = matches!(
self.template_generation_mode,
TemplateGenerationMode::Inline
);
let mut visitor = TransformVisitor {
current_scope: scope_to_use,
bindings_helper: self,
has_js_bindings: false,
is_inline,
is_in_assign_target: false,
is_in_destructure_assign: false,
is_v_model_transform: true,
local_vars: Vec::new(),
update_expr_helper: None,
should_consume_update_expr: false,
};
handler.visit_mut_with(&mut visitor);
}

// 3. Assign handler
v_model.update_handler = Some(handler);
Expand Down Expand Up @@ -230,6 +258,7 @@ impl<'s> VisitMut for TransformVisitor<'s> {
assign_expr.right.visit_mut_with(self);

// Check for special case: LHS is an ident of type `SetupLet`
// Also special case: LHS is `SetupMaybeRef` inside v-model
// This is only valid for Inline mode
let setup_let_ident = if self.is_inline {
let ident = match assign_expr.left {
Expand All @@ -242,7 +271,10 @@ impl<'s> VisitMut for TransformVisitor<'s> {
.bindings_helper
.get_var_binding_type(self.current_scope, &ident.sym);

if let BindingTypes::SetupLet | BindingTypes::SetupMaybeRef = binding_type {
if matches!(binding_type, BindingTypes::SetupLet)
|| self.is_v_model_transform
&& matches!(binding_type, BindingTypes::SetupMaybeRef)
{
Some((ident, binding_type))
} else {
None
Expand Down Expand Up @@ -489,6 +521,9 @@ impl<'s> VisitMut for TransformVisitor<'s> {

// This is a copy of `visit_mut_expr` because AssignTarget is more refined compared to Expr
fn visit_mut_assign_target(&mut self, n: &mut AssignTarget) {
let old_is_in_assign_target = self.is_in_assign_target;
self.is_in_assign_target = true;

match n {
AssignTarget::Simple(simple) => match simple {
SimpleAssignTarget::Ident(ident) => {
Expand Down Expand Up @@ -559,6 +594,8 @@ impl<'s> VisitMut for TransformVisitor<'s> {
self.is_in_destructure_assign = old_is_in_destructure;
}
}

self.is_in_assign_target = old_is_in_assign_target;
}

fn visit_mut_pat(&mut self, n: &mut Pat) {
Expand Down Expand Up @@ -726,7 +763,9 @@ impl TransformVisitor<'_> {
match binding_type {
// Update expression with MaybeRef: `maybe++` -> `maybe.value++`
BindingTypes::SetupMaybeRef
if self.update_expr_helper.is_some() || self.is_in_destructure_assign =>
if self.is_in_destructure_assign
|| (self.is_in_assign_target && !self.is_v_model_transform)
|| self.update_expr_helper.is_some() =>
{
IdentTransformStrategy::DotValue
}
Expand Down Expand Up @@ -1138,6 +1177,7 @@ mod tests {
test!(
"MaybeRef",
"_unref(MaybeRef)",
// This is the official spec, but it is inconsistent with the `v-on` transform
"$event=>_isRef(MaybeRef)?MaybeRef.value=$event:null"
);
test!("Const", "Const", "$event=>Const=$event"); // TODO must err
Expand Down
25 changes: 17 additions & 8 deletions crates/fervid_transform/src/template/v_on.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl TemplateVisitor<'_> {
let mut is_non_const_ident = false;
let mut needs_event = false;

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

Expand Down Expand Up @@ -243,6 +243,15 @@ fn wrap_in_args_arrow(mut expr: Box<Expr>, needs_check: bool) -> Box<Expr> {
}))
}

// Mirror what `@babel/parser` does
fn unwrap_parens(expr: &Expr) -> &Expr {
let Expr::Paren(p) = expr else {
return expr;
};

return &p.expr;
}

#[cfg(test)]
mod tests {
use fervid_core::{
Expand Down Expand Up @@ -401,6 +410,7 @@ mod tests {

// object
// FIXME this is a bug in SWC stringifier (it should add parens):
// or maybe it's not a bug, depends on if you interpret it as a Stmt or as an Expr
// test!("{}", "$event=>({})");
test!("{}", "$event=>{}");

Expand Down Expand Up @@ -476,7 +486,8 @@ mod tests {
// <div @click="maybe = count"/>
test!(
"maybe = count",
"$event=>_isRef(maybe)?maybe.value=count.value:null"
// This is the official spec, but it is inconsistent with the `v-model` transform
"$event=>maybe.value=count.value"
);

// <div @click="lett = count"/>
Expand Down Expand Up @@ -606,23 +617,21 @@ mod tests {
};
}

// TODO Implement the actual tests and get rid of the dummy
// Not a destructure, but an instant indicator if something is off
test!("count = val", "$event=>count.value=val");

// Template-local case
// TODO Implement
// TODO Add parenthesis when needed
// <div v-for="item in list"><div @click="({ item } = val)"/></div>
// test!("{ item } = val", "$event=>({item}=val})");
test!("({ item } = val)", "$event=>({item}=val)");

// <div @click="({ count } = val)"/>
// test!("({ count } = val)", "({ count: count.value } = val)");
test!("({ count } = val)", "$event=>({count:count.value}=val)");

// <div @click="[maybe] = val"/>
test!("[maybe] = val", "$event=>[maybe.value]=val");

// <div @click="({ lett } = val)"/>
// test!("({ lett } = val)", "{ lett: lett } = val");
test!("({ lett } = val)", "$event=>({lett:lett}=val)");
}

fn helper(bindings: Vec<SetupBinding>) -> BindingsHelper {
Expand Down

0 comments on commit 676293e

Please sign in to comment.