cloudflare-expression-lint 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +57 -0
- package/LICENSE +21 -0
- package/README.md +375 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.js +363 -0
- package/dist/cli.js.map +1 -0
- package/dist/eslint-plugin.d.ts +67 -0
- package/dist/eslint-plugin.js +211 -0
- package/dist/eslint-plugin.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer.d.ts +11 -0
- package/dist/lexer.js +416 -0
- package/dist/lexer.js.map +1 -0
- package/dist/parser.d.ts +16 -0
- package/dist/parser.js +320 -0
- package/dist/parser.js.map +1 -0
- package/dist/schemas/fields.d.ts +44 -0
- package/dist/schemas/fields.js +282 -0
- package/dist/schemas/fields.js.map +1 -0
- package/dist/schemas/functions.d.ts +33 -0
- package/dist/schemas/functions.js +261 -0
- package/dist/schemas/functions.js.map +1 -0
- package/dist/schemas/operators.d.ts +28 -0
- package/dist/schemas/operators.js +37 -0
- package/dist/schemas/operators.js.map +1 -0
- package/dist/types.d.ts +149 -0
- package/dist/types.js +38 -0
- package/dist/types.js.map +1 -0
- package/dist/validator.d.ts +18 -0
- package/dist/validator.js +420 -0
- package/dist/validator.js.map +1 -0
- package/dist/yaml-scanner.d.ts +97 -0
- package/dist/yaml-scanner.js +175 -0
- package/dist/yaml-scanner.js.map +1 -0
- package/package.json +80 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI for cloudflare-expression-lint.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* cf-expr-lint [options] <files...>
|
|
7
|
+
* cf-expr-lint --expression "http.host eq \"test.com\""
|
|
8
|
+
* cf-expr-lint --config .cf-expr-lint.json config/**\/*.yaml
|
|
9
|
+
*
|
|
10
|
+
* Options:
|
|
11
|
+
* --expression, -e Validate a single expression string
|
|
12
|
+
* --stdin Read expression from stdin
|
|
13
|
+
* --type, -t Expression type: filter, rewrite_url, rewrite_header, redirect_target
|
|
14
|
+
* --phase, -p Cloudflare phase (e.g., http_request_firewall_custom)
|
|
15
|
+
* --config, -c Path to config file (.json) with custom mappings
|
|
16
|
+
* --expr-key Add expression key mapping: key:type[:phase]
|
|
17
|
+
* --phase-map Add phase mapping: yaml_key:phase_name
|
|
18
|
+
* --format, -f Output format: text (default), json
|
|
19
|
+
* --quiet, -q Only output errors (suppress warnings)
|
|
20
|
+
* --help, -h Show this help message
|
|
21
|
+
*/
|
|
22
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
23
|
+
import { resolve } from 'node:path';
|
|
24
|
+
import { glob } from 'glob';
|
|
25
|
+
import { validate } from './validator.js';
|
|
26
|
+
import { scanYaml } from './yaml-scanner.js';
|
|
27
|
+
function parseArgs(argv) {
|
|
28
|
+
const opts = {
|
|
29
|
+
files: [],
|
|
30
|
+
stdin: false,
|
|
31
|
+
type: 'filter',
|
|
32
|
+
exprKeys: [],
|
|
33
|
+
phaseMaps: [],
|
|
34
|
+
format: 'text',
|
|
35
|
+
quiet: false,
|
|
36
|
+
help: false,
|
|
37
|
+
};
|
|
38
|
+
let i = 0;
|
|
39
|
+
while (i < argv.length) {
|
|
40
|
+
const arg = argv[i];
|
|
41
|
+
switch (arg) {
|
|
42
|
+
case '--help':
|
|
43
|
+
case '-h':
|
|
44
|
+
opts.help = true;
|
|
45
|
+
break;
|
|
46
|
+
case '--expression':
|
|
47
|
+
case '-e':
|
|
48
|
+
opts.expression = argv[++i];
|
|
49
|
+
break;
|
|
50
|
+
case '--stdin':
|
|
51
|
+
opts.stdin = true;
|
|
52
|
+
break;
|
|
53
|
+
case '--type':
|
|
54
|
+
case '-t':
|
|
55
|
+
opts.type = argv[++i];
|
|
56
|
+
break;
|
|
57
|
+
case '--phase':
|
|
58
|
+
case '-p':
|
|
59
|
+
opts.phase = argv[++i];
|
|
60
|
+
break;
|
|
61
|
+
case '--config':
|
|
62
|
+
case '-c':
|
|
63
|
+
opts.configFile = argv[++i];
|
|
64
|
+
break;
|
|
65
|
+
case '--expr-key': {
|
|
66
|
+
// Format: key:type[:phase]
|
|
67
|
+
const val = argv[++i];
|
|
68
|
+
const parts = val.split(':');
|
|
69
|
+
if (parts.length >= 2) {
|
|
70
|
+
opts.exprKeys.push({
|
|
71
|
+
key: parts[0],
|
|
72
|
+
type: parts[1],
|
|
73
|
+
phase: parts[2],
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case '--phase-map': {
|
|
79
|
+
// Format: yaml_key:phase_name
|
|
80
|
+
const val = argv[++i];
|
|
81
|
+
const colonIdx = val.indexOf(':');
|
|
82
|
+
if (colonIdx > 0) {
|
|
83
|
+
opts.phaseMaps.push({
|
|
84
|
+
yamlKey: val.substring(0, colonIdx),
|
|
85
|
+
phase: val.substring(colonIdx + 1),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case '--format':
|
|
91
|
+
case '-f':
|
|
92
|
+
opts.format = argv[++i];
|
|
93
|
+
break;
|
|
94
|
+
case '--quiet':
|
|
95
|
+
case '-q':
|
|
96
|
+
opts.quiet = true;
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
if (!arg.startsWith('-')) {
|
|
100
|
+
opts.files.push(arg);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
i++;
|
|
105
|
+
}
|
|
106
|
+
return opts;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Build ScannerOptions from CLI flags and config file.
|
|
110
|
+
*/
|
|
111
|
+
function buildScannerOptions(opts) {
|
|
112
|
+
const scannerOpts = {};
|
|
113
|
+
let hasOptions = false;
|
|
114
|
+
// Load config file if specified (or auto-detect)
|
|
115
|
+
const configPath = opts.configFile ?? findConfigFile();
|
|
116
|
+
if (configPath) {
|
|
117
|
+
try {
|
|
118
|
+
const raw = readFileSync(resolve(configPath), 'utf-8');
|
|
119
|
+
const config = JSON.parse(raw);
|
|
120
|
+
if (config.expressionKeys) {
|
|
121
|
+
scannerOpts.expressionKeys = config.expressionKeys;
|
|
122
|
+
hasOptions = true;
|
|
123
|
+
}
|
|
124
|
+
if (config.phaseMappings) {
|
|
125
|
+
scannerOpts.phaseMappings = config.phaseMappings;
|
|
126
|
+
hasOptions = true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
console.error(`Error reading config file ${configPath}: ${err instanceof Error ? err.message : err}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Apply --expr-key flags (merge with config file)
|
|
135
|
+
if (opts.exprKeys.length > 0) {
|
|
136
|
+
scannerOpts.expressionKeys = scannerOpts.expressionKeys ?? {};
|
|
137
|
+
for (const ek of opts.exprKeys) {
|
|
138
|
+
scannerOpts.expressionKeys[ek.key] = {
|
|
139
|
+
type: ek.type,
|
|
140
|
+
phaseHint: ek.phase,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
hasOptions = true;
|
|
144
|
+
}
|
|
145
|
+
// Apply --phase-map flags (merge with config file)
|
|
146
|
+
if (opts.phaseMaps.length > 0) {
|
|
147
|
+
scannerOpts.phaseMappings = scannerOpts.phaseMappings ?? {};
|
|
148
|
+
for (const pm of opts.phaseMaps) {
|
|
149
|
+
scannerOpts.phaseMappings[pm.yamlKey] = pm.phase;
|
|
150
|
+
}
|
|
151
|
+
hasOptions = true;
|
|
152
|
+
}
|
|
153
|
+
return hasOptions ? scannerOpts : undefined;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Auto-detect config file in the current directory.
|
|
157
|
+
*/
|
|
158
|
+
function findConfigFile() {
|
|
159
|
+
const candidates = [
|
|
160
|
+
'.cf-expr-lint.json',
|
|
161
|
+
'.cf-expr-lint.yaml',
|
|
162
|
+
'cf-expr-lint.config.json',
|
|
163
|
+
];
|
|
164
|
+
for (const name of candidates) {
|
|
165
|
+
if (existsSync(name))
|
|
166
|
+
return name;
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
function printHelp() {
|
|
171
|
+
console.log(`
|
|
172
|
+
cloudflare-expression-lint - Validate Cloudflare Rules Language expressions
|
|
173
|
+
|
|
174
|
+
Usage:
|
|
175
|
+
cf-expr-lint [options] <files...>
|
|
176
|
+
cf-expr-lint -e "http.host eq \\"test.com\\""
|
|
177
|
+
echo 'http.host eq "test"' | cf-expr-lint --stdin
|
|
178
|
+
|
|
179
|
+
Options:
|
|
180
|
+
--expression, -e <expr> Validate a single expression string
|
|
181
|
+
--stdin Read expression from stdin
|
|
182
|
+
--type, -t <type> Expression type: filter (default), rewrite_url,
|
|
183
|
+
rewrite_header, redirect_target
|
|
184
|
+
--phase, -p <phase> Cloudflare phase for field validation
|
|
185
|
+
--config, -c <file> Config file with custom mappings (JSON)
|
|
186
|
+
--expr-key <key:type[:phase]>
|
|
187
|
+
Add a YAML key that contains an expression.
|
|
188
|
+
Can be specified multiple times.
|
|
189
|
+
--phase-map <yaml_key:phase>
|
|
190
|
+
Map a YAML parent key to a Cloudflare phase.
|
|
191
|
+
Can be specified multiple times.
|
|
192
|
+
--format, -f <fmt> Output format: text (default), json
|
|
193
|
+
--quiet, -q Only show errors (suppress warnings/info)
|
|
194
|
+
--help, -h Show this help
|
|
195
|
+
|
|
196
|
+
Config File:
|
|
197
|
+
Place a .cf-expr-lint.json in your project root (auto-detected), or
|
|
198
|
+
specify with --config. Example:
|
|
199
|
+
|
|
200
|
+
{
|
|
201
|
+
"expressionKeys": {
|
|
202
|
+
"rewrite_expression": { "type": "rewrite_url", "phaseHint": "http_request_transform" },
|
|
203
|
+
"source_url_expression": { "type": "filter", "phaseHint": "http_request_dynamic_redirect" },
|
|
204
|
+
"redirect_target": { "type": "redirect_target" }
|
|
205
|
+
},
|
|
206
|
+
"phaseMappings": {
|
|
207
|
+
"waf_rules": "http_request_firewall_custom",
|
|
208
|
+
"transform_rules": "http_request_late_transform",
|
|
209
|
+
"url_rewrite_rules": "http_request_transform"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
Custom mappings are merged with built-in defaults. The built-in
|
|
214
|
+
expression key is "expression" (the Terraform provider attribute).
|
|
215
|
+
Built-in phase mappings include Cloudflare phase names and common
|
|
216
|
+
shorthands like "cache_rules" and "single_redirects".
|
|
217
|
+
|
|
218
|
+
Examples:
|
|
219
|
+
# Scan with defaults (detects "expression" keys, infers phase from context)
|
|
220
|
+
cf-expr-lint config/**/*.yaml
|
|
221
|
+
|
|
222
|
+
# Scan with custom expression keys and phase mappings
|
|
223
|
+
cf-expr-lint \\
|
|
224
|
+
--expr-key rewrite_expression:rewrite_url:http_request_transform \\
|
|
225
|
+
--expr-key source_url_expression:filter:http_request_dynamic_redirect \\
|
|
226
|
+
--phase-map waf_rules:http_request_firewall_custom \\
|
|
227
|
+
config/**/*.yaml
|
|
228
|
+
|
|
229
|
+
# Scan with config file
|
|
230
|
+
cf-expr-lint --config .cf-expr-lint.json config/**/*.yaml
|
|
231
|
+
|
|
232
|
+
# Validate a single expression
|
|
233
|
+
cf-expr-lint -e '(http.host eq "test.com")'
|
|
234
|
+
cf-expr-lint -e 'regex_replace(http.request.uri.path, "^/old/", "/")' -t rewrite_url
|
|
235
|
+
cf-expr-lint -e 'http.response.code eq 200' -p http_request_firewall_custom
|
|
236
|
+
`);
|
|
237
|
+
}
|
|
238
|
+
function formatDiagnostic(d) {
|
|
239
|
+
const prefix = d.severity === 'error' ? '✗' : d.severity === 'warning' ? '⚠' : 'ℹ';
|
|
240
|
+
const pos = d.position !== undefined ? ` (pos ${d.position})` : '';
|
|
241
|
+
return ` ${prefix} [${d.code}]${pos}: ${d.message}`;
|
|
242
|
+
}
|
|
243
|
+
async function main() {
|
|
244
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
245
|
+
if (opts.help) {
|
|
246
|
+
printHelp();
|
|
247
|
+
process.exit(0);
|
|
248
|
+
}
|
|
249
|
+
let hasErrors = false;
|
|
250
|
+
const jsonResults = [];
|
|
251
|
+
// ── Single expression mode ────────────────────────────────────────
|
|
252
|
+
if (opts.expression || opts.stdin) {
|
|
253
|
+
let expr;
|
|
254
|
+
if (opts.stdin) {
|
|
255
|
+
expr = readFileSync(0, 'utf-8').trim();
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
expr = opts.expression;
|
|
259
|
+
}
|
|
260
|
+
const ctx = {
|
|
261
|
+
expressionType: opts.type,
|
|
262
|
+
phase: opts.phase,
|
|
263
|
+
};
|
|
264
|
+
const result = validate(expr, ctx);
|
|
265
|
+
if (opts.format === 'json') {
|
|
266
|
+
console.log(JSON.stringify(result, null, 2));
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
const filteredDiags = opts.quiet
|
|
270
|
+
? result.diagnostics.filter(d => d.severity === 'error')
|
|
271
|
+
: result.diagnostics;
|
|
272
|
+
if (filteredDiags.length === 0) {
|
|
273
|
+
console.log(`✓ Expression is valid`);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
console.log(`Expression: ${expr.substring(0, 100)}${expr.length > 100 ? '...' : ''}`);
|
|
277
|
+
for (const d of filteredDiags) {
|
|
278
|
+
console.log(formatDiagnostic(d));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (!result.valid)
|
|
282
|
+
hasErrors = true;
|
|
283
|
+
}
|
|
284
|
+
process.exit(hasErrors ? 1 : 0);
|
|
285
|
+
}
|
|
286
|
+
// ── File scanning mode ────────────────────────────────────────────
|
|
287
|
+
if (opts.files.length === 0) {
|
|
288
|
+
printHelp();
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
const scannerOpts = buildScannerOptions(opts);
|
|
292
|
+
// Expand globs
|
|
293
|
+
const expandedFiles = [];
|
|
294
|
+
for (const pattern of opts.files) {
|
|
295
|
+
const matches = await glob(pattern);
|
|
296
|
+
expandedFiles.push(...matches);
|
|
297
|
+
}
|
|
298
|
+
if (expandedFiles.length === 0) {
|
|
299
|
+
console.error('No files matched the given patterns');
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
let totalExpressions = 0;
|
|
303
|
+
let totalErrors = 0;
|
|
304
|
+
let totalWarnings = 0;
|
|
305
|
+
for (const file of expandedFiles) {
|
|
306
|
+
const absPath = resolve(file);
|
|
307
|
+
let content;
|
|
308
|
+
try {
|
|
309
|
+
content = readFileSync(absPath, 'utf-8');
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
console.error(`Error reading ${file}: ${err instanceof Error ? err.message : err}`);
|
|
313
|
+
hasErrors = true;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const scanResult = scanYaml(content, file, scannerOpts);
|
|
317
|
+
if (scanResult.parseError) {
|
|
318
|
+
console.error(`Error parsing ${file}: ${scanResult.parseError}`);
|
|
319
|
+
hasErrors = true;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (opts.format === 'json') {
|
|
323
|
+
jsonResults.push(scanResult);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
for (const expr of scanResult.expressions) {
|
|
327
|
+
totalExpressions++;
|
|
328
|
+
const filteredDiags = opts.quiet
|
|
329
|
+
? expr.result.diagnostics.filter(d => d.severity === 'error')
|
|
330
|
+
: expr.result.diagnostics;
|
|
331
|
+
const errors = expr.result.diagnostics.filter(d => d.severity === 'error').length;
|
|
332
|
+
const warnings = expr.result.diagnostics.filter(d => d.severity === 'warning').length;
|
|
333
|
+
totalErrors += errors;
|
|
334
|
+
totalWarnings += warnings;
|
|
335
|
+
if (filteredDiags.length > 0) {
|
|
336
|
+
console.log(`\n${file} → ${expr.yamlPath}`);
|
|
337
|
+
console.log(` Expression: ${expr.expression.substring(0, 120)}${expr.expression.length > 120 ? '...' : ''}`);
|
|
338
|
+
for (const d of filteredDiags) {
|
|
339
|
+
console.log(formatDiagnostic(d));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (!expr.result.valid)
|
|
343
|
+
hasErrors = true;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (opts.format === 'json') {
|
|
347
|
+
console.log(JSON.stringify(jsonResults, null, 2));
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
console.log(`\n─────────────────────────────────────────────`);
|
|
351
|
+
console.log(`Scanned ${expandedFiles.length} files, ${totalExpressions} expressions`);
|
|
352
|
+
console.log(` ${totalErrors} errors, ${totalWarnings} warnings`);
|
|
353
|
+
if (!hasErrors) {
|
|
354
|
+
console.log(` ✓ All expressions valid`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
process.exit(hasErrors ? 1 : 0);
|
|
358
|
+
}
|
|
359
|
+
main().catch(err => {
|
|
360
|
+
console.error(err);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
});
|
|
363
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint plugin adapter for cloudflare-expression-lint.
|
|
3
|
+
*
|
|
4
|
+
* Provides a `validate-expression` rule that hooks into YAML AST nodes
|
|
5
|
+
* (via yaml-eslint-parser) and JSON files to detect and validate
|
|
6
|
+
* Cloudflare Rules Language expressions.
|
|
7
|
+
*
|
|
8
|
+
* yaml-eslint-parser and eslint are optional peer dependencies.
|
|
9
|
+
*/
|
|
10
|
+
import type { ExpressionType, DiagnosticSeverity } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Default mapping of YAML/JSON key names to expression types.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_EXPRESSION_KEYS: Record<string, ExpressionType>;
|
|
15
|
+
/**
|
|
16
|
+
* Check whether a given key name is one of the known expression keys.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isExpressionKey(key: string, customMappings?: Record<string, ExpressionType>): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Infer the ExpressionType for a given key name.
|
|
21
|
+
* Custom mappings take precedence over defaults.
|
|
22
|
+
*/
|
|
23
|
+
export declare function inferExpressionType(key: string, customMappings?: Record<string, ExpressionType>): ExpressionType;
|
|
24
|
+
/**
|
|
25
|
+
* Infer a phase hint from the key name, if one exists.
|
|
26
|
+
*/
|
|
27
|
+
export declare function inferPhaseFromKey(key: string, customPhaseMappings?: Record<string, string>): string | undefined;
|
|
28
|
+
declare function diagnosticSeverityToEslint(severity: DiagnosticSeverity): 1 | 2;
|
|
29
|
+
export interface ValidateExpressionRuleOptions {
|
|
30
|
+
/** Custom key name to ExpressionType mappings */
|
|
31
|
+
customKeyMappings?: Record<string, ExpressionType>;
|
|
32
|
+
/** Custom key name to phase mappings */
|
|
33
|
+
customPhaseMappings?: Record<string, string>;
|
|
34
|
+
/** Default phase when none can be inferred */
|
|
35
|
+
defaultPhase?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates the ESLint rule object for validate-expression.
|
|
39
|
+
* Factored out so it can be tested without requiring ESLint.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createValidateExpressionRule(): {
|
|
42
|
+
meta: Record<string, unknown>;
|
|
43
|
+
create: (context: any) => Record<string, (node: any) => void>;
|
|
44
|
+
};
|
|
45
|
+
declare const rule: {
|
|
46
|
+
meta: Record<string, unknown>;
|
|
47
|
+
create: (context: any) => Record<string, (node: any) => void>;
|
|
48
|
+
};
|
|
49
|
+
declare const plugin: {
|
|
50
|
+
rules: {
|
|
51
|
+
'validate-expression': {
|
|
52
|
+
meta: Record<string, unknown>;
|
|
53
|
+
create: (context: any) => Record<string, (node: any) => void>;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
configs: {
|
|
57
|
+
recommended: {
|
|
58
|
+
plugins: string[];
|
|
59
|
+
rules: {
|
|
60
|
+
'cloudflare-expression-lint/validate-expression': string;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
export default plugin;
|
|
66
|
+
export { plugin, rule as validateExpressionRule };
|
|
67
|
+
export { diagnosticSeverityToEslint };
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESLint plugin adapter for cloudflare-expression-lint.
|
|
3
|
+
*
|
|
4
|
+
* Provides a `validate-expression` rule that hooks into YAML AST nodes
|
|
5
|
+
* (via yaml-eslint-parser) and JSON files to detect and validate
|
|
6
|
+
* Cloudflare Rules Language expressions.
|
|
7
|
+
*
|
|
8
|
+
* yaml-eslint-parser and eslint are optional peer dependencies.
|
|
9
|
+
*/
|
|
10
|
+
import { validate } from './validator.js';
|
|
11
|
+
// ── Expression key detection ────────────────────────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* Default mapping of YAML/JSON key names to expression types.
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_EXPRESSION_KEYS = {
|
|
16
|
+
expression: 'filter',
|
|
17
|
+
rewrite_expression: 'rewrite_url',
|
|
18
|
+
source_url_expression: 'filter',
|
|
19
|
+
target_url_expression: 'redirect_target',
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Check whether a given key name is one of the known expression keys.
|
|
23
|
+
*/
|
|
24
|
+
export function isExpressionKey(key, customMappings) {
|
|
25
|
+
if (customMappings && key in customMappings)
|
|
26
|
+
return true;
|
|
27
|
+
return key in DEFAULT_EXPRESSION_KEYS;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Infer the ExpressionType for a given key name.
|
|
31
|
+
* Custom mappings take precedence over defaults.
|
|
32
|
+
*/
|
|
33
|
+
export function inferExpressionType(key, customMappings) {
|
|
34
|
+
if (customMappings && key in customMappings) {
|
|
35
|
+
return customMappings[key];
|
|
36
|
+
}
|
|
37
|
+
return DEFAULT_EXPRESSION_KEYS[key] ?? 'filter';
|
|
38
|
+
}
|
|
39
|
+
// ── Phase inference from key name ───────────────────────────────────────
|
|
40
|
+
/**
|
|
41
|
+
* Mapping from expression key names to a phase hint.
|
|
42
|
+
*/
|
|
43
|
+
const KEY_PHASE_HINTS = {
|
|
44
|
+
rewrite_expression: 'http_request_transform',
|
|
45
|
+
source_url_expression: 'http_request_dynamic_redirect',
|
|
46
|
+
target_url_expression: 'http_request_dynamic_redirect',
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Infer a phase hint from the key name, if one exists.
|
|
50
|
+
*/
|
|
51
|
+
export function inferPhaseFromKey(key, customPhaseMappings) {
|
|
52
|
+
if (customPhaseMappings && key in customPhaseMappings) {
|
|
53
|
+
return customPhaseMappings[key];
|
|
54
|
+
}
|
|
55
|
+
return KEY_PHASE_HINTS[key];
|
|
56
|
+
}
|
|
57
|
+
// ── ESLint severity mapping ─────────────────────────────────────────────
|
|
58
|
+
function diagnosticSeverityToEslint(severity) {
|
|
59
|
+
// ESLint only has warn (1) and error (2). Map info to warn.
|
|
60
|
+
switch (severity) {
|
|
61
|
+
case 'error':
|
|
62
|
+
return 2;
|
|
63
|
+
case 'warning':
|
|
64
|
+
case 'info':
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ── ESLint rule definition ──────────────────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* Creates the ESLint rule object for validate-expression.
|
|
71
|
+
* Factored out so it can be tested without requiring ESLint.
|
|
72
|
+
*/
|
|
73
|
+
export function createValidateExpressionRule() {
|
|
74
|
+
return {
|
|
75
|
+
meta: {
|
|
76
|
+
type: 'problem',
|
|
77
|
+
docs: {
|
|
78
|
+
description: 'Validate Cloudflare Rules Language expressions',
|
|
79
|
+
category: 'Possible Errors',
|
|
80
|
+
recommended: true,
|
|
81
|
+
},
|
|
82
|
+
schema: [
|
|
83
|
+
{
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
customKeyMappings: {
|
|
87
|
+
type: 'object',
|
|
88
|
+
additionalProperties: { type: 'string' },
|
|
89
|
+
},
|
|
90
|
+
customPhaseMappings: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
additionalProperties: { type: 'string' },
|
|
93
|
+
},
|
|
94
|
+
defaultPhase: { type: 'string' },
|
|
95
|
+
},
|
|
96
|
+
additionalProperties: false,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
messages: {
|
|
100
|
+
expressionDiagnostic: '{{message}}',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
create(context) {
|
|
104
|
+
const options = context.options?.[0] ?? {};
|
|
105
|
+
const { customKeyMappings, customPhaseMappings, defaultPhase } = options;
|
|
106
|
+
const filename = context.getFilename?.() ?? context.filename ?? '';
|
|
107
|
+
const visitors = {};
|
|
108
|
+
// ── YAML file handling (requires yaml-eslint-parser) ──────────
|
|
109
|
+
if (filename.endsWith('.yaml') || filename.endsWith('.yml')) {
|
|
110
|
+
visitors['YAMLPair'] = (node) => {
|
|
111
|
+
// YAMLPair has .key and .value nodes
|
|
112
|
+
const keyNode = node.key;
|
|
113
|
+
const valueNode = node.value;
|
|
114
|
+
if (!keyNode || !valueNode)
|
|
115
|
+
return;
|
|
116
|
+
// Get the key string
|
|
117
|
+
const keyName = keyNode.value ?? keyNode.raw;
|
|
118
|
+
if (typeof keyName !== 'string')
|
|
119
|
+
return;
|
|
120
|
+
// Check if this is an expression key
|
|
121
|
+
if (!isExpressionKey(keyName, customKeyMappings))
|
|
122
|
+
return;
|
|
123
|
+
// Get the value string
|
|
124
|
+
const valueStr = valueNode.value ?? valueNode.raw;
|
|
125
|
+
if (typeof valueStr !== 'string')
|
|
126
|
+
return;
|
|
127
|
+
const expression = valueStr.trim();
|
|
128
|
+
if (!expression)
|
|
129
|
+
return;
|
|
130
|
+
// Determine expression type and phase
|
|
131
|
+
const expressionType = inferExpressionType(keyName, customKeyMappings);
|
|
132
|
+
const phase = inferPhaseFromKey(keyName, customPhaseMappings) ?? defaultPhase;
|
|
133
|
+
// Validate
|
|
134
|
+
const result = validate(expression, {
|
|
135
|
+
expressionType,
|
|
136
|
+
phase,
|
|
137
|
+
allowPlaceholders: true,
|
|
138
|
+
});
|
|
139
|
+
// Report diagnostics
|
|
140
|
+
for (const diagnostic of result.diagnostics) {
|
|
141
|
+
context.report({
|
|
142
|
+
node: valueNode,
|
|
143
|
+
messageId: 'expressionDiagnostic',
|
|
144
|
+
data: { message: `[${diagnostic.code}] ${diagnostic.message}` },
|
|
145
|
+
// Use ESLint severity mapping (note: ESLint rule severity is
|
|
146
|
+
// configured at the config level, but we include our severity
|
|
147
|
+
// in the message for clarity)
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
// ── JSON file handling ────────────────────────────────────────
|
|
153
|
+
if (filename.endsWith('.json')) {
|
|
154
|
+
visitors['Property'] = (node) => {
|
|
155
|
+
const keyNode = node.key;
|
|
156
|
+
const valueNode = node.value;
|
|
157
|
+
if (!keyNode || !valueNode)
|
|
158
|
+
return;
|
|
159
|
+
// Get key name from JSON AST
|
|
160
|
+
const keyName = keyNode.value ?? keyNode.name;
|
|
161
|
+
if (typeof keyName !== 'string')
|
|
162
|
+
return;
|
|
163
|
+
if (!isExpressionKey(keyName, customKeyMappings))
|
|
164
|
+
return;
|
|
165
|
+
// Value must be a string literal
|
|
166
|
+
if (valueNode.type !== 'Literal' || typeof valueNode.value !== 'string')
|
|
167
|
+
return;
|
|
168
|
+
const expression = valueNode.value.trim();
|
|
169
|
+
if (!expression)
|
|
170
|
+
return;
|
|
171
|
+
const expressionType = inferExpressionType(keyName, customKeyMappings);
|
|
172
|
+
const phase = inferPhaseFromKey(keyName, customPhaseMappings) ?? defaultPhase;
|
|
173
|
+
const result = validate(expression, {
|
|
174
|
+
expressionType,
|
|
175
|
+
phase,
|
|
176
|
+
allowPlaceholders: true,
|
|
177
|
+
});
|
|
178
|
+
for (const diagnostic of result.diagnostics) {
|
|
179
|
+
context.report({
|
|
180
|
+
node: valueNode,
|
|
181
|
+
messageId: 'expressionDiagnostic',
|
|
182
|
+
data: { message: `[${diagnostic.code}] ${diagnostic.message}` },
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return visitors;
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// ── Plugin export ───────────────────────────────────────────────────────
|
|
192
|
+
const rule = createValidateExpressionRule();
|
|
193
|
+
const plugin = {
|
|
194
|
+
rules: {
|
|
195
|
+
'validate-expression': rule,
|
|
196
|
+
},
|
|
197
|
+
configs: {
|
|
198
|
+
recommended: {
|
|
199
|
+
plugins: ['cloudflare-expression-lint'],
|
|
200
|
+
rules: {
|
|
201
|
+
'cloudflare-expression-lint/validate-expression': 'warn',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
export default plugin;
|
|
207
|
+
// Also export for named imports
|
|
208
|
+
export { plugin, rule as validateExpressionRule };
|
|
209
|
+
// Re-export the severity mapper for testing
|
|
210
|
+
export { diagnosticSeverityToEslint };
|
|
211
|
+
//# sourceMappingURL=eslint-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eslint-plugin.js","sourceRoot":"","sources":["../src/eslint-plugin.ts"],"names":[],"mappings":"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"}
|