mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
improve(transformer): validate JSX pragma options (#3536)
Validate React JSX `pragma` and `pragma_frag` options. Don't allow: * empty string * `foo.bar.qux` * `foo.` * `.bar` * `.` If options provided are invalid, raise an error and use the defaults. Also fast path the defaults.
This commit is contained in:
parent
6506d086b3
commit
14c00a5c1d
3 changed files with 58 additions and 35 deletions
|
|
@ -6,6 +6,11 @@ pub fn pragma_and_pragma_frag_cannot_be_set() -> OxcDiagnostic {
|
|||
.with_help("Remove `pragma` and `pragmaFrag` options.")
|
||||
}
|
||||
|
||||
pub fn invalid_pragma() -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn("pragma and pragmaFrag must be of the form `foo` or `foo.bar`.")
|
||||
.with_help("Fix `pragma` and `pragmaFrag` options.")
|
||||
}
|
||||
|
||||
pub fn import_source_cannot_be_set() -> OxcDiagnostic {
|
||||
OxcDiagnostic::warn("importSource cannot be set when runtime is classic.")
|
||||
.with_help("Remove `importSource` option.")
|
||||
|
|
|
|||
|
|
@ -79,14 +79,42 @@ struct Pragma<'a> {
|
|||
}
|
||||
|
||||
impl<'a> Pragma<'a> {
|
||||
fn parse(pragma: &str, ast: &AstBuilder<'a>) -> Self {
|
||||
let mut parts = pragma.split('.');
|
||||
let object = ast.new_atom(parts.next().unwrap());
|
||||
let property = parts.next().map(|property| {
|
||||
assert!(parts.next().is_none(), "Invalid pragma");
|
||||
ast.new_atom(property)
|
||||
});
|
||||
Self { object, property }
|
||||
/// Parse `options.pragma` or `options.pragma_frag`.
|
||||
///
|
||||
/// If provided option is invalid, raise an error and use default.
|
||||
fn parse(pragma: Option<&String>, default_property_name: &'static str, ctx: &Ctx<'a>) -> Self {
|
||||
if let Some(pragma) = pragma {
|
||||
let mut parts = pragma.split('.');
|
||||
|
||||
let object_name = parts.next().unwrap();
|
||||
if object_name.is_empty() {
|
||||
return Self::invalid(default_property_name, ctx);
|
||||
}
|
||||
|
||||
let property = match parts.next() {
|
||||
Some(property_name) => {
|
||||
if property_name.is_empty() || parts.next().is_some() {
|
||||
return Self::invalid(default_property_name, ctx);
|
||||
}
|
||||
Some(ctx.ast.new_atom(property_name))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let object = ctx.ast.new_atom(object_name);
|
||||
Self { object, property }
|
||||
} else {
|
||||
Self::default(default_property_name)
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid(default_property_name: &'static str, ctx: &Ctx<'a>) -> Self {
|
||||
ctx.error(diagnostics::invalid_pragma());
|
||||
Self::default(default_property_name)
|
||||
}
|
||||
|
||||
fn default(default_property_name: &'static str) -> Self {
|
||||
Self { object: Atom::from("React"), property: Some(Atom::from(default_property_name)) }
|
||||
}
|
||||
|
||||
fn create_expression(&self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
|
||||
|
|
@ -119,11 +147,16 @@ impl<'a> ReactJsx<'a> {
|
|||
// Parse pragmas
|
||||
let (pragma, pragma_frag) = match options.runtime {
|
||||
ReactJsxRuntime::Classic => {
|
||||
let pragma = Pragma::parse(&options.pragma, &ctx.ast);
|
||||
let pragma_frag = Pragma::parse(&options.pragma_frag, &ctx.ast);
|
||||
let pragma = Pragma::parse(options.pragma.as_ref(), "createElement", ctx);
|
||||
let pragma_frag = Pragma::parse(options.pragma_frag.as_ref(), "Fragment", ctx);
|
||||
(Some(pragma), Some(pragma_frag))
|
||||
}
|
||||
ReactJsxRuntime::Automatic => (None, None),
|
||||
ReactJsxRuntime::Automatic => {
|
||||
if options.pragma.is_some() || options.pragma_frag.is_some() {
|
||||
ctx.error(diagnostics::pragma_and_pragma_frag_cannot_be_set());
|
||||
}
|
||||
(None, None)
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
|
|
@ -186,13 +219,6 @@ impl<'a> ReactJsx<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
if self.options.pragma != "React.createElement"
|
||||
|| self.options.pragma_frag != "React.Fragment"
|
||||
{
|
||||
self.ctx.error(diagnostics::pragma_and_pragma_frag_cannot_be_set());
|
||||
return;
|
||||
}
|
||||
|
||||
let imports = self.ctx.module_imports.get_import_statements();
|
||||
let mut index = program
|
||||
.body
|
||||
|
|
|
|||
|
|
@ -14,14 +14,6 @@ fn default_for_import_source() -> Cow<'static, str> {
|
|||
Cow::Borrowed("react")
|
||||
}
|
||||
|
||||
fn default_for_pragma() -> Cow<'static, str> {
|
||||
Cow::Borrowed("React.createElement")
|
||||
}
|
||||
|
||||
fn default_for_pragma_frag() -> Cow<'static, str> {
|
||||
Cow::Borrowed("React.Fragment")
|
||||
}
|
||||
|
||||
/// Decides which runtime to use.
|
||||
///
|
||||
/// Auto imports the functions that JSX transpiles to.
|
||||
|
|
@ -101,14 +93,14 @@ pub struct ReactOptions {
|
|||
/// Note that the @jsx React.DOM pragma has been deprecated as of React v0.12
|
||||
///
|
||||
/// Defaults to `React.createElement`.
|
||||
#[serde(default = "default_for_pragma")]
|
||||
pub pragma: Cow<'static, str>,
|
||||
#[serde(default)]
|
||||
pub pragma: Option<String>,
|
||||
|
||||
/// Replace the component used when compiling JSX fragments. It should be a valid JSX tag name.
|
||||
///
|
||||
/// Defaults to `React.Fragment`.
|
||||
#[serde(default = "default_for_pragma_frag")]
|
||||
pub pragma_frag: Cow<'static, str>,
|
||||
#[serde(default)]
|
||||
pub pragma_frag: Option<String>,
|
||||
|
||||
/// `useBuiltIns` is deprecated in Babel 8.
|
||||
///
|
||||
|
|
@ -133,8 +125,8 @@ impl Default for ReactOptions {
|
|||
throw_if_namespace: default_as_true(),
|
||||
pure: default_as_true(),
|
||||
import_source: default_for_import_source(),
|
||||
pragma: default_for_pragma(),
|
||||
pragma_frag: default_for_pragma_frag(),
|
||||
pragma: None,
|
||||
pragma_frag: None,
|
||||
use_built_ins: None,
|
||||
use_spread: None,
|
||||
}
|
||||
|
|
@ -187,20 +179,20 @@ impl ReactOptions {
|
|||
|
||||
// read jsxImportSource
|
||||
if let Some(import_source) = comment.strip_prefix("jsxImportSource").map(str::trim) {
|
||||
self.import_source = Cow::from(import_source.to_string());
|
||||
self.import_source = Cow::Owned(import_source.to_string());
|
||||
continue;
|
||||
}
|
||||
|
||||
// read jsxFrag
|
||||
if let Some(pragma_frag) = comment.strip_prefix("jsxFrag").map(str::trim) {
|
||||
self.pragma_frag = Cow::from(pragma_frag.to_string());
|
||||
self.pragma_frag = Some(pragma_frag.to_string());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Put this condition at the end to avoid breaking @jsxXX
|
||||
// read jsx
|
||||
if let Some(pragma) = comment.strip_prefix("jsx").map(str::trim) {
|
||||
self.pragma = Cow::from(pragma.to_string());
|
||||
self.pragma = Some(pragma.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue