@lowgular/code-graph 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/bin/cli.js +2458 -5
  2. package/lib.js +2430 -0
  3. package/package.json +3 -2
  4. package/code-graph-v2/code.graph.js +0 -37
  5. package/code-graph-v2/config-from-query.js +0 -131
  6. package/code-graph-v2/extractors/extractor.js +0 -27
  7. package/code-graph-v2/extractors/index.js +0 -1
  8. package/code-graph-v2/graph-builder/code-graph.builder.js +0 -49
  9. package/code-graph-v2/graph-builder/index.js +0 -1
  10. package/code-graph-v2/graph-builder/node.processor.js +0 -22
  11. package/code-graph-v2/graph-builder/relationship.processor.js +0 -55
  12. package/code-graph-v2/graph-builder/type.processor.js +0 -21
  13. package/code-graph-v2/index.js +0 -4
  14. package/code-graph-v2/tools/build-code-graph.tool.js +0 -19
  15. package/code-graph-v2/utils.js +0 -34
  16. package/codegular/index.js +0 -5
  17. package/codegular/node.js +0 -71
  18. package/codegular/program.js +0 -100
  19. package/codegular/string.js +0 -121
  20. package/codegular/type-checker.js +0 -133
  21. package/codegular/type.js +0 -356
  22. package/codegular/utils.js +0 -335
  23. package/cypher/index.js +0 -1
  24. package/cypher/lib/executor/condition-evaluator.js +0 -135
  25. package/cypher/lib/executor/executor.js +0 -60
  26. package/cypher/lib/executor/graph.js +0 -0
  27. package/cypher/lib/executor/match-engine.js +0 -130
  28. package/cypher/lib/executor/pattern-matcher.js +0 -86
  29. package/cypher/lib/executor/relationship-navigator.js +0 -41
  30. package/cypher/lib/executor/result-formatter.js +0 -149
  31. package/cypher/lib/executor/traverse-engine.js +0 -141
  32. package/cypher/lib/executor/utils.js +0 -14
  33. package/cypher/lib/graph.stub.js +0 -38
  34. package/cypher/lib/index.js +0 -32
  35. package/cypher/lib/lexer.js +0 -376
  36. package/cypher/lib/parser.js +0 -586
  37. package/cypher/lib/validator/query-validator.js +0 -75
  38. package/cypher/lib/validator/supported-features.config.js +0 -83
  39. package/cypher/lib/validator/unsupported-features.config.js +0 -124
  40. package/cypher-cli.js +0 -41
  41. package/infra/code-graph.js +0 -147
  42. package/main.js +0 -0
  43. package/resources-cli.js +0 -75
  44. package/run-cli.js +0 -43
