casper-context 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +253 -0
  2. package/dist/index.js +9 -0
  3. package/dist/lifecycle/post.js +239 -0
  4. package/dist/lifecycle/pre.js +113 -0
  5. package/dist/plugin.js +57 -0
  6. package/dist/transforms/autoContextTransform.js +1 -0
  7. package/dist/transforms/contextTransform.js +1 -0
  8. package/dist/transforms/stateTransform.js +1 -0
  9. package/dist/types/plugin.d.js +1 -0
  10. package/dist/utils/astHelpers.js +644 -0
  11. package/dist/utils/constants.js +111 -0
  12. package/dist/utils/names.js +1 -0
  13. package/dist/utils/scope.js +1 -0
  14. package/dist/utils/utilityHelpers.js +606 -0
  15. package/dist/visitors/AssignmentExpression.js +104 -0
  16. package/dist/visitors/CallExpression.js +1 -0
  17. package/dist/visitors/FunctionDeclaration.js +116 -0
  18. package/dist/visitors/Identifier.js +123 -0
  19. package/dist/visitors/JSXElement.js +1 -0
  20. package/dist/visitors/Program.js +278 -0
  21. package/dist/visitors/ReturnStatement.js +81 -0
  22. package/dist/visitors/VariableDeclaration.js +209 -0
  23. package/package.json +60 -0
  24. package/src/index.js +2 -0
  25. package/src/lifecycle/post.js +237 -0
  26. package/src/lifecycle/pre.js +103 -0
  27. package/src/plugin.js +51 -0
  28. package/src/transforms/autoContextTransform.js +0 -0
  29. package/src/transforms/contextTransform.js +0 -0
  30. package/src/transforms/stateTransform.js +0 -0
  31. package/src/types/plugin.d.ts +0 -0
  32. package/src/utils/astHelpers.js +767 -0
  33. package/src/utils/constants.js +102 -0
  34. package/src/utils/names.js +0 -0
  35. package/src/utils/scope.js +0 -0
  36. package/src/utils/utilityHelpers.js +636 -0
  37. package/src/visitors/AssignmentExpression.js +100 -0
  38. package/src/visitors/CallExpression.js +0 -0
  39. package/src/visitors/FunctionDeclaration.js +114 -0
  40. package/src/visitors/Identifier.js +142 -0
  41. package/src/visitors/JSXElement.js +0 -0
  42. package/src/visitors/Program.js +280 -0
  43. package/src/visitors/ReturnStatement.js +75 -0
  44. package/src/visitors/VariableDeclaration.js +216 -0
