@dynatrace/react-native-plugin 2.333.1 → 2.335.1
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/README.md +47 -178
- package/android/build.gradle +1 -1
- package/files/plugin-runtime.gradle +27 -13
- package/files/plugin.gradle +1 -1
- package/instrumentation/BabelPluginDynatrace.js +1 -0
- package/instrumentation/DynatraceInstrumentation.js +1 -1
- package/instrumentation/jsx/JsxRuntime.js +4 -4
- package/instrumentation/libs/UserInteraction.js +114 -0
- package/instrumentation/libs/community/gesture-handler/Touchables.InstrInfo.js +2 -0
- package/instrumentation/libs/community/gesture-handler/Touchables.js +3 -1
- package/instrumentation/libs/community/gesture-handler/index.js +3 -1
- package/instrumentation/libs/withOnPressMonitoring.js +6 -0
- package/lib/dynatrace-reporter.js +0 -14
- package/lib/dynatrace-transformer.js +10 -13
- package/lib/features/ui-interaction/Config.js +8 -2
- package/lib/features/ui-interaction/Plugin.Fragment.Test.js +170 -0
- package/lib/features/ui-interaction/Plugin.js +226 -882
- package/lib/features/ui-interaction/Run.js +11 -7
- package/lib/features/ui-interaction/Runtime.js +229 -896
- package/lib/features/ui-interaction/TouchMetaResolver.js +492 -0
- package/lib/features/ui-interaction/Types.js +1 -62
- package/package.json +6 -9
- package/react-native-dynatrace.podspec +1 -1
- package/scripts/Android.js +75 -62
- package/scripts/Config.js +11 -1
- package/scripts/core/InstrumentCall.js +1 -2
- package/scripts/core/LineOffsetAnalyzeCall.js +9 -15
- package/scripts/util/ReactOptions.js +0 -21
|
@@ -1,74 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
const fs = require("fs");
|
|
5
|
-
const path = require("path");
|
|
3
|
+
exports.applyDTPathIdInstrumentation = void 0;
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
6
|
const micromatch_1 = require("micromatch");
|
|
7
7
|
const jsc = require("jscodeshift");
|
|
8
8
|
const Types_1 = require("./Types");
|
|
9
|
-
const STOP_IDS_BASE = Object.values(Types_1.StopId).filter((x) => x !== Types_1.StopId.Segment && x !== Types_1.StopId.TabButton);
|
|
10
|
-
const STOP_MEMBER_BASE = Object.values(Types_1.StopMemberBase);
|
|
11
|
-
const TAB_BASE = Object.values(Types_1.TabBase);
|
|
12
|
-
const STOP_MEMBER_PROPS = Object.values(Types_1.StopMemberProp);
|
|
13
|
-
const DANGEROUS_WRAP_DENY = Object.values(Types_1.DangerousWrapDeny);
|
|
14
|
-
const PRESS_KEYS = Object.values(Types_1.PressKey);
|
|
15
|
-
const NODE_MODULES_UI_TAGS = [
|
|
16
|
-
'View',
|
|
17
|
-
'Text',
|
|
18
|
-
'Image',
|
|
19
|
-
'ScrollView',
|
|
20
|
-
'FlatList',
|
|
21
|
-
'SectionList',
|
|
22
|
-
'Pressable',
|
|
23
|
-
'TouchableOpacity',
|
|
24
|
-
'TouchableHighlight',
|
|
25
|
-
'TouchableWithoutFeedback',
|
|
26
|
-
'TouchableNativeFeedback',
|
|
27
|
-
'Modal',
|
|
28
|
-
'SafeAreaView',
|
|
29
|
-
'PlatformPressable',
|
|
30
|
-
];
|
|
31
|
-
const PATH_PREFIX_DEFAULT_NAME = 'PathPrefix';
|
|
32
|
-
const isPathInside = (baseDir, candidatePath) => {
|
|
33
|
-
const resolvedBase = path.resolve(baseDir);
|
|
34
|
-
const resolvedCandidate = path.resolve(candidatePath);
|
|
35
|
-
return (resolvedCandidate === resolvedBase ||
|
|
36
|
-
resolvedCandidate.startsWith(`${resolvedBase}${path.sep}`));
|
|
37
|
-
};
|
|
38
|
-
var GestureParentName;
|
|
39
|
-
(function (GestureParentName) {
|
|
40
|
-
GestureParentName["PanGestureHandler"] = "PanGestureHandler";
|
|
41
|
-
GestureParentName["TapGestureHandler"] = "TapGestureHandler";
|
|
42
|
-
GestureParentName["FlingGestureHandler"] = "FlingGestureHandler";
|
|
43
|
-
GestureParentName["NativeViewGestureHandler"] = "NativeViewGestureHandler";
|
|
44
|
-
GestureParentName["LongPressGestureHandler"] = "LongPressGestureHandler";
|
|
45
|
-
GestureParentName["RotationGestureHandler"] = "RotationGestureHandler";
|
|
46
|
-
GestureParentName["PinchGestureHandler"] = "PinchGestureHandler";
|
|
47
|
-
GestureParentName["ForceTouchGestureHandler"] = "ForceTouchGestureHandler";
|
|
48
|
-
GestureParentName["GestureDetector"] = "GestureDetector";
|
|
49
|
-
})(GestureParentName || (GestureParentName = {}));
|
|
50
|
-
var ChildWrapOnly;
|
|
51
|
-
(function (ChildWrapOnly) {
|
|
52
|
-
ChildWrapOnly["TouchableOpacity"] = "TouchableOpacity";
|
|
53
|
-
ChildWrapOnly["TouchableHighlight"] = "TouchableHighlight";
|
|
54
|
-
})(ChildWrapOnly || (ChildWrapOnly = {}));
|
|
55
|
-
const TAP_HANDLER_OVERLAY_TARGETS = [
|
|
56
|
-
{
|
|
57
|
-
pkg: 'react-native-gesture-handler',
|
|
58
|
-
fileSubstring: 'DrawerLayout',
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
pkg: '@react-navigation/drawer',
|
|
62
|
-
fileSubstring: '@react-navigation/drawer',
|
|
63
|
-
},
|
|
64
|
-
];
|
|
65
|
-
const shouldInstrumentTapHandlerInFile = (state, pkg) => {
|
|
66
|
-
if (!pkg) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
const rel = path.relative(process.cwd(), state.file).replace(/\\/g, '/');
|
|
70
|
-
return TAP_HANDLER_OVERLAY_TARGETS.some((t) => t.pkg === pkg && rel.includes(t.fileSubstring));
|
|
71
|
-
};
|
|
72
9
|
const getName = (j, n) => {
|
|
73
10
|
if (j.JSXIdentifier.check(n)) {
|
|
74
11
|
return n.name;
|
|
@@ -82,864 +19,271 @@ const getName = (j, n) => {
|
|
|
82
19
|
}
|
|
83
20
|
return 'Anonymous';
|
|
84
21
|
};
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const isTabScreen = (j, el) => {
|
|
107
|
-
if (!j.JSXMemberExpression.check(el.name)) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
const { base, prop } = getBaseAndPropFromMemberExpression(j, el.name);
|
|
111
|
-
return !!(base &&
|
|
112
|
-
TAB_BASE.includes(base) &&
|
|
113
|
-
prop === Types_1.StopMemberProp.Screen);
|
|
114
|
-
};
|
|
115
|
-
const isStoppedElementName = (name, segmentName, tabButtonName, rootName) => name === Types_1.Literals.ReactFragmentFQN ||
|
|
116
|
-
STOP_IDS_BASE.includes(name) ||
|
|
117
|
-
name === segmentName ||
|
|
118
|
-
name === tabButtonName ||
|
|
119
|
-
name === rootName;
|
|
120
|
-
const ensureNamedImport = (j, root, from, local) => {
|
|
121
|
-
const has = root
|
|
122
|
-
.find(j.ImportDeclaration, { source: { value: from } })
|
|
123
|
-
.filter((p) => (p.node.specifiers || []).some((s) => {
|
|
124
|
-
var _a;
|
|
125
|
-
return j.ImportSpecifier.check(s) &&
|
|
126
|
-
((_a = (j.Identifier.check(s.local)
|
|
127
|
-
? s.local.name
|
|
128
|
-
: undefined)) !== null && _a !== void 0 ? _a : (j.Identifier.check(s.imported)
|
|
129
|
-
? s.imported.name
|
|
130
|
-
: undefined)) === local;
|
|
131
|
-
}))
|
|
132
|
-
.size() > 0;
|
|
133
|
-
if (has) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const decl = j.importDeclaration([j.importSpecifier(j.identifier(local))], j.stringLiteral(from));
|
|
137
|
-
const first = root.find(j.ImportDeclaration).at(0);
|
|
138
|
-
if (first.size() > 0) {
|
|
139
|
-
first.insertBefore(decl);
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
root.get().node.program.body.unshift(decl);
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
const hasNamedImportBinding = (j, root, local) => root
|
|
146
|
-
.find(j.ImportDeclaration)
|
|
147
|
-
.filter((p) => (p.node.specifiers || []).some((s) => {
|
|
148
|
-
var _a;
|
|
149
|
-
return j.ImportSpecifier.check(s) &&
|
|
150
|
-
((_a = (j.Identifier.check(s.local) ? s.local.name : undefined)) !== null && _a !== void 0 ? _a : (j.Identifier.check(s.imported)
|
|
151
|
-
? s.imported.name
|
|
152
|
-
: undefined)) === local;
|
|
153
|
-
}))
|
|
154
|
-
.size() > 0;
|
|
155
|
-
const shouldSkipFile = (file, opts) => {
|
|
156
|
-
if (!(file !== null && file !== void 0 ? file : '')) {
|
|
157
|
-
return true;
|
|
158
|
-
}
|
|
159
|
-
const rel = path.relative(process.cwd(), file);
|
|
160
|
-
const relUnix = rel.replace(/\\/g, '/');
|
|
161
|
-
if (rel.includes(`dt${path.sep}runtime${path.sep}`)) {
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
const isNodeModulesFile = rel.includes(`node_modules${path.sep}`);
|
|
165
|
-
const isDrawerModule = rel.includes(`node_modules${path.sep}@react-navigation${path.sep}drawer`);
|
|
166
|
-
const includePatterns = opts === null || opts === void 0 ? void 0 : opts.include;
|
|
167
|
-
const excludePatterns = opts === null || opts === void 0 ? void 0 : opts.exclude;
|
|
168
|
-
if (!isNodeModulesFile && !isDrawerModule) {
|
|
169
|
-
return ((!!includePatterns && !(0, micromatch_1.isMatch)(relUnix, includePatterns)) ||
|
|
170
|
-
(!!excludePatterns && (0, micromatch_1.isMatch)(relUnix, excludePatterns)));
|
|
171
|
-
}
|
|
172
|
-
return false;
|
|
173
|
-
};
|
|
174
|
-
const valueToExpression = (j, value) => {
|
|
175
|
-
if (value === null) {
|
|
176
|
-
return j.nullLiteral();
|
|
177
|
-
}
|
|
178
|
-
const t = typeof value;
|
|
179
|
-
if (t === 'string') {
|
|
180
|
-
return j.stringLiteral(value);
|
|
181
|
-
}
|
|
182
|
-
if (t === 'number') {
|
|
183
|
-
return j.numericLiteral(value);
|
|
184
|
-
}
|
|
185
|
-
if (t === 'boolean') {
|
|
186
|
-
return j.booleanLiteral(value);
|
|
187
|
-
}
|
|
188
|
-
if (Array.isArray(value)) {
|
|
189
|
-
return j.arrayExpression(value.map((x) => valueToExpression(j, x)));
|
|
190
|
-
}
|
|
191
|
-
if (t === 'object') {
|
|
192
|
-
return j.objectExpression(Object.keys(value).map((k) => j.objectProperty(j.identifier(k), valueToExpression(j, value[k]))));
|
|
193
|
-
}
|
|
194
|
-
return j.nullLiteral();
|
|
195
|
-
};
|
|
196
|
-
const injectGlobalPrefixOnce = (j, root, pref) => {
|
|
197
|
-
if (!pref || !Array.isArray(pref) || pref.length === 0) {
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
const prefixIdent = j.identifier('__AN_PREFIX');
|
|
201
|
-
const stmt = j.expressionStatement(j.logicalExpression('&&', j.binaryExpression('!==', j.unaryExpression('typeof', j.identifier('globalThis')), j.literal('undefined')), j.logicalExpression('||', j.memberExpression(j.identifier('globalThis'), prefixIdent), j.assignmentExpression('=', j.memberExpression(j.identifier('globalThis'), prefixIdent), valueToExpression(j, pref)))));
|
|
202
|
-
root.get().node.program.body.unshift(stmt);
|
|
203
|
-
};
|
|
204
|
-
const parentIsNavigator = (j, p) => {
|
|
205
|
-
const par = p.parentPath;
|
|
206
|
-
if (!par || !par.value || !j.JSXElement.check(par.value)) {
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
const op = par.value.openingElement;
|
|
210
|
-
if (!j.JSXMemberExpression.check(op.name)) {
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
return (j.JSXIdentifier.check(op.name.property) &&
|
|
214
|
-
op.name.property.name === Types_1.Literals.NavigatorProp);
|
|
215
|
-
};
|
|
216
|
-
const makeUniqueSuffix = (s) => {
|
|
217
|
-
const ts = Date.now().toString(36);
|
|
218
|
-
const rand = Math.random().toString(36).slice(2, 8);
|
|
219
|
-
const seq = (s.seq++).toString(36);
|
|
220
|
-
return `${Types_1.Literals.KeySuffixPrefix}${ts}_${seq}_${rand}`;
|
|
221
|
-
};
|
|
222
|
-
const ensureOptionsObjectAttr = (j, openingEl) => {
|
|
223
|
-
let optionsAttr = (openingEl.attributes || []).find((a) => j.JSXAttribute.check(a) &&
|
|
224
|
-
j.JSXIdentifier.check(a.name) &&
|
|
225
|
-
a.name.name === Types_1.AttrName.Options);
|
|
226
|
-
if (!optionsAttr) {
|
|
227
|
-
optionsAttr = j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.Options), j.jsxExpressionContainer(j.objectExpression([])));
|
|
228
|
-
openingEl.attributes.push(optionsAttr);
|
|
229
|
-
}
|
|
230
|
-
return optionsAttr;
|
|
231
|
-
};
|
|
232
|
-
const injectTabBarButtonIntoOptions = (j, optionsAttr, withMask, tabButtonName) => {
|
|
233
|
-
if (!j.JSXAttribute.check(optionsAttr) ||
|
|
234
|
-
!j.JSXExpressionContainer.check(optionsAttr.value)) {
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
const expr = optionsAttr.value.expression;
|
|
238
|
-
if (!j.ObjectExpression.check(expr)) {
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
const already = (expr.properties || []).some((p) => j.ObjectProperty.check(p) &&
|
|
242
|
-
((j.Identifier.check(p.key) &&
|
|
243
|
-
p.key.name === Types_1.AttrName.TabBarButton) ||
|
|
244
|
-
(j.StringLiteral.check(p.key) &&
|
|
245
|
-
p.key.value === Types_1.AttrName.TabBarButton)));
|
|
246
|
-
if (already) {
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
const paramsId = j.identifier('props');
|
|
250
|
-
const attrs = [j.jsxSpreadAttribute(paramsId)];
|
|
251
|
-
if (withMask) {
|
|
252
|
-
attrs.push(j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.DtUIMask), j.jsxExpressionContainer(j.booleanLiteral(true))));
|
|
253
|
-
}
|
|
254
|
-
const opening = j.jsxOpeningElement(j.jsxIdentifier(tabButtonName), attrs, true);
|
|
255
|
-
const tabBtnEl = j.jsxElement(opening, null, []);
|
|
256
|
-
const arrow = j.arrowFunctionExpression([paramsId], tabBtnEl);
|
|
257
|
-
expr.properties.push(j.objectProperty(j.identifier(Types_1.AttrName.TabBarButton), arrow));
|
|
258
|
-
return true;
|
|
259
|
-
};
|
|
260
|
-
const pickAndRemoveDtUIMask = (j, openingEl) => {
|
|
261
|
-
const idx = (openingEl.attributes || []).findIndex((a) => j.JSXAttribute.check(a) &&
|
|
262
|
-
j.JSXIdentifier.check(a.name) &&
|
|
263
|
-
a.name.name === Types_1.AttrName.DtUIMask);
|
|
264
|
-
if (idx === -1) {
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
const attr = openingEl.attributes[idx];
|
|
268
|
-
let val = true;
|
|
269
|
-
if (attr.value) {
|
|
270
|
-
if (j.JSXExpressionContainer.check(attr.value) &&
|
|
271
|
-
j.BooleanLiteral.check(attr.value.expression)) {
|
|
272
|
-
val = attr.value.expression.value;
|
|
273
|
-
}
|
|
274
|
-
else if (j.JSXExpressionContainer.check(attr.value) &&
|
|
275
|
-
j.Identifier.check(attr.value.expression)) {
|
|
276
|
-
val = true;
|
|
277
|
-
}
|
|
278
|
-
else if (j.StringLiteral.check(attr.value)) {
|
|
279
|
-
val = attr.value.value.toLowerCase() !== 'false';
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
openingEl.attributes.splice(idx, 1);
|
|
283
|
-
return !!val;
|
|
284
|
-
};
|
|
285
|
-
const cloneJsxElement = (j, el) => {
|
|
286
|
-
const open = j.jsxOpeningElement(el.openingElement.name, el.openingElement.attributes ? [...el.openingElement.attributes] : [], el.openingElement.selfClosing);
|
|
287
|
-
const close = el.closingElement
|
|
288
|
-
? j.jsxClosingElement(el.closingElement.name)
|
|
289
|
-
: null;
|
|
290
|
-
const children = (el.children || []).map((c) => {
|
|
291
|
-
if (j.JSXElement.check(c)) {
|
|
292
|
-
return cloneJsxElement(j, c);
|
|
293
|
-
}
|
|
294
|
-
if (j.JSXText.check(c)) {
|
|
295
|
-
return j.jsxText(c.value);
|
|
296
|
-
}
|
|
297
|
-
if (j.JSXExpressionContainer.check(c)) {
|
|
298
|
-
return j.jsxExpressionContainer(c.expression);
|
|
22
|
+
const REACT_FRAGMENT_SOURCES = new Set([
|
|
23
|
+
'react',
|
|
24
|
+
'react/jsx-runtime',
|
|
25
|
+
'react/jsx-dev-runtime',
|
|
26
|
+
]);
|
|
27
|
+
const EXPO_PATH_SEGMENTS = [
|
|
28
|
+
'node_modules/expo/',
|
|
29
|
+
'node_modules/@expo/',
|
|
30
|
+
'node_modules/expo-router/',
|
|
31
|
+
];
|
|
32
|
+
const SELF_INSTRUMENTATION_PATH_SEGMENTS = [
|
|
33
|
+
'src/lib/features/ui-interaction/',
|
|
34
|
+
'lib/features/ui-interaction/',
|
|
35
|
+
'tests/lib/features/ui-interaction/',
|
|
36
|
+
];
|
|
37
|
+
const collectFragmentAliases = (root, j) => {
|
|
38
|
+
const fragmentAliases = new Set(['Fragment']);
|
|
39
|
+
root.find(j.ImportDeclaration).forEach((path) => {
|
|
40
|
+
const source = path.node.source;
|
|
41
|
+
if (!j.StringLiteral.check(source)) {
|
|
42
|
+
return;
|
|
299
43
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
return j.jsxElement(open, close, children);
|
|
303
|
-
};
|
|
304
|
-
const getNodeModulesPackageName = (filename) => {
|
|
305
|
-
if (!filename) {
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
const rel = path.relative(process.cwd(), filename);
|
|
309
|
-
const parts = rel.split(path.sep);
|
|
310
|
-
const nmIndex = parts.indexOf('node_modules');
|
|
311
|
-
if (nmIndex < 0 || nmIndex >= parts.length - 1) {
|
|
312
|
-
return null;
|
|
313
|
-
}
|
|
314
|
-
const first = parts[nmIndex + 1];
|
|
315
|
-
if (!first) {
|
|
316
|
-
return null;
|
|
317
|
-
}
|
|
318
|
-
if (first.startsWith('@')) {
|
|
319
|
-
if (nmIndex + 2 >= parts.length) {
|
|
320
|
-
return null;
|
|
44
|
+
if (!REACT_FRAGMENT_SOURCES.has(source.value)) {
|
|
45
|
+
return;
|
|
321
46
|
}
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const el = node.openingElement;
|
|
333
|
-
const name = getName(j, el.name);
|
|
334
|
-
if (Object.values(GestureParentName).includes(name)) {
|
|
335
|
-
gestureHandlerElements.add(node);
|
|
336
|
-
const children = node.children || [];
|
|
337
|
-
for (const ch of children) {
|
|
338
|
-
if (j.JSXElement.check(ch)) {
|
|
339
|
-
directGestureChildren.add(ch);
|
|
340
|
-
}
|
|
47
|
+
for (const specifier of path.node.specifiers || []) {
|
|
48
|
+
if (!j.ImportSpecifier.check(specifier)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const imported = specifier.imported;
|
|
52
|
+
if (!j.Identifier.check(imported) || imported.name !== 'Fragment') {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (j.Identifier.check(specifier.local)) {
|
|
56
|
+
fragmentAliases.add(specifier.local.name);
|
|
341
57
|
}
|
|
342
58
|
}
|
|
343
59
|
});
|
|
344
|
-
return
|
|
60
|
+
return fragmentAliases;
|
|
345
61
|
};
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
62
|
+
const isFragmentName = (tagName, blacklist, fragmentAliases) => blacklist.has(tagName) || fragmentAliases.has(tagName);
|
|
63
|
+
const isProviderLikeName = (tagName) => /Provider(?:Compat)?$/.test(tagName);
|
|
64
|
+
const isLikelyJsxFactoryCall = (j, callee) => {
|
|
65
|
+
if (j.Identifier.check(callee)) {
|
|
66
|
+
return /jsx/i.test(callee.name);
|
|
349
67
|
}
|
|
350
|
-
if (
|
|
351
|
-
|
|
68
|
+
if (j.MemberExpression.check(callee) &&
|
|
69
|
+
j.Identifier.check(callee.property)) {
|
|
70
|
+
return /jsx/i.test(callee.property.name);
|
|
352
71
|
}
|
|
353
|
-
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
return true;
|
|
72
|
+
return false;
|
|
357
73
|
};
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (!isTabScreen(j, el)) {
|
|
362
|
-
return false;
|
|
74
|
+
const isFragmentExpression = (j, expr, fragmentAliases) => {
|
|
75
|
+
if (j.Identifier.check(expr)) {
|
|
76
|
+
return fragmentAliases.has(expr.name);
|
|
363
77
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
state.instrumented = true;
|
|
78
|
+
if (j.MemberExpression.check(expr) &&
|
|
79
|
+
expr.computed !== true &&
|
|
80
|
+
j.Identifier.check(expr.property)) {
|
|
81
|
+
return expr.property.name === 'Fragment';
|
|
369
82
|
}
|
|
370
|
-
return
|
|
83
|
+
return false;
|
|
371
84
|
};
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
const { node, el, name, segName, tabBtnName, rootName } = meta;
|
|
375
|
-
if (!isNodeModulesFile) {
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
const isGestureSelf = gestureHandlerElements.has(node);
|
|
379
|
-
const isDirectGestureChild = directGestureChildren.has(node);
|
|
380
|
-
const hasJsxChildren = (node.children || []).some((ch) => j.JSXElement.check(ch));
|
|
381
|
-
if (isGestureSelf) {
|
|
382
|
-
return true;
|
|
383
|
-
}
|
|
384
|
-
if (isDirectGestureChild && hasJsxChildren) {
|
|
385
|
-
const origChildren = node.children || [];
|
|
386
|
-
if (!origChildren.length) {
|
|
387
|
-
return false;
|
|
388
|
-
}
|
|
389
|
-
const newChildren = [];
|
|
390
|
-
for (const ch of origChildren) {
|
|
391
|
-
if (!j.JSXElement.check(ch)) {
|
|
392
|
-
newChildren.push(ch);
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
const childOpening = ch.openingElement;
|
|
396
|
-
const childName = getName(j, childOpening.name);
|
|
397
|
-
if (!shouldWrapNodeModulesElement(childName, segName, tabBtnName, rootName, nodeModulesUITags)) {
|
|
398
|
-
newChildren.push(ch);
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
const innerClone = cloneJsxElement(j, ch);
|
|
402
|
-
const segAttrs = [
|
|
403
|
-
j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.Name), j.stringLiteral(childName)),
|
|
404
|
-
j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.Key), j.stringLiteral(makeUniqueSuffix(state))),
|
|
405
|
-
];
|
|
406
|
-
const segOpen = j.jsxOpeningElement(j.jsxIdentifier(segName), segAttrs, false);
|
|
407
|
-
const segClose = j.jsxClosingElement(j.jsxIdentifier(segName));
|
|
408
|
-
const segElement = j.jsxElement(segOpen, segClose, [innerClone]);
|
|
409
|
-
let wrapped = segElement;
|
|
410
|
-
if (nodeModulesPackageName) {
|
|
411
|
-
const pathPrefixOpen = j.jsxOpeningElement(j.jsxIdentifier(PATH_PREFIX_DEFAULT_NAME), [
|
|
412
|
-
j.jsxAttribute(j.jsxIdentifier('names'), j.jsxExpressionContainer(j.arrayExpression([
|
|
413
|
-
j.objectExpression([
|
|
414
|
-
j.objectProperty(j.identifier('name'), j.literal(nodeModulesPackageName)),
|
|
415
|
-
]),
|
|
416
|
-
]))),
|
|
417
|
-
], false);
|
|
418
|
-
const pathPrefixClose = j.jsxClosingElement(j.jsxIdentifier(PATH_PREFIX_DEFAULT_NAME));
|
|
419
|
-
wrapped = j.jsxElement(pathPrefixOpen, pathPrefixClose, [
|
|
420
|
-
segElement,
|
|
421
|
-
]);
|
|
422
|
-
}
|
|
423
|
-
newChildren.push(wrapped);
|
|
424
|
-
state.instrumented = true;
|
|
425
|
-
}
|
|
426
|
-
node.children = newChildren;
|
|
427
|
-
return state.instrumented;
|
|
428
|
-
}
|
|
429
|
-
if (skipTags.has(name)) {
|
|
85
|
+
const removePathIdFromObjectExpression = (j, objectExpression, propName) => {
|
|
86
|
+
if (!j.ObjectExpression.check(objectExpression)) {
|
|
430
87
|
return false;
|
|
431
88
|
}
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return false;
|
|
89
|
+
const before = objectExpression.properties.length;
|
|
90
|
+
objectExpression.properties = objectExpression.properties.filter((property) => {
|
|
91
|
+
if (!j.ObjectProperty.check(property)) {
|
|
92
|
+
return true;
|
|
437
93
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const { base, prop } = getBaseAndPropFromMemberExpression(j, el.name);
|
|
441
|
-
const full = base && prop ? `${base}.${prop}` : null;
|
|
442
|
-
if (!full || !allowMemberExpr.has(full)) {
|
|
94
|
+
if (j.Identifier.check(property.key) &&
|
|
95
|
+
property.key.name === propName) {
|
|
443
96
|
return false;
|
|
444
97
|
}
|
|
98
|
+
return !(j.StringLiteral.check(property.key) &&
|
|
99
|
+
property.key.value === propName);
|
|
100
|
+
});
|
|
101
|
+
return objectExpression.properties.length !== before;
|
|
102
|
+
};
|
|
103
|
+
const shouldSkipFile = (filename, options) => {
|
|
104
|
+
var _a, _b;
|
|
105
|
+
const include = (_a = options.include) !== null && _a !== void 0 ? _a : [];
|
|
106
|
+
const exclude = (_b = options.exclude) !== null && _b !== void 0 ? _b : [];
|
|
107
|
+
const normalized = path.normalize(filename);
|
|
108
|
+
const normalizedPosix = normalized.split('\\').join('/');
|
|
109
|
+
if (include.length > 0 && !(0, micromatch_1.isMatch)(normalized, include)) {
|
|
110
|
+
return true;
|
|
445
111
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
return false;
|
|
112
|
+
if (EXPO_PATH_SEGMENTS.some((segment) => normalizedPosix.includes(segment))) {
|
|
113
|
+
return true;
|
|
449
114
|
}
|
|
450
|
-
|
|
451
|
-
const segAttrs = [
|
|
452
|
-
j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.Name), j.stringLiteral(name)),
|
|
453
|
-
j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.Key), j.stringLiteral(makeUniqueSuffix(state))),
|
|
454
|
-
];
|
|
455
|
-
const segOpen = j.jsxOpeningElement(j.jsxIdentifier(segName), segAttrs, false);
|
|
456
|
-
const segClose = j.jsxClosingElement(j.jsxIdentifier(segName));
|
|
457
|
-
const segElement = j.jsxElement(segOpen, segClose, [childEl]);
|
|
458
|
-
if (!nodeModulesPackageName) {
|
|
459
|
-
p.replace(segElement);
|
|
460
|
-
state.instrumented = true;
|
|
115
|
+
if (SELF_INSTRUMENTATION_PATH_SEGMENTS.some((segment) => normalizedPosix.includes(segment))) {
|
|
461
116
|
return true;
|
|
462
117
|
}
|
|
463
|
-
|
|
464
|
-
j.jsxAttribute(j.jsxIdentifier('names'), j.jsxExpressionContainer(j.arrayExpression([
|
|
465
|
-
j.objectExpression([
|
|
466
|
-
j.objectProperty(j.identifier('name'), j.literal(nodeModulesPackageName)),
|
|
467
|
-
]),
|
|
468
|
-
]))),
|
|
469
|
-
], false);
|
|
470
|
-
const pathPrefixClose = j.jsxClosingElement(j.jsxIdentifier(PATH_PREFIX_DEFAULT_NAME));
|
|
471
|
-
const wrapped = j.jsxElement(pathPrefixOpen, pathPrefixClose, [segElement]);
|
|
472
|
-
p.replace(wrapped);
|
|
473
|
-
state.instrumented = true;
|
|
474
|
-
return true;
|
|
475
|
-
};
|
|
476
|
-
const handleNavigatorParent = (ctx, p, _meta) => {
|
|
477
|
-
const { j } = ctx;
|
|
478
|
-
return parentIsNavigator(j, p);
|
|
118
|
+
return (0, micromatch_1.isMatch)(normalized, exclude);
|
|
479
119
|
};
|
|
480
|
-
const
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
return
|
|
484
|
-
};
|
|
485
|
-
const handleStoppedElementHandler = (_ctx, _p, meta) => {
|
|
486
|
-
const { segName, tabBtnName, rootName, name } = meta;
|
|
487
|
-
return isStoppedElementName(name, segName, tabBtnName, rootName);
|
|
120
|
+
const isPathInside = (baseDir, candidatePath) => {
|
|
121
|
+
const resolvedBase = path.resolve(baseDir);
|
|
122
|
+
const resolvedCandidate = path.resolve(candidatePath);
|
|
123
|
+
return (resolvedCandidate === resolvedBase ||
|
|
124
|
+
resolvedCandidate.startsWith(`${resolvedBase}${path.sep}`));
|
|
488
125
|
};
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
for (let i = 0; i < attrs.length; i++) {
|
|
498
|
-
const a = attrs[i];
|
|
499
|
-
if (!j.JSXAttribute.check(a)) {
|
|
500
|
-
continue;
|
|
501
|
-
}
|
|
502
|
-
const propName = j.JSXIdentifier.check(a.name) ? a.name.name : null;
|
|
503
|
-
if (propName === null || !PRESS_KEYS.includes(propName)) {
|
|
504
|
-
continue;
|
|
505
|
-
}
|
|
506
|
-
if (!a.value) {
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
if (j.JSXExpressionContainer.check(a.value)) {
|
|
510
|
-
const expr = a.value.expression;
|
|
511
|
-
const orig = j.JSXEmptyExpression.check(expr)
|
|
512
|
-
? j.identifier('undefined')
|
|
513
|
-
: expr;
|
|
514
|
-
const phase = j.literal(propName);
|
|
515
|
-
const body = j.blockStatement([
|
|
516
|
-
j.variableDeclaration('const', [
|
|
517
|
-
j.variableDeclarator(j.identifier(Types_1.IdName.DtEmit), j.memberExpression(j.identifier('console'), j.identifier('log'))),
|
|
518
|
-
]),
|
|
519
|
-
j.expressionStatement(j.callExpression(j.identifier(Types_1.IdName.DtEmit), [
|
|
520
|
-
j.literal(Types_1.Literals.RawHandlerTag),
|
|
521
|
-
phase,
|
|
522
|
-
])),
|
|
523
|
-
j.returnStatement(j.conditionalExpression(j.binaryExpression('===', j.unaryExpression('typeof', orig), j.literal('function')), j.callExpression(orig, [
|
|
524
|
-
j.spreadElement(j.identifier('args')),
|
|
525
|
-
]), j.identifier('undefined'))),
|
|
526
|
-
]);
|
|
527
|
-
a.value = j.jsxExpressionContainer(j.arrowFunctionExpression([j.restElement(j.identifier('args'))], body));
|
|
528
|
-
touched = true;
|
|
126
|
+
const writeArtifact = (root, filename, options) => {
|
|
127
|
+
var _a, _b;
|
|
128
|
+
const outDir = (_a = options.artifactsDir) !== null && _a !== void 0 ? _a : Types_1.Literals.DefaultArtifactsDir;
|
|
129
|
+
try {
|
|
130
|
+
const rel = path.relative(globalThis.process.cwd(), filename);
|
|
131
|
+
const outPath = path.resolve(outDir, rel);
|
|
132
|
+
if (!isPathInside(outDir, outPath)) {
|
|
133
|
+
return;
|
|
529
134
|
}
|
|
135
|
+
const code = root.toSource({ quote: 'single' });
|
|
136
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
137
|
+
fs.writeFileSync(outPath, code, 'utf8');
|
|
530
138
|
}
|
|
531
|
-
|
|
532
|
-
|
|
139
|
+
catch (e) {
|
|
140
|
+
if (options.log === true) {
|
|
141
|
+
const errorMsg = e instanceof Error ? (_b = e.stack) !== null && _b !== void 0 ? _b : e.message : String(e);
|
|
142
|
+
globalThis.console.log(`[UIInteractionPlugin]: Unable to write artifact for ${filename}: ${errorMsg}`);
|
|
143
|
+
}
|
|
533
144
|
}
|
|
534
|
-
return true;
|
|
535
145
|
};
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
146
|
+
const applyDTPathIdInstrumentation = (root, filename, options = {}) => {
|
|
147
|
+
var _a, _b, _c, _d;
|
|
148
|
+
const j = jsc.withParser('tsx');
|
|
149
|
+
if (shouldSkipFile(filename, options)) {
|
|
150
|
+
return { root, changed: false };
|
|
151
|
+
}
|
|
152
|
+
const uiOptions = ((_a = options.ui) !== null && _a !== void 0 ? _a : {});
|
|
153
|
+
const pathIdConfig = (_b = uiOptions.pathId) !== null && _b !== void 0 ? _b : {};
|
|
154
|
+
const enabled = pathIdConfig.enabled !== false;
|
|
155
|
+
if (!enabled) {
|
|
156
|
+
return { root, changed: false };
|
|
157
|
+
}
|
|
158
|
+
const propName = (_c = pathIdConfig.propName) !== null && _c !== void 0 ? _c : 'dtPathID';
|
|
159
|
+
const uiRootPropName = (_d = pathIdConfig.uiRootPropName) !== null && _d !== void 0 ? _d : 'dtUIRoot';
|
|
160
|
+
const injectUiRoot = pathIdConfig.injectUiRoot === true;
|
|
161
|
+
const skipProviderTags = pathIdConfig.skipProviderTags !== false;
|
|
162
|
+
const blacklist = new Set(pathIdConfig.blacklist || [
|
|
163
|
+
'AnalyticsRoot',
|
|
164
|
+
'Stack',
|
|
165
|
+
'React.Fragment',
|
|
166
|
+
'Fragment',
|
|
167
|
+
]);
|
|
168
|
+
const fragmentAliases = collectFragmentAliases(root, j);
|
|
169
|
+
let seq = 0;
|
|
170
|
+
let changed = false;
|
|
171
|
+
let rootMarkerAssigned = false;
|
|
172
|
+
const getAttributes = (opening) => Array.isArray(opening.attributes) ? opening.attributes : [];
|
|
173
|
+
const hasAttribute = (opening, attrName) => {
|
|
174
|
+
const attrs = getAttributes(opening);
|
|
175
|
+
return attrs.some((attr) => j.JSXAttribute.check(attr) &&
|
|
176
|
+
j.JSXIdentifier.check(attr.name) &&
|
|
177
|
+
attr.name.name === attrName);
|
|
178
|
+
};
|
|
179
|
+
const hasPathIdAttribute = (opening) => hasAttribute(opening, propName);
|
|
180
|
+
const addBooleanAttribute = (opening, attrName) => {
|
|
181
|
+
opening.attributes = [
|
|
182
|
+
...getAttributes(opening),
|
|
183
|
+
j.jsxAttribute(j.jsxIdentifier(attrName)),
|
|
184
|
+
];
|
|
185
|
+
};
|
|
186
|
+
const removeAttribute = (opening, attrName) => {
|
|
187
|
+
const attrs = getAttributes(opening);
|
|
188
|
+
const filtered = attrs.filter((attr) => !(j.JSXAttribute.check(attr) &&
|
|
189
|
+
j.JSXIdentifier.check(attr.name) &&
|
|
190
|
+
attr.name.name === attrName));
|
|
191
|
+
if (filtered.length === attrs.length) {
|
|
554
192
|
return false;
|
|
555
193
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
570
|
-
const childEl = cloneJsxElement(j, node);
|
|
571
|
-
if (name === 'TouchableOpacity' || name === 'TouchableHighlight') {
|
|
572
|
-
const tpChildren = childEl.children || [];
|
|
573
|
-
const newTpChildren = [];
|
|
574
|
-
for (const ch of tpChildren) {
|
|
575
|
-
if (!j.JSXElement.check(ch)) {
|
|
576
|
-
newTpChildren.push(ch);
|
|
577
|
-
continue;
|
|
578
|
-
}
|
|
579
|
-
const grandOpening = ch.openingElement;
|
|
580
|
-
const grandName = getName(j, grandOpening.name);
|
|
581
|
-
if (isStoppedElementName(grandName, segName, tabBtnName, rootName) ||
|
|
582
|
-
DANGEROUS_WRAP_DENY.includes(grandName)) {
|
|
583
|
-
newTpChildren.push(ch);
|
|
584
|
-
continue;
|
|
585
|
-
}
|
|
586
|
-
const grandSegAttrs = [
|
|
587
|
-
j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.Name), j.stringLiteral(grandName)),
|
|
588
|
-
j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.Key), j.stringLiteral(makeUniqueSuffix(state))),
|
|
589
|
-
];
|
|
590
|
-
const grandSegOpen = j.jsxOpeningElement(j.jsxIdentifier(segName), grandSegAttrs, false);
|
|
591
|
-
const grandSegClose = j.jsxClosingElement(j.jsxIdentifier(segName));
|
|
592
|
-
const grandInner = cloneJsxElement(j, ch);
|
|
593
|
-
const wrappedGrand = j.jsxElement(grandSegOpen, grandSegClose, [
|
|
594
|
-
grandInner,
|
|
595
|
-
]);
|
|
596
|
-
newTpChildren.push(wrappedGrand);
|
|
597
|
-
state.instrumented = true;
|
|
194
|
+
opening.attributes = filtered;
|
|
195
|
+
return true;
|
|
196
|
+
};
|
|
197
|
+
const removePathIdAttribute = (opening) => removeAttribute(opening, propName);
|
|
198
|
+
const removeUiRootAttribute = (opening) => removeAttribute(opening, uiRootPropName);
|
|
199
|
+
const removeInstrumentationAttributes = (opening) => {
|
|
200
|
+
const removedPathId = removePathIdAttribute(opening);
|
|
201
|
+
const removedUiRoot = removeUiRootAttribute(opening);
|
|
202
|
+
return removedPathId || removedUiRoot;
|
|
203
|
+
};
|
|
204
|
+
const maybeAssignUiRoot = (opening, hasUiRootAttribute) => {
|
|
205
|
+
if (!injectUiRoot || rootMarkerAssigned || hasUiRootAttribute) {
|
|
206
|
+
return false;
|
|
598
207
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const grandSegClose = j.jsxClosingElement(j.jsxIdentifier(segName));
|
|
632
|
-
const grandInner = cloneJsxElement(j, grand);
|
|
633
|
-
const wrappedGrand = j.jsxElement(grandSegOpen, grandSegClose, [grandInner]);
|
|
634
|
-
newTpChildren.push(wrappedGrand);
|
|
635
|
-
state.instrumented = true;
|
|
636
|
-
}
|
|
637
|
-
tpClone.children = newTpChildren;
|
|
638
|
-
newChildren.push(tpClone);
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
if (isStoppedElementName(childName, segName, tabBtnName, rootName)) {
|
|
642
|
-
newChildren.push(ch);
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
if (DANGEROUS_WRAP_DENY.includes(childName)) {
|
|
646
|
-
newChildren.push(ch);
|
|
647
|
-
continue;
|
|
208
|
+
addBooleanAttribute(opening, uiRootPropName);
|
|
209
|
+
rootMarkerAssigned = true;
|
|
210
|
+
return true;
|
|
211
|
+
};
|
|
212
|
+
const addPathIdAttribute = (opening, tagName) => {
|
|
213
|
+
var _a, _b, _c, _d;
|
|
214
|
+
const baseName = Array.from(tagName)
|
|
215
|
+
.map((ch) => (/\w/.test(ch) ? ch : '_'))
|
|
216
|
+
.join('') || 'Anonymous';
|
|
217
|
+
const line = (_b = (_a = opening.loc) === null || _a === void 0 ? void 0 : _a.start) === null || _b === void 0 ? void 0 : _b.line;
|
|
218
|
+
const column = (_d = (_c = opening.loc) === null || _c === void 0 ? void 0 : _c.start) === null || _d === void 0 ? void 0 : _d.column;
|
|
219
|
+
const fallback = seq++;
|
|
220
|
+
const suffix = typeof line === 'number' && typeof column === 'number'
|
|
221
|
+
? `${line}_${column}`
|
|
222
|
+
: `${fallback}_0`;
|
|
223
|
+
const dtPathId = `${baseName}_${suffix}`;
|
|
224
|
+
opening.attributes = [
|
|
225
|
+
...getAttributes(opening),
|
|
226
|
+
j.jsxAttribute(j.jsxIdentifier(propName), j.stringLiteral(dtPathId)),
|
|
227
|
+
];
|
|
228
|
+
};
|
|
229
|
+
const processOpeningElement = (opening) => {
|
|
230
|
+
const tagName = getName(j, opening.name);
|
|
231
|
+
const hasUiRootAttribute = hasAttribute(opening, uiRootPropName);
|
|
232
|
+
if (!injectUiRoot &&
|
|
233
|
+
hasUiRootAttribute &&
|
|
234
|
+
removeUiRootAttribute(opening)) {
|
|
235
|
+
changed = true;
|
|
236
|
+
}
|
|
237
|
+
if (skipProviderTags && isProviderLikeName(tagName)) {
|
|
238
|
+
if (removeInstrumentationAttributes(opening)) {
|
|
239
|
+
changed = true;
|
|
648
240
|
}
|
|
649
|
-
|
|
650
|
-
j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.Name), j.stringLiteral(childName)),
|
|
651
|
-
j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.Key), j.stringLiteral(makeUniqueSuffix(state))),
|
|
652
|
-
];
|
|
653
|
-
const childSegOpen = j.jsxOpeningElement(j.jsxIdentifier(segName), childSegAttrs, false);
|
|
654
|
-
const childSegClose = j.jsxClosingElement(j.jsxIdentifier(segName));
|
|
655
|
-
const innerClone = cloneJsxElement(j, ch);
|
|
656
|
-
const wrappedChild = j.jsxElement(childSegOpen, childSegClose, [
|
|
657
|
-
innerClone,
|
|
658
|
-
]);
|
|
659
|
-
newChildren.push(wrappedChild);
|
|
660
|
-
state.instrumented = true;
|
|
661
|
-
}
|
|
662
|
-
childEl.children = newChildren;
|
|
663
|
-
}
|
|
664
|
-
const segOpen = j.jsxOpeningElement(j.jsxIdentifier(segName), segAttrs, false);
|
|
665
|
-
const segClose = j.jsxClosingElement(j.jsxIdentifier(segName));
|
|
666
|
-
const wrapped = j.jsxElement(segOpen, segClose, [childEl]);
|
|
667
|
-
p.replace(wrapped);
|
|
668
|
-
state.instrumented = true;
|
|
669
|
-
return true;
|
|
670
|
-
};
|
|
671
|
-
const handleTapHandlerOverlay = (ctx, p, meta) => {
|
|
672
|
-
const { j, state, isNodeModulesFile, nodeModulesPackageName } = ctx;
|
|
673
|
-
const { node, name, segName, tabBtnName, rootName } = meta;
|
|
674
|
-
if (!isNodeModulesFile) {
|
|
675
|
-
return false;
|
|
676
|
-
}
|
|
677
|
-
if (name !== 'TapGestureHandler') {
|
|
678
|
-
return false;
|
|
679
|
-
}
|
|
680
|
-
if (!shouldInstrumentTapHandlerInFile(state, nodeModulesPackageName)) {
|
|
681
|
-
return false;
|
|
682
|
-
}
|
|
683
|
-
const children = node.children || [];
|
|
684
|
-
let targetIndex = -1;
|
|
685
|
-
for (let i = 0; i < children.length; i++) {
|
|
686
|
-
const ch = children[i];
|
|
687
|
-
if (j.JSXElement.check(ch)) {
|
|
688
|
-
targetIndex = i;
|
|
689
|
-
break;
|
|
241
|
+
return;
|
|
690
242
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
return false;
|
|
694
|
-
}
|
|
695
|
-
const childEl = children[targetIndex];
|
|
696
|
-
if (!j.JSXElement.check(childEl)) {
|
|
697
|
-
return false;
|
|
698
|
-
}
|
|
699
|
-
const childName = getName(j, childEl.openingElement.name);
|
|
700
|
-
if (isStoppedElementName(childName, segName, tabBtnName, rootName) ||
|
|
701
|
-
DANGEROUS_WRAP_DENY.includes(childName)) {
|
|
702
|
-
return false;
|
|
703
|
-
}
|
|
704
|
-
const opening = childEl.openingElement;
|
|
705
|
-
const attrs = opening.attributes || [];
|
|
706
|
-
let styleAttrIndex = -1;
|
|
707
|
-
for (let i = 0; i < attrs.length; i++) {
|
|
708
|
-
const a = attrs[i];
|
|
709
|
-
if (j.JSXAttribute.check(a) &&
|
|
710
|
-
j.JSXIdentifier.check(a.name) &&
|
|
711
|
-
a.name.name === 'style') {
|
|
712
|
-
styleAttrIndex = i;
|
|
713
|
-
break;
|
|
243
|
+
if (injectUiRoot && hasUiRootAttribute) {
|
|
244
|
+
rootMarkerAssigned = true;
|
|
714
245
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
j.objectProperty(j.identifier('borderColor'), j.literal('red')),
|
|
719
|
-
]);
|
|
720
|
-
if (styleAttrIndex === -1) {
|
|
721
|
-
const styleExpr = j.arrayExpression([extraStyleObj]);
|
|
722
|
-
attrs.push(j.jsxAttribute(j.jsxIdentifier('style'), j.jsxExpressionContainer(styleExpr)));
|
|
723
|
-
}
|
|
724
|
-
else {
|
|
725
|
-
const styleAttr = attrs[styleAttrIndex];
|
|
726
|
-
if (j.JSXExpressionContainer.check(styleAttr.value)) {
|
|
727
|
-
const expr = styleAttr.value.expression;
|
|
728
|
-
if (j.ArrayExpression.check(expr)) {
|
|
729
|
-
expr.elements.push(extraStyleObj);
|
|
730
|
-
}
|
|
731
|
-
else {
|
|
732
|
-
styleAttr.value = j.jsxExpressionContainer(j.arrayExpression([expr, extraStyleObj]));
|
|
246
|
+
if (isFragmentName(tagName, blacklist, fragmentAliases)) {
|
|
247
|
+
if (removeInstrumentationAttributes(opening)) {
|
|
248
|
+
changed = true;
|
|
733
249
|
}
|
|
250
|
+
return;
|
|
734
251
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
opening.attributes = attrs;
|
|
740
|
-
state.instrumented = true;
|
|
741
|
-
return true;
|
|
742
|
-
};
|
|
743
|
-
const handleDrawerOverlayPressable = (ctx, p, meta) => {
|
|
744
|
-
const { j, state, isDrawerModuleFile, isNodeModulesFile } = ctx;
|
|
745
|
-
const { node, segName } = meta;
|
|
746
|
-
if (!isNodeModulesFile || !isDrawerModuleFile) {
|
|
747
|
-
return false;
|
|
748
|
-
}
|
|
749
|
-
const children = node.children || [];
|
|
750
|
-
if (!children.length) {
|
|
751
|
-
return false;
|
|
752
|
-
}
|
|
753
|
-
let modified = false;
|
|
754
|
-
const newChildren = [];
|
|
755
|
-
for (let i = 0; i < children.length; i++) {
|
|
756
|
-
const ch = children[i];
|
|
757
|
-
if (!j.JSXElement.check(ch)) {
|
|
758
|
-
newChildren.push(ch);
|
|
759
|
-
continue;
|
|
760
|
-
}
|
|
761
|
-
const childOpening = ch.openingElement;
|
|
762
|
-
const childName = getName(j, childOpening.name);
|
|
763
|
-
if (childName !== 'Pressable') {
|
|
764
|
-
newChildren.push(ch);
|
|
765
|
-
continue;
|
|
766
|
-
}
|
|
767
|
-
const attrs = childOpening.attributes || [];
|
|
768
|
-
let isOverlayPressable = false;
|
|
769
|
-
for (let k = 0; k < attrs.length; k++) {
|
|
770
|
-
const a = attrs[k];
|
|
771
|
-
if (j.JSXAttribute.check(a) &&
|
|
772
|
-
j.JSXIdentifier.check(a.name) &&
|
|
773
|
-
a.name.name === 'style' &&
|
|
774
|
-
j.JSXExpressionContainer.check(a.value)) {
|
|
775
|
-
const expr = a.value.expression;
|
|
776
|
-
if (j.ArrayExpression.check(expr)) {
|
|
777
|
-
for (const el of expr.elements || []) {
|
|
778
|
-
if (el &&
|
|
779
|
-
j.MemberExpression.check(el) &&
|
|
780
|
-
j.Identifier.check(el.object) &&
|
|
781
|
-
el.object.name === 'styles' &&
|
|
782
|
-
j.Identifier.check(el.property) &&
|
|
783
|
-
el.property.name === 'pressable') {
|
|
784
|
-
isOverlayPressable = true;
|
|
785
|
-
break;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
else if (j.MemberExpression.check(expr) &&
|
|
790
|
-
j.Identifier.check(expr.object) &&
|
|
791
|
-
expr.object.name === 'styles' &&
|
|
792
|
-
j.Identifier.check(expr.property) &&
|
|
793
|
-
expr.property.name === 'pressable') {
|
|
794
|
-
isOverlayPressable = true;
|
|
795
|
-
}
|
|
252
|
+
if (hasPathIdAttribute(opening)) {
|
|
253
|
+
if (maybeAssignUiRoot(opening, hasUiRootAttribute)) {
|
|
254
|
+
changed = true;
|
|
796
255
|
}
|
|
797
|
-
if (isOverlayPressable) {
|
|
798
|
-
break;
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
if (!isOverlayPressable) {
|
|
802
|
-
newChildren.push(ch);
|
|
803
|
-
continue;
|
|
804
|
-
}
|
|
805
|
-
const segAttrs = [
|
|
806
|
-
j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.Name), j.stringLiteral('DrawerOverlayPressable')),
|
|
807
|
-
j.jsxAttribute(j.jsxIdentifier(Types_1.AttrName.Key), j.stringLiteral(makeUniqueSuffix(state))),
|
|
808
|
-
];
|
|
809
|
-
const segOpen = j.jsxOpeningElement(j.jsxIdentifier(segName), segAttrs, false);
|
|
810
|
-
const segClose = j.jsxClosingElement(j.jsxIdentifier(segName));
|
|
811
|
-
const pressClone = cloneJsxElement(j, ch);
|
|
812
|
-
const segElement = j.jsxElement(segOpen, segClose, [pressClone]);
|
|
813
|
-
newChildren.push(segElement);
|
|
814
|
-
modified = true;
|
|
815
|
-
}
|
|
816
|
-
if (!modified) {
|
|
817
|
-
return false;
|
|
818
|
-
}
|
|
819
|
-
node.children = newChildren;
|
|
820
|
-
state.instrumented = true;
|
|
821
|
-
return true;
|
|
822
|
-
};
|
|
823
|
-
const applyDTUserInteraction = (root, filename, options = {}) => {
|
|
824
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
825
|
-
const j = jsc.withParser('tsx');
|
|
826
|
-
const { gestureHandlerElements, directGestureChildren } = collectGestureHandlerInfo(j, root);
|
|
827
|
-
const isDrawerModuleFile = typeof filename === 'string' &&
|
|
828
|
-
filename.includes(`node_modules${path.sep}@react-navigation${path.sep}drawer`);
|
|
829
|
-
const isNodeModulesFile = typeof filename === 'string' &&
|
|
830
|
-
filename.includes(`node_modules${path.sep}`);
|
|
831
|
-
const nodeModulesPackageName = getNodeModulesPackageName(filename);
|
|
832
|
-
const state = {
|
|
833
|
-
file: filename,
|
|
834
|
-
skip: shouldSkipFile(filename, options),
|
|
835
|
-
seq: 0,
|
|
836
|
-
instrumented: false,
|
|
837
|
-
};
|
|
838
|
-
if (!state.skip &&
|
|
839
|
-
Array.isArray(options === null || options === void 0 ? void 0 : options.globalPrefix) &&
|
|
840
|
-
options.globalPrefix.length > 0) {
|
|
841
|
-
injectGlobalPrefixOnce(j, root, options.globalPrefix);
|
|
842
|
-
}
|
|
843
|
-
const segName = (_b = (_a = options === null || options === void 0 ? void 0 : options.componentNames) === null || _a === void 0 ? void 0 : _a.segment) !== null && _b !== void 0 ? _b : Types_1.Literals.DefaultSegmentName;
|
|
844
|
-
const tabBtnName = (_d = (_c = options === null || options === void 0 ? void 0 : options.componentNames) === null || _c === void 0 ? void 0 : _c.tabButton) !== null && _d !== void 0 ? _d : Types_1.Literals.DefaultTabButtonName;
|
|
845
|
-
const rootName = (_f = (_e = options === null || options === void 0 ? void 0 : options.componentNames) === null || _e === void 0 ? void 0 : _e.root) !== null && _f !== void 0 ? _f : Types_1.Literals.DefaultRootName;
|
|
846
|
-
const uiOptions = ((options === null || options === void 0 ? void 0 : options.ui) || {});
|
|
847
|
-
const skipTags = new Set(uiOptions.skipTags || []);
|
|
848
|
-
const skipChildrenOf = new Set(uiOptions.skipChildrenOf || []);
|
|
849
|
-
const defaultAllowMemberExpr = [
|
|
850
|
-
'Animated.View',
|
|
851
|
-
'Animated.Text',
|
|
852
|
-
'Reanimated.View',
|
|
853
|
-
'Reanimated.Text',
|
|
854
|
-
];
|
|
855
|
-
const allowMemberExpr = new Set([
|
|
856
|
-
...defaultAllowMemberExpr,
|
|
857
|
-
...(uiOptions.allowMemberExpressions || []),
|
|
858
|
-
]);
|
|
859
|
-
const nodeModulesUITags = new Set([
|
|
860
|
-
...NODE_MODULES_UI_TAGS,
|
|
861
|
-
...(uiOptions.extraNodeModulesTags || []),
|
|
862
|
-
]);
|
|
863
|
-
const ctx = {
|
|
864
|
-
j,
|
|
865
|
-
state,
|
|
866
|
-
options,
|
|
867
|
-
isDrawerModuleFile,
|
|
868
|
-
isNodeModulesFile,
|
|
869
|
-
nodeModulesPackageName,
|
|
870
|
-
skipTags,
|
|
871
|
-
skipChildrenOf,
|
|
872
|
-
allowMemberExpr,
|
|
873
|
-
nodeModulesUITags,
|
|
874
|
-
gestureHandlerElements,
|
|
875
|
-
directGestureChildren,
|
|
876
|
-
};
|
|
877
|
-
const handlers = [
|
|
878
|
-
handleTabScreen,
|
|
879
|
-
handleNavigatorParent,
|
|
880
|
-
handleStoppedMemberHandler,
|
|
881
|
-
handleStoppedElementHandler,
|
|
882
|
-
handleDangerousWrapHandler,
|
|
883
|
-
handleTapHandlerOverlay,
|
|
884
|
-
handleDrawerOverlayPressable,
|
|
885
|
-
handleNodeModulesUI,
|
|
886
|
-
handleDefaultSegmentWrap,
|
|
887
|
-
];
|
|
888
|
-
root.find(j.JSXElement).forEach((p) => {
|
|
889
|
-
if (state.skip) {
|
|
890
256
|
return;
|
|
891
257
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
const meta = {
|
|
896
|
-
node,
|
|
897
|
-
el,
|
|
898
|
-
name,
|
|
899
|
-
segName,
|
|
900
|
-
tabBtnName,
|
|
901
|
-
rootName,
|
|
902
|
-
};
|
|
903
|
-
for (const h of handlers) {
|
|
904
|
-
if (h(ctx, p, meta)) {
|
|
905
|
-
break;
|
|
906
|
-
}
|
|
258
|
+
addPathIdAttribute(opening, tagName);
|
|
259
|
+
if (maybeAssignUiRoot(opening, hasUiRootAttribute)) {
|
|
260
|
+
changed = true;
|
|
907
261
|
}
|
|
262
|
+
changed = true;
|
|
263
|
+
};
|
|
264
|
+
root.find(j.JSXOpeningElement).forEach((path) => {
|
|
265
|
+
processOpeningElement(path.node);
|
|
908
266
|
});
|
|
909
|
-
|
|
910
|
-
const
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
ensureNamedImport(j, root, importPath, segNameImport);
|
|
914
|
-
ensureNamedImport(j, root, importPath, tabBtnNameImport);
|
|
915
|
-
ensureNamedImport(j, root, importPath, PATH_PREFIX_DEFAULT_NAME);
|
|
916
|
-
const shouldInjectPressableImport = options.shouldInjectPressableImport !== false;
|
|
917
|
-
if (shouldInjectPressableImport &&
|
|
918
|
-
!hasNamedImportBinding(j, root, 'Pressable')) {
|
|
919
|
-
ensureNamedImport(j, root, '@dynatrace/react-native-plugin/instrumentation/libs/react-native/', 'Pressable');
|
|
267
|
+
root.find(j.CallExpression).forEach((path) => {
|
|
268
|
+
const call = path.node;
|
|
269
|
+
if (!isLikelyJsxFactoryCall(j, call.callee)) {
|
|
270
|
+
return;
|
|
920
271
|
}
|
|
921
|
-
const
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (options.log) {
|
|
927
|
-
console.log(`[UIInteractionPlugin]: Skipping artifact outside output dir. file=${state.file} outDir=${outDir}`);
|
|
928
|
-
}
|
|
929
|
-
return { root, changed: !state.skip && state.instrumented };
|
|
930
|
-
}
|
|
931
|
-
const code = root.toSource({ quote: 'single' });
|
|
932
|
-
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
933
|
-
fs.writeFileSync(outPath, code, 'utf8');
|
|
272
|
+
const [typeArg, propsArg] = call.arguments;
|
|
273
|
+
if (!isFragmentExpression(j, typeArg, fragmentAliases) ||
|
|
274
|
+
propsArg === undefined ||
|
|
275
|
+
!j.ObjectExpression.check(propsArg)) {
|
|
276
|
+
return;
|
|
934
277
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
if (options.log) {
|
|
938
|
-
console.log(`[UIInteractionPlugin]: Unable to write artifact for ${state.file}: ${errorMsg}`);
|
|
939
|
-
}
|
|
278
|
+
if (removePathIdFromObjectExpression(j, propsArg, propName)) {
|
|
279
|
+
changed = true;
|
|
940
280
|
}
|
|
281
|
+
});
|
|
282
|
+
if (changed) {
|
|
283
|
+
writeArtifact(root, filename, options);
|
|
941
284
|
}
|
|
942
|
-
return { root, changed
|
|
285
|
+
return { root, changed };
|
|
943
286
|
};
|
|
944
|
-
exports.
|
|
945
|
-
|
|
287
|
+
exports.applyDTPathIdInstrumentation = applyDTPathIdInstrumentation;
|
|
288
|
+
const applyDTUserInteraction = exports.applyDTPathIdInstrumentation;
|
|
289
|
+
exports.default = applyDTUserInteraction;
|