swc-plugin-component-annotate 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -8
- package/src/constants.rs +125 -120
- package/src/jsx_utils.rs +60 -22
- package/src/lib.rs +137 -74
- package/swc_plugin_component_annotate.wasm +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swc-plugin-component-annotate",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Use SWC to automatically annotate React components with data attributes for component tracking",
|
|
5
5
|
"author": "scttcper <scttcper@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,11 +8,6 @@
|
|
|
8
8
|
"swc-plugin",
|
|
9
9
|
"swc"
|
|
10
10
|
],
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "cargo build --release --target wasm32-unknown-unknown",
|
|
13
|
-
"test": "cargo test",
|
|
14
|
-
"prepack": "cp -rf target/wasm32-unknown-unknown/release/swc_plugin_component_annotate.wasm ."
|
|
15
|
-
},
|
|
16
11
|
"main": "swc_plugin_component_annotate.wasm",
|
|
17
12
|
"files": [
|
|
18
13
|
"src",
|
|
@@ -28,7 +23,6 @@
|
|
|
28
23
|
},
|
|
29
24
|
"devDependencies": {},
|
|
30
25
|
"peerDependencies": {},
|
|
31
|
-
"packageManager": "pnpm@10.18.3",
|
|
32
26
|
"release": {
|
|
33
27
|
"branches": [
|
|
34
28
|
"main"
|
|
@@ -36,5 +30,9 @@
|
|
|
36
30
|
},
|
|
37
31
|
"publishConfig": {
|
|
38
32
|
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "cargo build --release --target wasm32-unknown-unknown",
|
|
36
|
+
"test": "cargo test"
|
|
39
37
|
}
|
|
40
|
-
}
|
|
38
|
+
}
|
package/src/constants.rs
CHANGED
|
@@ -1,125 +1,130 @@
|
|
|
1
1
|
use rustc_hash::FxHashSet;
|
|
2
|
+
use std::sync::OnceLock;
|
|
2
3
|
|
|
3
|
-
pub fn default_ignored_elements() -> FxHashSet<&'static str> {
|
|
4
|
-
|
|
5
|
-
let elements = [
|
|
6
|
-
"a",
|
|
7
|
-
"abbr",
|
|
8
|
-
"address",
|
|
9
|
-
"area",
|
|
10
|
-
"article",
|
|
11
|
-
"aside",
|
|
12
|
-
"audio",
|
|
13
|
-
"b",
|
|
14
|
-
"base",
|
|
15
|
-
"bdi",
|
|
16
|
-
"bdo",
|
|
17
|
-
"blockquote",
|
|
18
|
-
"body",
|
|
19
|
-
"br",
|
|
20
|
-
"button",
|
|
21
|
-
"canvas",
|
|
22
|
-
"caption",
|
|
23
|
-
"cite",
|
|
24
|
-
"code",
|
|
25
|
-
"col",
|
|
26
|
-
"colgroup",
|
|
27
|
-
"data",
|
|
28
|
-
"datalist",
|
|
29
|
-
"dd",
|
|
30
|
-
"del",
|
|
31
|
-
"details",
|
|
32
|
-
"dfn",
|
|
33
|
-
"dialog",
|
|
34
|
-
"div",
|
|
35
|
-
"dl",
|
|
36
|
-
"dt",
|
|
37
|
-
"em",
|
|
38
|
-
"embed",
|
|
39
|
-
"fieldset",
|
|
40
|
-
"figure",
|
|
41
|
-
"footer",
|
|
42
|
-
"form",
|
|
43
|
-
"h1",
|
|
44
|
-
"h2",
|
|
45
|
-
"h3",
|
|
46
|
-
"h4",
|
|
47
|
-
"h5",
|
|
48
|
-
"h6",
|
|
49
|
-
"head",
|
|
50
|
-
"header",
|
|
51
|
-
"hgroup",
|
|
52
|
-
"hr",
|
|
53
|
-
"html",
|
|
54
|
-
"i",
|
|
55
|
-
"iframe",
|
|
56
|
-
"img",
|
|
57
|
-
"input",
|
|
58
|
-
"ins",
|
|
59
|
-
"kbd",
|
|
60
|
-
"keygen",
|
|
61
|
-
"label",
|
|
62
|
-
"legend",
|
|
63
|
-
"li",
|
|
64
|
-
"link",
|
|
65
|
-
"main",
|
|
66
|
-
"map",
|
|
67
|
-
"mark",
|
|
68
|
-
"menu",
|
|
69
|
-
"menuitem",
|
|
70
|
-
"meter",
|
|
71
|
-
"nav",
|
|
72
|
-
"noscript",
|
|
73
|
-
"object",
|
|
74
|
-
"ol",
|
|
75
|
-
"optgroup",
|
|
76
|
-
"option",
|
|
77
|
-
"output",
|
|
78
|
-
"p",
|
|
79
|
-
"param",
|
|
80
|
-
"pre",
|
|
81
|
-
"progress",
|
|
82
|
-
"q",
|
|
83
|
-
"rb",
|
|
84
|
-
"rp",
|
|
85
|
-
"rt",
|
|
86
|
-
"rtc",
|
|
87
|
-
"ruby",
|
|
88
|
-
"s",
|
|
89
|
-
"samp",
|
|
90
|
-
"script",
|
|
91
|
-
"section",
|
|
92
|
-
"select",
|
|
93
|
-
"small",
|
|
94
|
-
"source",
|
|
95
|
-
"span",
|
|
96
|
-
"strong",
|
|
97
|
-
"style",
|
|
98
|
-
"sub",
|
|
99
|
-
"summary",
|
|
100
|
-
"sup",
|
|
101
|
-
"table",
|
|
102
|
-
"tbody",
|
|
103
|
-
"td",
|
|
104
|
-
"template",
|
|
105
|
-
"textarea",
|
|
106
|
-
"tfoot",
|
|
107
|
-
"th",
|
|
108
|
-
"thead",
|
|
109
|
-
"time",
|
|
110
|
-
"title",
|
|
111
|
-
"tr",
|
|
112
|
-
"track",
|
|
113
|
-
"u",
|
|
114
|
-
"ul",
|
|
115
|
-
"var",
|
|
116
|
-
"video",
|
|
117
|
-
"wbr",
|
|
118
|
-
];
|
|
4
|
+
pub fn default_ignored_elements() -> &'static FxHashSet<&'static str> {
|
|
5
|
+
static SET: OnceLock<FxHashSet<&'static str>> = OnceLock::new();
|
|
119
6
|
|
|
120
|
-
|
|
121
|
-
set
|
|
122
|
-
|
|
7
|
+
SET.get_or_init(|| {
|
|
8
|
+
let mut set = FxHashSet::default();
|
|
9
|
+
let elements = [
|
|
10
|
+
"a",
|
|
11
|
+
"abbr",
|
|
12
|
+
"address",
|
|
13
|
+
"area",
|
|
14
|
+
"article",
|
|
15
|
+
"aside",
|
|
16
|
+
"audio",
|
|
17
|
+
"b",
|
|
18
|
+
"base",
|
|
19
|
+
"bdi",
|
|
20
|
+
"bdo",
|
|
21
|
+
"blockquote",
|
|
22
|
+
"body",
|
|
23
|
+
"br",
|
|
24
|
+
"button",
|
|
25
|
+
"canvas",
|
|
26
|
+
"caption",
|
|
27
|
+
"cite",
|
|
28
|
+
"code",
|
|
29
|
+
"col",
|
|
30
|
+
"colgroup",
|
|
31
|
+
"data",
|
|
32
|
+
"datalist",
|
|
33
|
+
"dd",
|
|
34
|
+
"del",
|
|
35
|
+
"details",
|
|
36
|
+
"dfn",
|
|
37
|
+
"dialog",
|
|
38
|
+
"div",
|
|
39
|
+
"dl",
|
|
40
|
+
"dt",
|
|
41
|
+
"em",
|
|
42
|
+
"embed",
|
|
43
|
+
"fieldset",
|
|
44
|
+
"figure",
|
|
45
|
+
"footer",
|
|
46
|
+
"form",
|
|
47
|
+
"h1",
|
|
48
|
+
"h2",
|
|
49
|
+
"h3",
|
|
50
|
+
"h4",
|
|
51
|
+
"h5",
|
|
52
|
+
"h6",
|
|
53
|
+
"head",
|
|
54
|
+
"header",
|
|
55
|
+
"hgroup",
|
|
56
|
+
"hr",
|
|
57
|
+
"html",
|
|
58
|
+
"i",
|
|
59
|
+
"iframe",
|
|
60
|
+
"img",
|
|
61
|
+
"input",
|
|
62
|
+
"ins",
|
|
63
|
+
"kbd",
|
|
64
|
+
"keygen",
|
|
65
|
+
"label",
|
|
66
|
+
"legend",
|
|
67
|
+
"li",
|
|
68
|
+
"link",
|
|
69
|
+
"main",
|
|
70
|
+
"map",
|
|
71
|
+
"mark",
|
|
72
|
+
"menu",
|
|
73
|
+
"menuitem",
|
|
74
|
+
"meter",
|
|
75
|
+
"nav",
|
|
76
|
+
"noscript",
|
|
77
|
+
"object",
|
|
78
|
+
"ol",
|
|
79
|
+
"optgroup",
|
|
80
|
+
"option",
|
|
81
|
+
"output",
|
|
82
|
+
"p",
|
|
83
|
+
"param",
|
|
84
|
+
"pre",
|
|
85
|
+
"progress",
|
|
86
|
+
"q",
|
|
87
|
+
"rb",
|
|
88
|
+
"rp",
|
|
89
|
+
"rt",
|
|
90
|
+
"rtc",
|
|
91
|
+
"ruby",
|
|
92
|
+
"s",
|
|
93
|
+
"samp",
|
|
94
|
+
"script",
|
|
95
|
+
"section",
|
|
96
|
+
"select",
|
|
97
|
+
"small",
|
|
98
|
+
"source",
|
|
99
|
+
"span",
|
|
100
|
+
"strong",
|
|
101
|
+
"style",
|
|
102
|
+
"sub",
|
|
103
|
+
"summary",
|
|
104
|
+
"sup",
|
|
105
|
+
"table",
|
|
106
|
+
"tbody",
|
|
107
|
+
"td",
|
|
108
|
+
"template",
|
|
109
|
+
"textarea",
|
|
110
|
+
"tfoot",
|
|
111
|
+
"th",
|
|
112
|
+
"thead",
|
|
113
|
+
"time",
|
|
114
|
+
"title",
|
|
115
|
+
"tr",
|
|
116
|
+
"track",
|
|
117
|
+
"u",
|
|
118
|
+
"ul",
|
|
119
|
+
"var",
|
|
120
|
+
"video",
|
|
121
|
+
"wbr",
|
|
122
|
+
];
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
for element in elements {
|
|
125
|
+
set.insert(element);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
set
|
|
129
|
+
})
|
|
125
130
|
}
|
package/src/jsx_utils.rs
CHANGED
|
@@ -6,16 +6,14 @@ use swc_core::ecma::ast::*;
|
|
|
6
6
|
pub fn is_react_fragment(element: &JSXElementName) -> bool {
|
|
7
7
|
match element {
|
|
8
8
|
JSXElementName::Ident(ident) => ident.sym.as_ref() == "Fragment",
|
|
9
|
-
JSXElementName::JSXMemberExpr(member_expr) =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if obj.sym.as_ref() == "React"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
_ => false,
|
|
9
|
+
JSXElementName::JSXMemberExpr(member_expr) => matches!(
|
|
10
|
+
&member_expr.obj,
|
|
11
|
+
JSXObject::Ident(obj)
|
|
12
|
+
if obj.sym.as_ref() == "React" && member_expr.prop.sym.as_ref() == "Fragment"
|
|
13
|
+
),
|
|
14
|
+
JSXElementName::JSXNamespacedName(_) => false,
|
|
15
|
+
#[cfg(swc_ast_unknown)]
|
|
16
|
+
_ => panic!("unknown jsx element name"),
|
|
19
17
|
}
|
|
20
18
|
}
|
|
21
19
|
|
|
@@ -30,31 +28,49 @@ pub fn get_element_name(element: &JSXElementName) -> Cow<str> {
|
|
|
30
28
|
JSXElementName::JSXNamespacedName(namespaced) => {
|
|
31
29
|
Cow::Owned(format!("{}:{}", namespaced.ns.sym, namespaced.name.sym))
|
|
32
30
|
}
|
|
31
|
+
#[cfg(swc_ast_unknown)]
|
|
32
|
+
_ => panic!("unknown jsx element name"),
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/// Recursively build the name for member expressions (e.g., "Components.UI.Button")
|
|
37
37
|
fn get_member_expression_name(member_expr: &JSXMemberExpr) -> String {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
fn member_expression_name_len(member_expr: &JSXMemberExpr) -> usize {
|
|
39
|
+
let obj_len = match &member_expr.obj {
|
|
40
|
+
JSXObject::Ident(ident) => ident.sym.len(),
|
|
41
|
+
JSXObject::JSXMemberExpr(nested_member) => member_expression_name_len(nested_member),
|
|
42
|
+
#[cfg(swc_ast_unknown)]
|
|
43
|
+
_ => panic!("unknown jsx object"),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
obj_len + 1 + member_expr.prop.sym.len()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fn push_member_expression_name(target: &mut String, member_expr: &JSXMemberExpr) {
|
|
50
|
+
match &member_expr.obj {
|
|
51
|
+
JSXObject::Ident(ident) => target.push_str(ident.sym.as_ref()),
|
|
52
|
+
JSXObject::JSXMemberExpr(nested_member) => {
|
|
53
|
+
push_member_expression_name(target, nested_member);
|
|
54
|
+
}
|
|
55
|
+
#[cfg(swc_ast_unknown)]
|
|
56
|
+
_ => panic!("unknown jsx object"),
|
|
46
57
|
}
|
|
47
|
-
};
|
|
48
58
|
|
|
49
|
-
|
|
59
|
+
target.push('.');
|
|
60
|
+
target.push_str(member_expr.prop.sym.as_ref());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let mut output = String::with_capacity(member_expression_name_len(member_expr));
|
|
64
|
+
push_member_expression_name(&mut output, member_expr);
|
|
65
|
+
output
|
|
50
66
|
}
|
|
51
67
|
|
|
52
68
|
/// Check if a JSX element already has an attribute with the given name
|
|
53
69
|
#[inline]
|
|
54
70
|
pub fn has_attribute(element: &JSXOpeningElement, attr_name: &str) -> bool {
|
|
55
71
|
element.attrs.iter().any(|attr| {
|
|
56
|
-
matches!(attr, JSXAttrOrSpread::JSXAttr(jsx_attr)
|
|
57
|
-
if matches!(&jsx_attr.name, JSXAttrName::Ident(ident)
|
|
72
|
+
matches!(attr, JSXAttrOrSpread::JSXAttr(jsx_attr)
|
|
73
|
+
if matches!(&jsx_attr.name, JSXAttrName::Ident(ident)
|
|
58
74
|
if ident.sym.as_ref() == attr_name))
|
|
59
75
|
})
|
|
60
76
|
}
|
|
@@ -72,3 +88,25 @@ pub fn create_jsx_attr(name: &str, value: &str) -> JSXAttrOrSpread {
|
|
|
72
88
|
})),
|
|
73
89
|
})
|
|
74
90
|
}
|
|
91
|
+
|
|
92
|
+
#[inline]
|
|
93
|
+
pub fn create_jsx_attr_with_ident(name: &IdentName, value: &str) -> JSXAttrOrSpread {
|
|
94
|
+
JSXAttrOrSpread::JSXAttr(JSXAttr {
|
|
95
|
+
span: Default::default(),
|
|
96
|
+
name: JSXAttrName::Ident(name.clone()),
|
|
97
|
+
value: Some(JSXAttrValue::Str(Str {
|
|
98
|
+
span: Default::default(),
|
|
99
|
+
value: value.into(),
|
|
100
|
+
raw: None,
|
|
101
|
+
})),
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[inline]
|
|
106
|
+
pub fn create_jsx_attr_with_ident_and_str(name: &IdentName, value: &Str) -> JSXAttrOrSpread {
|
|
107
|
+
JSXAttrOrSpread::JSXAttr(JSXAttr {
|
|
108
|
+
span: Default::default(),
|
|
109
|
+
name: JSXAttrName::Ident(name.clone()),
|
|
110
|
+
value: Some(JSXAttrValue::Str(value.clone())),
|
|
111
|
+
})
|
|
112
|
+
}
|
package/src/lib.rs
CHANGED
|
@@ -8,7 +8,7 @@ use jsx_utils::*;
|
|
|
8
8
|
use path_utils::{extract_absolute_path, extract_filename};
|
|
9
9
|
use rustc_hash::FxHashSet;
|
|
10
10
|
use swc_core::{
|
|
11
|
-
common::FileName,
|
|
11
|
+
common::{FileName, DUMMY_SP},
|
|
12
12
|
ecma::{
|
|
13
13
|
ast::*,
|
|
14
14
|
visit::{noop_visit_mut_type, VisitMut, VisitMutWith},
|
|
@@ -21,31 +21,55 @@ use swc_core::{
|
|
|
21
21
|
|
|
22
22
|
pub struct ReactComponentAnnotateVisitor {
|
|
23
23
|
config: PluginConfig,
|
|
24
|
-
source_file_name: Option<
|
|
25
|
-
source_file_path: Option<
|
|
24
|
+
source_file_name: Option<Str>,
|
|
25
|
+
source_file_path: Option<Str>,
|
|
26
26
|
current_component_name: Option<String>,
|
|
27
|
-
ignored_elements: FxHashSet<&'static str>,
|
|
27
|
+
ignored_elements: &'static FxHashSet<&'static str>,
|
|
28
28
|
ignored_components_set: FxHashSet<String>,
|
|
29
|
+
component_attr_ident: IdentName,
|
|
30
|
+
element_attr_ident: IdentName,
|
|
31
|
+
source_file_attr_ident: IdentName,
|
|
32
|
+
source_path_attr_ident: Option<IdentName>,
|
|
29
33
|
/// Track the local identifier name for `styled` from @emotion/styled
|
|
30
34
|
styled_import: Option<String>,
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
impl ReactComponentAnnotateVisitor {
|
|
34
38
|
pub fn new(config: PluginConfig, filename: &FileName) -> Self {
|
|
35
|
-
let source_file_name = extract_filename(filename)
|
|
36
|
-
|
|
39
|
+
let source_file_name = extract_filename(filename).map(|value| Str {
|
|
40
|
+
span: DUMMY_SP,
|
|
41
|
+
value: value.into(),
|
|
42
|
+
raw: None,
|
|
43
|
+
});
|
|
44
|
+
let source_file_path = extract_absolute_path(filename).map(|value| Str {
|
|
45
|
+
span: DUMMY_SP,
|
|
46
|
+
value: value.into(),
|
|
47
|
+
raw: None,
|
|
48
|
+
});
|
|
37
49
|
|
|
38
50
|
// Pre-compute ignored components set for O(1) lookups
|
|
39
51
|
let ignored_components_set: FxHashSet<String> =
|
|
40
52
|
config.ignored_components.iter().cloned().collect();
|
|
53
|
+
let component_attr_ident = IdentName::new(config.component_attr_name().into(), DUMMY_SP);
|
|
54
|
+
let element_attr_ident = IdentName::new(config.element_attr_name().into(), DUMMY_SP);
|
|
55
|
+
let source_file_attr_ident =
|
|
56
|
+
IdentName::new(config.source_file_attr_name().into(), DUMMY_SP);
|
|
57
|
+
let source_path_attr_ident = config
|
|
58
|
+
.source_path_attr
|
|
59
|
+
.as_ref()
|
|
60
|
+
.map(|_| IdentName::new(config.source_path_attr_name().into(), DUMMY_SP));
|
|
41
61
|
|
|
42
62
|
Self {
|
|
63
|
+
component_attr_ident,
|
|
43
64
|
config,
|
|
65
|
+
element_attr_ident,
|
|
66
|
+
ignored_elements: constants::default_ignored_elements(),
|
|
67
|
+
ignored_components_set,
|
|
44
68
|
source_file_name,
|
|
69
|
+
source_file_attr_ident,
|
|
45
70
|
source_file_path,
|
|
71
|
+
source_path_attr_ident,
|
|
46
72
|
current_component_name: None,
|
|
47
|
-
ignored_elements: constants::default_ignored_elements(),
|
|
48
|
-
ignored_components_set,
|
|
49
73
|
styled_import: None,
|
|
50
74
|
}
|
|
51
75
|
}
|
|
@@ -86,6 +110,8 @@ impl ReactComponentAnnotateVisitor {
|
|
|
86
110
|
// Fragments are always transparent containers
|
|
87
111
|
jsx_fragment.visit_mut_with(self);
|
|
88
112
|
}
|
|
113
|
+
#[cfg(swc_ast_unknown)]
|
|
114
|
+
JSXElementChild::Unknown(..) => panic!("unknown jsx element child"),
|
|
89
115
|
_ => {}
|
|
90
116
|
}
|
|
91
117
|
}
|
|
@@ -101,6 +127,8 @@ impl ReactComponentAnnotateVisitor {
|
|
|
101
127
|
JSXElementChild::JSXFragment(jsx_fragment) => {
|
|
102
128
|
jsx_fragment.visit_mut_with(self);
|
|
103
129
|
}
|
|
130
|
+
#[cfg(swc_ast_unknown)]
|
|
131
|
+
JSXElementChild::Unknown(..) => panic!("unknown jsx element child"),
|
|
104
132
|
_ => {}
|
|
105
133
|
}
|
|
106
134
|
}
|
|
@@ -109,11 +137,6 @@ impl ReactComponentAnnotateVisitor {
|
|
|
109
137
|
fn add_attributes_to_element(&self, opening_element: &mut JSXOpeningElement) {
|
|
110
138
|
let element_name = get_element_name(&opening_element.name);
|
|
111
139
|
|
|
112
|
-
// Skip React fragments
|
|
113
|
-
if is_react_fragment(&opening_element.name) {
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
140
|
// Check if component should be ignored
|
|
118
141
|
if let Some(ref component_name) = self.current_component_name {
|
|
119
142
|
if self.should_ignore_component(component_name) {
|
|
@@ -121,58 +144,71 @@ impl ReactComponentAnnotateVisitor {
|
|
|
121
144
|
}
|
|
122
145
|
}
|
|
123
146
|
|
|
124
|
-
// Check if element should be ignored
|
|
125
147
|
if self.should_ignore_component(&element_name) {
|
|
126
148
|
return;
|
|
127
149
|
}
|
|
128
150
|
|
|
129
151
|
let is_ignored_html = self.should_ignore_element(&element_name);
|
|
130
|
-
|
|
131
|
-
// Add element attribute (for non-HTML elements or when component name differs)
|
|
132
|
-
if !is_ignored_html
|
|
152
|
+
let add_element_attr = !is_ignored_html
|
|
133
153
|
&& !has_attribute(opening_element, self.config.element_attr_name())
|
|
134
154
|
&& (self.config.component_attr_name() != self.config.element_attr_name()
|
|
135
|
-
|| self.current_component_name.is_none())
|
|
136
|
-
|
|
137
|
-
opening_element.
|
|
138
|
-
|
|
155
|
+
|| self.current_component_name.is_none());
|
|
156
|
+
let add_component_attr = self.current_component_name.is_some()
|
|
157
|
+
&& !has_attribute(opening_element, self.config.component_attr_name());
|
|
158
|
+
let add_source_file_attr = self.source_file_name.is_some()
|
|
159
|
+
&& (self.current_component_name.is_some() || !is_ignored_html)
|
|
160
|
+
&& !has_attribute(opening_element, self.config.source_file_attr_name());
|
|
161
|
+
let add_source_path_attr = self.source_file_path.is_some()
|
|
162
|
+
&& self.source_path_attr_ident.is_some()
|
|
163
|
+
&& (self.current_component_name.is_some() || !is_ignored_html)
|
|
164
|
+
&& !has_attribute(opening_element, self.config.source_path_attr_name());
|
|
165
|
+
|
|
166
|
+
let attr_count = usize::from(add_element_attr)
|
|
167
|
+
+ usize::from(add_component_attr)
|
|
168
|
+
+ usize::from(add_source_file_attr)
|
|
169
|
+
+ usize::from(add_source_path_attr);
|
|
170
|
+
|
|
171
|
+
if attr_count > 0 {
|
|
172
|
+
opening_element.attrs.reserve(attr_count);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if add_element_attr {
|
|
176
|
+
opening_element.attrs.push(create_jsx_attr_with_ident(
|
|
177
|
+
&self.element_attr_ident,
|
|
139
178
|
&element_name,
|
|
140
179
|
));
|
|
141
180
|
}
|
|
142
181
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
self.config.component_attr_name(),
|
|
182
|
+
if add_component_attr {
|
|
183
|
+
if let Some(ref component_name) = self.current_component_name {
|
|
184
|
+
opening_element.attrs.push(create_jsx_attr_with_ident(
|
|
185
|
+
&self.component_attr_ident,
|
|
148
186
|
component_name,
|
|
149
187
|
));
|
|
150
188
|
}
|
|
151
189
|
}
|
|
152
190
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
));
|
|
191
|
+
if add_source_file_attr {
|
|
192
|
+
if let Some(ref source_file) = self.source_file_name {
|
|
193
|
+
opening_element
|
|
194
|
+
.attrs
|
|
195
|
+
.push(create_jsx_attr_with_ident_and_str(
|
|
196
|
+
&self.source_file_attr_ident,
|
|
197
|
+
source_file,
|
|
198
|
+
));
|
|
162
199
|
}
|
|
163
200
|
}
|
|
164
201
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
202
|
+
if add_source_path_attr {
|
|
203
|
+
if let (Some(ref source_path), Some(ref source_path_attr_ident)) =
|
|
204
|
+
(&self.source_file_path, &self.source_path_attr_ident)
|
|
205
|
+
{
|
|
206
|
+
opening_element
|
|
207
|
+
.attrs
|
|
208
|
+
.push(create_jsx_attr_with_ident_and_str(
|
|
209
|
+
source_path_attr_ident,
|
|
173
210
|
source_path,
|
|
174
211
|
));
|
|
175
|
-
}
|
|
176
212
|
}
|
|
177
213
|
}
|
|
178
214
|
}
|
|
@@ -210,6 +246,8 @@ impl ReactComponentAnnotateVisitor {
|
|
|
210
246
|
Expr::Paren(paren_expr) => {
|
|
211
247
|
self.process_return_expression(&mut paren_expr.expr);
|
|
212
248
|
}
|
|
249
|
+
#[cfg(swc_ast_unknown)]
|
|
250
|
+
Expr::Unknown(..) => panic!("unknown expr"),
|
|
213
251
|
_ => {}
|
|
214
252
|
}
|
|
215
253
|
}
|
|
@@ -223,6 +261,8 @@ impl ReactComponentAnnotateVisitor {
|
|
|
223
261
|
let callee_name = match call_expr.callee.as_expr() {
|
|
224
262
|
Some(expr) => match expr.as_ref() {
|
|
225
263
|
Expr::Ident(ident) => ident.sym.as_ref(),
|
|
264
|
+
#[cfg(swc_ast_unknown)]
|
|
265
|
+
Expr::Unknown(..) => panic!("unknown expr"),
|
|
226
266
|
_ => return None,
|
|
227
267
|
},
|
|
228
268
|
_ => return None,
|
|
@@ -258,7 +298,12 @@ impl ReactComponentAnnotateVisitor {
|
|
|
258
298
|
});
|
|
259
299
|
|
|
260
300
|
// Build attributes in order: data attributes first, then spread
|
|
261
|
-
let mut attrs =
|
|
301
|
+
let mut attrs = Vec::with_capacity(
|
|
302
|
+
2 + usize::from(self.source_file_name.is_some())
|
|
303
|
+
+ usize::from(
|
|
304
|
+
self.source_path_attr_ident.is_some() && self.source_file_path.is_some(),
|
|
305
|
+
),
|
|
306
|
+
);
|
|
262
307
|
|
|
263
308
|
// Add data-element attribute using the styled component variable name
|
|
264
309
|
attrs.push(create_jsx_attr(
|
|
@@ -268,20 +313,20 @@ impl ReactComponentAnnotateVisitor {
|
|
|
268
313
|
|
|
269
314
|
// Add data-source-file attribute
|
|
270
315
|
if let Some(ref source_file) = self.source_file_name {
|
|
271
|
-
attrs.push(
|
|
272
|
-
self.
|
|
316
|
+
attrs.push(create_jsx_attr_with_ident_and_str(
|
|
317
|
+
&self.source_file_attr_ident,
|
|
273
318
|
source_file,
|
|
274
319
|
));
|
|
275
320
|
}
|
|
276
321
|
|
|
277
322
|
// Add data-source-path attribute (only if explicitly configured)
|
|
278
|
-
if
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
323
|
+
if let (Some(ref source_path), Some(ref source_path_attr_ident)) =
|
|
324
|
+
(&self.source_file_path, &self.source_path_attr_ident)
|
|
325
|
+
{
|
|
326
|
+
attrs.push(create_jsx_attr_with_ident_and_str(
|
|
327
|
+
source_path_attr_ident,
|
|
328
|
+
source_path,
|
|
329
|
+
));
|
|
285
330
|
}
|
|
286
331
|
|
|
287
332
|
// Add spread attribute AFTER data attributes: {...props}
|
|
@@ -352,16 +397,22 @@ impl VisitMut for ReactComponentAnnotateVisitor {
|
|
|
352
397
|
ImportSpecifier::Named(named_import) => {
|
|
353
398
|
// Check if the imported name is 'default' or 'styled'
|
|
354
399
|
let imported_name = match &named_import.imported {
|
|
355
|
-
Some(ModuleExportName::Ident(ident)) => ident.sym.as_ref(),
|
|
356
|
-
|
|
357
|
-
|
|
400
|
+
Some(ModuleExportName::Ident(ident)) => Some(ident.sym.as_ref()),
|
|
401
|
+
Some(ModuleExportName::Str(str)) => str.value.as_str(),
|
|
402
|
+
None => Some(named_import.local.sym.as_ref()),
|
|
403
|
+
#[cfg(swc_ast_unknown)]
|
|
404
|
+
Some(_) => panic!("unknown module export name"),
|
|
358
405
|
};
|
|
359
406
|
|
|
360
|
-
if imported_name
|
|
361
|
-
|
|
407
|
+
if let Some(imported_name) = imported_name {
|
|
408
|
+
if imported_name == "default" || imported_name == "styled" {
|
|
409
|
+
self.styled_import = Some(named_import.local.sym.to_string());
|
|
410
|
+
}
|
|
362
411
|
}
|
|
363
412
|
}
|
|
364
|
-
_ => {}
|
|
413
|
+
ImportSpecifier::Namespace(_) => {}
|
|
414
|
+
#[cfg(swc_ast_unknown)]
|
|
415
|
+
_ => panic!("unknown import specifier"),
|
|
365
416
|
}
|
|
366
417
|
}
|
|
367
418
|
}
|
|
@@ -416,6 +467,8 @@ impl VisitMut for ReactComponentAnnotateVisitor {
|
|
|
416
467
|
// Direct expression return
|
|
417
468
|
self.process_return_expression(expr);
|
|
418
469
|
}
|
|
470
|
+
#[cfg(swc_ast_unknown)]
|
|
471
|
+
_ => panic!("unknown block stmt or expr"),
|
|
419
472
|
}
|
|
420
473
|
|
|
421
474
|
self.current_component_name = None;
|
|
@@ -423,6 +476,8 @@ impl VisitMut for ReactComponentAnnotateVisitor {
|
|
|
423
476
|
Expr::Fn(func_expr) => {
|
|
424
477
|
self.find_jsx_in_function_body(&mut func_expr.function, component_name);
|
|
425
478
|
}
|
|
479
|
+
#[cfg(swc_ast_unknown)]
|
|
480
|
+
Expr::Unknown(..) => panic!("unknown expr"),
|
|
426
481
|
_ => {}
|
|
427
482
|
}
|
|
428
483
|
}
|
|
@@ -436,25 +491,33 @@ impl VisitMut for ReactComponentAnnotateVisitor {
|
|
|
436
491
|
|
|
437
492
|
// Look for render method
|
|
438
493
|
for member in &mut class_decl.class.body {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if let
|
|
449
|
-
|
|
494
|
+
match member {
|
|
495
|
+
ClassMember::Method(method) => match &method.key {
|
|
496
|
+
PropName::Ident(ident) => {
|
|
497
|
+
if ident.sym.as_ref() == "render" {
|
|
498
|
+
if let Some(body) = &mut method.function.body {
|
|
499
|
+
self.current_component_name = Some(component_name.clone());
|
|
500
|
+
|
|
501
|
+
// Look for return statements
|
|
502
|
+
for stmt in &mut body.stmts {
|
|
503
|
+
if let Stmt::Return(return_stmt) = stmt {
|
|
504
|
+
if let Some(arg) = &mut return_stmt.arg {
|
|
505
|
+
self.process_return_expression(arg);
|
|
506
|
+
}
|
|
450
507
|
}
|
|
451
508
|
}
|
|
452
|
-
}
|
|
453
509
|
|
|
454
|
-
|
|
510
|
+
self.current_component_name = None;
|
|
511
|
+
}
|
|
455
512
|
}
|
|
456
513
|
}
|
|
457
|
-
|
|
514
|
+
#[cfg(swc_ast_unknown)]
|
|
515
|
+
PropName::Unknown(..) => panic!("unknown prop name"),
|
|
516
|
+
_ => {}
|
|
517
|
+
},
|
|
518
|
+
#[cfg(swc_ast_unknown)]
|
|
519
|
+
ClassMember::Unknown(..) => panic!("unknown class member"),
|
|
520
|
+
_ => {}
|
|
458
521
|
}
|
|
459
522
|
}
|
|
460
523
|
|
|
Binary file
|