@@ -0,0 +1,767 @@
1
+ /**
2
+ * @fileoverview Core dependencies and configuration constants for the Babel transformation.
3
+ * This module orchestrates the AST node names, hook identifiers, and internal
4
+ * library prefixes used to inject and modify React Context logic.
5
+ */
6
+
7
+ /**
8
+ * AST & Babel Node Constants
9
+ * @description These constants map to standard Babel node types and configurations
10
+ * to ensure consistency across the transformation lifecycle.
11
+ */
12
+ import {
13
+ _CCTX_BODY, // Target property for injecting nodes into a block
14
+ _CCTX_VAR, // 'var' or 'const' declaration keyword
15
+ _CCTX_REQUIRE, // CommonJS 'require' identifier
16
+ ANONYMOUS_FUNCTION, // Placeholder for unnamed function expressions
17
+ PREV_STATE, // Identifier for functional state updates (e.g., prevState => ...)
18
+ _CCTX_, // Internal library prefix for generated variables
19
+ _CCTX_CONST, // 'const' declaration keyword
20
+ _CCTX_USE_CONTEXT, // React 'useContext' hook name
21
+ _CCTX_UNDUS_CORE_GBL_CONTEXT, // The default global context name for the core lib
22
+ _CCTX_CMP_NAME_PREFIX, // Prefix used when generating unique component identifiers
23
+ _CCTX_CREATE_ELEMENT, // 'createElement' identifier for JSX-to-JS conversion
24
+ _CCTX_PROVIDER, // Context '.Provider' identifier
25
+ _CCTX_VALUE, // 'value' prop identifier for Providers
26
+ _CCTX_SET, // Setter prefix for state management
27
+ REACT_IMPORT_USE_STATE_HOOKS_NAME, // 'useState' hook name
28
+ _CCTX_UNDUS_CORE_REACT, // Internal reference for the React package
29
+ _CCTX_EMPTY // Default empty string or null placeholder
30
+ } from './constants';
31
+
32
+ /**
33
+ * @module Logger
34
+ * @description Provides a controlled logging interface for the Babel transformation process.
35
+ */
36
+ import { log } from './utilityHelpers';
37
+
38
+ /**
39
+ * Utility Helpers
40
+ * @description Logic-heavy functions used to determine where and how to
41
+ * manipulate the AST tree without corrupting the code structure.
42
+ */
43
+ import {
44
+ getInsertionIndex, // Logic to find the safest line to inject new code
45
+ resolveReact, // Ensures React is available in the current scope
46
+ isContextInstanceDeclare // Check if a context has already been initialized
47
+ } from './utilityHelpers';
48
+
49
+ /**
50
+ * Inserts a `require` declaration at the top of a given AST node path.
51
+ *
52
+ * This function is designed to work with Babel AST transformations. It prepends
53
+ * a variable declaration that assigns the result of a `require` call to a
54
+ * specified identifier. Any errors during insertion are silently caught.
55
+ *
56
+ * @param {NodePath} path - The Babel AST node path where the `require` declaration should be inserted.
57
+ * Typically, this is the Program path or a block statement path.
58
+ * @param {object} t - The Babel types helper object (commonly imported from `@babel/types`) used to
59
+ * construct AST nodes such as variable declarations, identifiers, and call expressions.
60
+ * @param {string} identifier - The name of the variable that will hold the `require` result.
61
+ * Example: `"fs"` to create `const fs = require("fs")`.
62
+ * @param {string} stringLiteral - The module name to pass to `require`. Example: `"fs"`, `"path"`, etc.
63
+ *
64
+ * @returns {void} This function does not return a value. It directly modifies the AST at the given path.
65
+ *
66
+ * @important
67
+ * - `_CCTX_BODY`, `_CCTX_VAR`, and `_CCTX_REQUIRE` are assumed to be predefined constants in scope:
68
+ * - `_CCTX_BODY`: the container key where the declaration is inserted (e.g., `"body"`).
69
+ * - `_CCTX_VAR`: the variable declaration kind (`"var"`, `"let"`, or `"const"`).
70
+ * - `_CCTX_REQUIRE`: the identifier used for `require`.
71
+ * - Any insertion errors are silently caught; consider logging or handling errors if needed.
72
+ * - This function mutates the AST in place and does not return a new node.
73
+ */
74
+ export function buildRequireDeclaration(path, t, identifier, stringLiteral) {
75
+ try {
76
+ path.unshiftContainer(
77
+ _CCTX_BODY,
78
+ t.variableDeclaration(_CCTX_VAR, [
79
+ t.variableDeclarator(
80
+ t.identifier(identifier),
81
+ t.callExpression(
82
+ t.identifier(_CCTX_REQUIRE),
83
+ [t.stringLiteral(stringLiteral)]
84
+ )
85
+ )
86
+ ])
87
+ );
88
+ } catch (e) {
89
+ log('error', '[::buildRequireDeclaration::]', e.message);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Wraps a given return node with a React Context Provider call expression.
95
+ *
96
+ * This function transforms a React return node (JSX element or expression)
97
+ * into a `React.createElement` call for a context provider, injecting the
98
+ * specified state and its setter as the context value. It modifies the
99
+ * AST node in place at the given path.
100
+ *
101
+ * @param {NodePath} path - The Babel AST node path where the return node resides.
102
+ * Typically, this is the path of a return statement inside a function component.
103
+ * @param {object} t - The Babel types helper object (from `@babel/types`) used to construct AST nodes.
104
+ * Provides utilities like `callExpression`, `memberExpression`, and `objectExpression`.
105
+ * @param {Node} returnNode - The AST node that represents the React component's return value.
106
+ * Can be a JSX element or any valid expression.
107
+ * @param {object} state - The current state object, used to resolve the React import name via `resolveReact`.
108
+ * @param {string} stateName - The name of the state variable to be provided via context.
109
+ * Example: `"User"` will inject `{ user, setUser }` as context value.
110
+ *
111
+ * @returns {void} This function does not return a value. It mutates the AST node at `path` by replacing its
112
+ * `argument` with a `React.createElement` call for the context provider.
113
+ *
114
+ * @important
115
+ * - `_CCTX_CREATE_ELEMENT`, `_CCTX_UNDUS_CORE_GBL_CONTEXT`, `_CCTX_PROVIDER`, `_CCTX_VALUE`, and `_CCTX_SET`
116
+ * are assumed to be predefined constants controlling context creation and property naming.
117
+ * - The state is injected as an object containing the state variable and its setter, following the pattern:
118
+ * `{ stateNameLowerCase, setStateName }`.
119
+ * - JSX return nodes are converted to React.createElement using `t.jsxElementToReactCreateElement`.
120
+ * - Any errors during AST manipulation are silently caught; consider logging for debugging purposes.
121
+ * - This function mutates the original AST node in place and does not generate a new return statement.
122
+ */
123
+ export function buildCtxProvider(path, t, returnNode, state, stateName) {
124
+ try {
125
+ const reactName = resolveReact(path, t, state)
126
+ // Ensure returnNode is an expression
127
+ const childrenExpr = t.isJSXElement(returnNode)
128
+ ? t.jsxElementToReactCreateElement(returnNode) // We'll handle JSX separately if needed
129
+ : returnNode;
130
+ const reactCreateEl = t.callExpression(
131
+ t.memberExpression(reactName, t.identifier(_CCTX_CREATE_ELEMENT)),
132
+ [
133
+ t.memberExpression(
134
+ t.memberExpression(
135
+ t.identifier(_CCTX_UNDUS_CORE_GBL_CONTEXT),
136
+ t.identifier(stateName)
137
+ ),
138
+ t.identifier(_CCTX_PROVIDER)
139
+ ),
140
+ t.objectExpression([
141
+ t.objectProperty(
142
+ t.identifier(_CCTX_VALUE),
143
+ t.objectExpression([
144
+ t.objectProperty(t.identifier(stateName[0].toLowerCase() + stateName.slice(1)), t.identifier(stateName[0].toLowerCase() + stateName.slice(1)), false, true),
145
+ t.objectProperty(t.identifier(`${_CCTX_SET}${stateName}`), t.identifier(`${_CCTX_SET}${stateName}`), false, true)
146
+ ])
147
+ )
148
+ ]),
149
+ childrenExpr
150
+ ]
151
+ );
152
+ path.node.argument = reactCreateEl;
153
+ } catch (e) {
154
+ log('error', '[::buildCtxProvider::]', e.message);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Inserts a React `useState` declaration into the AST at the specified path.
160
+ *
161
+ * This function generates a `useState` hook declaration for a given state object
162
+ * and injects it at the top of the target AST node's body. It handles different
163
+ * import scenarios, including named `useState` imports, default React imports,
164
+ * or the absence of a React import. The resulting declaration follows the
165
+ * `[state, setState]` array pattern convention.
166
+ *
167
+ * @param {NodePath} path - The Babel AST node path where the `useState` declaration should be inserted.
168
+ * Usually the Program body or a function component body.
169
+ * @param {object} t - The Babel types helper object (`@babel/types`) for constructing AST nodes such as
170
+ * variable declarations, identifiers, array patterns, member expressions, and call expressions.
171
+ * @param {object} state - The current state of the transformation, including information about imported React identifiers.
172
+ * Example structure: `{ importState: { useStateId, reactId } }`.
173
+ * @param {Array<ObjectProperty>} objProps - An array of object properties to initialize the `useState` hook.
174
+ * These are used to construct the initial state object.
175
+ * @param {string} key - The name of the state variable. The hook declaration will follow the pattern:
176
+ * `[keyLowerCase, setKeyCapitalized]`.
177
+ * Example: `"User"` → `[user, setUser]`.
178
+ *
179
+ * @returns {void} This function does not return a value. It directly mutates the AST by inserting a variable declaration.
180
+ *
181
+ * @important
182
+ * - `_CCTX_CONST`, `_CCTX_SET`, and `_CCTX_UNDUS_CORE_REACT` are assumed to be predefined constants for variable
183
+ * declaration kind, setter naming, and fallback React identifier, respectively.
184
+ * - `REACT_IMPORT_USE_STATE_HOOKS_NAME` is expected to be a predefined string for `"useState"`.
185
+ * - The function creates a sequence expression `[0, useState]` as a fallback pattern; this ensures safe evaluation
186
+ * even if no React import is found.
187
+ * - Errors during AST mutation are silently caught. Consider logging or handling errors for debugging purposes.
188
+ * - The inserted variable declaration follows the standard React `useState` hook pattern and is prepended
189
+ * to the container body at `path.get(_CCTX_BODY)`.
190
+ */
191
+ export function buildCtxUseStateDeclaration(path, t, state, objProps, key) {
192
+ try {
193
+ let useStateMembers
194
+ if (state.importState.useStateId) {
195
+ // case: import { useState } from 'react'
196
+ useStateMembers = state.importState.useStateId;
197
+ } else if (state.importState.reactId) {
198
+ // case: import React from 'react'
199
+ useStateMembers = t.memberExpression(state.importState.reactId, t.identifier(REACT_IMPORT_USE_STATE_HOOKS_NAME));
200
+ } else {
201
+ // fallback: file has no React import → optional
202
+ const reactIdent = t.identifier(_CCTX_UNDUS_CORE_REACT);
203
+ useStateMembers = t.memberExpression(reactIdent, t.identifier(REACT_IMPORT_USE_STATE_HOOKS_NAME));
204
+ }
205
+ const useStateSeq = t.sequenceExpression([
206
+ t.numericLiteral(0),
207
+ useStateMembers
208
+ ]);
209
+
210
+ const useStateCall = t.callExpression(
211
+ useStateSeq,
212
+ [t.objectExpression(objProps)]
213
+ );
214
+ const stateDecl = t.variableDeclaration(_CCTX_CONST, [
215
+ t.variableDeclarator(
216
+ t.arrayPattern([
217
+ t.identifier(key[0].toLowerCase() + key.slice(1)),
218
+ t.identifier(_CCTX_SET + key[0].toUpperCase() + key.slice(1))
219
+ ]),
220
+ useStateCall
221
+ )
222
+ ]);
223
+ path.get(_CCTX_BODY).unshiftContainer(_CCTX_BODY, stateDecl);
224
+ } catch (e) {
225
+ log('error', '[::buildCtxUseStateDeclaration::]', e.message);
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Finds the body path of the first React component function within a given AST path.
231
+ *
232
+ * This function searches through the child nodes of the provided AST `path`
233
+ * and returns the body path of the first node that is a function declaration,
234
+ * function expression, or arrow function expression and is identified as a React component.
235
+ * If no matching component is found, it returns `null`.
236
+ *
237
+ * @param {NodePath} path - The Babel AST node path to search for React component functions.
238
+ * Typically, this is a Program or block statement path.
239
+ * @param {object} t - The Babel types helper object (`@babel/types`) for AST node inspection.
240
+ *
241
+ * @returns {NodePath|null} The AST node path corresponding to the body of the first React component function,
242
+ * or `null` if no React component function is found.
243
+ *
244
+ * @important
245
+ * - `_CCTX_BODY` is assumed to be a predefined constant specifying the container key for a node's body.
246
+ * - `isReactComponent(p)` is assumed to be a helper function that determines if a node path `p` represents
247
+ * a valid React component (e.g., starts with a capital letter, returns JSX, etc.).
248
+ * - Any errors during traversal are silently caught; consider adding logging for debugging.
249
+ * - This function does not mutate the AST; it only inspects and returns the relevant body path.
250
+ */
251
+ export function getComponentBodyPath(path, t) {
252
+ try {
253
+ const cmp = path.get(_CCTX_BODY).find(p =>
254
+ (p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression())
255
+ && isReactComponent(p)
256
+ );
257
+ if (!cmp) return null;
258
+ return cmp.get(_CCTX_BODY);
259
+ } catch (e) {
260
+ log('error', '[::getComponentBodyPath::]', e.message);
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Retrieves the name of the nearest enclosing function or class component for a given AST path.
266
+ *
267
+ * This function traverses up the AST from the provided `path` to find the nearest function
268
+ * declaration, function expression, arrow function, or class declaration. It returns the
269
+ * identifier name if available, or a default placeholder for anonymous functions/classes.
270
+ *
271
+ * @param {NodePath} path - The Babel AST node path from which to start searching upwards.
272
+ *
273
+ * @returns {string|null} The name of the nearest enclosing function or class component.
274
+ * - Returns `ANONYMOUS_FUNCTION` if the nearest function has no identifier.
275
+ * - Returns `ANONYMOUS_CLASS` if the nearest class has no identifier.
276
+ * - Returns `null` if no enclosing function or class is found (top-level).
277
+ *
278
+ * @important
279
+ * - This function handles:
280
+ * 1. Function declarations (`function MyComponent() {}`).
281
+ * 2. Function expressions or arrow functions assigned to a variable (`const MyComponent = () => {}`).
282
+ * 3. Function expressions or arrow functions assigned via assignment expression (`MyComponent = () => {}`).
283
+ * 4. Class declarations (`class MyComponent {}`).
284
+ * - `ANONYMOUS_FUNCTION` and `ANONYMOUS_CLASS` are assumed to be predefined constants for fallback names.
285
+ * - Any errors during AST traversal are silently caught; logging is recommended for debugging.
286
+ * - This function does **not** mutate the AST; it only inspects parent nodes to determine the name.
287
+ */
288
+ export function getInheritantDecComponent(path) {
289
+ try {
290
+ let current = path;
291
+ while (current) {
292
+ if (current.isFunctionDeclaration()) {
293
+ return current.node.id?.name || ANONYMOUS_FUNCTION;
294
+ }
295
+ if (
296
+ current.isFunctionExpression() ||
297
+ current.isArrowFunctionExpression()
298
+ ) {
299
+ // Check if assigned to a variable
300
+ const parent = current.parentPath;
301
+ if (parent?.isVariableDeclarator()) return parent.node.id?.name;
302
+ if (parent?.isAssignmentExpression() && t.isIdentifier(parent.node.left)) {
303
+ return parent.node.left.name;
304
+ }
305
+ return ANONYMOUS_FUNCTION;
306
+ }
307
+ if (current.isClassDeclaration()) {
308
+ return current.node.id?.name || ANONYMOUS_CLASS;
309
+ }
310
+ current = current.parentPath;
311
+ }
312
+ return null; // top-level, no enclosing component
313
+ } catch (e) {
314
+ log('error', '[::getInheritantDecComponent::]', e.message);
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Retrieves the name of a React component or function from a given AST path.
320
+ *
321
+ * This function traverses up the AST from the provided `path` to determine the
322
+ * component or function name. It handles function declarations, function/arrow
323
+ * expressions assigned to variables, and default export declarations.
324
+ *
325
+ * @param {NodePath} path - The Babel AST node path from which to start searching upwards.
326
+ *
327
+ * @returns {string|null} The name of the component or function if found.
328
+ * - Returns `null` if no name could be determined.
329
+ *
330
+ * @important
331
+ * - Handles:
332
+ * 1. Named function declarations: `function MyComponent() {}` → `"MyComponent"`.
333
+ * 2. Arrow or function expressions assigned to a variable: `const MyComponent = () => {}` → `"MyComponent"`.
334
+ * 3. Default exports with a named declaration: `export default function MyComponent() {}` → `"MyComponent"`.
335
+ * - Does **not** mutate the AST; only inspects parent nodes.
336
+ * - Any errors during traversal are silently caught; logging is recommended for debugging.
337
+ * - Returns `null` if the AST path is top-level or no suitable declaration is found.
338
+ */
339
+ export function getComponentName(path) {
340
+ try {
341
+ let current = path;
342
+
343
+ while (current) {
344
+ if (current.isFunctionDeclaration() && current.node.id) {
345
+ return current.node.id.name;
346
+ }
347
+
348
+ if (
349
+ (current.isArrowFunctionExpression() ||
350
+ current.isFunctionExpression()) &&
351
+ current.parentPath?.isVariableDeclarator()
352
+ ) {
353
+ return current.parentPath.node.id.name;
354
+ }
355
+
356
+ if (
357
+ current.isExportDefaultDeclaration()
358
+ ) {
359
+ const decl = current.node.declaration;
360
+
361
+ if (decl.id?.name) {
362
+ return decl.id.name;
363
+ }
364
+ }
365
+
366
+ current = current.parentPath;
367
+ }
368
+
369
+ return null;
370
+ } catch (e) {
371
+ log('error', '[::getComponentName::]', e.message);
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Determines whether a given AST function path represents a component-like function.
377
+ *
378
+ * This function checks if the provided AST `funcPath` corresponds to a function declaration,
379
+ * or an arrow/function expression assigned to a variable. It is useful for identifying React
380
+ * component functions in the AST.
381
+ *
382
+ * @param {NodePath} funcPath - The Babel AST node path to inspect.
383
+ *
384
+ * @returns {boolean} `true` if the node is a function declaration with an identifier, or an arrow/function expression
385
+ * assigned to a variable; otherwise, `false`.
386
+ *
387
+ * @important
388
+ * - Handles:
389
+ * 1. Named function declarations: `function MyComponent() {}` → `true`.
390
+ * 2. Arrow or function expressions assigned to a variable: `const MyComponent = () => {}` → `true`.
391
+ * - Does **not** consider anonymous functions not assigned to variables as components.
392
+ * - Any errors during AST inspection are silently caught; logging is recommended for debugging.
393
+ * - This function does **not** mutate the AST; it only inspects the node type and parent path.
394
+ */
395
+ function isComponentFunction(funcPath) {
396
+ try {
397
+ // function Child() {}
398
+ if (funcPath.isFunctionDeclaration() && funcPath.node.id) {
399
+ return true;
400
+ }
401
+
402
+ // const Child = () => {}
403
+ if (
404
+ (funcPath.isArrowFunctionExpression() ||
405
+ funcPath.isFunctionExpression()) &&
406
+ funcPath.parentPath.isVariableDeclarator()
407
+ ) {
408
+ return true;
409
+ }
410
+
411
+ return false;
412
+ } catch (e) {
413
+ log('error', '[::isComponentFunction::]', e.message);
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Finds the nearest enclosing React component function from a given AST path.
419
+ *
420
+ * This function traverses up the AST from the provided `path` and returns the
421
+ * first function node (function declaration, function expression, or arrow function)
422
+ * that qualifies as a component according to `isComponentFunction`. Callback functions
423
+ * (like those in `useEffect` or array methods) are ignored.
424
+ *
425
+ * @param {NodePath} path - The Babel AST node path from which to start searching upwards.
426
+ *
427
+ * @returns {NodePath|null} The AST node path of the nearest enclosing component function,
428
+ * or `null` if no component function is found.
429
+ *
430
+ * @important
431
+ * - Relies on `isComponentFunction(current)` to determine whether a function is a component.
432
+ * - Does not mutate the AST; it only traverses parent nodes to locate the component.
433
+ * - Any errors during traversal are silently caught; consider logging for debugging.
434
+ * - Returns `null` if the path is at the top-level or no qualifying component function exists.
435
+ */
436
+ export function getInheritantComponent(path) {
437
+ try {
438
+ let current = path;
439
+
440
+ while (current) {
441
+ if (
442
+ current.isFunctionDeclaration() ||
443
+ current.isFunctionExpression() ||
444
+ current.isArrowFunctionExpression()
445
+ ) {
446
+ // Exclude callbacks (useEffect, map, etc.)
447
+ if (isComponentFunction(current)) {
448
+ return current;
449
+ }
450
+ }
451
+ current = current.parentPath;
452
+ }
453
+
454
+ return null;
455
+ } catch (e) {
456
+ log('error', '[::getInheritantComponent::]', e.message);
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Generates a Babel AST arrow function that returns a new object by spreading an existing state
462
+ * and adding/updating a property with a specified value.
463
+ *
464
+ * This is typically used for creating updater functions in React state setters, e.g.,
465
+ * `(prevState) => ({ ...prevState, key: value })`.
466
+ *
467
+ * @param {NodePath} path - The Babel AST node path representing an assignment expression
468
+ * (e.g., `state.key = value`). The function uses `path.node.left`
469
+ * as the property name and `path.node.right` as the value.
470
+ * @param {object} t - The Babel types helper object (`@babel/types`) used to construct AST nodes
471
+ * like identifiers, object expressions, arrow functions, and spread elements.
472
+ *
473
+ * @returns {Node|null} A Babel AST arrow function expression of the form:
474
+ * `(prevState) => ({ ...prevState, [key]: value })`.
475
+ * Returns `null` if an error occurs during AST construction.
476
+ *
477
+ * @important
478
+ * - `PREV_STATE` is assumed to be a predefined constant string for the parameter name
479
+ * (commonly `"prevState"` in React state updater patterns).
480
+ * - This function does **not** mutate the AST; it constructs and returns a new AST node.
481
+ * - Errors during AST construction are silently caught. Consider logging for debugging purposes.
482
+ */
483
+ export function buildSpreadObject(path, t) {
484
+ try {
485
+ const assignedValue = path.node.right;
486
+ const paramIdentifier = t.identifier(PREV_STATE);
487
+ const spreadExistingObject = t.spreadElement(paramIdentifier);
488
+ const newProperty = t.objectProperty(
489
+ t.identifier(path.node.left.name),
490
+ assignedValue
491
+ );
492
+ const returnObject = t.objectExpression([
493
+ spreadExistingObject,
494
+ newProperty
495
+ ]);
496
+ const updateFunction = t.arrowFunctionExpression(
497
+ [paramIdentifier],
498
+ returnObject
499
+ );
500
+ return updateFunction
501
+ } catch (e) {
502
+ log('error', '[::buildSpreadObject::]', e.message);
503
+ }
504
+ }
505
+
506
+ /**
507
+ * Replaces an AST assignment or expression with a React setState call using a provided updater function.
508
+ *
509
+ * This function transforms the AST at the given `path` so that it calls the appropriate
510
+ * state setter function (e.g., `setStateName`) with the provided update function as its argument.
511
+ * Typically used in context/state management code transformations.
512
+ *
513
+ * @param {NodePath} path - The Babel AST node path to replace. Usually an assignment expression
514
+ * or other state update expression.
515
+ * @param {object} t - The Babel types helper object (`@babel/types`) used to construct AST nodes
516
+ * like call expressions and identifiers.
517
+ * @param {string} ctxName - The context or state name, which will be used to determine the setter
518
+ * function name. Prefixes like `_CCTX_CMP_NAME_PREFIX` are stripped.
519
+ * Example: `"User"` → generates `"setUser"`.
520
+ * @param {Node} updateFunction - The Babel AST node representing the updater function to pass
521
+ * to the setter (e.g., an arrow function created via `buildSpreadObject`).
522
+ *
523
+ * @returns {void} This function does not return a value; it directly replaces the AST node at `path`.
524
+ *
525
+ * @important
526
+ * - `_CCTX_CMP_NAME_PREFIX` and `_CCTX_EMPTY` are assumed to be predefined constants used for cleaning
527
+ * or formatting the setter function name.
528
+ * - This function mutates the AST in place and does **not** create new variable declarations.
529
+ * - Errors during AST replacement are silently caught; logging is recommended for debugging.
530
+ */
531
+ export function replaceWithSetState(path, t, ctxName, updateFunction) {
532
+ try {
533
+ path.replaceWith(t.callExpression(
534
+ t.identifier(`set${ctxName.replace(_CCTX_CMP_NAME_PREFIX, _CCTX_EMPTY)}`),
535
+ [updateFunction]
536
+ ));
537
+ } catch (e) {
538
+ log('error', '[::replaceWithSetState::]', e.message);
539
+ }
540
+ }
541
+
542
+ /**
543
+ * Replaces an AST identifier or expression with a member expression accessing a state property.
544
+ *
545
+ * This function transforms the AST at the given `path` so that it accesses a property
546
+ * of the specified state object. It converts the `ctxName` to lowercase for the object
547
+ * and uses the original node name as the property key.
548
+ *
549
+ * @param {NodePath} path - The Babel AST node path to replace. Typically an identifier or assignment expression.
550
+ * @param {object} t - The Babel types helper object (`@babel/types`) used to construct AST nodes
551
+ * like member expressions and identifiers.
552
+ * @param {string} ctxName - The context or state object name. The first character will be converted to lowercase
553
+ * to match the common React state naming convention.
554
+ *
555
+ * @returns {void} This function does not return a value; it directly replaces the AST node at `path`.
556
+ *
557
+ * @important
558
+ * - Converts `ctxName` to lowercase for the object reference: `"User"` → `"user.propertyName"`.
559
+ * - Uses `t.memberExpression` with `computed: true` to allow dynamic property access.
560
+ * - Mutates the AST in place; no new variables are declared.
561
+ * - Any errors during AST replacement are silently caught; logging is recommended for debugging.
562
+ */
563
+ export function replaceWithState(path, t, ctxName) {
564
+ try {
565
+ path.replaceWith(t.memberExpression(t.identifier(ctxName[0].toLowerCase() + ctxName.slice(1)), t.stringLiteral(path.node.name), true));
566
+ } catch (e) {
567
+ log('error', '[::replaceWithState::]', e.message);
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Replaces an AST node with a call expression to a context-based setState function.
573
+ *
574
+ * This function transforms the AST at the given `path` so that it calls the context's
575
+ * state setter function (e.g., `setStateName`) on a context object (e.g., `_CCTX_User`)
576
+ * with a provided updater function.
577
+ *
578
+ * @param {NodePath} path - The Babel AST node path to replace. Typically an assignment or expression.
579
+ * @param {object} t - The Babel types helper object (`@babel/types`) used to construct AST nodes
580
+ * like call expressions, identifiers, and member expressions.
581
+ * @param {string} ctxName - The context or state name. Used to construct both the context object
582
+ * (`_CCTX_${ctxName}`) and the setter function name (`set${ctxName}`),
583
+ * with `_CCTX_CMP_NAME_PREFIX` removed if present.
584
+ * @param {Node} updateFunction - The Babel AST node representing the updater function
585
+ * (e.g., an arrow function created via `buildSpreadObject`).
586
+ *
587
+ * @returns {void} This function does not return a value; it directly replaces the AST node at `path`.
588
+ *
589
+ * @important
590
+ * - `_CCTX_`, `_CCTX_CMP_NAME_PREFIX`, and `_CCTX_EMPTY` are assumed to be predefined constants controlling
591
+ * context object naming and setter name formatting.
592
+ * - Constructs the call as: `_CCTX_${ctxName}.set${ctxName}`(updateFunction).
593
+ * - Mutates the AST in place; no new variable declarations are created.
594
+ * - Errors during AST replacement are silently caught; logging is recommended for debugging.
595
+ */
596
+ export function replaceWithContextSetState(path, t, ctxName, updateFunction) {
597
+ try {
598
+ const finalCallExpression = t.callExpression(
599
+ t.memberExpression(
600
+ t.identifier(`${_CCTX_}${ctxName}`),
601
+ t.identifier(`set${ctxName.replace(_CCTX_CMP_NAME_PREFIX, _CCTX_EMPTY)}`)
602
+ ),
603
+ [updateFunction]
604
+ );
605
+ path.replaceWith(finalCallExpression);
606
+ } catch (e) {
607
+ log('error', '[::replaceWithContextSetState::]', e.message);
608
+ }
609
+ }
610
+
611
+ /**
612
+ * Replaces an AST node with a member expression accessing a property on a context state object.
613
+ *
614
+ * This function transforms the AST at the given `path` so that it accesses a property of
615
+ * a context state object (e.g., `_CCTX_User.user`) using the original node name as the property key.
616
+ *
617
+ * @param {NodePath} path - The Babel AST node path to replace. Typically an identifier or assignment expression.
618
+ * @param {object} t - The Babel types helper object (`@babel/types`) used to construct AST nodes
619
+ * like member expressions and identifiers.
620
+ * @param {string} ctxName - The context name. Used to construct the context object identifier (`_CCTX_${ctxName}`)
621
+ * and the lowercase state property (`${ctxName[0].toLowerCase()}${ctxName.slice(1)}`).
622
+ *
623
+ * @returns {void} This function does not return a value; it directly replaces the AST node at `path`.
624
+ *
625
+ * @important
626
+ * - Constructs the final access as: `_CCTX_${ctxName}.${ctxNameLowerCase}[propertyName]`.
627
+ * - Uses `t.memberExpression` with `computed: true` to allow dynamic property access.
628
+ * - Mutates the AST in place; no new variable declarations are created.
629
+ * - `_CCTX_` is assumed to be a predefined constant for context object prefixing.
630
+ * - Any errors during AST replacement are silently caught; logging is recommended for debugging.
631
+ */
632
+ export function replaceWithContextState(path, t, ctxName) {
633
+ try {
634
+ const stateMember = t.memberExpression(
635
+ t.identifier(`${_CCTX_}${ctxName}`),
636
+ t.identifier(`${ctxName[0].toLowerCase()}${ctxName.slice(1)}`)
637
+ );
638
+ const finalMemberExpression = t.memberExpression(
639
+ stateMember,
640
+ t.stringLiteral(path.node.name),
641
+ true
642
+ );
643
+ path.replaceWith(finalMemberExpression);
644
+ } catch (e) {
645
+ log('error', '[::replaceWithContextState::]', e.message);
646
+ }
647
+ }
648
+
649
+ /**
650
+ * Inserts a React `useContext` hook declaration for a specified context into a component's AST body.
651
+ *
652
+ * This function finds the nearest enclosing React component, checks whether a context
653
+ * instance already exists, and if not, inserts a new `const ctxName = useContext(Context)` declaration
654
+ * at an appropriate position in the component's body.
655
+ *
656
+ * @param {NodePath} path - The Babel AST node path from which to start searching for the enclosing component.
657
+ * @param {object} state - The current transformation state, used for resolving React identifiers.
658
+ * @param {object} t - The Babel types helper object (`@babel/types`) for constructing AST nodes
659
+ * such as variable declarations, call expressions, identifiers, and member expressions.
660
+ * @param {string} ctxName - The name of the context. Used to construct the context variable and access the
661
+ * global context object (e.g., `_CCTX_${ctxName}`).
662
+ *
663
+ * @returns {void} This function does not return a value; it mutates the AST by inserting a context declaration.
664
+ *
665
+ * @important
666
+ * - `_CCTX_`, `_CCTX_CONST`, `_CCTX_USE_CONTEXT`, and `_CCTX_UNDUS_CORE_GBL_CONTEXT` are assumed to be predefined
667
+ * constants controlling naming and React hook usage.
668
+ * - The inserted declaration follows the pattern:
669
+ * ```js
670
+ * const _CCTX_${ctxName} = React.useContext(_CCTX_UNDUS_CORE_GBL_CONTEXT[ctxName]);
671
+ * ```
672
+ * - Uses `getInheritantComponent` to find the enclosing component and `isContextInstanceDeclare`
673
+ * to avoid duplicate declarations.
674
+ * - The declaration is inserted at an index determined by `getInsertionIndex` to maintain proper AST ordering.
675
+ * - Silently catches errors; consider logging for debugging purposes.
676
+ * - Does not return a value; directly mutates the component body in the AST.
677
+ */
678
+ export function buildUseContextInstance(path, state, t, ctxName) {
679
+ try {
680
+ const inheritantCMP = getInheritantComponent(path);
681
+ if (!inheritantCMP) return;
682
+ const bodyPath = inheritantCMP.get(_CCTX_BODY);
683
+ if (!bodyPath.isBlockStatement()) return;
684
+ const ctxVarName = `${_CCTX_}${ctxName}`;
685
+ if (isContextInstanceDeclare(bodyPath, t, ctxVarName)) return;
686
+ const ctxId = t.identifier(ctxVarName);
687
+ const reactName = resolveReact(path, t, state);
688
+ const ctxDecl = t.variableDeclaration(_CCTX_CONST, [
689
+ t.variableDeclarator(
690
+ ctxId,
691
+ t.callExpression(
692
+ t.sequenceExpression([
693
+ t.numericLiteral(0),
694
+ t.memberExpression(
695
+ reactName,
696
+ t.identifier(_CCTX_USE_CONTEXT)
697
+ )
698
+ ]),
699
+ [
700
+ t.memberExpression(
701
+ t.identifier(_CCTX_UNDUS_CORE_GBL_CONTEXT),
702
+ t.identifier(`${ctxName}`)
703
+ )
704
+ ]
705
+ )
706
+ )
707
+ ]);
708
+ const insertIndex = getInsertionIndex(bodyPath.node.body, t);
709
+ bodyPath.node.body.splice(insertIndex, 0, ctxDecl);
710
+ } catch (e) {
711
+ log('error', '[::buildUseContextInstance::]', e.message);
712
+ }
713
+ }
714
+
715
+ /**
716
+ * Finds the topmost React component function in the AST from a given path.
717
+ *
718
+ * This function climbs the function parent hierarchy starting from the provided `path`,
719
+ * identifying the first function whose name starts with an uppercase letter (conventionally
720
+ * a React component). It returns both the component's AST path and its name.
721
+ *
722
+ * @param {NodePath} path - The Babel AST node path from which to start searching upwards.
723
+ *
724
+ * @returns {{currentFuncParent: NodePath|null, componentName: string|null}} An object containing:
725
+ * - `currentFuncParent`: The AST node path of the root parent component function, or `null` if none found.
726
+ * - `componentName`: The name of the root component, or `null` if no component is found.
727
+ *
728
+ * @important
729
+ * - Detects component functions using:
730
+ * 1. Named function declarations: `function MyComponent() {}`.
731
+ * 2. Arrow or function expressions assigned to a variable: `const MyComponent = () => {}`.
732
+ * - Uses the convention that React component names start with an uppercase letter.
733
+ * - Traverses function parents using `getFunctionParent()`.
734
+ * - Does **not** mutate the AST.
735
+ * - Errors during traversal are silently caught; consider logging for debugging.
736
+ */
737
+ export function getRootParentComponent(path) {
738
+ try {
739
+ let currentFuncParent = path.getFunctionParent();
740
+ let componentName = null;
741
+
742
+ while (currentFuncParent) {
743
+ let name = null;
744
+
745
+ // Extract name from Function Declaration
746
+ if (currentFuncParent.node.id) {
747
+ name = currentFuncParent.node.id.name;
748
+ }
749
+ // Extract name from Arrow Function Variable Assignment
750
+ else if (currentFuncParent.parentPath.isVariableDeclarator()) {
751
+ name = currentFuncParent.parentPath.node.id.name;
752
+ }
753
+
754
+ // Check if it's a Component (starts with Uppercase)
755
+ if (name && /^[A-Z]/.test(name)) {
756
+ componentName = name;
757
+ break; // Stop climbing! We found the Component.
758
+ }
759
+
760
+ // If not a component, keep climbing to the next function parent
761
+ currentFuncParent = currentFuncParent.getFunctionParent();
762
+ }
763
+ return { currentFuncParent, componentName }
764
+ } catch (e) {
765
+ log('error', '[::getRootParentComponent::]', e.message);
766
+ }
767
+ }