@truewatchtech/react-native-mobile-babel-plugin 0.4.3-alpha.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 (80) hide show
  1. package/README.md +185 -0
  2. package/lib/commonjs/actions/global.js +19 -0
  3. package/lib/commonjs/actions/global.js.map +1 -0
  4. package/lib/commonjs/actions/rum/components.js +81 -0
  5. package/lib/commonjs/actions/rum/components.js.map +1 -0
  6. package/lib/commonjs/actions/rum/content.js +168 -0
  7. package/lib/commonjs/actions/rum/content.js.map +1 -0
  8. package/lib/commonjs/actions/rum/index.js +68 -0
  9. package/lib/commonjs/actions/rum/index.js.map +1 -0
  10. package/lib/commonjs/actions/rum/memoization.js +88 -0
  11. package/lib/commonjs/actions/rum/memoization.js.map +1 -0
  12. package/lib/commonjs/actions/rum/metadata.js +63 -0
  13. package/lib/commonjs/actions/rum/metadata.js.map +1 -0
  14. package/lib/commonjs/actions/rum/tap.js +45 -0
  15. package/lib/commonjs/actions/rum/tap.js.map +1 -0
  16. package/lib/commonjs/constants.js +25 -0
  17. package/lib/commonjs/constants.js.map +1 -0
  18. package/lib/commonjs/index.js +48 -0
  19. package/lib/commonjs/index.js.map +1 -0
  20. package/lib/commonjs/options.js +23 -0
  21. package/lib/commonjs/options.js.map +1 -0
  22. package/lib/commonjs/state.js +32 -0
  23. package/lib/commonjs/state.js.map +1 -0
  24. package/lib/commonjs/types.js +6 -0
  25. package/lib/commonjs/types.js.map +1 -0
  26. package/lib/commonjs/utils/jsx.js +43 -0
  27. package/lib/commonjs/utils/jsx.js.map +1 -0
  28. package/lib/module/actions/global.js +13 -0
  29. package/lib/module/actions/global.js.map +1 -0
  30. package/lib/module/actions/rum/components.js +73 -0
  31. package/lib/module/actions/rum/components.js.map +1 -0
  32. package/lib/module/actions/rum/content.js +161 -0
  33. package/lib/module/actions/rum/content.js.map +1 -0
  34. package/lib/module/actions/rum/index.js +61 -0
  35. package/lib/module/actions/rum/index.js.map +1 -0
  36. package/lib/module/actions/rum/memoization.js +82 -0
  37. package/lib/module/actions/rum/memoization.js.map +1 -0
  38. package/lib/module/actions/rum/metadata.js +56 -0
  39. package/lib/module/actions/rum/metadata.js.map +1 -0
  40. package/lib/module/actions/rum/tap.js +38 -0
  41. package/lib/module/actions/rum/tap.js.map +1 -0
  42. package/lib/module/constants.js +19 -0
  43. package/lib/module/constants.js.map +1 -0
  44. package/lib/module/index.js +42 -0
  45. package/lib/module/index.js.map +1 -0
  46. package/lib/module/options.js +17 -0
  47. package/lib/module/options.js.map +1 -0
  48. package/lib/module/state.js +25 -0
  49. package/lib/module/state.js.map +1 -0
  50. package/lib/module/types.js +2 -0
  51. package/lib/module/types.js.map +1 -0
  52. package/lib/module/utils/jsx.js +36 -0
  53. package/lib/module/utils/jsx.js.map +1 -0
  54. package/lib/typescript/actions/global.d.ts +2 -0
  55. package/lib/typescript/actions/rum/components.d.ts +5 -0
  56. package/lib/typescript/actions/rum/content.d.ts +6 -0
  57. package/lib/typescript/actions/rum/index.d.ts +4 -0
  58. package/lib/typescript/actions/rum/memoization.d.ts +4 -0
  59. package/lib/typescript/actions/rum/metadata.d.ts +4 -0
  60. package/lib/typescript/actions/rum/tap.d.ts +6 -0
  61. package/lib/typescript/constants.d.ts +4 -0
  62. package/lib/typescript/index.d.ts +5 -0
  63. package/lib/typescript/options.d.ts +2 -0
  64. package/lib/typescript/state.d.ts +5 -0
  65. package/lib/typescript/types.d.ts +56 -0
  66. package/lib/typescript/utils/jsx.d.ts +4 -0
  67. package/package.json +68 -0
  68. package/src/actions/global.ts +48 -0
  69. package/src/actions/rum/components.ts +128 -0
  70. package/src/actions/rum/content.ts +274 -0
  71. package/src/actions/rum/index.ts +154 -0
  72. package/src/actions/rum/memoization.ts +136 -0
  73. package/src/actions/rum/metadata.ts +147 -0
  74. package/src/actions/rum/tap.ts +148 -0
  75. package/src/constants.ts +21 -0
  76. package/src/index.ts +50 -0
  77. package/src/options.ts +26 -0
  78. package/src/state.ts +39 -0
  79. package/src/types.ts +72 -0
  80. package/src/utils/jsx.ts +50 -0
