From a887d17a04cb48315bc01afd15474fe67daa2717 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Wed, 25 Oct 2023 17:27:35 +0200 Subject: [PATCH 1/2] fix: non-identifier properties not quoted --- src/transpiling/jsx_precompile.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/transpiling/jsx_precompile.rs b/src/transpiling/jsx_precompile.rs index 5d45ca1..316669d 100644 --- a/src/transpiling/jsx_precompile.rs +++ b/src/transpiling/jsx_precompile.rs @@ -520,7 +520,15 @@ impl JsxPrecompile { match attr { JSXAttrOrSpread::JSXAttr(jsx_attr) => { let attr_name = get_attr_name(jsx_attr, is_component); - let prop_name = PropName::Ident(quote_ident!(attr_name.clone())); + let prop_name = if attr_name.contains('-') { + PropName::Str(Str { + span: DUMMY_SP, + raw: None, + value: attr_name.clone().into(), + }) + } else { + PropName::Ident(quote_ident!(attr_name.clone())) + }; // Case: let Some(attr_value) = &jsx_attr.value else { @@ -1181,6 +1189,11 @@ const a = _jsxssr($$_tpl_1);"#, .as_str(), ); + let quoted = if mapping.1.contains('-') { + format!("\"{}\"", &mapping.1) + } else { + mapping.1.clone() + }; // should still be normalized if HTML element cannot // be serialized test_transform( @@ -1189,7 +1202,7 @@ const a = _jsxssr($$_tpl_1);"#, .as_str(), format!( "{}\nconst a = _jsx(\"label\", {{\n {}: \"foo\",\n ...foo\n}});", - "import { jsx as _jsx } from \"react/jsx-runtime\";", &mapping.1 + "import { jsx as _jsx } from \"react/jsx-runtime\";", quoted ) .as_str(), ); @@ -1286,6 +1299,19 @@ const a = _jsx("div", { ); } + #[test] + fn non_identiifer_attr_test() { + test_transform( + JsxPrecompile::default(), + r#"const a = ;"#, + r#"import { jsx as _jsx } from "react/jsx-runtime"; +const a = _jsx(Foo, { + "aria-label": "bar", + ...props +});"#, + ); + } + #[test] fn dangerously_html_test() { test_transform( From 863c1e4af7d6fc0cc15b3c7edbc05a3ca1469c2e Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Wed, 25 Oct 2023 19:11:35 +0200 Subject: [PATCH 2/2] fix: use swc's identifier checks --- src/transpiling/jsx_precompile.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/transpiling/jsx_precompile.rs b/src/transpiling/jsx_precompile.rs index 316669d..4d0ec31 100644 --- a/src/transpiling/jsx_precompile.rs +++ b/src/transpiling/jsx_precompile.rs @@ -1,5 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use crate::swc::parser::lexer::util::CharExt; use swc_common::DUMMY_SP; use swc_ecma_ast::*; use swc_ecma_utils::prepend_stmt; @@ -306,6 +307,18 @@ fn is_serializable(opening: &JSXOpeningElement) -> bool { } } +fn is_text_valid_identifier(string_value: &str) -> bool { + if string_value.is_empty() { + return false; + } + for (i, c) in string_value.chars().enumerate() { + if (i == 0 && !c.is_ident_start()) || !c.is_ident_part() { + return false; + } + } + true +} + fn string_lit_expr(str: String) -> Expr { Expr::Lit(Lit::Str(Str { span: DUMMY_SP, @@ -520,7 +533,7 @@ impl JsxPrecompile { match attr { JSXAttrOrSpread::JSXAttr(jsx_attr) => { let attr_name = get_attr_name(jsx_attr, is_component); - let prop_name = if attr_name.contains('-') { + let prop_name = if !is_text_valid_identifier(&attr_name) { PropName::Str(Str { span: DUMMY_SP, raw: None, @@ -1189,7 +1202,7 @@ const a = _jsxssr($$_tpl_1);"#, .as_str(), ); - let quoted = if mapping.1.contains('-') { + let quoted = if mapping.1.contains('-') || mapping.1.contains(':') { format!("\"{}\"", &mapping.1) } else { mapping.1.clone()