package/lib.js ADDED
@@ -0,0 +1,2430 @@
1
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/config-from-query.ts
2
+ var STANDARD_NODE_PROPERTIES = {
3
+ firstIdentifier: `helpers.getDescendantsBy(args.current, (node) => node.kind === args.ts.SyntaxKind.Identifier)[0]?.getText() ?? null`,
4
+ initializer: `args.current.initializer ? args.current.initializer?.getText() : null`,
5
+ name: `args.current.name?.getText() ?? null`,
6
+ filePath: `args.sourceFile.fileName`,
7
+ text: `args.current.getText()`,
8
+ type: `(() => { try { const t = args.typeChecker.getTypeAtLocation(args.current); return t ? args.typeChecker.typeToString(t) : null; } catch { return null; } })()`
9
+ };
10
+ var SUPPORTED_RELATIONSHIPS = {
11
+ // --- Traversal relationships ---
12
+ HAS_DESCENDANTS: (labels) => labels.length > 0 ? `helpers.getDescendantsBy(args.current, (node) => ${buildKindFilter(labels)})` : `helpers.getDescendantsBy(args.current, () => true)`,
13
+ HAS_ANCESTORS: (labels) => labels.length > 0 ? `helpers.getAncestorsBy(args.current, (node) => ${buildKindFilter(labels)})` : `helpers.getAncestorsBy(args.current, () => true)`,
14
+ HAS_TYPE: () => `helpers.getType(args.current)`,
15
+ HAS_NAME: () => `args.current.name ? [args.current.name] : []`,
16
+ // --- Direct AST property relationships ---
17
+ HAS_INITIALIZER: () => `args.current.initializer ? [args.current.initializer] : []`,
18
+ HAS_PARAMETERS: () => `args.current.parameters ? [...args.current.parameters] : []`,
19
+ HAS_EXPRESSION: () => `args.current.expression ? [args.current.expression] : []`,
20
+ HAS_TYPE_ANNOTATION: () => `args.current.type ? [args.current.type] : []`,
21
+ HAS_BODY: () => `args.current.body ? [args.current.body] : []`,
22
+ HAS_MEMBERS: () => `args.current.members ? [...args.current.members] : []`,
23
+ HAS_PROPERTIES: () => `args.current.properties ? [...args.current.properties] : []`,
24
+ HAS_ARGUMENTS: () => `args.current.arguments ? [...args.current.arguments] : []`,
25
+ HAS_DECORATORS: () => `args.ts.getDecorators(args.current) || []`,
26
+ // --- Heritage relationships ---
27
+ EXTENDS: () => `(() => { const c = (args.current.heritageClauses || []).find(c => c.token === args.ts.SyntaxKind.ExtendsKeyword); return c ? [...c.types] : []; })()`,
28
+ IMPLEMENTS: () => `(() => { const c = (args.current.heritageClauses || []).find(c => c.token === args.ts.SyntaxKind.ImplementsKeyword); return c ? [...c.types] : []; })()`
29
+ };
30
+ var buildConfigFromQuery = (query2) => {
31
+ const nodeExtractors = {};
32
+ const matchStatements = query2.statements.filter(
33
+ (s) => s.type === "MatchStatement"
34
+ );
35
+ const variableToLabels = {};
36
+ for (const match of matchStatements) {
37
+ for (const pattern of match.patterns) {
38
+ if (pattern.variable && pattern.labels.length > 0) {
39
+ variableToLabels[pattern.variable] = pattern.labels;
40
+ }
41
+ }
42
+ }
43
+ for (const match of matchStatements) {
44
+ buildExtractorsFromMatch(match, nodeExtractors, variableToLabels);
45
+ }
46
+ return { nodeExtractors };
47
+ };
48
+ var resolveLabels = (pattern, variableToLabels) => {
49
+ if (pattern.labels.length > 0)
50
+ return pattern.labels;
51
+ if (pattern.variable && variableToLabels[pattern.variable]) {
52
+ return variableToLabels[pattern.variable];
53
+ }
54
+ return [];
55
+ };
56
+ var buildExtractorsFromMatch = (match, rootNodeExtractors, variableToLabels) => {
57
+ const { patterns, relationships } = match;
58
+ if (patterns.length === 0)
59
+ return;
60
+ const rootPattern = patterns[0];
61
+ const rootLabels = resolveLabels(rootPattern, variableToLabels);
62
+ for (const label of rootLabels) {
63
+ rootNodeExtractors[label] ??= {
64
+ properties: { ...STANDARD_NODE_PROPERTIES }
65
+ };
66
+ }
67
+ if (!relationships || relationships.length === 0)
68
+ return;
69
+ let currentExtractors = rootNodeExtractors;
70
+ for (let i = 0; i < relationships.length; i++) {
71
+ const rel = relationships[i];
72
+ const sourcePattern = patterns[i];
73
+ const targetPattern = patterns[i + 1];
74
+ if (!targetPattern)
75
+ break;
76
+ const edgeType = resolveEdgeType(rel.edgeType);
77
+ const codeGenerator = getCodeGenerator(edgeType);
78
+ const sourceLabels = resolveLabels(sourcePattern, variableToLabels);
79
+ const targetLabels = resolveLabels(targetPattern, variableToLabels);
80
+ const targetNodeExtractors = {};
81
+ if (targetLabels.length === 0) {
82
+ targetNodeExtractors["*"] = {
83
+ properties: { ...STANDARD_NODE_PROPERTIES }
84
+ };
85
+ } else {
86
+ for (const label of targetLabels) {
87
+ targetNodeExtractors[label] = {
88
+ properties: { ...STANDARD_NODE_PROPERTIES }
89
+ };
90
+ }
91
+ }
92
+ for (const sourceLabel of sourceLabels) {
93
+ currentExtractors[sourceLabel] ??= {
94
+ properties: { ...STANDARD_NODE_PROPERTIES }
95
+ };
96
+ currentExtractors[sourceLabel].relationships ??= {};
97
+ currentExtractors[sourceLabel].relationships[edgeType] = {
98
+ extractorCode: codeGenerator(targetLabels),
99
+ nodeExtractors: targetNodeExtractors
100
+ };
101
+ }
102
+ currentExtractors = targetNodeExtractors;
103
+ }
104
+ };
105
+ var resolveEdgeType = (edgeType) => {
106
+ if (!edgeType) {
107
+ throw new Error(
108
+ `Relationship type is required in auto-config mode. Supported: ${Object.keys(SUPPORTED_RELATIONSHIPS).join(", ")}`
109
+ );
110
+ }
111
+ if (Array.isArray(edgeType)) {
112
+ throw new Error(
113
+ `Union relationship types are not supported in auto-config. Got: ${edgeType.join("|")}`
114
+ );
115
+ }
116
+ return edgeType;
117
+ };
118
+ var getCodeGenerator = (edgeType) => {
119
+ const generator = SUPPORTED_RELATIONSHIPS[edgeType];
120
+ if (!generator) {
121
+ throw new Error(
122
+ `Unsupported relationship type: "${edgeType}". Auto-config supports: ${Object.keys(SUPPORTED_RELATIONSHIPS).join(", ")}`
123
+ );
124
+ }
125
+ return generator;
126
+ };
127
+ var buildKindFilter = (labels) => {
128
+ return labels.map((l) => `node.kind === args.ts.SyntaxKind.${l}`).join(" || ");
129
+ };
130
+
131
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/extractors/extractor.ts
132
+ function executeExtractor(code, context) {
133
+ try {
134
+ const fn = new Function(
135
+ ...Object.keys(context),
136
+ `
137
+ return ${code}
138
+ `
139
+ );
140
+ return fn(...Object.values(context));
141
+ } catch (error) {
142
+ throw new Error(`Extractor execution failed: ${error.message}`);
143
+ }
144
+ }
145
+ function extractNodeProperties(context, propertyExectorMap = {}) {
146
+ const properties = {};
147
+ for (const [propertyName, extractorCode] of Object.entries(
148
+ propertyExectorMap
149
+ )) {
150
+ const value = executeExtractor(extractorCode, context);
151
+ properties[propertyName] = value;
152
+ }
153
+ return properties;
154
+ }
155
+
156
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/graph-builder/code-graph.builder.ts
157
+ import * as ts7 from "typescript";
158
+
159
+ // libs/cli/code-graph-core/src/lib/codegular/node.ts
160
+ import * as ts3 from "typescript";
161
+
162
+ // libs/cli/code-graph-core/src/lib/codegular/string.ts
163
+ import * as ts2 from "typescript";
164
+
165
+ // libs/cli/code-graph-core/src/lib/codegular/utils.ts
166
+ import * as ts from "typescript";
167
+ var { forEachChild: forEachChild2, SyntaxKind } = ts;
168
+ function getAncestorsBy(node, predicate) {
169
+ let currentNode = node;
170
+ const nodes = [];
171
+ while (currentNode) {
172
+ if (predicate(currentNode)) {
173
+ nodes.push(currentNode);
174
+ }
175
+ const parent = currentNode?.parent;
176
+ if (!parent) {
177
+ break;
178
+ }
179
+ currentNode = parent;
180
+ }
181
+ return nodes;
182
+ }
183
+ function getDescendantsBy(node, predicate) {
184
+ let foundElements = [];
185
+ let queue = [node];
186
+ while (queue.length > 0) {
187
+ const currentNode = queue.shift();
188
+ const children = Array.from(
189
+ currentNode?.getChildren(node.getSourceFile()) || []
190
+ );
191
+ foundElements = [
192
+ ...foundElements,
193
+ ...children.filter((node2) => predicate(node2))
194
+ ];
195
+ queue = [...queue, ...children];
196
+ }
197
+ return foundElements;
198
+ }
199
+ function getDescendantsByKind(node, kind) {
200
+ return getDescendantsBy(node, (node2) => node2.kind === kind);
201
+ }
202
+ var getSolutionSourceFiles = (program) => {
203
+ const allSourceFiles = program.getSourceFiles();
204
+ const sourceFiles = allSourceFiles.filter((sourceFile) => {
205
+ const fileName = sourceFile.fileName;
206
+ if (fileName.includes("node_modules")) {
207
+ return false;
208
+ }
209
+ if (fileName.endsWith(".d.ts")) {
210
+ return false;
211
+ }
212
+ return true;
213
+ });
214
+ return sourceFiles;
215
+ };
216
+
217
+ // libs/cli/code-graph-core/src/lib/codegular/string.ts
218
+ var { SyntaxKind: SyntaxKind3 } = ts2;
219
+
220
+ // libs/cli/code-graph-core/src/lib/codegular/node.ts
221
+ var { forEachChild: forEachChild3, SyntaxKind: SyntaxKind4 } = ts3;
222
+
223
+ // libs/cli/code-graph-core/src/lib/codegular/program.ts
224
+ import * as path from "path";
225
+ import * as ts4 from "typescript";
226
+ var createProgramFromFileSystem = (fileSystem) => {
227
+ const compilerOptions = {
228
+ target: ts4.ScriptTarget.ES2020,
229
+ module: ts4.ModuleKind.CommonJS,
230
+ moduleResolution: ts4.ModuleResolutionKind.NodeJs,
231
+ allowSyntheticDefaultImports: true,
232
+ esModuleInterop: true,
233
+ skipLibCheck: true,
234
+ lib: ["ES2020", "DOM"]
235
+ };
236
+ const compilerHost = ts4.createCompilerHost(compilerOptions);
237
+ const originalGetSourceFile = compilerHost.getSourceFile;
238
+ compilerHost.getSourceFile = (fileName, languageVersion) => {
239
+ if (fileSystem[fileName]) {
240
+ return ts4.createSourceFile(
241
+ fileName,
242
+ fileSystem[fileName],
243
+ languageVersion,
244
+ true
245
+ );
246
+ }
247
+ return originalGetSourceFile(fileName, languageVersion);
248
+ };
249
+ const fileNames = Object.keys(fileSystem);
250
+ return ts4.createProgram(fileNames, compilerOptions, compilerHost);
251
+ };
252
+ var createProgramFromTsConfig = (configPath) => {
253
+ const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile);
254
+ if (configFile.error) {
255
+ const errorMessage = ts4.formatDiagnostic(configFile.error, {
256
+ getCurrentDirectory: () => process.cwd(),
257
+ getNewLine: () => "\n",
258
+ getCanonicalFileName: (fileName) => fileName
259
+ });
260
+ throw new Error(`Error reading tsconfig at ${configPath}: ${errorMessage}`);
261
+ }
262
+ const parsedConfig = ts4.parseJsonConfigFileContent(
263
+ configFile.config,
264
+ ts4.sys,
265
+ path.dirname(configPath)
266
+ );
267
+ return ts4.createProgram(
268
+ parsedConfig.fileNames,
269
+ parsedConfig.options,
270
+ ts4.createCompilerHost(parsedConfig.options)
271
+ );
272
+ };
273
+
274
+ // libs/cli/code-graph-core/src/lib/codegular/type-checker.ts
275
+ import * as ts5 from "typescript";
276
+
277
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/code.graph.ts
278
+ var addNode = (graph, node) => {
279
+ graph.nodesById[node.id] = node;
280
+ return graph;
281
+ };
282
+ var getEdgeKey = (edge) => {
283
+ return `${edge.source}|${edge.target}|${edge.type}`;
284
+ };
285
+ var addEdge = (graph, edge) => {
286
+ const edgeKey = getEdgeKey(edge);
287
+ const sourceEdges = graph.edgesBySource[edge.source];
288
+ if (sourceEdges) {
289
+ const sourceEdgeKeys = new Set(sourceEdges.map(getEdgeKey));
290
+ if (sourceEdgeKeys.has(edgeKey)) {
291
+ return graph;
292
+ }
293
+ }
294
+ const targetEdges = graph.edgesByTarget[edge.target];
295
+ if (targetEdges) {
296
+ const targetEdgeKeys = new Set(targetEdges.map(getEdgeKey));
297
+ if (targetEdgeKeys.has(edgeKey)) {
298
+ return graph;
299
+ }
300
+ }
301
+ if (!graph.edgesBySource[edge.source]) {
302
+ graph.edgesBySource[edge.source] = [];
303
+ }
304
+ graph.edgesBySource[edge.source].push(edge);
305
+ if (!graph.edgesByTarget[edge.target]) {
306
+ graph.edgesByTarget[edge.target] = [];
307
+ }
308
+ graph.edgesByTarget[edge.target].push(edge);
309
+ return graph;
310
+ };
311
+
312
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/utils.ts
313
+ import * as ts6 from "typescript";
314
+ var getSyntaxKindName = (kind) => {
315
+ return ts6.SyntaxKind[kind];
316
+ };
317
+ var generateNodeId = (n) => {
318
+ const s = n.getSourceFile();
319
+ const start = s.getLineAndCharacterOfPosition(n.getStart(s));
320
+ const end = s.getLineAndCharacterOfPosition(n.getEnd());
321
+ return `${s.fileName}.${start.line}.${start.character}.${end.line}.${end.character}`;
322
+ };
323
+ var extractFromNodeId = (nodeId) => {
324
+ const parts = nodeId.split(".");
325
+ const endCharacter = parts.pop();
326
+ const endLine = parts.pop();
327
+ const startCharacter = parts.pop();
328
+ const startLine = parts.pop();
329
+ const filePath = parts.join(".");
330
+ return {
331
+ filePath,
332
+ start: {
333
+ line: parseInt(startLine),
334
+ character: parseInt(startCharacter)
335
+ },
336
+ end: {
337
+ line: parseInt(endLine),
338
+ character: parseInt(endCharacter)
339
+ }
340
+ };
341
+ };
342
+
343
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/graph-builder/type.processor.ts
344
+ var processType = (context, nodeExtractors, codeGraph) => {
345
+ const nodeId = context.args.typeChecker.typeToString(
346
+ context.args.current
347
+ );
348
+ const kindName = "Type";
349
+ const nodeConfig = nodeExtractors[kindName];
350
+ const graphNode = {
351
+ ...nodeConfig && nodeConfig.properties ? extractNodeProperties(context, nodeConfig.properties) : {},
352
+ id: nodeId,
353
+ labels: [kindName]
354
+ };
355
+ addNode(codeGraph, graphNode);
356
+ processRelationships(nodeId, context, nodeConfig, codeGraph);
357
+ return graphNode;
358
+ };
359
+
360
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/graph-builder/relationship.processor.ts
361
+ function isTsNode(obj) {
362
+ return obj && "kind" in obj && typeof obj.kind === "number";
363
+ }
364
+ function isTsType(obj) {
365
+ return obj && "flags" in obj && typeof obj.flags === "number" && !("kind" in obj);
366
+ }
367
+ var processRelationships = (parentId, context, nodeConfig, codeGraph) => {
368
+ if (nodeConfig?.relationships) {
369
+ for (const relationshipName of Object.keys(nodeConfig.relationships)) {
370
+ const relationshipExtractorFnCode = nodeConfig.relationships[relationshipName].extractorCode;
371
+ const relationshipNodeExtractors = nodeConfig.relationships[relationshipName].nodeExtractors;
372
+ Object.keys(relationshipNodeExtractors).forEach((label) => {
373
+ const children = executeExtractor(relationshipExtractorFnCode, context);
374
+ children.forEach(
375
+ (child) => {
376
+ if (isTsType(child)) {
377
+ const childNode = processType(
378
+ { ...context, args: { ...context.args, current: child } },
379
+ relationshipNodeExtractors,
380
+ codeGraph
381
+ );
382
+ addEdge(codeGraph, {
383
+ source: parentId,
384
+ target: childNode.id,
385
+ type: relationshipName
386
+ });
387
+ } else if (isTsNode(child)) {
388
+ const childNode = processNode(
389
+ { ...context, args: { ...context.args, current: child } },
390
+ relationshipNodeExtractors,
391
+ codeGraph
392
+ );
393
+ addEdge(codeGraph, {
394
+ source: parentId,
395
+ target: childNode.id,
396
+ type: relationshipName
397
+ });
398
+ } else {
399
+ throw new Error(
400
+ `The code provided: ${relationshipExtractorFnCode} returned an invalid child: ${child}. We support only ts.Node and ts.Type array returns from executed code`
401
+ );
402
+ }
403
+ }
404
+ );
405
+ });
406
+ }
407
+ }
408
+ };
409
+
410
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/graph-builder/node.processor.ts
411
+ var processNode = (context, nodeExtractors, codeGraph) => {
412
+ const nodeId = generateNodeId(context.args.current);
413
+ const kindName = getSyntaxKindName(context.args.current.kind);
414
+ const nodeConfig = nodeExtractors[kindName] ?? nodeExtractors["*"];
415
+ const graphNode = {
416
+ ...nodeConfig && nodeConfig.properties ? extractNodeProperties(context, nodeConfig.properties) : {},
417
+ id: nodeId,
418
+ labels: [kindName]
419
+ };
420
+ addNode(codeGraph, graphNode);
421
+ processRelationships(nodeId, context, nodeConfig, codeGraph);
422
+ return graphNode;
423
+ };
424
+
425
+ // libs/cli/code-graph-core/src/lib/code-graph-v2/graph-builder/code-graph.builder.ts
426
+ var buildCodeGraph = (program, config) => {
427
+ const codeGraph = {
428
+ nodesById: {},
429
+ edgesBySource: {},
430
+ edgesByTarget: {}
431
+ };
432
+ const sourceFiles = getSolutionSourceFiles(program);
433
+ sourceFiles.forEach((sourceFile) => {
434
+ Object.keys(config.nodeExtractors).forEach((kindName) => {
435
+ const nodes = getDescendantsByKind(sourceFile, ts7.SyntaxKind[kindName]);
436
+ nodes.forEach((node) => {
437
+ const extractorContext = {
438
+ args: {
439
+ current: node,
440
+ sourceFile,
441
+ typeChecker: program.getTypeChecker(),
442
+ ts: ts7
443
+ },
444
+ helpers: {
445
+ getDescendantsBy,
446
+ getAncestorsBy,
447
+ getType: (node2) => []
448
+ // TODO: add type extraction
449
+ // getType(
450
+ // node,
451
+ // program.getTypeChecker(),
452
+ // new TypeResolutionTracker(),
453
+ // ),
454
+ // getDeclarations: (node: ts.Type | ts.Node) =>
455
+ // getDeclarations(node, context.typeChecker),
456
+ }
457
+ };
458
+ processNode(extractorContext, config.nodeExtractors, codeGraph);
459
+ });
460
+ });
461
+ });
462
+ return codeGraph;
463
+ };
464
+
465
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/condition-evaluator.ts
466
+ var ConditionEvaluator = class {
467
+ // @TODO: apply where at match level
468
+ /**
469
+ * Apply WHERE clause filtering to matches
470
+ */
471
+ applyWhereClauseIfNeeded(matches, where) {
472
+ return matches.filter((match) => {
473
+ const conditionResults = where.conditions.map(
474
+ (condition) => this.evaluateCondition(match, condition)
475
+ );
476
+ const logic = where.logic || "AND";
477
+ if (logic === "AND") {
478
+ return conditionResults.every((result) => result === true);
479
+ } else {
480
+ return conditionResults.some((result) => result === true);
481
+ }
482
+ });
483
+ }
484
+ // @fixme move to utils
485
+ /**
486
+ * Escape special regex characters in a string
487
+ */
488
+ escapeRegex(str) {
489
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
490
+ }
491
+ /**
492
+ * Evaluate a single WHERE condition
493
+ * Supports flat properties only (no nesting, like Neo4j Cypher)
494
+ * Handles null values from optional matches (null values fail the condition)
495
+ */
496
+ evaluateCondition(match, condition) {
497
+ const value = match[condition.variable];
498
+ if ((condition.operator === "IS NULL" || condition.operator === "IS NOT NULL") && !condition.property) {
499
+ const isNull = value === null || value === void 0;
500
+ return condition.operator === "IS NULL" ? isNull : !isNull;
501
+ }
502
+ if (!value || value === null) {
503
+ return false;
504
+ }
505
+ let baseObject;
506
+ if ("id" in value) {
507
+ baseObject = value;
508
+ } else if ("type" in value && "source" in value) {
509
+ baseObject = value;
510
+ } else {
511
+ return false;
512
+ }
513
+ let actualValue;
514
+ if (!condition.property) {
515
+ actualValue = baseObject;
516
+ } else {
517
+ actualValue = baseObject[condition.property];
518
+ if (actualValue === void 0) {
519
+ if (condition.operator === "IS NULL" || condition.operator === "IS NOT NULL") {
520
+ actualValue = void 0;
521
+ } else {
522
+ return false;
523
+ }
524
+ }
525
+ }
526
+ const conditionValue = condition.value;
527
+ switch (condition.operator) {
528
+ case "=":
529
+ return actualValue === conditionValue;
530
+ case "!=":
531
+ return actualValue !== conditionValue;
532
+ case ">":
533
+ return actualValue > conditionValue;
534
+ case "<":
535
+ return actualValue < conditionValue;
536
+ case ">=":
537
+ return actualValue >= conditionValue;
538
+ case "<=":
539
+ return actualValue <= conditionValue;
540
+ case "IS NULL":
541
+ return actualValue === null || actualValue === void 0;
542
+ case "IS NOT NULL":
543
+ return actualValue !== null && actualValue !== void 0;
544
+ case "IN":
545
+ if (Array.isArray(conditionValue)) {
546
+ if (conditionValue.length > 0 && typeof conditionValue[0] === "object" && conditionValue[0] !== null) {
547
+ const extractedValues = conditionValue.map((item) => item?.name).filter((name) => name !== void 0);
548
+ return extractedValues.includes(actualValue);
549
+ }
550
+ return conditionValue.includes(actualValue);
551
+ }
552
+ return false;
553
+ case "NOT IN":
554
+ if (Array.isArray(conditionValue)) {
555
+ if (conditionValue.length > 0 && typeof conditionValue[0] === "object" && conditionValue[0] !== null) {
556
+ const extractedValues = conditionValue.map((item) => item?.name).filter((name) => name !== void 0);
557
+ return !extractedValues.includes(actualValue);
558
+ }
559
+ return !conditionValue.includes(actualValue);
560
+ }
561
+ return true;
562
+ case "STARTS WITH":
563
+ return this.testRegex(
564
+ new RegExp(`^${this.escapeRegex(conditionValue)}`),
565
+ actualValue
566
+ );
567
+ case "ENDS WITH":
568
+ return this.testRegex(
569
+ new RegExp(`${this.escapeRegex(conditionValue)}$`),
570
+ actualValue
571
+ );
572
+ case "CONTAINS":
573
+ return this.testRegex(
574
+ new RegExp(this.escapeRegex(conditionValue)),
575
+ actualValue
576
+ );
577
+ case "=~":
578
+ return this.testRegex(new RegExp(conditionValue), actualValue);
579
+ default:
580
+ throw new Error(`Unsupported operator: ${condition.operator}`);
581
+ }
582
+ }
583
+ testRegex(regexp, actualValue) {
584
+ if (actualValue === null || actualValue === void 0) {
585
+ return false;
586
+ }
587
+ if (typeof actualValue !== "string" && typeof actualValue !== "number" && typeof actualValue !== "boolean") {
588
+ return false;
589
+ }
590
+ const regexStr = typeof actualValue === "string" ? actualValue : String(actualValue);
591
+ try {
592
+ return regexp.test(regexStr);
593
+ } catch {
594
+ return false;
595
+ }
596
+ }
597
+ };
598
+
599
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/utils.ts
600
+ var Annonymizer = class {
601
+ constructor() {
602
+ this.anonymousVariablePrefix = "annonymous_";
603
+ }
604
+ generate(node) {
605
+ return this.anonymousVariablePrefix + node.id;
606
+ }
607
+ isAnonymous(variable) {
608
+ return variable.startsWith(this.anonymousVariablePrefix);
609
+ }
610
+ };
611
+
612
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/match-engine.ts
613
+ var MatchEngine = class {
614
+ constructor(patternMatcher, traverseEngine) {
615
+ this.patternMatcher = patternMatcher;
616
+ this.traverseEngine = traverseEngine;
617
+ this.annonymizer = new Annonymizer();
618
+ }
619
+ processMatchStatement(graph, match, existingResults = []) {
620
+ if (match.patterns.length === 0)
621
+ return existingResults;
622
+ const starting = match.patterns[0];
623
+ const allResults = [];
624
+ this.patternMatcher.filterMatchingNodes(graph, starting).forEach((node) => {
625
+ const matches = this.traverseEngine.traverseRelationships(
626
+ graph,
627
+ node,
628
+ starting.variable || this.annonymizer.generate(node),
629
+ match.patterns.slice(1),
630
+ match.relationships
631
+ );
632
+ allResults.push(...matches);
633
+ });
634
+ if (existingResults.length > 0) {
635
+ if (match.optional) {
636
+ return this.mergeOptionalResults(existingResults, allResults, match);
637
+ } else {
638
+ return this.mergeResults(existingResults, allResults);
639
+ }
640
+ }
641
+ return allResults;
642
+ }
643
+ mergeResults(existingResults, newResults) {
644
+ if (existingResults.length === 0) {
645
+ return newResults;
646
+ }
647
+ const mergedResults = [];
648
+ for (const existingResult of existingResults) {
649
+ for (const newResult of newResults) {
650
+ if (this.canMergeResult(existingResult, newResult)) {
651
+ mergedResults.push({ ...existingResult, ...newResult });
652
+ }
653
+ }
654
+ }
655
+ return mergedResults;
656
+ }
657
+ /**
658
+ * Merge optional match results with existing results (left outer join)
659
+ * - Keeps all existing results
660
+ * - Merges optional results where variables overlap
661
+ * - Adds nulls for optional variables when no match is found
662
+ */
663
+ mergeOptionalResults(existingResults, optionalResults, match) {
664
+ if (existingResults.length === 0) {
665
+ return optionalResults;
666
+ }
667
+ const optionalVarNames = /* @__PURE__ */ new Set();
668
+ for (const pattern of match.patterns) {
669
+ if (pattern.variable) {
670
+ optionalVarNames.add(pattern.variable);
671
+ }
672
+ }
673
+ if (match.relationships) {
674
+ for (const rel of match.relationships) {
675
+ if (rel.variable) {
676
+ optionalVarNames.add(rel.variable);
677
+ }
678
+ }
679
+ }
680
+ if (optionalResults.length === 0) {
681
+ return existingResults.map((result) => {
682
+ const resultWithNulls = { ...result };
683
+ for (const varName of optionalVarNames) {
684
+ if (!(varName in resultWithNulls)) {
685
+ resultWithNulls[varName] = null;
686
+ }
687
+ }
688
+ return resultWithNulls;
689
+ });
690
+ }
691
+ for (const optionalResult of optionalResults) {
692
+ for (const key in optionalResult) {
693
+ optionalVarNames.add(key);
694
+ }
695
+ }
696
+ const mergedResults = [];
697
+ for (const existingResult of existingResults) {
698
+ let hasMatch = false;
699
+ for (const optionalResult of optionalResults) {
700
+ if (this.canMergeResult(existingResult, optionalResult)) {
701
+ mergedResults.push({ ...existingResult, ...optionalResult });
702
+ hasMatch = true;
703
+ }
704
+ }
705
+ if (!hasMatch) {
706
+ const resultWithNulls = { ...existingResult };
707
+ for (const varName of optionalVarNames) {
708
+ if (!(varName in resultWithNulls)) {
709
+ resultWithNulls[varName] = null;
710
+ }
711
+ }
712
+ mergedResults.push(resultWithNulls);
713
+ }
714
+ }
715
+ return mergedResults;
716
+ }
717
+ /**
718
+ * Check if additionalResult can be merged with existingResult
719
+ * Returns true if shared variables match (same node/edge) or if there are no conflicts
720
+ * Does NOT mutate any parameters
721
+ */
722
+ canMergeResult(additionalResult, existingResult) {
723
+ for (const key in additionalResult) {
724
+ if (key in existingResult) {
725
+ const existing = existingResult[key];
726
+ const additional = additionalResult[key];
727
+ if (existing !== null && additional !== null) {
728
+ if ("id" in existing && "id" in additional && existing.id !== additional.id) {
729
+ return false;
730
+ }
731
+ } else if (existing !== additional) {
732
+ return false;
733
+ }
734
+ }
735
+ }
736
+ return true;
737
+ }
738
+ };
739
+
740
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/pattern-matcher.ts
741
+ var PatternMatcher = class {
742
+ /**
743
+ * Check if an edge type matches the relationship pattern's edge type(s)
744
+ * Supports both single edge type (string) and multiple edge types (array)
745
+ */
746
+ matchesEdgeType(edgeType, patternEdgeType) {
747
+ if (!patternEdgeType) {
748
+ return true;
749
+ }
750
+ if (typeof patternEdgeType === "string") {
751
+ return edgeType === patternEdgeType;
752
+ }
753
+ return patternEdgeType.includes(edgeType);
754
+ }
755
+ /**
756
+ * Deep equality check for arrays
757
+ */
758
+ arraysEqual(a, b) {
759
+ if (a.length !== b.length) {
760
+ return false;
761
+ }
762
+ for (let i = 0; i < a.length; i++) {
763
+ if (Array.isArray(a[i]) && Array.isArray(b[i])) {
764
+ if (!this.arraysEqual(a[i], b[i])) {
765
+ return false;
766
+ }
767
+ } else if (a[i] !== b[i]) {
768
+ return false;
769
+ }
770
+ }
771
+ return true;
772
+ }
773
+ /**
774
+ * Check if two values are equal (handles arrays with deep equality)
775
+ */
776
+ valuesEqual(expected, actual) {
777
+ if (Array.isArray(expected) && Array.isArray(actual)) {
778
+ return this.arraysEqual(expected, actual);
779
+ }
780
+ return expected === actual;
781
+ }
782
+ /**
783
+ * Check if a node matches a pattern (labels and properties)
784
+ */
785
+ doesNodeMatchPattern(node, pattern) {
786
+ if (pattern.labels.length > 0) {
787
+ const labelOperator = pattern.labelOperator || "AND";
788
+ if (labelOperator === "AND") {
789
+ if (!pattern.labels.every((label) => node.labels.includes(label))) {
790
+ return false;
791
+ }
792
+ } else {
793
+ if (!pattern.labels.some((label) => node.labels.includes(label))) {
794
+ return false;
795
+ }
796
+ }
797
+ }
798
+ if (pattern.properties) {
799
+ const notFound = Object.entries(pattern.properties).find(
800
+ ([key, expectedValue]) => !this.valuesEqual(expectedValue, node[key])
801
+ ) !== void 0;
802
+ if (notFound) {
803
+ return false;
804
+ }
805
+ }
806
+ return true;
807
+ }
808
+ findMatchingNodes(graph, pattern) {
809
+ return Object.values(graph.nodesById).find(
810
+ (node) => this.doesNodeMatchPattern(node, pattern)
811
+ );
812
+ }
813
+ /**
814
+ * Match a single node pattern
815
+ * Finds all nodes in the graph that match the given pattern
816
+ * Reuses matchesPattern to avoid code duplication
817
+ */
818
+ filterMatchingNodes(graph, pattern) {
819
+ return Object.values(graph.nodesById).filter(
820
+ (node) => this.doesNodeMatchPattern(node, pattern)
821
+ );
822
+ }
823
+ };
824
+
825
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/relationship-navigator.ts
826
+ var RelationshipNavigator = class {
827
+ constructor(patternMatcher) {
828
+ this.patternMatcher = patternMatcher;
829
+ }
830
+ /**
831
+ * Find nodes and edges connected via a relationship from the given node
832
+ * Returns both the connected node and the edge that connects them
833
+ */
834
+ findConnectedNodesAndEdges(graph, node, relationship) {
835
+ const connections = [];
836
+ const seenNodeIds = /* @__PURE__ */ new Set();
837
+ if (relationship.direction === "outgoing" || relationship.direction === "both") {
838
+ const outgoingEdges = graph.edgesBySource[node.id] || [];
839
+ for (const edge of outgoingEdges) {
840
+ if (this.patternMatcher.matchesEdgeType(edge.type, relationship.edgeType)) {
841
+ const targetNode = graph.nodesById[edge.target];
842
+ if (targetNode && !seenNodeIds.has(targetNode.id)) {
843
+ seenNodeIds.add(targetNode.id);
844
+ connections.push({ node: targetNode, edge });
845
+ }
846
+ }
847
+ }
848
+ }
849
+ if (relationship.direction === "incoming" || relationship.direction === "both") {
850
+ const incomingEdges = graph.edgesByTarget[node.id] || [];
851
+ for (const edge of incomingEdges) {
852
+ if (this.patternMatcher.matchesEdgeType(edge.type, relationship.edgeType)) {
853
+ const sourceNode = graph.nodesById[edge.source];
854
+ if (sourceNode && !seenNodeIds.has(sourceNode.id)) {
855
+ seenNodeIds.add(sourceNode.id);
856
+ connections.push({ node: sourceNode, edge });
857
+ }
858
+ }
859
+ }
860
+ }
861
+ return connections;
862
+ }
863
+ };
864
+
865
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/result-formatter.ts
866
+ var ResultFormatter = class {
867
+ constructor() {
868
+ this.annonymizer = new Annonymizer();
869
+ }
870
+ /**
871
+ * Format a single match value (node or edge) based on extractData flag
872
+ */
873
+ formatValue(value) {
874
+ if (value === null || value === void 0) {
875
+ return null;
876
+ }
877
+ if ("id" in value && "labels" in value) {
878
+ const node = value;
879
+ return node;
880
+ } else {
881
+ const edge = value;
882
+ return edge;
883
+ }
884
+ }
885
+ /**
886
+ * Sort results based on ORDER BY clause
887
+ */
888
+ sortResults(results, orderBy) {
889
+ return [...results].sort((a, b) => {
890
+ for (const item of orderBy.items) {
891
+ const valA = this.getPropertyValue(a[item.variable], item.property);
892
+ const valB = this.getPropertyValue(b[item.variable], item.property);
893
+ const comparison = this.compareValues(valA, valB, item.direction);
894
+ if (comparison !== 0)
895
+ return comparison;
896
+ }
897
+ return 0;
898
+ });
899
+ }
900
+ /**
901
+ * Get property value from an object, handling nested property access
902
+ */
903
+ getPropertyValue(obj, property) {
904
+ if (obj === null || obj === void 0)
905
+ return null;
906
+ return property ? obj[property] : obj;
907
+ }
908
+ /**
909
+ * Compare two values with Neo4j-style NULL handling
910
+ * ASC: NULLs first, then ascending values
911
+ * DESC: Values descending, then NULLs last
912
+ */
913
+ compareValues(a, b, direction) {
914
+ const aIsNull = a === null || a === void 0;
915
+ const bIsNull = b === null || b === void 0;
916
+ if (aIsNull && bIsNull)
917
+ return 0;
918
+ if (aIsNull)
919
+ return direction === "ASC" ? -1 : 1;
920
+ if (bIsNull)
921
+ return direction === "ASC" ? 1 : -1;
922
+ let result;
923
+ if (typeof a === "string" && typeof b === "string") {
924
+ result = a.localeCompare(b);
925
+ } else {
926
+ result = a < b ? -1 : a > b ? 1 : 0;
927
+ }
928
+ return direction === "DESC" ? -result : result;
929
+ }
930
+ /**
931
+ * Format results with RETURN clause
932
+ * Only includes variables specified in RETURN clause, with optional aliases
933
+ * If returnAll is true (RETURN *), returns all variables
934
+ * If distinct is true, deduplicate results based on returned variables' ids
935
+ */
936
+ formatResults(results, returnClause) {
937
+ let processedResults = returnClause.limit ? results.slice(0, returnClause.limit) : results;
938
+ if (returnClause.distinct) {
939
+ processedResults = this.deduplicateResults(processedResults, returnClause);
940
+ }
941
+ if (returnClause.returnAll) {
942
+ return this.formatDefault(processedResults);
943
+ }
944
+ return processedResults.map((match) => {
945
+ const result = {};
946
+ for (const item of returnClause.items) {
947
+ const variable = item.expression;
948
+ if (this.annonymizer.isAnonymous(variable)) {
949
+ continue;
950
+ }
951
+ const alias = item.alias || variable;
952
+ if (variable in match) {
953
+ const value = match[variable];
954
+ result[alias] = this.formatValue(value);
955
+ } else {
956
+ throw new Error(`Variable ${variable} not found in match results`);
957
+ }
958
+ }
959
+ return result;
960
+ });
961
+ }
962
+ /**
963
+ * Deduplicate results based on returned variables' ids
964
+ */
965
+ deduplicateResults(results, returnClause) {
966
+ const seen = /* @__PURE__ */ new Map();
967
+ for (const match of results) {
968
+ const key = returnClause.items.map((item) => this.getValueId(match[item.expression])).join("|");
969
+ if (!seen.has(key)) {
970
+ seen.set(key, match);
971
+ }
972
+ }
973
+ return Array.from(seen.values());
974
+ }
975
+ /**
976
+ * Get a unique identifier for a match result value
977
+ */
978
+ getValueId(value) {
979
+ if (value === null || value === void 0) {
980
+ return "null";
981
+ }
982
+ if ("id" in value && "labels" in value) {
983
+ return value.id;
984
+ }
985
+ const edge = value;
986
+ return `edge:${edge.source}|${edge.target}|${edge.type}`;
987
+ }
988
+ /**
989
+ * Format results without RETURN clause (default)
990
+ * Returns all matched variables keyed by their variable names
991
+ */
992
+ formatDefault(matches) {
993
+ return matches.map((match) => {
994
+ const result = {};
995
+ for (const [varName, value] of Object.entries(match)) {
996
+ if (this.annonymizer.isAnonymous(varName)) {
997
+ continue;
998
+ }
999
+ result[varName] = this.formatValue(value);
1000
+ }
1001
+ return result;
1002
+ });
1003
+ }
1004
+ };
1005
+
1006
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/traverse-engine.ts
1007
+ var TraverseEngine = class {
1008
+ constructor(patternMatcher, relationshipNavigator) {
1009
+ this.patternMatcher = patternMatcher;
1010
+ this.relationshipNavigator = relationshipNavigator;
1011
+ this.annonymizer = new Annonymizer();
1012
+ }
1013
+ /**
1014
+ * Recursively traverse relationships to match subsequent patterns
1015
+ * Supports returning edges as variables when relationship variable is specified
1016
+ */
1017
+ traverseRelationships(graph, currentNode, currentVarName, remainingPatterns, relationships) {
1018
+ if (remainingPatterns.length === 0) {
1019
+ return [{ [currentVarName]: currentNode }];
1020
+ }
1021
+ const results = [];
1022
+ const nextPattern = remainingPatterns[0];
1023
+ const nextRelationship = relationships[0];
1024
+ const nextVarName = nextPattern.variable || this.annonymizer.generate(currentNode);
1025
+ const remainingPatterns_ = remainingPatterns.slice(1);
1026
+ const remainingRelationships = relationships.slice(1);
1027
+ if (nextRelationship.variableLength) {
1028
+ return this.traverseVariableLengthPath(
1029
+ graph,
1030
+ currentNode,
1031
+ currentVarName,
1032
+ nextRelationship,
1033
+ nextPattern,
1034
+ nextVarName,
1035
+ remainingPatterns_,
1036
+ remainingRelationships
1037
+ );
1038
+ }
1039
+ const connections = this.relationshipNavigator.findConnectedNodesAndEdges(
1040
+ graph,
1041
+ currentNode,
1042
+ nextRelationship
1043
+ );
1044
+ for (const { node: connectedNode, edge } of connections) {
1045
+ if (this.patternMatcher.doesNodeMatchPattern(connectedNode, nextPattern)) {
1046
+ const subMatches = this.traverseRelationships(
1047
+ graph,
1048
+ connectedNode,
1049
+ nextVarName,
1050
+ remainingPatterns_,
1051
+ remainingRelationships
1052
+ );
1053
+ for (const subMatch of subMatches) {
1054
+ const result = {
1055
+ [currentVarName]: currentNode,
1056
+ [nextVarName]: connectedNode,
1057
+ // Always include the connected node
1058
+ ...subMatch
1059
+ };
1060
+ if (nextRelationship.variable && edge) {
1061
+ result[nextRelationship.variable] = edge;
1062
+ }
1063
+ results.push(result);
1064
+ }
1065
+ }
1066
+ }
1067
+ return results;
1068
+ }
1069
+ /**
1070
+ * Traverse variable-length paths (zero or more edges)
1071
+ * Handles cycles by tracking visited nodes
1072
+ */
1073
+ traverseVariableLengthPath(graph, startNode, startVarName, relationship, targetPattern, targetVarName, remainingPatterns, remainingRelationships) {
1074
+ const results = [];
1075
+ const minLength = relationship.minLength ?? 0;
1076
+ const maxLength = relationship.maxLength ?? Infinity;
1077
+ const traverse = (currentNode, pathLength, visited, pathEdges) => {
1078
+ if (pathLength >= minLength && this.patternMatcher.doesNodeMatchPattern(currentNode, targetPattern)) {
1079
+ const subMatches = this.traverseRelationships(
1080
+ graph,
1081
+ currentNode,
1082
+ targetVarName,
1083
+ remainingPatterns,
1084
+ remainingRelationships
1085
+ );
1086
+ for (const subMatch of subMatches) {
1087
+ const result = {
1088
+ [startVarName]: startNode,
1089
+ [targetVarName]: currentNode,
1090
+ ...subMatch
1091
+ };
1092
+ if (relationship.variable && pathEdges.length > 0) {
1093
+ result[relationship.variable] = pathEdges[pathEdges.length - 1];
1094
+ }
1095
+ results.push(result);
1096
+ }
1097
+ }
1098
+ if (pathLength < maxLength) {
1099
+ const connections2 = this.relationshipNavigator.findConnectedNodesAndEdges(
1100
+ graph,
1101
+ currentNode,
1102
+ relationship
1103
+ );
1104
+ for (const { node: nextNode, edge } of connections2) {
1105
+ if (!visited.has(nextNode.id)) {
1106
+ const newVisited = new Set(visited);
1107
+ newVisited.add(nextNode.id);
1108
+ traverse(nextNode, pathLength + 1, newVisited, [
1109
+ ...pathEdges,
1110
+ edge
1111
+ ]);
1112
+ }
1113
+ }
1114
+ }
1115
+ };
1116
+ if (minLength === 0 && this.patternMatcher.doesNodeMatchPattern(startNode, targetPattern)) {
1117
+ const subMatches = this.traverseRelationships(
1118
+ graph,
1119
+ startNode,
1120
+ targetVarName,
1121
+ remainingPatterns,
1122
+ remainingRelationships
1123
+ );
1124
+ for (const subMatch of subMatches) {
1125
+ const result = {
1126
+ [startVarName]: startNode,
1127
+ [targetVarName]: startNode,
1128
+ ...subMatch
1129
+ };
1130
+ results.push(result);
1131
+ }
1132
+ }
1133
+ const connections = this.relationshipNavigator.findConnectedNodesAndEdges(
1134
+ graph,
1135
+ startNode,
1136
+ relationship
1137
+ );
1138
+ for (const { node: nextNode, edge } of connections) {
1139
+ traverse(nextNode, 1, /* @__PURE__ */ new Set([startNode.id, nextNode.id]), [edge]);
1140
+ }
1141
+ return results;
1142
+ }
1143
+ };
1144
+
1145
+ // libs/cli/code-graph-core/src/lib/cypher/lib/executor/executor.ts
1146
+ var CypherExecutor = class {
1147
+ constructor(graph) {
1148
+ this.graph = graph;
1149
+ this.conditionEvaluator = new ConditionEvaluator();
1150
+ const patternMatcher = new PatternMatcher();
1151
+ const relationshipNavigator = new RelationshipNavigator(patternMatcher);
1152
+ const traverseEngine = new TraverseEngine(
1153
+ patternMatcher,
1154
+ relationshipNavigator
1155
+ );
1156
+ this.matchEngine = new MatchEngine(patternMatcher, traverseEngine);
1157
+ this.resultFormatter = new ResultFormatter();
1158
+ }
1159
+ /**
1160
+ * Execute a parsed Cypher query
1161
+ * @param extractData - If true (default), returns node.data. If false, returns full GraphNode objects.
1162
+ * For edges, always returns the full GraphEdge object (edges have no data property).
1163
+ * Returns results where keys are variable names and values are either node data or edge objects
1164
+ */
1165
+ execute(query2) {
1166
+ if (query2.statements.length === 0) {
1167
+ throw new Error("Query must have at least one statement");
1168
+ }
1169
+ if (query2.statements[query2.statements.length - 1].type !== "ReturnStatement") {
1170
+ throw new Error("MATCH Query must have a RETURN statement");
1171
+ }
1172
+ let results = [];
1173
+ for (const statement of query2.statements) {
1174
+ if (statement.type === "MatchStatement") {
1175
+ results = this.matchEngine.processMatchStatement(
1176
+ this.graph,
1177
+ statement,
1178
+ results
1179
+ );
1180
+ } else if (statement.type === "WhereStatement") {
1181
+ results = this.conditionEvaluator.applyWhereClauseIfNeeded(
1182
+ results,
1183
+ statement
1184
+ );
1185
+ } else if (statement.type === "OrderByStatement") {
1186
+ results = this.resultFormatter.sortResults(results, statement);
1187
+ }
1188
+ }
1189
+ const returnStatement = query2.statements[query2.statements.length - 1];
1190
+ let formattedResults = this.resultFormatter.formatResults(
1191
+ results,
1192
+ returnStatement
1193
+ );
1194
+ return formattedResults;
1195
+ }
1196
+ };
1197
+
1198
+ // libs/cli/code-graph-core/src/lib/cypher/lib/lexer.ts
1199
+ var Lexer = class {
1200
+ constructor(input) {
1201
+ this.input = input;
1202
+ this.position = 0;
1203
+ this.currentChar = input.length > 0 ? input[0] : null;
1204
+ }
1205
+ advance() {
1206
+ this.position++;
1207
+ this.currentChar = this.position >= this.input.length ? null : this.input[this.position];
1208
+ }
1209
+ skipWhitespace() {
1210
+ while (this.currentChar && /\s/.test(this.currentChar)) {
1211
+ this.advance();
1212
+ }
1213
+ }
1214
+ readIdentifier() {
1215
+ let result = "";
1216
+ while (this.currentChar && /[a-zA-Z0-9_]/.test(this.currentChar)) {
1217
+ result += this.currentChar;
1218
+ this.advance();
1219
+ }
1220
+ return result;
1221
+ }
1222
+ readString() {
1223
+ const quote = this.currentChar;
1224
+ this.advance();
1225
+ let result = "";
1226
+ while (this.currentChar && this.currentChar !== quote) {
1227
+ if (this.currentChar === "\\") {
1228
+ this.advance();
1229
+ if (this.currentChar === "n") {
1230
+ result += "\n";
1231
+ } else if (this.currentChar === "t") {
1232
+ result += " ";
1233
+ } else {
1234
+ result += this.currentChar;
1235
+ }
1236
+ this.advance();
1237
+ } else {
1238
+ result += this.currentChar;
1239
+ this.advance();
1240
+ }
1241
+ }
1242
+ if (this.currentChar === quote) {
1243
+ this.advance();
1244
+ }
1245
+ return result;
1246
+ }
1247
+ readNumber() {
1248
+ let result = "";
1249
+ while (this.currentChar && /[0-9.]/.test(this.currentChar)) {
1250
+ result += this.currentChar;
1251
+ this.advance();
1252
+ }
1253
+ return result;
1254
+ }
1255
+ peekKeyword() {
1256
+ const savedPos = this.position;
1257
+ const savedChar = this.currentChar;
1258
+ const keyword = this.readIdentifier();
1259
+ this.position = savedPos;
1260
+ this.currentChar = savedChar;
1261
+ return keyword.length > 0 ? keyword.toUpperCase() : null;
1262
+ }
1263
+ tokenize() {
1264
+ const tokens = [];
1265
+ this.position = 0;
1266
+ this.currentChar = this.input.length > 0 ? this.input[0] : null;
1267
+ while (this.currentChar !== null) {
1268
+ this.skipWhitespace();
1269
+ if (!this.currentChar)
1270
+ break;
1271
+ const startPos = this.position;
1272
+ const keyword = this.peekKeyword();
1273
+ const keywordMap = {
1274
+ MATCH: "MATCH" /* MATCH */,
1275
+ OPTIONAL: "OPTIONAL" /* OPTIONAL */,
1276
+ RETURN: "RETURN" /* RETURN */,
1277
+ DISTINCT: "DISTINCT" /* DISTINCT */,
1278
+ WHERE: "WHERE" /* WHERE */,
1279
+ AND: "AND" /* AND */,
1280
+ OR: "OR" /* OR */,
1281
+ AS: "AS" /* AS */,
1282
+ IN: "IN" /* IN */,
1283
+ NOT: "NOT" /* NOT */,
1284
+ IS: "IS" /* IS */,
1285
+ NULL: "NULL" /* NULL */,
1286
+ TRUE: "TRUE" /* TRUE */,
1287
+ FALSE: "FALSE" /* FALSE */,
1288
+ STARTS: "STARTS" /* STARTS */,
1289
+ ENDS: "ENDS" /* ENDS */,
1290
+ CONTAINS: "CONTAINS" /* CONTAINS */,
1291
+ WITH: "WITH" /* WITH */,
1292
+ LIMIT: "LIMIT" /* LIMIT */,
1293
+ ORDER: "ORDER" /* ORDER */,
1294
+ BY: "BY" /* BY */,
1295
+ ASC: "ASC" /* ASC */,
1296
+ DESC: "DESC" /* DESC */
1297
+ };
1298
+ if (keyword && keywordMap[keyword]) {
1299
+ tokens.push({
1300
+ type: keywordMap[keyword],
1301
+ value: keyword,
1302
+ position: startPos
1303
+ });
1304
+ this.readIdentifier();
1305
+ continue;
1306
+ }
1307
+ switch (this.currentChar) {
1308
+ case "(":
1309
+ tokens.push({
1310
+ type: "LPAREN" /* LPAREN */,
1311
+ value: "(",
1312
+ position: startPos
1313
+ });
1314
+ this.advance();
1315
+ break;
1316
+ case ")":
1317
+ tokens.push({
1318
+ type: "RPAREN" /* RPAREN */,
1319
+ value: ")",
1320
+ position: startPos
1321
+ });
1322
+ this.advance();
1323
+ break;
1324
+ case "[":
1325
+ tokens.push({
1326
+ type: "LBRACKET" /* LBRACKET */,
1327
+ value: "[",
1328
+ position: startPos
1329
+ });
1330
+ this.advance();
1331
+ break;
1332
+ case "]":
1333
+ tokens.push({
1334
+ type: "RBRACKET" /* RBRACKET */,
1335
+ value: "]",
1336
+ position: startPos
1337
+ });
1338
+ this.advance();
1339
+ break;
1340
+ case "{":
1341
+ tokens.push({
1342
+ type: "LBRACE" /* LBRACE */,
1343
+ value: "{",
1344
+ position: startPos
1345
+ });
1346
+ this.advance();
1347
+ break;
1348
+ case "}":
1349
+ tokens.push({
1350
+ type: "RBRACE" /* RBRACE */,
1351
+ value: "}",
1352
+ position: startPos
1353
+ });
1354
+ this.advance();
1355
+ break;
1356
+ case "<":
1357
+ if (this.position + 1 < this.input.length && this.input[this.position + 1] === "-") {
1358
+ tokens.push({
1359
+ type: "ARROW" /* ARROW */,
1360
+ value: "<-",
1361
+ position: startPos
1362
+ });
1363
+ this.advance();
1364
+ this.advance();
1365
+ } else if (this.position + 1 < this.input.length && this.input[this.position + 1] === "=") {
1366
+ tokens.push({
1367
+ type: "LTE" /* LTE */,
1368
+ value: "<=",
1369
+ position: startPos
1370
+ });
1371
+ this.advance();
1372
+ this.advance();
1373
+ } else {
1374
+ tokens.push({
1375
+ type: "LT" /* LT */,
1376
+ value: "<",
1377
+ position: startPos
1378
+ });
1379
+ this.advance();
1380
+ }
1381
+ break;
1382
+ case "-":
1383
+ if (this.position + 1 < this.input.length && this.input[this.position + 1] === ">") {
1384
+ tokens.push({
1385
+ type: "ARROW" /* ARROW */,
1386
+ value: "->",
1387
+ position: startPos
1388
+ });
1389
+ this.advance();
1390
+ this.advance();
1391
+ } else {
1392
+ tokens.push({
1393
+ type: "DASH" /* DASH */,
1394
+ value: "-",
1395
+ position: startPos
1396
+ });
1397
+ this.advance();
1398
+ }
1399
+ break;
1400
+ case "=":
1401
+ if (this.position + 1 < this.input.length && this.input[this.position + 1] === "~") {
1402
+ tokens.push({
1403
+ type: "REGEX_MATCH" /* REGEX_MATCH */,
1404
+ value: "=~",
1405
+ position: startPos
1406
+ });
1407
+ this.advance();
1408
+ this.advance();
1409
+ } else {
1410
+ tokens.push({
1411
+ type: "EQUALS" /* EQUALS */,
1412
+ value: "=",
1413
+ position: startPos
1414
+ });
1415
+ this.advance();
1416
+ }
1417
+ break;
1418
+ case "!":
1419
+ if (this.position + 1 < this.input.length && this.input[this.position + 1] === "=") {
1420
+ tokens.push({
1421
+ type: "NOT_EQUALS" /* NOT_EQUALS */,
1422
+ value: "!=",
1423
+ position: startPos
1424
+ });
1425
+ this.advance();
1426
+ this.advance();
1427
+ } else {
1428
+ throw new Error(`Unexpected character: ! at position ${startPos}`);
1429
+ }
1430
+ break;
1431
+ case ">":
1432
+ if (this.position + 1 < this.input.length && this.input[this.position + 1] === "=") {
1433
+ tokens.push({
1434
+ type: "GTE" /* GTE */,
1435
+ value: ">=",
1436
+ position: startPos
1437
+ });
1438
+ this.advance();
1439
+ this.advance();
1440
+ } else {
1441
+ tokens.push({
1442
+ type: "GT" /* GT */,
1443
+ value: ">",
1444
+ position: startPos
1445
+ });
1446
+ this.advance();
1447
+ }
1448
+ break;
1449
+ case ",":
1450
+ tokens.push({
1451
+ type: "COMMA" /* COMMA */,
1452
+ value: ",",
1453
+ position: startPos
1454
+ });
1455
+ this.advance();
1456
+ break;
1457
+ case ".":
1458
+ tokens.push({
1459
+ type: "DOT" /* DOT */,
1460
+ value: ".",
1461
+ position: startPos
1462
+ });
1463
+ this.advance();
1464
+ break;
1465
+ case "|":
1466
+ tokens.push({
1467
+ type: "PIPE" /* PIPE */,
1468
+ value: "|",
1469
+ position: startPos
1470
+ });
1471
+ this.advance();
1472
+ break;
1473
+ case "*":
1474
+ tokens.push({
1475
+ type: "ASTERISK" /* ASTERISK */,
1476
+ value: "*",
1477
+ position: startPos
1478
+ });
1479
+ this.advance();
1480
+ break;
1481
+ case ":":
1482
+ tokens.push({
1483
+ type: "LABEL" /* LABEL */,
1484
+ value: ":",
1485
+ position: startPos
1486
+ });
1487
+ this.advance();
1488
+ break;
1489
+ case '"':
1490
+ case "'":
1491
+ tokens.push({
1492
+ type: "STRING" /* STRING */,
1493
+ value: this.readString(),
1494
+ position: startPos
1495
+ });
1496
+ break;
1497
+ default:
1498
+ if (/[0-9]/.test(this.currentChar)) {
1499
+ tokens.push({
1500
+ type: "NUMBER" /* NUMBER */,
1501
+ value: this.readNumber(),
1502
+ position: startPos
1503
+ });
1504
+ } else if (/[a-zA-Z_]/.test(this.currentChar)) {
1505
+ tokens.push({
1506
+ type: "IDENTIFIER" /* IDENTIFIER */,
1507
+ value: this.readIdentifier(),
1508
+ position: startPos
1509
+ });
1510
+ } else {
1511
+ throw new Error(
1512
+ `Unexpected character: ${this.currentChar} at position ${startPos}`
1513
+ );
1514
+ }
1515
+ }
1516
+ }
1517
+ tokens.push({ type: "EOF" /* EOF */, value: "", position: this.position });
1518
+ return tokens;
1519
+ }
1520
+ };
1521
+
1522
+ // libs/cli/code-graph-core/src/lib/cypher/lib/parser.ts
1523
+ var Parser = class {
1524
+ constructor(tokens) {
1525
+ this.tokens = [];
1526
+ this.position = 0;
1527
+ this.tokens = tokens;
1528
+ }
1529
+ current() {
1530
+ return this.tokens[this.position];
1531
+ }
1532
+ advance() {
1533
+ const token = this.current();
1534
+ if (this.position < this.tokens.length - 1) {
1535
+ this.position++;
1536
+ }
1537
+ return token;
1538
+ }
1539
+ peek() {
1540
+ return this.tokens[this.position + 1] || this.tokens[this.tokens.length - 1];
1541
+ }
1542
+ expect(type) {
1543
+ const token = this.current();
1544
+ if (token.type !== type) {
1545
+ throw new Error(
1546
+ `Expected ${type}, got ${token.type} at position ${token.position}`
1547
+ );
1548
+ }
1549
+ return this.advance();
1550
+ }
1551
+ isKeyword(keyword) {
1552
+ return this.current().type === keyword;
1553
+ }
1554
+ /**
1555
+ * Parse a property object: { key: value, ... }
1556
+ * Note: Cypher uses colon (:) for properties in node patterns, not equals (=)
1557
+ */
1558
+ parseProperties() {
1559
+ this.expect("LBRACE" /* LBRACE */);
1560
+ const properties = {};
1561
+ if (this.current().type === "RBRACE" /* RBRACE */) {
1562
+ this.advance();
1563
+ return properties;
1564
+ }
1565
+ while (true) {
1566
+ const key = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1567
+ this.expect("LABEL" /* LABEL */);
1568
+ let value;
1569
+ if (this.current().type === "LBRACKET" /* LBRACKET */) {
1570
+ value = this.parseArrayLiteral();
1571
+ } else if (this.current().type === "STRING" /* STRING */) {
1572
+ value = this.advance().value;
1573
+ } else if (this.current().type === "NUMBER" /* NUMBER */) {
1574
+ value = parseFloat(this.advance().value);
1575
+ } else if (this.current().type === "TRUE" /* TRUE */) {
1576
+ value = true;
1577
+ this.advance();
1578
+ } else if (this.current().type === "FALSE" /* FALSE */) {
1579
+ value = false;
1580
+ this.advance();
1581
+ } else if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1582
+ value = this.advance().value;
1583
+ } else {
1584
+ throw new Error(
1585
+ `Unexpected token type for property value: ${this.current().type}`
1586
+ );
1587
+ }
1588
+ properties[key] = value;
1589
+ if (this.current().type === "RBRACE" /* RBRACE */) {
1590
+ this.advance();
1591
+ break;
1592
+ }
1593
+ this.expect("COMMA" /* COMMA */);
1594
+ }
1595
+ return properties;
1596
+ }
1597
+ /**
1598
+ * Parse a node pattern: (var:Label {prop: value}) or (:Label {prop: value})
1599
+ */
1600
+ parseNodePattern() {
1601
+ this.expect("LPAREN" /* LPAREN */);
1602
+ const pattern = {
1603
+ type: "NodePattern",
1604
+ labels: []
1605
+ };
1606
+ if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1607
+ const nextToken = this.peek();
1608
+ if (nextToken.type === "LABEL" /* LABEL */ || nextToken.type === "LBRACE" /* LBRACE */ || nextToken.type === "RPAREN" /* RPAREN */) {
1609
+ pattern.variable = this.advance().value;
1610
+ }
1611
+ }
1612
+ if (this.current().type === "LABEL" /* LABEL */) {
1613
+ this.advance();
1614
+ if (this.current().type !== "IDENTIFIER" /* IDENTIFIER */) {
1615
+ throw new Error(
1616
+ `Expected identifier after :, got ${this.current().type}`
1617
+ );
1618
+ }
1619
+ pattern.labels.push(this.advance().value);
1620
+ if (this.current().type === "LABEL" /* LABEL */) {
1621
+ pattern.labelOperator = "AND";
1622
+ while (this.current().type === "LABEL" /* LABEL */) {
1623
+ this.advance();
1624
+ if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1625
+ pattern.labels.push(this.advance().value);
1626
+ } else {
1627
+ throw new Error(
1628
+ `Expected identifier after :, got ${this.current().type}`
1629
+ );
1630
+ }
1631
+ }
1632
+ } else if (this.current().type === "PIPE" /* PIPE */) {
1633
+ pattern.labelOperator = "OR";
1634
+ while (this.current().type === "PIPE" /* PIPE */) {
1635
+ this.advance();
1636
+ if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1637
+ pattern.labels.push(this.advance().value);
1638
+ } else {
1639
+ throw new Error(
1640
+ `Expected identifier after |, got ${this.current().type}`
1641
+ );
1642
+ }
1643
+ }
1644
+ } else {
1645
+ pattern.labelOperator = "AND";
1646
+ }
1647
+ }
1648
+ if (this.current().type === "LBRACE" /* LBRACE */) {
1649
+ pattern.properties = this.parseProperties();
1650
+ }
1651
+ this.expect("RPAREN" /* RPAREN */);
1652
+ return pattern;
1653
+ }
1654
+ /**
1655
+ * Parse relationship pattern: -[:RELATIONSHIP_TYPE]-> or <-[:RELATIONSHIP_TYPE]- or -[r:RELATIONSHIP_TYPE]->
1656
+ */
1657
+ parseRelationship() {
1658
+ const relationship = {
1659
+ type: "RelationshipPattern",
1660
+ direction: "outgoing"
1661
+ };
1662
+ if (this.current().type === "ARROW" /* ARROW */ && this.current().value === "<-") {
1663
+ relationship.direction = "incoming";
1664
+ this.advance();
1665
+ } else {
1666
+ this.expect("DASH" /* DASH */);
1667
+ }
1668
+ if (this.current().type === "LBRACKET" /* LBRACKET */) {
1669
+ this.advance();
1670
+ if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1671
+ const nextToken = this.peek();
1672
+ if (nextToken.type === "LABEL" /* LABEL */ || nextToken.type === "ASTERISK" /* ASTERISK */) {
1673
+ relationship.variable = this.advance().value;
1674
+ }
1675
+ }
1676
+ if (this.current().type === "ASTERISK" /* ASTERISK */) {
1677
+ this.advance();
1678
+ relationship.variableLength = true;
1679
+ relationship.minLength = 0;
1680
+ relationship.maxLength = void 0;
1681
+ } else if (this.current().type === "LABEL" /* LABEL */) {
1682
+ this.advance();
1683
+ const edgeTypes = [];
1684
+ edgeTypes.push(this.expect("IDENTIFIER" /* IDENTIFIER */).value);
1685
+ while (this.current().type === "PIPE" /* PIPE */) {
1686
+ this.advance();
1687
+ edgeTypes.push(this.expect("IDENTIFIER" /* IDENTIFIER */).value);
1688
+ }
1689
+ relationship.edgeType = edgeTypes.length === 1 ? edgeTypes[0] : edgeTypes;
1690
+ if (this.current().type === "ASTERISK" /* ASTERISK */) {
1691
+ this.advance();
1692
+ relationship.variableLength = true;
1693
+ relationship.minLength = 0;
1694
+ relationship.maxLength = void 0;
1695
+ }
1696
+ }
1697
+ this.expect("RBRACKET" /* RBRACKET */);
1698
+ }
1699
+ if (relationship.direction === "incoming") {
1700
+ this.expect("DASH" /* DASH */);
1701
+ } else {
1702
+ if (this.current().type === "ARROW" /* ARROW */ && this.current().value === "->") {
1703
+ this.advance();
1704
+ } else if (this.current().type === "DASH" /* DASH */) {
1705
+ relationship.direction = "both";
1706
+ this.advance();
1707
+ }
1708
+ }
1709
+ return relationship;
1710
+ }
1711
+ /**
1712
+ * Parse MATCH clause (supports OPTIONAL MATCH)
1713
+ */
1714
+ parseMatch() {
1715
+ const isOptional = this.isKeyword("OPTIONAL" /* OPTIONAL */);
1716
+ if (isOptional) {
1717
+ this.advance();
1718
+ }
1719
+ this.expect("MATCH" /* MATCH */);
1720
+ const firstPattern = this.parsePatternChain();
1721
+ const matchClause = {
1722
+ type: "MatchClause",
1723
+ patterns: firstPattern.patterns,
1724
+ relationships: firstPattern.relationships,
1725
+ additionalMatches: []
1726
+ };
1727
+ while (this.current().type === "COMMA" /* COMMA */) {
1728
+ this.advance();
1729
+ const additionalPattern = this.parsePatternChain();
1730
+ matchClause.additionalMatches.push(additionalPattern);
1731
+ }
1732
+ if (isOptional) {
1733
+ matchClause.optionalMatches = [
1734
+ {
1735
+ type: "MatchStatement",
1736
+ patterns: firstPattern.patterns,
1737
+ relationships: firstPattern.relationships
1738
+ },
1739
+ ...matchClause.additionalMatches || []
1740
+ ];
1741
+ matchClause.patterns = [];
1742
+ matchClause.relationships = void 0;
1743
+ matchClause.additionalMatches = void 0;
1744
+ }
1745
+ return matchClause;
1746
+ }
1747
+ /**
1748
+ * Parse a pattern chain: (node)-[:rel]->(node)-[:rel]->(node)...
1749
+ * Returns patterns and relationships for a single connected pattern
1750
+ */
1751
+ parsePatternChain() {
1752
+ const patterns = [];
1753
+ const relationships = [];
1754
+ patterns.push(this.parseNodePattern());
1755
+ while (this.current().type === "DASH" /* DASH */ || this.current().type === "ARROW" /* ARROW */) {
1756
+ relationships.push(this.parseRelationship());
1757
+ patterns.push(this.parseNodePattern());
1758
+ }
1759
+ return {
1760
+ type: "MatchStatement",
1761
+ patterns,
1762
+ relationships: relationships.length > 0 ? relationships : void 0
1763
+ };
1764
+ }
1765
+ /**
1766
+ * Parse WHERE clause
1767
+ * Example: WHERE m.type = "Command" AND "param" IN m.parameters
1768
+ * Note: Flat properties only (no nesting, like Neo4j Cypher)
1769
+ */
1770
+ parseWhere() {
1771
+ this.expect("WHERE" /* WHERE */);
1772
+ const conditions = [];
1773
+ let logic;
1774
+ while (true) {
1775
+ const variable = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1776
+ let property;
1777
+ if (this.current().type === "DOT" /* DOT */) {
1778
+ this.advance();
1779
+ property = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1780
+ }
1781
+ let operator = "=";
1782
+ if (this.current().type === "EQUALS" /* EQUALS */) {
1783
+ operator = "=";
1784
+ this.advance();
1785
+ } else if (this.current().type === "NOT_EQUALS" /* NOT_EQUALS */) {
1786
+ operator = "!=";
1787
+ this.advance();
1788
+ } else if (this.current().type === "GT" /* GT */) {
1789
+ operator = ">";
1790
+ this.advance();
1791
+ } else if (this.current().type === "LT" /* LT */) {
1792
+ operator = "<";
1793
+ this.advance();
1794
+ } else if (this.current().type === "GTE" /* GTE */) {
1795
+ operator = ">=";
1796
+ this.advance();
1797
+ } else if (this.current().type === "LTE" /* LTE */) {
1798
+ operator = "<=";
1799
+ this.advance();
1800
+ } else if (this.current().type === "NOT" /* NOT */) {
1801
+ this.advance();
1802
+ this.expect("IN" /* IN */);
1803
+ operator = "NOT IN";
1804
+ } else if (this.current().type === "IN" /* IN */) {
1805
+ this.advance();
1806
+ operator = "IN";
1807
+ } else if (this.current().type === "IS" /* IS */) {
1808
+ this.advance();
1809
+ if (this.isKeyword("NULL" /* NULL */)) {
1810
+ this.advance();
1811
+ operator = "IS NULL";
1812
+ } else if (this.isKeyword("NOT" /* NOT */)) {
1813
+ this.advance();
1814
+ this.expect("NULL" /* NULL */);
1815
+ operator = "IS NOT NULL";
1816
+ } else {
1817
+ throw new Error(
1818
+ `Expected NULL or NOT NULL after IS at position ${this.current().position}`
1819
+ );
1820
+ }
1821
+ } else if (this.current().type === "STARTS" /* STARTS */) {
1822
+ this.advance();
1823
+ this.expect("WITH" /* WITH */);
1824
+ operator = "STARTS WITH";
1825
+ } else if (this.current().type === "ENDS" /* ENDS */) {
1826
+ this.advance();
1827
+ this.expect("WITH" /* WITH */);
1828
+ operator = "ENDS WITH";
1829
+ } else if (this.current().type === "CONTAINS" /* CONTAINS */) {
1830
+ operator = "CONTAINS";
1831
+ this.advance();
1832
+ } else if (this.current().type === "REGEX_MATCH" /* REGEX_MATCH */) {
1833
+ operator = "=~";
1834
+ this.advance();
1835
+ } else {
1836
+ throw new Error(
1837
+ `Unsupported operator at position ${this.current().position}.`
1838
+ );
1839
+ }
1840
+ let value = null;
1841
+ if (operator === "IS NULL" || operator === "IS NOT NULL") {
1842
+ } else if (operator === "STARTS WITH" || operator === "ENDS WITH" || operator === "CONTAINS" || operator === "=~") {
1843
+ if (this.current().type === "STRING" /* STRING */) {
1844
+ value = this.advance().value;
1845
+ } else {
1846
+ throw new Error(
1847
+ `String matching operators (${operator}) require a string value at position ${this.current().position}`
1848
+ );
1849
+ }
1850
+ } else if (this.current().type === "LBRACKET" /* LBRACKET */) {
1851
+ value = this.parseArrayLiteral();
1852
+ } else if (this.current().type === "STRING" /* STRING */) {
1853
+ value = this.advance().value;
1854
+ } else if (this.current().type === "NUMBER" /* NUMBER */) {
1855
+ value = parseFloat(this.advance().value);
1856
+ } else if (this.current().type === "TRUE" /* TRUE */) {
1857
+ value = true;
1858
+ this.advance();
1859
+ } else if (this.current().type === "FALSE" /* FALSE */) {
1860
+ value = false;
1861
+ this.advance();
1862
+ } else if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1863
+ value = this.advance().value;
1864
+ } else {
1865
+ throw new Error(
1866
+ `Unexpected token type for WHERE value: ${this.current().type}`
1867
+ );
1868
+ }
1869
+ conditions.push({ variable, property, operator, value });
1870
+ if (this.isKeyword("AND" /* AND */)) {
1871
+ if (logic === void 0) {
1872
+ logic = "AND";
1873
+ } else if (logic !== "AND") {
1874
+ throw new Error("Cannot mix AND and OR in WHERE clause");
1875
+ }
1876
+ this.advance();
1877
+ continue;
1878
+ } else if (this.isKeyword("OR" /* OR */)) {
1879
+ if (logic === void 0) {
1880
+ logic = "OR";
1881
+ } else if (logic !== "OR") {
1882
+ throw new Error("Cannot mix AND and OR in WHERE clause");
1883
+ }
1884
+ this.advance();
1885
+ continue;
1886
+ }
1887
+ break;
1888
+ }
1889
+ return {
1890
+ type: "WhereClause",
1891
+ conditions,
1892
+ logic
1893
+ };
1894
+ }
1895
+ /**
1896
+ * Parse array literal: [value1, value2, ...]
1897
+ */
1898
+ parseArrayLiteral() {
1899
+ this.expect("LBRACKET" /* LBRACKET */);
1900
+ const array = [];
1901
+ if (this.current().type === "RBRACKET" /* RBRACKET */) {
1902
+ this.advance();
1903
+ return array;
1904
+ }
1905
+ while (true) {
1906
+ if (this.current().type === "STRING" /* STRING */) {
1907
+ array.push(this.advance().value);
1908
+ } else if (this.current().type === "NUMBER" /* NUMBER */) {
1909
+ array.push(parseFloat(this.advance().value));
1910
+ } else if (this.current().type === "TRUE" /* TRUE */) {
1911
+ array.push(true);
1912
+ this.advance();
1913
+ } else if (this.current().type === "FALSE" /* FALSE */) {
1914
+ array.push(false);
1915
+ this.advance();
1916
+ } else if (this.current().type === "IDENTIFIER" /* IDENTIFIER */) {
1917
+ array.push(this.advance().value);
1918
+ } else {
1919
+ throw new Error(
1920
+ `Unexpected token type in array literal: ${this.current().type}`
1921
+ );
1922
+ }
1923
+ if (this.current().type === "COMMA" /* COMMA */) {
1924
+ this.advance();
1925
+ } else if (this.current().type === "RBRACKET" /* RBRACKET */) {
1926
+ this.advance();
1927
+ break;
1928
+ } else {
1929
+ throw new Error(
1930
+ `Expected comma or closing bracket in array literal at position ${this.current().position}`
1931
+ );
1932
+ }
1933
+ }
1934
+ return array;
1935
+ }
1936
+ /**
1937
+ * Parse RETURN clause
1938
+ * Supports: RETURN *, RETURN DISTINCT, RETURN var1, var2, RETURN var AS alias, LIMIT n
1939
+ */
1940
+ parseReturn() {
1941
+ this.expect("RETURN" /* RETURN */);
1942
+ const distinct = this.isKeyword("DISTINCT" /* DISTINCT */);
1943
+ if (distinct) {
1944
+ this.advance();
1945
+ }
1946
+ if (this.current().type === "ASTERISK" /* ASTERISK */) {
1947
+ this.advance();
1948
+ const limit2 = this.parseOptionalLimit();
1949
+ return {
1950
+ type: "ReturnClause",
1951
+ distinct,
1952
+ returnAll: true,
1953
+ items: [],
1954
+ limit: limit2
1955
+ };
1956
+ }
1957
+ const items = [];
1958
+ while (true) {
1959
+ const expression = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1960
+ let alias;
1961
+ if (this.isKeyword("AS" /* AS */)) {
1962
+ this.advance();
1963
+ alias = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
1964
+ }
1965
+ items.push({ expression, alias });
1966
+ if (this.current().type === "COMMA" /* COMMA */) {
1967
+ this.advance();
1968
+ continue;
1969
+ }
1970
+ break;
1971
+ }
1972
+ const limit = this.parseOptionalLimit();
1973
+ return {
1974
+ type: "ReturnClause",
1975
+ distinct,
1976
+ returnAll: false,
1977
+ items,
1978
+ limit
1979
+ };
1980
+ }
1981
+ /**
1982
+ * Parse optional LIMIT clause
1983
+ * Returns the limit number or undefined if not present
1984
+ */
1985
+ parseOptionalLimit() {
1986
+ if (this.isKeyword("LIMIT" /* LIMIT */)) {
1987
+ this.advance();
1988
+ const limitToken = this.expect("NUMBER" /* NUMBER */);
1989
+ return parseInt(limitToken.value, 10);
1990
+ }
1991
+ return void 0;
1992
+ }
1993
+ /**
1994
+ * Parse ORDER BY clause
1995
+ * Supports: ORDER BY n.name, ORDER BY n.name ASC, ORDER BY n.name DESC
1996
+ * Also supports multiple fields: ORDER BY n.type DESC, n.name ASC
1997
+ */
1998
+ parseOrderBy() {
1999
+ this.expect("ORDER" /* ORDER */);
2000
+ this.expect("BY" /* BY */);
2001
+ const items = [];
2002
+ while (true) {
2003
+ const variable = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
2004
+ let property;
2005
+ if (this.current().type === "DOT" /* DOT */) {
2006
+ this.advance();
2007
+ property = this.expect("IDENTIFIER" /* IDENTIFIER */).value;
2008
+ }
2009
+ let direction = "ASC";
2010
+ if (this.isKeyword("ASC" /* ASC */)) {
2011
+ this.advance();
2012
+ direction = "ASC";
2013
+ } else if (this.isKeyword("DESC" /* DESC */)) {
2014
+ this.advance();
2015
+ direction = "DESC";
2016
+ }
2017
+ items.push({ variable, property, direction });
2018
+ if (this.current().type === "COMMA" /* COMMA */) {
2019
+ this.advance();
2020
+ continue;
2021
+ }
2022
+ break;
2023
+ }
2024
+ return { type: "OrderByStatement", items };
2025
+ }
2026
+ /**
2027
+ * Parse entire Cypher query
2028
+ * Returns statements in execution order
2029
+ */
2030
+ parse() {
2031
+ const statements = [];
2032
+ while (this.current().type !== "EOF" /* EOF */) {
2033
+ if (this.isKeyword("MATCH" /* MATCH */) || this.isKeyword("OPTIONAL" /* OPTIONAL */)) {
2034
+ const isOptional = this.isKeyword("OPTIONAL" /* OPTIONAL */);
2035
+ const matchClause = this.parseMatch();
2036
+ if (matchClause.optionalMatches) {
2037
+ for (const optionalMatch of matchClause.optionalMatches) {
2038
+ statements.push({
2039
+ type: "MatchStatement",
2040
+ patterns: optionalMatch.patterns,
2041
+ relationships: optionalMatch.relationships,
2042
+ optional: true
2043
+ });
2044
+ }
2045
+ } else {
2046
+ if (matchClause.patterns.length > 0) {
2047
+ statements.push({
2048
+ type: "MatchStatement",
2049
+ patterns: matchClause.patterns,
2050
+ relationships: matchClause.relationships,
2051
+ optional: false
2052
+ });
2053
+ }
2054
+ if (matchClause.additionalMatches) {
2055
+ for (const additionalMatch of matchClause.additionalMatches) {
2056
+ statements.push({
2057
+ type: "MatchStatement",
2058
+ patterns: additionalMatch.patterns,
2059
+ relationships: additionalMatch.relationships,
2060
+ optional: false
2061
+ });
2062
+ }
2063
+ }
2064
+ }
2065
+ continue;
2066
+ }
2067
+ if (this.isKeyword("WHERE" /* WHERE */)) {
2068
+ const whereClause = this.parseWhere();
2069
+ statements.push({
2070
+ type: "WhereStatement",
2071
+ conditions: whereClause.conditions,
2072
+ logic: whereClause.logic
2073
+ });
2074
+ continue;
2075
+ }
2076
+ if (this.isKeyword("ORDER" /* ORDER */)) {
2077
+ const orderByStatement = this.parseOrderBy();
2078
+ statements.push(orderByStatement);
2079
+ continue;
2080
+ }
2081
+ if (this.isKeyword("RETURN" /* RETURN */)) {
2082
+ const returnClause = this.parseReturn();
2083
+ statements.push({
2084
+ type: "ReturnStatement",
2085
+ distinct: returnClause.distinct,
2086
+ returnAll: returnClause.returnAll,
2087
+ items: returnClause.items,
2088
+ limit: returnClause.limit
2089
+ });
2090
+ continue;
2091
+ }
2092
+ break;
2093
+ }
2094
+ if (this.current().type !== "EOF" /* EOF */) {
2095
+ throw new Error(
2096
+ `Unexpected token: ${this.current().type} at position ${this.current().position}`
2097
+ );
2098
+ }
2099
+ return {
2100
+ type: "CypherQuery",
2101
+ statements
2102
+ };
2103
+ }
2104
+ };
2105
+
2106
+ // libs/cli/code-graph-core/src/lib/cypher/lib/validator/unsupported-features.config.ts
2107
+ var FORBIDDEN_KEYWORDS = [
2108
+ {
2109
+ pattern: /\bWITH\b/i,
2110
+ name: "WITH clause",
2111
+ errorMessage: "WITH clause is not supported. Cannot use WITH for intermediate results or variable passing.",
2112
+ alternative: "Use multiple MATCH statements or inline variables directly in WHERE/RETURN clauses."
2113
+ },
2114
+ {
2115
+ pattern: /\bUNWIND\b/i,
2116
+ name: "UNWIND clause",
2117
+ errorMessage: "UNWIND clause is not supported. Cannot use UNWIND to expand arrays."
2118
+ },
2119
+ {
2120
+ pattern: /\bUNION\b/i,
2121
+ name: "UNION clause",
2122
+ errorMessage: "UNION clause is not supported. Cannot combine result sets with UNION."
2123
+ },
2124
+ {
2125
+ pattern: /\bCREATE\b/i,
2126
+ name: "CREATE clause",
2127
+ errorMessage: "CREATE clause is not supported. This is a read-only query engine (no mutations)."
2128
+ },
2129
+ {
2130
+ pattern: /\bMERGE\b/i,
2131
+ name: "MERGE clause",
2132
+ errorMessage: "MERGE clause is not supported. This is a read-only query engine (no mutations)."
2133
+ },
2134
+ {
2135
+ pattern: /\bDELETE\b/i,
2136
+ name: "DELETE clause",
2137
+ errorMessage: "DELETE clause is not supported. This is a read-only query engine (no mutations)."
2138
+ }
2139
+ // {
2140
+ // pattern: /\bSET\b/i,
2141
+ // name: 'SET clause',
2142
+ // errorMessage:
2143
+ // 'SET clause is not supported. This is a read-only query engine (no mutations).',
2144
+ // },
2145
+ ];
2146
+ var FORBIDDEN_AGGREGATIONS = [
2147
+ {
2148
+ pattern: /\bCOUNT\s*\(/i,
2149
+ name: "COUNT() function",
2150
+ errorMessage: "COUNT() aggregation function is not supported. Aggregation functions are not available.",
2151
+ alternative: "Use manual counting patterns or return all results and count in application code."
2152
+ },
2153
+ {
2154
+ pattern: /\bSUM\s*\(/i,
2155
+ name: "SUM() function",
2156
+ errorMessage: "SUM() aggregation function is not supported. Aggregation functions are not available."
2157
+ },
2158
+ {
2159
+ pattern: /\bcollect\s*\(/i,
2160
+ name: "collect() function",
2161
+ errorMessage: "collect() aggregation function is not supported. Aggregation functions are not available."
2162
+ },
2163
+ {
2164
+ pattern: /\bMIN\s*\(/i,
2165
+ name: "MIN() function",
2166
+ errorMessage: "MIN() aggregation function is not supported. Aggregation functions are not available."
2167
+ },
2168
+ {
2169
+ pattern: /\bMAX\s*\(/i,
2170
+ name: "MAX() function",
2171
+ errorMessage: "MAX() aggregation function is not supported. Aggregation functions are not available."
2172
+ },
2173
+ {
2174
+ pattern: /\bAVG\s*\(/i,
2175
+ name: "AVG() function",
2176
+ errorMessage: "AVG() aggregation function is not supported. Aggregation functions are not available."
2177
+ }
2178
+ ];
2179
+ var FORBIDDEN_MODIFIERS = [
2180
+ {
2181
+ pattern: /\bSKIP\b/i,
2182
+ name: "SKIP clause",
2183
+ errorMessage: "SKIP clause is not supported. Pagination is not available."
2184
+ }
2185
+ ];
2186
+ var FORBIDDEN_PATTERNS = [
2187
+ {
2188
+ pattern: /\[.*:\w+\*\d+\.\.\d+.*\]/,
2189
+ name: "Path range syntax",
2190
+ errorMessage: "Path range syntax is not supported. Patterns like `*1..3`, `*2..`, `*..3` are invalid.",
2191
+ alternative: "Use `*` (unlimited, zero or more) for variable-length paths."
2192
+ },
2193
+ {
2194
+ pattern: /\[.*:\w+\*\d+\.\..*\]/,
2195
+ name: "Path range syntax (lower bound)",
2196
+ errorMessage: "Path range syntax is not supported. Patterns like `*2..` are invalid.",
2197
+ alternative: "Use `*` (unlimited, zero or more) for variable-length paths."
2198
+ },
2199
+ {
2200
+ pattern: /\[.*:\w+\*\.\.\d+.*\]/,
2201
+ name: "Path range syntax (upper bound)",
2202
+ errorMessage: "Path range syntax is not supported. Patterns like `*..3` are invalid.",
2203
+ alternative: "Use `*` (unlimited, zero or more) for variable-length paths."
2204
+ },
2205
+ {
2206
+ pattern: /WHERE\s+NOT\s*\(/i,
2207
+ name: "WHERE NOT (pattern)",
2208
+ errorMessage: "WHERE NOT (pattern) is not supported. Cannot use pattern negation in WHERE clause.",
2209
+ alternative: "Use `OPTIONAL MATCH` + `WHERE IS NULL` instead. Example: `OPTIONAL MATCH (m)-[:HAS_PARAMETER]->(:ParameterDeclaration) WHERE ... IS NULL`."
2210
+ },
2211
+ {
2212
+ pattern: /MATCH\s*\(:\w+[^)]*\)(?:\s*,\s*\(:\w+[^)]*\))*(?:\s*WHERE[^R]*)?/i,
2213
+ name: "Anonymous-only queries",
2214
+ errorMessage: "Queries with only anonymous nodes (no variable bindings) are not useful.",
2215
+ alternative: "Use bound variables in the pattern. Example: `MATCH (a:A)` instead of `MATCH (:A)`."
2216
+ }
2217
+ ];
2218
+
2219
+ // libs/cli/code-graph-core/src/lib/cypher/lib/validator/query-validator.ts
2220
+ var UNSUPPORTED_FEATURES = [
2221
+ ...FORBIDDEN_KEYWORDS,
2222
+ ...FORBIDDEN_AGGREGATIONS,
2223
+ ...FORBIDDEN_MODIFIERS,
2224
+ ...FORBIDDEN_PATTERNS
2225
+ ];
2226
+ var UnsupportedFeatureError = class extends Error {
2227
+ constructor(feature, query2) {
2228
+ const message = `Unsupported Cypher feature detected: ${feature.name}
2229
+
2230
+ ${feature.errorMessage}${feature.alternative ? `
2231
+
2232
+ Alternative: ${feature.alternative}` : ""}
2233
+
2234
+ Query: ${query2}`;
2235
+ super(message);
2236
+ this.feature = feature;
2237
+ this.query = query2;
2238
+ this.name = "UnsupportedFeatureError";
2239
+ }
2240
+ };
2241
+ function validateQuery(query2) {
2242
+ if (!query2 || typeof query2 !== "string") {
2243
+ throw new Error("Query must be a non-empty string");
2244
+ }
2245
+ const normalizedQuery = query2.trim();
2246
+ if (normalizedQuery.length === 0) {
2247
+ throw new Error("Query cannot be empty");
2248
+ }
2249
+ for (const feature of UNSUPPORTED_FEATURES) {
2250
+ const pattern = typeof feature.pattern === "string" ? new RegExp(feature.pattern, "i") : feature.pattern;
2251
+ if (pattern.test(normalizedQuery)) {
2252
+ if (feature.name === "WITH clause") {
2253
+ const withMatches = normalizedQuery.matchAll(/\bWITH\b/gi);
2254
+ let isAllowed = false;
2255
+ for (const match of withMatches) {
2256
+ const beforeMatch = normalizedQuery.substring(
2257
+ Math.max(0, match.index - 10),
2258
+ match.index
2259
+ );
2260
+ if (/\bSTARTS\s+$/i.test(beforeMatch) || /\bENDS\s+$/i.test(beforeMatch)) {
2261
+ isAllowed = true;
2262
+ break;
2263
+ }
2264
+ }
2265
+ if (isAllowed) {
2266
+ continue;
2267
+ }
2268
+ }
2269
+ throw new UnsupportedFeatureError(feature, normalizedQuery);
2270
+ }
2271
+ }
2272
+ }
2273
+
2274
+ // libs/cli/code-graph-core/src/lib/cypher/lib/index.ts
2275
+ var buildQueryObject = (query2) => {
2276
+ const joinedQuery = Array.isArray(query2) ? query2.join(" ") : query2;
2277
+ validateQuery(joinedQuery);
2278
+ const lexer = new Lexer(joinedQuery);
2279
+ const tokens = lexer.tokenize();
2280
+ const parser = new Parser(tokens);
2281
+ return parser.parse();
2282
+ };
2283
+ var query = (queryStatements, graph) => {
2284
+ const queryObject = buildQueryObject(queryStatements);
2285
+ const executor = new CypherExecutor(graph);
2286
+ return executor.execute(queryObject);
2287
+ };
2288
+
2289
+ // libs/cli/code-graph-core/src/lib/code-graph-core.ts
2290
+ var matchFromGeneratedFiles = (cypherQuery, config, generatedFiles) => {
2291
+ const fileSystem = generatedFiles.reduce((acc, file) => {
2292
+ if (file.filePath.endsWith(".ts")) {
2293
+ acc[file.filePath] = file.code;
2294
+ }
2295
+ return acc;
2296
+ }, {});
2297
+ return matchFromFileSystem(cypherQuery, config, fileSystem);
2298
+ };
2299
+ var matchFromFileSystem = (cypherQuery, config, fileSystem) => {
2300
+ const program = createProgramFromFileSystem(fileSystem);
2301
+ const codeGraph = buildCodeGraph(program, config);
2302
+ const results = query(cypherQuery, codeGraph);
2303
+ return results;
2304
+ };
2305
+ var mapGeneratedFilesToFileSystem = (generatedFiles) => {
2306
+ return generatedFiles.reduce(
2307
+ (acc, file) => {
2308
+ acc[file.filePath] = file.code;
2309
+ return acc;
2310
+ },
2311
+ {}
2312
+ );
2313
+ };
2314
+ var resolveProgramFromConfig = (env) => {
2315
+ return env.tsConfig ? createProgramFromTsConfig(env.tsConfig) : createProgramFromFileSystem(
2316
+ mapGeneratedFilesToFileSystem(env.generatedFiles)
2317
+ );
2318
+ };
2319
+ var autoMatch = (cypherQuery, program, options) => {
2320
+ const queryObject = buildQueryObject(cypherQuery);
2321
+ const config = buildConfigFromQuery(queryObject);
2322
+ const codeGraph = buildCodeGraph(program, config);
2323
+ const results = query(cypherQuery, codeGraph);
2324
+ return options?.skipRewrite ? results : rewriteOccurenceOf(results);
2325
+ };
2326
+ var PATH_ANCHOR_SRC_APP = "src/app";
2327
+ var rewriteUntilPath = (pathOrNodeId, pathAnchor) => {
2328
+ const searchSegment = pathAnchor.startsWith("/") && pathAnchor.endsWith("/") ? pathAnchor : `/${pathAnchor.replace(/^\/|\/$/g, "")}/`;
2329
+ const idx = pathOrNodeId.lastIndexOf(searchSegment);
2330
+ if (idx < 0)
2331
+ return pathOrNodeId;
2332
+ const sliced = pathOrNodeId.slice(idx);
2333
+ return sliced.startsWith("/") ? sliced.slice(1) : sliced;
2334
+ };
2335
+ var rewriteOccurenceOf = (result) => {
2336
+ return result.map((result2) => {
2337
+ return Object.keys(result2).reduce((acc, key) => {
2338
+ if (!result2[key]) {
2339
+ return acc;
2340
+ }
2341
+ const id = result2[key]["id"];
2342
+ if (!id) {
2343
+ return acc;
2344
+ }
2345
+ return {
2346
+ ...acc,
2347
+ [key]: {
2348
+ ...result2[key],
2349
+ id: rewriteUntilPath(id, PATH_ANCHOR_SRC_APP)
2350
+ }
2351
+ };
2352
+ }, {});
2353
+ });
2354
+ };
2355
+ var filterResultsByGeneratedFiles = (results, generatedFiles) => {
2356
+ const filePaths = generatedFiles.map((file) => file.filePath);
2357
+ return results.filter(
2358
+ (result) => Object.values(result).find(
2359
+ (n) => filePaths.includes(n["id"].split(".")[0] + ".ts")
2360
+ )
2361
+ );
2362
+ };
2363
+ var autoMatchFromGeneratedFiles = (cypherQuery, generatedFiles) => {
2364
+ const queryObject = buildQueryObject(cypherQuery);
2365
+ const config = buildConfigFromQuery(queryObject);
2366
+ return matchFromGeneratedFiles(cypherQuery, config, generatedFiles);
2367
+ };
2368
+ var NODE_PROPERTIES_PRESET = {
2369
+ NAMEABLE: {
2370
+ name: `helpers.getDescendantsBy(args.current, (node) => node.kind === args.ts.SyntaxKind.Identifier)[0]?.getText();`,
2371
+ filePath: "args.sourceFile.fileName;"
2372
+ },
2373
+ /**
2374
+ * Creates a preset that extracts a property value from an ObjectLiteralExpression
2375
+ * @param key - The property name to look for in the object literal
2376
+ * @returns An extractor code string that finds the property and returns its initializer text
2377
+ *
2378
+ * @example
2379
+ * ```typescript
2380
+ * properties: {
2381
+ * selector: NODE_PROPERTIES_PRESET.OBJECT_PROPERTY('selector'),
2382
+ * template: NODE_PROPERTIES_PRESET.OBJECT_PROPERTY('template'),
2383
+ * }
2384
+ * ```
2385
+ */
2386
+ OBJECT_LITERAL_PROPERTY: (key, valueFns = []) => {
2387
+ return `(function() {
2388
+ const objectLiterals = helpers.getDescendantsBy(
2389
+ args.current,
2390
+ (node) => node.kind === args.ts.SyntaxKind.ObjectLiteralExpression
2391
+ );
2392
+
2393
+ for (const objLiteral of objectLiterals) {
2394
+ if (args.ts.isObjectLiteralExpression(objLiteral)) {
2395
+ for (const property of objLiteral.properties) {
2396
+ if (args.ts.isPropertyAssignment(property)) {
2397
+ const propName = property.name;
2398
+ let propNameText = '';
2399
+
2400
+ if (args.ts.isIdentifier(propName)) {
2401
+ propNameText = propName.text;
2402
+ } else if (args.ts.isStringLiteral(propName)) {
2403
+ propNameText = propName.text;
2404
+ }
2405
+
2406
+ if (propNameText === '${key}') {
2407
+ const value = property.initializer?.getText() || null;
2408
+ return [${valueFns.join(", ")}].reduce((acc, valueFn) => valueFn(acc), value);
2409
+ }
2410
+ }
2411
+ }
2412
+ }
2413
+ }
2414
+ return null;
2415
+ })();`;
2416
+ }
2417
+ };
2418
+ export {
2419
+ NODE_PROPERTIES_PRESET,
2420
+ autoMatch,
2421
+ autoMatchFromGeneratedFiles,
2422
+ createProgramFromFileSystem,
2423
+ createProgramFromTsConfig,
2424
+ extractFromNodeId,
2425
+ filterResultsByGeneratedFiles,
2426
+ matchFromFileSystem,
2427
+ matchFromGeneratedFiles,
2428
+ resolveProgramFromConfig,
2429
+ rewriteUntilPath
2430
+ };