@@ -0,0 +1 @@
1
+ {"version":3,"names":["mergeOptions","options","componentOptions","components","actionNameAttribute","tracked","useContent","undefined","useNamePrefix"],"sources":["options.ts"],"sourcesContent":["/*\n * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel\n * interaction tracking implementation, licensed under Apache-2.0.\n */\n\nimport type { NormalizedPluginOptions, PluginOptions } from './types';\n\nexport function mergeOptions(\n options: PluginOptions = {}\n): NormalizedPluginOptions {\n const componentOptions = options.components || {};\n return {\n actionNameAttribute: options.actionNameAttribute,\n components: {\n tracked: componentOptions.tracked || [],\n useContent:\n componentOptions.useContent === undefined\n ? true\n : componentOptions.useContent,\n useNamePrefix:\n componentOptions.useNamePrefix === undefined\n ? true\n : componentOptions.useNamePrefix,\n },\n };\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;;AAIA,OAAO,SAASA,YAAYA,CAC1BC,OAAsB,GAAG,CAAC,CAAC,EACF;EACzB,MAAMC,gBAAgB,GAAGD,OAAO,CAACE,UAAU,IAAI,CAAC,CAAC;EACjD,OAAO;IACLC,mBAAmB,EAAEH,OAAO,CAACG,mBAAmB;IAChDD,UAAU,EAAE;MACVE,OAAO,EAAEH,gBAAgB,CAACG,OAAO,IAAI,EAAE;MACvCC,UAAU,EACRJ,gBAAgB,CAACI,UAAU,KAAKC,SAAS,GACrC,IAAI,GACJL,gBAAgB,CAACI,UAAU;MACjCE,aAAa,EACXN,gBAAgB,CAACM,aAAa,KAAKD,SAAS,GACxC,IAAI,GACJL,gBAAgB,CAACM;IACzB;EACF,CAAC;AACH","ignoreList":[]}
@@ -0,0 +1,25 @@
1
+ /*
2
+ * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
3
+ * interaction tracking implementation, licensed under Apache-2.0.
4
+ */
5
+
6
+ import { createTrackedComponents } from './actions/rum/components';
7
+ export function isSkippedFile(state) {
8
+ var _state$file, _state$file2;
9
+ const caller = (_state$file = state.file) === null || _state$file === void 0 || (_state$file = _state$file.opts) === null || _state$file === void 0 ? void 0 : _state$file.caller;
10
+ const filename = state.filename || ((_state$file2 = state.file) === null || _state$file2 === void 0 || (_state$file2 = _state$file2.opts) === null || _state$file2 === void 0 ? void 0 : _state$file2.filename);
11
+ return (caller === null || caller === void 0 ? void 0 : caller.platform) === 'web' || Boolean(filename && filename.includes('node_modules'));
12
+ }
13
+
14
+ /** Initializes per-file state; identifiers and memoization never leak across files. */
15
+ export function initializePluginState(t, programPath, state, options) {
16
+ state._ftTrackedComponents = createTrackedComponents(t, programPath, options);
17
+ state._ftCustomNames = new Set(options.components.tracked.map(component => component.name));
18
+ state._ftMemoizedHandlers = new Set();
19
+ state._ftHasWrappedAction = false;
20
+ state._ftNeedsContentRuntime = false;
21
+ state._ftExtractTextIdentifier = programPath.scope.generateUidIdentifier('ftExtractText');
22
+ state._ftReactIdentifier = programPath.scope.generateUidIdentifier('FTReact');
23
+ state._ftTrackingIdentifier = programPath.scope.generateUidIdentifier('FTBabelInteractionTracking');
24
+ }
25
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["createTrackedComponents","isSkippedFile","state","_state$file","_state$file2","caller","file","opts","filename","platform","Boolean","includes","initializePluginState","t","programPath","options","_ftTrackedComponents","_ftCustomNames","Set","components","tracked","map","component","name","_ftMemoizedHandlers","_ftHasWrappedAction","_ftNeedsContentRuntime","_ftExtractTextIdentifier","scope","generateUidIdentifier","_ftReactIdentifier","_ftTrackingIdentifier"],"sources":["state.ts"],"sourcesContent":["/*\n * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel\n * interaction tracking implementation, licensed under Apache-2.0.\n */\n\nimport type * as Babel from '@babel/core';\nimport { createTrackedComponents } from './actions/rum/components';\nimport type { NormalizedPluginOptions, PluginState } from './types';\n\nexport function isSkippedFile(state: PluginState): boolean {\n const caller = state.file?.opts?.caller as { platform?: string } | undefined;\n const filename = state.filename || state.file?.opts?.filename;\n return (\n caller?.platform === 'web' ||\n Boolean(filename && filename.includes('node_modules'))\n );\n}\n\n/** Initializes per-file state; identifiers and memoization never leak across files. */\nexport function initializePluginState(\n t: typeof Babel.types,\n programPath: Babel.NodePath<Babel.types.Program>,\n state: PluginState,\n options: NormalizedPluginOptions\n): void {\n state._ftTrackedComponents = createTrackedComponents(t, programPath, options);\n state._ftCustomNames = new Set(\n options.components.tracked.map((component) => component.name)\n );\n state._ftMemoizedHandlers = new Set();\n state._ftHasWrappedAction = false;\n state._ftNeedsContentRuntime = false;\n state._ftExtractTextIdentifier =\n programPath.scope.generateUidIdentifier('ftExtractText');\n state._ftReactIdentifier = programPath.scope.generateUidIdentifier('FTReact');\n state._ftTrackingIdentifier = programPath.scope.generateUidIdentifier(\n 'FTBabelInteractionTracking'\n );\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;;AAGA,SAASA,uBAAuB,QAAQ,0BAA0B;AAGlE,OAAO,SAASC,aAAaA,CAACC,KAAkB,EAAW;EAAA,IAAAC,WAAA,EAAAC,YAAA;EACzD,MAAMC,MAAM,IAAAF,WAAA,GAAGD,KAAK,CAACI,IAAI,cAAAH,WAAA,gBAAAA,WAAA,GAAVA,WAAA,CAAYI,IAAI,cAAAJ,WAAA,uBAAhBA,WAAA,CAAkBE,MAA2C;EAC5E,MAAMG,QAAQ,GAAGN,KAAK,CAACM,QAAQ,MAAAJ,YAAA,GAAIF,KAAK,CAACI,IAAI,cAAAF,YAAA,gBAAAA,YAAA,GAAVA,YAAA,CAAYG,IAAI,cAAAH,YAAA,uBAAhBA,YAAA,CAAkBI,QAAQ;EAC7D,OACE,CAAAH,MAAM,aAANA,MAAM,uBAANA,MAAM,CAAEI,QAAQ,MAAK,KAAK,IAC1BC,OAAO,CAACF,QAAQ,IAAIA,QAAQ,CAACG,QAAQ,CAAC,cAAc,CAAC,CAAC;AAE1D;;AAEA;AACA,OAAO,SAASC,qBAAqBA,CACnCC,CAAqB,EACrBC,WAAgD,EAChDZ,KAAkB,EAClBa,OAAgC,EAC1B;EACNb,KAAK,CAACc,oBAAoB,GAAGhB,uBAAuB,CAACa,CAAC,EAAEC,WAAW,EAAEC,OAAO,CAAC;EAC7Eb,KAAK,CAACe,cAAc,GAAG,IAAIC,GAAG,CAC5BH,OAAO,CAACI,UAAU,CAACC,OAAO,CAACC,GAAG,CAAEC,SAAS,IAAKA,SAAS,CAACC,IAAI,CAC9D,CAAC;EACDrB,KAAK,CAACsB,mBAAmB,GAAG,IAAIN,GAAG,CAAC,CAAC;EACrChB,KAAK,CAACuB,mBAAmB,GAAG,KAAK;EACjCvB,KAAK,CAACwB,sBAAsB,GAAG,KAAK;EACpCxB,KAAK,CAACyB,wBAAwB,GAC5Bb,WAAW,CAACc,KAAK,CAACC,qBAAqB,CAAC,eAAe,CAAC;EAC1D3B,KAAK,CAAC4B,kBAAkB,GAAGhB,WAAW,CAACc,KAAK,CAACC,qBAAqB,CAAC,SAAS,CAAC;EAC7E3B,KAAK,CAAC6B,qBAAqB,GAAGjB,WAAW,CAACc,KAAK,CAACC,qBAAqB,CACnE,4BACF,CAAC;AACH","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["types.ts"],"sourcesContent":["/*\n * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel\n * interaction tracking implementation, licensed under Apache-2.0.\n */\n\nimport type * as Babel from '@babel/core';\n\nexport type TrackedHandler = {\n event: string;\n actionType?: string;\n mode?: 'default' | 'delayed';\n};\n\nexport type TrackedComponent = {\n name: string;\n useContent?: boolean;\n useNamePrefix?: boolean;\n contentProp?: string;\n handlers: TrackedHandler[];\n};\n\nexport type PluginOptions = {\n actionNameAttribute?: string;\n components?: {\n useContent?: boolean;\n useNamePrefix?: boolean;\n tracked?: TrackedComponent[];\n };\n};\n\nexport type NormalizedPluginOptions = {\n actionNameAttribute?: string;\n components: {\n useContent: boolean;\n useNamePrefix: boolean;\n tracked: TrackedComponent[];\n };\n};\n\nexport type TrackedComponentData = Omit<TrackedComponent, 'name'> & {\n importedName?: string;\n isCustom: boolean;\n useContent: boolean;\n useNamePrefix: boolean;\n};\n\nexport type ActionMetadata = {\n accessibilityLabels: Babel.types.StringLiteral[];\n actionNames: Babel.types.StringLiteral[];\n component: TrackedComponentData;\n componentName: string;\n customActionNames: Babel.types.StringLiteral[];\n getContent: Babel.types.ArrowFunctionExpression | null;\n};\n\nexport type PluginState = Babel.PluginPass & {\n _ftCustomNames: Set<string>;\n _ftExtractTextIdentifier: Babel.types.Identifier;\n _ftHasWrappedAction: boolean;\n _ftMemoizedHandlers: Set<Babel.types.Node>;\n _ftNeedsContentRuntime: boolean;\n _ftReactIdentifier: Babel.types.Identifier;\n _ftSkip: boolean;\n _ftTrackedComponents: Record<string, TrackedComponentData>;\n _ftTrackingIdentifier: Babel.types.Identifier;\n};\n\nexport type PluginAPI = typeof Babel & Babel.ConfigAPI;\n\nexport type JSXAttributePath = Babel.NodePath<\n Babel.types.JSXAttribute | Babel.types.JSXSpreadAttribute\n>;\n"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
3
+ * interaction tracking implementation, licensed under Apache-2.0.
4
+ */
5
+
6
+ export function getNodeName(t, node) {
7
+ if (!node) {
8
+ return null;
9
+ }
10
+ if (t.isIdentifier(node) || t.isJSXIdentifier(node)) {
11
+ return node.name;
12
+ }
13
+ if (t.isStringLiteral(node)) {
14
+ return node.value;
15
+ }
16
+ if (t.isJSXNamespacedName(node)) {
17
+ return `${node.namespace.name}:${node.name.name}`;
18
+ }
19
+ if (t.isJSXMemberExpression(node)) {
20
+ let nodeName = node.property.name;
21
+ let nodeTracker = node.object;
22
+ while (t.isJSXMemberExpression(nodeTracker)) {
23
+ nodeName = `${nodeTracker.property.name}.${nodeName}`;
24
+ nodeTracker = nodeTracker.object;
25
+ }
26
+ return t.isJSXIdentifier(nodeTracker) ? `${nodeTracker.name}.${nodeName}` : null;
27
+ }
28
+ if ('name' in node && node.name && typeof node.name !== 'string') {
29
+ return getNodeName(t, node.name);
30
+ }
31
+ return null;
32
+ }
33
+ export function getDirectAttributePaths(path) {
34
+ return path.get('openingElement.attributes');
35
+ }
36
+ //# sourceMappingURL=jsx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["getNodeName","t","node","isIdentifier","isJSXIdentifier","name","isStringLiteral","value","isJSXNamespacedName","namespace","isJSXMemberExpression","nodeName","property","nodeTracker","object","getDirectAttributePaths","path","get"],"sources":["jsx.ts"],"sourcesContent":["/*\n * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel\n * interaction tracking implementation, licensed under Apache-2.0.\n */\n\nimport type * as Babel from '@babel/core';\nimport type { JSXAttributePath } from '../types';\n\nexport function getNodeName(\n t: typeof Babel.types,\n node: Babel.types.Node | null | undefined\n): string | null {\n if (!node) {\n return null;\n }\n if (t.isIdentifier(node) || t.isJSXIdentifier(node)) {\n return node.name;\n }\n if (t.isStringLiteral(node)) {\n return node.value;\n }\n if (t.isJSXNamespacedName(node)) {\n return `${node.namespace.name}:${node.name.name}`;\n }\n if (t.isJSXMemberExpression(node)) {\n let nodeName = node.property.name;\n let nodeTracker:\n | Babel.types.JSXIdentifier\n | Babel.types.JSXMemberExpression = node.object;\n\n while (t.isJSXMemberExpression(nodeTracker)) {\n nodeName = `${nodeTracker.property.name}.${nodeName}`;\n nodeTracker = nodeTracker.object;\n }\n\n return t.isJSXIdentifier(nodeTracker)\n ? `${nodeTracker.name}.${nodeName}`\n : null;\n }\n if ('name' in node && node.name && typeof node.name !== 'string') {\n return getNodeName(t, node.name);\n }\n return null;\n}\n\nexport function getDirectAttributePaths(\n path: Babel.NodePath<Babel.types.JSXElement>\n): JSXAttributePath[] {\n return path.get('openingElement.attributes') as JSXAttributePath[];\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;;AAKA,OAAO,SAASA,WAAWA,CACzBC,CAAqB,EACrBC,IAAyC,EAC1B;EACf,IAAI,CAACA,IAAI,EAAE;IACT,OAAO,IAAI;EACb;EACA,IAAID,CAAC,CAACE,YAAY,CAACD,IAAI,CAAC,IAAID,CAAC,CAACG,eAAe,CAACF,IAAI,CAAC,EAAE;IACnD,OAAOA,IAAI,CAACG,IAAI;EAClB;EACA,IAAIJ,CAAC,CAACK,eAAe,CAACJ,IAAI,CAAC,EAAE;IAC3B,OAAOA,IAAI,CAACK,KAAK;EACnB;EACA,IAAIN,CAAC,CAACO,mBAAmB,CAACN,IAAI,CAAC,EAAE;IAC/B,OAAO,GAAGA,IAAI,CAACO,SAAS,CAACJ,IAAI,IAAIH,IAAI,CAACG,IAAI,CAACA,IAAI,EAAE;EACnD;EACA,IAAIJ,CAAC,CAACS,qBAAqB,CAACR,IAAI,CAAC,EAAE;IACjC,IAAIS,QAAQ,GAAGT,IAAI,CAACU,QAAQ,CAACP,IAAI;IACjC,IAAIQ,WAE+B,GAAGX,IAAI,CAACY,MAAM;IAEjD,OAAOb,CAAC,CAACS,qBAAqB,CAACG,WAAW,CAAC,EAAE;MAC3CF,QAAQ,GAAG,GAAGE,WAAW,CAACD,QAAQ,CAACP,IAAI,IAAIM,QAAQ,EAAE;MACrDE,WAAW,GAAGA,WAAW,CAACC,MAAM;IAClC;IAEA,OAAOb,CAAC,CAACG,eAAe,CAACS,WAAW,CAAC,GACjC,GAAGA,WAAW,CAACR,IAAI,IAAIM,QAAQ,EAAE,GACjC,IAAI;EACV;EACA,IAAI,MAAM,IAAIT,IAAI,IAAIA,IAAI,CAACG,IAAI,IAAI,OAAOH,IAAI,CAACG,IAAI,KAAK,QAAQ,EAAE;IAChE,OAAOL,WAAW,CAACC,CAAC,EAAEC,IAAI,CAACG,IAAI,CAAC;EAClC;EACA,OAAO,IAAI;AACb;AAEA,OAAO,SAASU,uBAAuBA,CACrCC,IAA4C,EACxB;EACpB,OAAOA,IAAI,CAACC,GAAG,CAAC,2BAA2B,CAAC;AAC9C","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ import type * as Babel from '@babel/core';
2
+ export declare function insertPluginEnabledFlag(t: typeof Babel.types, programPath: Babel.NodePath<Babel.types.Program>): void;
@@ -0,0 +1,5 @@
1
+ import type * as Babel from '@babel/core';
2
+ import type { NormalizedPluginOptions, TrackedComponentData } from '../../types';
3
+ export declare function createTrackedComponents(t: typeof Babel.types, programPath: Babel.NodePath<Babel.types.Program>, options: NormalizedPluginOptions): Record<string, TrackedComponentData>;
4
+ export declare function isInsideConfiguredComponent(t: typeof Babel.types, path: Babel.NodePath<Babel.types.JSXElement>, customComponentNames: Set<string>): boolean;
5
+ export declare function addRequiredTextInputHandler(t: typeof Babel.types, path: Babel.NodePath<Babel.types.JSXElement>, component: TrackedComponentData, componentName: string): void;
@@ -0,0 +1,6 @@
1
+ import type * as Babel from '@babel/core';
2
+ import type { PluginState, TrackedComponentData } from '../../types';
3
+ /** Checks whether moving content into a memo callback preserves every binding. */
4
+ export declare function isContentVisibleFromScope(t: typeof Babel.types, path: Babel.NodePath<Babel.types.JSXElement>, component: TrackedComponentData, destinationScope: Babel.NodePath['scope']): boolean;
5
+ /** Matches Datadog's click-time evaluation of content props and JSX children. */
6
+ export declare function buildContentGetter(t: typeof Babel.types, path: Babel.NodePath<Babel.types.JSXElement>, component: TrackedComponentData, state: PluginState): Babel.types.ArrowFunctionExpression | null;
@@ -0,0 +1,4 @@
1
+ import type * as Babel from '@babel/core';
2
+ import type { NormalizedPluginOptions, PluginState } from '../../types';
3
+ export declare function handleJSXElement(t: typeof Babel.types, path: Babel.NodePath<Babel.types.JSXElement>, state: PluginState, options: NormalizedPluginOptions): void;
4
+ export declare function insertRuntimeImports(t: typeof Babel.types, programPath: Babel.NodePath<Babel.types.Program>, state: PluginState): void;
@@ -0,0 +1,4 @@
1
+ import type * as Babel from '@babel/core';
2
+ import type { ActionMetadata, PluginState, TrackedHandler } from '../../types';
3
+ /** Wraps the hook callback once so JSX references retain their memoized identity. */
4
+ export declare function wrapMemoizedHandler(t: typeof Babel.types, path: Babel.NodePath<Babel.types.JSXAttribute>, expression: Babel.types.Expression, metadata: ActionMetadata, state: PluginState, mode: TrackedHandler['mode'], actionType: TrackedHandler['actionType']): boolean;
@@ -0,0 +1,4 @@
1
+ import type * as Babel from '@babel/core';
2
+ import type { ActionMetadata, NormalizedPluginOptions, PluginState, TrackedComponentData } from '../../types';
3
+ export declare function buildMetadata(t: typeof Babel.types, path: Babel.NodePath<Babel.types.JSXElement>, componentName: string, component: TrackedComponentData, options: NormalizedPluginOptions, state: PluginState): ActionMetadata;
4
+ export declare function buildTargetObject(t: typeof Babel.types, metadata: ActionMetadata, handlerArgsIdentifier: Babel.types.Identifier, state: PluginState): Babel.types.ObjectExpression;
@@ -0,0 +1,6 @@
1
+ import type * as Babel from '@babel/core';
2
+ import type { ActionMetadata, PluginState, TrackedHandler } from '../../types';
3
+ /** Wraps a handler, or a handler factory when delayed mode is configured. */
4
+ export declare function buildHandlerWrapper(t: typeof Babel.types, originalExpression: Babel.types.Expression, metadata: ActionMetadata, state: PluginState, scope: Babel.NodePath['scope'], mode?: TrackedHandler['mode'], actionType?: TrackedHandler['actionType']): Babel.types.ArrowFunctionExpression;
5
+ /** Resolves a conditional handler once at render time, preserving non-functions. */
6
+ export declare function buildGuardedHandlerWrapper(t: typeof Babel.types, originalExpression: Babel.types.Expression, metadata: ActionMetadata, state: PluginState, scope: Babel.NodePath['scope'], mode?: TrackedHandler['mode'], actionType?: TrackedHandler['actionType']): Babel.types.CallExpression;
@@ -0,0 +1,4 @@
1
+ export declare const RUNTIME_PACKAGE = "@truewatchtech/react-native-mobile";
2
+ export declare const ACTION_NAME_ATTRIBUTE = "ft-action-name";
3
+ export declare const PLUGIN_ENABLED_FLAG = "__FT_RN_BABEL_PLUGIN_ENABLED__";
4
+ export declare const NATIVE_COMPONENT_HANDLERS: Record<string, string[]>;
@@ -0,0 +1,5 @@
1
+ import type * as Babel from '@babel/core';
2
+ import type { PluginOptions, PluginState } from './types';
3
+ export type { PluginOptions, TrackedComponent, TrackedHandler } from './types';
4
+ declare const plugin: (api: typeof Babel & Babel.ConfigAPI, options: PluginOptions, dirname: string) => Babel.PluginObj<PluginState>;
5
+ export default plugin;
@@ -0,0 +1,2 @@
1
+ import type { NormalizedPluginOptions, PluginOptions } from './types';
2
+ export declare function mergeOptions(options?: PluginOptions): NormalizedPluginOptions;
@@ -0,0 +1,5 @@
1
+ import type * as Babel from '@babel/core';
2
+ import type { NormalizedPluginOptions, PluginState } from './types';
3
+ export declare function isSkippedFile(state: PluginState): boolean;
4
+ /** Initializes per-file state; identifiers and memoization never leak across files. */
5
+ export declare function initializePluginState(t: typeof Babel.types, programPath: Babel.NodePath<Babel.types.Program>, state: PluginState, options: NormalizedPluginOptions): void;
@@ -0,0 +1,56 @@
1
+ import type * as Babel from '@babel/core';
2
+ export type TrackedHandler = {
3
+ event: string;
4
+ actionType?: string;
5
+ mode?: 'default' | 'delayed';
6
+ };
7
+ export type TrackedComponent = {
8
+ name: string;
9
+ useContent?: boolean;
10
+ useNamePrefix?: boolean;
11
+ contentProp?: string;
12
+ handlers: TrackedHandler[];
13
+ };
14
+ export type PluginOptions = {
15
+ actionNameAttribute?: string;
16
+ components?: {
17
+ useContent?: boolean;
18
+ useNamePrefix?: boolean;
19
+ tracked?: TrackedComponent[];
20
+ };
21
+ };
22
+ export type NormalizedPluginOptions = {
23
+ actionNameAttribute?: string;
24
+ components: {
25
+ useContent: boolean;
26
+ useNamePrefix: boolean;
27
+ tracked: TrackedComponent[];
28
+ };
29
+ };
30
+ export type TrackedComponentData = Omit<TrackedComponent, 'name'> & {
31
+ importedName?: string;
32
+ isCustom: boolean;
33
+ useContent: boolean;
34
+ useNamePrefix: boolean;
35
+ };
36
+ export type ActionMetadata = {
37
+ accessibilityLabels: Babel.types.StringLiteral[];
38
+ actionNames: Babel.types.StringLiteral[];
39
+ component: TrackedComponentData;
40
+ componentName: string;
41
+ customActionNames: Babel.types.StringLiteral[];
42
+ getContent: Babel.types.ArrowFunctionExpression | null;
43
+ };
44
+ export type PluginState = Babel.PluginPass & {
45
+ _ftCustomNames: Set<string>;
46
+ _ftExtractTextIdentifier: Babel.types.Identifier;
47
+ _ftHasWrappedAction: boolean;
48
+ _ftMemoizedHandlers: Set<Babel.types.Node>;
49
+ _ftNeedsContentRuntime: boolean;
50
+ _ftReactIdentifier: Babel.types.Identifier;
51
+ _ftSkip: boolean;
52
+ _ftTrackedComponents: Record<string, TrackedComponentData>;
53
+ _ftTrackingIdentifier: Babel.types.Identifier;
54
+ };
55
+ export type PluginAPI = typeof Babel & Babel.ConfigAPI;
56
+ export type JSXAttributePath = Babel.NodePath<Babel.types.JSXAttribute | Babel.types.JSXSpreadAttribute>;
@@ -0,0 +1,4 @@
1
+ import type * as Babel from '@babel/core';
2
+ import type { JSXAttributePath } from '../types';
3
+ export declare function getNodeName(t: typeof Babel.types, node: Babel.types.Node | null | undefined): string | null;
4
+ export declare function getDirectAttributePaths(path: Babel.NodePath<Babel.types.JSXElement>): JSXAttributePath[];
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@truewatchtech/react-native-mobile-babel-plugin",
3
+ "version": "0.4.3-alpha.1",
4
+ "description": "Babel plugin for automatic TrueWatch React Native RUM action naming.",
5
+ "keywords": [
6
+ "babel",
7
+ "truewatch",
8
+ "react-native",
9
+ "rum"
10
+ ],
11
+ "main": "lib/commonjs/index",
12
+ "module": "lib/module/index",
13
+ "types": "lib/typescript/index.d.ts",
14
+ "source": "src/index",
15
+ "files": [
16
+ "src/**",
17
+ "lib",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "lint": "yarn run -T eslint \"src/**/*.{ts,tsx}\" \"test/**/*.js\"",
22
+ "prepare": "yarn run -T bob build",
23
+ "prepack": "yarn run -T check-version && yarn prepare",
24
+ "test": "yarn run -T jest",
25
+ "typescript": "yarn run -T tsc --noEmit"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/TrueWatchTech/datakit-react-native.git"
30
+ },
31
+ "license": "Apache-2.0",
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "registry": "https://registry.npmjs.org/"
35
+ },
36
+ "peerDependencies": {
37
+ "@babel/core": "^7.0.0",
38
+ "@truewatchtech/react-native-mobile": "^0.4.3-0",
39
+ "react": ">=16.13.1",
40
+ "react-native": ">=0.63.4 <1.0"
41
+ },
42
+ "dependencies": {
43
+ "@babel/helper-plugin-utils": "^7.20.2"
44
+ },
45
+ "devDependencies": {
46
+ "@babel/core": "^7.20.0",
47
+ "@babel/plugin-transform-modules-commonjs": "^7.28.6",
48
+ "@babel/plugin-transform-react-jsx": "^7.25.2"
49
+ },
50
+ "react-native-builder-bob": {
51
+ "source": "src",
52
+ "output": "lib",
53
+ "targets": [
54
+ "commonjs",
55
+ "module",
56
+ [
57
+ "typescript",
58
+ {
59
+ "tsc": "../../node_modules/.bin/tsc"
60
+ }
61
+ ]
62
+ ]
63
+ },
64
+ "jest": {
65
+ "testEnvironment": "node"
66
+ },
67
+ "gitHead": "41ab3e21571bff801c611771afd08e80b260de1b"
68
+ }
@@ -0,0 +1,48 @@
1
+ /*
2
+ * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
3
+ * interaction tracking implementation, licensed under Apache-2.0.
4
+ */
5
+
6
+ import type * as Babel from '@babel/core';
7
+ import { PLUGIN_ENABLED_FLAG } from '../constants';
8
+
9
+ export function insertPluginEnabledFlag(
10
+ t: typeof Babel.types,
11
+ programPath: Babel.NodePath<Babel.types.Program>
12
+ ): void {
13
+ const createFlagAssignment = (
14
+ globalIdentifier: Babel.types.Identifier
15
+ ): Babel.types.ExpressionStatement =>
16
+ t.expressionStatement(
17
+ t.assignmentExpression(
18
+ '=',
19
+ t.memberExpression(
20
+ t.cloneNode(globalIdentifier),
21
+ t.identifier(PLUGIN_ENABLED_FLAG)
22
+ ),
23
+ t.booleanLiteral(true)
24
+ )
25
+ );
26
+ const globalThisIdentifier = t.identifier('globalThis');
27
+ const reactNativeGlobalIdentifier = t.identifier('global');
28
+
29
+ programPath.unshiftContainer(
30
+ 'body',
31
+ t.ifStatement(
32
+ t.binaryExpression(
33
+ '!==',
34
+ t.unaryExpression('typeof', t.cloneNode(globalThisIdentifier)),
35
+ t.stringLiteral('undefined')
36
+ ),
37
+ t.blockStatement([createFlagAssignment(globalThisIdentifier)]),
38
+ t.ifStatement(
39
+ t.binaryExpression(
40
+ '!==',
41
+ t.unaryExpression('typeof', t.cloneNode(reactNativeGlobalIdentifier)),
42
+ t.stringLiteral('undefined')
43
+ ),
44
+ t.blockStatement([createFlagAssignment(reactNativeGlobalIdentifier)])
45
+ )
46
+ )
47
+ );
48
+ }
@@ -0,0 +1,128 @@
1
+ /*
2
+ * Portions of this file are adapted from Datadog's dd-sdk-reactnative Babel
3
+ * interaction tracking implementation, licensed under Apache-2.0.
4
+ */
5
+
6
+ import type * as Babel from '@babel/core';
7
+ import { NATIVE_COMPONENT_HANDLERS } from '../../constants';
8
+ import type {
9
+ NormalizedPluginOptions,
10
+ TrackedComponentData,
11
+ } from '../../types';
12
+ import { getDirectAttributePaths, getNodeName } from '../../utils/jsx';
13
+
14
+ export function createTrackedComponents(
15
+ t: typeof Babel.types,
16
+ programPath: Babel.NodePath<Babel.types.Program>,
17
+ options: NormalizedPluginOptions
18
+ ): Record<string, TrackedComponentData> {
19
+ const tracked: Record<string, TrackedComponentData> = {};
20
+
21
+ for (const statement of programPath.node.body) {
22
+ if (
23
+ !t.isImportDeclaration(statement) ||
24
+ statement.source.value !== 'react-native'
25
+ ) {
26
+ continue;
27
+ }
28
+
29
+ for (const specifier of statement.specifiers) {
30
+ if (!t.isImportSpecifier(specifier)) {
31
+ continue;
32
+ }
33
+ const importedName = getNodeName(t, specifier.imported);
34
+ const localName = getNodeName(t, specifier.local);
35
+ const events = importedName
36
+ ? NATIVE_COMPONENT_HANDLERS[importedName]
37
+ : undefined;
38
+ if (!events || !importedName || !localName) {
39
+ continue;
40
+ }
41
+ tracked[localName] = {
42
+ handlers: events.map((event) => ({ event })),
43
+ importedName,
44
+ isCustom: false,
45
+ useContent: options.components.useContent,
46
+ useNamePrefix: options.components.useNamePrefix,
47
+ };
48
+ }
49
+ }
50
+
51
+ for (const component of options.components.tracked) {
52
+ tracked[component.name] = {
53
+ ...(component.contentProp ? { contentProp: component.contentProp } : {}),
54
+ handlers: component.handlers || [],
55
+ isCustom: true,
56
+ useContent:
57
+ component.useContent === undefined
58
+ ? options.components.useContent
59
+ : component.useContent,
60
+ useNamePrefix:
61
+ component.useNamePrefix === undefined
62
+ ? options.components.useNamePrefix
63
+ : component.useNamePrefix,
64
+ };
65
+ }
66
+
67
+ return tracked;
68
+ }
69
+
70
+ export function isInsideConfiguredComponent(
71
+ t: typeof Babel.types,
72
+ path: Babel.NodePath<Babel.types.JSXElement>,
73
+ customComponentNames: Set<string>
74
+ ): boolean {
75
+ let current: Babel.NodePath | null = path.parentPath;
76
+ while (current && !current.isProgram()) {
77
+ if (
78
+ current.isFunctionDeclaration() ||
79
+ current.isClassDeclaration() ||
80
+ current.isVariableDeclarator()
81
+ ) {
82
+ const name = getNodeName(t, current.node.id);
83
+ if (name && customComponentNames.has(name)) {
84
+ return true;
85
+ }
86
+ }
87
+ current = current.parentPath;
88
+ }
89
+ return false;
90
+ }
91
+
92
+ export function addRequiredTextInputHandler(
93
+ t: typeof Babel.types,
94
+ path: Babel.NodePath<Babel.types.JSXElement>,
95
+ component: TrackedComponentData,
96
+ componentName: string
97
+ ): void {
98
+ if (
99
+ component.isCustom ||
100
+ component.importedName !== 'TextInput' ||
101
+ componentName !== 'TextInput'
102
+ ) {
103
+ return;
104
+ }
105
+ const attributes = getDirectAttributePaths(path);
106
+ const hasSpread = attributes.some((attribute) =>
107
+ attribute.isJSXSpreadAttribute()
108
+ );
109
+ const hasOnFocus = attributes.some(
110
+ (attribute) =>
111
+ attribute.isJSXAttribute() &&
112
+ getNodeName(t, attribute.node.name) === 'onFocus'
113
+ );
114
+ if (!hasSpread && !hasOnFocus) {
115
+ const openingElementPath = path.get(
116
+ 'openingElement'
117
+ ) as Babel.NodePath<Babel.types.JSXOpeningElement>;
118
+ openingElementPath.pushContainer(
119
+ 'attributes',
120
+ t.jsxAttribute(
121
+ t.jsxIdentifier('onFocus'),
122
+ t.jsxExpressionContainer(
123
+ t.arrowFunctionExpression([], t.blockStatement([]))
124
+ )
125
+ )
126
+ );
127
+ }
128
+ }