@dynatrace/react-native-plugin 2.331.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.
Files changed (48) hide show
  1. package/README.md +136 -177
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/com/dynatrace/android/agent/DynatraceRNBridgeImpl.kt +7 -1
  4. package/android/src/main/java/com/dynatrace/android/agent/DynatraceUtils.kt +1 -0
  5. package/android/src/new/java/com/dynatrace/android/agent/DynatraceRNBridge.kt +1 -0
  6. package/android/src/old/java/com/dynatrace/android/agent/DynatraceRNBridge.kt +2 -1
  7. package/files/plugin-runtime.gradle +27 -13
  8. package/files/plugin.gradle +1 -1
  9. package/instrumentation/BabelPluginDynatrace.js +1 -0
  10. package/instrumentation/DynatraceInstrumentation.js +1 -1
  11. package/instrumentation/jsx/CreateElement.js +106 -6
  12. package/instrumentation/jsx/JsxDevRuntime.js +2 -6
  13. package/instrumentation/jsx/JsxRuntime.js +6 -10
  14. package/instrumentation/libs/UserInteraction.js +114 -0
  15. package/instrumentation/libs/community/gesture-handler/Touchables.InstrInfo.js +2 -0
  16. package/instrumentation/libs/community/gesture-handler/Touchables.js +3 -1
  17. package/instrumentation/libs/community/gesture-handler/index.js +3 -1
  18. package/instrumentation/libs/withOnPressMonitoring.js +55 -3
  19. package/ios/DynatraceRNBridge.mm +8 -1
  20. package/lib/core/Application.js +2 -0
  21. package/lib/core/Dynatrace.js +2 -1
  22. package/lib/core/UserPrivacyOptions.js +8 -1
  23. package/lib/core/configuration/ConfigurationHandler.js +21 -0
  24. package/lib/dynatrace-reporter.js +0 -14
  25. package/lib/dynatrace-transformer.js +10 -13
  26. package/lib/features/ui-interaction/Config.js +42 -0
  27. package/lib/features/ui-interaction/IUserInteractionEvent.js +16 -0
  28. package/lib/features/ui-interaction/Plugin.Fragment.Test.js +170 -0
  29. package/lib/features/ui-interaction/Plugin.js +289 -0
  30. package/lib/features/ui-interaction/RootDetection.js +51 -0
  31. package/lib/features/ui-interaction/RootWrapper.js +236 -0
  32. package/lib/features/ui-interaction/Run.js +38 -0
  33. package/lib/features/ui-interaction/Runtime.js +827 -0
  34. package/lib/features/ui-interaction/TouchMetaResolver.js +492 -0
  35. package/lib/features/ui-interaction/Types.js +14 -0
  36. package/lib/next/Dynatrace.js +1 -1
  37. package/lib/next/configuration/INativeRuntimeConfiguration.js +1 -0
  38. package/lib/next/configuration/RuntimeConfigurationObserver.js +47 -12
  39. package/lib/next/events/EventPipeline.js +9 -0
  40. package/package.json +19 -13
  41. package/react-native-dynatrace.podspec +1 -1
  42. package/scripts/Android.js +75 -62
  43. package/scripts/Config.js +12 -1
  44. package/scripts/core/InstrumentCall.js +1 -2
  45. package/scripts/core/LineOffsetAnalyzeCall.js +9 -15
  46. package/src/lib/core/interface/NativeDynatraceBridge.ts +1 -0
  47. package/types.d.ts +22 -9
  48. package/scripts/util/ReactOptions.js +0 -21
