From c211f1e57f19e34d78868ae5cb2c4bae3d7e3d97 Mon Sep 17 00:00:00 2001 From: Boshen Date: Sun, 14 Apr 2024 21:42:32 +0800 Subject: [PATCH] feat(transformer): add diagnostics to react transform (#2974) --- .../src/react/jsx/diagnostics.rs | 30 +++++++++++++++++++ crates/oxc_transformer/src/react/jsx/mod.rs | 29 +++++++++++------- crates/oxc_transformer/src/react/options.rs | 7 +++++ tasks/transform_conformance/babel.snap.md | 16 ++-------- 4 files changed, 57 insertions(+), 25 deletions(-) create mode 100644 crates/oxc_transformer/src/react/jsx/diagnostics.rs diff --git a/crates/oxc_transformer/src/react/jsx/diagnostics.rs b/crates/oxc_transformer/src/react/jsx/diagnostics.rs new file mode 100644 index 0000000000000..2eff220081a78 --- /dev/null +++ b/crates/oxc_transformer/src/react/jsx/diagnostics.rs @@ -0,0 +1,30 @@ +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_span::Span; + +#[derive(Debug, Error, Diagnostic)] +#[error("pragma and pragmaFrag cannot be set when runtime is automatic.")] +#[diagnostic(severity(warning), help("Remove `pragma` and `pragmaFrag` options."))] +pub struct PragmaAndPragmaFragCannotBeSet; + +#[derive(Debug, Error, Diagnostic)] +#[error("importSource cannot be set when runtime is classic.")] +#[diagnostic(severity(warning), help("Remove `importSource` option."))] +pub struct ImportSourceCannotBeSet; + +#[derive(Debug, Error, Diagnostic)] +#[error("Namespace tags are not supported by default. React's JSX doesn't support namespace tags. You can set `throwIfNamespace: false` to bypass this warning.")] +#[diagnostic(severity(warning))] +pub struct NamespaceDoesNotSupport(#[label] pub Span); + +#[derive(Debug, Error, Diagnostic)] +#[error("Please provide an explicit key value. Using \"key\" as a shorthand for \"key={{true}}\" is not allowed.")] +#[diagnostic(severity(warning))] +pub struct ValuelessKey(#[label] pub Span); + +#[derive(Debug, Error, Diagnostic)] +#[error("Spread children are not supported in React.")] +#[diagnostic(severity(warning))] +pub struct SpreadChildrenAreNotSupported(#[label] pub Span); diff --git a/crates/oxc_transformer/src/react/jsx/mod.rs b/crates/oxc_transformer/src/react/jsx/mod.rs index d2d1506d60f85..ba80ad9bd595e 100644 --- a/crates/oxc_transformer/src/react/jsx/mod.rs +++ b/crates/oxc_transformer/src/react/jsx/mod.rs @@ -1,8 +1,10 @@ +mod diagnostics; + use std::rc::Rc; use oxc_allocator::Vec; use oxc_ast::{ast::*, AstBuilder}; -use oxc_span::{CompactStr, SPAN}; +use oxc_span::{CompactStr, GetSpan, SPAN}; use oxc_syntax::{ identifier::{is_irregular_whitespace, is_line_terminator}, xml_entities::XML_ENTITIES, @@ -16,6 +18,11 @@ pub use super::{ options::{ReactJsxRuntime, ReactOptions}, }; +use self::diagnostics::{ + ImportSourceCannotBeSet, NamespaceDoesNotSupport, PragmaAndPragmaFragCannotBeSet, + SpreadChildrenAreNotSupported, ValuelessKey, +}; + /// [plugin-transform-react-jsx](https://babeljs.io/docs/babel-plugin-transform-react-jsx) /// /// This plugin generates production-ready JS code. @@ -99,7 +106,7 @@ impl<'a> ReactJsx<'a> { pub fn add_runtime_imports(&mut self, program: &mut Program<'a>) { if self.options.runtime.is_classic() { if self.options.import_source != "react" { - // self.ctx.error(ImportSourceCannotBeSet); + self.ctx.error(ImportSourceCannotBeSet); } return; } @@ -107,7 +114,7 @@ impl<'a> ReactJsx<'a> { if self.options.pragma != "React.createElement" || self.options.pragma_frag != "React.Fragment" { - // self.ctx.error(PragmaAndPragmaFragCannotBeSet); + self.ctx.error(PragmaAndPragmaFragCannotBeSet); return; } @@ -332,9 +339,9 @@ impl<'a> ReactJsx<'a> { } } JSXAttributeItem::Attribute(attr) if attr.is_key() => { - // if attr.value.is_none() { - // self.ctx.error(ValuelessKey(attr.name.span())); - // } + if attr.value.is_none() { + self.ctx.error(ValuelessKey(attr.name.span())); + } // In automatic mode, extract the key before spread prop, // and add it to the third argument later. if is_automatic && !has_key_after_props_spread { @@ -431,9 +438,9 @@ impl<'a> ReactJsx<'a> { self.transform_jsx_member_expression(member_expr) } JSXElementName::NamespacedName(name) => { - // if self.options.throw_if_namespace { - // self.ctx.error(NamespaceDoesNotSupport(name.span)); - // } + if self.options.throw_if_namespace { + self.ctx.error(NamespaceDoesNotSupport(name.span)); + } let name = self.ast().new_atom(&name.to_string()); let string_literal = StringLiteral::new(SPAN, name); self.ast().literal_string_expression(string_literal) @@ -610,8 +617,8 @@ impl<'a> ReactJsx<'a> { }, JSXChild::Element(e) => Some(self.transform_jsx(&JSXElementOrFragment::Element(e))), JSXChild::Fragment(e) => Some(self.transform_jsx(&JSXElementOrFragment::Fragment(e))), - JSXChild::Spread(_e) => { - // self.ctx.error(SpreadChildrenAreNotSupported(e.span)); + JSXChild::Spread(e) => { + self.ctx.error(SpreadChildrenAreNotSupported(e.span)); None } } diff --git a/crates/oxc_transformer/src/react/options.rs b/crates/oxc_transformer/src/react/options.rs index 50125bdf50f4d..eede23090ee98 100644 --- a/crates/oxc_transformer/src/react/options.rs +++ b/crates/oxc_transformer/src/react/options.rs @@ -46,6 +46,12 @@ pub struct ReactOptions { #[serde(default = "default_as_true")] pub development: bool, + /// Toggles whether or not to throw an error if a XML namespaced tag name is used. + /// + /// Though the JSX spec allows this, it is disabled by default since React's JSX does not currently have support for it. + #[serde(default = "default_as_true")] + pub throw_if_namespace: bool, + /// Enables `@babel/plugin-transform-react-pure-annotations`. /// /// It will mark top-level React method calls as pure for tree shaking. @@ -100,6 +106,7 @@ impl Default for ReactOptions { jsx_source_plugin: true, runtime: ReactJsxRuntime::default(), development: default_as_true(), + throw_if_namespace: default_as_true(), pure: default_as_true(), import_source: default_for_import_source(), pragma: default_for_pragma(), diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index 08bf0d234e130..44c436c0366eb 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 191/415 +Passed: 203/415 # All Passed: * babel-plugin-transform-react-jsx-source @@ -141,20 +141,17 @@ Passed: 191/415 * regression/another-preset-with-custom-jsx-keep-source-self/input.mjs * regression/runtime-classic-allow-multiple-source-self/input.mjs -# babel-plugin-transform-react-jsx (100/163) +# babel-plugin-transform-react-jsx (112/163) * autoImport/auto-import-react-source-type-module/input.js * autoImport/complicated-scope-module/input.js * autoImport/import-source-pragma/input.js * autoImport/react-defined/input.js * pure/false-pragma-comment-automatic-runtime/input.js * pure/false-pragma-comment-classic-runtime/input.js -* pure/false-pragma-option-automatic-runtime/input.js * pure/true-pragma-comment-automatic-runtime/input.js * pure/true-pragma-comment-classic-runtime/input.js -* pure/true-pragma-option-automatic-runtime/input.js * pure/unset-pragma-comment-automatic-runtime/input.js * pure/unset-pragma-comment-classic-runtime/input.js -* pure/unset-pragma-option-automatic-runtime/input.js * react/adds-appropriate-newlines-when-using-spread-attribute-babel-7/input.js * react/arrow-functions/input.js * react/assignment-babel-7/input.js @@ -169,10 +166,6 @@ Passed: 191/415 * react/should-allow-jsx-docs-comment-with-pragma/input.js * react/should-allow-no-pragmafrag-if-frag-unused/input.js * react/should-allow-pragmafrag-and-frag/input.js -* react/should-disallow-spread-children/input.js -* react/should-disallow-valueless-key/input.js -* react/should-disallow-xml-namespacing/input.js -* react/should-throw-error-namespaces-if-not-flag/input.js * react/should-warn-when-importSource-is-set/input.js * react/should-warn-when-importSource-pragma-is-set/input.js * react/wraps-props-in-react-spread-for-first-spread-attributes-babel-7/input.js @@ -188,17 +181,12 @@ Passed: 191/415 * react-automatic/pragma-works-with-no-space-at-the-end/input.js * react-automatic/should-add-quotes-es3/input.js * react-automatic/should-allow-nested-fragments/input.js -* react-automatic/should-disallow-spread-children/input.js -* react-automatic/should-disallow-valueless-key/input.js -* react-automatic/should-disallow-xml-namespacing/input.js * react-automatic/should-escape-xhtml-jsxtext/input.js * react-automatic/should-escape-xhtml-jsxtext-babel-7/input.js * react-automatic/should-handle-attributed-elements/input.js * react-automatic/should-have-correct-comma-in-nested-children/input.js * react-automatic/should-properly-handle-keys/input.js -* react-automatic/should-throw-error-namespaces-if-not-flag/input.js * react-automatic/should-throw-when-filter-is-specified/input.js -* react-automatic/should-warn-when-pragma-or-pragmaFrag-is-set/input.js * regression/issue-12478-automatic/input.js * regression/issue-12478-classic/input.js * regression/pragma-frag-set-default-classic-runtime/input.js