react_remove_properties/
lib.rs1use serde::Deserialize;
2use swc_cached::regex::CachedRegex;
3use swc_ecma_ast::*;
4use swc_ecma_visit::{fold_pass, noop_fold_type, Fold, FoldWith};
5
6#[derive(Clone, Debug, Deserialize)]
7#[serde(untagged)]
8pub enum Config {
9 All(bool),
10 WithOptions(Options),
11}
12
13impl Config {
14 pub fn truthy(&self) -> bool {
15 match self {
16 Config::All(b) => *b,
17 Config::WithOptions(_) => true,
18 }
19 }
20}
21
22#[derive(Clone, Debug, Deserialize)]
23pub struct Options {
24 #[serde(default)]
25 pub properties: Vec<String>,
26}
27
28pub fn react_remove_properties(config: Config) -> impl Pass {
29 let mut properties: Vec<CachedRegex> = match config {
30 Config::WithOptions(x) => x
31 .properties
32 .iter()
33 .map(|pattern| {
34 CachedRegex::new(pattern).unwrap_or_else(|e| {
35 panic!("error compiling property regex `{pattern}`: {e}");
36 })
37 })
38 .collect(),
39 _ => vec![],
40 };
41 if properties.is_empty() {
42 properties.push(CachedRegex::new(r"^data-test").unwrap());
44 }
45 fold_pass(RemoveProperties { properties })
46}
47
48struct RemoveProperties {
49 properties: Vec<CachedRegex>,
50}
51
52impl RemoveProperties {
53 fn should_remove_property(&self, name: &str) -> bool {
54 self.properties.iter().any(|p| p.is_match(name))
55 }
56}
57
58impl Fold for RemoveProperties {
59 noop_fold_type!();
60
61 fn fold_jsx_opening_element(&mut self, mut el: JSXOpeningElement) -> JSXOpeningElement {
62 el.attrs.retain(|attr| {
63 !matches!(attr, JSXAttrOrSpread::JSXAttr(JSXAttr {
64 name: JSXAttrName::Ident(ident),
65 ..
66 }) if self.should_remove_property(ident.sym.as_ref()))
67 });
68 el.fold_children_with(self)
69 }
70}