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