vue-oxc-parser 0.1.2

Covert Vue AST (vue-compiler-core) to JSX AST (oxc)
Documentation
use oxc_allocator::{Allocator, TakeIn};
use oxc_ast::{
  AstBuilder, NONE,
  ast::{
    BinaryOperator, Expression, FormalParameter, FormalParameterKind, JSXAttribute,
    JSXAttributeItem, JSXAttributeName, JSXAttributeValue, JSXChild, JSXElement, JSXExpression,
    ObjectPropertyKind, PropertyKind, Statement,
  },
};
use oxc_ast_visit::VisitMut;
use oxc_parser::Parser;
use oxc_span::{GetSpan, SPAN, SourceType};

use crate::parser::{ParserReturn, VueOxcParser};

impl<'a> VueOxcParser<'a> {
  pub fn parse_for_semantic(self) -> ParserReturn<'a> {
    let allocator = self.ast.allocator;
    let source_type = self.source_type.clone();
    let source_text = self.source_text;
    let mut ret = self.parse();
    let body = &mut ret.program.body as *mut oxc_allocator::Vec<Statement>;
    let Statement::ExpressionStatement(stmt) = &mut unsafe { &mut *body }[0] else {
      return ret;
    };
    let Expression::JSXFragment(jsx_fragment) = &mut stmt.expression else {
      return ret;
    };
    let ast = AstBuilder::new(allocator);
    let children = &mut jsx_fragment.children as *mut oxc_allocator::Vec<JSXChild>;
    for child in unsafe { &mut *children } {
      match child {
        JSXChild::Element(element) => {
          if element
            .opening_element
            .name
            .get_identifier_name()
            .and_then(|name| Some(name.eq("script")))
            .unwrap_or_default()
          {
            let is_setup = element.opening_element.attributes.iter().any(|attr| {
              attr
                .as_attribute()
                .and_then(|attr| Some(attr.name.get_identifier().name.eq("setup")))
                .unwrap_or_default()
            });

            if let JSXChild::ExpressionContainer(child) = &mut element.children[0]
              && let JSXExpression::ArrowFunctionExpression(expr) = &mut child.expression
            {
              let mut statements = expr.body.statements.take_in(allocator);
              if is_setup {
                let last = unsafe { &mut *body }.last_mut().unwrap();
                statements.push(last.take_in(allocator));
                *last = ast.statement_expression(
                  SPAN,
                  ast.expression_arrow_function(
                    SPAN,
                    false,
                    false,
                    NONE,
                    ast.formal_parameters(
                      SPAN,
                      FormalParameterKind::ArrowFormalParameters,
                      ast.vec(),
                      NONE,
                    ),
                    NONE,
                    ast.function_body(SPAN, ast.vec(), statements),
                  ),
                );
              } else {
                let index = ret.program.body.len() - 1;
                unsafe { &mut *body }.splice(index..index, statements);
              }
            };
          }
        }
        _ => (),
      }
    }

    TransformDreictives {
      ast: &ast,
      allocator: &allocator,
      source_text,
      source_type,
    }
    .visit_program(&mut ret.program);
    ret
  }
}

struct TransformDreictives<'a, 'b> {
  ast: &'b AstBuilder<'a>,
  allocator: &'a Allocator,
  source_type: SourceType,
  source_text: &'b str,
}

impl<'a, 'b> VisitMut<'a> for TransformDreictives<'a, 'b> {
  fn visit_jsx_element(&mut self, it: &mut JSXElement<'a>) {
    let mut v_slot = None;
    let mut v_for = None;
    let _it = it as *mut JSXElement;
    for dir in &mut unsafe { &mut *_it }.opening_element.attributes {
      let JSXAttributeItem::Attribute(dir) = dir else {
        continue;
      };

      let dir_name = match &mut dir.name {
        JSXAttributeName::Identifier(name) => name.name,
        JSXAttributeName::NamespacedName(name) => name.namespace.name,
      };

      match dir_name.as_str() {
        "v-slot" => v_slot = Some(dir),
        "v-for" => v_for = Some(dir),
        _ => (),
      }
    }

    if let Some(v_slot) = v_slot {
      self.transform_v_slot(v_slot, it)
    }
    if let Some(v_for) = v_for {
      self.transform_v_for(v_for, it)
    }
    self.visit_jsx_children(&mut it.children);
  }
}

