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,840 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import traverse from "@babel/traverse";
|
|
3
|
+
import { parse } from "@babel/parser";
|
|
4
|
+
import t from "@babel/types";
|
|
5
|
+
import postcss from "postcss";
|
|
6
|
+
import { ERROR_CODES } from "../../schemas/index.js";
|
|
7
|
+
import { LoomaError } from "../../server/error.js";
|
|
8
|
+
/**
|
|
9
|
+
* Parses JavaScript/TypeScript/JSX source code into AST.
|
|
10
|
+
*
|
|
11
|
+
* ------------------------------------------------------------
|
|
12
|
+
* WHAT THIS FUNCTION DOES
|
|
13
|
+
* ------------------------------------------------------------
|
|
14
|
+
*
|
|
15
|
+
* This function converts source code text into:
|
|
16
|
+
*
|
|
17
|
+
* AST (Abstract Syntax Tree)
|
|
18
|
+
*
|
|
19
|
+
* Example:
|
|
20
|
+
*
|
|
21
|
+
* INPUT:
|
|
22
|
+
*
|
|
23
|
+
* function sum(a, b) {
|
|
24
|
+
* return a + b;
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* OUTPUT:
|
|
28
|
+
*
|
|
29
|
+
* Program
|
|
30
|
+
* └── FunctionDeclaration
|
|
31
|
+
* └── Identifier(sum)
|
|
32
|
+
*
|
|
33
|
+
* ------------------------------------------------------------
|
|
34
|
+
* WHAT IS AST?
|
|
35
|
+
* ------------------------------------------------------------
|
|
36
|
+
*
|
|
37
|
+
* AST = structural representation of code.
|
|
38
|
+
*
|
|
39
|
+
* Instead of manipulating raw strings,
|
|
40
|
+
* tools manipulate syntax nodes safely.
|
|
41
|
+
*
|
|
42
|
+
* Example:
|
|
43
|
+
*
|
|
44
|
+
* Code:
|
|
45
|
+
*
|
|
46
|
+
* const x = 10;
|
|
47
|
+
*
|
|
48
|
+
* becomes:
|
|
49
|
+
*
|
|
50
|
+
* VariableDeclaration
|
|
51
|
+
* └── VariableDeclarator
|
|
52
|
+
* └── Identifier(x)
|
|
53
|
+
*
|
|
54
|
+
* ------------------------------------------------------------
|
|
55
|
+
* WHY THIS FUNCTION EXISTS
|
|
56
|
+
* ------------------------------------------------------------
|
|
57
|
+
*
|
|
58
|
+
* Almost every intelligent code transformation requires AST.
|
|
59
|
+
*
|
|
60
|
+
* Example operations:
|
|
61
|
+
*
|
|
62
|
+
* - insert imports
|
|
63
|
+
* - update variables
|
|
64
|
+
* - create components
|
|
65
|
+
* - manipulate JSX
|
|
66
|
+
* - optimize imports
|
|
67
|
+
* - refactor functions
|
|
68
|
+
*
|
|
69
|
+
* ALL depend on parsing source code first.
|
|
70
|
+
*
|
|
71
|
+
* ------------------------------------------------------------
|
|
72
|
+
* IMPORTANT ARCHITECTURAL IDEA
|
|
73
|
+
* ------------------------------------------------------------
|
|
74
|
+
*
|
|
75
|
+
* parseAST is:
|
|
76
|
+
*
|
|
77
|
+
* INTERNAL ONLY
|
|
78
|
+
*
|
|
79
|
+
* It should NOT be exposed directly as user task.
|
|
80
|
+
*
|
|
81
|
+
* WHY?
|
|
82
|
+
*
|
|
83
|
+
* Users never say:
|
|
84
|
+
*
|
|
85
|
+
* "parse this AST"
|
|
86
|
+
*
|
|
87
|
+
* Instead they say:
|
|
88
|
+
*
|
|
89
|
+
* "add button"
|
|
90
|
+
* "remove import"
|
|
91
|
+
* "create component"
|
|
92
|
+
*
|
|
93
|
+
* Higher-level primitives internally use parseAST.
|
|
94
|
+
*
|
|
95
|
+
* ------------------------------------------------------------
|
|
96
|
+
* WHY AST IS BETTER THAN STRING MANIPULATION
|
|
97
|
+
* ------------------------------------------------------------
|
|
98
|
+
*
|
|
99
|
+
* String manipulation breaks easily.
|
|
100
|
+
*
|
|
101
|
+
* Problems:
|
|
102
|
+
*
|
|
103
|
+
* - formatting differences
|
|
104
|
+
* - nested JSX
|
|
105
|
+
* - multiline code
|
|
106
|
+
* - syntax ambiguity
|
|
107
|
+
* - duplicate names
|
|
108
|
+
*
|
|
109
|
+
* AST guarantees:
|
|
110
|
+
*
|
|
111
|
+
* - syntax-aware transformations
|
|
112
|
+
* - deterministic edits
|
|
113
|
+
* - valid output generation
|
|
114
|
+
*
|
|
115
|
+
* ------------------------------------------------------------
|
|
116
|
+
* SUPPORTED CODE TYPES
|
|
117
|
+
* ------------------------------------------------------------
|
|
118
|
+
*
|
|
119
|
+
* Current parser supports:
|
|
120
|
+
*
|
|
121
|
+
* - JavaScript
|
|
122
|
+
* - JSX
|
|
123
|
+
* - TypeScript
|
|
124
|
+
* - TSX
|
|
125
|
+
*
|
|
126
|
+
* via Babel plugins.
|
|
127
|
+
*
|
|
128
|
+
* ------------------------------------------------------------
|
|
129
|
+
* PARSER PLUGINS
|
|
130
|
+
* ------------------------------------------------------------
|
|
131
|
+
*
|
|
132
|
+
* jsx
|
|
133
|
+
* → enables React JSX parsing
|
|
134
|
+
*
|
|
135
|
+
* typescript
|
|
136
|
+
* → enables TS/TSX parsing
|
|
137
|
+
*
|
|
138
|
+
* ------------------------------------------------------------
|
|
139
|
+
* IMPORTANT LIMITATION
|
|
140
|
+
* ------------------------------------------------------------
|
|
141
|
+
*
|
|
142
|
+
* Current implementation:
|
|
143
|
+
*
|
|
144
|
+
* - assumes modern JS syntax
|
|
145
|
+
*
|
|
146
|
+
* Future improvements:
|
|
147
|
+
*
|
|
148
|
+
* - decorators
|
|
149
|
+
* - class properties
|
|
150
|
+
* - import assertions
|
|
151
|
+
* - pipeline operators
|
|
152
|
+
* - custom parser plugins
|
|
153
|
+
*
|
|
154
|
+
* ------------------------------------------------------------
|
|
155
|
+
* PARAMS
|
|
156
|
+
* ------------------------------------------------------------
|
|
157
|
+
*
|
|
158
|
+
* @param {Object} params
|
|
159
|
+
*
|
|
160
|
+
* @param {string} params.code
|
|
161
|
+
* Source code string.
|
|
162
|
+
*
|
|
163
|
+
* ------------------------------------------------------------
|
|
164
|
+
* RETURNS
|
|
165
|
+
* ------------------------------------------------------------
|
|
166
|
+
*
|
|
167
|
+
* @returns {object}
|
|
168
|
+
* Babel AST object.
|
|
169
|
+
*
|
|
170
|
+
*/
|
|
171
|
+
function parseAST({ code }) {
|
|
172
|
+
// ----------------------------------------------------------
|
|
173
|
+
// STEP 1:
|
|
174
|
+
// Parse source code into AST
|
|
175
|
+
//
|
|
176
|
+
// sourceType: "module"
|
|
177
|
+
//
|
|
178
|
+
// enables:
|
|
179
|
+
//
|
|
180
|
+
// import/export parsing.
|
|
181
|
+
//
|
|
182
|
+
// plugins:
|
|
183
|
+
//
|
|
184
|
+
// jsx
|
|
185
|
+
// → React JSX support
|
|
186
|
+
//
|
|
187
|
+
// typescript
|
|
188
|
+
// → TS/TSX support
|
|
189
|
+
// ----------------------------------------------------------
|
|
190
|
+
try {
|
|
191
|
+
return parse(code, {
|
|
192
|
+
sourceType: "module",
|
|
193
|
+
plugins: ["jsx", "typescript"],
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
throw new LoomaError(ERROR_CODES.PARSE_ERROR, `AST parsing failed: ${error.message}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Parses CSS into a structured AST representation.
|
|
202
|
+
*
|
|
203
|
+
* ------------------------------------------------------------
|
|
204
|
+
* WHAT THIS FUNCTION DOES
|
|
205
|
+
* ------------------------------------------------------------
|
|
206
|
+
*
|
|
207
|
+
* This function converts raw CSS code into:
|
|
208
|
+
*
|
|
209
|
+
* CSS AST (Abstract Syntax Tree)
|
|
210
|
+
*
|
|
211
|
+
* using:
|
|
212
|
+
*
|
|
213
|
+
* PostCSS
|
|
214
|
+
*
|
|
215
|
+
* Example:
|
|
216
|
+
*
|
|
217
|
+
* INPUT:
|
|
218
|
+
*
|
|
219
|
+
* .header {
|
|
220
|
+
* color: red;
|
|
221
|
+
* }
|
|
222
|
+
*
|
|
223
|
+
* OUTPUT:
|
|
224
|
+
*
|
|
225
|
+
* {
|
|
226
|
+
* type: "root",
|
|
227
|
+
* nodes: [...]
|
|
228
|
+
* }
|
|
229
|
+
*
|
|
230
|
+
* ------------------------------------------------------------
|
|
231
|
+
* WHY THIS FUNCTION EXISTS
|
|
232
|
+
* ------------------------------------------------------------
|
|
233
|
+
*
|
|
234
|
+
* String replacement becomes dangerous
|
|
235
|
+
* as CSS complexity grows.
|
|
236
|
+
*
|
|
237
|
+
* Example problems:
|
|
238
|
+
*
|
|
239
|
+
* - nested media queries
|
|
240
|
+
* - duplicate selectors
|
|
241
|
+
* - partial replacements
|
|
242
|
+
* - malformed css
|
|
243
|
+
* - accidental deletions
|
|
244
|
+
*
|
|
245
|
+
* AST parsing allows:
|
|
246
|
+
*
|
|
247
|
+
* - safe css mutations
|
|
248
|
+
* - selector analysis
|
|
249
|
+
* - declaration traversal
|
|
250
|
+
* - style refactoring
|
|
251
|
+
* - advanced css tooling
|
|
252
|
+
*
|
|
253
|
+
* ------------------------------------------------------------
|
|
254
|
+
* IMPORTANT ARCHITECTURAL DECISION
|
|
255
|
+
* ------------------------------------------------------------
|
|
256
|
+
*
|
|
257
|
+
* Looma should eventually perform:
|
|
258
|
+
*
|
|
259
|
+
* ALL CSS OPERATIONS
|
|
260
|
+
*
|
|
261
|
+
* using AST transformations.
|
|
262
|
+
*
|
|
263
|
+
* NOT regex.
|
|
264
|
+
*
|
|
265
|
+
* Regex-based CSS editing breaks
|
|
266
|
+
* at scale.
|
|
267
|
+
*
|
|
268
|
+
* This function is foundational
|
|
269
|
+
* infrastructure for that transition.
|
|
270
|
+
*
|
|
271
|
+
* ------------------------------------------------------------
|
|
272
|
+
* DEPENDENCIES
|
|
273
|
+
* ------------------------------------------------------------
|
|
274
|
+
*
|
|
275
|
+
* This function complements:
|
|
276
|
+
*
|
|
277
|
+
* - insertStyles()
|
|
278
|
+
* - updateStyles()
|
|
279
|
+
* - removeStyles()
|
|
280
|
+
* - renameCssClass()
|
|
281
|
+
* - syncComponentStyles()
|
|
282
|
+
*
|
|
283
|
+
* ------------------------------------------------------------
|
|
284
|
+
* WHERE THIS FUNCTION IS USEFUL
|
|
285
|
+
* ------------------------------------------------------------
|
|
286
|
+
*
|
|
287
|
+
* Useful in commands like:
|
|
288
|
+
*
|
|
289
|
+
* - "make button red"
|
|
290
|
+
* - "update responsive styles"
|
|
291
|
+
* - "rename css class"
|
|
292
|
+
* - "remove unused styles"
|
|
293
|
+
* - "extract component"
|
|
294
|
+
* - "optimize css"
|
|
295
|
+
* - "convert to dark mode"
|
|
296
|
+
*
|
|
297
|
+
* It is usually used BEFORE:
|
|
298
|
+
*
|
|
299
|
+
* - advanced css mutations
|
|
300
|
+
* - css analysis
|
|
301
|
+
* - selector synchronization
|
|
302
|
+
*
|
|
303
|
+
* ------------------------------------------------------------
|
|
304
|
+
* IMPORTANT NOTE
|
|
305
|
+
* ------------------------------------------------------------
|
|
306
|
+
*
|
|
307
|
+
* This function ONLY parses CSS.
|
|
308
|
+
*
|
|
309
|
+
* It does NOT:
|
|
310
|
+
*
|
|
311
|
+
* - modify css
|
|
312
|
+
* - optimize css
|
|
313
|
+
* - write files
|
|
314
|
+
*
|
|
315
|
+
* It only converts CSS:
|
|
316
|
+
*
|
|
317
|
+
* text
|
|
318
|
+
* →
|
|
319
|
+
* AST
|
|
320
|
+
*
|
|
321
|
+
* ------------------------------------------------------------
|
|
322
|
+
* REQUIRED PACKAGE
|
|
323
|
+
* ------------------------------------------------------------
|
|
324
|
+
*
|
|
325
|
+
* npm install postcss
|
|
326
|
+
*
|
|
327
|
+
* ------------------------------------------------------------
|
|
328
|
+
* PARAMS
|
|
329
|
+
* ------------------------------------------------------------
|
|
330
|
+
*
|
|
331
|
+
* @param {Object} params
|
|
332
|
+
*
|
|
333
|
+
* @param {string} [params.cssCode]
|
|
334
|
+
* Raw css code.
|
|
335
|
+
*
|
|
336
|
+
* OR
|
|
337
|
+
*
|
|
338
|
+
* @param {string} [params.cssPath]
|
|
339
|
+
* Path to css file.
|
|
340
|
+
*
|
|
341
|
+
* One of:
|
|
342
|
+
*
|
|
343
|
+
* - cssCode
|
|
344
|
+
* - cssPath
|
|
345
|
+
*
|
|
346
|
+
* is required.
|
|
347
|
+
*
|
|
348
|
+
* ------------------------------------------------------------
|
|
349
|
+
* RETURNS
|
|
350
|
+
* ------------------------------------------------------------
|
|
351
|
+
*
|
|
352
|
+
* @returns {Object}
|
|
353
|
+
*
|
|
354
|
+
* {
|
|
355
|
+
* ast,
|
|
356
|
+
* cssCode
|
|
357
|
+
* }
|
|
358
|
+
*
|
|
359
|
+
*/
|
|
360
|
+
function parseCSS({ cssCode, cssPath, }) {
|
|
361
|
+
// ----------------------------------------------------------
|
|
362
|
+
// STEP 1:
|
|
363
|
+
// Validate input
|
|
364
|
+
// ----------------------------------------------------------
|
|
365
|
+
if (!cssCode && !cssPath) {
|
|
366
|
+
throw new LoomaError(ERROR_CODES.PARSE_ERROR, "Either cssCode or cssPath is required");
|
|
367
|
+
}
|
|
368
|
+
// ----------------------------------------------------------
|
|
369
|
+
// STEP 2:
|
|
370
|
+
// Read css file if path provided
|
|
371
|
+
// ----------------------------------------------------------
|
|
372
|
+
if (cssPath) {
|
|
373
|
+
// --------------------------------------------------------
|
|
374
|
+
// Validate css file existence
|
|
375
|
+
// --------------------------------------------------------
|
|
376
|
+
if (!fs.existsSync(cssPath)) {
|
|
377
|
+
throw new LoomaError(ERROR_CODES.FILE_NOT_FOUND, `CSS file does not exist: ${cssPath}`);
|
|
378
|
+
}
|
|
379
|
+
// --------------------------------------------------------
|
|
380
|
+
// Read css content
|
|
381
|
+
// --------------------------------------------------------
|
|
382
|
+
cssCode = fs.readFileSync(cssPath, "utf8");
|
|
383
|
+
}
|
|
384
|
+
// ----------------------------------------------------------
|
|
385
|
+
// STEP 3:
|
|
386
|
+
// Parse css using PostCSS
|
|
387
|
+
//
|
|
388
|
+
// Converts:
|
|
389
|
+
//
|
|
390
|
+
// raw css
|
|
391
|
+
// →
|
|
392
|
+
// AST
|
|
393
|
+
// ----------------------------------------------------------
|
|
394
|
+
const ast = postcss.parse(cssCode);
|
|
395
|
+
// ----------------------------------------------------------
|
|
396
|
+
// STEP 4:
|
|
397
|
+
// Return parsed result
|
|
398
|
+
// ----------------------------------------------------------
|
|
399
|
+
return {
|
|
400
|
+
success: true,
|
|
401
|
+
ast,
|
|
402
|
+
cssCode,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Parses React Router routes from AST.
|
|
407
|
+
*
|
|
408
|
+
* returns
|
|
409
|
+
* {
|
|
410
|
+
success: true,
|
|
411
|
+
routes: [ {}, { path: 'step-2' }, { path: 'step-3' } ]
|
|
412
|
+
}
|
|
413
|
+
*
|
|
414
|
+
*/
|
|
415
|
+
function parseRoutes({ ast }) {
|
|
416
|
+
const routes = [];
|
|
417
|
+
traverse.default(ast, {
|
|
418
|
+
JSXElement(path) {
|
|
419
|
+
const openingElement = path.node.openingElement;
|
|
420
|
+
if (t.isJSXIdentifier(openingElement.name) &&
|
|
421
|
+
openingElement.name.name === "Route") {
|
|
422
|
+
const route = {};
|
|
423
|
+
openingElement.attributes.forEach((attr) => {
|
|
424
|
+
if (attr.type !== "JSXAttribute") {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const name = attr.name.name;
|
|
428
|
+
if (attr.value && attr.value.type === "StringLiteral") {
|
|
429
|
+
if (typeof name === "string") {
|
|
430
|
+
route[name] = attr.value.value;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
routes.push(route);
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
if (routes.length === 0) {
|
|
439
|
+
return { success: false, message: "No react route found." };
|
|
440
|
+
}
|
|
441
|
+
return { success: true, routes };
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* returns all the imports of a component like this
|
|
445
|
+
*
|
|
446
|
+
* [
|
|
447
|
+
{ localName: 'useState', source: 'react' },
|
|
448
|
+
{ localName: 'useEffect', source: 'react' },
|
|
449
|
+
{ localName: 'reactLogo', source: '../../assets/react.svg' },
|
|
450
|
+
{ localName: 'viteLogo', source: '/vite.svg' },
|
|
451
|
+
{ localName: 'Header', source: './Header' }
|
|
452
|
+
]
|
|
453
|
+
*
|
|
454
|
+
*/
|
|
455
|
+
function parseComponentDependencies({ ast }) {
|
|
456
|
+
const dependencies = [];
|
|
457
|
+
traverse.default(ast, {
|
|
458
|
+
ImportDeclaration(path) {
|
|
459
|
+
const source = path.node.source.value;
|
|
460
|
+
path.node.specifiers.forEach((specifier) => {
|
|
461
|
+
dependencies.push({
|
|
462
|
+
localName: specifier.local.name,
|
|
463
|
+
source,
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
return { success: true, dependencies };
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Parses component props usage.
|
|
472
|
+
*/
|
|
473
|
+
function parseProps({ ast }) {
|
|
474
|
+
const props = new Set();
|
|
475
|
+
traverse.default(ast, {
|
|
476
|
+
MemberExpression(path) {
|
|
477
|
+
if (t.isIdentifier(path.node.object) &&
|
|
478
|
+
path.node.object.name === "props") {
|
|
479
|
+
if (t.isIdentifier(path.node.property)) {
|
|
480
|
+
props.add(path.node.property.name);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
ObjectPattern(path) {
|
|
485
|
+
path.node.properties.forEach((property) => {
|
|
486
|
+
if (property.type === "ObjectProperty") {
|
|
487
|
+
if (property.key && property.key.type === "Identifier") {
|
|
488
|
+
props.add(property.key.name);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
return { success: true, props: [...props] };
|
|
495
|
+
}
|
|
496
|
+
// /**
|
|
497
|
+
// * Parses useState usage.
|
|
498
|
+
// */
|
|
499
|
+
// function parseStateUsage(
|
|
500
|
+
// ast: TaskPayload<"parseStateUsage">
|
|
501
|
+
// ): TaskResponse<TaskReturn<"parseStateUsage">> {
|
|
502
|
+
// const states = [];
|
|
503
|
+
// traverse.default(ast, {
|
|
504
|
+
// VariableDeclarator(path) {
|
|
505
|
+
// const init = path.node.init;
|
|
506
|
+
// if (
|
|
507
|
+
// init &&
|
|
508
|
+
// init.type === "CallExpression" &&
|
|
509
|
+
// init.callee.name === "useState"
|
|
510
|
+
// ) {
|
|
511
|
+
// const elements = path.node.id.elements;
|
|
512
|
+
// states.push({
|
|
513
|
+
// state: elements?.[0]?.name,
|
|
514
|
+
// setter: elements?.[1]?.name,
|
|
515
|
+
// });
|
|
516
|
+
// }
|
|
517
|
+
// },
|
|
518
|
+
// });
|
|
519
|
+
// return states;
|
|
520
|
+
// }
|
|
521
|
+
// /**
|
|
522
|
+
// * Parses JSX event handlers.
|
|
523
|
+
// */
|
|
524
|
+
// function parseEventHandlers(
|
|
525
|
+
// ast: TaskPayload<"parseEventHandlers">
|
|
526
|
+
// ): TaskResponse<TaskReturn<"parseEventHandlers">> {
|
|
527
|
+
// const handlers = [];
|
|
528
|
+
// traverse.default(ast, {
|
|
529
|
+
// JSXAttribute(path) {
|
|
530
|
+
// const name = path.node.name.name;
|
|
531
|
+
// if (!name.startsWith("on")) {
|
|
532
|
+
// return;
|
|
533
|
+
// }
|
|
534
|
+
// const expression = path.node.value?.expression;
|
|
535
|
+
// handlers.push({
|
|
536
|
+
// event: name,
|
|
537
|
+
// handler: expression?.name,
|
|
538
|
+
// });
|
|
539
|
+
// },
|
|
540
|
+
// });
|
|
541
|
+
// return handlers;
|
|
542
|
+
// }
|
|
543
|
+
/**
|
|
544
|
+
* Parses fetch and axios API calls.
|
|
545
|
+
*/
|
|
546
|
+
// function parseAPICalls(
|
|
547
|
+
// ast: TaskPayload<"parseEventHandlers">
|
|
548
|
+
// ): TaskResponse<TaskReturn<"parseAPICalls">> {
|
|
549
|
+
// const calls = [];
|
|
550
|
+
// traverse.default(ast, {
|
|
551
|
+
// CallExpression(path) {
|
|
552
|
+
// const callee = path.node.callee;
|
|
553
|
+
// if (callee.name === "fetch") {
|
|
554
|
+
// calls.push({
|
|
555
|
+
// type: "fetch",
|
|
556
|
+
// });
|
|
557
|
+
// }
|
|
558
|
+
// if (callee.object?.name === "axios") {
|
|
559
|
+
// calls.push({
|
|
560
|
+
// type: "axios",
|
|
561
|
+
// method: callee.property.name,
|
|
562
|
+
// });
|
|
563
|
+
// }
|
|
564
|
+
// },
|
|
565
|
+
// });
|
|
566
|
+
// return calls;
|
|
567
|
+
// }
|
|
568
|
+
/**
|
|
569
|
+
* Parses TypeScript interfaces and types.
|
|
570
|
+
*/
|
|
571
|
+
// function parseTypescriptTypes(
|
|
572
|
+
// ast: TaskPayload<"parseTypescriptTypes">
|
|
573
|
+
// ): TaskResponse<TaskReturn<"parseTypescriptTypes">> {
|
|
574
|
+
// const types = [];
|
|
575
|
+
// traverse.default(ast, {
|
|
576
|
+
// TSInterfaceDeclaration(path) {
|
|
577
|
+
// types.push({
|
|
578
|
+
// type: "interface",
|
|
579
|
+
// name: path.node.id.name,
|
|
580
|
+
// });
|
|
581
|
+
// },
|
|
582
|
+
// TSTypeAliasDeclaration(path) {
|
|
583
|
+
// types.push({
|
|
584
|
+
// type: "alias",
|
|
585
|
+
// name: path.node.id.name,
|
|
586
|
+
// });
|
|
587
|
+
// },
|
|
588
|
+
// });
|
|
589
|
+
// return types;
|
|
590
|
+
// }
|
|
591
|
+
/**
|
|
592
|
+
* Parses module exports.
|
|
593
|
+
*
|
|
594
|
+
* returns
|
|
595
|
+
* { success: true, exportsList: [ 'default' ] }
|
|
596
|
+
*
|
|
597
|
+
*/
|
|
598
|
+
function parseExports({ ast }) {
|
|
599
|
+
const exportsList = [];
|
|
600
|
+
traverse.default(ast, {
|
|
601
|
+
ExportNamedDeclaration(path) {
|
|
602
|
+
path.node.specifiers.forEach((specifier) => {
|
|
603
|
+
if (t.isIdentifier(specifier.exported)) {
|
|
604
|
+
exportsList.push(specifier.exported.name);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
},
|
|
608
|
+
ExportDefaultDeclaration(path) {
|
|
609
|
+
exportsList.push("default");
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
return { success: true, exportsList };
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Parses React hooks usage.
|
|
616
|
+
*/
|
|
617
|
+
function parseHooksUsage({ ast }) {
|
|
618
|
+
const hooks = [];
|
|
619
|
+
traverse.default(ast, {
|
|
620
|
+
CallExpression(path) {
|
|
621
|
+
const callee = path.node.callee;
|
|
622
|
+
if (t.isIdentifier(callee) && callee.name.startsWith("use")) {
|
|
623
|
+
hooks.push(callee.name);
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
return [...new Set(hooks)];
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Parses JSX DOM hierarchy.
|
|
631
|
+
*/
|
|
632
|
+
// function parseDOMHierarchy(
|
|
633
|
+
// ast: TaskPayload<"parseDOMHierarchy">
|
|
634
|
+
// ): TaskResponse<TaskReturn<"parseDOMHierarchy">> {
|
|
635
|
+
// function parseElement(node) {
|
|
636
|
+
// if (node.type !== "JSXElement") {
|
|
637
|
+
// return null;
|
|
638
|
+
// }
|
|
639
|
+
// return {
|
|
640
|
+
// tag: node.openingElement.name.name,
|
|
641
|
+
// children: node.children.map(parseElement).filter(Boolean),
|
|
642
|
+
// };
|
|
643
|
+
// }
|
|
644
|
+
// let tree = null;
|
|
645
|
+
// traverse.default(ast, {
|
|
646
|
+
// ReturnStatement(path) {
|
|
647
|
+
// const argument = path.node.argument;
|
|
648
|
+
// if (argument?.type === "JSXElement") {
|
|
649
|
+
// tree = parseElement(argument);
|
|
650
|
+
// }
|
|
651
|
+
// },
|
|
652
|
+
// });
|
|
653
|
+
// return tree;
|
|
654
|
+
// }
|
|
655
|
+
function parseSelector(selector) {
|
|
656
|
+
// ----------------------------------------------------------
|
|
657
|
+
// Extract attribute selectors
|
|
658
|
+
//
|
|
659
|
+
// div.card[data-id="123"][role="button"]
|
|
660
|
+
// ----------------------------------------------------------
|
|
661
|
+
const attributeRegex = /\[([^\]=]+)=['"]?([^'"\]]+)['"]?\]/g;
|
|
662
|
+
const attributes = {};
|
|
663
|
+
let match;
|
|
664
|
+
while ((match = attributeRegex.exec(selector)) !== null) {
|
|
665
|
+
const [, key, value] = match;
|
|
666
|
+
attributes[key] = value;
|
|
667
|
+
}
|
|
668
|
+
// ----------------------------------------------------------
|
|
669
|
+
// Remove attribute section
|
|
670
|
+
//
|
|
671
|
+
// div.card[data-id="123"]
|
|
672
|
+
// ↓
|
|
673
|
+
// div.card
|
|
674
|
+
// ----------------------------------------------------------
|
|
675
|
+
const selectorWithoutAttributes = selector.replace(attributeRegex, "");
|
|
676
|
+
// ----------------------------------------------------------
|
|
677
|
+
// Extract tag and classes
|
|
678
|
+
//
|
|
679
|
+
// div.card.primary
|
|
680
|
+
// ----------------------------------------------------------
|
|
681
|
+
const [tagName, ...classes] = selectorWithoutAttributes.split(".");
|
|
682
|
+
return {
|
|
683
|
+
tagName,
|
|
684
|
+
classes,
|
|
685
|
+
attributes,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
/*
|
|
689
|
+
parseJSCode returns
|
|
690
|
+
returns {
|
|
691
|
+
"imports": [
|
|
692
|
+
"react",
|
|
693
|
+
"react",
|
|
694
|
+
"../../assets/react.svg",
|
|
695
|
+
"/vite.svg",
|
|
696
|
+
"./App.css",
|
|
697
|
+
"./Header"
|
|
698
|
+
],
|
|
699
|
+
"exports": ["default"],
|
|
700
|
+
"components": [],
|
|
701
|
+
"functions": [
|
|
702
|
+
{ "name": "button", "line": [8, 10] },
|
|
703
|
+
{ "name": "App", "line": [11, 32] }
|
|
704
|
+
],
|
|
705
|
+
"variables": [{ "name": "button", "line": [8, 10] }, { "line": [12, 12] }]
|
|
706
|
+
}
|
|
707
|
+
*/
|
|
708
|
+
function parseJSCode({ code }) {
|
|
709
|
+
const ast = parse(code, {
|
|
710
|
+
sourceType: "module",
|
|
711
|
+
plugins: [
|
|
712
|
+
"jsx",
|
|
713
|
+
// "typescript",
|
|
714
|
+
// "classProperties",
|
|
715
|
+
// "topLevelAwait",
|
|
716
|
+
// "objectRestSpread"
|
|
717
|
+
],
|
|
718
|
+
});
|
|
719
|
+
const info = {
|
|
720
|
+
imports: [],
|
|
721
|
+
exports: [],
|
|
722
|
+
components: [],
|
|
723
|
+
functions: [],
|
|
724
|
+
variables: [],
|
|
725
|
+
};
|
|
726
|
+
traverse.default(ast, {
|
|
727
|
+
ImportDeclaration({ node }) {
|
|
728
|
+
info.imports.push(node.source.value);
|
|
729
|
+
},
|
|
730
|
+
ExportNamedDeclaration({ node }) {
|
|
731
|
+
if (node.declaration &&
|
|
732
|
+
"id" in node.declaration &&
|
|
733
|
+
t.isIdentifier(node.declaration.id)) {
|
|
734
|
+
info.exports.push(node.declaration.id.name);
|
|
735
|
+
}
|
|
736
|
+
},
|
|
737
|
+
ExportDefaultDeclaration({ node }) {
|
|
738
|
+
info.exports.push("default");
|
|
739
|
+
},
|
|
740
|
+
FunctionDeclaration({ node }) {
|
|
741
|
+
if (node.id?.name) {
|
|
742
|
+
info.functions.push({
|
|
743
|
+
name: node.id.name,
|
|
744
|
+
line: [node.loc.start.line, node.loc.end.line],
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
VariableDeclaration({ node }) {
|
|
749
|
+
node.declarations.forEach((decl) => {
|
|
750
|
+
const name = t.isIdentifier(decl.id) ? decl.id.name : undefined;
|
|
751
|
+
const line = [decl.loc.start.line, decl.loc.end.line];
|
|
752
|
+
// Arrow function components (already handled)
|
|
753
|
+
if (decl.init?.type === "ArrowFunctionExpression" &&
|
|
754
|
+
(decl.init.body.type === "JSXElement" ||
|
|
755
|
+
decl.init.body.type === "BlockStatement")) {
|
|
756
|
+
info.functions.push({ name, line });
|
|
757
|
+
}
|
|
758
|
+
// All variables, regardless of initializer
|
|
759
|
+
info.variables = info.variables || [];
|
|
760
|
+
info.variables.push({ name, line });
|
|
761
|
+
});
|
|
762
|
+
},
|
|
763
|
+
});
|
|
764
|
+
return info;
|
|
765
|
+
}
|
|
766
|
+
function parseLLMJsonResponse(response) {
|
|
767
|
+
try {
|
|
768
|
+
return JSON.parse(response);
|
|
769
|
+
}
|
|
770
|
+
catch {
|
|
771
|
+
try {
|
|
772
|
+
const extractedData = extractJSONFromLLM(response);
|
|
773
|
+
// CRITICAL FIX: You must explicitly return the extracted data here
|
|
774
|
+
return extractedData;
|
|
775
|
+
}
|
|
776
|
+
catch (error) {
|
|
777
|
+
throw new LoomaError(ERROR_CODES.LLM_INVALID_RESPONSE, "LLM's response in invalid JSON", {
|
|
778
|
+
response,
|
|
779
|
+
originalError: error instanceof Error ? error.message : String(error),
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function extractJSONFromLLM(llmResponse) {
|
|
785
|
+
try {
|
|
786
|
+
// 1. Clean markdown code blocks if present
|
|
787
|
+
let cleanedText = llmResponse
|
|
788
|
+
.replace(/```json/gi, "")
|
|
789
|
+
.replace(/```/g, "")
|
|
790
|
+
.trim();
|
|
791
|
+
// 2. Use regex to find the first '{' or '[' and the last '}' or ']'
|
|
792
|
+
const jsonMatch = cleanedText.match(/[\{\[]([\s\S]*?)[\}\]]/);
|
|
793
|
+
if (!jsonMatch) {
|
|
794
|
+
throw new Error("No JSON structure found in the response.");
|
|
795
|
+
}
|
|
796
|
+
// Extract the matched substring including the outer brackets
|
|
797
|
+
const jsonString = jsonMatch[0];
|
|
798
|
+
// 3. Attempt to parse the extracted string
|
|
799
|
+
return JSON.parse(jsonString);
|
|
800
|
+
}
|
|
801
|
+
catch (firstError) {
|
|
802
|
+
// 4. Fallback: Heavy cleaning for common LLM syntax errors
|
|
803
|
+
try {
|
|
804
|
+
const sanitized = repairLLMJsonString(llmResponse);
|
|
805
|
+
return JSON.parse(sanitized);
|
|
806
|
+
}
|
|
807
|
+
catch (secondError) {
|
|
808
|
+
throw new Error(`Failed to parse LLM JSON. Original error: ${firstError.message}`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function repairLLMJsonString(text) {
|
|
813
|
+
// Isolate the text between first '{' and last '}'
|
|
814
|
+
const start = text.indexOf("{");
|
|
815
|
+
const end = text.lastIndexOf("}");
|
|
816
|
+
if (start === -1 || end === -1)
|
|
817
|
+
throw new Error("Brackets missing");
|
|
818
|
+
let jsonStr = text.substring(start, end + 1);
|
|
819
|
+
// Strip trailing commas before closing braces/brackets
|
|
820
|
+
jsonStr = jsonStr.replace(/,\s*([\]\}])/g, "$1");
|
|
821
|
+
return jsonStr;
|
|
822
|
+
}
|
|
823
|
+
export default {
|
|
824
|
+
parseAST,
|
|
825
|
+
parseCSS,
|
|
826
|
+
parseRoutes,
|
|
827
|
+
parseComponentDependencies,
|
|
828
|
+
parseProps,
|
|
829
|
+
// parseTypescriptTypes,
|
|
830
|
+
parseExports,
|
|
831
|
+
parseJSCode,
|
|
832
|
+
parseHooksUsage,
|
|
833
|
+
parseSelector,
|
|
834
|
+
// parseStateUsage,
|
|
835
|
+
// parseEventHandlers,
|
|
836
|
+
// parseAPICalls,
|
|
837
|
+
// parseDOMHierarchy,
|
|
838
|
+
parseLLMJsonResponse,
|
|
839
|
+
};
|
|
840
|
+
//# sourceMappingURL=parsers.js.map
|