flowlint 0.3.1 → 0.3.3

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 (77) hide show
  1. package/dist/packages/config/flowlint-config.d.ts +64 -0
  2. package/dist/packages/config/flowlint-config.js +103 -0
  3. package/dist/packages/config/flowlint-config.js.map +1 -0
  4. package/dist/packages/config/index.d.ts +4 -0
  5. package/dist/packages/config/index.js +21 -0
  6. package/dist/packages/config/index.js.map +1 -0
  7. package/dist/packages/github/client.d.ts +2 -0
  8. package/dist/packages/github/client.js +94 -0
  9. package/dist/packages/github/client.js.map +1 -0
  10. package/dist/packages/logger/index.d.ts +11 -0
  11. package/dist/packages/logger/index.js +40 -0
  12. package/dist/packages/logger/index.js.map +1 -0
  13. package/dist/packages/observability/collectors.d.ts +40 -0
  14. package/dist/packages/observability/collectors.js +75 -0
  15. package/dist/packages/observability/collectors.js.map +1 -0
  16. package/dist/packages/observability/index.d.ts +10 -0
  17. package/dist/packages/observability/index.js +35 -0
  18. package/dist/packages/observability/index.js.map +1 -0
  19. package/dist/packages/observability/metrics.d.ts +119 -0
  20. package/dist/packages/observability/metrics.js +194 -0
  21. package/dist/packages/observability/metrics.js.map +1 -0
  22. package/dist/packages/observability/middleware.d.ts +32 -0
  23. package/dist/packages/observability/middleware.js +58 -0
  24. package/dist/packages/observability/middleware.js.map +1 -0
  25. package/dist/packages/review/analysis-engine.d.ts +19 -0
  26. package/dist/packages/review/analysis-engine.js +111 -0
  27. package/dist/packages/review/analysis-engine.js.map +1 -0
  28. package/dist/packages/review/index.d.ts +12 -0
  29. package/dist/packages/review/index.js +29 -0
  30. package/dist/packages/review/index.js.map +1 -0
  31. package/dist/packages/review/parser-n8n.d.ts +2 -0
  32. package/dist/packages/review/parser-n8n.js +118 -0
  33. package/dist/packages/review/parser-n8n.js.map +1 -0
  34. package/dist/packages/review/providers/github.d.ts +62 -0
  35. package/dist/packages/review/providers/github.js +275 -0
  36. package/dist/packages/review/providers/github.js.map +1 -0
  37. package/dist/packages/review/providers.d.ts +106 -0
  38. package/dist/packages/review/providers.js +12 -0
  39. package/dist/packages/review/providers.js.map +1 -0
  40. package/dist/packages/review/reporter.d.ts +17 -0
  41. package/dist/packages/review/reporter.js +62 -0
  42. package/dist/packages/review/reporter.js.map +1 -0
  43. package/dist/packages/review/rules/index.d.ts +9 -0
  44. package/dist/packages/review/rules/index.js +313 -0
  45. package/dist/packages/review/rules/index.js.map +1 -0
  46. package/dist/packages/review/rules/rule-utils.d.ts +36 -0
  47. package/dist/packages/review/rules/rule-utils.js +75 -0
  48. package/dist/packages/review/rules/rule-utils.js.map +1 -0
  49. package/dist/packages/review/schemas/index.d.ts +17 -0
  50. package/dist/packages/review/schemas/index.js +139 -0
  51. package/dist/packages/review/schemas/index.js.map +1 -0
  52. package/dist/packages/review/schemas/n8n-workflow.schema.json +177 -0
  53. package/dist/packages/review/sniffer.d.ts +15 -0
  54. package/dist/packages/review/sniffer.js +47 -0
  55. package/dist/packages/review/sniffer.js.map +1 -0
  56. package/dist/packages/review/types.d.ts +38 -0
  57. package/dist/packages/review/types.js +3 -0
  58. package/dist/packages/review/types.js.map +1 -0
  59. package/dist/packages/review/utils/findings.d.ts +23 -0
  60. package/dist/packages/review/utils/findings.js +34 -0
  61. package/dist/packages/review/utils/findings.js.map +1 -0
  62. package/dist/packages/review/utils/merge.d.ts +12 -0
  63. package/dist/packages/review/utils/merge.js +40 -0
  64. package/dist/packages/review/utils/merge.js.map +1 -0
  65. package/dist/packages/review/utils.d.ts +60 -0
  66. package/dist/packages/review/utils.js +214 -0
  67. package/dist/packages/review/utils.js.map +1 -0
  68. package/dist/packages/tracing/github-tracer.d.ts +38 -0
  69. package/dist/packages/tracing/github-tracer.js +79 -0
  70. package/dist/packages/tracing/github-tracer.js.map +1 -0
  71. package/dist/packages/tracing/index.d.ts +81 -0
  72. package/dist/packages/tracing/index.js +240 -0
  73. package/dist/packages/tracing/index.js.map +1 -0
  74. package/dist/packages/tracing/tracer.d.ts +30 -0
  75. package/dist/packages/tracing/tracer.js +141 -0
  76. package/dist/packages/tracing/tracer.js.map +1 -0
  77. package/package.json +10 -1
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runAllRules = runAllRules;
4
+ const rule_utils_1 = require("./rule-utils");
5
+ const utils_1 = require("../utils");
6
+ // --- Rule Definitions using Helpers ---
7
+ const r1Retry = (0, rule_utils_1.createNodeRule)('R1', 'rate_limit_retry', (node, graph, ctx) => {
8
+ if (!(0, utils_1.isApiNode)(node.type))
9
+ return null;
10
+ const params = (node.params ?? {});
11
+ const options = (params.options ?? {});
12
+ const retryCandidates = [
13
+ options.retryOnFail,
14
+ params.retryOnFail,
15
+ node.flags?.retryOnFail,
16
+ ];
17
+ const retryOnFail = retryCandidates.find((value) => value !== undefined && value !== null);
18
+ if (retryOnFail === true) {
19
+ return null;
20
+ }
21
+ if (typeof retryOnFail === 'string') {
22
+ const normalized = retryOnFail.trim().toLowerCase();
23
+ if (retryOnFail.includes('{{') || normalized === 'true') {
24
+ return null;
25
+ }
26
+ }
27
+ return {
28
+ rule: 'R1',
29
+ severity: 'must',
30
+ path: ctx.path,
31
+ message: `Node ${node.name || node.id} is missing retry/backoff configuration`,
32
+ raw_details: `In the node properties, enable "Retry on Fail" under Options.`,
33
+ nodeId: node.id,
34
+ line: ctx.nodeLines?.[node.id],
35
+ };
36
+ });
37
+ const r2ErrorHandling = (0, rule_utils_1.createNodeRule)('R2', 'error_handling', (node, graph, ctx) => {
38
+ if (ctx.cfg.rules.error_handling.forbid_continue_on_fail && node.flags?.continueOnFail) {
39
+ return {
40
+ rule: 'R2',
41
+ severity: 'must',
42
+ path: ctx.path,
43
+ message: `Node ${node.name || node.id} has continueOnFail enabled (disable it and route errors explicitly)`,
44
+ nodeId: node.id,
45
+ line: ctx.nodeLines?.[node.id],
46
+ raw_details: 'Open the node in n8n and disable "Continue On Fail" (Options > Continue On Fail). Route failures down an explicit error branch instead.',
47
+ };
48
+ }
49
+ return null;
50
+ });
51
+ const r4Secrets = (0, rule_utils_1.createHardcodedStringRule)({
52
+ ruleId: 'R4',
53
+ severity: 'must',
54
+ configKey: 'secrets',
55
+ messageFn: (node) => `Node ${node.name || node.id} contains a hardcoded secret (move it to credentials/env vars)`,
56
+ details: 'Move API keys/tokens into Credentials or environment variables; the workflow should only reference {{$credentials.*}} expressions.',
57
+ });
58
+ const r9ConfigLiterals = (0, rule_utils_1.createHardcodedStringRule)({
59
+ ruleId: 'R9',
60
+ severity: 'should',
61
+ configKey: 'config_literals',
62
+ messageFn: (node, value) => `Node ${node.name || node.id} contains env-specific literal "${value.substring(0, 40)}" (move to expression/credential)`,
63
+ details: 'Move environment-specific URLs/IDs into expressions or credentials (e.g., {{$env.API_BASE_URL}}) so the workflow is portable.',
64
+ });
65
+ const r10NamingConvention = (0, rule_utils_1.createNodeRule)('R10', 'naming_convention', (node, graph, ctx) => {
66
+ const genericNames = new Set(ctx.cfg.rules.naming_convention.generic_names ?? []);
67
+ if (!node.name || genericNames.has(node.name.toLowerCase())) {
68
+ return {
69
+ rule: 'R10',
70
+ severity: 'nit',
71
+ path: ctx.path,
72
+ message: `Node ${node.id} uses a generic name "${node.name ?? ''}" (rename it to describe the action)`,
73
+ nodeId: node.id,
74
+ line: ctx.nodeLines?.[node.id],
75
+ raw_details: 'Rename the node to describe its purpose (e.g., "Check subscription status" instead of "IF") for easier reviews and debugging.',
76
+ };
77
+ }
78
+ return null;
79
+ });
80
+ const DEPRECATED_NODES = {
81
+ 'n8n-nodes-base.splitInBatches': 'Use Loop over items instead',
82
+ 'n8n-nodes-base.executeWorkflow': 'Use Execute Workflow (Sub-Workflow) instead',
83
+ };
84
+ const r11DeprecatedNodes = (0, rule_utils_1.createNodeRule)('R11', 'deprecated_nodes', (node, graph, ctx) => {
85
+ if (DEPRECATED_NODES[node.type]) {
86
+ return {
87
+ rule: 'R11',
88
+ severity: 'should',
89
+ path: ctx.path,
90
+ message: `Node ${node.name || node.id} uses deprecated type ${node.type} (replace with ${DEPRECATED_NODES[node.type]})`,
91
+ nodeId: node.id,
92
+ line: ctx.nodeLines?.[node.id],
93
+ raw_details: `Replace this node with ${DEPRECATED_NODES[node.type]} so future n8n upgrades don’t break the workflow.`,
94
+ };
95
+ }
96
+ return null;
97
+ });
98
+ const r12UnhandledErrorPath = (0, rule_utils_1.createNodeRule)('R12', 'unhandled_error_path', (node, graph, ctx) => {
99
+ if (!(0, utils_1.isErrorProneNode)(node.type))
100
+ return null;
101
+ const hasErrorPath = graph.edges.some((edge) => {
102
+ if (edge.from !== node.id)
103
+ return false;
104
+ if (edge.on === 'error')
105
+ return true;
106
+ const targetNode = graph.nodes.find((candidate) => candidate.id === edge.to);
107
+ return targetNode ? (0, utils_1.isErrorHandlerNode)(targetNode.type, targetNode.name) : false;
108
+ });
109
+ if (!hasErrorPath) {
110
+ return {
111
+ rule: 'R12',
112
+ severity: 'must',
113
+ path: ctx.path,
114
+ message: `Node ${node.name || node.id} has no error branch (add a red connector to handler)`,
115
+ nodeId: node.id,
116
+ line: ctx.nodeLines?.[node.id],
117
+ raw_details: 'Add an error (red) branch to a Stop and Error or logging/alert node so failures do not disappear silently.',
118
+ };
119
+ }
120
+ return null;
121
+ });
122
+ // --- Rules with custom logic (not fitting the simple node-by-node pattern) ---
123
+ function r3Idempotency(graph, ctx) {
124
+ const cfg = ctx.cfg.rules.idempotency;
125
+ if (!cfg?.enabled)
126
+ return [];
127
+ const hasIngress = graph.nodes.some((node) => /webhook|trigger|start/i.test(node.type));
128
+ if (!hasIngress)
129
+ return [];
130
+ const mutationNodes = graph.nodes.filter((node) => (0, utils_1.isMutationNode)(node.type));
131
+ if (!mutationNodes.length)
132
+ return [];
133
+ const findings = [];
134
+ for (const mutationNode of mutationNodes) {
135
+ const upstreamNodeIds = (0, utils_1.findAllUpstreamNodes)(graph, mutationNode.id);
136
+ const upstreamNodes = graph.nodes.filter((n) => upstreamNodeIds.has(n.id));
137
+ const hasGuard = upstreamNodes.some((p) => (0, utils_1.containsCandidate)(p.params, cfg.key_field_candidates));
138
+ if (!hasGuard) {
139
+ findings.push({
140
+ rule: 'R3',
141
+ severity: 'must',
142
+ path: ctx.path,
143
+ message: `The mutation path ending at "${mutationNode.name || mutationNode.id}" appears to be missing an idempotency guard.`,
144
+ raw_details: `Ensure one of the upstream nodes or the mutation node itself uses an idempotency key, such as one of: ${cfg.key_field_candidates.join(', ')}`,
145
+ nodeId: mutationNode.id,
146
+ line: ctx.nodeLines?.[mutationNode.id],
147
+ });
148
+ }
149
+ }
150
+ return findings;
151
+ }
152
+ function r5DeadEnds(graph, ctx) {
153
+ const cfg = ctx.cfg.rules.dead_ends;
154
+ if (!cfg?.enabled)
155
+ return [];
156
+ if (graph.nodes.length <= 1)
157
+ return [];
158
+ const outgoing = new Map();
159
+ for (const node of graph.nodes)
160
+ outgoing.set(node.id, 0);
161
+ for (const edge of graph.edges)
162
+ outgoing.set(edge.from, (outgoing.get(edge.from) || 0) + 1);
163
+ const findings = [];
164
+ for (const node of graph.nodes) {
165
+ if ((outgoing.get(node.id) || 0) === 0 && !(0, utils_1.isTerminalNode)(node.type, node.name)) {
166
+ findings.push({
167
+ rule: 'R5',
168
+ severity: 'nit',
169
+ path: ctx.path,
170
+ message: `Node ${node.name || node.id} has no outgoing connections (either wire it up or remove it)`,
171
+ nodeId: node.id,
172
+ line: ctx.nodeLines?.[node.id],
173
+ raw_details: 'Either remove this node as dead code or connect it to the next/safe step so the workflow can continue.',
174
+ });
175
+ }
176
+ }
177
+ return findings;
178
+ }
179
+ function r6LongRunning(graph, ctx) {
180
+ const cfg = ctx.cfg.rules.long_running;
181
+ if (!cfg?.enabled)
182
+ return [];
183
+ const findings = [];
184
+ const loopNodes = graph.nodes.filter((node) => /loop|batch|while|repeat/i.test(node.type));
185
+ for (const node of loopNodes) {
186
+ const iterations = (0, utils_1.readNumber)(node.params, [
187
+ 'maxIterations',
188
+ 'maxIteration',
189
+ 'limit',
190
+ 'options.maxIterations',
191
+ ]);
192
+ if (!iterations || (cfg.max_iterations && iterations > cfg.max_iterations)) {
193
+ findings.push({
194
+ rule: 'R6',
195
+ severity: 'should',
196
+ path: ctx.path,
197
+ message: `Node ${node.name || node.id} allows ${iterations ?? 'unbounded'} iterations (limit ${cfg.max_iterations}; set a lower cap)`,
198
+ nodeId: node.id,
199
+ line: ctx.nodeLines?.[node.id],
200
+ raw_details: `Set Options > Max iterations to ≤ ${cfg.max_iterations} or split the processing into smaller batches.`,
201
+ });
202
+ }
203
+ if (cfg.timeout_ms) {
204
+ const timeout = (0, utils_1.readNumber)(node.params, ['timeout', 'timeoutMs', 'options.timeout']);
205
+ if (timeout && timeout > cfg.timeout_ms) {
206
+ findings.push({
207
+ rule: 'R6',
208
+ severity: 'should',
209
+ path: ctx.path,
210
+ message: `Node ${node.name || node.id} uses timeout ${timeout}ms (limit ${cfg.timeout_ms}ms; shorten the timeout or break work apart)`,
211
+ nodeId: node.id,
212
+ line: ctx.nodeLines?.[node.id],
213
+ raw_details: `Lower the timeout to ≤ ${cfg.timeout_ms}ms or split the workflow so no single step blocks for too long.`,
214
+ });
215
+ }
216
+ }
217
+ }
218
+ return findings;
219
+ }
220
+ function r7AlertLogEnforcement(graph, ctx) {
221
+ const cfg = ctx.cfg.rules.alert_log_enforcement;
222
+ if (!cfg?.enabled)
223
+ return [];
224
+ const findings = [];
225
+ const errorEdges = graph.edges.filter((edge) => edge.on === 'error');
226
+ for (const edge of errorEdges) {
227
+ const fromNode = graph.nodes.find((n) => n.id === edge.from);
228
+ let isHandled = false;
229
+ const queue = [edge.to];
230
+ const visited = new Set([edge.to]);
231
+ let head = 0;
232
+ while (head < queue.length) {
233
+ const currentId = queue[head++];
234
+ const currentNode = graph.nodes.find((n) => n.id === currentId);
235
+ if ((0, utils_1.isNotificationNode)(currentNode.type)) {
236
+ isHandled = true;
237
+ break; // Found a handler, stop searching this path
238
+ }
239
+ if ((0, utils_1.isRejoinNode)(graph, currentId)) {
240
+ continue; // It's a rejoin point, but not a handler, so stop traversing this path
241
+ }
242
+ // Add successors to queue
243
+ const outgoing = graph.edges.filter((e) => e.from === currentId);
244
+ for (const outEdge of outgoing) {
245
+ if (!visited.has(outEdge.to)) {
246
+ visited.add(outEdge.to);
247
+ queue.push(outEdge.to);
248
+ }
249
+ }
250
+ }
251
+ if (!isHandled) {
252
+ findings.push({
253
+ rule: 'R7',
254
+ severity: 'should',
255
+ path: ctx.path,
256
+ message: `Error path from node ${fromNode.name || fromNode.id} has no log/alert before rejoining (add notification node)`,
257
+ nodeId: fromNode.id,
258
+ line: ctx.nodeLines?.[fromNode.id],
259
+ raw_details: 'Add a Slack/Email/Log node on the error branch before it rejoins the main flow so failures leave an audit trail.',
260
+ });
261
+ }
262
+ }
263
+ return findings;
264
+ }
265
+ function r8UnusedData(graph, ctx) {
266
+ const cfg = ctx.cfg.rules.unused_data;
267
+ if (!cfg?.enabled)
268
+ return [];
269
+ const findings = [];
270
+ for (const node of graph.nodes) {
271
+ // If a node has no successors, R5 handles it. If it's a terminal node, its "use" is to end the flow.
272
+ if ((0, utils_1.isTerminalNode)(node.type, node.name) || !graph.edges.some((e) => e.from === node.id)) {
273
+ continue;
274
+ }
275
+ const downstreamNodes = (0, utils_1.findAllDownstreamNodes)(graph, node.id);
276
+ downstreamNodes.delete(node.id);
277
+ const leadsToConsumer = [...downstreamNodes].some((id) => {
278
+ const downstreamNode = graph.nodes.find((n) => n.id === id);
279
+ return (0, utils_1.isMeaningfulConsumer)(downstreamNode);
280
+ });
281
+ if (!leadsToConsumer) {
282
+ findings.push({
283
+ rule: 'R8',
284
+ severity: 'nit',
285
+ path: ctx.path,
286
+ message: `Node "${node.name || node.id}" produces data that never reaches any consumer`,
287
+ nodeId: node.id,
288
+ line: ctx.nodeLines?.[node.id],
289
+ raw_details: 'Wire this branch into a consumer (DB/API/response) or remove it—otherwise the data produced here is never used.',
290
+ });
291
+ }
292
+ }
293
+ return findings;
294
+ }
295
+ // --- Rule Registration ---
296
+ const rules = [
297
+ r1Retry,
298
+ r2ErrorHandling,
299
+ r3Idempotency,
300
+ r4Secrets,
301
+ r5DeadEnds,
302
+ r6LongRunning,
303
+ r7AlertLogEnforcement,
304
+ r8UnusedData,
305
+ r9ConfigLiterals,
306
+ r10NamingConvention,
307
+ r11DeprecatedNodes,
308
+ r12UnhandledErrorPath,
309
+ ];
310
+ function runAllRules(graph, ctx) {
311
+ return rules.flatMap((rule) => rule(graph, ctx));
312
+ }
313
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/review/rules/index.ts"],"names":[],"mappings":";;AAyXA,kCAEC;AAzXD,6CAAyE;AACzE,oCAakB;AAMlB,yCAAyC;AAEzC,MAAM,OAAO,GAAG,IAAA,2BAAc,EAAC,IAAI,EAAE,kBAAkB,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IAC5E,IAAI,CAAC,IAAA,iBAAS,EAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvC,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAA4B,CAAC;IAC9D,MAAM,OAAO,GAAG,CAAE,MAAc,CAAC,OAAO,IAAI,EAAE,CAA4B,CAAC;IAE3E,MAAM,eAAe,GAAc;QACjC,OAAO,CAAC,WAAW;QAClB,MAAc,CAAC,WAAW;QAC3B,IAAI,CAAC,KAAK,EAAE,WAAW;KACxB,CAAC;IAEF,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC;IAE3F,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACpD,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;YACxD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,IAAI;QACV,QAAQ,EAAE,MAAM;QAChB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,OAAO,EAAE,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,yCAAyC;QAC9E,WAAW,EAAE,+DAA+D;QAC5E,MAAM,EAAE,IAAI,CAAC,EAAE;QACf,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;KAC/B,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,IAAA,2BAAc,EAAC,IAAI,EAAE,gBAAgB,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IAClF,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,uBAAuB,IAAI,IAAI,CAAC,KAAK,EAAE,cAAc,EAAE,CAAC;QACvF,OAAO;YACL,IAAI,EAAE,IAAI;YACV,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,OAAO,EAAE,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,sEAAsE;YAC3G,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,WAAW,EACT,yIAAyI;SAC5I,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,IAAA,sCAAyB,EAAC;IAC1C,MAAM,EAAE,IAAI;IACZ,QAAQ,EAAE,MAAM;IAChB,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,gEAAgE;IACjH,OAAO,EAAE,oIAAoI;CAC9I,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,IAAA,sCAAyB,EAAC;IACjD,MAAM,EAAE,IAAI;IACZ,QAAQ,EAAE,QAAQ;IAClB,SAAS,EAAE,iBAAiB;IAC5B,SAAS,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,mCAAmC,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,mCAAmC;IACpJ,OAAO,EAAE,+HAA+H;CACzI,CAAC,CAAC;AAEH,MAAM,mBAAmB,GAAG,IAAA,2BAAc,EAAC,KAAK,EAAE,mBAAmB,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IAC1F,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;IAClF,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QAC5D,OAAO;YACL,IAAI,EAAE,KAAK;YACX,QAAQ,EAAE,KAAK;YACf,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,OAAO,EAAE,QAAQ,IAAI,CAAC,EAAE,yBAAyB,IAAI,CAAC,IAAI,IAAI,EAAE,sCAAsC;YACtG,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,WAAW,EAAE,+HAA+H;SAC7I,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAA2B;IAC/C,+BAA+B,EAAE,6BAA6B;IAC9D,gCAAgC,EAAE,6CAA6C;CAChF,CAAC;AAEF,MAAM,kBAAkB,GAAG,IAAA,2BAAc,EAAC,KAAK,EAAE,kBAAkB,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IACxF,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,OAAO;YACL,IAAI,EAAE,KAAK;YACX,QAAQ,EAAE,QAAQ;YAClB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,OAAO,EAAE,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,yBAAyB,IAAI,CAAC,IAAI,kBAAkB,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YACvH,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,WAAW,EAAE,0BAA0B,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,mDAAmD;SACtH,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC,CAAC;AAEH,MAAM,qBAAqB,GAAG,IAAA,2BAAc,EAAC,KAAK,EAAE,sBAAsB,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IAC/F,IAAI,CAAC,IAAA,wBAAgB,EAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE9C,MAAM,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;QAC7C,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC;QACxC,IAAI,IAAI,CAAC,EAAE,KAAK,OAAO;YAAE,OAAO,IAAI,CAAC;QAErC,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7E,OAAO,UAAU,CAAC,CAAC,CAAC,IAAA,0BAAkB,EAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACnF,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO;YACL,IAAI,EAAE,KAAK;YACX,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,OAAO,EAAE,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,uDAAuD;YAC5F,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,WAAW,EACT,4GAA4G;SAC/G,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC,CAAC;AAGH,gFAAgF;AAEhF,SAAS,aAAa,CAAC,KAAY,EAAE,GAAgB;IACnD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC;IACtC,IAAI,CAAC,GAAG,EAAE,OAAO;QAAE,OAAO,EAAE,CAAC;IAE7B,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACxF,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,CAAC;IAE3B,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAA,sBAAc,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9E,IAAI,CAAC,aAAa,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAErC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;QACzC,MAAM,eAAe,GAAG,IAAA,4BAAoB,EAAC,KAAK,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;QACrE,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAE3E,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CACxC,IAAA,yBAAiB,EAAC,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,oBAAoB,CAAC,CACtD,CAAC;QAEF,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,IAAI;gBACV,QAAQ,EAAE,MAAM;gBAChB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,OAAO,EAAE,gCACP,YAAY,CAAC,IAAI,IAAI,YAAY,CAAC,EACpC,+CAA+C;gBAC/C,WAAW,EAAE,yGAAyG,GAAG,CAAC,oBAAoB,CAAC,IAAI,CACjJ,IAAI,CACL,EAAE;gBACH,MAAM,EAAE,YAAY,CAAC,EAAE;gBACvB,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC;aACvC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,UAAU,CAAC,KAAY,EAAE,GAAgB;IAChD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC;IACpC,IAAI,CAAC,GAAG,EAAE,OAAO;QAAE,OAAO,EAAE,CAAC;IAC7B,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAEvC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK;QAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACzD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK;QAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAE5F,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAA,sBAAc,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChF,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,IAAI;gBACV,QAAQ,EAAE,KAAK;gBACf,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,OAAO,EAAE,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,+DAA+D;gBACpG,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9B,WAAW,EAAE,wGAAwG;aACtH,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,aAAa,CAAC,KAAY,EAAE,GAAgB;IACnD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC;IACvC,IAAI,CAAC,GAAG,EAAE,OAAO;QAAE,OAAO,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAE3F,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,IAAA,kBAAU,EAAC,IAAI,CAAC,MAAM,EAAE;YACzC,eAAe;YACf,cAAc;YACd,OAAO;YACP,uBAAuB;SACxB,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,IAAI,CAAC,GAAG,CAAC,cAAc,IAAI,UAAU,GAAG,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;YAC3E,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,IAAI;gBACV,QAAQ,EAAE,QAAQ;gBAClB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,OAAO,EAAE,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,WACnC,UAAU,IAAI,WAChB,sBAAsB,GAAG,CAAC,cAAc,oBAAoB;gBAC5D,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9B,WAAW,EAAE,qCAAqC,GAAG,CAAC,cAAc,gDAAgD;aACrH,CAAC,CAAC;QACL,CAAC;QAED,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YACnB,MAAM,OAAO,GAAG,IAAA,kBAAU,EAAC,IAAI,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;YACrF,IAAI,OAAO,IAAI,OAAO,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;gBACtC,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,IAAI;oBACV,QAAQ,EAAE,QAAQ;oBAClB,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,OAAO,EAAE,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,iBAAiB,OAAO,aAC3D,GAAG,CAAC,UACN,8CAA8C;oBAChD,MAAM,EAAE,IAAI,CAAC,EAAE;oBACf,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC9B,WAAW,EAAE,0BAA0B,GAAG,CAAC,UAAU,iEAAiE;iBACvH,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAY,EAAE,GAAgB;IAC3D,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,qBAAqB,CAAC;IAChD,IAAI,CAAC,GAAG,EAAE,OAAO;QAAE,OAAO,EAAE,CAAC;IAE7B,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;IAErE,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,IAAI,CAAE,CAAC;QAC9D,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,MAAM,KAAK,GAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAE3C,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,OAAO,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,EAAE,CAAE,CAAC;YACjC,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,CAAE,CAAC;YAEjE,IAAI,IAAA,0BAAkB,EAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzC,SAAS,GAAG,IAAI,CAAC;gBACjB,MAAM,CAAC,4CAA4C;YACrD,CAAC;YAED,IAAI,IAAA,oBAAY,EAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;gBACnC,SAAS,CAAC,uEAAuE;YACnF,CAAC;YAED,0BAA0B;YAC1B,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;YACjE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC/B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;oBAC7B,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACxB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,IAAI;gBACV,QAAQ,EAAE,QAAQ;gBAClB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,OAAO,EAAE,wBACP,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,EAC5B,4DAA4D;gBAC5D,MAAM,EAAE,QAAQ,CAAC,EAAE;gBACnB,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAClC,WAAW,EAAE,kHAAkH;aAChI,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,YAAY,CAAC,KAAY,EAAE,GAAgB;IAClD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC;IACtC,IAAI,CAAC,GAAG,EAAE,OAAO;QAAE,OAAO,EAAE,CAAC;IAE7B,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,qGAAqG;QACrG,IAAI,IAAA,sBAAc,EAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YACzF,SAAS;QACX,CAAC;QAED,MAAM,eAAe,GAAG,IAAA,8BAAsB,EAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/D,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEhC,MAAM,eAAe,GAAG,CAAC,GAAG,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE;YACvD,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAE,CAAC;YAC7D,OAAO,IAAA,4BAAoB,EAAC,cAAc,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,IAAI;gBACV,QAAQ,EAAE,KAAK;gBACf,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,OAAO,EAAE,SAAS,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,iDAAiD;gBACvF,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9B,WAAW,EAAE,iHAAiH;aAC/H,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,4BAA4B;AAE5B,MAAM,KAAK,GAAiB;IAC1B,OAAO;IACP,eAAe;IACf,aAAa;IACb,SAAS;IACT,UAAU;IACV,aAAa;IACb,qBAAqB;IACrB,YAAY;IACZ,gBAAgB;IAChB,mBAAmB;IACnB,kBAAkB;IAClB,qBAAqB;CACtB,CAAC;AAEF,SAAgB,WAAW,CAAC,KAAY,EAAE,GAAgB;IACxD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { Graph, Finding, NodeRef, FindingSeverity } from '../types';
2
+ import type { FlowLintConfig } from '../../config/flowlint-config';
3
+ type Rule = string;
4
+ type RuleContext = {
5
+ path: string;
6
+ cfg: FlowLintConfig;
7
+ nodeLines?: Record<string, number>;
8
+ };
9
+ type RuleRunner = (graph: Graph, ctx: RuleContext) => Finding[];
10
+ type NodeRuleLogic = (node: NodeRef, graph: Graph, ctx: RuleContext) => Finding | Finding[] | null;
11
+ /**
12
+ * A higher-order function to create a rule that iterates over each node in the graph.
13
+ * It abstracts the boilerplate of checking if the rule is enabled and iterating through nodes.
14
+ *
15
+ * @param ruleId - The ID of the rule (e.g., 'R1').
16
+ * @param configKey - The key in the FlowLintConfig rules object.
17
+ * @param logic - The function containing the core logic to be executed for each node.
18
+ * @returns A RuleRunner function.
19
+ */
20
+ export declare function createNodeRule(ruleId: Rule, configKey: keyof FlowLintConfig['rules'], logic: NodeRuleLogic): RuleRunner;
21
+ type HardcodedStringRuleOptions = {
22
+ ruleId: Rule;
23
+ severity: FindingSeverity;
24
+ configKey: 'secrets' | 'config_literals';
25
+ messageFn: (node: NodeRef, value: string) => string;
26
+ details: string;
27
+ };
28
+ /**
29
+ * Creates a rule that checks for hardcoded strings in node parameters based on a denylist of regex patterns.
30
+ * This is used to create R4 (Secrets) and R9 (Config Literals).
31
+ *
32
+ * @param options - The configuration for the hardcoded string rule.
33
+ * @returns A RuleRunner function.
34
+ */
35
+ export declare function createHardcodedStringRule({ ruleId, severity, configKey, messageFn, details, }: HardcodedStringRuleOptions): RuleRunner;
36
+ export {};
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createNodeRule = createNodeRule;
4
+ exports.createHardcodedStringRule = createHardcodedStringRule;
5
+ const utils_1 = require("../utils");
6
+ /**
7
+ * A higher-order function to create a rule that iterates over each node in the graph.
8
+ * It abstracts the boilerplate of checking if the rule is enabled and iterating through nodes.
9
+ *
10
+ * @param ruleId - The ID of the rule (e.g., 'R1').
11
+ * @param configKey - The key in the FlowLintConfig rules object.
12
+ * @param logic - The function containing the core logic to be executed for each node.
13
+ * @returns A RuleRunner function.
14
+ */
15
+ function createNodeRule(ruleId, configKey, logic) {
16
+ return (graph, ctx) => {
17
+ const ruleConfig = ctx.cfg.rules[configKey];
18
+ if (!ruleConfig?.enabled) {
19
+ return [];
20
+ }
21
+ const findings = [];
22
+ for (const node of graph.nodes) {
23
+ const result = logic(node, graph, ctx);
24
+ if (result) {
25
+ if (Array.isArray(result)) {
26
+ findings.push(...result);
27
+ }
28
+ else {
29
+ findings.push(result);
30
+ }
31
+ }
32
+ }
33
+ return findings;
34
+ };
35
+ }
36
+ /**
37
+ * Creates a rule that checks for hardcoded strings in node parameters based on a denylist of regex patterns.
38
+ * This is used to create R4 (Secrets) and R9 (Config Literals).
39
+ *
40
+ * @param options - The configuration for the hardcoded string rule.
41
+ * @returns A RuleRunner function.
42
+ */
43
+ function createHardcodedStringRule({ ruleId, severity, configKey, messageFn, details, }) {
44
+ const logic = (node, graph, ctx) => {
45
+ const cfg = ctx.cfg.rules[configKey];
46
+ if (!cfg.denylist_regex?.length) {
47
+ return null;
48
+ }
49
+ const regexes = cfg.denylist_regex.map((pattern) => (0, utils_1.toRegex)(pattern));
50
+ const findings = [];
51
+ const strings = (0, utils_1.collectStrings)(node.params);
52
+ for (const value of strings) {
53
+ // Ignore expressions and empty strings
54
+ if (!value || value.includes('{{')) {
55
+ continue;
56
+ }
57
+ if (regexes.some((regex) => regex.test(value))) {
58
+ findings.push({
59
+ rule: ruleId,
60
+ severity,
61
+ path: ctx.path,
62
+ message: messageFn(node, value),
63
+ nodeId: node.id,
64
+ line: ctx.nodeLines?.[node.id],
65
+ raw_details: details,
66
+ });
67
+ // Only report one finding per node to avoid noise
68
+ break;
69
+ }
70
+ }
71
+ return findings;
72
+ };
73
+ return createNodeRule(ruleId, configKey, logic);
74
+ }
75
+ //# sourceMappingURL=rule-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-utils.js","sourceRoot":"","sources":["../../../../packages/review/rules/rule-utils.ts"],"names":[],"mappings":";;AAkBA,wCAwBC;AAiBD,8DAyCC;AAlGD,oCAAmD;AAOnD;;;;;;;;GAQG;AACH,SAAgB,cAAc,CAC5B,MAAY,EACZ,SAAwC,EACxC,KAAoB;IAEpB,OAAO,CAAC,KAAY,EAAE,GAAgB,EAAa,EAAE;QACnD,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAA0B,CAAC;QACrE,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC;YACzB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;YACvC,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC1B,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;gBAC3B,CAAC;qBAAM,CAAC;oBACN,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACxB,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC;AAUD;;;;;;GAMG;AACH,SAAgB,yBAAyB,CAAC,EACxC,MAAM,EACN,QAAQ,EACR,SAAS,EACT,SAAS,EACT,OAAO,GACoB;IAC3B,MAAM,KAAK,GAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAChD,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAA,eAAO,EAAC,OAAO,CAAC,CAAC,CAAC;QAEtE,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,IAAA,sBAAc,EAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAE5C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,uCAAuC;YACvC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,SAAS;YACX,CAAC;YAED,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC/C,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,QAAQ;oBACR,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,OAAO,EAAE,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC;oBAC/B,MAAM,EAAE,IAAI,CAAC,EAAE;oBACf,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC9B,WAAW,EAAE,OAAO;iBACrB,CAAC,CAAC;gBACH,kDAAkD;gBAClD,MAAM;YACR,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,17 @@
1
+ export declare class ValidationError extends Error {
2
+ errors: Array<{
3
+ path: string;
4
+ message: string;
5
+ suggestion?: string;
6
+ }>;
7
+ constructor(errors: Array<{
8
+ path: string;
9
+ message: string;
10
+ suggestion?: string;
11
+ }>);
12
+ }
13
+ /**
14
+ * Validate n8n workflow structure
15
+ * Throws ValidationError with detailed messages if validation fails
16
+ */
17
+ export declare function validateN8nWorkflow(data: any): void;
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ValidationError = void 0;
7
+ exports.validateN8nWorkflow = validateN8nWorkflow;
8
+ const ajv_1 = __importDefault(require("ajv"));
9
+ const ajv_formats_1 = __importDefault(require("ajv-formats"));
10
+ const n8n_workflow_schema_json_1 = __importDefault(require("./n8n-workflow.schema.json"));
11
+ const utils_1 = require("../utils");
12
+ // Custom error class for validation failures
13
+ class ValidationError extends Error {
14
+ constructor(errors) {
15
+ super(`Workflow validation failed: ${errors.length} error(s)`);
16
+ this.errors = errors;
17
+ this.name = 'ValidationError';
18
+ }
19
+ }
20
+ exports.ValidationError = ValidationError;
21
+ // Create and configure AJV instance
22
+ const ajv = new ajv_1.default({
23
+ allErrors: true,
24
+ strict: false,
25
+ verbose: true,
26
+ });
27
+ (0, ajv_formats_1.default)(ajv);
28
+ // Compile the schema
29
+ const validate = ajv.compile(n8n_workflow_schema_json_1.default);
30
+ /**
31
+ * Throws a ValidationError if the provided set contains items.
32
+ * Centralizes the pattern of checking validation results and throwing errors.
33
+ *
34
+ * @param items - Set of items that represent validation failures
35
+ * @param config - Configuration for building error messages
36
+ * @throws ValidationError if items set is not empty
37
+ */
38
+ function throwIfInvalid(items, config) {
39
+ if (items.size > 0) {
40
+ const errors = (0, utils_1.buildValidationErrors)(items, config);
41
+ throw new ValidationError(errors);
42
+ }
43
+ }
44
+ /**
45
+ * Check for duplicate node IDs in the workflow
46
+ */
47
+ function checkDuplicateNodeIds(data) {
48
+ if (!Array.isArray(data.nodes))
49
+ return;
50
+ const seen = new Set();
51
+ const duplicates = new Set();
52
+ for (const node of data.nodes) {
53
+ if (node.id && seen.has(node.id)) {
54
+ duplicates.add(node.id);
55
+ }
56
+ if (node.id) {
57
+ seen.add(node.id);
58
+ }
59
+ }
60
+ throwIfInvalid(duplicates, {
61
+ path: 'nodes[].id',
62
+ messageTemplate: (id) => `Duplicate node ID: "${id}"`,
63
+ suggestionTemplate: (id) => `Each node must have a unique ID. Remove or rename the duplicate node with ID "${id}".`,
64
+ });
65
+ }
66
+ /**
67
+ * Check for orphaned connections (references to non-existent nodes)
68
+ */
69
+ function checkOrphanedConnections(data) {
70
+ if (!data.connections || !Array.isArray(data.nodes))
71
+ return;
72
+ const nodeIds = new Set();
73
+ const nodeNames = new Set();
74
+ // Collect all node IDs and names
75
+ for (const node of data.nodes) {
76
+ if (node.id)
77
+ nodeIds.add(node.id);
78
+ if (node.name)
79
+ nodeNames.add(node.name);
80
+ }
81
+ const orphanedRefs = new Set();
82
+ // Check all connection targets
83
+ Object.entries(data.connections).forEach(([sourceId, channels]) => {
84
+ // Check if source exists
85
+ if (!nodeIds.has(sourceId) && !nodeNames.has(sourceId)) {
86
+ orphanedRefs.add(sourceId);
87
+ }
88
+ // Check targets
89
+ if (typeof channels === 'object' && channels !== null) {
90
+ Object.values(channels).forEach((connArray) => {
91
+ const flatConnections = (0, utils_1.flattenConnections)(connArray);
92
+ flatConnections.forEach((conn) => {
93
+ if (conn?.node) {
94
+ if (!nodeIds.has(conn.node) && !nodeNames.has(conn.node)) {
95
+ orphanedRefs.add(conn.node);
96
+ }
97
+ }
98
+ });
99
+ });
100
+ }
101
+ });
102
+ throwIfInvalid(orphanedRefs, {
103
+ path: 'connections',
104
+ messageTemplate: (ref) => `Orphaned connection reference: "${ref}"`,
105
+ suggestionTemplate: (ref) => `Connection references node "${ref}" which does not exist. Add the missing node or remove the invalid connection.`,
106
+ });
107
+ }
108
+ /**
109
+ * Validate n8n workflow structure
110
+ * Throws ValidationError with detailed messages if validation fails
111
+ */
112
+ function validateN8nWorkflow(data) {
113
+ // Basic schema validation
114
+ if (!validate(data)) {
115
+ const errors = (validate.errors || []).map((err) => {
116
+ const path = err.instancePath || err.schemaPath;
117
+ const message = err.message || 'Validation error';
118
+ let suggestion = '';
119
+ // Provide helpful suggestions based on error type
120
+ if (err.keyword === 'required') {
121
+ const missing = err.params?.missingProperty;
122
+ suggestion = `Add the required field "${missing}" to the workflow.`;
123
+ }
124
+ else if (err.keyword === 'type') {
125
+ const expected = err.params?.type;
126
+ suggestion = `The field should be of type "${expected}".`;
127
+ }
128
+ else if (err.keyword === 'minLength') {
129
+ suggestion = 'This field cannot be empty.';
130
+ }
131
+ return { path, message, suggestion };
132
+ });
133
+ throw new ValidationError(errors);
134
+ }
135
+ // Additional custom validations
136
+ checkDuplicateNodeIds(data);
137
+ checkOrphanedConnections(data);
138
+ }
139
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../packages/review/schemas/index.ts"],"names":[],"mappings":"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"}