impl<'a, 'b> TransformDreictives<'a, 'b> {
  fn transform_v_slot(&self, dir: &mut JSXAttribute<'a>, it: &mut JSXElement<'a>) {
    let ast = self.ast;
    let mut name = ast.property_key_static_identifier(
      SPAN,
      match &mut dir.name {
        JSXAttributeName::Identifier(_) => "default",
        JSXAttributeName::NamespacedName(name) => name.name.name.as_str(),
      },
    );
    let mut computed = false;

    let params = if let Some(value) = &mut dir.value
      && let JSXAttributeValue::ExpressionContainer(value) = value
    {
      let mut value = value.expression.take_in(self.allocator);
      let expr = if let JSXExpression::ObjectExpression(value) = &mut value
        && let Some(ObjectPropertyKind::ObjectProperty(prop)) = value.properties.get_mut(0)
        && prop.computed
        && prop.span.eq(&SPAN)
      {
        computed = true;
        name = prop.key.take_in(self.allocator);
        &mut prop.value
      } else {
        value.to_expression_mut()
      };
      self.parse_params(expr)
    } else {
      ast.vec()
    };

    it.children = ast.vec1(
      ast.jsx_child_expression_container(
        SPAN,
        ast
          .expression_object(
            SPAN,
            ast.vec_from_array([ast.object_property_kind_object_property(
              SPAN,
              PropertyKind::Init,
              name,
              ast.expression_arrow_function(
                SPAN,
                true,
                false,
                NONE,
                ast.formal_parameters(
                  SPAN,
                  FormalParameterKind::ArrowFormalParameters,
                  params,
                  NONE,
                ),
                NONE,
                ast.function_body(
                  SPAN,
                  ast.vec(),
                  ast.vec1(ast.statement_expression(
                    SPAN,
                    ast.expression_jsx_fragment(
                      SPAN,
                      ast.jsx_opening_fragment(SPAN),
                      it.children.take_in(self.allocator),
                      ast.jsx_closing_fragment(SPAN),
                    ),
                  )),
                ),
              ),
              false,
              false,
              computed,
            )]),
          )
          .into(),
      ),
    )
  }

  fn transform_v_for(&self, dir: &mut JSXAttribute<'a>, it: &mut JSXElement<'a>) {
    let ast = self.ast;

    let params = if let Some(value) = &mut dir.value
      && let JSXAttributeValue::ExpressionContainer(value) = value
      && let JSXExpression::BinaryExpression(expr) = &mut value.expression
      && expr.operator == BinaryOperator::In
    {
      let mut left = expr.left.take_in(self.allocator);
      self.parse_params(&mut left)
    } else {
      ast.vec()
    };

    it.children = ast.vec1(
      ast.jsx_child_expression_container(
        SPAN,
        ast
          .expression_arrow_function(
            SPAN,
            true,
            false,
            NONE,
            ast.formal_parameters(
              SPAN,
              FormalParameterKind::ArrowFormalParameters,
              params,
              NONE,
            ),
            NONE,
            ast.function_body(
              SPAN,
              ast.vec(),
              ast.vec1(ast.statement_expression(
                SPAN,
                ast.expression_jsx_fragment(
                  SPAN,
                  ast.jsx_opening_fragment(SPAN),
                  it.children.take_in(self.allocator),
                  ast.jsx_closing_fragment(SPAN),
                ),
              )),
            ),
          )
          .into(),
      ),
    )
  }

  fn parse_params(&self, expr: &mut Expression) -> oxc_allocator::Vec<'a, FormalParameter<'a>> {
    let span = expr.without_parentheses().span();
    if !span.eq(&SPAN)
      && let Expression::ArrowFunctionExpression(mut expr) = Parser::new(
        self.allocator,
        self
          .ast
          .atom(&format!(
            "/*{}*/({})=>{{}}",
            "*".repeat(span.start as usize - 5),
            span.source_text(self.source_text)
          ))
          .as_str(),
        self.source_type,
      )
      .parse_expression()
      .unwrap()
    {
      expr.params.items.take_in(self.allocator)
    } else {
      self.ast.vec()
    }
  }
}