From 0e4656ac1ce6c37e2292025fa7bc52db02c0327b Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Fri, 28 Jul 2023 16:12:16 -0700 Subject: [PATCH] [rust][wip] swc->estree for jsx --- .../crates/forget_build_hir/src/build.rs | 59 +++- .../crates/forget_estree/src/generated.rs | 3 + ...get_estree__tests__fixtures@test.json.snap | 15 + .../forget_estree_codegen/src/ecmascript.json | 5 + .../crates/forget_estree_swc/src/lib.rs | 328 ++++++++++++++++-- .../forget_fixtures/tests/fixtures/jsx.js | 9 + .../crates/forget_hir/src/instruction.rs | 31 ++ 7 files changed, 409 insertions(+), 41 deletions(-) create mode 100644 compiler/forget/crates/forget_fixtures/tests/fixtures/jsx.js diff --git a/compiler/forget/crates/forget_build_hir/src/build.rs b/compiler/forget/crates/forget_build_hir/src/build.rs index 4401287c55..e072d044ec 100644 --- a/compiler/forget/crates/forget_build_hir/src/build.rs +++ b/compiler/forget/crates/forget_build_hir/src/build.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use bumpalo::collections::String; +use bumpalo::collections::{CollectIn, String}; use forget_diagnostics::Diagnostic; use forget_estree::{ AssignmentTarget, BinaryExpression, BlockStatement, Expression, ExpressionOrSpread, @@ -9,8 +9,8 @@ use forget_estree::{ }; use forget_hir::{ BlockKind, BranchTerminal, Environment, ForTerminal, GotoKind, IdentifierOperand, InstrIx, - InstructionKind, InstructionValue, LValue, LoadGlobal, LoadLocal, Operand, PlaceOrSpread, - PrimitiveValue, TerminalValue, + InstructionKind, InstructionValue, JSXAttribute, JSXElement, LValue, LoadGlobal, LoadLocal, + Operand, PlaceOrSpread, PrimitiveValue, TerminalValue, }; use crate::builder::{Binding, Builder, LoopScope}; @@ -468,6 +468,10 @@ fn lower_expression<'a>( }) } + Expression::JSXElement(expr) => { + InstructionValue::JSXElement(lower_jsx_element(env, builder, *expr)?) + } + _ => todo!("Lower expr {expr:#?}"), }; Ok(builder.push(value)) @@ -530,6 +534,55 @@ fn lower_function<'a>( }) } +fn lower_jsx_element<'a>( + env: &'a Environment<'a>, + builder: &mut Builder<'a>, + expr: forget_estree::JSXElement, +) -> Result, Diagnostic> { + let props: Result>, Diagnostic> = expr + .opening_element + .attributes + .into_iter() + .map(|attr| lower_jsx_attribute(env, builder, attr)) + .collect(); + let props = props?; + let children: Result, Diagnostic> = expr + .children + .into_iter() + .map(|child| { + let ix = lower_jsx_child(env, builder, child)?; + Ok(Operand { effect: None, ix }) + }) + .collect(); + let children = children?; + todo!("lower jsx element"); + // Ok(JSXElement { + // tag: todo!(), + // props, + // children: if children.is_empty() { + // None + // } else { + // Some(children) + // }, + // }) +} + +fn lower_jsx_attribute<'a>( + env: &'a Environment<'a>, + builder: &mut Builder<'a>, + attr: forget_estree::JSXAttributeOrSpread, +) -> Result, Diagnostic> { + todo!("lower jsx attribute") +} + +fn lower_jsx_child<'a>( + env: &'a Environment<'a>, + builder: &mut Builder<'a>, + child: forget_estree::JSXChildItem, +) -> Result { + todo!("lower jsx child") +} + fn lower_assignment<'a>( env: &'a Environment<'a>, builder: &mut Builder<'a>, diff --git a/compiler/forget/crates/forget_estree/src/generated.rs b/compiler/forget/crates/forget_estree/src/generated.rs index b5b2e8fa97..50d76141d7 100644 --- a/compiler/forget/crates/forget_estree/src/generated.rs +++ b/compiler/forget/crates/forget_estree/src/generated.rs @@ -1429,6 +1429,9 @@ impl Serialize for ExportAllDeclaration { #[derive(Deserialize, Clone, Debug)] pub struct JSXIdentifier { pub name: String, + #[serde(skip)] + #[serde(default)] + pub binding: Option, #[serde(default)] pub loc: Option, #[serde(default)] diff --git a/compiler/forget/crates/forget_estree/src/snapshots/forget_estree__tests__fixtures@test.json.snap b/compiler/forget/crates/forget_estree/src/snapshots/forget_estree__tests__fixtures@test.json.snap index 4b87ee2f85..67ced0dac8 100644 --- a/compiler/forget/crates/forget_estree/src/snapshots/forget_estree__tests__fixtures@test.json.snap +++ b/compiler/forget/crates/forget_estree/src/snapshots/forget_estree__tests__fixtures@test.json.snap @@ -395,7 +395,10 @@ Output: "type": "JSXMemberExpression", "object": { "type": "JSXIdentifier", + "type": "Identifier", "name": "Foo", + "loc": null, + "range": null, "loc": { "source": null, "start": { @@ -414,7 +417,10 @@ Output: }, "property": { "type": "JSXIdentifier", + "type": "Identifier", "name": "Bar", + "loc": null, + "range": null, "loc": { "source": null, "start": { @@ -452,7 +458,10 @@ Output: "type": "JSXAttribute", "name": { "type": "JSXIdentifier", + "type": "Identifier", "name": "a", + "loc": null, + "range": null, "loc": { "source": null, "start": { @@ -552,7 +561,10 @@ Output: "type": "JSXMemberExpression", "object": { "type": "JSXIdentifier", + "type": "Identifier", "name": "Foo", + "loc": null, + "range": null, "loc": { "source": null, "start": { @@ -571,7 +583,10 @@ Output: }, "property": { "type": "JSXIdentifier", + "type": "Identifier", "name": "Bar", + "loc": null, + "range": null, "loc": { "source": null, "start": { diff --git a/compiler/forget/crates/forget_estree_codegen/src/ecmascript.json b/compiler/forget/crates/forget_estree_codegen/src/ecmascript.json index 96e8cfcd23..1ae57eee07 100644 --- a/compiler/forget/crates/forget_estree_codegen/src/ecmascript.json +++ b/compiler/forget/crates/forget_estree_codegen/src/ecmascript.json @@ -681,6 +681,11 @@ "fields": { "name": { "type": "String" + }, + "binding": { + "type": "Option", + "optional": true, + "skip": true } } }, diff --git a/compiler/forget/crates/forget_estree_swc/src/lib.rs b/compiler/forget/crates/forget_estree_swc/src/lib.rs index 8bd1e5a191..b6e185c2cf 100644 --- a/compiler/forget/crates/forget_estree_swc/src/lib.rs +++ b/compiler/forget/crates/forget_estree_swc/src/lib.rs @@ -8,11 +8,13 @@ use swc_core::common::errors::Handler; use swc_core::common::source_map::Pos; use swc_core::common::{FileName, FilePathMapping, Mark, SourceMap, Span, SyntaxContext, GLOBALS}; use swc_core::ecma::ast::{ - AssignOp, BinaryOp, BlockStmt, BlockStmtOrExpr, CallExpr, Callee, Decl, EsVersion, Expr, - ExprOrSpread, Function, Ident, Lit, MemberExpr, MemberProp, ModuleItem, OptChainBase, Pat, - PatOrExpr, Program, Stmt, UnaryOp, VarDecl, VarDeclKind, VarDeclOrExpr, + AssignOp, BinaryOp, BlockStmt, BlockStmtOrExpr, Callee, Decl, EsVersion, Expr, ExprOrSpread, + Function, Ident, JSXAttr, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXElement, + JSXElementChild, JSXElementName, JSXExpr, JSXMemberExpr, JSXObject, Lit, MemberExpr, + MemberProp, ModuleItem, OptChainBase, Pat, PatOrExpr, Program, Stmt, UnaryOp, VarDecl, + VarDeclKind, VarDeclOrExpr, }; -use swc_core::ecma::parser::Syntax; +use swc_core::ecma::parser::{Syntax, TsConfig}; use swc_core::ecma::transforms::base::resolver; use swc_core::ecma::visit::FoldWith; @@ -35,7 +37,10 @@ pub fn parse( fm.clone(), &handler, EsVersion::Es5, - Syntax::Typescript(Default::default()), + Syntax::Typescript(TsConfig { + tsx: true, + ..Default::default() + }), swc::config::IsModule::Bool(true), Some(&comments), )?; @@ -340,13 +345,20 @@ fn convert_expression(cx: &Context, expr: &Expr) -> forget_estree::Expression { range: convert_span(&expr.span), })) } - Expr::Await(_expr) => { - // forget_estree::Expression::AwaitExpression(Box::new(forget_estree::AwaitExpression { - // argument: convert_expression(cx, &expr.arg), - // loc: None, - // range: convert_span(&expr.span), - // })) - todo!("await expression") + Expr::Await(expr) => { + forget_estree::Expression::AwaitExpression(Box::new(forget_estree::AwaitExpression { + argument: convert_expression(cx, &expr.arg), + loc: None, + range: convert_span(&expr.span), + })) + } + Expr::Yield(expr) => { + forget_estree::Expression::YieldExpression(Box::new(forget_estree::YieldExpression { + argument: expr.arg.as_ref().map(|arg| convert_expression(cx, arg)), + is_delegate: expr.delegate, + loc: None, + range: convert_span(&expr.span), + })) } Expr::Unary(expr) => { forget_estree::Expression::UnaryExpression(Box::new(forget_estree::UnaryExpression { @@ -377,32 +389,7 @@ fn convert_expression(cx: &Context, expr: &Expr) -> forget_estree::Expression { }, )), }, - Expr::Lit(expr) => { - let (value, range) = match expr { - Lit::Bool(expr) => ( - forget_estree::JsValue::Bool(expr.value), - convert_span(&expr.span), - ), - Lit::Num(expr) => ( - forget_estree::JsValue::Number(expr.value.into()), - convert_span(&expr.span), - ), - Lit::Str(expr) => ( - forget_estree::JsValue::String(expr.value.to_string()), - convert_span(&expr.span), - ), - Lit::Null(expr) => (forget_estree::JsValue::Null, convert_span(&expr.span)), - _ => todo!(), - }; - forget_estree::Expression::Literal(Box::new(forget_estree::Literal { - value, - raw: None, - loc: None, - regex: None, - range, - bigint: None, - })) - } + Expr::Lit(expr) => forget_estree::Expression::Literal(Box::new(convert_literal(cx, expr))), Expr::Assign(expr) => forget_estree::Expression::AssignmentExpression(Box::new( forget_estree::AssignmentExpression { operator: convert_assignment_operator(expr.op), @@ -470,10 +457,265 @@ fn convert_expression(cx: &Context, expr: &Expr) -> forget_estree::Expression { forget_estree::Expression::MemberExpression(Box::new(member)) } }, + Expr::JSXElement(expr) => { + forget_estree::Expression::JSXElement(Box::new(convert_jsx_element(cx, expr))) + } + Expr::Paren(expr) => convert_expression(cx, &expr.expr), _ => todo!("translate expression {:#?}", expr), } } +fn convert_jsx_element(cx: &Context, expr: &JSXElement) -> forget_estree::JSXElement { + let attributes = expr + .opening + .attrs + .iter() + .map(|attr| match attr { + JSXAttrOrSpread::JSXAttr(attr) => forget_estree::JSXAttributeOrSpread::JSXAttribute( + Box::new(convert_jsx_attribute(cx, attr)), + ), + JSXAttrOrSpread::SpreadElement(attr) => { + forget_estree::JSXAttributeOrSpread::JSXSpreadAttribute(Box::new( + forget_estree::JSXSpreadAttribute { + argument: convert_expression(cx, &attr.expr), + loc: None, + range: None, // sigh, no span + }, + )) + } + }) + .collect(); + let opening_element = forget_estree::JSXOpeningElement { + attributes, + name: convert_jsx_name(cx, &expr.opening.name), + self_closing: expr.opening.self_closing, + loc: None, + range: convert_span(&expr.opening.span), + }; + let children = expr + .children + .iter() + .map(|child| convert_jsx_child(cx, child)) + .collect(); + let closing_element = expr + .closing + .as_ref() + .map(|closing| forget_estree::JSXClosingElement { + name: convert_jsx_name(cx, &closing.name), + loc: None, + range: convert_span(&closing.span), + }); + forget_estree::JSXElement { + opening_element, + children, + closing_element, + loc: None, + range: convert_span(&expr.span), + } +} + +fn convert_jsx_name(cx: &Context, name: &JSXElementName) -> forget_estree::JSXElementName { + match name { + JSXElementName::Ident(name) => { + forget_estree::JSXElementName::JSXIdentifier(Box::new(convert_jsx_identifier(cx, name))) + } + JSXElementName::JSXMemberExpr(name) => forget_estree::JSXElementName::JSXMemberExpression( + Box::new(convert_jsx_member_expression(cx, name)), + ), + JSXElementName::JSXNamespacedName(name) => { + forget_estree::JSXElementName::JSXNamespacedName(Box::new( + forget_estree::JSXNamespacedName { + namespace: convert_jsx_identifier(cx, &name.ns), + name: convert_jsx_identifier(cx, &name.name), + loc: None, + range: None, // sigh, swc doesn't have a span for the entire name + }, + )) + } + } +} + +fn convert_jsx_member_expression( + cx: &Context, + expr: &JSXMemberExpr, +) -> forget_estree::JSXMemberExpression { + forget_estree::JSXMemberExpression { + object: match &expr.obj { + JSXObject::JSXMemberExpr(obj) => { + forget_estree::JSXMemberExpressionOrIdentifier::JSXMemberExpression(Box::new( + convert_jsx_member_expression(cx, obj), + )) + } + JSXObject::Ident(obj) => forget_estree::JSXMemberExpressionOrIdentifier::JSXIdentifier( + Box::new(convert_jsx_identifier(cx, obj)), + ), + }, + property: convert_jsx_identifier(cx, &expr.prop), + loc: None, + range: None, // sigh, swc doesn't have a span for the member expr + } +} + +fn convert_jsx_child(cx: &Context, child: &JSXElementChild) -> forget_estree::JSXChildItem { + match child { + JSXElementChild::JSXText(child) => { + forget_estree::JSXChildItem::JSXText(Box::new(forget_estree::JSXText { + value: child.value.to_string(), + raw: child.raw.to_string(), + loc: None, + range: convert_span(&child.span), + })) + } + JSXElementChild::JSXElement(child) => { + forget_estree::JSXChildItem::JSXElement(Box::new(convert_jsx_element(cx, child))) + } + JSXElementChild::JSXExprContainer(child) => { + forget_estree::JSXChildItem::JSXExpressionContainer(Box::new( + forget_estree::JSXExpressionContainer { + expression: match &child.expr { + JSXExpr::Expr(expr) => forget_estree::JSXExpressionOrEmpty::Expression( + convert_expression(cx, expr), + ), + JSXExpr::JSXEmptyExpr(expr) => { + forget_estree::JSXExpressionOrEmpty::JSXEmptyExpression(Box::new( + forget_estree::JSXEmptyExpression { + loc: None, + range: convert_span(&expr.span), + }, + )) + } + }, + loc: None, + range: convert_span(&child.span), + }, + )) + } + _ => todo!("handle other jsx child types"), + } +} + +fn convert_jsx_attribute(cx: &Context, attr: &JSXAttr) -> forget_estree::JSXAttribute { + let name = match &attr.name { + JSXAttrName::Ident(name) => { + forget_estree::JSXIdentifierOrNamespacedName::JSXIdentifier(Box::new({ + let ident = convert_identifier(cx, name); + forget_estree::JSXIdentifier { + name: ident.name, + binding: ident.binding, + loc: ident.loc, + range: ident.range, + } + })) + } + JSXAttrName::JSXNamespacedName(name) => { + forget_estree::JSXIdentifierOrNamespacedName::JSXNamespacedName(Box::new( + forget_estree::JSXNamespacedName { + namespace: convert_jsx_identifier(cx, &name.ns), + name: convert_jsx_identifier(cx, &name.name), + loc: None, + range: None, // sigh, swc doesn't have a span for the entire name + }, + )) + } + }; + let value = attr.value.as_ref().map(|attr| match attr { + JSXAttrValue::Lit(value) => { + forget_estree::JSXAttributeValue::Literal(Box::new(convert_literal(cx, value))) + } + JSXAttrValue::JSXElement(value) => { + forget_estree::JSXAttributeValue::JSXElement(Box::new(convert_jsx_element(cx, value))) + } + JSXAttrValue::JSXExprContainer(value) => { + forget_estree::JSXAttributeValue::JSXExpressionContainer(Box::new( + forget_estree::JSXExpressionContainer { + expression: match &value.expr { + JSXExpr::Expr(expr) => forget_estree::JSXExpressionOrEmpty::Expression( + convert_expression(cx, expr), + ), + JSXExpr::JSXEmptyExpr(expr) => { + forget_estree::JSXExpressionOrEmpty::JSXEmptyExpression(Box::new( + forget_estree::JSXEmptyExpression { + loc: None, + range: convert_span(&expr.span), + }, + )) + } + }, + loc: None, + range: convert_span(&value.span), + }, + )) + } + _ => todo!("handle other attribute value types"), + }); + forget_estree::JSXAttribute { + name, + value, + loc: None, + range: convert_span(&attr.span), + } +} + +fn convert_literal(_cx: &Context, expr: &Lit) -> forget_estree::Literal { + let (value, range, regex, bigint) = match expr { + Lit::Bool(expr) => ( + forget_estree::JsValue::Bool(expr.value), + convert_span(&expr.span), + None, + None, + ), + Lit::Num(expr) => ( + forget_estree::JsValue::Number(expr.value.into()), + convert_span(&expr.span), + None, + None, + ), + Lit::Str(expr) => ( + forget_estree::JsValue::String(expr.value.to_string()), + convert_span(&expr.span), + None, + None, + ), + Lit::Null(expr) => ( + forget_estree::JsValue::Null, + convert_span(&expr.span), + None, + None, + ), + Lit::Regex(expr) => ( + forget_estree::JsValue::Undefined, + convert_span(&expr.span), + Some(forget_estree::RegExpValue { + pattern: expr.exp.to_string(), + flags: expr.flags.to_string(), + }), + None, + ), + Lit::BigInt(expr) => ( + forget_estree::JsValue::Undefined, + convert_span(&expr.span), + None, + Some( + expr.raw + .as_ref() + .expect("Expected bigint to have a raw value") + .to_string(), + ), + ), + Lit::JSXText(_) => { + panic!("Unexpected expression: JSXText is not an expression") + } + }; + forget_estree::Literal { + value, + raw: None, + loc: None, + regex, + range, + bigint, + } +} + fn convert_arguments( cx: &Context, arguments: &[ExprOrSpread], @@ -638,6 +880,16 @@ fn convert_identifier(cx: &Context, identifier: &Ident) -> forget_estree::Identi } } +fn convert_jsx_identifier(cx: &Context, identifier: &Ident) -> forget_estree::JSXIdentifier { + let name = identifier.sym.as_ref().to_string(); + forget_estree::JSXIdentifier { + name, + binding: convert_binding(cx, identifier.span.ctxt), + loc: None, + range: convert_span(&identifier.span), + } +} + #[cfg(test)] mod tests { #[test] diff --git a/compiler/forget/crates/forget_fixtures/tests/fixtures/jsx.js b/compiler/forget/crates/forget_fixtures/tests/fixtures/jsx.js new file mode 100644 index 0000000000..b1071a9beb --- /dev/null +++ b/compiler/forget/crates/forget_fixtures/tests/fixtures/jsx.js @@ -0,0 +1,9 @@ +function Component(props) { + return ( + + Hello + {props.foo} +
{props.bar}
+
+ ); +} diff --git a/compiler/forget/crates/forget_hir/src/instruction.rs b/compiler/forget/crates/forget_hir/src/instruction.rs index fb18c8c661..8f77bd3602 100644 --- a/compiler/forget/crates/forget_hir/src/instruction.rs +++ b/compiler/forget/crates/forget_hir/src/instruction.rs @@ -36,6 +36,7 @@ impl<'a> Instruction<'a> { | InstructionValue::LoadLocal(_) | InstructionValue::Primitive(_) | InstructionValue::Function(_) + | InstructionValue::JSXElement(_) | InstructionValue::Tombstone => {} } } @@ -62,6 +63,7 @@ impl<'a> Instruction<'a> { | InstructionValue::LoadLocal(_) | InstructionValue::Primitive(_) | InstructionValue::Function(_) + | InstructionValue::JSXElement(_) | InstructionValue::Tombstone => {} } Ok(()) @@ -83,6 +85,7 @@ impl<'a> Instruction<'a> { | InstructionValue::Primitive(_) | InstructionValue::StoreLocal(_) | InstructionValue::Function(_) + | InstructionValue::JSXElement(_) | InstructionValue::Tombstone => {} } } @@ -122,6 +125,20 @@ impl<'a> Instruction<'a> { f(dep) } } + InstructionValue::JSXElement(value) => { + f(&mut value.tag); + for attr in &mut value.props { + match attr { + JSXAttribute::Spread { argument } => f(argument), + JSXAttribute::Attribute { name: _, value } => f(value), + } + } + if let Some(children) = &mut value.children { + for child in children { + f(child) + } + } + } InstructionValue::DeclareContext(_) | InstructionValue::LoadContext(_) | InstructionValue::LoadGlobal(_) @@ -147,6 +164,7 @@ pub enum InstructionValue<'a> { DeclareLocal(DeclareLocal<'a>), // Destructure(Destructure<'a>), Function(FunctionExpression<'a>), + JSXElement(JSXElement<'a>), // JsxFragment(JsxFragment<'a>), // JsxText(JsxText<'a>), LoadContext(LoadContext), @@ -382,6 +400,19 @@ pub struct StoreLocal<'a> { pub value: Operand, } +#[derive(Debug)] +pub struct JSXElement<'a> { + pub tag: Operand, + pub props: Vec<'a, JSXAttribute<'a>>, + pub children: Option>, +} + +#[derive(Debug)] +pub enum JSXAttribute<'a> { + Spread { argument: Operand }, + Attribute { name: String<'a>, value: Operand }, +} + #[derive(Clone, Debug)] pub struct Operand { pub ix: InstrIx,