@@ -9,7 +9,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- const child_process_1 = require("child_process");
13
12
  const TerminalReporter = (() => {
14
13
  try {
15
14
  return require('metro/src/lib/TerminalReporter');
@@ -24,19 +23,6 @@ const paths = require('../scripts/PathsConstants');
24
23
  const reporter = new TerminalReporter(new Terminal(process.stdout));
25
24
  const updateOrig = TerminalReporter.prototype.update;
26
25
  TerminalReporter.prototype.update = function update(event) {
27
- if ((event === null || event === void 0 ? void 0 : event.type) === 'bundle_build_done') {
28
- (0, child_process_1.exec)('npx lineOffsetDynatrace env prod', (error, stdout, stderr) => {
29
- if (error) {
30
- console.error(`Error: ${error.message}`);
31
- return;
32
- }
33
- if (stderr) {
34
- console.error(`stderr: ${stderr}`);
35
- return;
36
- }
37
- console.log(`${stdout}`);
38
- });
39
- }
40
26
  return updateOrig === null || updateOrig === void 0 ? void 0 : updateOrig.apply(this, arguments);
41
27
  };
42
28
  const update = (event) => __awaiter(void 0, void 0, void 0, function* () {
@@ -1,24 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getUpstreamTransformer = exports.transform = void 0;
4
- const nodePath = require("path");
5
- const PathsConstants_1 = require("../scripts/PathsConstants");
6
4
  const config = require("../scripts/Config");
7
5
  const Logger_1 = require("../scripts/Logger");
8
- const CustomArgumentUtil_1 = require("../scripts/util/CustomArgumentUtil");
6
+ const BabelPluginDynatrace_1 = require("../instrumentation/BabelPluginDynatrace");
9
7
  const instrumentor = require("../instrumentation/DynatraceInstrumentation");
10
- const customArguments = (0, CustomArgumentUtil_1.readCustomArguments)();
11
- const transform = (src, filename, options) => {
12
- if (typeof src === 'object') {
13
- ({ src, filename, options } = src);
14
- }
8
+ const Config_1 = require("../scripts/Config");
9
+ const transform = (args) => {
10
+ var _a;
11
+ let src = args.src;
12
+ const { filename, options } = args;
15
13
  let reactOptions;
16
14
  try {
17
- if (customArguments.isCustomConfigurationPathSet()) {
18
- reactOptions = config.readConfig(nodePath.join(PathsConstants_1.default.getApplicationPath(), customArguments.getCustomConfigurationPath()));
19
- }
20
- else {
21
- reactOptions = config.readConfig(nodePath.join(PathsConstants_1.default.getConfigFilePath()));
15
+ reactOptions = (0, Config_1.readConfigDefault)();
16
+ if (!reactOptions.react.useLegacyJscodeshift) {
17
+ args.plugins = [BabelPluginDynatrace_1.default, ...((_a = args.plugins) !== null && _a !== void 0 ? _a : [])];
18
+ return (0, exports.getUpstreamTransformer)(reactOptions === null || reactOptions === void 0 ? void 0 : reactOptions.react).transform(args);
22
19
  }
23
20
  src = instrumentor.instrument(src, filename, reactOptions.react);
24
21
  }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DT_UII_ROOT_CONFIG = exports.getDTConfig = exports.DEFAULT_DT_CONFIG = void 0;
4
+ const Types_1 = require("./Types");
5
+ exports.DEFAULT_DT_CONFIG = {
6
+ log: true,
7
+ artifactsDir: Types_1.Literals.DefaultArtifactsDir,
8
+ runtimeImport: Types_1.Literals.DefaultRuntimeImport,
9
+ componentNames: {
10
+ root: Types_1.Literals.DefaultRootName,
11
+ },
12
+ ui: {
13
+ skipTags: ['NavigationContainer'],
14
+ pathId: {
15
+ enabled: true,
16
+ propName: 'dtPathID',
17
+ uiRootPropName: 'dtUIRoot',
18
+ injectUiRoot: false,
19
+ skipProviderTags: true,
20
+ blacklist: ['AnalyticsRoot', 'Stack', 'React.Fragment', 'Fragment'],
21
+ },
22
+ },
23
+ };
24
+ const getDTConfig = () => exports.DEFAULT_DT_CONFIG;
25
+ exports.getDTConfig = getDTConfig;
26
+ exports.DT_UII_ROOT_CONFIG = {
27
+ path: {
28
+ withRole: true,
29
+ withIndex: true,
30
+ indexMode: 'global',
31
+ indexStyle: 'bracket',
32
+ withSid: false,
33
+ separator: '/',
34
+ },
35
+ mask: {
36
+ replaceWith: '***',
37
+ testID: (id) => /email|phone/i.test(id),
38
+ text: (s) => s.length > 20,
39
+ },
40
+ click: { capturePosition: 'page' },
41
+ touch: { enabled: true, mode: 'grouped' },
42
+ };
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UserInteractionEventKey = void 0;
4
+ var UserInteractionEventKey;
5
+ (function (UserInteractionEventKey) {
6
+ UserInteractionEventKey["EventHasUserInteraction"] = "characteristics.has_user_interaction";
7
+ UserInteractionEventKey["UiElementDetectedName"] = "ui_element.detected_name";
8
+ UserInteractionEventKey["UiElementComponents"] = "ui_element.components";
9
+ UserInteractionEventKey["UiElementId"] = "ui_element.id";
10
+ UserInteractionEventKey["UiElementNameOrigin"] = "ui_element.name_origin";
11
+ UserInteractionEventKey["InteractionType"] = "interaction.type";
12
+ UserInteractionEventKey["UiElementResponderDetectedName"] = "ui_element.responder.detected_name";
13
+ UserInteractionEventKey["UiElementResponderComponents"] = "ui_element.responder.components";
14
+ UserInteractionEventKey["UiElementResponderOriginName"] = "ui_element.responder.name_origin";
15
+ UserInteractionEventKey["UiElementResponderId"] = "ui_element.responder.id";
16
+ })(UserInteractionEventKey = exports.UserInteractionEventKey || (exports.UserInteractionEventKey = {}));
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsc = require("jscodeshift");
4
+ const Plugin_1 = require("./Plugin");
5
+ const j = jsc.withParser('tsx');
6
+ const runPlugin = (src, filename = 'FragmentCase.tsx') => {
7
+ const root = j(src);
8
+ const out = (0, Plugin_1.applyDTPathIdInstrumentation)(root, filename);
9
+ return out.root.toSource({ quote: 'single' });
10
+ };
11
+ describe('UI interaction Fragment blacklist', () => {
12
+ it('does not inject dtUIRoot by default', () => {
13
+ const code = runPlugin(`
14
+ import React from 'react';
15
+ import { View, Text } from 'react-native';
16
+
17
+ export default function Screen() {
18
+ return (
19
+ <View>
20
+ <Text>Hello</Text>
21
+ </View>
22
+ );
23
+ }
24
+ `);
25
+ expect(code).toContain('<View dtPathID=');
26
+ expect(code).not.toContain('dtUIRoot');
27
+ });
28
+ it('does not instrument React.Fragment', () => {
29
+ const code = runPlugin(`
30
+ import React from 'react';
31
+ import { View, Text } from 'react-native';
32
+
33
+ export default function Screen() {
34
+ return (
35
+ <View>
36
+ <React.Fragment>
37
+ <Text>Hello</Text>
38
+ </React.Fragment>
39
+ </View>
40
+ );
41
+ }
42
+ `);
43
+ expect(code).toContain('<React.Fragment>');
44
+ expect(code).not.toContain('<React.Fragment dtPathID=');
45
+ expect(code).toContain('<View dtPathID=');
46
+ expect(code).toContain('<Text dtPathID=');
47
+ });
48
+ it('does not instrument imported Fragment', () => {
49
+ const code = runPlugin(`
50
+ import React, { Fragment } from 'react';
51
+ import { View, Text } from 'react-native';
52
+
53
+ export default function Screen() {
54
+ return (
55
+ <View>
56
+ <Fragment>
57
+ <Text>Hello</Text>
58
+ </Fragment>
59
+ </View>
60
+ );
61
+ }
62
+ `);
63
+ expect(code).toContain('<Fragment>');
64
+ expect(code).not.toContain('<Fragment dtPathID=');
65
+ expect(code).toContain('<View dtPathID=');
66
+ expect(code).toContain('<Text dtPathID=');
67
+ });
68
+ it('does not instrument aliased Fragment imports', () => {
69
+ const code = runPlugin(`
70
+ import React, { Fragment as F } from 'react';
71
+ import { View, Text } from 'react-native';
72
+
73
+ export default function Screen() {
74
+ return (
75
+ <View>
76
+ <F>
77
+ <Text>Hello</Text>
78
+ </F>
79
+ </View>
80
+ );
81
+ }
82
+ `);
83
+ expect(code).toContain('<F>');
84
+ expect(code).not.toContain('<F dtPathID=');
85
+ expect(code).toContain('<View dtPathID=');
86
+ expect(code).toContain('<Text dtPathID=');
87
+ });
88
+ it('does not instrument empty fragment tags', () => {
89
+ const code = runPlugin(`
90
+ import React from 'react';
91
+ import { View, Text } from 'react-native';
92
+
93
+ export default function Screen() {
94
+ return (
95
+ <>
96
+ <View>
97
+ <Text>Hello</Text>
98
+ </View>
99
+ </>
100
+ );
101
+ }
102
+ `);
103
+ expect(code).toContain('<>');
104
+ expect(code).toContain('<View dtPathID=');
105
+ expect(code).toContain('<Text dtPathID=');
106
+ });
107
+ it('removes existing dtPathID from React.Fragment JSX', () => {
108
+ const code = runPlugin(`
109
+ import React from 'react';
110
+ export default function Screen() {
111
+ return <React.Fragment dtPathID='bad'>Hello</React.Fragment>;
112
+ }
113
+ `);
114
+ expect(code).toContain('<React.Fragment>');
115
+ expect(code).not.toContain('dtPathID=');
116
+ });
117
+ it('removes dtPathID from transpiled jsx Fragment call', () => {
118
+ const code = runPlugin(`
119
+ import { Fragment as _Fragment, jsx as _jsx } from 'react/jsx-runtime';
120
+ export default function Screen() {
121
+ return _jsx(_Fragment, { dtPathID: 'bad', children: 'Hello' });
122
+ }
123
+ `);
124
+ expect(code).toContain('_jsx(_Fragment');
125
+ expect(code).not.toContain('dtPathID');
126
+ });
127
+ it('removes dtPathID from transpiled jsxDEV Fragment call', () => {
128
+ const code = runPlugin(`
129
+ import { Fragment as _Fragment, jsxDEV as _jsxDEV } from 'react/jsx-dev-runtime';
130
+ export default function Screen() {
131
+ return _jsxDEV(_Fragment, { dtPathID: 'bad', children: 'Hello' }, void 0, false, void 0, this);
132
+ }
133
+ `);
134
+ expect(code).toContain('_jsxDEV(_Fragment');
135
+ expect(code).not.toContain('dtPathID');
136
+ });
137
+ it('does not instrument Stack elements', () => {
138
+ const code = runPlugin(`
139
+ import React from 'react';
140
+ import { Stack } from 'expo-router';
141
+ import { View, Text } from 'react-native';
142
+
143
+ export default function Screen() {
144
+ return (
145
+ <View>
146
+ <Stack>
147
+ <Text>Hello</Text>
148
+ </Stack>
149
+ </View>
150
+ );
151
+ }
152
+ `);
153
+ expect(code).toContain('<Stack>');
154
+ expect(code).not.toContain('<Stack dtPathID=');
155
+ expect(code).toContain('<View dtPathID=');
156
+ expect(code).toContain('<Text dtPathID=');
157
+ });
158
+ it('skips instrumentation for Expo package files', () => {
159
+ const src = `
160
+ import React from 'react';
161
+ import { View, Text } from 'react-native';
162
+ export default function Screen() {
163
+ return <View><Text>Hello</Text></View>;
164
+ }
165
+ `;
166
+ const code = runPlugin(src, 'node_modules/expo-router/build/qualified-entry.js');
167
+ expect(code).toEqual(j(src).toSource({ quote: 'single' }));
168
+ expect(code).not.toContain('dtPathID');
169
+ });
170
+ });
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyDTPathIdInstrumentation = void 0;
4
+ const fs = require("node:fs");
5
+ const path = require("node:path");
6
+ const micromatch_1 = require("micromatch");
7
+ const jsc = require("jscodeshift");
8
+ const Types_1 = require("./Types");
9
+ const getName = (j, n) => {
10
+ if (j.JSXIdentifier.check(n)) {
11
+ return n.name;
12
+ }
13
+ if (j.JSXMemberExpression.check(n)) {
14
+ let c = n;
15
+ while (j.JSXMemberExpression.check(c)) {
16
+ c = c.property;
17
+ }
18
+ return j.JSXIdentifier.check(c) ? c.name : 'Anonymous';
19
+ }
20
+ return 'Anonymous';
21
+ };
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;
43
+ }
44
+ if (!REACT_FRAGMENT_SOURCES.has(source.value)) {
45
+ return;
46
+ }
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);
57
+ }
58
+ }
59
+ });
60
+ return fragmentAliases;
61
+ };
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);
67
+ }
68
+ if (j.MemberExpression.check(callee) &&
69
+ j.Identifier.check(callee.property)) {
70
+ return /jsx/i.test(callee.property.name);
71
+ }
72
+ return false;
73
+ };
74
+ const isFragmentExpression = (j, expr, fragmentAliases) => {
75
+ if (j.Identifier.check(expr)) {
76
+ return fragmentAliases.has(expr.name);
77
+ }
78
+ if (j.MemberExpression.check(expr) &&
79
+ expr.computed !== true &&
80
+ j.Identifier.check(expr.property)) {
81
+ return expr.property.name === 'Fragment';
82
+ }
83
+ return false;
84
+ };
85
+ const removePathIdFromObjectExpression = (j, objectExpression, propName) => {
86
+ if (!j.ObjectExpression.check(objectExpression)) {
87
+ return false;
88
+ }
89
+ const before = objectExpression.properties.length;
90
+ objectExpression.properties = objectExpression.properties.filter((property) => {
91
+ if (!j.ObjectProperty.check(property)) {
92
+ return true;
93
+ }
94
+ if (j.Identifier.check(property.key) &&
95
+ property.key.name === propName) {
96
+ return false;
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;
111
+ }
112
+ if (EXPO_PATH_SEGMENTS.some((segment) => normalizedPosix.includes(segment))) {
113
+ return true;
114
+ }
115
+ if (SELF_INSTRUMENTATION_PATH_SEGMENTS.some((segment) => normalizedPosix.includes(segment))) {
116
+ return true;
117
+ }
118
+ return (0, micromatch_1.isMatch)(normalized, exclude);
119
+ };
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}`));
125
+ };
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;
134
+ }
135
+ const code = root.toSource({ quote: 'single' });
136
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
137
+ fs.writeFileSync(outPath, code, 'utf8');
138
+ }
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
+ }
144
+ }
145
+ };
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) {
192
+ return false;
193
+ }
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;
207
+ }
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;
240
+ }
241
+ return;
242
+ }
243
+ if (injectUiRoot && hasUiRootAttribute) {
244
+ rootMarkerAssigned = true;
245
+ }
246
+ if (isFragmentName(tagName, blacklist, fragmentAliases)) {
247
+ if (removeInstrumentationAttributes(opening)) {
248
+ changed = true;
249
+ }
250
+ return;
251
+ }
252
+ if (hasPathIdAttribute(opening)) {
253
+ if (maybeAssignUiRoot(opening, hasUiRootAttribute)) {
254
+ changed = true;
255
+ }
256
+ return;
257
+ }
258
+ addPathIdAttribute(opening, tagName);
259
+ if (maybeAssignUiRoot(opening, hasUiRootAttribute)) {
260
+ changed = true;
261
+ }
262
+ changed = true;
263
+ };
264
+ root.find(j.JSXOpeningElement).forEach((path) => {
265
+ processOpeningElement(path.node);
266
+ });
267
+ root.find(j.CallExpression).forEach((path) => {
268
+ const call = path.node;
269
+ if (!isLikelyJsxFactoryCall(j, call.callee)) {
270
+ return;
271
+ }
272
+ const [typeArg, propsArg] = call.arguments;
273
+ if (!isFragmentExpression(j, typeArg, fragmentAliases) ||
274
+ propsArg === undefined ||
275
+ !j.ObjectExpression.check(propsArg)) {
276
+ return;
277
+ }
278
+ if (removePathIdFromObjectExpression(j, propsArg, propName)) {
279
+ changed = true;
280
+ }
281
+ });
282
+ if (changed) {
283
+ writeArtifact(root, filename, options);
284
+ }
285
+ return { root, changed };
286
+ };
287
+ exports.applyDTPathIdInstrumentation = applyDTPathIdInstrumentation;
288
+ const applyDTUserInteraction = exports.applyDTPathIdInstrumentation;
289
+ exports.default = applyDTUserInteraction;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectRootRegistrations = void 0;
4
+ const j = require("jscodeshift");
5
+ const isProjectFile = (filename) => filename && !/\/node_modules\//.test(filename);
6
+ const detectRootRegistrations = (root, filename) => {
7
+ if (!isProjectFile(filename)) {
8
+ return [];
9
+ }
10
+ const regs = [];
11
+ root.find(j.ImportDeclaration).forEach((p) => {
12
+ const importSource = (p.node.source.value || '');
13
+ if (importSource === 'expo-router' ||
14
+ importSource === 'expo-router/entry') {
15
+ regs.push({ type: 'expo_router_entry', path: p });
16
+ }
17
+ });
18
+ root.find(j.CallExpression, {
19
+ callee: { type: 'Identifier', name: 'registerRootComponent' },
20
+ }).forEach((p) => {
21
+ var _a;
22
+ const componentFactoryArg = (_a = p.node.arguments) === null || _a === void 0 ? void 0 : _a[0];
23
+ if (!componentFactoryArg) {
24
+ return;
25
+ }
26
+ regs.push({
27
+ type: 'expo_register',
28
+ path: p,
29
+ });
30
+ });
31
+ root.find(j.CallExpression, {
32
+ callee: {
33
+ type: 'MemberExpression',
34
+ object: { type: 'Identifier', name: 'AppRegistry' },
35
+ property: { type: 'Identifier', name: 'registerComponent' },
36
+ },
37
+ }).forEach((p) => {
38
+ var _a, _b;
39
+ const appNameArg = (_a = p.node.arguments) === null || _a === void 0 ? void 0 : _a[0];
40
+ const componentFactoryArg = (_b = p.node.arguments) === null || _b === void 0 ? void 0 : _b[1];
41
+ if (!appNameArg || !componentFactoryArg) {
42
+ return;
43
+ }
44
+ regs.push({
45
+ type: 'rn_register',
46
+ path: p,
47
+ });
48
+ });
49
+ return regs;
50
+ };
51
+ exports.detectRootRegistrations = detectRootRegistrations;