swc-plugin-component-annotate 1.13.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 +55 -23
- package/src/lib.rs +91 -54
- 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,15 +6,11 @@ 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
|
-
false
|
|
17
|
-
}
|
|
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
|
+
),
|
|
18
14
|
JSXElementName::JSXNamespacedName(_) => false,
|
|
19
15
|
#[cfg(swc_ast_unknown)]
|
|
20
16
|
_ => panic!("unknown jsx element name"),
|
|
@@ -39,28 +35,42 @@ pub fn get_element_name(element: &JSXElementName) -> Cow<str> {
|
|
|
39
35
|
|
|
40
36
|
/// Recursively build the name for member expressions (e.g., "Components.UI.Button")
|
|
41
37
|
fn get_member_expression_name(member_expr: &JSXMemberExpr) -> String {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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"),
|
|
50
57
|
}
|
|
51
|
-
#[cfg(swc_ast_unknown)]
|
|
52
|
-
_ => panic!("unknown jsx object"),
|
|
53
|
-
};
|
|
54
58
|
|
|
55
|
-
|
|
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
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
/// Check if a JSX element already has an attribute with the given name
|
|
59
69
|
#[inline]
|
|
60
70
|
pub fn has_attribute(element: &JSXOpeningElement, attr_name: &str) -> bool {
|
|
61
71
|
element.attrs.iter().any(|attr| {
|
|
62
|
-
matches!(attr, JSXAttrOrSpread::JSXAttr(jsx_attr)
|
|
63
|
-
if matches!(&jsx_attr.name, JSXAttrName::Ident(ident)
|
|
72
|
+
matches!(attr, JSXAttrOrSpread::JSXAttr(jsx_attr)
|
|
73
|
+
if matches!(&jsx_attr.name, JSXAttrName::Ident(ident)
|
|
64
74
|
if ident.sym.as_ref() == attr_name))
|
|
65
75
|
})
|
|
66
76
|
}
|
|
@@ -78,3 +88,25 @@ pub fn create_jsx_attr(name: &str, value: &str) -> JSXAttrOrSpread {
|
|
|
78
88
|
})),
|
|
79
89
|
})
|
|
80
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
|
}
|
|
@@ -113,11 +137,6 @@ impl ReactComponentAnnotateVisitor {
|
|
|
113
137
|
fn add_attributes_to_element(&self, opening_element: &mut JSXOpeningElement) {
|
|
114
138
|
let element_name = get_element_name(&opening_element.name);
|
|
115
139
|
|
|
116
|
-
// Skip React fragments
|
|
117
|
-
if is_react_fragment(&opening_element.name) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
140
|
// Check if component should be ignored
|
|
122
141
|
if let Some(ref component_name) = self.current_component_name {
|
|
123
142
|
if self.should_ignore_component(component_name) {
|
|
@@ -125,58 +144,71 @@ impl ReactComponentAnnotateVisitor {
|
|
|
125
144
|
}
|
|
126
145
|
}
|
|
127
146
|
|
|
128
|
-
// Check if element should be ignored
|
|
129
147
|
if self.should_ignore_component(&element_name) {
|
|
130
148
|
return;
|
|
131
149
|
}
|
|
132
150
|
|
|
133
151
|
let is_ignored_html = self.should_ignore_element(&element_name);
|
|
134
|
-
|
|
135
|
-
// Add element attribute (for non-HTML elements or when component name differs)
|
|
136
|
-
if !is_ignored_html
|
|
152
|
+
let add_element_attr = !is_ignored_html
|
|
137
153
|
&& !has_attribute(opening_element, self.config.element_attr_name())
|
|
138
154
|
&& (self.config.component_attr_name() != self.config.element_attr_name()
|
|
139
|
-
|| self.current_component_name.is_none())
|
|
140
|
-
|
|
141
|
-
opening_element.
|
|
142
|
-
|
|
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,
|
|
143
178
|
&element_name,
|
|
144
179
|
));
|
|
145
180
|
}
|
|
146
181
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
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,
|
|
152
186
|
component_name,
|
|
153
187
|
));
|
|
154
188
|
}
|
|
155
189
|
}
|
|
156
190
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
));
|
|
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
|
+
));
|
|
166
199
|
}
|
|
167
200
|
}
|
|
168
201
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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,
|
|
177
210
|
source_path,
|
|
178
211
|
));
|
|
179
|
-
}
|
|
180
212
|
}
|
|
181
213
|
}
|
|
182
214
|
}
|
|
@@ -266,7 +298,12 @@ impl ReactComponentAnnotateVisitor {
|
|
|
266
298
|
});
|
|
267
299
|
|
|
268
300
|
// Build attributes in order: data attributes first, then spread
|
|
269
|
-
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
|
+
);
|
|
270
307
|
|
|
271
308
|
// Add data-element attribute using the styled component variable name
|
|
272
309
|
attrs.push(create_jsx_attr(
|
|
@@ -276,20 +313,20 @@ impl ReactComponentAnnotateVisitor {
|
|
|
276
313
|
|
|
277
314
|
// Add data-source-file attribute
|
|
278
315
|
if let Some(ref source_file) = self.source_file_name {
|
|
279
|
-
attrs.push(
|
|
280
|
-
self.
|
|
316
|
+
attrs.push(create_jsx_attr_with_ident_and_str(
|
|
317
|
+
&self.source_file_attr_ident,
|
|
281
318
|
source_file,
|
|
282
319
|
));
|
|
283
320
|
}
|
|
284
321
|
|
|
285
322
|
// Add data-source-path attribute (only if explicitly configured)
|
|
286
|
-
if
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
+
));
|
|
293
330
|
}
|
|
294
331
|
|
|
295
332
|
// Add spread attribute AFTER data attributes: {...props}
|
|
File without changes
|