farmon 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/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/bin/farmon.js +12 -0
- package/dist/bin/farmon.js.map +1 -0
- package/dist/execute/agents/index.js +19 -0
- package/dist/execute/agents/index.js.map +1 -0
- package/dist/execute/agents/instruction-classifier-agent.js +16 -0
- package/dist/execute/agents/instruction-classifier-agent.js.map +1 -0
- package/dist/execute/agents/mutation-agent.js +272 -0
- package/dist/execute/agents/mutation-agent.js.map +1 -0
- package/dist/execute/agents/query-agent.js +118 -0
- package/dist/execute/agents/query-agent.js.map +1 -0
- package/dist/execute/helpers/analyzers.js +8 -0
- package/dist/execute/helpers/analyzers.js.map +1 -0
- package/dist/execute/helpers/ensurers.js +1053 -0
- package/dist/execute/helpers/ensurers.js.map +1 -0
- package/dist/execute/helpers/finders.js +1454 -0
- package/dist/execute/helpers/finders.js.map +1 -0
- package/dist/execute/helpers/general.js +3736 -0
- package/dist/execute/helpers/general.js.map +1 -0
- package/dist/execute/helpers/import-helpers.js +183 -0
- package/dist/execute/helpers/import-helpers.js.map +1 -0
- package/dist/execute/helpers/parsers.js +840 -0
- package/dist/execute/helpers/parsers.js.map +1 -0
- package/dist/execute/helpers/prompt-maker.js +1163 -0
- package/dist/execute/helpers/prompt-maker.js.map +1 -0
- package/dist/execute/helpers/validators.js +40 -0
- package/dist/execute/helpers/validators.js.map +1 -0
- package/dist/execute/history/history-manager.js +1030 -0
- package/dist/execute/history/history-manager.js.map +1 -0
- package/dist/execute/history/rollback-handlers.js +2524 -0
- package/dist/execute/history/rollback-handlers.js.map +1 -0
- package/dist/execute/index.js +44 -0
- package/dist/execute/index.js.map +1 -0
- package/dist/execute/llm/call.js +103 -0
- package/dist/execute/llm/call.js.map +1 -0
- package/dist/execute/tasks/ast.js +3819 -0
- package/dist/execute/tasks/ast.js.map +1 -0
- package/dist/execute/tasks/generators.js +96 -0
- package/dist/execute/tasks/generators.js.map +1 -0
- package/dist/execute/tasks/index.js +7 -0
- package/dist/execute/tasks/index.js.map +1 -0
- package/dist/execute/tasks/mutations.js +8139 -0
- package/dist/execute/tasks/mutations.js.map +1 -0
- package/dist/execute/tasks/query.js +248 -0
- package/dist/execute/tasks/query.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/index.js +15 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/ollama.js +40 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openai-compatible.js +52 -0
- package/dist/providers/openai-compatible.js.map +1 -0
- package/dist/runtime/inject.js +250 -0
- package/dist/runtime/inject.js.map +1 -0
- package/dist/schemas/agent/action.schema.js +935 -0
- package/dist/schemas/agent/action.schema.js.map +1 -0
- package/dist/schemas/agent/index.js +4 -0
- package/dist/schemas/agent/index.js.map +1 -0
- package/dist/schemas/agent/llm.schema.js +16 -0
- package/dist/schemas/agent/llm.schema.js.map +1 -0
- package/dist/schemas/agent/planner.schema.js +17 -0
- package/dist/schemas/agent/planner.schema.js.map +1 -0
- package/dist/schemas/index.js +7 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/project/context.schema.js +2 -0
- package/dist/schemas/project/context.schema.js.map +1 -0
- package/dist/schemas/project/index.js +2 -0
- package/dist/schemas/project/index.js.map +1 -0
- package/dist/schemas/runtime/index.js +4 -0
- package/dist/schemas/runtime/index.js.map +1 -0
- package/dist/schemas/runtime/injector.schema.js +11 -0
- package/dist/schemas/runtime/injector.schema.js.map +1 -0
- package/dist/schemas/runtime/runtime.schema.js +73 -0
- package/dist/schemas/runtime/runtime.schema.js.map +1 -0
- package/dist/schemas/runtime/sse.schema.js +15 -0
- package/dist/schemas/runtime/sse.schema.js.map +1 -0
- package/dist/schemas/system/index.js +2 -0
- package/dist/schemas/system/index.js.map +1 -0
- package/dist/schemas/system/logger.schema.js +56 -0
- package/dist/schemas/system/logger.schema.js.map +1 -0
- package/dist/schemas/task/index.js +9 -0
- package/dist/schemas/task/index.js.map +1 -0
- package/dist/server/app-context.js +254 -0
- package/dist/server/app-context.js.map +1 -0
- package/dist/server/config.js +22 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/error.js +22 -0
- package/dist/server/error.js.map +1 -0
- package/dist/server/event-bus.js +60 -0
- package/dist/server/event-bus.js.map +1 -0
- package/dist/server/logger.js +57 -0
- package/dist/server/logger.js.map +1 -0
- package/dist/server/run.js +265 -0
- package/dist/server/run.js.map +1 -0
- package/dist/server/sse.js +143 -0
- package/dist/server/sse.js.map +1 -0
- package/dist/ui/assets/index-C4ydQSAw.css +2 -0
- package/dist/ui/assets/index-Dzo7S5xs.js +85 -0
- package/dist/ui/favicon.svg +1 -0
- package/dist/ui/icons.svg +24 -0
- package/dist/ui/index.html +14 -0
- package/dist/workers/prettier.js +11 -0
- package/dist/workers/prettier.js.map +1 -0
- package/package.json +114 -0
|
@@ -0,0 +1,3819 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
Pure Tasks: AST/code transforms only: Changes will happen in memmory, but not reflected in actual file system.
|
|
4
|
+
-----------
|
|
5
|
+
insertJSX - Inserts new JSX into a target component.
|
|
6
|
+
replaceJSX - Replaces an existing JSX element with new JSX.
|
|
7
|
+
removeJSX - Removes a JSX element from a component.
|
|
8
|
+
moveJSX - Moves a JSX element to a different location in the JSX tree.
|
|
9
|
+
wrapJSX - Wraps a JSX element with a parent JSX element.
|
|
10
|
+
insertVariable - Inserts a new variable declaration into code.
|
|
11
|
+
updateVariable - Updates an existing variable declaration.
|
|
12
|
+
deleteVariable - Removes a variable declaration from code.
|
|
13
|
+
createFunction - Creates a new function declaration.
|
|
14
|
+
updateFunction - Updates an existing function declaration.
|
|
15
|
+
deleteFunction - Removes a function declaration from code.
|
|
16
|
+
renameCssClass - Renames a CSS class in both CSS and JSX.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
*/
|
|
20
|
+
// import * as parser from "@babel/parser";
|
|
21
|
+
import traverse from "@babel/traverse";
|
|
22
|
+
import { parse, parseExpression } from "@babel/parser";
|
|
23
|
+
import { generate } from "@babel/generator";
|
|
24
|
+
import t from "@babel/types";
|
|
25
|
+
import helpers from "../../execute/helpers/general.js";
|
|
26
|
+
import utils from "../../execute/helpers/general.js";
|
|
27
|
+
import { ERROR_CODES, } from "../../schemas/index.js";
|
|
28
|
+
import { LoomaError } from "../../server/error.js";
|
|
29
|
+
/**
|
|
30
|
+
* DescriptionForPrompt: Inserts a new variable declaration into code.
|
|
31
|
+
*
|
|
32
|
+
* ------------------------------------------------------------
|
|
33
|
+
* WHAT THIS FUNCTION DOES
|
|
34
|
+
* ------------------------------------------------------------
|
|
35
|
+
*
|
|
36
|
+
* This function inserts variables in:
|
|
37
|
+
*
|
|
38
|
+
* 1) global/module scope
|
|
39
|
+
* OR
|
|
40
|
+
* 2) inside a function/component scope
|
|
41
|
+
*
|
|
42
|
+
* ------------------------------------------------------------
|
|
43
|
+
* EXAMPLES
|
|
44
|
+
* ------------------------------------------------------------
|
|
45
|
+
*
|
|
46
|
+
* GLOBAL SCOPE
|
|
47
|
+
*
|
|
48
|
+
* BEFORE:
|
|
49
|
+
*
|
|
50
|
+
* import React from "react";
|
|
51
|
+
*
|
|
52
|
+
* function App() {}
|
|
53
|
+
*
|
|
54
|
+
* AFTER:
|
|
55
|
+
*
|
|
56
|
+
* import React from "react";
|
|
57
|
+
*
|
|
58
|
+
* const API_URL = "/api";
|
|
59
|
+
*
|
|
60
|
+
* function App() {}
|
|
61
|
+
*
|
|
62
|
+
* ------------------------------------------------------------
|
|
63
|
+
*
|
|
64
|
+
* FUNCTION / COMPONENT SCOPE
|
|
65
|
+
*
|
|
66
|
+
* BEFORE:
|
|
67
|
+
*
|
|
68
|
+
* function App() {
|
|
69
|
+
* return <div />;
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* AFTER:
|
|
73
|
+
*
|
|
74
|
+
* function App() {
|
|
75
|
+
* const count = 0;
|
|
76
|
+
*
|
|
77
|
+
* return <div />;
|
|
78
|
+
* }
|
|
79
|
+
*
|
|
80
|
+
* ------------------------------------------------------------
|
|
81
|
+
* WHY AST IS IMPORTANT
|
|
82
|
+
* ------------------------------------------------------------
|
|
83
|
+
*
|
|
84
|
+
* Variable insertion is NOT safe using string replacement.
|
|
85
|
+
*
|
|
86
|
+
* Because:
|
|
87
|
+
* - imports may move
|
|
88
|
+
* - formatting changes
|
|
89
|
+
* - nested scopes exist
|
|
90
|
+
* - components/functions vary
|
|
91
|
+
*
|
|
92
|
+
* AST manipulation guarantees:
|
|
93
|
+
* - correct scope
|
|
94
|
+
* - valid syntax
|
|
95
|
+
* - deterministic insertion
|
|
96
|
+
*
|
|
97
|
+
* Useful commands:
|
|
98
|
+
* - add state: insertVariable(count)
|
|
99
|
+
* - store api url: insertVariable(API_URL)
|
|
100
|
+
* - add loading state: insertVariable(loading)
|
|
101
|
+
* - add mock data: insertVariable(mockData)
|
|
102
|
+
* - save form values: insertVariable(formValues)
|
|
103
|
+
*
|
|
104
|
+
* User Command
|
|
105
|
+
↓
|
|
106
|
+
Planner
|
|
107
|
+
↓
|
|
108
|
+
insertVariable
|
|
109
|
+
↓
|
|
110
|
+
updateJSX / updateFunction
|
|
111
|
+
*
|
|
112
|
+
* ------------------------------------------------------------
|
|
113
|
+
* IMPORTANT ARCHITECTURAL CONSTRAINT
|
|
114
|
+
* ------------------------------------------------------------
|
|
115
|
+
*
|
|
116
|
+
* This implementation assumes:
|
|
117
|
+
*
|
|
118
|
+
* - React components use function declarations
|
|
119
|
+
*
|
|
120
|
+
* Example:
|
|
121
|
+
*
|
|
122
|
+
* function Header() {}
|
|
123
|
+
*
|
|
124
|
+
* NOT:
|
|
125
|
+
*
|
|
126
|
+
* const Header = () => {}
|
|
127
|
+
*
|
|
128
|
+
* This simplifies deterministic targeting.
|
|
129
|
+
*
|
|
130
|
+
* ------------------------------------------------------------
|
|
131
|
+
* SUPPORTED VALUE TYPES
|
|
132
|
+
* ------------------------------------------------------------
|
|
133
|
+
*
|
|
134
|
+
* string
|
|
135
|
+
* number
|
|
136
|
+
* boolean
|
|
137
|
+
* object
|
|
138
|
+
* array
|
|
139
|
+
* null
|
|
140
|
+
*
|
|
141
|
+
* ------------------------------------------------------------
|
|
142
|
+
* PARAMS
|
|
143
|
+
* ------------------------------------------------------------
|
|
144
|
+
*
|
|
145
|
+
* @param {Object} params
|
|
146
|
+
*
|
|
147
|
+
* @param {string} params.code
|
|
148
|
+
* Entire source code.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} params.variableName
|
|
151
|
+
* Name of variable to insert.
|
|
152
|
+
*
|
|
153
|
+
* @param {any} params.value
|
|
154
|
+
* Initial value of variable.
|
|
155
|
+
*
|
|
156
|
+
* @param {"global"|"function"} params.scope
|
|
157
|
+
* Where variable should be inserted.
|
|
158
|
+
*
|
|
159
|
+
* @param {string=} params.functionName
|
|
160
|
+
* Required when scope === "function"
|
|
161
|
+
*
|
|
162
|
+
* ------------------------------------------------------------
|
|
163
|
+
* RETURNS
|
|
164
|
+
* ------------------------------------------------------------
|
|
165
|
+
*
|
|
166
|
+
* @returns {{ updatedCode: string }}
|
|
167
|
+
* Updated source code.
|
|
168
|
+
*
|
|
169
|
+
*/
|
|
170
|
+
function insertVariable({ code, variableName, value, scope, functionName }, context) {
|
|
171
|
+
// ----------------------------------------------------------
|
|
172
|
+
// STEP 1:
|
|
173
|
+
// Parse source code into AST
|
|
174
|
+
// ----------------------------------------------------------
|
|
175
|
+
const ast = parse(code, {
|
|
176
|
+
sourceType: "module",
|
|
177
|
+
plugins: ["jsx", "typescript"],
|
|
178
|
+
});
|
|
179
|
+
// ----------------------------------------------------------
|
|
180
|
+
// STEP 2:
|
|
181
|
+
// Convert JS value into AST expression
|
|
182
|
+
//
|
|
183
|
+
// Example:
|
|
184
|
+
//
|
|
185
|
+
// "hello" -> StringLiteral
|
|
186
|
+
// 123 -> NumericLiteral
|
|
187
|
+
// ----------------------------------------------------------
|
|
188
|
+
const valueNode = t.valueToNode(value);
|
|
189
|
+
// ----------------------------------------------------------
|
|
190
|
+
// STEP 3:
|
|
191
|
+
// Create variable declaration AST node
|
|
192
|
+
//
|
|
193
|
+
// Example:
|
|
194
|
+
//
|
|
195
|
+
// const count = 0;
|
|
196
|
+
// ----------------------------------------------------------
|
|
197
|
+
const variableDeclaration = t.variableDeclaration("const", [
|
|
198
|
+
t.variableDeclarator(t.identifier(variableName), valueNode),
|
|
199
|
+
]);
|
|
200
|
+
// ==========================================================
|
|
201
|
+
// GLOBAL SCOPE INSERTION
|
|
202
|
+
// ==========================================================
|
|
203
|
+
if (scope === "global") {
|
|
204
|
+
// --------------------------------------------------------
|
|
205
|
+
// Find last import statement
|
|
206
|
+
//
|
|
207
|
+
// WHY?
|
|
208
|
+
//
|
|
209
|
+
// Variables should appear AFTER imports.
|
|
210
|
+
//
|
|
211
|
+
// GOOD:
|
|
212
|
+
//
|
|
213
|
+
// import React from "react";
|
|
214
|
+
//
|
|
215
|
+
// const API_URL = "...";
|
|
216
|
+
//
|
|
217
|
+
// --------------------------------------------------------
|
|
218
|
+
let insertIndex = 0;
|
|
219
|
+
// --------------------------------------------------------
|
|
220
|
+
// Iterate through top-level statements
|
|
221
|
+
// --------------------------------------------------------
|
|
222
|
+
ast.program.body.forEach((node, index) => {
|
|
223
|
+
// ------------------------------------------------------
|
|
224
|
+
// Track last import position
|
|
225
|
+
// ------------------------------------------------------
|
|
226
|
+
if (t.isImportDeclaration(node)) {
|
|
227
|
+
insertIndex = index + 1;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
// --------------------------------------------------------
|
|
231
|
+
// Insert variable after imports
|
|
232
|
+
// ------------------------------------------------------
|
|
233
|
+
ast.program.body.splice(insertIndex, 0, variableDeclaration);
|
|
234
|
+
}
|
|
235
|
+
// ==========================================================
|
|
236
|
+
// FUNCTION / COMPONENT SCOPE INSERTION
|
|
237
|
+
// ==========================================================
|
|
238
|
+
if (scope === "function") {
|
|
239
|
+
// --------------------------------------------------------
|
|
240
|
+
// Traverse AST searching target function
|
|
241
|
+
// --------------------------------------------------------
|
|
242
|
+
traverse.default(ast, {
|
|
243
|
+
FunctionDeclaration(path) {
|
|
244
|
+
// ----------------------------------------------------
|
|
245
|
+
// Ignore unrelated functions
|
|
246
|
+
// ----------------------------------------------------
|
|
247
|
+
if (path.node.id?.name !== functionName) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// ----------------------------------------------------
|
|
251
|
+
// Get function body statements
|
|
252
|
+
//
|
|
253
|
+
// Example:
|
|
254
|
+
//
|
|
255
|
+
// function App() {
|
|
256
|
+
// BODY HERE
|
|
257
|
+
// }
|
|
258
|
+
// ----------------------------------------------------
|
|
259
|
+
const bodyStatements = path.node.body.body;
|
|
260
|
+
// ----------------------------------------------------
|
|
261
|
+
// Insert variable at beginning of function body
|
|
262
|
+
//
|
|
263
|
+
// BEFORE:
|
|
264
|
+
//
|
|
265
|
+
// function App() {
|
|
266
|
+
// return <div />
|
|
267
|
+
// }
|
|
268
|
+
//
|
|
269
|
+
// AFTER:
|
|
270
|
+
//
|
|
271
|
+
// function App() {
|
|
272
|
+
// const count = 0;
|
|
273
|
+
// return <div />
|
|
274
|
+
// }
|
|
275
|
+
// ----------------------------------------------------
|
|
276
|
+
bodyStatements.unshift(variableDeclaration);
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
// ----------------------------------------------------------
|
|
281
|
+
// STEP 4:
|
|
282
|
+
// Generate updated code from AST
|
|
283
|
+
// ----------------------------------------------------------
|
|
284
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* DescriptionForPrompt: Updates the value of an existing variable.
|
|
288
|
+
*
|
|
289
|
+
* ------------------------------------------------------------
|
|
290
|
+
* WHAT THIS FUNCTION DOES
|
|
291
|
+
* ------------------------------------------------------------
|
|
292
|
+
*
|
|
293
|
+
* This function searches for a variable declaration and
|
|
294
|
+
* replaces its value.
|
|
295
|
+
*
|
|
296
|
+
* ------------------------------------------------------------
|
|
297
|
+
* EXAMPLES
|
|
298
|
+
* ------------------------------------------------------------
|
|
299
|
+
*
|
|
300
|
+
* BEFORE:
|
|
301
|
+
*
|
|
302
|
+
* const API_URL = "/api";
|
|
303
|
+
*
|
|
304
|
+
* AFTER:
|
|
305
|
+
*
|
|
306
|
+
* const API_URL = "/v2/api";
|
|
307
|
+
*
|
|
308
|
+
* ------------------------------------------------------------
|
|
309
|
+
*
|
|
310
|
+
* BEFORE:
|
|
311
|
+
*
|
|
312
|
+
* const loading = false;
|
|
313
|
+
*
|
|
314
|
+
* AFTER:
|
|
315
|
+
*
|
|
316
|
+
* const loading = true;
|
|
317
|
+
*
|
|
318
|
+
* ------------------------------------------------------------
|
|
319
|
+
*
|
|
320
|
+
* BEFORE:
|
|
321
|
+
*
|
|
322
|
+
* const count = 0;
|
|
323
|
+
*
|
|
324
|
+
* AFTER:
|
|
325
|
+
*
|
|
326
|
+
* const count = 10;
|
|
327
|
+
*
|
|
328
|
+
* ------------------------------------------------------------
|
|
329
|
+
* WHY AST IS IMPORTANT
|
|
330
|
+
* ------------------------------------------------------------
|
|
331
|
+
*
|
|
332
|
+
* Updating variables using string replacement is dangerous.
|
|
333
|
+
*
|
|
334
|
+
* Because:
|
|
335
|
+
* - duplicate variable names may exist
|
|
336
|
+
* - nested scopes may exist
|
|
337
|
+
* - formatting changes break regex logic
|
|
338
|
+
*
|
|
339
|
+
* AST manipulation ensures:
|
|
340
|
+
* - valid syntax
|
|
341
|
+
* - deterministic updates
|
|
342
|
+
* - structure-aware targeting
|
|
343
|
+
*
|
|
344
|
+
*
|
|
345
|
+
* Useful commands:
|
|
346
|
+
* - change api url:updateVariable(API_URL)
|
|
347
|
+
* - set loading true:updateVariable(loading)
|
|
348
|
+
* - change default count to 10:updateVariable(count)
|
|
349
|
+
* - update mock data:updateVariable(mockData)
|
|
350
|
+
* - change theme color:updateVariable(themeColor)
|
|
351
|
+
*
|
|
352
|
+
* User Command
|
|
353
|
+
↓
|
|
354
|
+
Planner
|
|
355
|
+
↓
|
|
356
|
+
updateVariable
|
|
357
|
+
↓
|
|
358
|
+
updateJSX / updateFunction
|
|
359
|
+
*
|
|
360
|
+
* ------------------------------------------------------------
|
|
361
|
+
* IMPORTANT ARCHITECTURAL IDEA
|
|
362
|
+
* ------------------------------------------------------------
|
|
363
|
+
*
|
|
364
|
+
* This function only updates variable declarations.
|
|
365
|
+
*
|
|
366
|
+
* Example:
|
|
367
|
+
*
|
|
368
|
+
* const count = 0;
|
|
369
|
+
*
|
|
370
|
+
* It does NOT update:
|
|
371
|
+
*
|
|
372
|
+
* count = 5;
|
|
373
|
+
*
|
|
374
|
+
* because assignment expressions are a separate concern.
|
|
375
|
+
*
|
|
376
|
+
* ------------------------------------------------------------
|
|
377
|
+
* SUPPORTED VALUE TYPES
|
|
378
|
+
* ------------------------------------------------------------
|
|
379
|
+
*
|
|
380
|
+
* string
|
|
381
|
+
* number
|
|
382
|
+
* boolean
|
|
383
|
+
* object
|
|
384
|
+
* array
|
|
385
|
+
* null
|
|
386
|
+
*
|
|
387
|
+
* ------------------------------------------------------------
|
|
388
|
+
* OPTIONAL LINE TARGETING
|
|
389
|
+
* ------------------------------------------------------------
|
|
390
|
+
*
|
|
391
|
+
* Multiple variables with same name may exist.
|
|
392
|
+
*
|
|
393
|
+
* Example:
|
|
394
|
+
*
|
|
395
|
+
* const data = 1;
|
|
396
|
+
*
|
|
397
|
+
* function App() {
|
|
398
|
+
* const data = 2;
|
|
399
|
+
* }
|
|
400
|
+
*
|
|
401
|
+
* line can be used for deterministic targeting.
|
|
402
|
+
*
|
|
403
|
+
* ------------------------------------------------------------
|
|
404
|
+
* PARAMS
|
|
405
|
+
* ------------------------------------------------------------
|
|
406
|
+
*
|
|
407
|
+
* @param {Object} params
|
|
408
|
+
*
|
|
409
|
+
* @param {string} params.code
|
|
410
|
+
* Entire source code.
|
|
411
|
+
*
|
|
412
|
+
* @param {string} params.variableName
|
|
413
|
+
* Name of variable to update.
|
|
414
|
+
*
|
|
415
|
+
* @param {any} params.newValue
|
|
416
|
+
* New value for variable.
|
|
417
|
+
*
|
|
418
|
+
* @param {number=} params.line
|
|
419
|
+
* Optional declaration line number.
|
|
420
|
+
*
|
|
421
|
+
* ------------------------------------------------------------
|
|
422
|
+
* RETURNS
|
|
423
|
+
* ------------------------------------------------------------
|
|
424
|
+
*
|
|
425
|
+
* @returns {{ updatedCode: string }}
|
|
426
|
+
* Updated source code.
|
|
427
|
+
*
|
|
428
|
+
*/
|
|
429
|
+
function updateVariable({ code, variableName, newValue, line }, context) {
|
|
430
|
+
// ----------------------------------------------------------
|
|
431
|
+
// STEP 1:
|
|
432
|
+
// Parse source code into AST
|
|
433
|
+
// ----------------------------------------------------------
|
|
434
|
+
const ast = parse(code, {
|
|
435
|
+
sourceType: "module",
|
|
436
|
+
plugins: ["jsx", "typescript"],
|
|
437
|
+
});
|
|
438
|
+
// ----------------------------------------------------------
|
|
439
|
+
// STEP 2:
|
|
440
|
+
// Convert JS value into AST node
|
|
441
|
+
//
|
|
442
|
+
// Example:
|
|
443
|
+
//
|
|
444
|
+
// "hello" -> StringLiteral
|
|
445
|
+
// 123 -> NumericLiteral
|
|
446
|
+
// ----------------------------------------------------------
|
|
447
|
+
const newValueNode = t.valueToNode(newValue);
|
|
448
|
+
// ----------------------------------------------------------
|
|
449
|
+
// STEP 3:
|
|
450
|
+
// Traverse AST searching variable declarations
|
|
451
|
+
// ----------------------------------------------------------
|
|
452
|
+
traverse.default(ast, {
|
|
453
|
+
VariableDeclarator(path) {
|
|
454
|
+
// ------------------------------------------------------
|
|
455
|
+
// Ignore unrelated variables
|
|
456
|
+
// ------------------------------------------------------
|
|
457
|
+
if (path.node.id.type !== "Identifier") {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (path.node.id.name !== variableName) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
// ------------------------------------------------------
|
|
464
|
+
// Optional line targeting
|
|
465
|
+
//
|
|
466
|
+
// If line provided:
|
|
467
|
+
// only update matching declaration line
|
|
468
|
+
// ------------------------------------------------------
|
|
469
|
+
if (line && path.node.loc?.start.line !== line) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
// ------------------------------------------------------
|
|
473
|
+
// Replace variable value
|
|
474
|
+
//
|
|
475
|
+
// BEFORE:
|
|
476
|
+
// const count = 0;
|
|
477
|
+
//
|
|
478
|
+
// AFTER:
|
|
479
|
+
// const count = 10;
|
|
480
|
+
// ------------------------------------------------------
|
|
481
|
+
path.node.init = newValueNode;
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
// ----------------------------------------------------------
|
|
485
|
+
// STEP 4:
|
|
486
|
+
// Generate updated code from AST
|
|
487
|
+
// ----------------------------------------------------------
|
|
488
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* DescriptionForPrompt: Removes a variable declaration from code.
|
|
492
|
+
*
|
|
493
|
+
* ------------------------------------------------------------
|
|
494
|
+
* WHAT THIS FUNCTION DOES
|
|
495
|
+
* ------------------------------------------------------------
|
|
496
|
+
*
|
|
497
|
+
* This function removes variable declarations.
|
|
498
|
+
*
|
|
499
|
+
* Example:
|
|
500
|
+
*
|
|
501
|
+
* BEFORE:
|
|
502
|
+
*
|
|
503
|
+
* const loading = true;
|
|
504
|
+
*
|
|
505
|
+
* AFTER:
|
|
506
|
+
*
|
|
507
|
+
* <removed>
|
|
508
|
+
*
|
|
509
|
+
* ------------------------------------------------------------
|
|
510
|
+
* SUPPORTED DECLARATIONS
|
|
511
|
+
* ------------------------------------------------------------
|
|
512
|
+
*
|
|
513
|
+
* const
|
|
514
|
+
* let
|
|
515
|
+
* var
|
|
516
|
+
*
|
|
517
|
+
* ------------------------------------------------------------
|
|
518
|
+
* IMPORTANT BEHAVIOR
|
|
519
|
+
* ------------------------------------------------------------
|
|
520
|
+
*
|
|
521
|
+
* This function removes:
|
|
522
|
+
*
|
|
523
|
+
* - only the matching variable declarator
|
|
524
|
+
*
|
|
525
|
+
* Example:
|
|
526
|
+
*
|
|
527
|
+
* BEFORE:
|
|
528
|
+
*
|
|
529
|
+
* const a = 1, b = 2;
|
|
530
|
+
*
|
|
531
|
+
* deleteVariable("a")
|
|
532
|
+
*
|
|
533
|
+
* AFTER:
|
|
534
|
+
*
|
|
535
|
+
* const b = 2;
|
|
536
|
+
*
|
|
537
|
+
* ------------------------------------------------------------
|
|
538
|
+
*
|
|
539
|
+
* If all declarators are removed:
|
|
540
|
+
*
|
|
541
|
+
* BEFORE:
|
|
542
|
+
*
|
|
543
|
+
* const a = 1;
|
|
544
|
+
*
|
|
545
|
+
* AFTER:
|
|
546
|
+
*
|
|
547
|
+
* <entire statement removed>
|
|
548
|
+
*
|
|
549
|
+
* ------------------------------------------------------------
|
|
550
|
+
* WHY AST IS IMPORTANT
|
|
551
|
+
* ------------------------------------------------------------
|
|
552
|
+
*
|
|
553
|
+
* Variable deletion using regex/string replacement is fragile.
|
|
554
|
+
*
|
|
555
|
+
* Problems:
|
|
556
|
+
* - multiline declarations
|
|
557
|
+
* - nested scopes
|
|
558
|
+
* - duplicate variable names
|
|
559
|
+
* - comma-separated variables
|
|
560
|
+
*
|
|
561
|
+
* AST guarantees:
|
|
562
|
+
* - syntax-safe deletion
|
|
563
|
+
* - deterministic targeting
|
|
564
|
+
* - scope-aware manipulation
|
|
565
|
+
*
|
|
566
|
+
* Useful commands:
|
|
567
|
+
* - remove loading state: deleteVariable(loading)
|
|
568
|
+
* - delete mock data: deleteVariable(mockData)
|
|
569
|
+
* - remove api url: deleteVariable(API_URL)
|
|
570
|
+
* - clear temporary state: deleteVariable(tempState)
|
|
571
|
+
* - remove unused variable: deleteVariable(variableName)
|
|
572
|
+
*
|
|
573
|
+
* ------------------------------------------------------------
|
|
574
|
+
* OPTIONAL LINE TARGETING
|
|
575
|
+
* ------------------------------------------------------------
|
|
576
|
+
*
|
|
577
|
+
* Multiple variables with same name may exist.
|
|
578
|
+
*
|
|
579
|
+
* Example:
|
|
580
|
+
*
|
|
581
|
+
* const data = 1;
|
|
582
|
+
*
|
|
583
|
+
* function App() {
|
|
584
|
+
* const data = 2;
|
|
585
|
+
* }
|
|
586
|
+
*
|
|
587
|
+
* line allows precise targeting.
|
|
588
|
+
*
|
|
589
|
+
* ------------------------------------------------------------
|
|
590
|
+
* IMPORTANT ARCHITECTURAL IDEA
|
|
591
|
+
* ------------------------------------------------------------
|
|
592
|
+
*
|
|
593
|
+
* This function removes declarations only.
|
|
594
|
+
*
|
|
595
|
+
* It does NOT:
|
|
596
|
+
* - remove usages
|
|
597
|
+
* - remove JSX references
|
|
598
|
+
* - optimize imports
|
|
599
|
+
*
|
|
600
|
+
* Those should be separate cleanup steps.
|
|
601
|
+
*
|
|
602
|
+
* ------------------------------------------------------------
|
|
603
|
+
* PARAMS
|
|
604
|
+
* ------------------------------------------------------------
|
|
605
|
+
*
|
|
606
|
+
* @param {Object} params
|
|
607
|
+
*
|
|
608
|
+
* @param {string} params.code
|
|
609
|
+
* Entire source code.
|
|
610
|
+
*
|
|
611
|
+
* @param {string} params.variableName
|
|
612
|
+
* Variable to remove.
|
|
613
|
+
*
|
|
614
|
+
* @param {number=} params.line
|
|
615
|
+
* Optional declaration line number.
|
|
616
|
+
*
|
|
617
|
+
* ------------------------------------------------------------
|
|
618
|
+
* RETURNS
|
|
619
|
+
* ------------------------------------------------------------
|
|
620
|
+
*
|
|
621
|
+
* @returns {{ updatedCode: string }}
|
|
622
|
+
* Updated source code.
|
|
623
|
+
*
|
|
624
|
+
*/
|
|
625
|
+
function deleteVariable({ code, variableName, line }, context) {
|
|
626
|
+
// ----------------------------------------------------------
|
|
627
|
+
// STEP 1:
|
|
628
|
+
// Parse source code into AST
|
|
629
|
+
// ----------------------------------------------------------
|
|
630
|
+
const ast = parse(code, {
|
|
631
|
+
sourceType: "module",
|
|
632
|
+
plugins: ["jsx", "typescript"],
|
|
633
|
+
});
|
|
634
|
+
// ----------------------------------------------------------
|
|
635
|
+
// STEP 2:
|
|
636
|
+
// Traverse AST searching variable declarations
|
|
637
|
+
// ----------------------------------------------------------
|
|
638
|
+
traverse.default(ast, {
|
|
639
|
+
VariableDeclaration(path) {
|
|
640
|
+
// ------------------------------------------------------
|
|
641
|
+
// Filter matching declarators
|
|
642
|
+
//
|
|
643
|
+
// Example:
|
|
644
|
+
//
|
|
645
|
+
// const a = 1, b = 2;
|
|
646
|
+
//
|
|
647
|
+
// removing "a" keeps only "b"
|
|
648
|
+
// ------------------------------------------------------
|
|
649
|
+
const remainingDeclarators = path.node.declarations.filter((declarator) => {
|
|
650
|
+
// ------------------------------------------------
|
|
651
|
+
// Ignore non-Identifier patterns
|
|
652
|
+
//
|
|
653
|
+
// Example:
|
|
654
|
+
// const { a } = obj;
|
|
655
|
+
// ------------------------------------------------
|
|
656
|
+
if (declarator.id.type !== "Identifier") {
|
|
657
|
+
return true;
|
|
658
|
+
}
|
|
659
|
+
// ------------------------------------------------
|
|
660
|
+
// Keep unrelated variables
|
|
661
|
+
// ------------------------------------------------
|
|
662
|
+
if (declarator.id.name !== variableName) {
|
|
663
|
+
return true;
|
|
664
|
+
}
|
|
665
|
+
// ------------------------------------------------
|
|
666
|
+
// Optional line targeting
|
|
667
|
+
//
|
|
668
|
+
// Remove only if line matches
|
|
669
|
+
// ------------------------------------------------
|
|
670
|
+
if (line && declarator.loc?.start.line !== line) {
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
// ------------------------------------------------
|
|
674
|
+
// Returning false removes declarator
|
|
675
|
+
// ------------------------------------------------
|
|
676
|
+
return false;
|
|
677
|
+
});
|
|
678
|
+
// ------------------------------------------------------
|
|
679
|
+
// If no declarators remain:
|
|
680
|
+
// remove entire variable statement
|
|
681
|
+
//
|
|
682
|
+
// BEFORE:
|
|
683
|
+
//
|
|
684
|
+
// const a = 1;
|
|
685
|
+
//
|
|
686
|
+
// AFTER:
|
|
687
|
+
//
|
|
688
|
+
// <removed>
|
|
689
|
+
// ------------------------------------------------------
|
|
690
|
+
if (remainingDeclarators.length === 0) {
|
|
691
|
+
path.remove();
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
// ------------------------------------------------------
|
|
695
|
+
// Otherwise update remaining declarators
|
|
696
|
+
// ------------------------------------------------------
|
|
697
|
+
path.node.declarations = remainingDeclarators;
|
|
698
|
+
},
|
|
699
|
+
});
|
|
700
|
+
// ----------------------------------------------------------
|
|
701
|
+
// STEP 3:
|
|
702
|
+
// Generate updated code from AST
|
|
703
|
+
// ----------------------------------------------------------
|
|
704
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* DescriptionForPrompt: Creates a new function declaration.
|
|
708
|
+
*
|
|
709
|
+
* ------------------------------------------------------------
|
|
710
|
+
* WHAT THIS FUNCTION DOES
|
|
711
|
+
* ------------------------------------------------------------
|
|
712
|
+
*
|
|
713
|
+
* This function creates a standard JavaScript function
|
|
714
|
+
* declaration and inserts it into the file.
|
|
715
|
+
*
|
|
716
|
+
* Example:
|
|
717
|
+
*
|
|
718
|
+
* BEFORE:
|
|
719
|
+
*
|
|
720
|
+
* function App() {
|
|
721
|
+
* return <div />;
|
|
722
|
+
* }
|
|
723
|
+
*
|
|
724
|
+
* AFTER:
|
|
725
|
+
*
|
|
726
|
+
* function formatPrice(price) {
|
|
727
|
+
* return `$${price}`;
|
|
728
|
+
* }
|
|
729
|
+
*
|
|
730
|
+
* function App() {
|
|
731
|
+
* return <div />;
|
|
732
|
+
* }
|
|
733
|
+
*
|
|
734
|
+
* ------------------------------------------------------------
|
|
735
|
+
* WHY AST IS IMPORTANT
|
|
736
|
+
* ------------------------------------------------------------
|
|
737
|
+
*
|
|
738
|
+
* Function creation using string concatenation is fragile.
|
|
739
|
+
*
|
|
740
|
+
* Problems:
|
|
741
|
+
* - invalid syntax
|
|
742
|
+
* - incorrect insertion position
|
|
743
|
+
* - broken formatting
|
|
744
|
+
* - nested scope mistakes
|
|
745
|
+
*
|
|
746
|
+
* AST guarantees:
|
|
747
|
+
* - syntax-safe insertion
|
|
748
|
+
* - deterministic structure
|
|
749
|
+
* - scope awareness
|
|
750
|
+
* - formatting preserved
|
|
751
|
+
*
|
|
752
|
+
* Useful commands:
|
|
753
|
+
* - add submit handler: createFunction(handleSubmit)
|
|
754
|
+
* - create formatter function: createFunction(formatPrice)
|
|
755
|
+
* - add validation logic: createFunction(validateForm)
|
|
756
|
+
* - add api fetch function: createFunction(fetchUsers)
|
|
757
|
+
* - create helper function: createFunction(helper)
|
|
758
|
+
*
|
|
759
|
+
* createFunction
|
|
760
|
+
↓
|
|
761
|
+
insertJSX / updateFunction
|
|
762
|
+
↓
|
|
763
|
+
optimizeImports
|
|
764
|
+
* ------------------------------------------------------------
|
|
765
|
+
* IMPORTANT ARCHITECTURAL CONSTRAINT
|
|
766
|
+
* ------------------------------------------------------------
|
|
767
|
+
*
|
|
768
|
+
* This implementation uses ONLY:
|
|
769
|
+
*
|
|
770
|
+
* function declarations
|
|
771
|
+
*
|
|
772
|
+
* Example:
|
|
773
|
+
*
|
|
774
|
+
* function myFunc() {}
|
|
775
|
+
*
|
|
776
|
+
* NOT:
|
|
777
|
+
*
|
|
778
|
+
* const myFunc = () => {}
|
|
779
|
+
*
|
|
780
|
+
* This keeps architecture deterministic and easy to analyze.
|
|
781
|
+
*
|
|
782
|
+
* ------------------------------------------------------------
|
|
783
|
+
* INSERTION STRATEGY
|
|
784
|
+
* ------------------------------------------------------------
|
|
785
|
+
*
|
|
786
|
+
* Global scope:
|
|
787
|
+
* - inserted after imports
|
|
788
|
+
*
|
|
789
|
+
* Function scope:
|
|
790
|
+
* - inserted inside target function
|
|
791
|
+
*
|
|
792
|
+
* ------------------------------------------------------------
|
|
793
|
+
* FUNCTION BODY
|
|
794
|
+
* ------------------------------------------------------------
|
|
795
|
+
*
|
|
796
|
+
* body should contain raw JavaScript statements.
|
|
797
|
+
*
|
|
798
|
+
* Example:
|
|
799
|
+
*
|
|
800
|
+
* return value * 2;
|
|
801
|
+
*
|
|
802
|
+
* NOT:
|
|
803
|
+
*
|
|
804
|
+
* {
|
|
805
|
+
* return value * 2;
|
|
806
|
+
* }
|
|
807
|
+
*
|
|
808
|
+
* ------------------------------------------------------------
|
|
809
|
+
* PARAMS
|
|
810
|
+
* ------------------------------------------------------------
|
|
811
|
+
*
|
|
812
|
+
* @param {Object} params
|
|
813
|
+
*
|
|
814
|
+
* @param {string} params.code
|
|
815
|
+
* Entire source code.
|
|
816
|
+
*
|
|
817
|
+
* @param {string} params.functionName
|
|
818
|
+
* Name of function to create.
|
|
819
|
+
*
|
|
820
|
+
* @param {string[]} params.params
|
|
821
|
+
* Array of parameter names.
|
|
822
|
+
*
|
|
823
|
+
* Example:
|
|
824
|
+
* ["price", "currency"]
|
|
825
|
+
*
|
|
826
|
+
* @param {string} params.body
|
|
827
|
+
* Raw JavaScript statements.
|
|
828
|
+
*
|
|
829
|
+
* @param {"global"|"function"} params.scope
|
|
830
|
+
* Where function should be inserted.
|
|
831
|
+
*
|
|
832
|
+
* @param {string=} params.parentFunctionName
|
|
833
|
+
* Required if scope === "function"
|
|
834
|
+
*
|
|
835
|
+
* ------------------------------------------------------------
|
|
836
|
+
* RETURNS
|
|
837
|
+
* ------------------------------------------------------------
|
|
838
|
+
*
|
|
839
|
+
* @returns {{ updatedCode: string }}
|
|
840
|
+
* Updated source code.
|
|
841
|
+
*
|
|
842
|
+
*/
|
|
843
|
+
function createFunction({ code, functionName, params = [], body = "", scope = "global", parentFunctionName, }) {
|
|
844
|
+
// ----------------------------------------------------------
|
|
845
|
+
// STEP 1:
|
|
846
|
+
// Parse source code into AST
|
|
847
|
+
// ----------------------------------------------------------
|
|
848
|
+
const ast = parse(code, {
|
|
849
|
+
sourceType: "module",
|
|
850
|
+
plugins: ["jsx", "typescript"],
|
|
851
|
+
});
|
|
852
|
+
// ----------------------------------------------------------
|
|
853
|
+
// STEP 2:
|
|
854
|
+
// Convert parameter names into AST identifiers
|
|
855
|
+
//
|
|
856
|
+
// Example:
|
|
857
|
+
//
|
|
858
|
+
// ["price"]
|
|
859
|
+
//
|
|
860
|
+
// becomes:
|
|
861
|
+
//
|
|
862
|
+
// Identifier(price)
|
|
863
|
+
// ----------------------------------------------------------
|
|
864
|
+
const functionParams = params.map((param) => t.identifier(param));
|
|
865
|
+
// ----------------------------------------------------------
|
|
866
|
+
// STEP 3:
|
|
867
|
+
// Parse function body into AST statements
|
|
868
|
+
//
|
|
869
|
+
// WHY?
|
|
870
|
+
//
|
|
871
|
+
// Babel requires valid AST statements inside function body.
|
|
872
|
+
//
|
|
873
|
+
// Example:
|
|
874
|
+
//
|
|
875
|
+
// return value * 2;
|
|
876
|
+
// ----------------------------------------------------------
|
|
877
|
+
const parsedBody = parse(body, {
|
|
878
|
+
sourceType: "module",
|
|
879
|
+
plugins: ["jsx", "typescript"],
|
|
880
|
+
});
|
|
881
|
+
// ----------------------------------------------------------
|
|
882
|
+
// STEP 4:
|
|
883
|
+
// Create FunctionDeclaration AST node
|
|
884
|
+
//
|
|
885
|
+
// Example:
|
|
886
|
+
//
|
|
887
|
+
// function formatPrice(price) {
|
|
888
|
+
// return `$${price}`;
|
|
889
|
+
// }
|
|
890
|
+
// ----------------------------------------------------------
|
|
891
|
+
const functionDeclaration = t.functionDeclaration(t.identifier(functionName), functionParams, t.blockStatement(parsedBody.program.body));
|
|
892
|
+
// ==========================================================
|
|
893
|
+
// GLOBAL SCOPE INSERTION
|
|
894
|
+
// ==========================================================
|
|
895
|
+
if (scope === "global") {
|
|
896
|
+
// --------------------------------------------------------
|
|
897
|
+
// Find insertion point after imports
|
|
898
|
+
// --------------------------------------------------------
|
|
899
|
+
let insertIndex = 0;
|
|
900
|
+
ast.program.body.forEach((node, index) => {
|
|
901
|
+
if (t.isImportDeclaration(node)) {
|
|
902
|
+
insertIndex = index + 1;
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
// --------------------------------------------------------
|
|
906
|
+
// Insert function after imports
|
|
907
|
+
// --------------------------------------------------------
|
|
908
|
+
ast.program.body.splice(insertIndex, 0, functionDeclaration);
|
|
909
|
+
}
|
|
910
|
+
// ==========================================================
|
|
911
|
+
// FUNCTION SCOPE INSERTION
|
|
912
|
+
// ==========================================================
|
|
913
|
+
if (scope === "function") {
|
|
914
|
+
// --------------------------------------------------------
|
|
915
|
+
// Traverse AST searching parent function
|
|
916
|
+
// --------------------------------------------------------
|
|
917
|
+
traverse.default(ast, {
|
|
918
|
+
FunctionDeclaration(path) {
|
|
919
|
+
// ----------------------------------------------------
|
|
920
|
+
// Ignore unrelated functions
|
|
921
|
+
// ----------------------------------------------------
|
|
922
|
+
if (path.node.id?.name !== parentFunctionName) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
// ----------------------------------------------------
|
|
926
|
+
// Insert child function at top of body
|
|
927
|
+
// ----------------------------------------------------
|
|
928
|
+
path.node.body.body.unshift(functionDeclaration);
|
|
929
|
+
},
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
// ----------------------------------------------------------
|
|
933
|
+
// STEP 5:
|
|
934
|
+
// Generate updated code from AST
|
|
935
|
+
// ----------------------------------------------------------
|
|
936
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* DescriptionForPrompt: Updates an existing function declaration.
|
|
940
|
+
*
|
|
941
|
+
* ------------------------------------------------------------
|
|
942
|
+
* WHAT THIS FUNCTION DOES
|
|
943
|
+
* ------------------------------------------------------------
|
|
944
|
+
*
|
|
945
|
+
* This function replaces the contents of an existing function.
|
|
946
|
+
*
|
|
947
|
+
* Example:
|
|
948
|
+
*
|
|
949
|
+
* BEFORE:
|
|
950
|
+
*
|
|
951
|
+
* function calculate() {
|
|
952
|
+
* return 1;
|
|
953
|
+
* }
|
|
954
|
+
*
|
|
955
|
+
* AFTER:
|
|
956
|
+
*
|
|
957
|
+
* function calculate() {
|
|
958
|
+
* return 10;
|
|
959
|
+
* }
|
|
960
|
+
*
|
|
961
|
+
* ------------------------------------------------------------
|
|
962
|
+
* IMPORTANT BEHAVIOR
|
|
963
|
+
* ------------------------------------------------------------
|
|
964
|
+
*
|
|
965
|
+
* This function:
|
|
966
|
+
*
|
|
967
|
+
* - preserves function name
|
|
968
|
+
* - preserves parameters
|
|
969
|
+
* - preserves declaration type
|
|
970
|
+
*
|
|
971
|
+
* It ONLY replaces:
|
|
972
|
+
*
|
|
973
|
+
* - function body
|
|
974
|
+
*
|
|
975
|
+
* ------------------------------------------------------------
|
|
976
|
+
* WHY AST IS IMPORTANT
|
|
977
|
+
* ------------------------------------------------------------
|
|
978
|
+
*
|
|
979
|
+
* Function updates using string replacement are dangerous.
|
|
980
|
+
*
|
|
981
|
+
* Problems:
|
|
982
|
+
* - nested braces
|
|
983
|
+
* - multiline formatting
|
|
984
|
+
* - duplicate function names
|
|
985
|
+
* - invalid syntax generation
|
|
986
|
+
*
|
|
987
|
+
* AST guarantees:
|
|
988
|
+
* - structure-aware updates
|
|
989
|
+
* - valid syntax
|
|
990
|
+
* - deterministic replacement
|
|
991
|
+
*
|
|
992
|
+
* Useful commands:
|
|
993
|
+
* - change submit logic: updateFunction(handleSubmit)
|
|
994
|
+
* - update validation function: updateFunction(validateForm)
|
|
995
|
+
* - modify fetch api logic: updateFunction(fetchUsers)
|
|
996
|
+
* - change formatter behavior: updateFunction(formatPrice)
|
|
997
|
+
* - replace helper logic: updateFunction(helper)
|
|
998
|
+
*
|
|
999
|
+
* updateFunction
|
|
1000
|
+
↓
|
|
1001
|
+
updateJSX / optimizeImports
|
|
1002
|
+
* ------------------------------------------------------------
|
|
1003
|
+
* IMPORTANT ARCHITECTURAL CONSTRAINT
|
|
1004
|
+
* ------------------------------------------------------------
|
|
1005
|
+
*
|
|
1006
|
+
* This implementation supports ONLY:
|
|
1007
|
+
*
|
|
1008
|
+
* function declarations
|
|
1009
|
+
*
|
|
1010
|
+
* Example:
|
|
1011
|
+
*
|
|
1012
|
+
* function myFunc() {}
|
|
1013
|
+
*
|
|
1014
|
+
* NOT:
|
|
1015
|
+
*
|
|
1016
|
+
* const myFunc = () => {}
|
|
1017
|
+
*
|
|
1018
|
+
* ------------------------------------------------------------
|
|
1019
|
+
* BODY FORMAT
|
|
1020
|
+
* ------------------------------------------------------------
|
|
1021
|
+
*
|
|
1022
|
+
* body should contain raw JavaScript statements.
|
|
1023
|
+
*
|
|
1024
|
+
* Example:
|
|
1025
|
+
*
|
|
1026
|
+
* return data.map(item => item.name);
|
|
1027
|
+
*
|
|
1028
|
+
* NOT:
|
|
1029
|
+
*
|
|
1030
|
+
* {
|
|
1031
|
+
* return data.map(...);
|
|
1032
|
+
* }
|
|
1033
|
+
*
|
|
1034
|
+
* ------------------------------------------------------------
|
|
1035
|
+
* OPTIONAL LINE TARGETING
|
|
1036
|
+
* ------------------------------------------------------------
|
|
1037
|
+
*
|
|
1038
|
+
* Multiple functions with same name may exist.
|
|
1039
|
+
*
|
|
1040
|
+
* line targeting allows deterministic selection.
|
|
1041
|
+
*
|
|
1042
|
+
* ------------------------------------------------------------
|
|
1043
|
+
* PARAMS
|
|
1044
|
+
* ------------------------------------------------------------
|
|
1045
|
+
*
|
|
1046
|
+
* @param {Object} params
|
|
1047
|
+
*
|
|
1048
|
+
* @param {string} params.code
|
|
1049
|
+
* Entire source code.
|
|
1050
|
+
*
|
|
1051
|
+
* @param {string} params.functionName
|
|
1052
|
+
* Function to update.
|
|
1053
|
+
*
|
|
1054
|
+
* @param {string} params.newBody
|
|
1055
|
+
* New raw JavaScript statements.
|
|
1056
|
+
*
|
|
1057
|
+
* @param {number=} params.line
|
|
1058
|
+
* Optional function declaration line.
|
|
1059
|
+
*
|
|
1060
|
+
* ------------------------------------------------------------
|
|
1061
|
+
* RETURNS
|
|
1062
|
+
* ------------------------------------------------------------
|
|
1063
|
+
*
|
|
1064
|
+
* @returns {{ updatedCode: string }}
|
|
1065
|
+
* Updated source code.
|
|
1066
|
+
*
|
|
1067
|
+
*/
|
|
1068
|
+
function updateFunction({ code, functionName, newBody, line, }) {
|
|
1069
|
+
// ----------------------------------------------------------
|
|
1070
|
+
// STEP 1:
|
|
1071
|
+
// Parse source code into AST
|
|
1072
|
+
// ----------------------------------------------------------
|
|
1073
|
+
const ast = parse(code, {
|
|
1074
|
+
sourceType: "module",
|
|
1075
|
+
plugins: ["jsx", "typescript"],
|
|
1076
|
+
});
|
|
1077
|
+
// ----------------------------------------------------------
|
|
1078
|
+
// STEP 2:
|
|
1079
|
+
// Parse new function body into AST statements
|
|
1080
|
+
//
|
|
1081
|
+
// Example:
|
|
1082
|
+
//
|
|
1083
|
+
// return 10;
|
|
1084
|
+
// ----------------------------------------------------------
|
|
1085
|
+
const parsedBody = parse(newBody, {
|
|
1086
|
+
sourceType: "module",
|
|
1087
|
+
plugins: ["jsx", "typescript"],
|
|
1088
|
+
});
|
|
1089
|
+
// ----------------------------------------------------------
|
|
1090
|
+
// STEP 3:
|
|
1091
|
+
// Traverse AST searching function declarations
|
|
1092
|
+
// ----------------------------------------------------------
|
|
1093
|
+
traverse.default(ast, {
|
|
1094
|
+
FunctionDeclaration(path) {
|
|
1095
|
+
// ------------------------------------------------------
|
|
1096
|
+
// Ignore anonymous functions
|
|
1097
|
+
// ------------------------------------------------------
|
|
1098
|
+
if (!path.node.id) {
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
// ------------------------------------------------------
|
|
1102
|
+
// Ignore unrelated functions
|
|
1103
|
+
// ------------------------------------------------------
|
|
1104
|
+
if (path.node.id.name !== functionName) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
// ------------------------------------------------------
|
|
1108
|
+
// Optional precise line targeting
|
|
1109
|
+
// ------------------------------------------------------
|
|
1110
|
+
if (line && path.node.loc?.start?.line !== line) {
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
// ------------------------------------------------------
|
|
1114
|
+
// Replace function body
|
|
1115
|
+
//
|
|
1116
|
+
// BEFORE:
|
|
1117
|
+
//
|
|
1118
|
+
// function test() {
|
|
1119
|
+
// return 1;
|
|
1120
|
+
// }
|
|
1121
|
+
//
|
|
1122
|
+
// AFTER:
|
|
1123
|
+
//
|
|
1124
|
+
// function test() {
|
|
1125
|
+
// return 10;
|
|
1126
|
+
// }
|
|
1127
|
+
// ------------------------------------------------------
|
|
1128
|
+
path.node.body = t.blockStatement(parsedBody.program.body);
|
|
1129
|
+
},
|
|
1130
|
+
});
|
|
1131
|
+
// ----------------------------------------------------------
|
|
1132
|
+
// STEP 4:
|
|
1133
|
+
// Generate updated source code from AST
|
|
1134
|
+
// ----------------------------------------------------------
|
|
1135
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* DescriptionForPrompt: Removes a function declaration from code.
|
|
1139
|
+
*
|
|
1140
|
+
* ------------------------------------------------------------
|
|
1141
|
+
* WHAT THIS FUNCTION DOES
|
|
1142
|
+
* ------------------------------------------------------------
|
|
1143
|
+
*
|
|
1144
|
+
* This function removes an entire function declaration.
|
|
1145
|
+
*
|
|
1146
|
+
* Example:
|
|
1147
|
+
*
|
|
1148
|
+
* BEFORE:
|
|
1149
|
+
*
|
|
1150
|
+
* function formatPrice(price) {
|
|
1151
|
+
* return `$${price}`;
|
|
1152
|
+
* }
|
|
1153
|
+
*
|
|
1154
|
+
* AFTER:
|
|
1155
|
+
*
|
|
1156
|
+
* <removed>
|
|
1157
|
+
*
|
|
1158
|
+
* ------------------------------------------------------------
|
|
1159
|
+
* IMPORTANT BEHAVIOR
|
|
1160
|
+
* ------------------------------------------------------------
|
|
1161
|
+
*
|
|
1162
|
+
* This function removes:
|
|
1163
|
+
*
|
|
1164
|
+
* - the complete function declaration
|
|
1165
|
+
* - function name
|
|
1166
|
+
* - parameters
|
|
1167
|
+
* - body
|
|
1168
|
+
*
|
|
1169
|
+
* ------------------------------------------------------------
|
|
1170
|
+
* WHY AST IS IMPORTANT
|
|
1171
|
+
* ------------------------------------------------------------
|
|
1172
|
+
*
|
|
1173
|
+
* Function deletion using regex/string replacement is fragile.
|
|
1174
|
+
*
|
|
1175
|
+
* Problems:
|
|
1176
|
+
* - nested braces
|
|
1177
|
+
* - multiline functions
|
|
1178
|
+
* - formatting variations
|
|
1179
|
+
* - duplicate function names
|
|
1180
|
+
*
|
|
1181
|
+
* AST guarantees:
|
|
1182
|
+
* - syntax-safe removal
|
|
1183
|
+
* - deterministic targeting
|
|
1184
|
+
* - structure-aware deletion
|
|
1185
|
+
*
|
|
1186
|
+
* Useful commands:
|
|
1187
|
+
* - remove submit handler: deleteFunction(handleSubmit)
|
|
1188
|
+
* - delete helper function: deleteFunction(helper)
|
|
1189
|
+
* - remove validation logic: deleteFunction(validateForm)
|
|
1190
|
+
* - remove formatter: deleteFunction(formatPrice)
|
|
1191
|
+
* - delete fetch function: deleteFunction(fetchUsers)
|
|
1192
|
+
*
|
|
1193
|
+
* deleteFunction
|
|
1194
|
+
↓
|
|
1195
|
+
remove usages
|
|
1196
|
+
↓
|
|
1197
|
+
optimizeImports
|
|
1198
|
+
* ------------------------------------------------------------
|
|
1199
|
+
* IMPORTANT ARCHITECTURAL CONSTRAINT
|
|
1200
|
+
* ------------------------------------------------------------
|
|
1201
|
+
*
|
|
1202
|
+
* This implementation supports ONLY:
|
|
1203
|
+
*
|
|
1204
|
+
* function declarations
|
|
1205
|
+
*
|
|
1206
|
+
* Example:
|
|
1207
|
+
*
|
|
1208
|
+
* function myFunc() {}
|
|
1209
|
+
*
|
|
1210
|
+
* NOT:
|
|
1211
|
+
*
|
|
1212
|
+
* const myFunc = () => {}
|
|
1213
|
+
*
|
|
1214
|
+
* ------------------------------------------------------------
|
|
1215
|
+
* OPTIONAL LINE TARGETING
|
|
1216
|
+
* ------------------------------------------------------------
|
|
1217
|
+
*
|
|
1218
|
+
* Multiple functions with same name may exist.
|
|
1219
|
+
*
|
|
1220
|
+
* Example:
|
|
1221
|
+
*
|
|
1222
|
+
* function helper() {}
|
|
1223
|
+
*
|
|
1224
|
+
* function helper() {}
|
|
1225
|
+
*
|
|
1226
|
+
* line targeting allows deterministic selection.
|
|
1227
|
+
*
|
|
1228
|
+
* ------------------------------------------------------------
|
|
1229
|
+
* IMPORTANT ARCHITECTURAL IDEA
|
|
1230
|
+
* ------------------------------------------------------------
|
|
1231
|
+
*
|
|
1232
|
+
* This function ONLY removes function declarations.
|
|
1233
|
+
*
|
|
1234
|
+
* It does NOT:
|
|
1235
|
+
* - remove function calls
|
|
1236
|
+
* - remove JSX usage
|
|
1237
|
+
* - remove imports
|
|
1238
|
+
* - optimize imports
|
|
1239
|
+
*
|
|
1240
|
+
* Those should happen in separate cleanup passes.
|
|
1241
|
+
*
|
|
1242
|
+
* ------------------------------------------------------------
|
|
1243
|
+
* PARAMS
|
|
1244
|
+
* ------------------------------------------------------------
|
|
1245
|
+
*
|
|
1246
|
+
* @param {Object} params
|
|
1247
|
+
*
|
|
1248
|
+
* @param {string} params.code
|
|
1249
|
+
* Entire source code.
|
|
1250
|
+
*
|
|
1251
|
+
* @param {string} params.functionName
|
|
1252
|
+
* Name of function to remove.
|
|
1253
|
+
*
|
|
1254
|
+
* @param {number=} params.line
|
|
1255
|
+
* Optional function declaration line.
|
|
1256
|
+
*
|
|
1257
|
+
* ------------------------------------------------------------
|
|
1258
|
+
* RETURNS
|
|
1259
|
+
* ------------------------------------------------------------
|
|
1260
|
+
*
|
|
1261
|
+
* @returns {{ updatedCode: string }}
|
|
1262
|
+
* Updated source code.
|
|
1263
|
+
*
|
|
1264
|
+
*/
|
|
1265
|
+
function deleteFunction({ code, functionName, line, }) {
|
|
1266
|
+
// ----------------------------------------------------------
|
|
1267
|
+
// STEP 1:
|
|
1268
|
+
// Parse source code into AST
|
|
1269
|
+
// ----------------------------------------------------------
|
|
1270
|
+
const ast = parse(code, {
|
|
1271
|
+
sourceType: "module",
|
|
1272
|
+
plugins: ["jsx", "typescript"],
|
|
1273
|
+
});
|
|
1274
|
+
// ----------------------------------------------------------
|
|
1275
|
+
// STEP 2:
|
|
1276
|
+
// Traverse AST searching function declarations
|
|
1277
|
+
// ----------------------------------------------------------
|
|
1278
|
+
traverse.default(ast, {
|
|
1279
|
+
FunctionDeclaration(path) {
|
|
1280
|
+
// ------------------------------------------------------
|
|
1281
|
+
// Ignore anonymous functions
|
|
1282
|
+
// ------------------------------------------------------
|
|
1283
|
+
if (!path.node.id) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
// ------------------------------------------------------
|
|
1287
|
+
// Ignore unrelated functions
|
|
1288
|
+
// ------------------------------------------------------
|
|
1289
|
+
if (path.node.id.name !== functionName) {
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
// ------------------------------------------------------
|
|
1293
|
+
// Optional precise line targeting
|
|
1294
|
+
//
|
|
1295
|
+
// Useful when duplicate names exist
|
|
1296
|
+
// ------------------------------------------------------
|
|
1297
|
+
if (line && path.node.loc?.start?.line !== line) {
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
// ------------------------------------------------------
|
|
1301
|
+
// Remove entire function declaration
|
|
1302
|
+
//
|
|
1303
|
+
// BEFORE:
|
|
1304
|
+
//
|
|
1305
|
+
// function helper() {}
|
|
1306
|
+
//
|
|
1307
|
+
// AFTER:
|
|
1308
|
+
//
|
|
1309
|
+
// <removed>
|
|
1310
|
+
// ------------------------------------------------------
|
|
1311
|
+
path.remove();
|
|
1312
|
+
},
|
|
1313
|
+
});
|
|
1314
|
+
// ----------------------------------------------------------
|
|
1315
|
+
// STEP 3:
|
|
1316
|
+
// Generate updated source code from AST
|
|
1317
|
+
// ----------------------------------------------------------
|
|
1318
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* DescriptionForPrompt: Inserts new JSX into a target component.
|
|
1322
|
+
*
|
|
1323
|
+
* ------------------------------------------------------------
|
|
1324
|
+
* WHAT THIS FUNCTION DOES
|
|
1325
|
+
* ------------------------------------------------------------
|
|
1326
|
+
*
|
|
1327
|
+
* This function inserts JSX elements into:
|
|
1328
|
+
*
|
|
1329
|
+
* - a component's returned JSX
|
|
1330
|
+
* - a target JSX element
|
|
1331
|
+
*
|
|
1332
|
+
* Example:
|
|
1333
|
+
*
|
|
1334
|
+
* BEFORE:
|
|
1335
|
+
*
|
|
1336
|
+
* function Header() {
|
|
1337
|
+
* return (
|
|
1338
|
+
* <header>
|
|
1339
|
+
* <h1>Logo</h1>
|
|
1340
|
+
* </header>
|
|
1341
|
+
* );
|
|
1342
|
+
* }
|
|
1343
|
+
*
|
|
1344
|
+
* AFTER:
|
|
1345
|
+
*
|
|
1346
|
+
* function Header() {
|
|
1347
|
+
* return (
|
|
1348
|
+
* <header>
|
|
1349
|
+
* <h1>Logo</h1>
|
|
1350
|
+
* <button>Sign Up</button>
|
|
1351
|
+
* </header>
|
|
1352
|
+
* );
|
|
1353
|
+
* }
|
|
1354
|
+
*
|
|
1355
|
+
* ------------------------------------------------------------
|
|
1356
|
+
* WHY THIS FUNCTION EXISTS
|
|
1357
|
+
* ------------------------------------------------------------
|
|
1358
|
+
*
|
|
1359
|
+
* In UI generation systems,
|
|
1360
|
+
* users frequently say:
|
|
1361
|
+
*
|
|
1362
|
+
* - "add button"
|
|
1363
|
+
* - "add search input"
|
|
1364
|
+
* - "add hero section"
|
|
1365
|
+
* - "insert navbar"
|
|
1366
|
+
*
|
|
1367
|
+
* These commands usually mean:
|
|
1368
|
+
*
|
|
1369
|
+
* "insert JSX into existing UI"
|
|
1370
|
+
*
|
|
1371
|
+
* NOT:
|
|
1372
|
+
*
|
|
1373
|
+
* "replace entire component"
|
|
1374
|
+
*
|
|
1375
|
+
* ------------------------------------------------------------
|
|
1376
|
+
* WHY AST IS IMPORTANT
|
|
1377
|
+
* ------------------------------------------------------------
|
|
1378
|
+
*
|
|
1379
|
+
* JSX insertion using string replacement is extremely fragile.
|
|
1380
|
+
*
|
|
1381
|
+
* Problems:
|
|
1382
|
+
* - nested JSX
|
|
1383
|
+
* - formatting changes
|
|
1384
|
+
* - self-closing tags
|
|
1385
|
+
* - conditional rendering
|
|
1386
|
+
* - fragments
|
|
1387
|
+
*
|
|
1388
|
+
* AST guarantees:
|
|
1389
|
+
* - valid JSX structure
|
|
1390
|
+
* - deterministic insertion
|
|
1391
|
+
* - syntax-safe manipulation
|
|
1392
|
+
*
|
|
1393
|
+
* Useful commands:
|
|
1394
|
+
* - add sign up button: insertJSX(button)
|
|
1395
|
+
* - add logo in header: insertJSX(img/logo)
|
|
1396
|
+
* - insert search bar: insertJSX(input)
|
|
1397
|
+
* - add navigation links: insertJSX(nav links)
|
|
1398
|
+
* - add hero section: insertJSX(section)
|
|
1399
|
+
*
|
|
1400
|
+
* insertJSX
|
|
1401
|
+
↓
|
|
1402
|
+
ensureImport
|
|
1403
|
+
↓
|
|
1404
|
+
optimizeImports
|
|
1405
|
+
* ------------------------------------------------------------
|
|
1406
|
+
* IMPORTANT ARCHITECTURAL CONSTRAINT
|
|
1407
|
+
* ------------------------------------------------------------
|
|
1408
|
+
*
|
|
1409
|
+
* This implementation assumes:
|
|
1410
|
+
*
|
|
1411
|
+
* - React components use function declarations
|
|
1412
|
+
*
|
|
1413
|
+
* Example:
|
|
1414
|
+
*
|
|
1415
|
+
* function Header() {}
|
|
1416
|
+
*
|
|
1417
|
+
* NOT:
|
|
1418
|
+
*
|
|
1419
|
+
* const Header = () => {}
|
|
1420
|
+
*
|
|
1421
|
+
* ------------------------------------------------------------
|
|
1422
|
+
* INSERTION STRATEGY
|
|
1423
|
+
* ------------------------------------------------------------
|
|
1424
|
+
*
|
|
1425
|
+
* This function inserts JSX:
|
|
1426
|
+
*
|
|
1427
|
+
* - inside a target JSX element
|
|
1428
|
+
*
|
|
1429
|
+
* Example:
|
|
1430
|
+
*
|
|
1431
|
+
* targetElement = "header"
|
|
1432
|
+
*
|
|
1433
|
+
* JSX gets inserted INSIDE:
|
|
1434
|
+
*
|
|
1435
|
+
* <header>
|
|
1436
|
+
* HERE
|
|
1437
|
+
* </header>
|
|
1438
|
+
*
|
|
1439
|
+
* ------------------------------------------------------------
|
|
1440
|
+
* SUPPORTED TARGETS
|
|
1441
|
+
* ------------------------------------------------------------
|
|
1442
|
+
*
|
|
1443
|
+
* HTML tags:
|
|
1444
|
+
* - div
|
|
1445
|
+
* - header
|
|
1446
|
+
* - main
|
|
1447
|
+
* - section
|
|
1448
|
+
*
|
|
1449
|
+
* React components:
|
|
1450
|
+
* - Layout
|
|
1451
|
+
* - Sidebar
|
|
1452
|
+
* - Card
|
|
1453
|
+
*
|
|
1454
|
+
* ------------------------------------------------------------
|
|
1455
|
+
* JSX FORMAT
|
|
1456
|
+
* ------------------------------------------------------------
|
|
1457
|
+
*
|
|
1458
|
+
* jsx should contain valid JSX ONLY.
|
|
1459
|
+
*
|
|
1460
|
+
* Example:
|
|
1461
|
+
*
|
|
1462
|
+
* <button>Login</button>
|
|
1463
|
+
*
|
|
1464
|
+
* NOT:
|
|
1465
|
+
*
|
|
1466
|
+
* return <button />
|
|
1467
|
+
*
|
|
1468
|
+
* ------------------------------------------------------------
|
|
1469
|
+
* IMPORTANT LIMITATION
|
|
1470
|
+
* ------------------------------------------------------------
|
|
1471
|
+
*
|
|
1472
|
+
* This function currently inserts:
|
|
1473
|
+
*
|
|
1474
|
+
* - at end of target children
|
|
1475
|
+
*
|
|
1476
|
+
* Future improvements:
|
|
1477
|
+
* - before element
|
|
1478
|
+
* - after element
|
|
1479
|
+
* - prepend
|
|
1480
|
+
* - replace child
|
|
1481
|
+
*
|
|
1482
|
+
* ------------------------------------------------------------
|
|
1483
|
+
* PARAMS
|
|
1484
|
+
* ------------------------------------------------------------
|
|
1485
|
+
*
|
|
1486
|
+
* @param {Object} params
|
|
1487
|
+
*
|
|
1488
|
+
* @param {string} params.code
|
|
1489
|
+
* Entire source code like this not just jsx
|
|
1490
|
+
* function Header() {
|
|
1491
|
+
* return (
|
|
1492
|
+
* <header>
|
|
1493
|
+
* <h1>Logo</h1>
|
|
1494
|
+
* </header>
|
|
1495
|
+
* );
|
|
1496
|
+
* }
|
|
1497
|
+
*
|
|
1498
|
+
* @param {string} params.componentName
|
|
1499
|
+
* Component containing JSX.
|
|
1500
|
+
*
|
|
1501
|
+
* @param {string} params.targetElement
|
|
1502
|
+
* JSX element where content should be inserted.
|
|
1503
|
+
*
|
|
1504
|
+
* Example:
|
|
1505
|
+
* "header"
|
|
1506
|
+
*
|
|
1507
|
+
* @param {string} params.jsx
|
|
1508
|
+
* Only JSX to insert.
|
|
1509
|
+
* <button>Login</button>
|
|
1510
|
+
*
|
|
1511
|
+
* ------------------------------------------------------------
|
|
1512
|
+
* RETURNS
|
|
1513
|
+
* ------------------------------------------------------------
|
|
1514
|
+
*
|
|
1515
|
+
* @returns {{ updatedCode: string }}
|
|
1516
|
+
* Updated source code.
|
|
1517
|
+
*
|
|
1518
|
+
*/
|
|
1519
|
+
function insertJSX({ code, componentName, targetElement, jsx, position }, context) {
|
|
1520
|
+
// const updatedCode = await formatCode(code);
|
|
1521
|
+
// return { success: true, updatedCode };
|
|
1522
|
+
// ----------------------------------------------------------
|
|
1523
|
+
// STEP 1:
|
|
1524
|
+
// Parse source code into AST
|
|
1525
|
+
// ----------------------------------------------------------
|
|
1526
|
+
const ast = parse(code, {
|
|
1527
|
+
sourceType: "module",
|
|
1528
|
+
plugins: ["jsx", "typescript"],
|
|
1529
|
+
});
|
|
1530
|
+
// ----------------------------------------------------------
|
|
1531
|
+
// STEP 2:
|
|
1532
|
+
// Parse JSX snippet into AST node
|
|
1533
|
+
//
|
|
1534
|
+
// Example:
|
|
1535
|
+
//
|
|
1536
|
+
// <button>Login</button>
|
|
1537
|
+
// ----------------------------------------------------------
|
|
1538
|
+
const jsxNode = parseExpression(jsx, {
|
|
1539
|
+
plugins: ["jsx"],
|
|
1540
|
+
});
|
|
1541
|
+
// ----------------------------------------------------------
|
|
1542
|
+
// STEP 3:
|
|
1543
|
+
// Traverse AST searching target component
|
|
1544
|
+
// ----------------------------------------------------------
|
|
1545
|
+
traverse.default(ast, {
|
|
1546
|
+
FunctionDeclaration(path) {
|
|
1547
|
+
// ------------------------------------------------------
|
|
1548
|
+
// Ignore unrelated components
|
|
1549
|
+
// ------------------------------------------------------
|
|
1550
|
+
if (path.node.id?.name !== componentName) {
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
// ------------------------------------------------------
|
|
1554
|
+
// Traverse inside component body
|
|
1555
|
+
// ------------------------------------------------------
|
|
1556
|
+
path.traverse({
|
|
1557
|
+
JSXElement(jsxPath) {
|
|
1558
|
+
// --------------------------------------------------
|
|
1559
|
+
// Get opening tag name
|
|
1560
|
+
//
|
|
1561
|
+
// Example:
|
|
1562
|
+
//
|
|
1563
|
+
// <header>
|
|
1564
|
+
// ^
|
|
1565
|
+
// --------------------------------------------------
|
|
1566
|
+
const openingElement = jsxPath.node.openingElement;
|
|
1567
|
+
// --------------------------------------------------
|
|
1568
|
+
// Ignore unsupported tag types
|
|
1569
|
+
// --------------------------------------------------
|
|
1570
|
+
if (!t.isJSXIdentifier(openingElement.name)) {
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
// --------------------------------------------------
|
|
1574
|
+
// Ignore unrelated JSX elements
|
|
1575
|
+
// --------------------------------------------------
|
|
1576
|
+
if (!helpers.matchesSelector({
|
|
1577
|
+
openingElement,
|
|
1578
|
+
selector: targetElement,
|
|
1579
|
+
})) {
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
// --------------------------------------------------
|
|
1583
|
+
// Insert JSX according to requested position
|
|
1584
|
+
//
|
|
1585
|
+
// first:
|
|
1586
|
+
//
|
|
1587
|
+
// <header>
|
|
1588
|
+
// <button />
|
|
1589
|
+
// <h1 />
|
|
1590
|
+
// </header>
|
|
1591
|
+
//
|
|
1592
|
+
// last:
|
|
1593
|
+
//
|
|
1594
|
+
// <header>
|
|
1595
|
+
// <h1 />
|
|
1596
|
+
// <button />
|
|
1597
|
+
// </header>
|
|
1598
|
+
// --------------------------------------------------
|
|
1599
|
+
if (position === "first") {
|
|
1600
|
+
jsxPath.node.children.unshift(t.jsxExpressionContainer(jsxNode), t.jsxText("\n"));
|
|
1601
|
+
}
|
|
1602
|
+
else {
|
|
1603
|
+
jsxPath.node.children.push(t.jsxText("\n"), t.jsxExpressionContainer(jsxNode));
|
|
1604
|
+
}
|
|
1605
|
+
// --------------------------------------------------
|
|
1606
|
+
// Stop traversal after insertion
|
|
1607
|
+
// --------------------------------------------------
|
|
1608
|
+
jsxPath.stop();
|
|
1609
|
+
},
|
|
1610
|
+
});
|
|
1611
|
+
},
|
|
1612
|
+
});
|
|
1613
|
+
// ----------------------------------------------------------
|
|
1614
|
+
// STEP 4:
|
|
1615
|
+
// Generate updated source code from AST
|
|
1616
|
+
// ----------------------------------------------------------
|
|
1617
|
+
const updatedCode = utils.formatCode(generate(ast).code);
|
|
1618
|
+
return { success: true, updatedCode };
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* DescriptionForPrompt: Replaces an existing JSX element with new JSX.
|
|
1622
|
+
*
|
|
1623
|
+
* ------------------------------------------------------------
|
|
1624
|
+
* WHAT THIS FUNCTION DOES
|
|
1625
|
+
* ------------------------------------------------------------
|
|
1626
|
+
*
|
|
1627
|
+
* This function searches for a JSX element and completely
|
|
1628
|
+
* replaces it with new JSX.
|
|
1629
|
+
*
|
|
1630
|
+
* Example:
|
|
1631
|
+
*
|
|
1632
|
+
* BEFORE:
|
|
1633
|
+
*
|
|
1634
|
+
* <button>Login</button>
|
|
1635
|
+
*
|
|
1636
|
+
* AFTER:
|
|
1637
|
+
*
|
|
1638
|
+
* <button className="primary">
|
|
1639
|
+
* Sign Up
|
|
1640
|
+
* </button>
|
|
1641
|
+
*
|
|
1642
|
+
* ------------------------------------------------------------
|
|
1643
|
+
* IMPORTANT DIFFERENCE
|
|
1644
|
+
* ------------------------------------------------------------
|
|
1645
|
+
*
|
|
1646
|
+
* insertJSX
|
|
1647
|
+
* → inserts INSIDE existing JSX
|
|
1648
|
+
*
|
|
1649
|
+
* replaceJSX
|
|
1650
|
+
* → completely replaces existing JSX node
|
|
1651
|
+
*
|
|
1652
|
+
* ------------------------------------------------------------
|
|
1653
|
+
* EXAMPLE
|
|
1654
|
+
* ------------------------------------------------------------
|
|
1655
|
+
*
|
|
1656
|
+
* BEFORE:
|
|
1657
|
+
*
|
|
1658
|
+
* function Header() {
|
|
1659
|
+
* return (
|
|
1660
|
+
* <header>
|
|
1661
|
+
* <button>Login</button>
|
|
1662
|
+
* </header>
|
|
1663
|
+
* );
|
|
1664
|
+
* }
|
|
1665
|
+
*
|
|
1666
|
+
* AFTER:
|
|
1667
|
+
*
|
|
1668
|
+
* function Header() {
|
|
1669
|
+
* return (
|
|
1670
|
+
* <header>
|
|
1671
|
+
* <button className="primary">
|
|
1672
|
+
* Sign Up
|
|
1673
|
+
* </button>
|
|
1674
|
+
* </header>
|
|
1675
|
+
* );
|
|
1676
|
+
* }
|
|
1677
|
+
*
|
|
1678
|
+
* ------------------------------------------------------------
|
|
1679
|
+
* WHY THIS FUNCTION EXISTS
|
|
1680
|
+
* ------------------------------------------------------------
|
|
1681
|
+
*
|
|
1682
|
+
* Users frequently give commands like:
|
|
1683
|
+
*
|
|
1684
|
+
* - "replace login button"
|
|
1685
|
+
* - "change navbar into sidebar"
|
|
1686
|
+
* - "replace div with section"
|
|
1687
|
+
* - "make button outlined"
|
|
1688
|
+
*
|
|
1689
|
+
* These commands usually mean:
|
|
1690
|
+
*
|
|
1691
|
+
* "replace existing JSX element"
|
|
1692
|
+
*
|
|
1693
|
+
* NOT:
|
|
1694
|
+
*
|
|
1695
|
+
* "insert new JSX"
|
|
1696
|
+
*
|
|
1697
|
+
* ------------------------------------------------------------
|
|
1698
|
+
* WHY AST IS IMPORTANT
|
|
1699
|
+
* ------------------------------------------------------------
|
|
1700
|
+
*
|
|
1701
|
+
* JSX replacement using string replacement is fragile.
|
|
1702
|
+
*
|
|
1703
|
+
* Problems:
|
|
1704
|
+
* - nested JSX
|
|
1705
|
+
* - duplicate elements
|
|
1706
|
+
* - multiline formatting
|
|
1707
|
+
* - invalid syntax generation
|
|
1708
|
+
* - conditional rendering
|
|
1709
|
+
*
|
|
1710
|
+
* AST guarantees:
|
|
1711
|
+
* - valid JSX
|
|
1712
|
+
* - deterministic replacement
|
|
1713
|
+
* - structure-aware manipulation
|
|
1714
|
+
*
|
|
1715
|
+
* ------------------------------------------------------------
|
|
1716
|
+
* IMPORTANT ARCHITECTURAL CONSTRAINT
|
|
1717
|
+
* ------------------------------------------------------------
|
|
1718
|
+
*
|
|
1719
|
+
* This implementation assumes:
|
|
1720
|
+
*
|
|
1721
|
+
* - React components use function declarations
|
|
1722
|
+
*
|
|
1723
|
+
* Example:
|
|
1724
|
+
*
|
|
1725
|
+
* function Header() {}
|
|
1726
|
+
*
|
|
1727
|
+
* NOT:
|
|
1728
|
+
*
|
|
1729
|
+
* const Header = () => {}
|
|
1730
|
+
*
|
|
1731
|
+
* ------------------------------------------------------------
|
|
1732
|
+
* REPLACEMENT STRATEGY
|
|
1733
|
+
* ------------------------------------------------------------
|
|
1734
|
+
*
|
|
1735
|
+
* This function replaces:
|
|
1736
|
+
*
|
|
1737
|
+
* - first matching JSX element
|
|
1738
|
+
*
|
|
1739
|
+
* Example:
|
|
1740
|
+
*
|
|
1741
|
+
* targetElement = "button"
|
|
1742
|
+
*
|
|
1743
|
+
* FIRST:
|
|
1744
|
+
*
|
|
1745
|
+
* <button />
|
|
1746
|
+
*
|
|
1747
|
+
* gets replaced.
|
|
1748
|
+
*
|
|
1749
|
+
* ------------------------------------------------------------
|
|
1750
|
+
* SUPPORTED TARGETS
|
|
1751
|
+
* ------------------------------------------------------------
|
|
1752
|
+
*
|
|
1753
|
+
* HTML tags:
|
|
1754
|
+
* - div
|
|
1755
|
+
* - button
|
|
1756
|
+
* - header
|
|
1757
|
+
* - section
|
|
1758
|
+
*
|
|
1759
|
+
* React components:
|
|
1760
|
+
* - Card
|
|
1761
|
+
* - Sidebar
|
|
1762
|
+
* - Layout
|
|
1763
|
+
*
|
|
1764
|
+
* ------------------------------------------------------------
|
|
1765
|
+
* JSX FORMAT
|
|
1766
|
+
* ------------------------------------------------------------
|
|
1767
|
+
*
|
|
1768
|
+
* newJSX should contain valid JSX ONLY.
|
|
1769
|
+
*
|
|
1770
|
+
* Example:
|
|
1771
|
+
*
|
|
1772
|
+
* <button>Save</button>
|
|
1773
|
+
*
|
|
1774
|
+
* NOT:
|
|
1775
|
+
*
|
|
1776
|
+
* return <button />
|
|
1777
|
+
*
|
|
1778
|
+
* Useful commands:
|
|
1779
|
+
* - replace login button with signup button: replaceJSX(button)
|
|
1780
|
+
* - change div to section: replaceJSX(div)
|
|
1781
|
+
* - replace navbar with sidebar: replaceJSX(Navbar)
|
|
1782
|
+
* - make button outlined: replaceJSX(button)
|
|
1783
|
+
* - replace image with video: replaceJSX(img)
|
|
1784
|
+
*
|
|
1785
|
+
* replaceJSX
|
|
1786
|
+
↓
|
|
1787
|
+
ensureImport
|
|
1788
|
+
↓
|
|
1789
|
+
optimizeImports
|
|
1790
|
+
* ------------------------------------------------------------
|
|
1791
|
+
* IMPORTANT LIMITATION
|
|
1792
|
+
* ------------------------------------------------------------
|
|
1793
|
+
*
|
|
1794
|
+
* Current implementation:
|
|
1795
|
+
*
|
|
1796
|
+
* - replaces first matching JSX node only
|
|
1797
|
+
*
|
|
1798
|
+
* Future improvements:
|
|
1799
|
+
* - replace nth match
|
|
1800
|
+
* - replace by attribute
|
|
1801
|
+
* - replace by text content
|
|
1802
|
+
* - replace multiple nodes
|
|
1803
|
+
*
|
|
1804
|
+
* ------------------------------------------------------------
|
|
1805
|
+
* PARAMS
|
|
1806
|
+
* ------------------------------------------------------------
|
|
1807
|
+
*
|
|
1808
|
+
* @param {Object} params
|
|
1809
|
+
*
|
|
1810
|
+
* @param {string} params.code
|
|
1811
|
+
* Entire source code.
|
|
1812
|
+
*
|
|
1813
|
+
* @param {string} params.componentName
|
|
1814
|
+
* Component containing JSX.
|
|
1815
|
+
*
|
|
1816
|
+
* @param {string} params.targetElement
|
|
1817
|
+
* JSX element to replace.
|
|
1818
|
+
*
|
|
1819
|
+
* Example:
|
|
1820
|
+
* "button"
|
|
1821
|
+
*
|
|
1822
|
+
* @param {string} params.newJSX
|
|
1823
|
+
* Replacement JSX.
|
|
1824
|
+
*
|
|
1825
|
+
* ------------------------------------------------------------
|
|
1826
|
+
* RETURNS
|
|
1827
|
+
* ------------------------------------------------------------
|
|
1828
|
+
*
|
|
1829
|
+
* @returns {{ updatedCode: string }}
|
|
1830
|
+
* Updated source code.
|
|
1831
|
+
*
|
|
1832
|
+
*/
|
|
1833
|
+
function replaceJSX({ code, componentName, targetElement, newJSX }, context) {
|
|
1834
|
+
// ----------------------------------------------------------
|
|
1835
|
+
// STEP 1:
|
|
1836
|
+
// Parse source code into AST
|
|
1837
|
+
// ----------------------------------------------------------
|
|
1838
|
+
const ast = parse(code, {
|
|
1839
|
+
sourceType: "module",
|
|
1840
|
+
plugins: ["jsx", "typescript"],
|
|
1841
|
+
});
|
|
1842
|
+
// ----------------------------------------------------------
|
|
1843
|
+
// STEP 2:
|
|
1844
|
+
// Parse replacement JSX into AST node
|
|
1845
|
+
//
|
|
1846
|
+
// Example:
|
|
1847
|
+
//
|
|
1848
|
+
// <button>Save</button>
|
|
1849
|
+
// ----------------------------------------------------------
|
|
1850
|
+
const replacementNode = parseExpression(newJSX, {
|
|
1851
|
+
plugins: ["jsx"],
|
|
1852
|
+
});
|
|
1853
|
+
// ----------------------------------------------------------
|
|
1854
|
+
// STEP 3:
|
|
1855
|
+
// Track whether replacement already happened
|
|
1856
|
+
//
|
|
1857
|
+
// WHY?
|
|
1858
|
+
//
|
|
1859
|
+
// Prevent replacing multiple nodes accidentally.
|
|
1860
|
+
// ----------------------------------------------------------
|
|
1861
|
+
let replaced = false;
|
|
1862
|
+
// ----------------------------------------------------------
|
|
1863
|
+
// STEP 4:
|
|
1864
|
+
// Traverse AST searching component
|
|
1865
|
+
// ----------------------------------------------------------
|
|
1866
|
+
traverse.default(ast, {
|
|
1867
|
+
FunctionDeclaration(path) {
|
|
1868
|
+
// ------------------------------------------------------
|
|
1869
|
+
// Ignore unrelated components
|
|
1870
|
+
// ------------------------------------------------------
|
|
1871
|
+
if (path.node.id?.name !== componentName) {
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
// ------------------------------------------------------
|
|
1875
|
+
// Traverse JSX inside component
|
|
1876
|
+
// ------------------------------------------------------
|
|
1877
|
+
path.traverse({
|
|
1878
|
+
JSXElement(jsxPath) {
|
|
1879
|
+
// --------------------------------------------------
|
|
1880
|
+
// Stop if replacement already done
|
|
1881
|
+
// --------------------------------------------------
|
|
1882
|
+
if (replaced) {
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
// --------------------------------------------------
|
|
1886
|
+
// Get opening JSX tag
|
|
1887
|
+
//
|
|
1888
|
+
// Example:
|
|
1889
|
+
//
|
|
1890
|
+
// <button>
|
|
1891
|
+
// ^
|
|
1892
|
+
// --------------------------------------------------
|
|
1893
|
+
const openingElement = jsxPath.node.openingElement;
|
|
1894
|
+
// --------------------------------------------------
|
|
1895
|
+
// Ignore unsupported tag types
|
|
1896
|
+
// --------------------------------------------------
|
|
1897
|
+
if (!t.isJSXIdentifier(openingElement.name)) {
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
// --------------------------------------------------
|
|
1901
|
+
// Ignore unrelated JSX elements
|
|
1902
|
+
// --------------------------------------------------
|
|
1903
|
+
if (!helpers.matchesSelector({
|
|
1904
|
+
openingElement,
|
|
1905
|
+
selector: targetElement,
|
|
1906
|
+
})) {
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
// --------------------------------------------------
|
|
1910
|
+
// Replace JSX node
|
|
1911
|
+
//
|
|
1912
|
+
// BEFORE:
|
|
1913
|
+
//
|
|
1914
|
+
// <button>Login</button>
|
|
1915
|
+
//
|
|
1916
|
+
// AFTER:
|
|
1917
|
+
//
|
|
1918
|
+
// <button>Save</button>
|
|
1919
|
+
// --------------------------------------------------
|
|
1920
|
+
jsxPath.replaceWith(replacementNode);
|
|
1921
|
+
// --------------------------------------------------
|
|
1922
|
+
// Mark replacement completed
|
|
1923
|
+
// --------------------------------------------------
|
|
1924
|
+
replaced = true;
|
|
1925
|
+
// --------------------------------------------------
|
|
1926
|
+
// Stop traversal for performance/safety
|
|
1927
|
+
// --------------------------------------------------
|
|
1928
|
+
jsxPath.stop();
|
|
1929
|
+
},
|
|
1930
|
+
});
|
|
1931
|
+
},
|
|
1932
|
+
});
|
|
1933
|
+
// ----------------------------------------------------------
|
|
1934
|
+
// STEP 5:
|
|
1935
|
+
// Generate updated source code from AST
|
|
1936
|
+
// ----------------------------------------------------------
|
|
1937
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* DescriptionForPrompt: Removes a JSX element from a component.
|
|
1941
|
+
*
|
|
1942
|
+
* ------------------------------------------------------------
|
|
1943
|
+
* WHAT THIS FUNCTION DOES
|
|
1944
|
+
* ------------------------------------------------------------
|
|
1945
|
+
*
|
|
1946
|
+
* This function searches for a JSX element and removes it
|
|
1947
|
+
* completely from the JSX tree.
|
|
1948
|
+
*
|
|
1949
|
+
* Example:
|
|
1950
|
+
*
|
|
1951
|
+
* BEFORE:
|
|
1952
|
+
*
|
|
1953
|
+
* <header>
|
|
1954
|
+
* <Logo />
|
|
1955
|
+
* <button>Login</button>
|
|
1956
|
+
* </header>
|
|
1957
|
+
*
|
|
1958
|
+
* AFTER:
|
|
1959
|
+
*
|
|
1960
|
+
* <header>
|
|
1961
|
+
* <Logo />
|
|
1962
|
+
* </header>
|
|
1963
|
+
*
|
|
1964
|
+
* ------------------------------------------------------------
|
|
1965
|
+
* IMPORTANT DIFFERENCE
|
|
1966
|
+
* ------------------------------------------------------------
|
|
1967
|
+
*
|
|
1968
|
+
* insertJSX
|
|
1969
|
+
* → adds JSX
|
|
1970
|
+
*
|
|
1971
|
+
* replaceJSX
|
|
1972
|
+
* → swaps JSX
|
|
1973
|
+
*
|
|
1974
|
+
* removeJSX
|
|
1975
|
+
* → deletes JSX
|
|
1976
|
+
*
|
|
1977
|
+
* ------------------------------------------------------------
|
|
1978
|
+
* WHY THIS FUNCTION EXISTS
|
|
1979
|
+
* ------------------------------------------------------------
|
|
1980
|
+
*
|
|
1981
|
+
* Users frequently give commands like:
|
|
1982
|
+
*
|
|
1983
|
+
* - "remove login button"
|
|
1984
|
+
* - "delete hero section"
|
|
1985
|
+
* - "remove image"
|
|
1986
|
+
* - "remove sidebar"
|
|
1987
|
+
* - "hide footer"
|
|
1988
|
+
*
|
|
1989
|
+
* These commands usually mean:
|
|
1990
|
+
*
|
|
1991
|
+
* "remove JSX element from UI tree"
|
|
1992
|
+
*
|
|
1993
|
+
* NOT:
|
|
1994
|
+
*
|
|
1995
|
+
* "delete component file"
|
|
1996
|
+
*
|
|
1997
|
+
* ------------------------------------------------------------
|
|
1998
|
+
* WHY AST IS IMPORTANT
|
|
1999
|
+
* ------------------------------------------------------------
|
|
2000
|
+
*
|
|
2001
|
+
* JSX removal using string replacement is fragile.
|
|
2002
|
+
*
|
|
2003
|
+
* Problems:
|
|
2004
|
+
* - nested JSX
|
|
2005
|
+
* - multiline formatting
|
|
2006
|
+
* - duplicate elements
|
|
2007
|
+
* - invalid JSX after deletion
|
|
2008
|
+
* - conditional rendering
|
|
2009
|
+
*
|
|
2010
|
+
* AST guarantees:
|
|
2011
|
+
* - syntax-safe deletion
|
|
2012
|
+
* - deterministic traversal
|
|
2013
|
+
* - structure-aware removal
|
|
2014
|
+
*
|
|
2015
|
+
* ------------------------------------------------------------
|
|
2016
|
+
* IMPORTANT ARCHITECTURAL CONSTRAINT
|
|
2017
|
+
* ------------------------------------------------------------
|
|
2018
|
+
*
|
|
2019
|
+
* This implementation assumes:
|
|
2020
|
+
*
|
|
2021
|
+
* - React components use function declarations
|
|
2022
|
+
*
|
|
2023
|
+
* Example:
|
|
2024
|
+
*
|
|
2025
|
+
* function Header() {}
|
|
2026
|
+
*
|
|
2027
|
+
* NOT:
|
|
2028
|
+
*
|
|
2029
|
+
* const Header = () => {}
|
|
2030
|
+
*
|
|
2031
|
+
* ------------------------------------------------------------
|
|
2032
|
+
* REMOVAL STRATEGY
|
|
2033
|
+
* ------------------------------------------------------------
|
|
2034
|
+
*
|
|
2035
|
+
* This function removes:
|
|
2036
|
+
*
|
|
2037
|
+
* - first matching JSX element
|
|
2038
|
+
*
|
|
2039
|
+
* Example:
|
|
2040
|
+
*
|
|
2041
|
+
* targetElement = "button"
|
|
2042
|
+
*
|
|
2043
|
+
* FIRST:
|
|
2044
|
+
*
|
|
2045
|
+
* <button />
|
|
2046
|
+
*
|
|
2047
|
+
* gets removed.
|
|
2048
|
+
*
|
|
2049
|
+
* ------------------------------------------------------------
|
|
2050
|
+
* SUPPORTED TARGETS
|
|
2051
|
+
* ------------------------------------------------------------
|
|
2052
|
+
*
|
|
2053
|
+
* HTML tags:
|
|
2054
|
+
* - div
|
|
2055
|
+
* - button
|
|
2056
|
+
* - img
|
|
2057
|
+
* - section
|
|
2058
|
+
*
|
|
2059
|
+
* React components:
|
|
2060
|
+
* - Sidebar
|
|
2061
|
+
* - Card
|
|
2062
|
+
* - Navbar
|
|
2063
|
+
*
|
|
2064
|
+
* ------------------------------------------------------------
|
|
2065
|
+
* IMPORTANT LIMITATION
|
|
2066
|
+
* ------------------------------------------------------------
|
|
2067
|
+
*
|
|
2068
|
+
* Current implementation:
|
|
2069
|
+
*
|
|
2070
|
+
* - removes first matching JSX node only
|
|
2071
|
+
*
|
|
2072
|
+
* Future improvements:
|
|
2073
|
+
* - remove nth match
|
|
2074
|
+
* - remove by className
|
|
2075
|
+
* - remove by text content
|
|
2076
|
+
* - remove multiple nodes
|
|
2077
|
+
*
|
|
2078
|
+
* Useful commands:
|
|
2079
|
+
* - remove login button: removeJSX(button)
|
|
2080
|
+
* - delete hero section: removeJSX(section)
|
|
2081
|
+
* - remove sidebar: removeJSX(Sidebar)
|
|
2082
|
+
* - hide footer links: removeJSX(footer)
|
|
2083
|
+
* - remove profile image: removeJSX(img)
|
|
2084
|
+
* ------------------------------------------------------------
|
|
2085
|
+
* IMPORTANT ARCHITECTURAL IDEA
|
|
2086
|
+
* ------------------------------------------------------------
|
|
2087
|
+
*
|
|
2088
|
+
* removeJSX should usually be followed by:
|
|
2089
|
+
*
|
|
2090
|
+
* - removeImport
|
|
2091
|
+
* - optimizeImports
|
|
2092
|
+
*
|
|
2093
|
+
* if deleted JSX used imported components.
|
|
2094
|
+
*
|
|
2095
|
+
* ------------------------------------------------------------
|
|
2096
|
+
* PARAMS
|
|
2097
|
+
* ------------------------------------------------------------
|
|
2098
|
+
*
|
|
2099
|
+
* @param {Object} params
|
|
2100
|
+
*
|
|
2101
|
+
* @param {string} params.code
|
|
2102
|
+
* Entire source code.
|
|
2103
|
+
*
|
|
2104
|
+
* @param {string} params.componentName
|
|
2105
|
+
* Component containing JSX.
|
|
2106
|
+
*
|
|
2107
|
+
* @param {string} params.targetElement
|
|
2108
|
+
* JSX element to remove.
|
|
2109
|
+
*
|
|
2110
|
+
* Example:
|
|
2111
|
+
* "button"
|
|
2112
|
+
*
|
|
2113
|
+
* ------------------------------------------------------------
|
|
2114
|
+
* RETURNS
|
|
2115
|
+
* ------------------------------------------------------------
|
|
2116
|
+
*
|
|
2117
|
+
* @returns {{ updatedCode: string }}
|
|
2118
|
+
* Updated source code.
|
|
2119
|
+
*
|
|
2120
|
+
*/
|
|
2121
|
+
function removeJSX({ code, componentName, targetElement }, context) {
|
|
2122
|
+
// ----------------------------------------------------------
|
|
2123
|
+
// STEP 1:
|
|
2124
|
+
// Parse source code into AST
|
|
2125
|
+
// ----------------------------------------------------------
|
|
2126
|
+
const ast = parse(code, {
|
|
2127
|
+
sourceType: "module",
|
|
2128
|
+
plugins: ["jsx", "typescript"],
|
|
2129
|
+
});
|
|
2130
|
+
// ----------------------------------------------------------
|
|
2131
|
+
// STEP 2:
|
|
2132
|
+
// Track whether removal already happened
|
|
2133
|
+
//
|
|
2134
|
+
// WHY?
|
|
2135
|
+
//
|
|
2136
|
+
// Prevent accidental removal of multiple elements.
|
|
2137
|
+
// ----------------------------------------------------------
|
|
2138
|
+
let removed = false;
|
|
2139
|
+
// ----------------------------------------------------------
|
|
2140
|
+
// STEP 3:
|
|
2141
|
+
// Traverse AST searching target component
|
|
2142
|
+
// ----------------------------------------------------------
|
|
2143
|
+
traverse.default(ast, {
|
|
2144
|
+
FunctionDeclaration(path) {
|
|
2145
|
+
// ------------------------------------------------------
|
|
2146
|
+
// Ignore unrelated components
|
|
2147
|
+
// ------------------------------------------------------
|
|
2148
|
+
if (path.node.id?.name !== componentName) {
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
// ------------------------------------------------------
|
|
2152
|
+
// Traverse JSX inside component
|
|
2153
|
+
// ------------------------------------------------------
|
|
2154
|
+
path.traverse({
|
|
2155
|
+
JSXElement(jsxPath) {
|
|
2156
|
+
// --------------------------------------------------
|
|
2157
|
+
// Stop if removal already completed
|
|
2158
|
+
// --------------------------------------------------
|
|
2159
|
+
if (removed) {
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
// --------------------------------------------------
|
|
2163
|
+
// Get opening JSX element
|
|
2164
|
+
//
|
|
2165
|
+
// Example:
|
|
2166
|
+
//
|
|
2167
|
+
// <button>
|
|
2168
|
+
// ^
|
|
2169
|
+
// --------------------------------------------------
|
|
2170
|
+
const openingElement = jsxPath.node.openingElement;
|
|
2171
|
+
// --------------------------------------------------
|
|
2172
|
+
// Ignore unsupported tag types
|
|
2173
|
+
// --------------------------------------------------
|
|
2174
|
+
if (!t.isJSXIdentifier(openingElement.name)) {
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
// --------------------------------------------------
|
|
2178
|
+
// Ignore unrelated JSX elements
|
|
2179
|
+
// --------------------------------------------------
|
|
2180
|
+
// if (openingElement.name.name !== targetElement) {
|
|
2181
|
+
// return;
|
|
2182
|
+
// }
|
|
2183
|
+
if (!helpers.matchesSelector({
|
|
2184
|
+
openingElement,
|
|
2185
|
+
selector: targetElement,
|
|
2186
|
+
})) {
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
// --------------------------------------------------
|
|
2190
|
+
// Remove JSX node
|
|
2191
|
+
//
|
|
2192
|
+
// BEFORE:
|
|
2193
|
+
//
|
|
2194
|
+
// <button>Login</button>
|
|
2195
|
+
//
|
|
2196
|
+
// AFTER:
|
|
2197
|
+
//
|
|
2198
|
+
// <removed>
|
|
2199
|
+
// --------------------------------------------------
|
|
2200
|
+
jsxPath.remove();
|
|
2201
|
+
// --------------------------------------------------
|
|
2202
|
+
// Mark removal completed
|
|
2203
|
+
// --------------------------------------------------
|
|
2204
|
+
removed = true;
|
|
2205
|
+
// --------------------------------------------------
|
|
2206
|
+
// Stop traversal for performance/safety
|
|
2207
|
+
// --------------------------------------------------
|
|
2208
|
+
jsxPath.stop();
|
|
2209
|
+
},
|
|
2210
|
+
});
|
|
2211
|
+
},
|
|
2212
|
+
});
|
|
2213
|
+
// ----------------------------------------------------------
|
|
2214
|
+
// STEP 4:
|
|
2215
|
+
// Generate updated source code from AST
|
|
2216
|
+
// ----------------------------------------------------------
|
|
2217
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* DescriptionForPrompt: Wraps a JSX element with a parent JSX element.
|
|
2221
|
+
*
|
|
2222
|
+
* ------------------------------------------------------------
|
|
2223
|
+
* WHAT THIS FUNCTION DOES
|
|
2224
|
+
* ------------------------------------------------------------
|
|
2225
|
+
*
|
|
2226
|
+
* This function takes an existing JSX element and wraps it
|
|
2227
|
+
* inside a new parent JSX element.
|
|
2228
|
+
*
|
|
2229
|
+
* Example:
|
|
2230
|
+
*
|
|
2231
|
+
* BEFORE:
|
|
2232
|
+
*
|
|
2233
|
+
* <button>Login</button>
|
|
2234
|
+
*
|
|
2235
|
+
* AFTER:
|
|
2236
|
+
*
|
|
2237
|
+
* <div className="container">
|
|
2238
|
+
* <button>Login</button>
|
|
2239
|
+
* </div>
|
|
2240
|
+
*
|
|
2241
|
+
* ------------------------------------------------------------
|
|
2242
|
+
* IMPORTANT DIFFERENCE
|
|
2243
|
+
* ------------------------------------------------------------
|
|
2244
|
+
*
|
|
2245
|
+
* insertJSX
|
|
2246
|
+
* → adds new JSX
|
|
2247
|
+
*
|
|
2248
|
+
* replaceJSX
|
|
2249
|
+
* → replaces JSX
|
|
2250
|
+
*
|
|
2251
|
+
* removeJSX
|
|
2252
|
+
* → deletes JSX
|
|
2253
|
+
*
|
|
2254
|
+
* wrapJSX
|
|
2255
|
+
* → nests existing JSX inside another JSX
|
|
2256
|
+
*
|
|
2257
|
+
* ------------------------------------------------------------
|
|
2258
|
+
* WHY THIS FUNCTION EXISTS
|
|
2259
|
+
* ------------------------------------------------------------
|
|
2260
|
+
*
|
|
2261
|
+
* Users frequently give commands like:
|
|
2262
|
+
*
|
|
2263
|
+
* - "wrap button in div"
|
|
2264
|
+
* - "put card inside container"
|
|
2265
|
+
* - "wrap navbar in header"
|
|
2266
|
+
* - "add layout wrapper"
|
|
2267
|
+
* - "center this section"
|
|
2268
|
+
*
|
|
2269
|
+
* These commands usually mean:
|
|
2270
|
+
*
|
|
2271
|
+
* "preserve existing JSX but add parent wrapper"
|
|
2272
|
+
*
|
|
2273
|
+
* ------------------------------------------------------------
|
|
2274
|
+
* WHY AST IS IMPORTANT
|
|
2275
|
+
* ------------------------------------------------------------
|
|
2276
|
+
*
|
|
2277
|
+
* JSX wrapping using string replacement is fragile.
|
|
2278
|
+
*
|
|
2279
|
+
* Problems:
|
|
2280
|
+
* - nested JSX
|
|
2281
|
+
* - malformed closing tags
|
|
2282
|
+
* - multiline formatting
|
|
2283
|
+
* - fragments
|
|
2284
|
+
* - conditional rendering
|
|
2285
|
+
*
|
|
2286
|
+
* AST guarantees:
|
|
2287
|
+
* - valid JSX structure
|
|
2288
|
+
* - syntax-safe wrapping
|
|
2289
|
+
* - deterministic nesting
|
|
2290
|
+
*
|
|
2291
|
+
* ------------------------------------------------------------
|
|
2292
|
+
* IMPORTANT ARCHITECTURAL CONSTRAINT
|
|
2293
|
+
* ------------------------------------------------------------
|
|
2294
|
+
*
|
|
2295
|
+
* This implementation assumes:
|
|
2296
|
+
*
|
|
2297
|
+
* - React components use function declarations
|
|
2298
|
+
*
|
|
2299
|
+
* Example:
|
|
2300
|
+
*
|
|
2301
|
+
* function Header() {}
|
|
2302
|
+
*
|
|
2303
|
+
* NOT:
|
|
2304
|
+
*
|
|
2305
|
+
* const Header = () => {}
|
|
2306
|
+
*
|
|
2307
|
+
* ------------------------------------------------------------
|
|
2308
|
+
* WRAPPING STRATEGY
|
|
2309
|
+
* ------------------------------------------------------------
|
|
2310
|
+
*
|
|
2311
|
+
* This function:
|
|
2312
|
+
*
|
|
2313
|
+
* - finds first matching JSX element
|
|
2314
|
+
* - inserts that JSX inside wrapper element
|
|
2315
|
+
*
|
|
2316
|
+
* Example:
|
|
2317
|
+
*
|
|
2318
|
+
* targetElement = "button"
|
|
2319
|
+
*
|
|
2320
|
+
* wrapperJSX =
|
|
2321
|
+
*
|
|
2322
|
+
* <div className="container"></div>
|
|
2323
|
+
*
|
|
2324
|
+
* RESULT:
|
|
2325
|
+
*
|
|
2326
|
+
* <div className="container">
|
|
2327
|
+
* <button />
|
|
2328
|
+
* </div>
|
|
2329
|
+
*
|
|
2330
|
+
*
|
|
2331
|
+
* Useful commands:
|
|
2332
|
+
* - wrap button in div: wrapJSX(button)
|
|
2333
|
+
* - put navbar inside header: wrapJSX(Navbar)
|
|
2334
|
+
* - wrap hero section in container: wrapJSX(section)
|
|
2335
|
+
* - add layout wrapper: wrapJSX(main)
|
|
2336
|
+
* - center this card: wrapJSX(Card)
|
|
2337
|
+
*
|
|
2338
|
+
* wrapJSX
|
|
2339
|
+
↓
|
|
2340
|
+
ensureImport
|
|
2341
|
+
↓
|
|
2342
|
+
optimizeImports
|
|
2343
|
+
*
|
|
2344
|
+
* ------------------------------------------------------------
|
|
2345
|
+
* IMPORTANT LIMITATION
|
|
2346
|
+
* ------------------------------------------------------------
|
|
2347
|
+
*
|
|
2348
|
+
* Current implementation:
|
|
2349
|
+
*
|
|
2350
|
+
* - wraps first matching node only
|
|
2351
|
+
*
|
|
2352
|
+
* Future improvements:
|
|
2353
|
+
* - wrap multiple nodes
|
|
2354
|
+
* - wrap by className
|
|
2355
|
+
* - wrap sibling groups
|
|
2356
|
+
* - wrap conditional JSX
|
|
2357
|
+
*
|
|
2358
|
+
* ------------------------------------------------------------
|
|
2359
|
+
* WRAPPER FORMAT
|
|
2360
|
+
* ------------------------------------------------------------
|
|
2361
|
+
*
|
|
2362
|
+
* wrapperJSX should be:
|
|
2363
|
+
*
|
|
2364
|
+
* valid empty JSX container
|
|
2365
|
+
*
|
|
2366
|
+
* Example:
|
|
2367
|
+
*
|
|
2368
|
+
* <div className="wrapper"></div>
|
|
2369
|
+
*
|
|
2370
|
+
* NOT:
|
|
2371
|
+
*
|
|
2372
|
+
* <div>
|
|
2373
|
+
* Something
|
|
2374
|
+
* </div>
|
|
2375
|
+
*
|
|
2376
|
+
* because children will be injected automatically.
|
|
2377
|
+
*
|
|
2378
|
+
* ------------------------------------------------------------
|
|
2379
|
+
* PARAMS
|
|
2380
|
+
* ------------------------------------------------------------
|
|
2381
|
+
*
|
|
2382
|
+
* @param {Object} params
|
|
2383
|
+
*
|
|
2384
|
+
* @param {string} params.code
|
|
2385
|
+
* Entire source code.
|
|
2386
|
+
*
|
|
2387
|
+
* @param {string} params.componentName
|
|
2388
|
+
* Component containing JSX.
|
|
2389
|
+
*
|
|
2390
|
+
* @param {string} params.targetElement
|
|
2391
|
+
* JSX element to wrap.
|
|
2392
|
+
*
|
|
2393
|
+
* Example:
|
|
2394
|
+
* "button"
|
|
2395
|
+
*
|
|
2396
|
+
* @param {string} params.wrapperJSX
|
|
2397
|
+
* Parent wrapper JSX.
|
|
2398
|
+
*
|
|
2399
|
+
* Example:
|
|
2400
|
+
* '<div className="container"></div>'
|
|
2401
|
+
*
|
|
2402
|
+
* ------------------------------------------------------------
|
|
2403
|
+
* RETURNS
|
|
2404
|
+
* ------------------------------------------------------------
|
|
2405
|
+
*
|
|
2406
|
+
* @returns {{ updatedCode: string }}
|
|
2407
|
+
* Updated source code.
|
|
2408
|
+
*
|
|
2409
|
+
*/
|
|
2410
|
+
function wrapJSX({ code, componentName, targetElement, wrapperJSX }, context) {
|
|
2411
|
+
// ----------------------------------------------------------
|
|
2412
|
+
// STEP 1:
|
|
2413
|
+
// Parse source code into AST
|
|
2414
|
+
// ----------------------------------------------------------
|
|
2415
|
+
const ast = parse(code, {
|
|
2416
|
+
sourceType: "module",
|
|
2417
|
+
plugins: ["jsx", "typescript"],
|
|
2418
|
+
});
|
|
2419
|
+
// ----------------------------------------------------------
|
|
2420
|
+
// STEP 2:
|
|
2421
|
+
// Parse wrapper JSX into AST node
|
|
2422
|
+
//
|
|
2423
|
+
// Example:
|
|
2424
|
+
//
|
|
2425
|
+
// <div className="wrapper"></div>
|
|
2426
|
+
// ----------------------------------------------------------
|
|
2427
|
+
const wrapperNode = parseExpression(wrapperJSX, {
|
|
2428
|
+
plugins: ["jsx"],
|
|
2429
|
+
});
|
|
2430
|
+
// ----------------------------------------------------------
|
|
2431
|
+
// STEP 3:
|
|
2432
|
+
// Ensure wrapper is JSX element
|
|
2433
|
+
// ----------------------------------------------------------
|
|
2434
|
+
if (!t.isJSXElement(wrapperNode)) {
|
|
2435
|
+
throw new LoomaError(ERROR_CODES.INVALID_JSX, "wrapperJSX must be a valid JSX element");
|
|
2436
|
+
}
|
|
2437
|
+
// ----------------------------------------------------------
|
|
2438
|
+
// STEP 4:
|
|
2439
|
+
// Track whether wrapping already happened
|
|
2440
|
+
// ----------------------------------------------------------
|
|
2441
|
+
let wrapped = false;
|
|
2442
|
+
// ----------------------------------------------------------
|
|
2443
|
+
// STEP 5:
|
|
2444
|
+
// Traverse AST searching target component
|
|
2445
|
+
// ----------------------------------------------------------
|
|
2446
|
+
traverse.default(ast, {
|
|
2447
|
+
FunctionDeclaration(path) {
|
|
2448
|
+
// ------------------------------------------------------
|
|
2449
|
+
// Ignore unrelated components
|
|
2450
|
+
// ------------------------------------------------------
|
|
2451
|
+
if (path.node.id?.name !== componentName) {
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
// ------------------------------------------------------
|
|
2455
|
+
// Traverse JSX inside component
|
|
2456
|
+
// ------------------------------------------------------
|
|
2457
|
+
path.traverse({
|
|
2458
|
+
JSXElement(jsxPath) {
|
|
2459
|
+
// --------------------------------------------------
|
|
2460
|
+
// Stop if wrapping already completed
|
|
2461
|
+
// --------------------------------------------------
|
|
2462
|
+
if (wrapped) {
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
// --------------------------------------------------
|
|
2466
|
+
// Get opening JSX tag
|
|
2467
|
+
// --------------------------------------------------
|
|
2468
|
+
const openingElement = jsxPath.node.openingElement;
|
|
2469
|
+
// --------------------------------------------------
|
|
2470
|
+
// Ignore unsupported tag types
|
|
2471
|
+
// --------------------------------------------------
|
|
2472
|
+
if (!t.isJSXIdentifier(openingElement.name)) {
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
// --------------------------------------------------
|
|
2476
|
+
// Ignore unrelated JSX elements
|
|
2477
|
+
// --------------------------------------------------
|
|
2478
|
+
if (!helpers.matchesSelector({
|
|
2479
|
+
openingElement,
|
|
2480
|
+
selector: targetElement,
|
|
2481
|
+
})) {
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
// --------------------------------------------------
|
|
2485
|
+
// Clone wrapper node
|
|
2486
|
+
//
|
|
2487
|
+
// WHY?
|
|
2488
|
+
//
|
|
2489
|
+
// Prevent AST reference mutation issues.
|
|
2490
|
+
// --------------------------------------------------
|
|
2491
|
+
const wrapperClone = t.cloneNode(wrapperNode, true);
|
|
2492
|
+
// --------------------------------------------------
|
|
2493
|
+
// Inject existing JSX inside wrapper
|
|
2494
|
+
//
|
|
2495
|
+
// BEFORE:
|
|
2496
|
+
//
|
|
2497
|
+
// <div className="wrapper"></div>
|
|
2498
|
+
//
|
|
2499
|
+
// AFTER:
|
|
2500
|
+
//
|
|
2501
|
+
// <div className="wrapper">
|
|
2502
|
+
// <button />
|
|
2503
|
+
// </div>
|
|
2504
|
+
// --------------------------------------------------
|
|
2505
|
+
wrapperClone.children = [
|
|
2506
|
+
t.jsxText("\n"),
|
|
2507
|
+
jsxPath.node,
|
|
2508
|
+
t.jsxText("\n"),
|
|
2509
|
+
];
|
|
2510
|
+
// --------------------------------------------------
|
|
2511
|
+
// Replace original JSX with wrapped JSX
|
|
2512
|
+
// --------------------------------------------------
|
|
2513
|
+
jsxPath.replaceWith(wrapperClone);
|
|
2514
|
+
// --------------------------------------------------
|
|
2515
|
+
// Mark wrapping completed
|
|
2516
|
+
// --------------------------------------------------
|
|
2517
|
+
wrapped = true;
|
|
2518
|
+
// --------------------------------------------------
|
|
2519
|
+
// Stop traversal for safety/performance
|
|
2520
|
+
// --------------------------------------------------
|
|
2521
|
+
jsxPath.stop();
|
|
2522
|
+
},
|
|
2523
|
+
});
|
|
2524
|
+
},
|
|
2525
|
+
});
|
|
2526
|
+
// ----------------------------------------------------------
|
|
2527
|
+
// STEP 6:
|
|
2528
|
+
// Generate updated source code from AST
|
|
2529
|
+
// ----------------------------------------------------------
|
|
2530
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
2531
|
+
}
|
|
2532
|
+
/**
|
|
2533
|
+
* DescriptionForPrompt: Moves a JSX element to a different location in the JSX tree.
|
|
2534
|
+
*
|
|
2535
|
+
* ------------------------------------------------------------
|
|
2536
|
+
* WHAT THIS FUNCTION DOES
|
|
2537
|
+
* ------------------------------------------------------------
|
|
2538
|
+
*
|
|
2539
|
+
* This function:
|
|
2540
|
+
*
|
|
2541
|
+
* 1. Finds a JSX element
|
|
2542
|
+
* 2. Removes it from current location
|
|
2543
|
+
* 3. Inserts it inside another JSX element
|
|
2544
|
+
*
|
|
2545
|
+
* Example:
|
|
2546
|
+
*
|
|
2547
|
+
* BEFORE:
|
|
2548
|
+
*
|
|
2549
|
+
* <header>
|
|
2550
|
+
* <button>Login</button>
|
|
2551
|
+
* </header>
|
|
2552
|
+
*
|
|
2553
|
+
* <footer></footer>
|
|
2554
|
+
*
|
|
2555
|
+
* AFTER:
|
|
2556
|
+
*
|
|
2557
|
+
* <header></header>
|
|
2558
|
+
*
|
|
2559
|
+
* <footer>
|
|
2560
|
+
* <button>Login</button>
|
|
2561
|
+
* </footer>
|
|
2562
|
+
*
|
|
2563
|
+
* ------------------------------------------------------------
|
|
2564
|
+
* IMPORTANT DIFFERENCE
|
|
2565
|
+
* ------------------------------------------------------------
|
|
2566
|
+
*
|
|
2567
|
+
* insertJSX
|
|
2568
|
+
* → creates new JSX
|
|
2569
|
+
*
|
|
2570
|
+
* replaceJSX
|
|
2571
|
+
* → swaps JSX
|
|
2572
|
+
*
|
|
2573
|
+
* removeJSX
|
|
2574
|
+
* → deletes JSX
|
|
2575
|
+
*
|
|
2576
|
+
* wrapJSX
|
|
2577
|
+
* → nests JSX
|
|
2578
|
+
*
|
|
2579
|
+
* moveJSX
|
|
2580
|
+
* → relocates existing JSX
|
|
2581
|
+
*
|
|
2582
|
+
* ------------------------------------------------------------
|
|
2583
|
+
* WHY THIS FUNCTION EXISTS
|
|
2584
|
+
* ------------------------------------------------------------
|
|
2585
|
+
*
|
|
2586
|
+
* Users frequently give commands like:
|
|
2587
|
+
*
|
|
2588
|
+
* - "move login button to footer"
|
|
2589
|
+
* - "put navbar inside sidebar"
|
|
2590
|
+
* - "move search bar to header"
|
|
2591
|
+
* - "shift button below form"
|
|
2592
|
+
* - "move logo to left section"
|
|
2593
|
+
*
|
|
2594
|
+
* These commands usually mean:
|
|
2595
|
+
*
|
|
2596
|
+
* "preserve existing JSX but change location"
|
|
2597
|
+
*
|
|
2598
|
+
* ------------------------------------------------------------
|
|
2599
|
+
* WHY AST IS IMPORTANT
|
|
2600
|
+
* ------------------------------------------------------------
|
|
2601
|
+
*
|
|
2602
|
+
* JSX movement using string replacement is fragile.
|
|
2603
|
+
*
|
|
2604
|
+
* Problems:
|
|
2605
|
+
* - nested JSX
|
|
2606
|
+
* - invalid nesting
|
|
2607
|
+
* - multiline formatting
|
|
2608
|
+
* - duplicate elements
|
|
2609
|
+
* - broken parent structure
|
|
2610
|
+
*
|
|
2611
|
+
* AST guarantees:
|
|
2612
|
+
* - syntax-safe movement
|
|
2613
|
+
* - deterministic relocation
|
|
2614
|
+
* - structure-aware manipulation
|
|
2615
|
+
*
|
|
2616
|
+
* ------------------------------------------------------------
|
|
2617
|
+
* IMPORTANT ARCHITECTURAL CONSTRAINT
|
|
2618
|
+
* ------------------------------------------------------------
|
|
2619
|
+
*
|
|
2620
|
+
* This implementation assumes:
|
|
2621
|
+
*
|
|
2622
|
+
* - React components use function declarations
|
|
2623
|
+
*
|
|
2624
|
+
* Example:
|
|
2625
|
+
*
|
|
2626
|
+
* function Header() {}
|
|
2627
|
+
*
|
|
2628
|
+
* NOT:
|
|
2629
|
+
*
|
|
2630
|
+
* const Header = () => {}
|
|
2631
|
+
*
|
|
2632
|
+
* ------------------------------------------------------------
|
|
2633
|
+
* MOVEMENT STRATEGY
|
|
2634
|
+
* ------------------------------------------------------------
|
|
2635
|
+
*
|
|
2636
|
+
* This function:
|
|
2637
|
+
*
|
|
2638
|
+
* - removes first matching JSX node
|
|
2639
|
+
* - inserts it inside destination element
|
|
2640
|
+
*
|
|
2641
|
+
* Example:
|
|
2642
|
+
*
|
|
2643
|
+
* sourceElement = "button"
|
|
2644
|
+
* destinationElement = "footer"
|
|
2645
|
+
*
|
|
2646
|
+
* RESULT:
|
|
2647
|
+
*
|
|
2648
|
+
* <footer>
|
|
2649
|
+
* <button />
|
|
2650
|
+
* </footer>
|
|
2651
|
+
*
|
|
2652
|
+
* Useful commands:
|
|
2653
|
+
* - move login button to footer: moveJSX(button → footer)
|
|
2654
|
+
* - move search bar into header: moveJSX(input → header)
|
|
2655
|
+
* - put navbar inside sidebar: moveJSX(Navbar → Sidebar)
|
|
2656
|
+
* - move logo to left section: moveJSX(Logo → div)
|
|
2657
|
+
* - shift button below form: moveJSX(button → form)
|
|
2658
|
+
*
|
|
2659
|
+
* moveJSX
|
|
2660
|
+
↓
|
|
2661
|
+
optimizeImports
|
|
2662
|
+
* ------------------------------------------------------------
|
|
2663
|
+
* IMPORTANT LIMITATION
|
|
2664
|
+
* ------------------------------------------------------------
|
|
2665
|
+
*
|
|
2666
|
+
* Current implementation:
|
|
2667
|
+
*
|
|
2668
|
+
* - moves first matching node only
|
|
2669
|
+
*
|
|
2670
|
+
* Future improvements:
|
|
2671
|
+
* - move nth node
|
|
2672
|
+
* - move before/after sibling
|
|
2673
|
+
* - move by className
|
|
2674
|
+
* - reorder sibling elements
|
|
2675
|
+
*
|
|
2676
|
+
* ------------------------------------------------------------
|
|
2677
|
+
* PARAMS
|
|
2678
|
+
* ------------------------------------------------------------
|
|
2679
|
+
*
|
|
2680
|
+
* @param {Object} params
|
|
2681
|
+
*
|
|
2682
|
+
* @param {string} params.code
|
|
2683
|
+
* Entire source code.
|
|
2684
|
+
*
|
|
2685
|
+
* @param {string} params.componentName
|
|
2686
|
+
* Component containing JSX.
|
|
2687
|
+
*
|
|
2688
|
+
* @param {string} params.sourceElement
|
|
2689
|
+
* JSX element to move.
|
|
2690
|
+
*
|
|
2691
|
+
* Example:
|
|
2692
|
+
* "button"
|
|
2693
|
+
*
|
|
2694
|
+
* @param {string} params.destinationElement
|
|
2695
|
+
* JSX destination parent.
|
|
2696
|
+
*
|
|
2697
|
+
* Example:
|
|
2698
|
+
* "footer"
|
|
2699
|
+
*
|
|
2700
|
+
* ------------------------------------------------------------
|
|
2701
|
+
* RETURNS
|
|
2702
|
+
* ------------------------------------------------------------
|
|
2703
|
+
*
|
|
2704
|
+
* @returns {{ updatedCode: string }}
|
|
2705
|
+
* Updated source code.
|
|
2706
|
+
*
|
|
2707
|
+
*/
|
|
2708
|
+
function moveJSX({ code, componentName, sourceElement, destinationElement }, context) {
|
|
2709
|
+
// ----------------------------------------------------------
|
|
2710
|
+
// STEP 1:
|
|
2711
|
+
// Parse source code into AST
|
|
2712
|
+
// ----------------------------------------------------------
|
|
2713
|
+
const ast = parse(code, {
|
|
2714
|
+
sourceType: "module",
|
|
2715
|
+
plugins: ["jsx", "typescript"],
|
|
2716
|
+
});
|
|
2717
|
+
// ----------------------------------------------------------
|
|
2718
|
+
// STEP 2:
|
|
2719
|
+
// Store JSX node that will be moved
|
|
2720
|
+
// ----------------------------------------------------------
|
|
2721
|
+
let nodeToMove = null;
|
|
2722
|
+
// ----------------------------------------------------------
|
|
2723
|
+
// STEP 3:
|
|
2724
|
+
// Track movement completion
|
|
2725
|
+
// ----------------------------------------------------------
|
|
2726
|
+
let moved = false;
|
|
2727
|
+
// ----------------------------------------------------------
|
|
2728
|
+
// STEP 4:
|
|
2729
|
+
// Traverse AST searching target component
|
|
2730
|
+
// ----------------------------------------------------------
|
|
2731
|
+
traverse.default(ast, {
|
|
2732
|
+
FunctionDeclaration(path) {
|
|
2733
|
+
// ------------------------------------------------------
|
|
2734
|
+
// Ignore unrelated components
|
|
2735
|
+
// ------------------------------------------------------
|
|
2736
|
+
if (path.node.id?.name !== componentName) {
|
|
2737
|
+
return;
|
|
2738
|
+
}
|
|
2739
|
+
// ======================================================
|
|
2740
|
+
// PHASE 1:
|
|
2741
|
+
// FIND + REMOVE SOURCE ELEMENT
|
|
2742
|
+
// ======================================================
|
|
2743
|
+
path.traverse({
|
|
2744
|
+
JSXElement(jsxPath) {
|
|
2745
|
+
// --------------------------------------------------
|
|
2746
|
+
// Stop if source already found
|
|
2747
|
+
// --------------------------------------------------
|
|
2748
|
+
if (nodeToMove) {
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
// --------------------------------------------------
|
|
2752
|
+
// Get opening JSX element
|
|
2753
|
+
// --------------------------------------------------
|
|
2754
|
+
const openingElement = jsxPath.node.openingElement;
|
|
2755
|
+
// --------------------------------------------------
|
|
2756
|
+
// Ignore unsupported tag types
|
|
2757
|
+
// --------------------------------------------------
|
|
2758
|
+
if (!t.isJSXIdentifier(openingElement.name)) {
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
// --------------------------------------------------
|
|
2762
|
+
// Ignore unrelated JSX elements
|
|
2763
|
+
// --------------------------------------------------
|
|
2764
|
+
if (openingElement.name.name !== sourceElement) {
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
// --------------------------------------------------
|
|
2768
|
+
// Clone node before removal
|
|
2769
|
+
//
|
|
2770
|
+
// WHY?
|
|
2771
|
+
//
|
|
2772
|
+
// Removing node destroys original reference.
|
|
2773
|
+
// --------------------------------------------------
|
|
2774
|
+
nodeToMove = t.cloneNode(jsxPath.node, true);
|
|
2775
|
+
// --------------------------------------------------
|
|
2776
|
+
// Remove original JSX node
|
|
2777
|
+
// --------------------------------------------------
|
|
2778
|
+
jsxPath.remove();
|
|
2779
|
+
},
|
|
2780
|
+
});
|
|
2781
|
+
// ======================================================
|
|
2782
|
+
// PHASE 2:
|
|
2783
|
+
// INSERT INTO DESTINATION
|
|
2784
|
+
// ======================================================
|
|
2785
|
+
if (!nodeToMove) {
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2788
|
+
path.traverse({
|
|
2789
|
+
JSXElement(jsxPath) {
|
|
2790
|
+
// --------------------------------------------------
|
|
2791
|
+
// Stop if movement already completed
|
|
2792
|
+
// --------------------------------------------------
|
|
2793
|
+
if (moved) {
|
|
2794
|
+
return;
|
|
2795
|
+
}
|
|
2796
|
+
// --------------------------------------------------
|
|
2797
|
+
// Get opening JSX element
|
|
2798
|
+
// --------------------------------------------------
|
|
2799
|
+
const openingElement = jsxPath.node.openingElement;
|
|
2800
|
+
// --------------------------------------------------
|
|
2801
|
+
// Ignore unsupported tag types
|
|
2802
|
+
// --------------------------------------------------
|
|
2803
|
+
if (!t.isJSXIdentifier(openingElement.name)) {
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
// --------------------------------------------------
|
|
2807
|
+
// Ignore unrelated destination elements
|
|
2808
|
+
// --------------------------------------------------
|
|
2809
|
+
if (openingElement.name.name !== destinationElement) {
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
// --------------------------------------------------
|
|
2813
|
+
// Insert moved JSX inside destination
|
|
2814
|
+
//
|
|
2815
|
+
// BEFORE:
|
|
2816
|
+
//
|
|
2817
|
+
// <footer></footer>
|
|
2818
|
+
//
|
|
2819
|
+
// AFTER:
|
|
2820
|
+
//
|
|
2821
|
+
// <footer>
|
|
2822
|
+
// <button />
|
|
2823
|
+
// </footer>
|
|
2824
|
+
// --------------------------------------------------
|
|
2825
|
+
if (nodeToMove) {
|
|
2826
|
+
jsxPath.node.children.push(t.jsxText("\n"), nodeToMove);
|
|
2827
|
+
}
|
|
2828
|
+
// --------------------------------------------------
|
|
2829
|
+
// Mark movement completed
|
|
2830
|
+
// --------------------------------------------------
|
|
2831
|
+
moved = true;
|
|
2832
|
+
// --------------------------------------------------
|
|
2833
|
+
// Stop traversal for safety/performance
|
|
2834
|
+
// --------------------------------------------------
|
|
2835
|
+
jsxPath.stop();
|
|
2836
|
+
},
|
|
2837
|
+
});
|
|
2838
|
+
},
|
|
2839
|
+
});
|
|
2840
|
+
// ----------------------------------------------------------
|
|
2841
|
+
// STEP 5:
|
|
2842
|
+
// Generate updated source code from AST
|
|
2843
|
+
// ----------------------------------------------------------
|
|
2844
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* DescriptionForPrompt: Inserts new CSS styles into a component style file.
|
|
2848
|
+
*
|
|
2849
|
+
* ------------------------------------------------------------
|
|
2850
|
+
* WHAT THIS FUNCTION DOES
|
|
2851
|
+
* ------------------------------------------------------------
|
|
2852
|
+
*
|
|
2853
|
+
* This function appends new css styles into:
|
|
2854
|
+
*
|
|
2855
|
+
* Component.css
|
|
2856
|
+
*
|
|
2857
|
+
* Example:
|
|
2858
|
+
*
|
|
2859
|
+
* BEFORE:
|
|
2860
|
+
*
|
|
2861
|
+
* .header {
|
|
2862
|
+
* padding: 16px;
|
|
2863
|
+
* }
|
|
2864
|
+
*
|
|
2865
|
+
* AFTER:
|
|
2866
|
+
*
|
|
2867
|
+
* .header {
|
|
2868
|
+
* padding: 16px;
|
|
2869
|
+
* }
|
|
2870
|
+
*
|
|
2871
|
+
* .header-title {
|
|
2872
|
+
* color: red;
|
|
2873
|
+
* }
|
|
2874
|
+
*
|
|
2875
|
+
* ------------------------------------------------------------
|
|
2876
|
+
* WHY THIS FUNCTION EXISTS
|
|
2877
|
+
* ------------------------------------------------------------
|
|
2878
|
+
*
|
|
2879
|
+
* Looma frequently needs to:
|
|
2880
|
+
*
|
|
2881
|
+
* - add new styles
|
|
2882
|
+
* - add responsive rules
|
|
2883
|
+
* - add hover effects
|
|
2884
|
+
* - add utility classes
|
|
2885
|
+
* - add animations
|
|
2886
|
+
*
|
|
2887
|
+
* Instead of replacing entire css file,
|
|
2888
|
+
* this function safely appends styles.
|
|
2889
|
+
*
|
|
2890
|
+
* Useful in commands like:
|
|
2891
|
+
|
|
2892
|
+
make header red
|
|
2893
|
+
add hover effect
|
|
2894
|
+
add responsive styles
|
|
2895
|
+
add animation
|
|
2896
|
+
style this button
|
|
2897
|
+
increase card padding
|
|
2898
|
+
make navbar sticky
|
|
2899
|
+
|
|
2900
|
+
Usually called after:
|
|
2901
|
+
|
|
2902
|
+
ensureStyleFile()
|
|
2903
|
+
|
|
2904
|
+
Usually used before:
|
|
2905
|
+
|
|
2906
|
+
optimizeStyles()
|
|
2907
|
+
formatStyles()
|
|
2908
|
+
*
|
|
2909
|
+
* ------------------------------------------------------------
|
|
2910
|
+
* IMPORTANT DESIGN DECISION
|
|
2911
|
+
* ------------------------------------------------------------
|
|
2912
|
+
*
|
|
2913
|
+
* This function:
|
|
2914
|
+
*
|
|
2915
|
+
* ONLY inserts styles.
|
|
2916
|
+
*
|
|
2917
|
+
* It does NOT:
|
|
2918
|
+
*
|
|
2919
|
+
* - update existing styles
|
|
2920
|
+
* - remove styles
|
|
2921
|
+
* - optimize styles
|
|
2922
|
+
* - merge selectors
|
|
2923
|
+
*
|
|
2924
|
+
* Those should be handled by separate tasks.
|
|
2925
|
+
*
|
|
2926
|
+
* ------------------------------------------------------------
|
|
2927
|
+
* DEPENDENCY
|
|
2928
|
+
* ------------------------------------------------------------
|
|
2929
|
+
*
|
|
2930
|
+
* This function assumes:
|
|
2931
|
+
*
|
|
2932
|
+
* ensureStyleFile()
|
|
2933
|
+
*
|
|
2934
|
+
* has already been executed.
|
|
2935
|
+
*
|
|
2936
|
+
* Meaning:
|
|
2937
|
+
*
|
|
2938
|
+
* css file already exists.
|
|
2939
|
+
*
|
|
2940
|
+
* ------------------------------------------------------------
|
|
2941
|
+
* PARAMS
|
|
2942
|
+
* ------------------------------------------------------------
|
|
2943
|
+
*
|
|
2944
|
+
* @param {Object} params
|
|
2945
|
+
*
|
|
2946
|
+
* @param {string} params.cssPath
|
|
2947
|
+
* Path of css file.
|
|
2948
|
+
*
|
|
2949
|
+
* Example:
|
|
2950
|
+
*
|
|
2951
|
+
* "./src/components/Header/Header.css"
|
|
2952
|
+
*
|
|
2953
|
+
* @param {string} params.styles
|
|
2954
|
+
* CSS styles to insert.
|
|
2955
|
+
*
|
|
2956
|
+
* Example:
|
|
2957
|
+
*
|
|
2958
|
+
* `
|
|
2959
|
+
* .header-title {
|
|
2960
|
+
* color: red;
|
|
2961
|
+
* }
|
|
2962
|
+
* `
|
|
2963
|
+
*
|
|
2964
|
+
* @param {boolean} [params.addNewLine=true]
|
|
2965
|
+
* Whether to insert spacing before styles.
|
|
2966
|
+
*
|
|
2967
|
+
* ------------------------------------------------------------
|
|
2968
|
+
* RETURNS
|
|
2969
|
+
* ------------------------------------------------------------
|
|
2970
|
+
*
|
|
2971
|
+
* @returns {Object}
|
|
2972
|
+
*
|
|
2973
|
+
* {
|
|
2974
|
+
* inserted: boolean,
|
|
2975
|
+
* cssPath: string
|
|
2976
|
+
* }
|
|
2977
|
+
*
|
|
2978
|
+
*/
|
|
2979
|
+
// function insertStyles({ cssPath, styles, addNewLine = true }) {
|
|
2980
|
+
// // ----------------------------------------------------------
|
|
2981
|
+
// // STEP 1:
|
|
2982
|
+
// // Validate css file existence
|
|
2983
|
+
// // ----------------------------------------------------------
|
|
2984
|
+
// if (!fs.existsSync(cssPath)) {
|
|
2985
|
+
// throw new Error(`CSS file does not exist: ${cssPath}`);
|
|
2986
|
+
// }
|
|
2987
|
+
// // ----------------------------------------------------------
|
|
2988
|
+
// // STEP 2:
|
|
2989
|
+
// // Read existing css content
|
|
2990
|
+
// // ----------------------------------------------------------
|
|
2991
|
+
// const existingCss = fs.readFileSync(cssPath, "utf8");
|
|
2992
|
+
// // ----------------------------------------------------------
|
|
2993
|
+
// // STEP 3:
|
|
2994
|
+
// // Prepare final styles content
|
|
2995
|
+
// //
|
|
2996
|
+
// // Adds spacing before inserted styles
|
|
2997
|
+
// // for readability.
|
|
2998
|
+
// // ----------------------------------------------------------
|
|
2999
|
+
// const finalStyles = addNewLine ? `\n\n${styles}` : styles;
|
|
3000
|
+
// // ----------------------------------------------------------
|
|
3001
|
+
// // STEP 4:
|
|
3002
|
+
// // Append styles to existing css
|
|
3003
|
+
// // ----------------------------------------------------------
|
|
3004
|
+
// const updatedCss = existingCss + finalStyles;
|
|
3005
|
+
// // ----------------------------------------------------------
|
|
3006
|
+
// // STEP 5:
|
|
3007
|
+
// // Write updated css back to file
|
|
3008
|
+
// // ----------------------------------------------------------
|
|
3009
|
+
// fs.writeFileSync(cssPath, updatedCss, "utf8");
|
|
3010
|
+
// // ----------------------------------------------------------
|
|
3011
|
+
// // STEP 6:
|
|
3012
|
+
// // Return operation result
|
|
3013
|
+
// // ----------------------------------------------------------
|
|
3014
|
+
// return {
|
|
3015
|
+
// success: true,
|
|
3016
|
+
// cssPath,
|
|
3017
|
+
// };
|
|
3018
|
+
// }
|
|
3019
|
+
/**
|
|
3020
|
+
* Removes imports from a source/module.
|
|
3021
|
+
*
|
|
3022
|
+
* ------------------------------------------------------------
|
|
3023
|
+
* WHAT THIS FUNCTION DOES
|
|
3024
|
+
* ------------------------------------------------------------
|
|
3025
|
+
*
|
|
3026
|
+
* This function removes:
|
|
3027
|
+
*
|
|
3028
|
+
* - default imports
|
|
3029
|
+
* - named imports
|
|
3030
|
+
* - namespace imports
|
|
3031
|
+
*
|
|
3032
|
+
* from a specific source.
|
|
3033
|
+
*
|
|
3034
|
+
* ------------------------------------------------------------
|
|
3035
|
+
* IMPORTANT BEHAVIOR
|
|
3036
|
+
* ------------------------------------------------------------
|
|
3037
|
+
*
|
|
3038
|
+
* This function intelligently handles:
|
|
3039
|
+
*
|
|
3040
|
+
* 1) removing only a specific named import
|
|
3041
|
+
*
|
|
3042
|
+
* BEFORE:
|
|
3043
|
+
* import React, { useState, useEffect } from "react";
|
|
3044
|
+
*
|
|
3045
|
+
* AFTER:
|
|
3046
|
+
* import React, { useEffect } from "react";
|
|
3047
|
+
*
|
|
3048
|
+
* ------------------------------------------------------------
|
|
3049
|
+
*
|
|
3050
|
+
* 2) removing entire import declaration if empty
|
|
3051
|
+
*
|
|
3052
|
+
* BEFORE:
|
|
3053
|
+
* import { useState } from "react";
|
|
3054
|
+
*
|
|
3055
|
+
* AFTER:
|
|
3056
|
+
* <removed entirely>
|
|
3057
|
+
*
|
|
3058
|
+
* ------------------------------------------------------------
|
|
3059
|
+
*
|
|
3060
|
+
* 3) removing default import
|
|
3061
|
+
*
|
|
3062
|
+
* BEFORE:
|
|
3063
|
+
* import React from "react";
|
|
3064
|
+
*
|
|
3065
|
+
* AFTER:
|
|
3066
|
+
* <removed entirely>
|
|
3067
|
+
*
|
|
3068
|
+
* ------------------------------------------------------------
|
|
3069
|
+
*
|
|
3070
|
+
* 4) removing namespace import
|
|
3071
|
+
*
|
|
3072
|
+
* BEFORE:
|
|
3073
|
+
* import * as React from "react";
|
|
3074
|
+
*
|
|
3075
|
+
* AFTER:
|
|
3076
|
+
* <removed entirely>
|
|
3077
|
+
*
|
|
3078
|
+
* ------------------------------------------------------------
|
|
3079
|
+
* WHY AST IS IMPORTANT
|
|
3080
|
+
* ------------------------------------------------------------
|
|
3081
|
+
*
|
|
3082
|
+
* Imports are syntax structures.
|
|
3083
|
+
*
|
|
3084
|
+
* Using string replacement is fragile because:
|
|
3085
|
+
* - formatting changes break logic
|
|
3086
|
+
* - multiline imports become difficult
|
|
3087
|
+
* - commas/braces become error-prone
|
|
3088
|
+
*
|
|
3089
|
+
* AST manipulation is:
|
|
3090
|
+
* - deterministic
|
|
3091
|
+
* - formatting-safe
|
|
3092
|
+
* - syntax-aware
|
|
3093
|
+
*
|
|
3094
|
+
* ------------------------------------------------------------
|
|
3095
|
+
* EXAMPLES
|
|
3096
|
+
* ------------------------------------------------------------
|
|
3097
|
+
*
|
|
3098
|
+
* removeImport({
|
|
3099
|
+
* code,
|
|
3100
|
+
* source: "react",
|
|
3101
|
+
* importName: "useState",
|
|
3102
|
+
* importType: "named"
|
|
3103
|
+
* });
|
|
3104
|
+
*
|
|
3105
|
+
* ------------------------------------------------------------
|
|
3106
|
+
*
|
|
3107
|
+
* removeImport({
|
|
3108
|
+
* code,
|
|
3109
|
+
* source: "react",
|
|
3110
|
+
* importType: "default"
|
|
3111
|
+
* });
|
|
3112
|
+
*
|
|
3113
|
+
* ------------------------------------------------------------
|
|
3114
|
+
* PARAMS
|
|
3115
|
+
* ------------------------------------------------------------
|
|
3116
|
+
*
|
|
3117
|
+
* @param {Object} params
|
|
3118
|
+
*
|
|
3119
|
+
* @param {string} params.code
|
|
3120
|
+
* Entire source code.
|
|
3121
|
+
*
|
|
3122
|
+
* @param {string} params.source
|
|
3123
|
+
* Import source/package.
|
|
3124
|
+
*
|
|
3125
|
+
* Example:
|
|
3126
|
+
* "react"
|
|
3127
|
+
* "./Header"
|
|
3128
|
+
*
|
|
3129
|
+
* @param {string=} params.importName
|
|
3130
|
+
* Required only for named imports.
|
|
3131
|
+
*
|
|
3132
|
+
* Example:
|
|
3133
|
+
* "useState"
|
|
3134
|
+
*
|
|
3135
|
+
* @param {"default"|"named"|"namespace"} params.importType
|
|
3136
|
+
* Type of import to remove.
|
|
3137
|
+
*
|
|
3138
|
+
* ------------------------------------------------------------
|
|
3139
|
+
* RETURNS
|
|
3140
|
+
* ------------------------------------------------------------
|
|
3141
|
+
*
|
|
3142
|
+
* @returns {{updatedCode: string}}
|
|
3143
|
+
* Updated source code.
|
|
3144
|
+
*
|
|
3145
|
+
*/
|
|
3146
|
+
function removeImport({ code, source, importName, importType }, context) {
|
|
3147
|
+
// ----------------------------------------------------------
|
|
3148
|
+
// STEP 1:
|
|
3149
|
+
// Parse source code into AST
|
|
3150
|
+
//
|
|
3151
|
+
// sourceType: "module"
|
|
3152
|
+
// enables ES module parsing
|
|
3153
|
+
// ----------------------------------------------------------
|
|
3154
|
+
const ast = parse(code, {
|
|
3155
|
+
sourceType: "module",
|
|
3156
|
+
plugins: ["jsx", "typescript"],
|
|
3157
|
+
});
|
|
3158
|
+
// ----------------------------------------------------------
|
|
3159
|
+
// STEP 2:
|
|
3160
|
+
// Traverse AST looking for import declarations
|
|
3161
|
+
// ----------------------------------------------------------
|
|
3162
|
+
traverse.default(ast, {
|
|
3163
|
+
ImportDeclaration(path) {
|
|
3164
|
+
// ------------------------------------------------------
|
|
3165
|
+
// Ignore unrelated imports
|
|
3166
|
+
//
|
|
3167
|
+
// Example:
|
|
3168
|
+
//
|
|
3169
|
+
// import React from "react"
|
|
3170
|
+
//
|
|
3171
|
+
// source.value = "react"
|
|
3172
|
+
// ------------------------------------------------------
|
|
3173
|
+
if (path.node.source.value !== source) {
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
// ------------------------------------------------------
|
|
3177
|
+
// Get all import specifiers
|
|
3178
|
+
//
|
|
3179
|
+
// Example:
|
|
3180
|
+
//
|
|
3181
|
+
// import React, { useState } from "react"
|
|
3182
|
+
//
|
|
3183
|
+
// specifiers:
|
|
3184
|
+
// - ImportDefaultSpecifier
|
|
3185
|
+
// - ImportSpecifier
|
|
3186
|
+
// ------------------------------------------------------
|
|
3187
|
+
let specifiers = path.node.specifiers;
|
|
3188
|
+
// ======================================================
|
|
3189
|
+
// REMOVE DEFAULT IMPORT
|
|
3190
|
+
// ======================================================
|
|
3191
|
+
if (importType === "default") {
|
|
3192
|
+
// ----------------------------------------------------
|
|
3193
|
+
// Remove ImportDefaultSpecifier
|
|
3194
|
+
// ----------------------------------------------------
|
|
3195
|
+
specifiers = specifiers.filter((specifier) => !t.isImportDefaultSpecifier(specifier));
|
|
3196
|
+
}
|
|
3197
|
+
// ======================================================
|
|
3198
|
+
// REMOVE NAMED IMPORT
|
|
3199
|
+
// ======================================================
|
|
3200
|
+
if (importType === "named") {
|
|
3201
|
+
// ----------------------------------------------------
|
|
3202
|
+
// Remove matching named import
|
|
3203
|
+
//
|
|
3204
|
+
// BEFORE:
|
|
3205
|
+
// import { useState, useEffect }
|
|
3206
|
+
//
|
|
3207
|
+
// AFTER:
|
|
3208
|
+
// import { useEffect }
|
|
3209
|
+
// ----------------------------------------------------
|
|
3210
|
+
specifiers = specifiers.filter((specifier) => {
|
|
3211
|
+
// keep non-named specifiers
|
|
3212
|
+
if (!t.isImportSpecifier(specifier)) {
|
|
3213
|
+
return true;
|
|
3214
|
+
}
|
|
3215
|
+
// remove matching named import
|
|
3216
|
+
return (t.isIdentifier(specifier.imported) &&
|
|
3217
|
+
specifier.imported.name !== importName);
|
|
3218
|
+
});
|
|
3219
|
+
}
|
|
3220
|
+
// ======================================================
|
|
3221
|
+
// REMOVE NAMESPACE IMPORT
|
|
3222
|
+
// ======================================================
|
|
3223
|
+
if (importType === "namespace") {
|
|
3224
|
+
// ----------------------------------------------------
|
|
3225
|
+
// Remove namespace specifier
|
|
3226
|
+
//
|
|
3227
|
+
// import * as React from "react"
|
|
3228
|
+
// ----------------------------------------------------
|
|
3229
|
+
specifiers = specifiers.filter((specifier) => !t.isImportNamespaceSpecifier(specifier));
|
|
3230
|
+
}
|
|
3231
|
+
// ------------------------------------------------------
|
|
3232
|
+
// STEP 3:
|
|
3233
|
+
// If no specifiers remain:
|
|
3234
|
+
// remove entire import declaration
|
|
3235
|
+
//
|
|
3236
|
+
// Example:
|
|
3237
|
+
//
|
|
3238
|
+
// import React from "react"
|
|
3239
|
+
//
|
|
3240
|
+
// becomes:
|
|
3241
|
+
// <removed>
|
|
3242
|
+
// ------------------------------------------------------
|
|
3243
|
+
if (specifiers.length === 0) {
|
|
3244
|
+
path.remove();
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
// ------------------------------------------------------
|
|
3248
|
+
// STEP 4:
|
|
3249
|
+
// Otherwise update remaining specifiers
|
|
3250
|
+
// ------------------------------------------------------
|
|
3251
|
+
path.node.specifiers = specifiers;
|
|
3252
|
+
},
|
|
3253
|
+
});
|
|
3254
|
+
// ----------------------------------------------------------
|
|
3255
|
+
// STEP 5:
|
|
3256
|
+
// Generate updated source code from AST
|
|
3257
|
+
// ----------------------------------------------------------
|
|
3258
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
3259
|
+
}
|
|
3260
|
+
/**
|
|
3261
|
+
* Ensures that an import exists in a file.
|
|
3262
|
+
*
|
|
3263
|
+
* ------------------------------------------------------------
|
|
3264
|
+
* WHAT THIS FUNCTION DOES
|
|
3265
|
+
* ------------------------------------------------------------
|
|
3266
|
+
*
|
|
3267
|
+
* This function guarantees that a specific import exists.
|
|
3268
|
+
*
|
|
3269
|
+
* If import already exists:
|
|
3270
|
+
* - enrich existing import declaration
|
|
3271
|
+
*
|
|
3272
|
+
* If import does not exist:
|
|
3273
|
+
* - create a new import declaration
|
|
3274
|
+
*
|
|
3275
|
+
* ------------------------------------------------------------
|
|
3276
|
+
* Useful commands
|
|
3277
|
+
* ------------------------------------------------------------
|
|
3278
|
+
* - add state: ensureImport(useState)
|
|
3279
|
+
* - add routing: ensureImport(BrowserRouter)
|
|
3280
|
+
* - add header component: ensureImport(Header)
|
|
3281
|
+
* - add animation: ensureImport(motion)
|
|
3282
|
+
* - use useEffect: ensureImport(useEffect)
|
|
3283
|
+
* - add icons: ensureImport(Menu)
|
|
3284
|
+
*
|
|
3285
|
+
* ------------------------------------------------------------
|
|
3286
|
+
* WHY "ENSURE" IS IMPORTANT
|
|
3287
|
+
* ------------------------------------------------------------
|
|
3288
|
+
*
|
|
3289
|
+
* A naive system blindly inserts imports:
|
|
3290
|
+
*
|
|
3291
|
+
* import React from "react";
|
|
3292
|
+
* import { useState } from "react";
|
|
3293
|
+
*
|
|
3294
|
+
* Over time this causes:
|
|
3295
|
+
* - duplicate imports
|
|
3296
|
+
* - fragmented imports
|
|
3297
|
+
* - unstable formatting
|
|
3298
|
+
*
|
|
3299
|
+
* ensureImport prevents this problem.
|
|
3300
|
+
*
|
|
3301
|
+
* ------------------------------------------------------------
|
|
3302
|
+
* EXAMPLE
|
|
3303
|
+
* ------------------------------------------------------------
|
|
3304
|
+
*
|
|
3305
|
+
* ensureImport({
|
|
3306
|
+
* code,
|
|
3307
|
+
* source: "react",
|
|
3308
|
+
* importName: "useState",
|
|
3309
|
+
* importType: "named"
|
|
3310
|
+
* });
|
|
3311
|
+
*
|
|
3312
|
+
* RESULT:
|
|
3313
|
+
*
|
|
3314
|
+
* import React, { useState } from "react";
|
|
3315
|
+
*
|
|
3316
|
+
* ------------------------------------------------------------
|
|
3317
|
+
* SUPPORTED IMPORT TYPES
|
|
3318
|
+
* ------------------------------------------------------------
|
|
3319
|
+
*
|
|
3320
|
+
* default:
|
|
3321
|
+
* import React from "react";
|
|
3322
|
+
*
|
|
3323
|
+
* named:
|
|
3324
|
+
* import { useState } from "react";
|
|
3325
|
+
*
|
|
3326
|
+
* namespace:
|
|
3327
|
+
* import * as React from "react";
|
|
3328
|
+
*
|
|
3329
|
+
* ------------------------------------------------------------
|
|
3330
|
+
* PARAMS
|
|
3331
|
+
* ------------------------------------------------------------
|
|
3332
|
+
*
|
|
3333
|
+
* @param {Object} params
|
|
3334
|
+
*
|
|
3335
|
+
* @param {string} params.code
|
|
3336
|
+
* Entire source code of file.
|
|
3337
|
+
*
|
|
3338
|
+
* @param {string} params.source
|
|
3339
|
+
* Import source/package.
|
|
3340
|
+
*
|
|
3341
|
+
* Example:
|
|
3342
|
+
* "react"
|
|
3343
|
+
* "./Header"
|
|
3344
|
+
*
|
|
3345
|
+
* @param {string} params.importName
|
|
3346
|
+
* Name of imported symbol.
|
|
3347
|
+
*
|
|
3348
|
+
* Example:
|
|
3349
|
+
* "useState"
|
|
3350
|
+
* "React"
|
|
3351
|
+
*
|
|
3352
|
+
* @param {"default"|"named"|"namespace"} params.importType
|
|
3353
|
+
* Type of import.
|
|
3354
|
+
*
|
|
3355
|
+
* @param {string=} params.alias
|
|
3356
|
+
* Optional alias name.
|
|
3357
|
+
*
|
|
3358
|
+
* Example:
|
|
3359
|
+
* import { useState as customState }
|
|
3360
|
+
*
|
|
3361
|
+
* ------------------------------------------------------------
|
|
3362
|
+
* RETURNS
|
|
3363
|
+
* ------------------------------------------------------------
|
|
3364
|
+
*
|
|
3365
|
+
* @returns {{updatedCode: string}}
|
|
3366
|
+
* Updated source code.
|
|
3367
|
+
*
|
|
3368
|
+
*/
|
|
3369
|
+
function ensureImport({ code, source, importName, importType, alias }, context) {
|
|
3370
|
+
// ----------------------------------------------------------
|
|
3371
|
+
// STEP 1:
|
|
3372
|
+
// Parse source code into AST
|
|
3373
|
+
//
|
|
3374
|
+
// WHY?
|
|
3375
|
+
//
|
|
3376
|
+
// AST lets us manipulate imports safely instead of using
|
|
3377
|
+
// fragile string replacement.
|
|
3378
|
+
// ----------------------------------------------------------
|
|
3379
|
+
const ast = parse(code, {
|
|
3380
|
+
sourceType: "module",
|
|
3381
|
+
plugins: ["jsx", "typescript"],
|
|
3382
|
+
});
|
|
3383
|
+
// ----------------------------------------------------------
|
|
3384
|
+
// STEP 2:
|
|
3385
|
+
// Track whether matching source import already exists
|
|
3386
|
+
//
|
|
3387
|
+
// Example:
|
|
3388
|
+
//
|
|
3389
|
+
// import React from "react"
|
|
3390
|
+
//
|
|
3391
|
+
// sourceExists -> true
|
|
3392
|
+
// ----------------------------------------------------------
|
|
3393
|
+
let sourceExists = false;
|
|
3394
|
+
// ----------------------------------------------------------
|
|
3395
|
+
// STEP 3:
|
|
3396
|
+
// Traverse AST to search existing imports
|
|
3397
|
+
// ----------------------------------------------------------
|
|
3398
|
+
traverse.default(ast, {
|
|
3399
|
+
ImportDeclaration(path) {
|
|
3400
|
+
// ------------------------------------------------------
|
|
3401
|
+
// Compare import source
|
|
3402
|
+
//
|
|
3403
|
+
// Example:
|
|
3404
|
+
//
|
|
3405
|
+
// import React from "react"
|
|
3406
|
+
// ^^^^^^^
|
|
3407
|
+
// ------------------------------------------------------
|
|
3408
|
+
if (path.node.source.value === source) {
|
|
3409
|
+
sourceExists = true;
|
|
3410
|
+
}
|
|
3411
|
+
},
|
|
3412
|
+
});
|
|
3413
|
+
// ----------------------------------------------------------
|
|
3414
|
+
// STEP 4:
|
|
3415
|
+
// If source import already exists:
|
|
3416
|
+
// enrich existing import declaration
|
|
3417
|
+
//
|
|
3418
|
+
// Example:
|
|
3419
|
+
//
|
|
3420
|
+
// BEFORE:
|
|
3421
|
+
// import React from "react";
|
|
3422
|
+
//
|
|
3423
|
+
// AFTER:
|
|
3424
|
+
// import React, { useState } from "react";
|
|
3425
|
+
// ----------------------------------------------------------
|
|
3426
|
+
if (sourceExists) {
|
|
3427
|
+
let modified = false;
|
|
3428
|
+
traverse.default(ast, {
|
|
3429
|
+
ImportDeclaration(path) {
|
|
3430
|
+
if (path.node.source.value !== source)
|
|
3431
|
+
return;
|
|
3432
|
+
const specifiers = path.node.specifiers;
|
|
3433
|
+
// =========================
|
|
3434
|
+
// DEFAULT IMPORT
|
|
3435
|
+
// import React from "react"
|
|
3436
|
+
// =========================
|
|
3437
|
+
if (importType === "default") {
|
|
3438
|
+
const hasDefault = specifiers.some((s) => t.isImportDefaultSpecifier(s));
|
|
3439
|
+
if (!hasDefault) {
|
|
3440
|
+
specifiers.unshift(t.importDefaultSpecifier(t.identifier(alias || importName)));
|
|
3441
|
+
modified = true;
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
// =========================
|
|
3445
|
+
// NAMED IMPORT
|
|
3446
|
+
// import { useState } from "react"
|
|
3447
|
+
// =========================
|
|
3448
|
+
if (importType === "named") {
|
|
3449
|
+
// namespace import already covers all
|
|
3450
|
+
const hasNamespace = specifiers.some((s) => t.isImportNamespaceSpecifier(s));
|
|
3451
|
+
if (hasNamespace)
|
|
3452
|
+
return;
|
|
3453
|
+
const hasNamed = specifiers.some((s) => t.isImportSpecifier(s) &&
|
|
3454
|
+
t.isIdentifier(s.imported) &&
|
|
3455
|
+
s.imported.name === importName);
|
|
3456
|
+
if (!hasNamed) {
|
|
3457
|
+
specifiers.push(t.importSpecifier(t.identifier(alias || importName), t.identifier(importName)));
|
|
3458
|
+
modified = true;
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
// =========================
|
|
3462
|
+
// NAMESPACE IMPORT
|
|
3463
|
+
// import * as React from "react"
|
|
3464
|
+
// =========================
|
|
3465
|
+
if (importType === "namespace") {
|
|
3466
|
+
const hasNamespace = specifiers.some((s) => t.isImportNamespaceSpecifier(s));
|
|
3467
|
+
if (!hasNamespace) {
|
|
3468
|
+
path.node.specifiers = [
|
|
3469
|
+
t.importNamespaceSpecifier(t.identifier(alias || importName)),
|
|
3470
|
+
];
|
|
3471
|
+
modified = true;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
},
|
|
3475
|
+
});
|
|
3476
|
+
if (!modified)
|
|
3477
|
+
return { success: true, updatedCode: code };
|
|
3478
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
3479
|
+
}
|
|
3480
|
+
// ----------------------------------------------------------
|
|
3481
|
+
// STEP 5:
|
|
3482
|
+
// Create import specifier based on import type
|
|
3483
|
+
//
|
|
3484
|
+
// Different import types require different AST nodes.
|
|
3485
|
+
// ----------------------------------------------------------
|
|
3486
|
+
let specifier;
|
|
3487
|
+
// ----------------------------------------------------------
|
|
3488
|
+
// DEFAULT IMPORT
|
|
3489
|
+
//
|
|
3490
|
+
// import React from "react"
|
|
3491
|
+
// ----------------------------------------------------------
|
|
3492
|
+
if (importType === "default") {
|
|
3493
|
+
specifier = t.importDefaultSpecifier(t.identifier(alias || importName));
|
|
3494
|
+
}
|
|
3495
|
+
// ----------------------------------------------------------
|
|
3496
|
+
// NAMED IMPORT
|
|
3497
|
+
//
|
|
3498
|
+
// import { useState } from "react"
|
|
3499
|
+
// ----------------------------------------------------------
|
|
3500
|
+
if (importType === "named") {
|
|
3501
|
+
specifier = t.importSpecifier(t.identifier(alias || importName), t.identifier(importName));
|
|
3502
|
+
}
|
|
3503
|
+
// ----------------------------------------------------------
|
|
3504
|
+
// NAMESPACE IMPORT
|
|
3505
|
+
//
|
|
3506
|
+
// import * as React from "react"
|
|
3507
|
+
// ----------------------------------------------------------
|
|
3508
|
+
if (importType === "namespace") {
|
|
3509
|
+
specifier = t.importNamespaceSpecifier(t.identifier(alias || importName));
|
|
3510
|
+
}
|
|
3511
|
+
// ----------------------------------------------------------
|
|
3512
|
+
// STEP 6:
|
|
3513
|
+
// Create new ImportDeclaration AST node
|
|
3514
|
+
// ----------------------------------------------------------
|
|
3515
|
+
const importDeclaration = t.importDeclaration([specifier], t.stringLiteral(source));
|
|
3516
|
+
// ----------------------------------------------------------
|
|
3517
|
+
// STEP 7:
|
|
3518
|
+
// Insert import at top of file
|
|
3519
|
+
//
|
|
3520
|
+
// unshift():
|
|
3521
|
+
// inserts at beginning of array
|
|
3522
|
+
// ----------------------------------------------------------
|
|
3523
|
+
ast.program.body.unshift(importDeclaration);
|
|
3524
|
+
// ----------------------------------------------------------
|
|
3525
|
+
// STEP 8:
|
|
3526
|
+
// Generate updated code from AST
|
|
3527
|
+
// ----------------------------------------------------------
|
|
3528
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
3529
|
+
}
|
|
3530
|
+
/**
|
|
3531
|
+
* Removes unused imports from a file.
|
|
3532
|
+
*
|
|
3533
|
+
* ------------------------------------------------------------
|
|
3534
|
+
* WHAT THIS FUNCTION DOES
|
|
3535
|
+
* ------------------------------------------------------------
|
|
3536
|
+
*
|
|
3537
|
+
* This function analyzes the entire file and removes imports
|
|
3538
|
+
* that are not used anywhere in the code.
|
|
3539
|
+
*
|
|
3540
|
+
* It supports:
|
|
3541
|
+
*
|
|
3542
|
+
* - default imports
|
|
3543
|
+
* - named imports
|
|
3544
|
+
* - namespace imports
|
|
3545
|
+
*
|
|
3546
|
+
* ------------------------------------------------------------
|
|
3547
|
+
* EXAMPLE
|
|
3548
|
+
* ------------------------------------------------------------
|
|
3549
|
+
*
|
|
3550
|
+
* BEFORE:
|
|
3551
|
+
*
|
|
3552
|
+
* import React from "react";
|
|
3553
|
+
* import { useState, useEffect } from "react";
|
|
3554
|
+
*
|
|
3555
|
+
* function App() {
|
|
3556
|
+
* const [count] = useState(0);
|
|
3557
|
+
* return <div>{count}</div>;
|
|
3558
|
+
* }
|
|
3559
|
+
*
|
|
3560
|
+
* AFTER:
|
|
3561
|
+
*
|
|
3562
|
+
* import { useState } from "react";
|
|
3563
|
+
*
|
|
3564
|
+
* function App() {
|
|
3565
|
+
* const [count] = useState(0);
|
|
3566
|
+
* return <div>{count}</div>;
|
|
3567
|
+
* }
|
|
3568
|
+
*
|
|
3569
|
+
* ------------------------------------------------------------
|
|
3570
|
+
* WHY THIS FUNCTION IS IMPORTANT
|
|
3571
|
+
* ------------------------------------------------------------
|
|
3572
|
+
*
|
|
3573
|
+
* During automatic code generation:
|
|
3574
|
+
*
|
|
3575
|
+
* - components get deleted
|
|
3576
|
+
* - hooks get removed
|
|
3577
|
+
* - JSX gets replaced
|
|
3578
|
+
*
|
|
3579
|
+
* which leaves behind:
|
|
3580
|
+
*
|
|
3581
|
+
* - dead imports
|
|
3582
|
+
* - duplicate imports
|
|
3583
|
+
* - stale imports
|
|
3584
|
+
*
|
|
3585
|
+
* optimizeImports cleans the file afterward.
|
|
3586
|
+
*
|
|
3587
|
+
* Useful commands:
|
|
3588
|
+
* - clear it: removes stale imports after JSX cleanup
|
|
3589
|
+
* - remove header: removes unused Header import
|
|
3590
|
+
* - remove useEffect: removes dead hook imports
|
|
3591
|
+
* - replace navbar: cleans obsolete imports afterward
|
|
3592
|
+
* - delete chart section: removes unused chart imports
|
|
3593
|
+
*
|
|
3594
|
+
* ------------------------------------------------------------
|
|
3595
|
+
* IMPORTANT ARCHITECTURAL IDEA
|
|
3596
|
+
* ------------------------------------------------------------
|
|
3597
|
+
*
|
|
3598
|
+
* This function should usually run AFTER:
|
|
3599
|
+
*
|
|
3600
|
+
* - removeJSX
|
|
3601
|
+
* - deleteComponent
|
|
3602
|
+
* - updateFunction
|
|
3603
|
+
* - removeImport
|
|
3604
|
+
* - replaceJSX
|
|
3605
|
+
*
|
|
3606
|
+
* It acts as a cleanup/sanitization pass.
|
|
3607
|
+
*
|
|
3608
|
+
* ------------------------------------------------------------
|
|
3609
|
+
* IMPORTANT LIMITATION
|
|
3610
|
+
* ------------------------------------------------------------
|
|
3611
|
+
*
|
|
3612
|
+
* This is a lightweight AST-based optimizer.
|
|
3613
|
+
*
|
|
3614
|
+
* It checks identifier usage inside the current file only.
|
|
3615
|
+
*
|
|
3616
|
+
* It does NOT:
|
|
3617
|
+
* - understand runtime usage
|
|
3618
|
+
* - understand dynamic imports
|
|
3619
|
+
* - understand string-based references
|
|
3620
|
+
* - understand reflection/meta-programming
|
|
3621
|
+
*
|
|
3622
|
+
* ------------------------------------------------------------
|
|
3623
|
+
* PARAMS
|
|
3624
|
+
* ------------------------------------------------------------
|
|
3625
|
+
*
|
|
3626
|
+
* @param {Object} params
|
|
3627
|
+
*
|
|
3628
|
+
* @param {string} params.code
|
|
3629
|
+
* Entire source code.
|
|
3630
|
+
*
|
|
3631
|
+
* ------------------------------------------------------------
|
|
3632
|
+
* RETURNS
|
|
3633
|
+
* ------------------------------------------------------------
|
|
3634
|
+
*
|
|
3635
|
+
* @returns {{updatedCode: string}}
|
|
3636
|
+
* Updated optimized code.
|
|
3637
|
+
*
|
|
3638
|
+
*/
|
|
3639
|
+
function optimizeImports({ code }, context) {
|
|
3640
|
+
// ----------------------------------------------------------
|
|
3641
|
+
// STEP 1:
|
|
3642
|
+
// Parse code into AST
|
|
3643
|
+
//
|
|
3644
|
+
// WHY?
|
|
3645
|
+
//
|
|
3646
|
+
// AST lets us safely analyze:
|
|
3647
|
+
// - imports
|
|
3648
|
+
// - identifiers
|
|
3649
|
+
// - JSX usage
|
|
3650
|
+
// ----------------------------------------------------------
|
|
3651
|
+
const ast = parse(code, {
|
|
3652
|
+
sourceType: "module",
|
|
3653
|
+
plugins: ["jsx", "typescript"],
|
|
3654
|
+
});
|
|
3655
|
+
// ----------------------------------------------------------
|
|
3656
|
+
// STEP 2:
|
|
3657
|
+
// Store all identifiers actually used in file
|
|
3658
|
+
//
|
|
3659
|
+
// Example:
|
|
3660
|
+
//
|
|
3661
|
+
// useState
|
|
3662
|
+
// Header
|
|
3663
|
+
// React
|
|
3664
|
+
// ----------------------------------------------------------
|
|
3665
|
+
const usedIdentifiers = new Set();
|
|
3666
|
+
// ----------------------------------------------------------
|
|
3667
|
+
// STEP 3:
|
|
3668
|
+
// Traverse AST and collect identifier usage
|
|
3669
|
+
// ----------------------------------------------------------
|
|
3670
|
+
traverse.default(ast, {
|
|
3671
|
+
Identifier(path) {
|
|
3672
|
+
// ------------------------------------------------------
|
|
3673
|
+
// Ignore identifiers inside import declarations
|
|
3674
|
+
//
|
|
3675
|
+
// Example:
|
|
3676
|
+
//
|
|
3677
|
+
// import React from "react"
|
|
3678
|
+
//
|
|
3679
|
+
// "React" here should NOT count as usage
|
|
3680
|
+
// ------------------------------------------------------
|
|
3681
|
+
if (path.parent.type === "ImportSpecifier") {
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3684
|
+
if (path.parent.type === "ImportDefaultSpecifier") {
|
|
3685
|
+
return;
|
|
3686
|
+
}
|
|
3687
|
+
if (path.parent.type === "ImportNamespaceSpecifier") {
|
|
3688
|
+
return;
|
|
3689
|
+
}
|
|
3690
|
+
// ------------------------------------------------------
|
|
3691
|
+
// Add identifier name to used set
|
|
3692
|
+
// ------------------------------------------------------
|
|
3693
|
+
usedIdentifiers.add(path.node.name);
|
|
3694
|
+
},
|
|
3695
|
+
// --------------------------------------------------------
|
|
3696
|
+
// JSX identifiers are separate from normal identifiers
|
|
3697
|
+
//
|
|
3698
|
+
// Example:
|
|
3699
|
+
//
|
|
3700
|
+
// <Header />
|
|
3701
|
+
//
|
|
3702
|
+
// Header is JSXIdentifier
|
|
3703
|
+
// --------------------------------------------------------
|
|
3704
|
+
JSXIdentifier(path) {
|
|
3705
|
+
// ------------------------------------------------------
|
|
3706
|
+
// Ignore native HTML tags
|
|
3707
|
+
//
|
|
3708
|
+
// div
|
|
3709
|
+
// button
|
|
3710
|
+
// span
|
|
3711
|
+
// ------------------------------------------------------
|
|
3712
|
+
const isNativeTag = path.node.name[0] === path.node.name[0].toLowerCase();
|
|
3713
|
+
if (isNativeTag) {
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
// ------------------------------------------------------
|
|
3717
|
+
// Track JSX component usage
|
|
3718
|
+
// ------------------------------------------------------
|
|
3719
|
+
usedIdentifiers.add(path.node.name);
|
|
3720
|
+
},
|
|
3721
|
+
});
|
|
3722
|
+
// ----------------------------------------------------------
|
|
3723
|
+
// STEP 4:
|
|
3724
|
+
// Traverse imports and remove unused specifiers
|
|
3725
|
+
// ----------------------------------------------------------
|
|
3726
|
+
traverse.default(ast, {
|
|
3727
|
+
ImportDeclaration(path) {
|
|
3728
|
+
// ------------------------------------------------------
|
|
3729
|
+
// Filter import specifiers
|
|
3730
|
+
// ------------------------------------------------------
|
|
3731
|
+
const remainingSpecifiers = path.node.specifiers.filter((specifier) => {
|
|
3732
|
+
// ==================================================
|
|
3733
|
+
// DEFAULT IMPORT
|
|
3734
|
+
// ==================================================
|
|
3735
|
+
if (t.isImportDefaultSpecifier(specifier)) {
|
|
3736
|
+
return usedIdentifiers.has(specifier.local.name);
|
|
3737
|
+
}
|
|
3738
|
+
// ==================================================
|
|
3739
|
+
// NAMED IMPORT
|
|
3740
|
+
// ==================================================
|
|
3741
|
+
if (t.isImportSpecifier(specifier)) {
|
|
3742
|
+
return usedIdentifiers.has(specifier.local.name);
|
|
3743
|
+
}
|
|
3744
|
+
// ==================================================
|
|
3745
|
+
// NAMESPACE IMPORT
|
|
3746
|
+
// ==================================================
|
|
3747
|
+
if (t.isImportNamespaceSpecifier(specifier)) {
|
|
3748
|
+
return usedIdentifiers.has(specifier.local.name);
|
|
3749
|
+
}
|
|
3750
|
+
return true;
|
|
3751
|
+
});
|
|
3752
|
+
// ------------------------------------------------------
|
|
3753
|
+
// If no imports remain:
|
|
3754
|
+
// remove entire import declaration
|
|
3755
|
+
// ------------------------------------------------------
|
|
3756
|
+
if (remainingSpecifiers.length === 0) {
|
|
3757
|
+
path.remove();
|
|
3758
|
+
return;
|
|
3759
|
+
}
|
|
3760
|
+
// ------------------------------------------------------
|
|
3761
|
+
// Otherwise update remaining imports
|
|
3762
|
+
// ------------------------------------------------------
|
|
3763
|
+
path.node.specifiers = remainingSpecifiers;
|
|
3764
|
+
},
|
|
3765
|
+
});
|
|
3766
|
+
// ----------------------------------------------------------
|
|
3767
|
+
// STEP 5:
|
|
3768
|
+
// Generate updated source code from AST
|
|
3769
|
+
// ----------------------------------------------------------
|
|
3770
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
3771
|
+
}
|
|
3772
|
+
function updateImportSource({ code, oldSource, newSource }) {
|
|
3773
|
+
// ----------------------------------------------------------
|
|
3774
|
+
// STEP 1:
|
|
3775
|
+
// Parse code into AST
|
|
3776
|
+
//
|
|
3777
|
+
// WHY?
|
|
3778
|
+
//
|
|
3779
|
+
// AST lets us safely analyze:
|
|
3780
|
+
// - imports
|
|
3781
|
+
// - identifiers
|
|
3782
|
+
// - JSX usage
|
|
3783
|
+
// ----------------------------------------------------------
|
|
3784
|
+
const ast = parse(code, {
|
|
3785
|
+
sourceType: "module",
|
|
3786
|
+
plugins: ["jsx", "typescript"],
|
|
3787
|
+
});
|
|
3788
|
+
traverse.default(ast, {
|
|
3789
|
+
ImportDeclaration(path) {
|
|
3790
|
+
if (path.node.source.value === oldSource) {
|
|
3791
|
+
path.node.source.value = newSource;
|
|
3792
|
+
}
|
|
3793
|
+
},
|
|
3794
|
+
});
|
|
3795
|
+
return { success: true, updatedCode: generate(ast).code };
|
|
3796
|
+
}
|
|
3797
|
+
export default {
|
|
3798
|
+
insertJSX,
|
|
3799
|
+
replaceJSX,
|
|
3800
|
+
removeJSX,
|
|
3801
|
+
moveJSX,
|
|
3802
|
+
wrapJSX,
|
|
3803
|
+
// expose only when planner becomes mature enough to use it directly
|
|
3804
|
+
insertVariable,
|
|
3805
|
+
updateVariable,
|
|
3806
|
+
deleteVariable,
|
|
3807
|
+
removeImport,
|
|
3808
|
+
ensureImport,
|
|
3809
|
+
optimizeImports,
|
|
3810
|
+
updateImportSource,
|
|
3811
|
+
// createFunction,
|
|
3812
|
+
// updateFunction,
|
|
3813
|
+
// deleteFunction,
|
|
3814
|
+
// do not expose
|
|
3815
|
+
// generateClassNames,
|
|
3816
|
+
// syncComponentStyles,
|
|
3817
|
+
// resolveCssClassConflicts,
|
|
3818
|
+
};
|
|
3819
|
+
//# sourceMappingURL=ast.js.map
|