@utoo/lint 0.0.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.
package/index.cjs ADDED
@@ -0,0 +1,4018 @@
1
+ const { spawnSync } = require("node:child_process");
2
+ const { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } = require("node:fs");
3
+ const { writeFile: writeFileAsync } = require("node:fs/promises");
4
+ const { tmpdir } = require("node:os");
5
+ const { dirname, extname, isAbsolute, join, resolve: resolvePath } = require("node:path");
6
+
7
+ const { platformPackageName, resolveBinary } = require("./lib/binary.cjs");
8
+
9
+ const version = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8")).version;
10
+
11
+ const LINTABLE_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]);
12
+ const MAX_AUTOFIX_PASSES = 10;
13
+ const JS_KEYWORDS = new Set([
14
+ "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete",
15
+ "do", "else", "export", "extends", "finally", "for", "function", "if", "import", "in",
16
+ "instanceof", "let", "new", "return", "super", "switch", "this", "throw", "try", "typeof",
17
+ "var", "void", "while", "with", "yield"
18
+ ]);
19
+ const AST_TRAVERSAL_SKIP_KEYS = new Set([
20
+ "type", "parent", "loc", "range", "tokens", "comments", "leadingComments", "trailingComments",
21
+ "innerComments", "raw", "value", "name", "regex", "bigint", "errors"
22
+ ]);
23
+ const RULE_TESTER_INITIAL_CONFIG = { rules: {} };
24
+ let ruleTesterDefaultConfig = { rules: {} };
25
+ let ruleTesterDescribe = null;
26
+ let ruleTesterIt = null;
27
+ let ruleTesterItOnly = null;
28
+ const FISHLINT_DROP_FLAGS = new Set([
29
+ "--cache",
30
+ "--color",
31
+ "--debug",
32
+ "--disable-legacy",
33
+ "--disable-setup",
34
+ "--no-cache",
35
+ "--no-color",
36
+ "--no-error-on-unmatched-pattern",
37
+ "--no-eslintrc",
38
+ "--no-ignore",
39
+ "--no-inline-config",
40
+ "--no-warn-ignored",
41
+ "--pass-on-no-patterns",
42
+ "--quiet",
43
+ "--report-unused-disable-directives",
44
+ "--stats",
45
+ "--stdin",
46
+ "--verbose",
47
+ "-v"
48
+ ]);
49
+ const FISHLINT_DROP_VALUE_FLAGS = new Set([
50
+ "--cache-location",
51
+ "--cache-strategy",
52
+ "--env",
53
+ "--global",
54
+ "--ignore-path",
55
+ "--ignore-pattern",
56
+ "--max-warnings",
57
+ "--output-file",
58
+ "--parser",
59
+ "--parser-options",
60
+ "--plugin",
61
+ "--print-config",
62
+ "--resolve-plugins-relative-to",
63
+ "--rule",
64
+ "--rulesdir",
65
+ "--stdin-filename",
66
+ "-E",
67
+ "-o"
68
+ ]);
69
+ const CONFIG_FILENAMES = ["utoo.json", "utoo-lint.json"];
70
+ const JAVASCRIPT_CONFIG_EXTENSIONS = new Set([".js", ".mjs", ".cjs"]);
71
+ const JAVASCRIPT_CONFIG_LOADER_SCRIPT = `
72
+ import { pathToFileURL } from "node:url";
73
+
74
+ const configPath = process.argv[1];
75
+ const loaded = await import(pathToFileURL(configPath).href);
76
+ const value = loaded.default ?? loaded;
77
+ const json = JSON.stringify(value);
78
+ if (json === undefined) {
79
+ throw new TypeError("config did not export a JSON-serializable value");
80
+ }
81
+ process.stdout.write(json);
82
+ `;
83
+ const BUILTIN_RULE_IDS = [
84
+ "array-callback-return",
85
+ "block-scoped-var",
86
+ "consistent-return",
87
+ "constructor-super",
88
+ "curly",
89
+ "default-case",
90
+ "default-case-last",
91
+ "dot-notation",
92
+ "eol-last",
93
+ "eqeqeq",
94
+ "for-direction",
95
+ "getter-return",
96
+ "guard-for-in",
97
+ "linebreak-style",
98
+ "new-cap",
99
+ "new-parens",
100
+ "no-alert",
101
+ "no-array-constructor",
102
+ "no-async-promise-executor",
103
+ "no-await-in-loop",
104
+ "no-bitwise",
105
+ "no-buffer-constructor",
106
+ "no-caller",
107
+ "no-case-declarations",
108
+ "no-class-assign",
109
+ "no-comma-operator",
110
+ "no-compare-neg-zero",
111
+ "no-cond-assign",
112
+ "no-console",
113
+ "no-const-assign",
114
+ "no-constant-condition",
115
+ "no-constructor-return",
116
+ "no-continue",
117
+ "no-control-regex",
118
+ "no-debugger",
119
+ "no-delete-var",
120
+ "no-div-regex",
121
+ "no-dupe-args",
122
+ "no-dupe-class-members",
123
+ "no-dupe-else-if",
124
+ "no-dupe-keys",
125
+ "no-duplicate-case",
126
+ "no-duplicate-imports",
127
+ "no-else-return",
128
+ "no-empty",
129
+ "no-empty-block-statements",
130
+ "no-empty-character-class",
131
+ "no-empty-function",
132
+ "no-empty-pattern",
133
+ "no-empty-static-block",
134
+ "no-eq-null",
135
+ "no-eval",
136
+ "no-ex-assign",
137
+ "no-extend-native",
138
+ "no-extra-bind",
139
+ "no-extra-boolean-cast",
140
+ "no-extra-label",
141
+ "no-extra-semi",
142
+ "no-fallthrough",
143
+ "no-floating-decimal",
144
+ "no-for-in",
145
+ "no-func-assign",
146
+ "no-global-assign",
147
+ "no-global-is-finite",
148
+ "no-global-is-nan",
149
+ "no-implicit-coercion",
150
+ "no-implied-eval",
151
+ "no-import-assign",
152
+ "no-inline-comments",
153
+ "no-inner-declarations",
154
+ "no-invalid-regexp",
155
+ "no-irregular-whitespace",
156
+ "no-iterator",
157
+ "no-label-var",
158
+ "no-labels",
159
+ "no-lone-blocks",
160
+ "no-lonely-if",
161
+ "no-loop-func",
162
+ "no-loss-of-precision",
163
+ "no-mixed-spaces-and-tabs",
164
+ "no-misleading-character-class",
165
+ "no-multi-assign",
166
+ "no-multi-spaces",
167
+ "no-multi-str",
168
+ "no-multiple-empty-lines",
169
+ "no-negated-condition",
170
+ "no-nested-ternary",
171
+ "no-new",
172
+ "no-new-func",
173
+ "no-new-native-nonconstructor",
174
+ "no-new-object",
175
+ "no-new-require",
176
+ "no-new-symbol",
177
+ "no-new-wrappers",
178
+ "no-nonoctal-decimal-escape",
179
+ "no-obj-calls",
180
+ "no-object-constructor",
181
+ "no-octal",
182
+ "no-octal-escape",
183
+ "no-param-reassign",
184
+ "no-path-concat",
185
+ "no-plusplus",
186
+ "no-process-env",
187
+ "no-process-exit",
188
+ "no-promise-executor-return",
189
+ "no-proto",
190
+ "no-prototype-builtins",
191
+ "no-regex-spaces",
192
+ "no-return-assign",
193
+ "no-return-await",
194
+ "no-script-url",
195
+ "no-self-assign",
196
+ "no-self-compare",
197
+ "no-sequences",
198
+ "no-setter-return",
199
+ "no-shadow-restricted-names",
200
+ "no-sparse-arrays",
201
+ "no-tabs",
202
+ "no-template-curly-in-string",
203
+ "no-ternary",
204
+ "no-this-before-super",
205
+ "no-throw-literal",
206
+ "no-trailing-spaces",
207
+ "no-undef",
208
+ "no-undef-init",
209
+ "no-unneeded-ternary",
210
+ "no-unreachable",
211
+ "no-unsafe-finally",
212
+ "no-unsafe-negation",
213
+ "no-unused-expressions",
214
+ "no-unused-labels",
215
+ "no-unused-vars",
216
+ "no-useless-call",
217
+ "no-useless-catch",
218
+ "no-useless-computed-key",
219
+ "no-useless-concat",
220
+ "no-useless-constructor",
221
+ "no-useless-escape",
222
+ "no-useless-rename",
223
+ "no-useless-return",
224
+ "no-var",
225
+ "no-void",
226
+ "no-warning-comments",
227
+ "no-with",
228
+ "object-shorthand",
229
+ "one-var",
230
+ "operator-assignment",
231
+ "prefer-const",
232
+ "prefer-destructuring",
233
+ "prefer-exponentiation-operator",
234
+ "prefer-promise-reject-errors",
235
+ "prefer-regex-literals",
236
+ "prefer-rest-params",
237
+ "prefer-spread",
238
+ "prefer-template",
239
+ "radix",
240
+ "require-atomic-updates",
241
+ "require-yield",
242
+ "spaced-comment",
243
+ "symbol-description",
244
+ "unicode-bom",
245
+ "use-isnan",
246
+ "valid-typeof",
247
+ "yoda",
248
+ "import/default",
249
+ "import/export",
250
+ "import/first",
251
+ "import/named",
252
+ "import/namespace",
253
+ "import/newline-after-import",
254
+ "import/no-amd",
255
+ "import/no-cycle",
256
+ "import/no-duplicates",
257
+ "import/no-named-as-default",
258
+ "import/no-named-as-default-member",
259
+ "import/no-unresolved",
260
+ "import/no-self-import",
261
+ "jsx-a11y/aria-props",
262
+ "jsx-a11y/iframe-has-title",
263
+ "jsx-a11y/img-redundant-alt",
264
+ "jsx-a11y/no-access-key",
265
+ "react/jsx-boolean-value",
266
+ "react/jsx-no-comment-textnodes",
267
+ "react/jsx-no-duplicate-props",
268
+ "react/jsx-no-target-blank",
269
+ "react/jsx-pascal-case",
270
+ "react/no-danger",
271
+ "react/no-find-dom-node",
272
+ "react/no-is-mounted",
273
+ "react/no-render-return-value",
274
+ "react/no-unescaped-entities",
275
+ "react/no-unused-prop-types",
276
+ "react/prop-types",
277
+ "react-hooks/rules-of-hooks",
278
+ "@typescript-eslint/adjacent-overload-signatures",
279
+ "@typescript-eslint/array-type",
280
+ "@typescript-eslint/ban-ts-comment",
281
+ "@typescript-eslint/ban-tslint-comment",
282
+ "@typescript-eslint/ban-types",
283
+ "@typescript-eslint/class-literal-property-style",
284
+ "@typescript-eslint/consistent-type-assertions",
285
+ "@typescript-eslint/consistent-type-definitions",
286
+ "@typescript-eslint/dot-notation",
287
+ "@typescript-eslint/explicit-member-accessibility",
288
+ "@typescript-eslint/member-ordering",
289
+ "@typescript-eslint/method-signature-style",
290
+ "@typescript-eslint/no-array-constructor",
291
+ "@typescript-eslint/no-confusing-non-null-assertion",
292
+ "@typescript-eslint/no-dupe-class-members",
293
+ "@typescript-eslint/no-empty-function",
294
+ "@typescript-eslint/no-empty-interface",
295
+ "@typescript-eslint/no-duplicate-enum-values",
296
+ "@typescript-eslint/no-extra-semi",
297
+ "@typescript-eslint/no-extra-non-null-assertion",
298
+ "@typescript-eslint/no-inferrable-types",
299
+ "@typescript-eslint/no-invalid-void-type",
300
+ "@typescript-eslint/no-loss-of-precision",
301
+ "@typescript-eslint/no-misused-new",
302
+ "@typescript-eslint/no-namespace",
303
+ "@typescript-eslint/no-non-null-asserted-optional-chain",
304
+ "@typescript-eslint/no-redeclare",
305
+ "@typescript-eslint/no-require-imports",
306
+ "@typescript-eslint/no-shadow",
307
+ "@typescript-eslint/no-this-alias",
308
+ "@typescript-eslint/no-unsafe-declaration-merging",
309
+ "@typescript-eslint/triple-slash-reference",
310
+ "@typescript-eslint/typedef",
311
+ "@typescript-eslint/unified-signatures",
312
+ "@typescript-eslint/no-unnecessary-parameter-property-assignment",
313
+ "@typescript-eslint/no-unnecessary-type-constraint",
314
+ "@typescript-eslint/no-useless-constructor",
315
+ "@typescript-eslint/no-useless-empty-export",
316
+ "@typescript-eslint/no-unused-expressions",
317
+ "@typescript-eslint/no-unused-vars",
318
+ "@typescript-eslint/no-use-before-define",
319
+ "@typescript-eslint/no-var-requires",
320
+ "@typescript-eslint/no-wrapper-object-types",
321
+ "@typescript-eslint/prefer-as-const",
322
+ "@typescript-eslint/prefer-namespace-keyword",
323
+ "@typescript-eslint/restrict-plus-operands"
324
+ ];
325
+ const BUILTIN_RULES = new Map(BUILTIN_RULE_IDS.map((ruleId) => [ruleId, createBuiltinRule(ruleId)]));
326
+
327
+ class UtooLint {
328
+ static get version() {
329
+ return version;
330
+ }
331
+
332
+ static get configType() {
333
+ return "flat";
334
+ }
335
+
336
+ static get defaultConfig() {
337
+ return [];
338
+ }
339
+
340
+ static async fromOptionsModule(optionsURL) {
341
+ if (!(optionsURL instanceof URL)) {
342
+ throw new TypeError("Argument must be a URL object");
343
+ }
344
+ const loaded = await import(optionsURL.href);
345
+ return new UtooLint(loaded.default ?? loaded);
346
+ }
347
+
348
+ static async outputFixes(results) {
349
+ if (!Array.isArray(results)) {
350
+ throw new Error("'results' must be an array");
351
+ }
352
+
353
+ await Promise.all(
354
+ results
355
+ .filter((result) => {
356
+ if (typeof result !== "object" || result === null) {
357
+ throw new Error("'results' must include only objects");
358
+ }
359
+ return typeof result.output === "string" && isAbsolute(result.filePath);
360
+ })
361
+ .map((result) => writeFileAsync(result.filePath, result.output))
362
+ );
363
+ }
364
+
365
+ static getErrorResults(results) {
366
+ return getErrorResults(results);
367
+ }
368
+
369
+ constructor(options = {}) {
370
+ this.options = { ...options };
371
+ }
372
+
373
+ async lintFiles(patterns = ["."], options = {}) {
374
+ const mergedOptions = mergeLintOptions(this.options, options);
375
+ const customRuleConfig = [mergedOptions.baseConfig, mergedOptions.overrideConfig].filter(Boolean);
376
+ const customRuleFilterMap = customRuleMapForConfig(customRuleConfig, new Map());
377
+ const nativeOptions = lintOptionsWithoutCustomRules(mergedOptions, customRuleFilterMap);
378
+ const report = lintFiles(patterns, nativeOptions);
379
+ throwOnUnmatchedPatternDiagnostics(report, nativeOptions);
380
+ const results = reportToESLintResults(report, {
381
+ cwd: mergedOptions.cwd,
382
+ filePaths: reportFilePaths(report, mergedOptions.cwd, explicitLintFilePaths(report.filePaths ?? patterns, mergedOptions.cwd)),
383
+ ruleSeverityForFile: (filePath) => ruleSeverityMapForOptions(nativeOptions, filePath)
384
+ });
385
+ appendCustomLintFileMessages(results, customRuleConfig, mergedOptions);
386
+ return maybeFilterQuietResults(results, mergedOptions);
387
+ }
388
+
389
+ async lintText(code, options = {}) {
390
+ if (typeof code !== "string") {
391
+ throw new TypeError("code must be a string");
392
+ }
393
+
394
+ const mergedOptions = mergeLintOptions(this.options, options);
395
+ const filePath = normalizeESLintFilePath(textFilePathForOptions(options, "<text>"), mergedOptions.cwd);
396
+ const customRuleConfig = [mergedOptions.baseConfig, mergedOptions.overrideConfig].filter(Boolean);
397
+ const customRuleMap = customRuleMapForConfig(customRuleConfig, new Map(), filePath, mergedOptions.cwd);
398
+ const customRuleFilterMap = customRuleMapForConfig(customRuleConfig, new Map());
399
+ const nativeOptions = lintOptionsWithoutCustomRules(mergedOptions, customRuleFilterMap);
400
+ const report = lintText(code, nativeOptions);
401
+ const results = reportToESLintResults(report, {
402
+ source: code,
403
+ filePath,
404
+ includeEmptyTextResult: report.files !== 0 || (report.diagnostics?.length ?? 0) > 0,
405
+ ruleSeverityForFile: (filePath) => ruleSeverityMapForOptions(nativeOptions, filePath)
406
+ });
407
+ if (report.files !== 0) {
408
+ appendCustomLintTextMessages(results, code, filePath, customRuleConfig, customRuleMap, mergedOptions);
409
+ }
410
+ return maybeFilterQuietResults(results, mergedOptions);
411
+ }
412
+
413
+ async isPathIgnored(filePath) {
414
+ return isPathIgnored(filePath, mergeLintOptions(this.options, {}));
415
+ }
416
+
417
+ async calculateConfigForFile(filePath) {
418
+ return publicCalculatedConfig(eslintConstructorOptions(this.options), filePath);
419
+ }
420
+
421
+ async findConfigFile(filePath) {
422
+ const options = eslintConstructorOptions(this.options);
423
+ if (options.noConfig) {
424
+ return undefined;
425
+ }
426
+ if (filePath) {
427
+ return configPathForFile(options, filePath);
428
+ }
429
+ return configPathForOptions(options);
430
+ }
431
+
432
+ getRulesMetaForResults(results) {
433
+ if (!Array.isArray(results)) {
434
+ throw new Error("'results' must be an array");
435
+ }
436
+ const options = eslintConstructorOptions(this.options);
437
+ const customRuleConfig = [options.baseConfig, options.overrideConfig].filter(Boolean);
438
+ const rulesByFilePath = new Map();
439
+ return rulesMetaForResults(results, (ruleId, result) => {
440
+ if (customRuleConfig.length === 0 || typeof result.filePath !== "string") {
441
+ return undefined;
442
+ }
443
+ const filePath = normalizeESLintFilePath(result.filePath, options.cwd);
444
+ if (!rulesByFilePath.has(filePath)) {
445
+ rulesByFilePath.set(filePath, customRuleMapForConfig(customRuleConfig, new Map(), filePath, options.cwd));
446
+ }
447
+ return rulesByFilePath.get(filePath).get(ruleId)?.meta;
448
+ });
449
+ }
450
+
451
+ hasFlag(flag) {
452
+ return hasFlagInOptions(this.options, flag);
453
+ }
454
+
455
+ async loadFormatter(name = "stylish") {
456
+ const eslint = this;
457
+ return {
458
+ format(results) {
459
+ return formatResultsByName(results, name, {
460
+ rulesMeta: (results) => eslint.getRulesMetaForResults(results)
461
+ });
462
+ }
463
+ };
464
+ }
465
+ }
466
+
467
+ const ESLint = UtooLint;
468
+
469
+ class CLIEngine {
470
+ static get version() {
471
+ return version;
472
+ }
473
+
474
+ static outputFixes(report) {
475
+ const results = Array.isArray(report) ? report : report?.results;
476
+ if (!Array.isArray(results)) {
477
+ throw new Error("'report' must be an ESLint report or result array");
478
+ }
479
+
480
+ for (const result of results) {
481
+ if (typeof result !== "object" || result === null) {
482
+ throw new Error("'report' must include only result objects");
483
+ }
484
+ if (typeof result.output === "string" && isAbsolute(result.filePath)) {
485
+ writeFileSync(result.filePath, result.output);
486
+ }
487
+ }
488
+ }
489
+
490
+ static getErrorResults(results) {
491
+ return getErrorResults(results);
492
+ }
493
+
494
+ constructor(options = {}) {
495
+ this.options = { ...options };
496
+ this.plugins = new Map();
497
+ }
498
+
499
+ executeOnFiles(patterns) {
500
+ const mergedOptions = eslintConstructorOptions(this.options);
501
+ const report = lintFiles(patterns, mergedOptions);
502
+ throwOnUnmatchedPatternDiagnostics(report, mergedOptions);
503
+ const results = maybeFilterQuietResults(reportToESLintResults(report, {
504
+ cwd: mergedOptions.cwd,
505
+ filePaths: reportFilePaths(report, mergedOptions.cwd, explicitLintFilePaths(report.filePaths ?? patterns, mergedOptions.cwd)),
506
+ ruleSeverityForFile: (filePath) => ruleSeverityMapForOptions(mergedOptions, filePath)
507
+ }), mergedOptions);
508
+ return resultsToCLIEngineReport(results);
509
+ }
510
+
511
+ executeOnText(code, filePathOrOptions = "input.js") {
512
+ const textOptions =
513
+ typeof filePathOrOptions === "object" && filePathOrOptions !== null
514
+ ? filePathOrOptions
515
+ : {};
516
+ const filePath =
517
+ typeof filePathOrOptions === "object" && filePathOrOptions !== null
518
+ ? filePathOrOptions.filePath ?? filePathOrOptions.filename ?? "input.js"
519
+ : filePathOrOptions;
520
+ const mergedOptions = {
521
+ ...eslintConstructorOptions(this.options),
522
+ ...textOptions
523
+ };
524
+ const report = lintText(code, {
525
+ ...mergedOptions,
526
+ filePath
527
+ });
528
+ const results = maybeFilterQuietResults(reportToESLintResults(report, {
529
+ source: code,
530
+ filePath: normalizeESLintFilePath(filePath, mergedOptions.cwd),
531
+ includeEmptyTextResult: report.files !== 0 || (report.diagnostics?.length ?? 0) > 0,
532
+ ruleSeverityForFile: (filePath) => ruleSeverityMapForOptions(mergedOptions, filePath)
533
+ }), mergedOptions);
534
+ return resultsToCLIEngineReport(results);
535
+ }
536
+
537
+ getFormatter(name = "stylish") {
538
+ return (results) => {
539
+ return formatResultsByName(results, name);
540
+ };
541
+ }
542
+
543
+ getRules() {
544
+ return new Linter().getRules();
545
+ }
546
+
547
+ addPlugin(name, pluginObject) {
548
+ this.plugins.set(name, pluginObject);
549
+ }
550
+
551
+ resolveFileGlobPatterns(patterns) {
552
+ return normalizeStringArray(Array.isArray(patterns) ? patterns : [patterns], "patterns");
553
+ }
554
+
555
+ isPathIgnored(filePath) {
556
+ return isPathIgnored(filePath, eslintConstructorOptions(this.options));
557
+ }
558
+
559
+ getConfigForFile(filePath) {
560
+ return publicCalculatedConfig(eslintConstructorOptions(this.options), filePath);
561
+ }
562
+ }
563
+
564
+ function run(args = [], options = {}) {
565
+ const cliArgs = normalizeStringArray(args, "args");
566
+ const env = options.env ? { ...process.env, ...options.env } : process.env;
567
+ const binary = options.binary ?? resolveBinary({ env });
568
+
569
+ return spawnSync(binary, cliArgs, {
570
+ cwd: options.cwd,
571
+ env,
572
+ encoding: options.encoding ?? "utf8",
573
+ input: options.input,
574
+ stdio: options.stdio
575
+ });
576
+ }
577
+
578
+ function runFishlint(args = [], options = {}) {
579
+ const cliArgs = normalizeStringArray(args, "args");
580
+ const env = options.env ? { ...process.env, ...options.env } : { ...process.env };
581
+ if (options.binary) {
582
+ env.UTOO_LINT_BIN = options.binary;
583
+ }
584
+
585
+ return spawnSync(process.execPath, [join(__dirname, "bin", "fishlint.js"), ...cliArgs], {
586
+ cwd: options.cwd,
587
+ env,
588
+ encoding: options.encoding ?? "utf8",
589
+ input: options.input,
590
+ stdio: options.stdio
591
+ });
592
+ }
593
+
594
+ function translateFishlintArgs(args = [], options = {}) {
595
+ const values = normalizeStringArray(args, "args");
596
+ const warn = options.warn ?? (() => {});
597
+
598
+ if (values.length === 0) {
599
+ return ["--help"];
600
+ }
601
+
602
+ const [command, ...rest] = values;
603
+ if (command !== "eslint") {
604
+ throw new Error(`utoo-lint fishlint compatibility only supports the eslint command, received: ${command}`);
605
+ }
606
+
607
+ const translated = [];
608
+ const globTargets = [];
609
+ let index = 0;
610
+
611
+ while (index < rest.length) {
612
+ const arg = rest[index];
613
+ if (arg === "--") {
614
+ index += 1;
615
+ continue;
616
+ }
617
+ if (FISHLINT_DROP_FLAGS.has(arg)) {
618
+ if (arg === "--no-eslintrc") {
619
+ translated.push("--no-config");
620
+ }
621
+ index += 1;
622
+ continue;
623
+ }
624
+ if (startsWithFlagValue(arg, FISHLINT_DROP_FLAGS)) {
625
+ if (arg.startsWith("--no-eslintrc=") && booleanFlagValue(arg) !== false) {
626
+ translated.push("--no-config");
627
+ }
628
+ index += 1;
629
+ continue;
630
+ }
631
+ if (FISHLINT_DROP_VALUE_FLAGS.has(arg)) {
632
+ const value = rest[index + 1];
633
+ if (!value) {
634
+ throw new Error(`utoo-lint: fishlint ${arg} requires a value`);
635
+ }
636
+ index += 2;
637
+ continue;
638
+ }
639
+ if (startsWithFlagValue(arg, FISHLINT_DROP_VALUE_FLAGS)) {
640
+ index += 1;
641
+ continue;
642
+ }
643
+ if (arg === "--fix") {
644
+ warn("utoo-lint: fishlint --fix is ignored because utoo-lint does not apply fixes yet.");
645
+ index += 1;
646
+ continue;
647
+ }
648
+ if (arg === "--fix-dry-run") {
649
+ warn("utoo-lint: fishlint --fix-dry-run is ignored because utoo-lint does not apply fixes yet.");
650
+ index += 1;
651
+ continue;
652
+ }
653
+ if (arg === "--fix-type") {
654
+ const value = rest[index + 1];
655
+ if (!value) {
656
+ throw new Error("utoo-lint: fishlint --fix-type requires a value");
657
+ }
658
+ warn("utoo-lint: fishlint --fix-type is ignored because utoo-lint does not apply fixes yet.");
659
+ index += 2;
660
+ continue;
661
+ }
662
+ if (arg.startsWith("--fix-type=")) {
663
+ warn("utoo-lint: fishlint --fix-type is ignored because utoo-lint does not apply fixes yet.");
664
+ index += 1;
665
+ continue;
666
+ }
667
+ if (arg === "--format" || arg === "-f") {
668
+ const value = rest[index + 1];
669
+ if (!value) {
670
+ throw new Error(`utoo-lint: fishlint ${arg} requires a formatter name`);
671
+ }
672
+ translated.push(`--format=${translateFishlintFormat(value, warn)}`);
673
+ index += 2;
674
+ continue;
675
+ }
676
+ if (arg.startsWith("--format=")) {
677
+ translated.push(`--format=${translateFishlintFormat(arg.slice("--format=".length), warn)}`);
678
+ index += 1;
679
+ continue;
680
+ }
681
+ if (arg === "--threads") {
682
+ const value = rest[index + 1];
683
+ if (!value) {
684
+ throw new Error("utoo-lint: fishlint --threads requires a number");
685
+ }
686
+ translated.push(`--threads=${value}`);
687
+ index += 2;
688
+ continue;
689
+ }
690
+ if (arg.startsWith("--threads=")) {
691
+ translated.push(arg);
692
+ index += 1;
693
+ continue;
694
+ }
695
+ if (arg === "--rules") {
696
+ const value = rest[index + 1];
697
+ if (!value) {
698
+ throw new Error("utoo-lint: fishlint --rules requires a comma-separated rule list");
699
+ }
700
+ translated.push(`--rules=${value}`);
701
+ index += 2;
702
+ continue;
703
+ }
704
+ if (arg.startsWith("--rules=")) {
705
+ translated.push(arg);
706
+ index += 1;
707
+ continue;
708
+ }
709
+ if (arg === "--config" || arg === "-c") {
710
+ const value = rest[index + 1];
711
+ if (!value) {
712
+ throw new Error(`utoo-lint: fishlint ${arg} requires a path`);
713
+ }
714
+ translated.push(`--config=${value}`);
715
+ index += 2;
716
+ continue;
717
+ }
718
+ if (arg.startsWith("--config=")) {
719
+ translated.push(arg);
720
+ index += 1;
721
+ continue;
722
+ }
723
+ if (arg === "--ext") {
724
+ const value = rest[index + 1];
725
+ if (!value) {
726
+ throw new Error("utoo-lint: fishlint --ext requires an extension list");
727
+ }
728
+ index += 2;
729
+ continue;
730
+ }
731
+ if (arg.startsWith("--ext=")) {
732
+ index += 1;
733
+ continue;
734
+ }
735
+ if (arg === "--glob") {
736
+ const value = rest[index + 1];
737
+ if (!value) {
738
+ throw new Error("utoo-lint: fishlint --glob requires a path");
739
+ }
740
+ globTargets.push(value);
741
+ index += 2;
742
+ continue;
743
+ }
744
+ if (arg.startsWith("--glob=")) {
745
+ globTargets.push(arg.slice("--glob=".length));
746
+ index += 1;
747
+ continue;
748
+ }
749
+ translated.push(arg);
750
+ index += 1;
751
+ }
752
+
753
+ if (globTargets.length > 0) {
754
+ translated.push(...globTargets);
755
+ }
756
+
757
+ return translated;
758
+ }
759
+
760
+ function translateFishlintFormat(format, warn) {
761
+ if (format === "json" || format === "text") {
762
+ return format;
763
+ }
764
+ if (format === "json-with-metadata") {
765
+ return "json";
766
+ }
767
+ if (format !== "stylish") {
768
+ warn(`utoo-lint: fishlint formatter '${format}' is not implemented; using native text output.`);
769
+ }
770
+ return "text";
771
+ }
772
+
773
+ function startsWithFlagValue(arg, flags) {
774
+ for (const flag of flags) {
775
+ if (arg.startsWith(`${flag}=`)) {
776
+ return true;
777
+ }
778
+ }
779
+ return false;
780
+ }
781
+
782
+ function booleanFlagValue(arg) {
783
+ const value = arg.slice(arg.indexOf("=") + 1).trim().toLowerCase();
784
+ if (value === "false" || value === "0") {
785
+ return false;
786
+ }
787
+ return true;
788
+ }
789
+
790
+ function lintFiles(paths = ["."], options = {}) {
791
+ return withTemporaryConfig(options, (resolvedOptions) => {
792
+ const ignoredDiagnostics = ignoredLintPathDiagnostics(paths, resolvedOptions);
793
+ const lintPaths = filteredLintPaths(paths, resolvedOptions);
794
+ if (lintPaths.length === 0) {
795
+ return { files: 0, filePaths: [], diagnostics: ignoredDiagnostics, exitCode: ignoredDiagnostics.length > 0 ? 1 : 0 };
796
+ }
797
+
798
+ const cliArgs = buildLintArgs(lintPaths, resolvedOptions);
799
+ const result = run(cliArgs, { ...resolvedOptions, stdio: undefined, encoding: "utf8" });
800
+
801
+ if (result.error) {
802
+ throw result.error;
803
+ }
804
+
805
+ const status = result.status ?? 1;
806
+ const stdout = result.stdout ?? "";
807
+ const stderr = result.stderr ?? "";
808
+
809
+ if (status !== 0 && status !== 1) {
810
+ throw new Error(stderr.trim() || `utoo-lint exited with status ${status}`);
811
+ }
812
+
813
+ let report;
814
+ try {
815
+ report = JSON.parse(stdout);
816
+ } catch (error) {
817
+ throw new Error(`utoo-lint returned invalid JSON: ${error.message}`);
818
+ }
819
+
820
+ report.exitCode = status;
821
+ report.diagnostics = [
822
+ ...(report.diagnostics ?? []),
823
+ ...ignoredDiagnostics
824
+ ];
825
+ if (!resolvedOptions.deferDiagnosticConfigFiltering) {
826
+ report.filePaths = normalizeReportFilePaths(report.filePaths, resolvedOptions);
827
+ report.diagnostics = normalizeDiagnosticFilePaths(report.diagnostics, resolvedOptions);
828
+ report.diagnostics = normalizeReportDiagnostics(report.diagnostics, resolvedOptions);
829
+ report.exitCode = exitCodeForDiagnostics(report.diagnostics);
830
+ }
831
+ if (stderr) {
832
+ Object.defineProperty(report, "stderr", {
833
+ value: stderr,
834
+ enumerable: false
835
+ });
836
+ }
837
+
838
+ return report;
839
+ });
840
+ }
841
+
842
+ function lintText(code, options = {}) {
843
+ if (typeof code !== "string") {
844
+ throw new TypeError("code must be a string");
845
+ }
846
+
847
+ const tmp = mkdtempSync(join(tmpdir(), "utoo-lint-"));
848
+ const requestedPath = textFilePathForOptions(options, "text.js");
849
+ const extension = extname(requestedPath) || ".js";
850
+ const tempFile = join(tmp, `input${extension}`);
851
+ const discoveredConfig =
852
+ !options.noConfig && !options.config && !options.overrideConfig
853
+ ? configPathForOptions(options)
854
+ : undefined;
855
+
856
+ try {
857
+ if (isPathIgnored(requestedPath, options)) {
858
+ const diagnostics = options.warnIgnored === false ? [] : [ignoredFileDiagnostic(normalizeESLintFilePath(requestedPath, options.cwd))];
859
+ return {
860
+ files: 0,
861
+ filePaths: [],
862
+ diagnostics,
863
+ exitCode: diagnostics.length > 0 ? 1 : 0
864
+ };
865
+ }
866
+
867
+ writeFileSync(tempFile, code);
868
+ const report = lintFiles([tempFile], {
869
+ ...options,
870
+ cwd: options.cwd,
871
+ config: options.config ?? discoveredConfig,
872
+ noConfig: options.noConfig,
873
+ deferDiagnosticConfigFiltering: true
874
+ });
875
+ report.filePaths = (report.filePaths ?? []).map((filePath) => (filePath === tempFile ? requestedPath : filePath));
876
+ report.diagnostics = report.diagnostics.map((diagnostic) => ({
877
+ ...diagnostic,
878
+ filePath: requestedPath
879
+ }));
880
+ report.diagnostics = normalizeReportDiagnostics(report.diagnostics, options);
881
+ report.exitCode = exitCodeForDiagnostics(report.diagnostics);
882
+ return report;
883
+ } finally {
884
+ rmSync(tmp, { recursive: true, force: true });
885
+ }
886
+ }
887
+
888
+ function buildLintArgs(paths, options) {
889
+ const cliArgs = [];
890
+
891
+ if (options.config) {
892
+ cliArgs.push(`--config=${options.config}`);
893
+ }
894
+ if (options.noConfig) {
895
+ cliArgs.push("--no-config");
896
+ }
897
+ if (options.rules) {
898
+ const rules = Array.isArray(options.rules) ? options.rules.join(",") : options.rules;
899
+ if (rules) {
900
+ cliArgs.push(`--rules=${rules}`);
901
+ }
902
+ }
903
+ if (options.threads != null) {
904
+ cliArgs.push(`--threads=${options.threads}`);
905
+ }
906
+
907
+ cliArgs.push("--format=json");
908
+
909
+ if (options.extraArgs) {
910
+ cliArgs.push(...normalizeStringArray(options.extraArgs, "extraArgs"));
911
+ }
912
+
913
+ cliArgs.push(...normalizeStringArray(Array.isArray(paths) ? paths : [paths], "paths"));
914
+ return cliArgs;
915
+ }
916
+
917
+ function mergeLintOptions(base, override) {
918
+ return {
919
+ ...eslintConstructorOptions(base),
920
+ ...override
921
+ };
922
+ }
923
+
924
+ function eslintConstructorOptions(options) {
925
+ const mapped = {
926
+ cwd: options.cwd,
927
+ threads: options.threads,
928
+ binary: options.binary,
929
+ env: options.env,
930
+ extraArgs: options.extraArgs,
931
+ config: options.config ?? options.configFile,
932
+ ignorePath: options.ignorePath,
933
+ ignorePatterns: options.ignorePatterns,
934
+ noIgnore: options.noIgnore ?? options.ignore === false,
935
+ baseConfig: options.baseConfig,
936
+ noConfig: options.noConfig,
937
+ fix: options.fix,
938
+ quiet: options.quiet,
939
+ errorOnUnmatchedPattern: options.errorOnUnmatchedPattern,
940
+ warnIgnored: options.warnIgnored
941
+ };
942
+
943
+ if (options.useEslintrc === false || options.overrideConfigFile === true) {
944
+ mapped.noConfig = true;
945
+ }
946
+ if (typeof options.overrideConfigFile === "string") {
947
+ mapped.config = options.overrideConfigFile;
948
+ }
949
+ if (options.overrideConfig) {
950
+ mapped.overrideConfig = Array.isArray(options.overrideConfig) ? options.overrideConfig : { ...options.overrideConfig };
951
+ if (!Array.isArray(mapped.overrideConfig) && !mapped.overrideConfig.rules && options.baseConfig?.rules) {
952
+ mapped.overrideConfig.rules = options.baseConfig.rules;
953
+ }
954
+ } else if (options.baseConfig?.rules) {
955
+ mapped.overrideConfig = { rules: options.baseConfig.rules };
956
+ }
957
+ return mapped;
958
+ }
959
+
960
+ function flagsFromOptions(options = {}) {
961
+ return Array.isArray(options.flags) ? [...options.flags] : [];
962
+ }
963
+
964
+ function hasFlagInOptions(options, flag) {
965
+ return flagsFromOptions(options).includes(flag);
966
+ }
967
+
968
+ function calculatedConfig(options = {}, filePath) {
969
+ const configData = mergeConfigData(
970
+ mergeConfigData(configDataFromConfig(options.baseConfig, filePath, options.cwd), configDataFromFileConfig(options, filePath)),
971
+ configDataFromConfig(options.overrideConfig, filePath, options.cwd)
972
+ );
973
+ return {
974
+ ...configData,
975
+ rules: {
976
+ ...rulesFromNativeRuleList(options.rules),
977
+ ...rulesFromConfig(options.baseConfig, filePath, options.cwd),
978
+ ...rulesFromFileConfig(options, filePath),
979
+ ...rulesFromConfig(options.overrideConfig, filePath, options.cwd)
980
+ }
981
+ };
982
+ }
983
+
984
+ function configDataFromConfig(config, filePath, cwd) {
985
+ if (!config) {
986
+ return {};
987
+ }
988
+ if (Array.isArray(config)) {
989
+ return config.reduce((result, entry) => mergeConfigData(result, configDataFromConfig(entry, filePath, cwd)), {});
990
+ }
991
+ if (!configAppliesToFile(config, filePath, cwd)) {
992
+ return {};
993
+ }
994
+
995
+ const result = {};
996
+ if (config.settings && typeof config.settings === "object") {
997
+ result.settings = { ...config.settings };
998
+ }
999
+ if (config.languageOptions && typeof config.languageOptions === "object") {
1000
+ result.languageOptions = { ...config.languageOptions };
1001
+ }
1002
+ if (config.parserOptions && typeof config.parserOptions === "object") {
1003
+ result.parserOptions = { ...config.parserOptions };
1004
+ }
1005
+ if (config.globals && typeof config.globals === "object") {
1006
+ result.globals = { ...config.globals };
1007
+ }
1008
+ if (config.env && typeof config.env === "object") {
1009
+ result.env = { ...config.env };
1010
+ }
1011
+ if (config.parser != null) {
1012
+ result.parser = config.parser;
1013
+ }
1014
+ return result;
1015
+ }
1016
+
1017
+ function mergeConfigData(base, override) {
1018
+ const result = { ...base, ...override };
1019
+ if (base.settings || override.settings) {
1020
+ result.settings = { ...(base.settings ?? {}), ...(override.settings ?? {}) };
1021
+ }
1022
+ if (base.languageOptions || override.languageOptions) {
1023
+ result.languageOptions = { ...(base.languageOptions ?? {}), ...(override.languageOptions ?? {}) };
1024
+ }
1025
+ if (base.parserOptions || override.parserOptions) {
1026
+ result.parserOptions = { ...(base.parserOptions ?? {}), ...(override.parserOptions ?? {}) };
1027
+ }
1028
+ if (base.globals || override.globals) {
1029
+ result.globals = { ...(base.globals ?? {}), ...(override.globals ?? {}) };
1030
+ }
1031
+ if (base.env || override.env) {
1032
+ result.env = { ...(base.env ?? {}), ...(override.env ?? {}) };
1033
+ }
1034
+ return result;
1035
+ }
1036
+
1037
+ function publicCalculatedConfig(options = {}, filePath) {
1038
+ const config = calculatedConfig(options, filePath);
1039
+ return {
1040
+ ...config,
1041
+ rules: Object.fromEntries(
1042
+ Object.entries(config.rules ?? {}).map(([rule, value]) => [rule, publicRuleConfigValue(value)])
1043
+ )
1044
+ };
1045
+ }
1046
+
1047
+ function publicRuleConfigValue(value) {
1048
+ if (Array.isArray(value)) {
1049
+ const [severity, ...rest] = value;
1050
+ return [ruleConfigSeverity(severity), ...rest];
1051
+ }
1052
+ return [ruleConfigSeverity(value)];
1053
+ }
1054
+
1055
+ function rulesFromNativeRuleList(rules) {
1056
+ if (!rules) {
1057
+ return {};
1058
+ }
1059
+ const values = Array.isArray(rules) ? rules : String(rules).split(",");
1060
+ const result = {};
1061
+ for (const rule of values) {
1062
+ const name = String(rule).trim();
1063
+ if (name) {
1064
+ result[name] = "warn";
1065
+ }
1066
+ }
1067
+ return result;
1068
+ }
1069
+
1070
+ function rulesFromConfig(config, filePath, cwd) {
1071
+ if (!config) {
1072
+ return {};
1073
+ }
1074
+ if (Array.isArray(config)) {
1075
+ return config.reduce((rules, entry) => ({
1076
+ ...rules,
1077
+ ...rulesFromConfig(entry, filePath, cwd)
1078
+ }), {});
1079
+ }
1080
+ if (!configAppliesToFile(config, filePath, cwd)) {
1081
+ return {};
1082
+ }
1083
+ return config.rules && typeof config.rules === "object" ? config.rules : {};
1084
+ }
1085
+
1086
+ function rulesFromFileConfig(options, filePath) {
1087
+ if (options.noConfig) {
1088
+ return {};
1089
+ }
1090
+
1091
+ const configPath = filePath ? configPathForFile(options, filePath) : configPathForOptions(options);
1092
+ if (!configPath) {
1093
+ return {};
1094
+ }
1095
+
1096
+ const config = readConfig(configPath, options.cwd);
1097
+ return rulesFromConfig(config, filePath, options.cwd);
1098
+ }
1099
+
1100
+ function configDataFromFileConfig(options, filePath) {
1101
+ if (options.noConfig) {
1102
+ return {};
1103
+ }
1104
+
1105
+ const configPath = filePath ? configPathForFile(options, filePath) : configPathForOptions(options);
1106
+ if (!configPath) {
1107
+ return {};
1108
+ }
1109
+
1110
+ const config = readConfig(configPath, options.cwd);
1111
+ return configDataFromConfig(config, filePath, options.cwd);
1112
+ }
1113
+
1114
+ function configAppliesToFile(config, filePath, cwd) {
1115
+ if (!filePath) {
1116
+ return true;
1117
+ }
1118
+
1119
+ const normalized = normalizeIgnoredPath(filePath, cwd ?? process.cwd());
1120
+ const files = normalizeConfigPatterns(config.files);
1121
+ if (files.length > 0 && !files.some((pattern) => matchesConfigFilePattern(normalized, normalizeIgnoredPattern(pattern)))) {
1122
+ return false;
1123
+ }
1124
+
1125
+ const ignores = normalizeIgnorePatterns(config.ignores);
1126
+ return !pathIgnoredByPatterns(normalized, ignores);
1127
+ }
1128
+
1129
+ function normalizeConfigPatterns(patterns) {
1130
+ if (!patterns) {
1131
+ return [];
1132
+ }
1133
+ const values = Array.isArray(patterns) ? patterns : [patterns];
1134
+ return values.flatMap((value) => {
1135
+ if (typeof value === "string") {
1136
+ return [value];
1137
+ }
1138
+ return [];
1139
+ });
1140
+ }
1141
+
1142
+ function configPathForOptions(options) {
1143
+ if (options.config) {
1144
+ return resolvePath(options.cwd ?? process.cwd(), options.config);
1145
+ }
1146
+
1147
+ const cwd = options.cwd ?? process.cwd();
1148
+ return configPathFromDirectory(cwd);
1149
+ }
1150
+
1151
+ function configPathForFile(options, filePath) {
1152
+ if (options.config) {
1153
+ return resolvePath(options.cwd ?? process.cwd(), options.config);
1154
+ }
1155
+ return configPathFromDirectory(configSearchDirectoryForFile(filePath, options.cwd));
1156
+ }
1157
+
1158
+ function configSearchDirectoryForFile(filePath, cwd) {
1159
+ const absolute = normalizeESLintFilePath(filePath, cwd);
1160
+ try {
1161
+ if (statSync(absolute).isDirectory()) {
1162
+ return absolute;
1163
+ }
1164
+ } catch {
1165
+ // Non-existent filenames are common for editor integrations.
1166
+ }
1167
+ return dirname(absolute);
1168
+ }
1169
+
1170
+ function configPathFromDirectory(directory) {
1171
+ let current = resolvePath(directory);
1172
+ while (true) {
1173
+ for (const candidate of CONFIG_FILENAMES) {
1174
+ const path = resolvePath(current, candidate);
1175
+ if (existsSync(path)) {
1176
+ return path;
1177
+ }
1178
+ }
1179
+ const parent = dirname(current);
1180
+ if (parent === current) {
1181
+ return undefined;
1182
+ }
1183
+ current = parent;
1184
+ }
1185
+ }
1186
+
1187
+ function readConfig(path, cwd) {
1188
+ if (isJavaScriptConfigPath(path)) {
1189
+ return readJavaScriptConfig(path, cwd);
1190
+ }
1191
+
1192
+ try {
1193
+ return JSON.parse(readFileSync(path, "utf8"));
1194
+ } catch (error) {
1195
+ throw new Error(`utoo-lint unable to read config ${path}: ${error.message}`);
1196
+ }
1197
+ }
1198
+
1199
+ function readJavaScriptConfig(path, cwd) {
1200
+ const result = spawnSync(process.execPath, ["--input-type=module", "--eval", JAVASCRIPT_CONFIG_LOADER_SCRIPT, path], {
1201
+ cwd: cwd ?? process.cwd(),
1202
+ encoding: "utf8"
1203
+ });
1204
+ if (result.error) {
1205
+ throw result.error;
1206
+ }
1207
+ if (result.status !== 0) {
1208
+ throw new Error(`utoo-lint unable to read config ${path}: ${(result.stderr ?? "").trim() || "JavaScript config loader failed"}`);
1209
+ }
1210
+ try {
1211
+ return JSON.parse(result.stdout);
1212
+ } catch (error) {
1213
+ throw new Error(`utoo-lint unable to read config ${path}: ${error.message}`);
1214
+ }
1215
+ }
1216
+
1217
+ function isJavaScriptConfigPath(path) {
1218
+ return JAVASCRIPT_CONFIG_EXTENSIONS.has(extname(path));
1219
+ }
1220
+
1221
+ function withTemporaryConfig(options, callback) {
1222
+ const fileConfigPath = !options.noConfig ? configPathForOptions(options) : undefined;
1223
+ const shouldMaterializeFileConfig = fileConfigPath && isJavaScriptConfigPath(fileConfigPath);
1224
+ const fileConfig = shouldMaterializeFileConfig ? readConfig(fileConfigPath, options.cwd) : undefined;
1225
+ const inlineRules = {
1226
+ ...rulesFromConfig(fileConfig),
1227
+ ...rulesFromConfig(options.baseConfig),
1228
+ ...rulesFromConfig(options.overrideConfig)
1229
+ };
1230
+ if (Object.keys(inlineRules).length === 0) {
1231
+ if (shouldMaterializeFileConfig) {
1232
+ return callback({
1233
+ ...options,
1234
+ config: undefined,
1235
+ noConfig: true
1236
+ });
1237
+ }
1238
+ return callback(options);
1239
+ }
1240
+
1241
+ const rules = runtimeRulesFromConfigs(fileConfig, options.baseConfig, options.overrideConfig);
1242
+ const enabledRules = enabledRuleNamesFromConfigs(fileConfig, options.baseConfig, options.overrideConfig);
1243
+ if (!hasRuleOptions(rules)) {
1244
+ return callback({
1245
+ ...options,
1246
+ config: shouldMaterializeFileConfig ? undefined : options.config,
1247
+ noConfig: shouldMaterializeFileConfig ? true : options.noConfig ?? true,
1248
+ rules: options.rules ?? enabledRules
1249
+ });
1250
+ }
1251
+
1252
+ const tmp = mkdtempSync(join(tmpdir(), "utoo-lint-config-"));
1253
+ const configPath = join(tmp, "utoo.json");
1254
+ try {
1255
+ writeFileSync(configPath, JSON.stringify({ rules }));
1256
+ return callback({
1257
+ ...options,
1258
+ config: shouldMaterializeFileConfig ? configPath : options.config ?? configPath,
1259
+ noConfig: false
1260
+ });
1261
+ } finally {
1262
+ rmSync(tmp, { recursive: true, force: true });
1263
+ }
1264
+ }
1265
+
1266
+ function enabledRuleNames(rules) {
1267
+ return Object.entries(rules)
1268
+ .filter(([, value]) => ruleConfigSeverity(value) > 0)
1269
+ .map(([rule]) => rule);
1270
+ }
1271
+
1272
+ function runtimeRulesFromConfigs(...configs) {
1273
+ const rules = {};
1274
+ for (const config of configs) {
1275
+ Object.assign(rules, runtimeRulesFromConfig(config));
1276
+ }
1277
+ return rules;
1278
+ }
1279
+
1280
+ function runtimeRulesFromConfig(config) {
1281
+ if (!config) {
1282
+ return {};
1283
+ }
1284
+ if (Array.isArray(config)) {
1285
+ return config.reduce((rules, entry) => ({
1286
+ ...rules,
1287
+ ...runtimeRulesFromConfig(entry)
1288
+ }), {});
1289
+ }
1290
+
1291
+ const rules = {};
1292
+ for (const [rule, value] of Object.entries(rulesFromConfig(config))) {
1293
+ if (ruleConfigSeverity(value) > 0) {
1294
+ rules[rule] = value;
1295
+ }
1296
+ }
1297
+ return rules;
1298
+ }
1299
+
1300
+ function enabledRuleNamesFromConfigs(...configs) {
1301
+ const rules = new Set();
1302
+ for (const config of configs) {
1303
+ for (const rule of enabledRuleNamesFromConfig(config)) {
1304
+ rules.add(rule);
1305
+ }
1306
+ }
1307
+ return [...rules];
1308
+ }
1309
+
1310
+ function enabledRuleNamesFromConfig(config) {
1311
+ if (!config) {
1312
+ return [];
1313
+ }
1314
+ if (Array.isArray(config)) {
1315
+ return config.flatMap((entry) => enabledRuleNamesFromConfig(entry));
1316
+ }
1317
+ return enabledRuleNames(rulesFromConfig(config));
1318
+ }
1319
+
1320
+ function hasRuleOptions(rules) {
1321
+ return Object.values(rules).some((value) => Array.isArray(value) && value.length > 1);
1322
+ }
1323
+
1324
+ async function loadESLint() {
1325
+ return UtooLint;
1326
+ }
1327
+
1328
+ class Linter {
1329
+ static get version() {
1330
+ return version;
1331
+ }
1332
+
1333
+ constructor(options = {}) {
1334
+ this.flags = flagsFromOptions(options);
1335
+ this.rules = new Map();
1336
+ this.parsers = new Map();
1337
+ this.sourceCode = null;
1338
+ this.suppressedMessages = [];
1339
+ this.times = { passes: [] };
1340
+ this.fixPassCount = 0;
1341
+ }
1342
+
1343
+ verify(code, config = {}, options = {}) {
1344
+ if (typeof code !== "string") {
1345
+ throw new TypeError("code must be a string");
1346
+ }
1347
+
1348
+ const verifyOptions = typeof options === "string" ? { filename: options } : { ...options };
1349
+ const filePath = verifyOptions.filename ?? verifyOptions.filePath ?? "input.js";
1350
+ const normalizedFilePath = normalizeESLintFilePath(filePath, verifyOptions.cwd);
1351
+ const sourceCode = createLinterSourceCode(
1352
+ code,
1353
+ parserForConfig(config, this.parsers, normalizedFilePath, verifyOptions.cwd),
1354
+ parserOptionsForConfig(config, normalizedFilePath, verifyOptions.cwd)
1355
+ );
1356
+ const customRuleMap = customRuleMapForConfig(config, this.rules, normalizedFilePath, verifyOptions.cwd);
1357
+ const customRuleFilterMap = customRuleMapForConfig(config, this.rules);
1358
+ const customRules = customRuleEntriesForConfig(config, customRuleMap, normalizedFilePath, verifyOptions.cwd);
1359
+ const nativeConfig = configWithoutCustomRules(config, customRuleFilterMap);
1360
+ const lintOptions = {
1361
+ cwd: verifyOptions.cwd,
1362
+ filePath,
1363
+ noConfig: true,
1364
+ noIgnore: true,
1365
+ warnIgnored: false,
1366
+ overrideConfig: nativeConfig
1367
+ };
1368
+ const report = lintText(code, lintOptions);
1369
+ const ruleSeverities = ruleSeverityMapForOptions(lintOptions, normalizedFilePath);
1370
+ this.sourceCode = sourceCode;
1371
+ const customRuleMessages = runCustomLinterRules(customRules, sourceCode, {
1372
+ cwd: verifyOptions.cwd,
1373
+ filename: filePath,
1374
+ config: calculatedConfig({ cwd: verifyOptions.cwd, noConfig: true, overrideConfig: config }, normalizedFilePath)
1375
+ });
1376
+ const customRuleFilter = applyDisableDirectives(customRuleMessages, sourceCode);
1377
+ this.suppressedMessages = customRuleFilter.suppressedMessages;
1378
+ this.times = { passes: [] };
1379
+ this.fixPassCount = 0;
1380
+ return [
1381
+ ...(report.diagnostics ?? []).map((diagnostic) => diagnosticToESLintMessage(diagnostic, ruleSeverities)),
1382
+ ...customRuleFilter.messages
1383
+ ];
1384
+ }
1385
+
1386
+ verifyAndFix(code, config = {}, options = {}) {
1387
+ let output = code;
1388
+ let fixed = false;
1389
+ let fixPassCount = 0;
1390
+ for (let pass = 0; pass < MAX_AUTOFIX_PASSES; pass += 1) {
1391
+ const messages = this.verify(output, config, options);
1392
+ const fixedOutput = applyRuleFixes(output, messages.flatMap((message) => ruleFixItems(message.fix)));
1393
+ if (fixedOutput === output) {
1394
+ this.fixPassCount = fixPassCount;
1395
+ return {
1396
+ fixed,
1397
+ messages,
1398
+ output
1399
+ };
1400
+ }
1401
+ fixed = true;
1402
+ fixPassCount += 1;
1403
+ output = fixedOutput;
1404
+ }
1405
+ const messages = this.verify(output, config, options);
1406
+ this.fixPassCount = fixPassCount;
1407
+ return {
1408
+ fixed,
1409
+ messages,
1410
+ output
1411
+ };
1412
+ }
1413
+
1414
+ getSourceCode() {
1415
+ return this.sourceCode;
1416
+ }
1417
+
1418
+ getSuppressedMessages() {
1419
+ return this.suppressedMessages;
1420
+ }
1421
+
1422
+ getTimes() {
1423
+ return this.times;
1424
+ }
1425
+
1426
+ getFixPassCount() {
1427
+ return this.fixPassCount;
1428
+ }
1429
+
1430
+ hasFlag(flag) {
1431
+ return this.flags.includes(flag);
1432
+ }
1433
+
1434
+ getRules() {
1435
+ return new Map([...BUILTIN_RULES, ...this.rules]);
1436
+ }
1437
+
1438
+ defineRule(ruleId, rule) {
1439
+ if (typeof ruleId !== "string" || ruleId.length === 0) {
1440
+ throw new TypeError("Linter#defineRule requires a rule id string");
1441
+ }
1442
+ this.rules.set(ruleId, rule);
1443
+ }
1444
+
1445
+ defineRules(rules) {
1446
+ if (typeof rules !== "object" || rules === null) {
1447
+ throw new TypeError("Linter#defineRules requires a rules object");
1448
+ }
1449
+ for (const [ruleId, rule] of Object.entries(rules)) {
1450
+ this.defineRule(ruleId, rule);
1451
+ }
1452
+ }
1453
+
1454
+ defineParser(parserId, parser) {
1455
+ if (typeof parserId !== "string" || parserId.length === 0) {
1456
+ throw new TypeError("Linter#defineParser requires a parser id string");
1457
+ }
1458
+ this.parsers.set(parserId, parser);
1459
+ }
1460
+ }
1461
+
1462
+ function parserForConfig(config, parsers, filePath, cwd) {
1463
+ let parser = null;
1464
+ for (const entry of matchingConfigEntries(config, filePath, cwd)) {
1465
+ parser = entry.languageOptions?.parser ?? entry.parser ?? parser;
1466
+ }
1467
+ if (typeof parser === "string") {
1468
+ return parsers.get(parser) ?? null;
1469
+ }
1470
+ return parser && typeof parser === "object" ? parser : null;
1471
+ }
1472
+
1473
+ function parserOptionsForConfig(config, filePath, cwd) {
1474
+ const configData = configDataFromConfig(config, filePath, cwd);
1475
+ return {
1476
+ ...(configData.parserOptions ?? {}),
1477
+ ...(configData.languageOptions?.parserOptions ?? {}),
1478
+ filename: filePath,
1479
+ filePath,
1480
+ cwd
1481
+ };
1482
+ }
1483
+
1484
+ function customRuleEntriesForConfig(config, rules, filePath, cwd) {
1485
+ const entries = new Map();
1486
+ for (const [ruleId, ruleConfig] of ruleConfigEntries(config, filePath, cwd)) {
1487
+ if (!rules.has(ruleId)) {
1488
+ continue;
1489
+ }
1490
+ const severity = ruleConfigSeverity(ruleConfig);
1491
+ if (severity === 0) {
1492
+ entries.delete(ruleId);
1493
+ continue;
1494
+ }
1495
+ entries.set(ruleId, {
1496
+ ruleId,
1497
+ rule: rules.get(ruleId),
1498
+ severity,
1499
+ options: Array.isArray(ruleConfig) ? ruleConfig.slice(1) : []
1500
+ });
1501
+ }
1502
+ return [...entries.values()];
1503
+ }
1504
+
1505
+ function customRuleMapForConfig(config, definedRules, filePath, cwd) {
1506
+ return new Map([
1507
+ ...pluginRulesForConfig(config, filePath, cwd),
1508
+ ...definedRules
1509
+ ]);
1510
+ }
1511
+
1512
+ function pluginRulesForConfig(config, filePath, cwd) {
1513
+ const rules = new Map();
1514
+ for (const entry of matchingConfigEntries(config, filePath, cwd)) {
1515
+ if (!entry.plugins || typeof entry.plugins !== "object") {
1516
+ continue;
1517
+ }
1518
+ for (const [pluginName, plugin] of Object.entries(entry.plugins)) {
1519
+ if (!plugin?.rules || typeof plugin.rules !== "object") {
1520
+ continue;
1521
+ }
1522
+ for (const [ruleName, rule] of Object.entries(plugin.rules)) {
1523
+ rules.set(`${pluginName}/${ruleName}`, rule);
1524
+ }
1525
+ }
1526
+ }
1527
+ return rules;
1528
+ }
1529
+
1530
+ function matchingConfigEntries(config, filePath = undefined, cwd = undefined) {
1531
+ if (!config) {
1532
+ return [];
1533
+ }
1534
+ if (Array.isArray(config)) {
1535
+ return config.flatMap((entry) => matchingConfigEntries(entry, filePath, cwd));
1536
+ }
1537
+ if (typeof config !== "object" || !configAppliesToFile(config, filePath, cwd)) {
1538
+ return [];
1539
+ }
1540
+ return [config];
1541
+ }
1542
+
1543
+ function ruleConfigEntries(config, filePath = undefined, cwd = undefined) {
1544
+ if (Array.isArray(config)) {
1545
+ return config.flatMap((item) => ruleConfigEntries(item, filePath, cwd));
1546
+ }
1547
+ if (!config || typeof config !== "object" || !config.rules || typeof config.rules !== "object") {
1548
+ return [];
1549
+ }
1550
+ if (!configAppliesToFile(config, filePath, cwd)) {
1551
+ return [];
1552
+ }
1553
+ return Object.entries(config.rules);
1554
+ }
1555
+
1556
+ function configWithoutCustomRules(config, rules) {
1557
+ if (Array.isArray(config)) {
1558
+ return config.map((item) => configWithoutCustomRules(item, rules));
1559
+ }
1560
+ if (!config || typeof config !== "object" || !config.rules || typeof config.rules !== "object") {
1561
+ return config;
1562
+ }
1563
+
1564
+ let removedCustomRule = false;
1565
+ const nativeRules = {};
1566
+ for (const [ruleId, ruleConfig] of Object.entries(config.rules)) {
1567
+ if (rules.has(ruleId)) {
1568
+ removedCustomRule = true;
1569
+ continue;
1570
+ }
1571
+ nativeRules[ruleId] = ruleConfig;
1572
+ }
1573
+ return removedCustomRule ? { ...config, rules: nativeRules } : config;
1574
+ }
1575
+
1576
+ function lintOptionsWithoutCustomRules(options, rules) {
1577
+ if (rules.size === 0) {
1578
+ return options;
1579
+ }
1580
+ return {
1581
+ ...options,
1582
+ baseConfig: configWithoutCustomRules(options.baseConfig, rules),
1583
+ overrideConfig: configWithoutCustomRules(options.overrideConfig, rules)
1584
+ };
1585
+ }
1586
+
1587
+ function appendCustomLintTextMessages(results, code, filePath, config, rules, options) {
1588
+ const customRules = customRuleEntriesForConfig(config, rules, filePath, options.cwd);
1589
+ if (customRules.length === 0) {
1590
+ return results;
1591
+ }
1592
+ const sourceCode = createLinterSourceCode(code);
1593
+ const messages = runCustomLinterRules(customRules, sourceCode, {
1594
+ cwd: options.cwd,
1595
+ filename: filePath,
1596
+ config: calculatedConfig({ cwd: options.cwd, noConfig: true, baseConfig: options.baseConfig, overrideConfig: options.overrideConfig }, filePath)
1597
+ });
1598
+ const filtered = applyDisableDirectives(messages, sourceCode);
1599
+ if (filtered.messages.length === 0 && filtered.suppressedMessages.length === 0) {
1600
+ return results;
1601
+ }
1602
+
1603
+ let result = results.find((item) => item.filePath === filePath);
1604
+ if (!result) {
1605
+ result = emptyESLintResult(filePath, code);
1606
+ results.push(result);
1607
+ }
1608
+ result.messages.push(...filtered.messages);
1609
+ result.suppressedMessages.push(...filtered.suppressedMessages);
1610
+ applyResultFixes(result, code, options);
1611
+ finalizeESLintResult(result);
1612
+ return results;
1613
+ }
1614
+
1615
+ function appendCustomLintFileMessages(results, config, options) {
1616
+ for (const result of results) {
1617
+ const filePath = result.filePath;
1618
+ const rules = customRuleMapForConfig(config, new Map(), filePath, options.cwd);
1619
+ const customRules = customRuleEntriesForConfig(config, rules, filePath, options.cwd);
1620
+ if (customRules.length === 0) {
1621
+ continue;
1622
+ }
1623
+ let code;
1624
+ try {
1625
+ code = readFileSync(filePath, "utf8");
1626
+ } catch {
1627
+ continue;
1628
+ }
1629
+ const sourceCode = createLinterSourceCode(code);
1630
+ const messages = runCustomLinterRules(customRules, sourceCode, {
1631
+ cwd: options.cwd,
1632
+ filename: filePath,
1633
+ config: calculatedConfig({ cwd: options.cwd, noConfig: true, baseConfig: options.baseConfig, overrideConfig: options.overrideConfig }, filePath)
1634
+ });
1635
+ const filtered = applyDisableDirectives(messages, sourceCode);
1636
+ if (filtered.messages.length === 0 && filtered.suppressedMessages.length === 0) {
1637
+ continue;
1638
+ }
1639
+ result.messages.push(...filtered.messages);
1640
+ result.suppressedMessages.push(...filtered.suppressedMessages);
1641
+ applyResultFixes(result, code, options);
1642
+ finalizeESLintResult(result);
1643
+ }
1644
+ return results;
1645
+ }
1646
+
1647
+ function applyResultFixes(result, code, options) {
1648
+ if (!options.fix) {
1649
+ return;
1650
+ }
1651
+ const output = applyRuleFixes(code, result.messages.flatMap((message) => ruleFixItems(message.fix)));
1652
+ if (output !== code) {
1653
+ result.output = output;
1654
+ }
1655
+ }
1656
+
1657
+ function runCustomLinterRules(ruleEntries, sourceCode, options) {
1658
+ if (ruleEntries.length === 0) {
1659
+ return [];
1660
+ }
1661
+
1662
+ const messages = [];
1663
+ const program = sourceCode.ast ?? { type: "Program", range: [0, sourceCode.text.length] };
1664
+ for (const ruleEntry of ruleEntries) {
1665
+ const context = createCustomRuleContext(ruleEntry, sourceCode, options, messages);
1666
+ const listeners = customRuleListeners(ruleEntry.rule, context);
1667
+ if (customRuleChildNodes(program, sourceCode.visitorKeys).length > 0) {
1668
+ traverseCustomRuleAst(program, listeners, context, sourceCode.visitorKeys);
1669
+ } else {
1670
+ runCustomRuleTokenListeners(program, sourceCode.tokens, listeners, context);
1671
+ }
1672
+ }
1673
+ return messages;
1674
+ }
1675
+
1676
+ function applyDisableDirectives(messages, sourceCode) {
1677
+ if (messages.length === 0) {
1678
+ return { messages, suppressedMessages: [] };
1679
+ }
1680
+ const directives = sourceCode.getDisableDirectives()
1681
+ .filter((directive) => directive.node?.loc?.start?.line)
1682
+ .sort((left, right) => left.node.loc.start.line - right.node.loc.start.line);
1683
+ if (directives.length === 0) {
1684
+ return { messages, suppressedMessages: [] };
1685
+ }
1686
+
1687
+ const kept = [];
1688
+ const suppressed = [];
1689
+ for (const message of messages) {
1690
+ const directive = disableDirectiveForMessage(message, directives);
1691
+ if (directive) {
1692
+ suppressed.push({
1693
+ ...message,
1694
+ suppressions: [{
1695
+ kind: "directive",
1696
+ justification: directive.justification
1697
+ }]
1698
+ });
1699
+ } else {
1700
+ kept.push(message);
1701
+ }
1702
+ }
1703
+ return { messages: kept, suppressedMessages: suppressed };
1704
+ }
1705
+
1706
+ function disableDirectiveForMessage(message, directives) {
1707
+ const active = {
1708
+ all: null,
1709
+ allEnabledRules: new Set(),
1710
+ rules: new Map()
1711
+ };
1712
+ for (const directive of directives) {
1713
+ const line = directive.node.loc.start.line;
1714
+ if (directive.type === "eslint-disable-line" && line === message.line && disableDirectiveMatchesRule(directive, message.ruleId)) {
1715
+ return directive;
1716
+ }
1717
+ if (directive.type === "eslint-disable-next-line" && line + 1 === message.line && disableDirectiveMatchesRule(directive, message.ruleId)) {
1718
+ return directive;
1719
+ }
1720
+ if (line > message.line) {
1721
+ break;
1722
+ }
1723
+ if (directive.type === "eslint-disable") {
1724
+ if (directive.ruleId) {
1725
+ active.rules.set(directive.ruleId, directive);
1726
+ active.allEnabledRules.delete(directive.ruleId);
1727
+ } else {
1728
+ active.all = directive;
1729
+ active.allEnabledRules.clear();
1730
+ }
1731
+ } else if (directive.type === "eslint-enable") {
1732
+ if (directive.ruleId) {
1733
+ active.rules.delete(directive.ruleId);
1734
+ if (active.all) {
1735
+ active.allEnabledRules.add(directive.ruleId);
1736
+ }
1737
+ } else {
1738
+ active.all = null;
1739
+ active.allEnabledRules.clear();
1740
+ active.rules.clear();
1741
+ }
1742
+ }
1743
+ }
1744
+ if (active.rules.has(message.ruleId)) {
1745
+ return active.rules.get(message.ruleId);
1746
+ }
1747
+ if (active.all && !active.allEnabledRules.has(message.ruleId)) {
1748
+ return active.all;
1749
+ }
1750
+ return null;
1751
+ }
1752
+
1753
+ function disableDirectiveMatchesRule(directive, ruleId) {
1754
+ return directive.ruleId == null || directive.ruleId === ruleId;
1755
+ }
1756
+
1757
+ function traverseCustomRuleAst(node, listeners, context, visitorKeys, seen = new Set(), ancestors = [], siblings = {}) {
1758
+ if (!node || typeof node !== "object" || typeof node.type !== "string" || seen.has(node)) {
1759
+ return;
1760
+ }
1761
+ seen.add(node);
1762
+ for (const listener of customRuleMatchingListeners(listeners, node, false, ancestors, siblings, visitorKeys)) {
1763
+ context.setCurrentNode(node);
1764
+ listener(node);
1765
+ }
1766
+ const children = customRuleChildNodes(node, visitorKeys);
1767
+ for (const [index, child] of children.entries()) {
1768
+ traverseCustomRuleAst(child, listeners, context, visitorKeys, seen, [...ancestors, node], {
1769
+ previous: children[index - 1] ?? null,
1770
+ previousAll: children.slice(0, index)
1771
+ });
1772
+ }
1773
+ for (const listener of customRuleMatchingListeners(listeners, node, true, ancestors, siblings, visitorKeys)) {
1774
+ context.setCurrentNode(node);
1775
+ listener(node);
1776
+ }
1777
+ }
1778
+
1779
+ function customRuleMatchingListeners(listeners, node, exit, ancestors = [], siblings = {}, visitorKeys = null) {
1780
+ const matches = [];
1781
+ for (const [selector, listener] of Object.entries(listeners)) {
1782
+ if (typeof listener !== "function" || !customRuleSelectorMatches(selector, node, exit, ancestors, siblings, visitorKeys)) {
1783
+ continue;
1784
+ }
1785
+ matches.push(listener);
1786
+ }
1787
+ return matches;
1788
+ }
1789
+
1790
+ function customRuleSelectorMatches(selector, node, exit, ancestors = [], siblings = {}, visitorKeys = null) {
1791
+ const suffix = ":exit";
1792
+ const isExit = selector.endsWith(suffix);
1793
+ if (isExit !== exit) {
1794
+ return false;
1795
+ }
1796
+ const expression = isExit ? selector.slice(0, -suffix.length) : selector;
1797
+ const childSelector = customRuleSplitTopLevelSelector(expression, ">");
1798
+ if (childSelector) {
1799
+ const parent = ancestors.at(-1);
1800
+ return Boolean(parent)
1801
+ && customRuleSimpleSelectorMatches(childSelector[0].trim(), parent, visitorKeys)
1802
+ && customRuleSimpleSelectorMatches(childSelector[1].trim(), node, visitorKeys);
1803
+ }
1804
+ const adjacentSelector = customRuleSplitTopLevelSelector(expression, "+");
1805
+ if (adjacentSelector) {
1806
+ return Boolean(siblings.previous)
1807
+ && customRuleSimpleSelectorMatches(adjacentSelector[0].trim(), siblings.previous, visitorKeys)
1808
+ && customRuleSimpleSelectorMatches(adjacentSelector[1].trim(), node, visitorKeys);
1809
+ }
1810
+ const siblingSelector = customRuleSplitTopLevelSelector(expression, "~");
1811
+ if (siblingSelector) {
1812
+ return (siblings.previousAll ?? []).some((sibling) => customRuleSimpleSelectorMatches(siblingSelector[0].trim(), sibling, visitorKeys))
1813
+ && customRuleSimpleSelectorMatches(siblingSelector[1].trim(), node, visitorKeys);
1814
+ }
1815
+ const descendantSelector = customRuleSplitTopLevelDescendantSelector(expression);
1816
+ if (descendantSelector) {
1817
+ return ancestors.some((ancestor) => customRuleSimpleSelectorMatches(descendantSelector[0].trim(), ancestor, visitorKeys))
1818
+ && customRuleSimpleSelectorMatches(descendantSelector[1].trim(), node, visitorKeys);
1819
+ }
1820
+ return customRuleSimpleSelectorMatches(expression, node, visitorKeys);
1821
+ }
1822
+
1823
+ function customRuleSimpleSelectorMatches(expression, node, visitorKeys = null) {
1824
+ const hasSelector = expression.match(/^(.+?):has\((.+)\)$/u);
1825
+ if (hasSelector) {
1826
+ return customRuleSimpleSelectorMatches(hasSelector[1].trim(), node, visitorKeys)
1827
+ && customRuleDescendants(node, visitorKeys).some((descendant) => (
1828
+ customRuleSplitSelectorList(hasSelector[2]).some((selector) => customRuleSimpleSelectorMatches(selector.trim(), descendant, visitorKeys))
1829
+ ));
1830
+ }
1831
+ const notSelector = expression.match(/^(.+?):not\((.+)\)$/u);
1832
+ if (notSelector) {
1833
+ return customRuleSimpleSelectorMatches(notSelector[1].trim(), node, visitorKeys)
1834
+ && !customRuleSimpleSelectorMatches(notSelector[2].trim(), node, visitorKeys);
1835
+ }
1836
+ const matchesSelector = expression.match(/^(.*?):matches\((.+)\)$/u);
1837
+ if (matchesSelector) {
1838
+ const prefix = matchesSelector[1].trim();
1839
+ if (prefix && !customRuleSimpleSelectorMatches(prefix, node, visitorKeys)) {
1840
+ return false;
1841
+ }
1842
+ return customRuleSplitSelectorList(matchesSelector[2]).some((selector) => (
1843
+ customRuleSimpleSelectorMatches(selector.trim(), node, visitorKeys)
1844
+ ));
1845
+ }
1846
+ if (expression === node.type) {
1847
+ return true;
1848
+ }
1849
+ const match = expression.match(/^([A-Za-z_$][\w$-]*|\*)?(?:\[([^\]]+)\])?$/u);
1850
+ if (!match) {
1851
+ return false;
1852
+ }
1853
+ const [, type = "*", attribute] = match;
1854
+ if (type !== "*" && type !== node.type) {
1855
+ return false;
1856
+ }
1857
+ return attribute ? customRuleAttributeSelectorMatches(node, attribute.trim()) : type === "*" || type === node.type;
1858
+ }
1859
+
1860
+ function customRuleDescendants(node, visitorKeys, seen = new Set()) {
1861
+ const descendants = [];
1862
+ for (const child of customRuleChildNodes(node, visitorKeys)) {
1863
+ if (seen.has(child)) {
1864
+ continue;
1865
+ }
1866
+ seen.add(child);
1867
+ descendants.push(child, ...customRuleDescendants(child, visitorKeys, seen));
1868
+ }
1869
+ return descendants;
1870
+ }
1871
+
1872
+ function customRuleSplitTopLevelSelector(expression, separator) {
1873
+ let state = customRuleSelectorScanState();
1874
+ for (let index = 0; index < expression.length; index += 1) {
1875
+ state = customRuleUpdateSelectorScanState(state, expression[index]);
1876
+ if (state.depth === 0 && !state.quote && expression[index] === separator) {
1877
+ return [expression.slice(0, index), expression.slice(index + 1)];
1878
+ }
1879
+ }
1880
+ return null;
1881
+ }
1882
+
1883
+ function customRuleSplitTopLevelDescendantSelector(expression) {
1884
+ let state = customRuleSelectorScanState();
1885
+ for (let index = 0; index < expression.length; index += 1) {
1886
+ state = customRuleUpdateSelectorScanState(state, expression[index]);
1887
+ if (state.depth === 0 && !state.quote && /\s/u.test(expression[index])) {
1888
+ const left = expression.slice(0, index).trim();
1889
+ const right = expression.slice(index).trim();
1890
+ return left && right ? [left, right] : null;
1891
+ }
1892
+ }
1893
+ return null;
1894
+ }
1895
+
1896
+ function customRuleSplitSelectorList(value) {
1897
+ const selectors = [];
1898
+ let state = customRuleSelectorScanState();
1899
+ let start = 0;
1900
+ for (let index = 0; index < value.length; index += 1) {
1901
+ state = customRuleUpdateSelectorScanState(state, value[index]);
1902
+ if (state.depth === 0 && !state.quote && value[index] === ",") {
1903
+ selectors.push(value.slice(start, index));
1904
+ start = index + 1;
1905
+ }
1906
+ }
1907
+ selectors.push(value.slice(start));
1908
+ return selectors.filter((selector) => selector.trim());
1909
+ }
1910
+
1911
+ function customRuleSelectorScanState() {
1912
+ return { depth: 0, quote: null, escaped: false };
1913
+ }
1914
+
1915
+ function customRuleUpdateSelectorScanState(state, char) {
1916
+ if (state.escaped) {
1917
+ return { ...state, escaped: false };
1918
+ }
1919
+ if (char === "\\") {
1920
+ return { ...state, escaped: true };
1921
+ }
1922
+ if (state.quote) {
1923
+ return char === state.quote ? { ...state, quote: null } : state;
1924
+ }
1925
+ if (char === "\"" || char === "'") {
1926
+ return { ...state, quote: char };
1927
+ }
1928
+ if (char === "[" || char === "(") {
1929
+ return { ...state, depth: state.depth + 1 };
1930
+ }
1931
+ if ((char === "]" || char === ")") && state.depth > 0) {
1932
+ return { ...state, depth: state.depth - 1 };
1933
+ }
1934
+ return state;
1935
+ }
1936
+
1937
+ function customRuleAttributeSelectorMatches(node, attribute) {
1938
+ const match = attribute.match(/^([\w$.-]+)\s*(!=|=)\s*(.+)$/u);
1939
+ if (!match) {
1940
+ return customRuleValueByPath(node, attribute) != null;
1941
+ }
1942
+ const [, path, operator, rawExpected] = match;
1943
+ const actual = customRuleValueByPath(node, path);
1944
+ const expected = customRuleSelectorValue(rawExpected);
1945
+ return operator === "="
1946
+ ? String(actual) === expected
1947
+ : String(actual) !== expected;
1948
+ }
1949
+
1950
+ function customRuleSelectorValue(raw) {
1951
+ const value = raw.trim();
1952
+ const quote = value[0];
1953
+ if ((quote === "\"" || quote === "'") && value.at(-1) === quote) {
1954
+ return value.slice(1, -1);
1955
+ }
1956
+ return value;
1957
+ }
1958
+
1959
+ function customRuleValueByPath(node, path) {
1960
+ return path.split(".").reduce((value, key) => (
1961
+ value && typeof value === "object" ? value[key] : undefined
1962
+ ), node);
1963
+ }
1964
+
1965
+ function customRuleChildNodes(node, visitorKeys) {
1966
+ const keys = Array.isArray(visitorKeys?.[node.type])
1967
+ ? visitorKeys[node.type]
1968
+ : Object.keys(node).filter((key) => !AST_TRAVERSAL_SKIP_KEYS.has(key));
1969
+ const children = [];
1970
+ for (const key of keys) {
1971
+ const value = node[key];
1972
+ const values = Array.isArray(value) ? value : [value];
1973
+ for (const child of values) {
1974
+ if (child && typeof child === "object" && typeof child.type === "string") {
1975
+ children.push(child);
1976
+ }
1977
+ }
1978
+ }
1979
+ return children;
1980
+ }
1981
+
1982
+ function runCustomRuleTokenListeners(program, tokens, listeners, context) {
1983
+ if (typeof listeners.Program === "function") {
1984
+ context.setCurrentNode(program);
1985
+ listeners.Program(program);
1986
+ }
1987
+ for (const node of tokens) {
1988
+ if (typeof listeners[node.type] === "function") {
1989
+ context.setCurrentNode(node);
1990
+ listeners[node.type](node);
1991
+ }
1992
+ }
1993
+ for (let index = tokens.length - 1; index >= 0; index -= 1) {
1994
+ const node = tokens[index];
1995
+ const exitListener = listeners[`${node.type}:exit`];
1996
+ if (typeof exitListener === "function") {
1997
+ context.setCurrentNode(node);
1998
+ exitListener(node);
1999
+ }
2000
+ }
2001
+ if (typeof listeners["Program:exit"] === "function") {
2002
+ context.setCurrentNode(program);
2003
+ listeners["Program:exit"](program);
2004
+ }
2005
+ }
2006
+
2007
+ function createCustomRuleContext(ruleEntry, sourceCode, options, messages) {
2008
+ const filename = options.filename ?? "<input>";
2009
+ const cwd = options.cwd ?? "";
2010
+ const config = options.config ?? {};
2011
+ const languageOptions = config.languageOptions ?? {};
2012
+ const parserOptions = languageOptions.parserOptions ?? config.parserOptions ?? {};
2013
+ let currentNode = sourceCode.ast ?? null;
2014
+ const context = {
2015
+ id: ruleEntry.ruleId,
2016
+ options: ruleEntry.options,
2017
+ filename,
2018
+ physicalFilename: filename,
2019
+ cwd,
2020
+ sourceCode,
2021
+ settings: config.settings ?? {},
2022
+ parserOptions,
2023
+ parserPath: config.parser ?? languageOptions.parser ?? null,
2024
+ parserServices: sourceCode.parserServices ?? {},
2025
+ languageOptions,
2026
+ getCwd() {
2027
+ return cwd;
2028
+ },
2029
+ getFilename() {
2030
+ return filename;
2031
+ },
2032
+ getPhysicalFilename() {
2033
+ return filename;
2034
+ },
2035
+ getSourceCode() {
2036
+ return sourceCode;
2037
+ },
2038
+ getScope() {
2039
+ return sourceCode.getScope(currentNode);
2040
+ },
2041
+ getAncestors() {
2042
+ return currentNode ? sourceCode.getAncestors(currentNode) : [];
2043
+ },
2044
+ getDeclaredVariables(node) {
2045
+ return sourceCode.getDeclaredVariables(node);
2046
+ },
2047
+ markVariableAsUsed(name) {
2048
+ return sourceCode.markVariableAsUsed(name, currentNode);
2049
+ },
2050
+ report(...args) {
2051
+ messages.push(customRuleMessageFromReport(ruleEntry, sourceCode, args));
2052
+ }
2053
+ };
2054
+ Object.defineProperty(context, "setCurrentNode", {
2055
+ value(node) {
2056
+ currentNode = node;
2057
+ }
2058
+ });
2059
+ return context;
2060
+ }
2061
+
2062
+ function customRuleListeners(rule, context) {
2063
+ if (typeof rule === "function") {
2064
+ return rule(context) ?? {};
2065
+ }
2066
+ if (typeof rule?.create === "function") {
2067
+ return rule.create(context) ?? {};
2068
+ }
2069
+ return {};
2070
+ }
2071
+
2072
+ function customRuleMessageFromReport(ruleEntry, sourceCode, args) {
2073
+ const descriptor = customRuleReportDescriptor(args);
2074
+ const node = descriptor.node ?? null;
2075
+ const location = customRuleReportLocation(sourceCode, descriptor, node);
2076
+ const message = customRuleReportMessage(ruleEntry.rule, descriptor);
2077
+ const result = {
2078
+ ruleId: ruleEntry.ruleId,
2079
+ severity: ruleEntry.severity,
2080
+ message,
2081
+ line: location.start.line,
2082
+ column: location.start.column + 1,
2083
+ nodeType: node?.type ?? null
2084
+ };
2085
+ if (location.end) {
2086
+ result.endLine = location.end.line;
2087
+ result.endColumn = location.end.column + 1;
2088
+ }
2089
+ const fix = customRuleFix(sourceCode, descriptor);
2090
+ if (fix) {
2091
+ result.fix = fix;
2092
+ }
2093
+ if (Array.isArray(descriptor.suggest)) {
2094
+ result.suggestions = customRuleSuggestions(ruleEntry.rule, sourceCode, descriptor.suggest);
2095
+ }
2096
+ return result;
2097
+ }
2098
+
2099
+ function customRuleReportDescriptor(args) {
2100
+ const [first, second, third, fourth] = args;
2101
+ if (first && typeof first === "object" && (
2102
+ Object.hasOwn(first, "message")
2103
+ || Object.hasOwn(first, "messageId")
2104
+ || Object.hasOwn(first, "node")
2105
+ || Object.hasOwn(first, "loc")
2106
+ )) {
2107
+ return first;
2108
+ }
2109
+ if (typeof second === "string") {
2110
+ return { node: first, message: second, data: third };
2111
+ }
2112
+ return { node: first, loc: second, message: third, data: fourth };
2113
+ }
2114
+
2115
+ function customRuleReportMessage(rule, descriptor) {
2116
+ if (descriptor.messageId != null) {
2117
+ return ruleTesterMessageForId(rule, descriptor.messageId, descriptor.data);
2118
+ }
2119
+ if (typeof descriptor.message === "string") {
2120
+ return replaceRuleMessageData(descriptor.message, descriptor.data);
2121
+ }
2122
+ return "";
2123
+ }
2124
+
2125
+ function customRuleReportLocation(sourceCode, descriptor, node) {
2126
+ const loc = descriptor.loc ?? node?.loc ?? sourceCode.getLoc(node);
2127
+ const start = loc?.start ?? loc ?? { line: 1, column: 0 };
2128
+ const end = loc?.end ?? null;
2129
+ return {
2130
+ start: {
2131
+ line: start.line ?? 1,
2132
+ column: start.column ?? 0
2133
+ },
2134
+ end: end ? {
2135
+ line: end.line ?? start.line ?? 1,
2136
+ column: end.column ?? start.column ?? 0
2137
+ } : null
2138
+ };
2139
+ }
2140
+
2141
+ function customRuleSuggestions(rule, sourceCode, suggestions) {
2142
+ return suggestions.map((suggestion) => {
2143
+ const result = {
2144
+ desc: customRuleSuggestionDescription(rule, suggestion)
2145
+ };
2146
+ if (suggestion.messageId != null) {
2147
+ result.messageId = suggestion.messageId;
2148
+ }
2149
+ const fix = customRuleSuggestionFix(sourceCode, suggestion);
2150
+ if (fix) {
2151
+ result.fix = fix;
2152
+ }
2153
+ return result;
2154
+ });
2155
+ }
2156
+
2157
+ function customRuleSuggestionDescription(rule, suggestion) {
2158
+ if (typeof suggestion.desc === "string") {
2159
+ return replaceRuleMessageData(suggestion.desc, suggestion.data);
2160
+ }
2161
+ if (suggestion.messageId != null) {
2162
+ return ruleTesterMessageForId(rule, suggestion.messageId, suggestion.data);
2163
+ }
2164
+ return "";
2165
+ }
2166
+
2167
+ function customRuleSuggestionFix(sourceCode, suggestion) {
2168
+ return customRuleFix(sourceCode, suggestion);
2169
+ }
2170
+
2171
+ function customRuleFix(sourceCode, descriptor) {
2172
+ if (typeof descriptor.fix !== "function") {
2173
+ return null;
2174
+ }
2175
+ const value = descriptor.fix(customRuleFixer(sourceCode));
2176
+ const fixes = ruleFixItems(value);
2177
+ if (fixes.length === 0) {
2178
+ return null;
2179
+ }
2180
+ return fixes.length === 1 ? fixes[0] : fixes;
2181
+ }
2182
+
2183
+ function customRuleFixer(sourceCode) {
2184
+ return {
2185
+ insertTextAfter(node, text) {
2186
+ const range = sourceCode.getRange(node);
2187
+ return { range: [range[1], range[1]], text };
2188
+ },
2189
+ insertTextAfterRange(range, text) {
2190
+ return { range: [range[1], range[1]], text };
2191
+ },
2192
+ insertTextBefore(node, text) {
2193
+ const range = sourceCode.getRange(node);
2194
+ return { range: [range[0], range[0]], text };
2195
+ },
2196
+ insertTextBeforeRange(range, text) {
2197
+ return { range: [range[0], range[0]], text };
2198
+ },
2199
+ remove(node) {
2200
+ return this.replaceText(node, "");
2201
+ },
2202
+ removeRange(range) {
2203
+ return this.replaceTextRange(range, "");
2204
+ },
2205
+ replaceText(node, text) {
2206
+ return this.replaceTextRange(sourceCode.getRange(node), text);
2207
+ },
2208
+ replaceTextRange(range, text) {
2209
+ return { range: [range[0], range[1]], text };
2210
+ }
2211
+ };
2212
+ }
2213
+
2214
+ class SourceCode {
2215
+ static splitLines(text) {
2216
+ if (typeof text !== "string") {
2217
+ throw new TypeError("SourceCode.splitLines requires source text");
2218
+ }
2219
+ return text.split(/\r\n|\r|\n/u);
2220
+ }
2221
+
2222
+ constructor(textOrConfig, astIfNoConfig = null) {
2223
+ if (typeof textOrConfig === "string") {
2224
+ this.text = textOrConfig;
2225
+ this.ast = astIfNoConfig;
2226
+ this.parserServices = {};
2227
+ this.scopeManager = null;
2228
+ this.visitorKeys = null;
2229
+ this.hasBOM = false;
2230
+ } else if (textOrConfig && typeof textOrConfig === "object" && typeof textOrConfig.text === "string") {
2231
+ this.text = textOrConfig.text;
2232
+ this.ast = textOrConfig.ast ?? null;
2233
+ this.parserServices = textOrConfig.parserServices ?? {};
2234
+ this.scopeManager = textOrConfig.scopeManager ?? null;
2235
+ this.visitorKeys = textOrConfig.visitorKeys ?? null;
2236
+ this.hasBOM = Boolean(textOrConfig.hasBOM);
2237
+ } else {
2238
+ throw new TypeError("SourceCode requires source text");
2239
+ }
2240
+ this.lines = SourceCode.splitLines(this.text);
2241
+ this.comments = Array.isArray(this.ast?.comments) ? this.ast.comments : [];
2242
+ this.tokens = Array.isArray(this.ast?.tokens) ? this.ast.tokens : [];
2243
+ this.lineStartIndices = sourceLineStartIndices(this.text);
2244
+ }
2245
+
2246
+ getText(node, beforeCount = 0, afterCount = 0) {
2247
+ if (node?.range) {
2248
+ return this.text.slice(Math.max(node.range[0] - beforeCount, 0), node.range[1] + afterCount);
2249
+ }
2250
+ return this.text;
2251
+ }
2252
+
2253
+ getLines() {
2254
+ return this.lines;
2255
+ }
2256
+
2257
+ getAllComments() {
2258
+ return this.comments;
2259
+ }
2260
+
2261
+ getIndexFromLoc(loc) {
2262
+ return this.lineStartIndices[Math.max(loc.line - 1, 0)] + loc.column;
2263
+ }
2264
+
2265
+ getLocFromIndex(index) {
2266
+ const clamped = Math.max(0, Math.min(index, this.text.length));
2267
+ let line = 0;
2268
+ while (line + 1 < this.lineStartIndices.length && this.lineStartIndices[line + 1] <= clamped) {
2269
+ line += 1;
2270
+ }
2271
+ return {
2272
+ line: line + 1,
2273
+ column: clamped - this.lineStartIndices[line]
2274
+ };
2275
+ }
2276
+
2277
+ getRange(node) {
2278
+ if (node?.range) {
2279
+ return [node.range[0], node.range[1]];
2280
+ }
2281
+ if (node?.loc) {
2282
+ return [this.getIndexFromLoc(node.loc.start), this.getIndexFromLoc(node.loc.end)];
2283
+ }
2284
+ return [0, this.text.length];
2285
+ }
2286
+
2287
+ getLoc(node) {
2288
+ if (node?.loc) {
2289
+ return node.loc;
2290
+ }
2291
+ const range = this.getRange(node);
2292
+ return {
2293
+ start: this.getLocFromIndex(range[0]),
2294
+ end: this.getLocFromIndex(range[1])
2295
+ };
2296
+ }
2297
+
2298
+ getAllTokens() {
2299
+ return this.tokens;
2300
+ }
2301
+
2302
+ getTokens(node, beforeCount = 0, afterCount = 0) {
2303
+ const options = sourceTokenRangeOptions(beforeCount, afterCount);
2304
+ const items = sourceTokenItems(this, options);
2305
+ if (!node) {
2306
+ return sourceApplyTokenFilter(items, options);
2307
+ }
2308
+ const range = expandSourceRange(this.getRange(node), options.beforeCount, options.afterCount, this.text.length);
2309
+ return sourceApplyTokenFilter(sourceItemsInRange(items, range), options);
2310
+ }
2311
+
2312
+ getFirstToken(node, skipOrOptions = 0) {
2313
+ return sourceForwardToken(this.getTokens(node, sourceTokenOptions(skipOrOptions, "skip")), skipOrOptions);
2314
+ }
2315
+
2316
+ getFirstTokens(node, countOrOptions = 1) {
2317
+ return sourceForwardTokens(this.getTokens(node, sourceTokenOptions(countOrOptions, "count")), countOrOptions);
2318
+ }
2319
+
2320
+ getLastToken(node, skipOrOptions = 0) {
2321
+ return sourceBackwardToken(this.getTokens(node, sourceTokenOptions(skipOrOptions, "skip")), skipOrOptions);
2322
+ }
2323
+
2324
+ getLastTokens(node, countOrOptions = 1) {
2325
+ return sourceBackwardTokens(this.getTokens(node, sourceTokenOptions(countOrOptions, "count")), countOrOptions);
2326
+ }
2327
+
2328
+ getTokenBefore(nodeOrToken, skipOrOptions = 0) {
2329
+ const options = sourceTokenOptions(skipOrOptions, "skip");
2330
+ return sourceBackwardToken(sourceApplyTokenFilter(sourceItemsBefore(sourceTokenItems(this, options), this.getRange(nodeOrToken)[0]), options), options);
2331
+ }
2332
+
2333
+ getTokensBefore(nodeOrToken, countOrOptions = 1) {
2334
+ const options = sourceTokenOptions(countOrOptions, "count");
2335
+ return sourceBackwardTokens(sourceApplyTokenFilter(sourceItemsBefore(sourceTokenItems(this, options), this.getRange(nodeOrToken)[0]), options), options);
2336
+ }
2337
+
2338
+ getTokenAfter(nodeOrToken, skipOrOptions = 0) {
2339
+ const options = sourceTokenOptions(skipOrOptions, "skip");
2340
+ return sourceForwardToken(sourceApplyTokenFilter(sourceItemsAfter(sourceTokenItems(this, options), this.getRange(nodeOrToken)[1]), options), options);
2341
+ }
2342
+
2343
+ getTokensAfter(nodeOrToken, countOrOptions = 1) {
2344
+ const options = sourceTokenOptions(countOrOptions, "count");
2345
+ return sourceForwardTokens(sourceApplyTokenFilter(sourceItemsAfter(sourceTokenItems(this, options), this.getRange(nodeOrToken)[1]), options), options);
2346
+ }
2347
+
2348
+ getTokensBetween(left, right, optionsOrCount) {
2349
+ const options = sourceTokenOptions(optionsOrCount, "count");
2350
+ return sourceForwardTokens(sourceTokensBetween(this, left, right, options), options);
2351
+ }
2352
+
2353
+ getFirstTokenBetween(left, right, skipOrOptions = 0) {
2354
+ const options = sourceTokenOptions(skipOrOptions, "skip");
2355
+ return sourceForwardToken(sourceTokensBetween(this, left, right, options), options);
2356
+ }
2357
+
2358
+ getFirstTokensBetween(left, right, countOrOptions = 1) {
2359
+ const options = sourceTokenOptions(countOrOptions, "count");
2360
+ return sourceForwardTokens(sourceTokensBetween(this, left, right, options), options);
2361
+ }
2362
+
2363
+ getLastTokenBetween(left, right, skipOrOptions = 0) {
2364
+ const options = sourceTokenOptions(skipOrOptions, "skip");
2365
+ return sourceBackwardToken(sourceTokensBetween(this, left, right, options), options);
2366
+ }
2367
+
2368
+ getLastTokensBetween(left, right, countOrOptions = 1) {
2369
+ const options = sourceTokenOptions(countOrOptions, "count");
2370
+ return sourceBackwardTokens(sourceTokensBetween(this, left, right, options), options);
2371
+ }
2372
+
2373
+ getTokenByRangeStart(index, options = {}) {
2374
+ return sourceTokenItems(this, sourceTokenOptions(options, "skip")).find((token) => token.range?.[0] === index) ?? null;
2375
+ }
2376
+
2377
+ getTokenOrCommentBefore(nodeOrToken) {
2378
+ return sourceItemsBefore(sourceTokensAndComments(this), this.getRange(nodeOrToken)[0]).at(-1) ?? null;
2379
+ }
2380
+
2381
+ getTokenOrCommentAfter(nodeOrToken) {
2382
+ return sourceItemsAfter(sourceTokensAndComments(this), this.getRange(nodeOrToken)[1])[0] ?? null;
2383
+ }
2384
+
2385
+ getCommentsBefore(nodeOrToken) {
2386
+ return sourceItemsBefore(this.comments, this.getRange(nodeOrToken)[0]);
2387
+ }
2388
+
2389
+ getCommentsAfter(nodeOrToken) {
2390
+ return sourceItemsAfter(this.comments, this.getRange(nodeOrToken)[1]);
2391
+ }
2392
+
2393
+ getCommentsInside(node) {
2394
+ return sourceItemsInRange(this.comments, this.getRange(node));
2395
+ }
2396
+
2397
+ getComments(node) {
2398
+ return {
2399
+ before: this.getCommentsBefore(node),
2400
+ after: this.getCommentsAfter(node),
2401
+ inside: this.getCommentsInside(node)
2402
+ };
2403
+ }
2404
+
2405
+ getJSDocComment(node) {
2406
+ return this.getCommentsBefore(node).findLast((comment) => String(comment.value ?? "").startsWith("*")) ?? null;
2407
+ }
2408
+
2409
+ commentsExistBetween(left, right) {
2410
+ return sourceItemsBetween(this.comments, this.getRange(left)[1], this.getRange(right)[0]).length > 0;
2411
+ }
2412
+
2413
+ isSpaceBetween(left, right) {
2414
+ return /\s/u.test(this.text.slice(this.getRange(left)[1], this.getRange(right)[0]));
2415
+ }
2416
+
2417
+ isSpaceBetweenTokens(left, right) {
2418
+ return this.isSpaceBetween(left, right);
2419
+ }
2420
+
2421
+ getNodeByRangeIndex(index) {
2422
+ return sourceNodeByRangeIndex(this.ast, index);
2423
+ }
2424
+
2425
+ getAncestors(node) {
2426
+ return node ? sourceAncestorsForNode(this.ast, node) ?? [] : [];
2427
+ }
2428
+
2429
+ getDeclaredVariables(node) {
2430
+ return typeof this.scopeManager?.getDeclaredVariables === "function" ? this.scopeManager.getDeclaredVariables(node) : [];
2431
+ }
2432
+
2433
+ getScope(node) {
2434
+ if (node && typeof this.scopeManager?.acquire === "function") {
2435
+ return this.scopeManager.acquire(node, true) ?? this.scopeManager.acquire(node, false) ?? this.scopeManager.globalScope ?? null;
2436
+ }
2437
+ return this.scopeManager?.globalScope ?? null;
2438
+ }
2439
+
2440
+ markVariableAsUsed(name, node) {
2441
+ return markScopeVariableAsUsed(this.getScope(node), name);
2442
+ }
2443
+
2444
+ getDisableDirectives() {
2445
+ return this.comments.flatMap((comment) => disableDirectivesFromComment(comment));
2446
+ }
2447
+
2448
+ getInlineConfigNodes() {
2449
+ return this.comments.filter((comment) => isInlineConfigComment(comment));
2450
+ }
2451
+
2452
+ applyInlineConfig() {
2453
+ return undefined;
2454
+ }
2455
+
2456
+ applyLanguageOptions() {
2457
+ return undefined;
2458
+ }
2459
+
2460
+ finalize() {
2461
+ return undefined;
2462
+ }
2463
+
2464
+ traverse() {
2465
+ return [];
2466
+ }
2467
+
2468
+ isGlobalReference() {
2469
+ return false;
2470
+ }
2471
+ }
2472
+
2473
+ function sourceLineStartIndices(text) {
2474
+ const indices = [0];
2475
+ for (let index = 0; index < text.length; index += 1) {
2476
+ if (text[index] === "\n") {
2477
+ indices.push(index + 1);
2478
+ }
2479
+ }
2480
+ return indices;
2481
+ }
2482
+
2483
+ function expandSourceRange(range, beforeCount, afterCount, textLength) {
2484
+ return [
2485
+ Math.max(range[0] - beforeCount, 0),
2486
+ Math.min(range[1] + afterCount, textLength)
2487
+ ];
2488
+ }
2489
+
2490
+ function sourceItemsInRange(items, range) {
2491
+ return items.filter((item) => item.range && item.range[0] >= range[0] && item.range[1] <= range[1]);
2492
+ }
2493
+
2494
+ function sourceItemsBefore(items, index) {
2495
+ return items.filter((item) => item.range && item.range[1] <= index);
2496
+ }
2497
+
2498
+ function sourceItemsAfter(items, index) {
2499
+ return items.filter((item) => item.range && item.range[0] >= index);
2500
+ }
2501
+
2502
+ function sourceItemsBetween(items, start, end) {
2503
+ return items.filter((item) => item.range && item.range[0] >= start && item.range[1] <= end);
2504
+ }
2505
+
2506
+ function sourceTokensAndComments(sourceCode) {
2507
+ return [...sourceCode.tokens, ...sourceCode.comments].sort((left, right) => (left.range?.[0] ?? 0) - (right.range?.[0] ?? 0));
2508
+ }
2509
+
2510
+ function sourceTokensBetween(sourceCode, left, right, options) {
2511
+ return sourceApplyTokenFilter(
2512
+ sourceItemsBetween(sourceTokenItems(sourceCode, options), sourceCode.getRange(left)[1], sourceCode.getRange(right)[0]),
2513
+ options
2514
+ );
2515
+ }
2516
+
2517
+ function sourceTokenItems(sourceCode, options) {
2518
+ return options.includeComments ? sourceTokensAndComments(sourceCode) : sourceCode.tokens;
2519
+ }
2520
+
2521
+ function sourceTokenRangeOptions(beforeCount, afterCount) {
2522
+ if (beforeCount && typeof beforeCount === "object") {
2523
+ return sourceTokenOptions(beforeCount, "count");
2524
+ }
2525
+ return {
2526
+ beforeCount: sourceTokenCount(beforeCount, 0),
2527
+ afterCount: sourceTokenCount(afterCount, 0),
2528
+ includeComments: false,
2529
+ filter: null,
2530
+ skip: 0,
2531
+ count: null
2532
+ };
2533
+ }
2534
+
2535
+ function sourceTokenOptions(optionsOrNumber, numericKey) {
2536
+ const options = optionsOrNumber && typeof optionsOrNumber === "object" ? optionsOrNumber : { [numericKey]: optionsOrNumber };
2537
+ return {
2538
+ beforeCount: sourceTokenCount(options.beforeCount, 0),
2539
+ afterCount: sourceTokenCount(options.afterCount, 0),
2540
+ includeComments: Boolean(options.includeComments),
2541
+ filter: typeof options.filter === "function" ? options.filter : null,
2542
+ skip: sourceTokenCount(options.skip, 0),
2543
+ count: options.count === undefined ? null : sourceTokenCount(options.count, 1)
2544
+ };
2545
+ }
2546
+
2547
+ function sourceTokenCount(value, defaultValue) {
2548
+ return Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : defaultValue;
2549
+ }
2550
+
2551
+ function sourceApplyTokenFilter(items, options) {
2552
+ return options.filter ? items.filter((item) => options.filter(item)) : items;
2553
+ }
2554
+
2555
+ function sourceForwardToken(items, optionsOrNumber) {
2556
+ const options = sourceTokenOptions(optionsOrNumber, "skip");
2557
+ return items[options.skip] ?? null;
2558
+ }
2559
+
2560
+ function sourceForwardTokens(items, optionsOrNumber) {
2561
+ const options = sourceTokenOptions(optionsOrNumber, "count");
2562
+ const start = options.skip;
2563
+ const end = options.count === null ? undefined : start + options.count;
2564
+ return items.slice(start, end);
2565
+ }
2566
+
2567
+ function sourceBackwardToken(items, optionsOrNumber) {
2568
+ const options = sourceTokenOptions(optionsOrNumber, "skip");
2569
+ return items.at(-(options.skip + 1)) ?? null;
2570
+ }
2571
+
2572
+ function sourceBackwardTokens(items, optionsOrNumber) {
2573
+ const options = sourceTokenOptions(optionsOrNumber, "count");
2574
+ if (options.count === 0) {
2575
+ return [];
2576
+ }
2577
+ const end = options.skip === 0 ? undefined : -options.skip;
2578
+ const available = end === undefined ? items : items.slice(0, end);
2579
+ return options.count === null ? available : available.slice(-options.count);
2580
+ }
2581
+
2582
+ function sourceNodeByRangeIndex(node, index, seen = new Set()) {
2583
+ if (!node || typeof node !== "object" || seen.has(node)) {
2584
+ return null;
2585
+ }
2586
+ seen.add(node);
2587
+ if (!node.range || index < node.range[0] || index > node.range[1]) {
2588
+ return null;
2589
+ }
2590
+ for (const value of Object.values(node)) {
2591
+ const children = Array.isArray(value) ? value : [value];
2592
+ for (const child of children) {
2593
+ const match = sourceNodeByRangeIndex(child, index, seen);
2594
+ if (match) {
2595
+ return match;
2596
+ }
2597
+ }
2598
+ }
2599
+ return node;
2600
+ }
2601
+
2602
+ function sourceAncestorsForNode(root, target, ancestors = [], seen = new Set()) {
2603
+ if (!root || typeof root !== "object" || seen.has(root)) {
2604
+ return null;
2605
+ }
2606
+ if (root === target) {
2607
+ return ancestors;
2608
+ }
2609
+ seen.add(root);
2610
+
2611
+ for (const value of Object.values(root)) {
2612
+ const children = Array.isArray(value) ? value : [value];
2613
+ for (const child of children) {
2614
+ if (!child || typeof child !== "object") {
2615
+ continue;
2616
+ }
2617
+ const result = sourceAncestorsForNode(child, target, [...ancestors, root], seen);
2618
+ if (result) {
2619
+ return result;
2620
+ }
2621
+ }
2622
+ }
2623
+ return null;
2624
+ }
2625
+
2626
+ function markScopeVariableAsUsed(scope, name) {
2627
+ let current = scope;
2628
+ while (current) {
2629
+ const variable = scopeVariableByName(current, name);
2630
+ if (variable) {
2631
+ variable.eslintUsed = true;
2632
+ return true;
2633
+ }
2634
+ current = current.upper ?? null;
2635
+ }
2636
+ return false;
2637
+ }
2638
+
2639
+ function scopeVariableByName(scope, name) {
2640
+ if (scope?.set instanceof Map && scope.set.has(name)) {
2641
+ return scope.set.get(name);
2642
+ }
2643
+ if (Array.isArray(scope?.variables)) {
2644
+ return scope.variables.find((variable) => variable?.name === name) ?? null;
2645
+ }
2646
+ return null;
2647
+ }
2648
+
2649
+ function isInlineConfigComment(comment) {
2650
+ return /^(?:eslint(?:-disable|-enable|-disable-next-line|-disable-line|-env)?|global|globals|exported)(?:\s|$)/u.test(String(comment?.value ?? "").trim());
2651
+ }
2652
+
2653
+ function disableDirectivesFromComment(comment) {
2654
+ const match = String(comment?.value ?? "").trim().match(/^(eslint-(?:disable-next-line|disable-line|disable|enable))\b\s*(.*)$/u);
2655
+ if (!match) {
2656
+ return [];
2657
+ }
2658
+
2659
+ const [, type, rawValue] = match;
2660
+ const [rawRules, justification = ""] = rawValue.split(/\s--\s/u, 2).map((value) => value.trim());
2661
+ const ruleIds = rawRules.split(",").map((ruleId) => ruleId.trim()).filter(Boolean);
2662
+ if (ruleIds.length === 0) {
2663
+ return [{ type, node: comment, value: null, ruleId: null, justification }];
2664
+ }
2665
+ return ruleIds.map((ruleId) => ({ type, node: comment, value: ruleId, ruleId, justification }));
2666
+ }
2667
+
2668
+ class RuleTester {
2669
+ static get version() {
2670
+ return version;
2671
+ }
2672
+
2673
+ static setDefaultConfig(config) {
2674
+ if (typeof config !== "object" || config === null) {
2675
+ throw new TypeError("RuleTester.setDefaultConfig: config must be an object");
2676
+ }
2677
+ ruleTesterDefaultConfig = config;
2678
+ ruleTesterDefaultConfig.rules = ruleTesterDefaultConfig.rules || {};
2679
+ }
2680
+
2681
+ static getDefaultConfig() {
2682
+ return ruleTesterDefaultConfig;
2683
+ }
2684
+
2685
+ static resetDefaultConfig() {
2686
+ ruleTesterDefaultConfig = {
2687
+ rules: {
2688
+ ...RULE_TESTER_INITIAL_CONFIG.rules
2689
+ }
2690
+ };
2691
+ }
2692
+
2693
+ static get describe() {
2694
+ return ruleTesterDescribe ?? (typeof globalThis.describe === "function" ? globalThis.describe : ruleTesterDescribeDefaultHandler);
2695
+ }
2696
+
2697
+ static set describe(value) {
2698
+ ruleTesterDescribe = value;
2699
+ }
2700
+
2701
+ static get it() {
2702
+ return ruleTesterIt ?? (typeof globalThis.it === "function" ? globalThis.it : ruleTesterItDefaultHandler);
2703
+ }
2704
+
2705
+ static set it(value) {
2706
+ ruleTesterIt = value;
2707
+ }
2708
+
2709
+ static only(item) {
2710
+ if (typeof item === "string") {
2711
+ return { code: item, only: true };
2712
+ }
2713
+ return { ...item, only: true };
2714
+ }
2715
+
2716
+ static get itOnly() {
2717
+ if (typeof ruleTesterItOnly === "function") {
2718
+ return ruleTesterItOnly;
2719
+ }
2720
+ if (typeof ruleTesterIt === "function" && typeof ruleTesterIt.only === "function") {
2721
+ return Function.bind.call(ruleTesterIt.only, ruleTesterIt);
2722
+ }
2723
+ if (typeof globalThis.it === "function" && typeof globalThis.it.only === "function") {
2724
+ return Function.bind.call(globalThis.it.only, globalThis.it);
2725
+ }
2726
+ if (typeof ruleTesterDescribe === "function" || typeof ruleTesterIt === "function") {
2727
+ throw new Error("Set `RuleTester.itOnly` to use `only` with a custom test framework.");
2728
+ }
2729
+ throw new Error("To use `only`, use RuleTester with a test framework that provides `it.only()` like Mocha.");
2730
+ }
2731
+
2732
+ static set itOnly(value) {
2733
+ ruleTesterItOnly = value;
2734
+ }
2735
+
2736
+ constructor(config = {}) {
2737
+ this.config = mergeRuleTesterConfig(ruleTesterDefaultConfig, config);
2738
+ }
2739
+
2740
+ run(ruleName, rule, tests = {}) {
2741
+ const linter = new Linter();
2742
+ linter.defineRule(ruleName, rule);
2743
+ RuleTester.describe(ruleName, () => {
2744
+ RuleTester.describe("valid", () => {
2745
+ for (const test of tests.valid ?? []) {
2746
+ const testCase = normalizeRuleTesterCase(test);
2747
+ RuleTester[testCase.only ? "itOnly" : "it"](testCase.name ?? testCase.code, () => {
2748
+ const messages = linter.verify(testCase.code, ruleTesterConfig(ruleName, this.config, testCase, "error"), ruleTesterOptions(testCase));
2749
+ if (messages.length > 0) {
2750
+ throw new Error(`Should have no errors but had ${messages.length}: ${JSON.stringify(messages)}`);
2751
+ }
2752
+ });
2753
+ }
2754
+ });
2755
+
2756
+ RuleTester.describe("invalid", () => {
2757
+ for (const test of tests.invalid ?? []) {
2758
+ const testCase = normalizeRuleTesterCase(test);
2759
+ RuleTester[testCase.only ? "itOnly" : "it"](testCase.name ?? testCase.code, () => {
2760
+ const config = ruleTesterConfig(ruleName, this.config, testCase, "error");
2761
+ const options = ruleTesterOptions(testCase);
2762
+ const result = Object.hasOwn(testCase, "output")
2763
+ ? linter.verifyAndFix(testCase.code, config, options)
2764
+ : { messages: linter.verify(testCase.code, config, options), output: testCase.code };
2765
+ const messages = result.messages;
2766
+ assertRuleTesterErrors(testCase, messages, rule);
2767
+ assertRuleTesterOutput(testCase, result.output);
2768
+ });
2769
+ }
2770
+ });
2771
+ });
2772
+ }
2773
+ }
2774
+
2775
+ function ruleTesterDescribeDefaultHandler(_text, method) {
2776
+ return method();
2777
+ }
2778
+
2779
+ function ruleTesterItDefaultHandler(_text, method) {
2780
+ return method();
2781
+ }
2782
+
2783
+ function mergeRuleTesterConfig(baseConfig, overrideConfig) {
2784
+ return {
2785
+ ...baseConfig,
2786
+ ...overrideConfig,
2787
+ rules: {
2788
+ ...(baseConfig.rules ?? {}),
2789
+ ...(overrideConfig.rules ?? {})
2790
+ }
2791
+ };
2792
+ }
2793
+
2794
+ function normalizeRuleTesterCase(test) {
2795
+ if (typeof test === "string") {
2796
+ return { code: test };
2797
+ }
2798
+ if (!test || typeof test !== "object" || typeof test.code !== "string") {
2799
+ throw new TypeError("RuleTester cases must be strings or objects with a code string");
2800
+ }
2801
+ return test;
2802
+ }
2803
+
2804
+ function ruleTesterConfig(ruleName, baseConfig, testCase, defaultSeverity) {
2805
+ return {
2806
+ ...baseConfig,
2807
+ ...testCase,
2808
+ rules: {
2809
+ [ruleName]: testCase.options ? [defaultSeverity, ...testCase.options] : defaultSeverity,
2810
+ ...(baseConfig.rules ?? {}),
2811
+ ...(testCase.rules ?? {})
2812
+ }
2813
+ };
2814
+ }
2815
+
2816
+ function ruleTesterOptions(testCase) {
2817
+ return {
2818
+ filename: testCase.filename ?? testCase.filePath ?? "input.js"
2819
+ };
2820
+ }
2821
+
2822
+ function assertRuleTesterErrors(testCase, messages, rule) {
2823
+ const expected = testCase.errors;
2824
+ if (typeof expected === "number") {
2825
+ if (messages.length !== expected) {
2826
+ throw new Error(`Should have ${expected} error${expected === 1 ? "" : "s"} but had ${messages.length}: ${JSON.stringify(messages)}`);
2827
+ }
2828
+ return;
2829
+ }
2830
+
2831
+ if (!Array.isArray(expected)) {
2832
+ if (messages.length === 0) {
2833
+ throw new Error("Should have at least one error but had 0: []");
2834
+ }
2835
+ return;
2836
+ }
2837
+
2838
+ if (messages.length !== expected.length) {
2839
+ throw new Error(`Should have ${expected.length} error${expected.length === 1 ? "" : "s"} but had ${messages.length}: ${JSON.stringify(messages)}`);
2840
+ }
2841
+
2842
+ expected.forEach((expectation, index) => {
2843
+ if (typeof expectation === "number") {
2844
+ return;
2845
+ }
2846
+ const message = messages[index];
2847
+ for (const property of ["message", "line", "column", "endLine", "endColumn"]) {
2848
+ if (expectation[property] != null && message[property] !== expectation[property]) {
2849
+ throw new Error(`Error ${index + 1} ${property} should be ${JSON.stringify(expectation[property])} but was ${JSON.stringify(message[property])}`);
2850
+ }
2851
+ }
2852
+ if (expectation.messageId != null) {
2853
+ const expectedMessage = ruleTesterMessageForId(rule, expectation.messageId, expectation.data);
2854
+ if (message.message !== expectedMessage) {
2855
+ throw new Error(`Error ${index + 1} messageId ${JSON.stringify(expectation.messageId)} should resolve to ${JSON.stringify(expectedMessage)} but message was ${JSON.stringify(message.message)}`);
2856
+ }
2857
+ }
2858
+ if (expectation.type != null && message.nodeType !== expectation.type) {
2859
+ throw new Error(`Error ${index + 1} type should be ${JSON.stringify(expectation.type)} but was ${JSON.stringify(message.nodeType)}`);
2860
+ }
2861
+ if (Object.hasOwn(expectation, "suggestions")) {
2862
+ assertRuleTesterSuggestions(index, message.suggestions ?? [], expectation.suggestions, testCase.code, rule);
2863
+ }
2864
+ });
2865
+ }
2866
+
2867
+ function assertRuleTesterSuggestions(errorIndex, actualSuggestions, expectedSuggestions, code, rule) {
2868
+ if (expectedSuggestions == null) {
2869
+ if (actualSuggestions.length > 0) {
2870
+ throw new Error(`Error ${errorIndex + 1} should have no suggestions but had ${actualSuggestions.length}: ${JSON.stringify(actualSuggestions)}`);
2871
+ }
2872
+ return;
2873
+ }
2874
+ if (typeof expectedSuggestions === "number") {
2875
+ if (actualSuggestions.length !== expectedSuggestions) {
2876
+ throw new Error(`Error ${errorIndex + 1} should have ${expectedSuggestions} suggestion${expectedSuggestions === 1 ? "" : "s"} but had ${actualSuggestions.length}: ${JSON.stringify(actualSuggestions)}`);
2877
+ }
2878
+ return;
2879
+ }
2880
+ if (!Array.isArray(expectedSuggestions)) {
2881
+ throw new TypeError("RuleTester suggestions must be a number, null, or an array");
2882
+ }
2883
+ if (actualSuggestions.length !== expectedSuggestions.length) {
2884
+ throw new Error(`Error ${errorIndex + 1} should have ${expectedSuggestions.length} suggestion${expectedSuggestions.length === 1 ? "" : "s"} but had ${actualSuggestions.length}: ${JSON.stringify(actualSuggestions)}`);
2885
+ }
2886
+
2887
+ expectedSuggestions.forEach((expectation, suggestionIndex) => {
2888
+ const suggestion = actualSuggestions[suggestionIndex];
2889
+ if (expectation.messageId != null) {
2890
+ const expectedDesc = ruleTesterMessageForId(rule, expectation.messageId, expectation.data);
2891
+ if (suggestion.desc !== expectedDesc) {
2892
+ throw new Error(`Error ${errorIndex + 1} suggestion ${suggestionIndex + 1} messageId ${JSON.stringify(expectation.messageId)} should resolve to ${JSON.stringify(expectedDesc)} but desc was ${JSON.stringify(suggestion.desc)}`);
2893
+ }
2894
+ }
2895
+ if (expectation.desc != null && suggestion.desc !== expectation.desc) {
2896
+ throw new Error(`Error ${errorIndex + 1} suggestion ${suggestionIndex + 1} desc should be ${JSON.stringify(expectation.desc)} but was ${JSON.stringify(suggestion.desc)}`);
2897
+ }
2898
+ if (Object.hasOwn(expectation, "output")) {
2899
+ const output = applyRuleTesterSuggestionFixes(code, suggestion.fix);
2900
+ if (output !== expectation.output) {
2901
+ throw new Error(`Error ${errorIndex + 1} suggestion ${suggestionIndex + 1} output should be ${JSON.stringify(expectation.output)} but was ${JSON.stringify(output)}`);
2902
+ }
2903
+ }
2904
+ });
2905
+ }
2906
+
2907
+ function applyRuleTesterSuggestionFixes(code, fix) {
2908
+ return applyRuleFixes(code, fix);
2909
+ }
2910
+
2911
+ function applyRuleFixes(code, fix) {
2912
+ return ruleFixItems(fix)
2913
+ .sort((left, right) => right.range[0] - left.range[0])
2914
+ .reduce((output, item) => output.slice(0, item.range[0]) + item.text + output.slice(item.range[1]), code);
2915
+ }
2916
+
2917
+ function ruleFixItems(fix) {
2918
+ if (!fix) {
2919
+ return [];
2920
+ }
2921
+ if (Array.isArray(fix)) {
2922
+ return fix.flatMap((item) => ruleFixItems(item));
2923
+ }
2924
+ return fix.range ? [fix] : [];
2925
+ }
2926
+
2927
+ function ruleTesterMessageForId(rule, messageId, data = {}) {
2928
+ const template = rule?.meta?.messages?.[messageId];
2929
+ if (typeof template !== "string") {
2930
+ throw new Error(`RuleTester messageId ${JSON.stringify(messageId)} was not found in rule.meta.messages`);
2931
+ }
2932
+ return replaceRuleMessageData(template, data);
2933
+ }
2934
+
2935
+ function replaceRuleMessageData(template, data = {}) {
2936
+ return template.replace(/\{\{\s*([^{}]+?)\s*\}\}/gu, (placeholder, key) => (
2937
+ Object.hasOwn(data, key) ? String(data[key]) : placeholder
2938
+ ));
2939
+ }
2940
+
2941
+ function assertRuleTesterOutput(testCase, actualOutput) {
2942
+ if (!Object.hasOwn(testCase, "output")) {
2943
+ return;
2944
+ }
2945
+ if (testCase.output == null) {
2946
+ return;
2947
+ }
2948
+ if (testCase.output !== actualOutput) {
2949
+ throw new Error(`Output is incorrect. Expected ${JSON.stringify(testCase.output)} but was ${JSON.stringify(actualOutput)}.`);
2950
+ }
2951
+ }
2952
+
2953
+ function createLinterSourceCode(text, parser = null, parserOptions = {}) {
2954
+ const parsed = parseSourceCode(text, parser, parserOptions);
2955
+ if (parsed) {
2956
+ return parsed;
2957
+ }
2958
+ return new SourceCode({
2959
+ text,
2960
+ ast: {
2961
+ type: "Program",
2962
+ range: [0, text.length],
2963
+ comments: sourceCommentsFromText(text),
2964
+ tokens: sourceTokensFromText(text)
2965
+ }
2966
+ });
2967
+ }
2968
+
2969
+ function parseSourceCode(text, parser, parserOptions) {
2970
+ if (!parser) {
2971
+ return null;
2972
+ }
2973
+ if (typeof parser.parseForESLint === "function") {
2974
+ const result = parser.parseForESLint(text, parserOptions) ?? {};
2975
+ return new SourceCode({
2976
+ text,
2977
+ ast: result.ast ?? null,
2978
+ parserServices: result.services ?? result.parserServices ?? {},
2979
+ scopeManager: result.scopeManager ?? null,
2980
+ visitorKeys: result.visitorKeys ?? null
2981
+ });
2982
+ }
2983
+ if (typeof parser.parse === "function") {
2984
+ return new SourceCode({
2985
+ text,
2986
+ ast: parser.parse(text, parserOptions)
2987
+ });
2988
+ }
2989
+ return null;
2990
+ }
2991
+
2992
+ function sourceTokensFromText(text) {
2993
+ const tokens = [];
2994
+ const lineStarts = sourceLineStartIndices(text);
2995
+ let index = 0;
2996
+
2997
+ while (index < text.length) {
2998
+ const char = text[index];
2999
+ const next = text[index + 1];
3000
+ if (/\s/u.test(char)) {
3001
+ index += 1;
3002
+ continue;
3003
+ }
3004
+ if (char === "/" && next === "/") {
3005
+ index += 2;
3006
+ while (index < text.length && text[index] !== "\n" && text[index] !== "\r") {
3007
+ index += 1;
3008
+ }
3009
+ continue;
3010
+ }
3011
+ if (char === "/" && next === "*") {
3012
+ const close = text.indexOf("*/", index + 2);
3013
+ index = close === -1 ? text.length : close + 2;
3014
+ continue;
3015
+ }
3016
+ if (char === "\"" || char === "'" || char === "`") {
3017
+ const start = index;
3018
+ const end = skipQuotedSourceText(text, index, char);
3019
+ tokens.push(sourceTokenNode("String", text.slice(start, end), start, end, lineStarts));
3020
+ index = end;
3021
+ continue;
3022
+ }
3023
+ if (isIdentifierStart(char)) {
3024
+ const start = index;
3025
+ index += 1;
3026
+ while (index < text.length && isIdentifierPart(text[index])) {
3027
+ index += 1;
3028
+ }
3029
+ const value = text.slice(start, index);
3030
+ tokens.push(sourceTokenNode(JS_KEYWORDS.has(value) ? "Keyword" : "Identifier", value, start, index, lineStarts));
3031
+ continue;
3032
+ }
3033
+ if (isDigit(char)) {
3034
+ const start = index;
3035
+ index += 1;
3036
+ while (index < text.length && /[0-9._a-fA-FxXbBoO]/u.test(text[index])) {
3037
+ index += 1;
3038
+ }
3039
+ tokens.push(sourceTokenNode("Numeric", text.slice(start, index), start, index, lineStarts));
3040
+ continue;
3041
+ }
3042
+ const punctuator = sourcePunctuatorAt(text, index);
3043
+ tokens.push(sourceTokenNode("Punctuator", punctuator, index, index + punctuator.length, lineStarts));
3044
+ index += punctuator.length;
3045
+ }
3046
+ return tokens;
3047
+ }
3048
+
3049
+ function sourceCommentsFromText(text) {
3050
+ const comments = [];
3051
+ const lineStarts = sourceLineStartIndices(text);
3052
+ let index = 0;
3053
+
3054
+ while (index < text.length) {
3055
+ const char = text[index];
3056
+ const next = text[index + 1];
3057
+ if (char === "\"" || char === "'" || char === "`") {
3058
+ index = skipQuotedSourceText(text, index, char);
3059
+ continue;
3060
+ }
3061
+ if (char === "/" && next === "/") {
3062
+ const start = index;
3063
+ const valueStart = index + 2;
3064
+ let end = valueStart;
3065
+ while (end < text.length && text[end] !== "\n" && text[end] !== "\r") {
3066
+ end += 1;
3067
+ }
3068
+ comments.push(sourceCommentNode("Line", text.slice(valueStart, end), start, end, lineStarts));
3069
+ index = end;
3070
+ continue;
3071
+ }
3072
+ if (char === "/" && next === "*") {
3073
+ const start = index;
3074
+ const valueStart = index + 2;
3075
+ const close = text.indexOf("*/", valueStart);
3076
+ const valueEnd = close === -1 ? text.length : close;
3077
+ const end = close === -1 ? text.length : close + 2;
3078
+ comments.push(sourceCommentNode("Block", text.slice(valueStart, valueEnd), start, end, lineStarts));
3079
+ index = end;
3080
+ continue;
3081
+ }
3082
+ index += 1;
3083
+ }
3084
+ return comments;
3085
+ }
3086
+
3087
+ function skipQuotedSourceText(text, start, quote) {
3088
+ let index = start + 1;
3089
+ while (index < text.length) {
3090
+ if (text[index] === "\\") {
3091
+ index += 2;
3092
+ continue;
3093
+ }
3094
+ if (text[index] === quote) {
3095
+ return index + 1;
3096
+ }
3097
+ index += 1;
3098
+ }
3099
+ return text.length;
3100
+ }
3101
+
3102
+ function isIdentifierStart(char) {
3103
+ return /[$_\p{ID_Start}]/u.test(char);
3104
+ }
3105
+
3106
+ function isIdentifierPart(char) {
3107
+ return /[$_\u200c\u200d\p{ID_Continue}]/u.test(char);
3108
+ }
3109
+
3110
+ function isDigit(char) {
3111
+ return /[0-9]/u.test(char);
3112
+ }
3113
+
3114
+ function sourcePunctuatorAt(text, index) {
3115
+ const candidates = ["===", "!==", ">>>", "**=", "<<=", ">>=", "=>", "==", "!=", "<=", ">=", "++", "--", "&&", "||", "??", "+=", "-=", "*=", "/=", "%=", "**", "<<", ">>", "?.", "..."];
3116
+ return candidates.find((candidate) => text.startsWith(candidate, index)) ?? text[index];
3117
+ }
3118
+
3119
+ function sourceTokenNode(type, value, start, end, lineStarts) {
3120
+ return {
3121
+ type,
3122
+ value,
3123
+ range: [start, end],
3124
+ loc: {
3125
+ start: sourceLocFromIndex(lineStarts, start),
3126
+ end: sourceLocFromIndex(lineStarts, end)
3127
+ }
3128
+ };
3129
+ }
3130
+
3131
+ function sourceCommentNode(type, value, start, end, lineStarts) {
3132
+ return {
3133
+ type,
3134
+ value,
3135
+ range: [start, end],
3136
+ loc: {
3137
+ start: sourceLocFromIndex(lineStarts, start),
3138
+ end: sourceLocFromIndex(lineStarts, end)
3139
+ }
3140
+ };
3141
+ }
3142
+
3143
+ function sourceLocFromIndex(lineStarts, index) {
3144
+ let line = 0;
3145
+ while (line + 1 < lineStarts.length && lineStarts[line + 1] <= index) {
3146
+ line += 1;
3147
+ }
3148
+ return {
3149
+ line: line + 1,
3150
+ column: index - lineStarts[line]
3151
+ };
3152
+ }
3153
+
3154
+ function normalizeStringArray(values, name) {
3155
+ if (!Array.isArray(values)) {
3156
+ throw new TypeError(`${name} must be an array of strings`);
3157
+ }
3158
+ for (const value of values) {
3159
+ if (typeof value !== "string") {
3160
+ throw new TypeError(`${name} must be an array of strings`);
3161
+ }
3162
+ }
3163
+ return values;
3164
+ }
3165
+
3166
+ function reportToESLintResults(report, textOptions = {}) {
3167
+ const byFile = new Map();
3168
+
3169
+ for (const diagnostic of report.diagnostics ?? []) {
3170
+ const filePath = textOptions.filePath ?? normalizeESLintFilePath(diagnostic.filePath, textOptions.cwd);
3171
+ if (!byFile.has(filePath)) {
3172
+ byFile.set(filePath, emptyESLintResult(filePath, textOptions.source));
3173
+ }
3174
+ const ruleSeverities = textOptions.ruleSeverityForFile?.(filePath) ?? textOptions.ruleSeverities;
3175
+ if (diagnostic.ruleId && ruleSeverities?.get(diagnostic.ruleId) === 0) {
3176
+ continue;
3177
+ }
3178
+ byFile.get(filePath).messages.push(diagnosticToESLintMessage(diagnostic, ruleSeverities));
3179
+ }
3180
+
3181
+ if (textOptions.filePath && textOptions.includeEmptyTextResult !== false && !byFile.has(textOptions.filePath)) {
3182
+ byFile.set(textOptions.filePath, emptyESLintResult(textOptions.filePath, textOptions.source));
3183
+ }
3184
+ for (const filePath of textOptions.filePaths ?? []) {
3185
+ if (!byFile.has(filePath)) {
3186
+ byFile.set(filePath, emptyESLintResult(filePath));
3187
+ }
3188
+ }
3189
+
3190
+ for (const result of byFile.values()) {
3191
+ finalizeESLintResult(result);
3192
+ }
3193
+
3194
+ return [...byFile.values()];
3195
+ }
3196
+
3197
+ function normalizeReportDiagnostics(diagnostics, options = {}) {
3198
+ const filterUnconfiguredRules = hasRuleConfigSource(options);
3199
+ return (diagnostics ?? []).flatMap((diagnostic) => {
3200
+ if (!diagnostic?.ruleId) {
3201
+ return [diagnostic];
3202
+ }
3203
+ if (diagnostic.ruleId === "io") {
3204
+ return [diagnostic];
3205
+ }
3206
+
3207
+ const filePath = normalizeESLintFilePath(diagnostic.filePath, options.cwd);
3208
+ const ruleSeverities = ruleSeverityMapForOptions(options, filePath);
3209
+ const severity = ruleSeverities?.get(diagnostic.ruleId);
3210
+ if (filterUnconfiguredRules && !ruleSeverities?.has(diagnostic.ruleId)) {
3211
+ return [];
3212
+ }
3213
+ if (severity === 0) {
3214
+ return [];
3215
+ }
3216
+ if (severity === 1 || severity === 2) {
3217
+ return [{ ...diagnostic, severity: severity === 2 ? "error" : "warning" }];
3218
+ }
3219
+ return [diagnostic];
3220
+ });
3221
+ }
3222
+
3223
+ function normalizeReportFilePaths(filePaths, options = {}) {
3224
+ return (filePaths ?? []).map((filePath) => normalizeESLintFilePath(filePath, options.cwd));
3225
+ }
3226
+
3227
+ function normalizeDiagnosticFilePaths(diagnostics, options = {}) {
3228
+ return (diagnostics ?? []).map((diagnostic) => ({
3229
+ ...diagnostic,
3230
+ filePath: normalizeESLintFilePath(diagnostic.filePath, options.cwd)
3231
+ }));
3232
+ }
3233
+
3234
+ function hasRuleConfigSource(options = {}) {
3235
+ if (options.noConfig || options.baseConfig || options.overrideConfig || options.rules) {
3236
+ return true;
3237
+ }
3238
+ return !options.noConfig && Boolean(configPathForOptions(options));
3239
+ }
3240
+
3241
+ function exitCodeForDiagnostics(diagnostics) {
3242
+ return (diagnostics?.length ?? 0) > 0 ? 1 : 0;
3243
+ }
3244
+
3245
+ function throwOnUnmatchedPatternDiagnostics(report, options = {}) {
3246
+ if (options.errorOnUnmatchedPattern === false) {
3247
+ return;
3248
+ }
3249
+ const diagnostic = (report.diagnostics ?? []).find((item) => item?.ruleId === "io" && /unable to stat path/i.test(item.message ?? ""));
3250
+ if (diagnostic) {
3251
+ throw new Error(`No files matching '${unmatchedPatternDisplayPath(diagnostic.filePath, options.cwd)}' were found.`);
3252
+ }
3253
+ }
3254
+
3255
+ function unmatchedPatternDisplayPath(filePath, cwd) {
3256
+ const root = resolvePath(cwd ?? process.cwd());
3257
+ if (typeof filePath === "string" && filePath.startsWith(`${root}/`)) {
3258
+ return filePath.slice(root.length + 1);
3259
+ }
3260
+ return filePath;
3261
+ }
3262
+
3263
+ function normalizeESLintFilePath(filePath, cwd) {
3264
+ if (filePath === "<text>") return filePath;
3265
+ if (isAbsolute(filePath)) return filePath;
3266
+ return resolvePath(cwd ?? process.cwd(), filePath);
3267
+ }
3268
+
3269
+ function textFilePathForOptions(options = {}, fallback) {
3270
+ return options.filePath ?? options.filename ?? fallback;
3271
+ }
3272
+
3273
+ function reportFilePaths(report, cwd, fallbackFilePaths = []) {
3274
+ const filePaths = new Set();
3275
+ if (Array.isArray(report.filePaths)) {
3276
+ for (const filePath of report.filePaths) {
3277
+ if (typeof filePath === "string") {
3278
+ filePaths.add(normalizeESLintFilePath(filePath, cwd));
3279
+ }
3280
+ }
3281
+ }
3282
+ for (const filePath of fallbackFilePaths) {
3283
+ filePaths.add(filePath);
3284
+ }
3285
+ return [...filePaths];
3286
+ }
3287
+
3288
+ function explicitLintFilePaths(patterns, cwd) {
3289
+ const files = [];
3290
+ for (const pattern of normalizeStringArray(Array.isArray(patterns) ? patterns : [patterns], "patterns")) {
3291
+ if (hasGlobMagic(pattern)) continue;
3292
+
3293
+ const filePath = normalizeESLintFilePath(pattern, cwd);
3294
+ if (!isLintableFilePath(filePath)) continue;
3295
+
3296
+ try {
3297
+ if (statSync(filePath).isFile()) {
3298
+ files.push(filePath);
3299
+ }
3300
+ } catch {
3301
+ // Let the native binary report missing paths. This helper only fills in
3302
+ // empty ESLint results for files that were checked successfully.
3303
+ }
3304
+ }
3305
+ return files;
3306
+ }
3307
+
3308
+ function hasGlobMagic(pattern) {
3309
+ return /[*?[\]{}()!+@]/.test(pattern);
3310
+ }
3311
+
3312
+ function isLintableFilePath(filePath) {
3313
+ return LINTABLE_EXTENSIONS.has(extname(filePath));
3314
+ }
3315
+
3316
+ function filteredLintPaths(paths, options = {}) {
3317
+ const values = normalizeStringArray(Array.isArray(paths) ? paths : [paths], "paths");
3318
+ const cwd = options.cwd ?? process.cwd();
3319
+ const patterns = ignoreDisabled(options) ? [] : ignorePatternsForOptions(options, cwd);
3320
+ if (patterns.length === 0 && options.errorOnUnmatchedPattern !== false && !values.some(hasGlobMagic)) {
3321
+ return values;
3322
+ }
3323
+ const excludedPatterns = negatedLintPatterns(values);
3324
+
3325
+ const filtered = new Set();
3326
+ for (const target of values) {
3327
+ if (isNegatedLintPattern(target)) {
3328
+ continue;
3329
+ }
3330
+ if (hasGlobMagic(target)) {
3331
+ const expanded = expandGlobTarget(target, cwd, patterns);
3332
+ if (expanded.length > 0) {
3333
+ for (const filePath of expanded) {
3334
+ filtered.add(filePath);
3335
+ }
3336
+ continue;
3337
+ }
3338
+ if (options.errorOnUnmatchedPattern === false) {
3339
+ continue;
3340
+ }
3341
+ if (!pathIgnoredByPatterns(normalizeIgnoredPath(target, cwd), patterns)) {
3342
+ filtered.add(target);
3343
+ }
3344
+ continue;
3345
+ }
3346
+
3347
+ const expanded = expandLintTarget(target, cwd, patterns);
3348
+ if (expanded == null) {
3349
+ if (options.errorOnUnmatchedPattern === false) {
3350
+ continue;
3351
+ }
3352
+ if (!pathIgnoredByPatterns(normalizeIgnoredPath(target, cwd), patterns)) {
3353
+ filtered.add(target);
3354
+ }
3355
+ continue;
3356
+ }
3357
+ for (const filePath of expanded) {
3358
+ filtered.add(filePath);
3359
+ }
3360
+ }
3361
+ return [...filtered].filter((filePath) => !pathIgnoredByPatterns(normalizeIgnoredPath(filePath, cwd), excludedPatterns));
3362
+ }
3363
+
3364
+ function negatedLintPatterns(values) {
3365
+ return values
3366
+ .filter(isNegatedLintPattern)
3367
+ .map((value) => value.slice(1))
3368
+ .filter((value) => value.length > 0);
3369
+ }
3370
+
3371
+ function isNegatedLintPattern(value) {
3372
+ return value.startsWith("!") && value.length > 1;
3373
+ }
3374
+
3375
+ function expandGlobTarget(target, cwd, patterns) {
3376
+ const base = globBaseDirectory(target);
3377
+ const absoluteBase = normalizeESLintFilePath(base, cwd);
3378
+ let stat;
3379
+ try {
3380
+ stat = statSync(absoluteBase);
3381
+ } catch {
3382
+ return [];
3383
+ }
3384
+ if (!stat.isDirectory()) {
3385
+ return [];
3386
+ }
3387
+
3388
+ const expression = new RegExp(`^${globPatternRegExpSource(normalizePath(target))}$`);
3389
+ const files = [];
3390
+ collectGlobFiles(base, cwd, patterns, expression, files);
3391
+ return files;
3392
+ }
3393
+
3394
+ function globBaseDirectory(pattern) {
3395
+ const segments = normalizePath(pattern).split("/");
3396
+ const baseSegments = [];
3397
+ for (const segment of segments) {
3398
+ if (hasGlobMagic(segment)) {
3399
+ break;
3400
+ }
3401
+ baseSegments.push(segment);
3402
+ }
3403
+ return baseSegments.length > 0 ? baseSegments.join("/") : ".";
3404
+ }
3405
+
3406
+ function collectGlobFiles(target, cwd, patterns, expression, files) {
3407
+ const absolute = normalizeESLintFilePath(target, cwd);
3408
+ for (const entry of readdirSync(absolute, { withFileTypes: true })) {
3409
+ if (shouldSkipDirectoryEntry(entry.name)) {
3410
+ continue;
3411
+ }
3412
+
3413
+ const child = join(target, entry.name);
3414
+ if (entry.isDirectory()) {
3415
+ if (!pathIgnoredByPatterns(normalizeIgnoredPath(child, cwd), patterns)) {
3416
+ collectGlobFiles(child, cwd, patterns, expression, files);
3417
+ }
3418
+ continue;
3419
+ }
3420
+ if (
3421
+ entry.isFile() &&
3422
+ isLintableFilePath(child) &&
3423
+ expression.test(normalizePath(child)) &&
3424
+ !pathIgnoredByPatterns(normalizeIgnoredPath(child, cwd), patterns)
3425
+ ) {
3426
+ files.push(child);
3427
+ }
3428
+ }
3429
+ }
3430
+
3431
+ function ignoredLintPathDiagnostics(paths, options = {}) {
3432
+ if (ignoreDisabled(options) || options.warnIgnored === false) {
3433
+ return [];
3434
+ }
3435
+
3436
+ const cwd = options.cwd ?? process.cwd();
3437
+ const patterns = ignorePatternsForOptions(options, cwd);
3438
+ if (patterns.length === 0) {
3439
+ return [];
3440
+ }
3441
+
3442
+ const diagnostics = [];
3443
+ for (const target of normalizeStringArray(Array.isArray(paths) ? paths : [paths], "paths")) {
3444
+ if (hasGlobMagic(target)) continue;
3445
+
3446
+ const filePath = normalizeESLintFilePath(target, cwd);
3447
+ if (!isLintableFilePath(filePath)) continue;
3448
+
3449
+ try {
3450
+ if (!statSync(filePath).isFile()) continue;
3451
+ } catch {
3452
+ continue;
3453
+ }
3454
+
3455
+ if (pathIgnoredByPatterns(normalizeIgnoredPath(target, cwd), patterns)) {
3456
+ diagnostics.push(ignoredFileDiagnostic(filePath));
3457
+ }
3458
+ }
3459
+ return diagnostics;
3460
+ }
3461
+
3462
+ function ignoredFileDiagnostic(filePath) {
3463
+ return {
3464
+ filePath,
3465
+ line: 0,
3466
+ column: 0,
3467
+ severity: "warning",
3468
+ message: "File ignored because of a matching ignore pattern. Use noIgnore to override.",
3469
+ ruleId: null
3470
+ };
3471
+ }
3472
+
3473
+ function expandLintTarget(target, cwd, patterns) {
3474
+ const absolute = normalizeESLintFilePath(target, cwd);
3475
+ let stat;
3476
+ try {
3477
+ stat = statSync(absolute);
3478
+ } catch {
3479
+ return null;
3480
+ }
3481
+
3482
+ if (stat.isFile()) {
3483
+ return isLintableFilePath(target) && !pathIgnoredByPatterns(normalizeIgnoredPath(target, cwd), patterns) ? [target] : [];
3484
+ }
3485
+ if (!stat.isDirectory() || pathIgnoredByPatterns(normalizeIgnoredPath(target, cwd), patterns)) {
3486
+ return [];
3487
+ }
3488
+
3489
+ const files = [];
3490
+ collectLintableFiles(target, cwd, patterns, files);
3491
+ return files;
3492
+ }
3493
+
3494
+ function collectLintableFiles(target, cwd, patterns, files) {
3495
+ const absolute = normalizeESLintFilePath(target, cwd);
3496
+ for (const entry of readdirSync(absolute, { withFileTypes: true })) {
3497
+ if (shouldSkipDirectoryEntry(entry.name)) {
3498
+ continue;
3499
+ }
3500
+
3501
+ const child = join(target, entry.name);
3502
+ if (entry.isDirectory()) {
3503
+ if (!pathIgnoredByPatterns(normalizeIgnoredPath(child, cwd), patterns)) {
3504
+ collectLintableFiles(child, cwd, patterns, files);
3505
+ }
3506
+ continue;
3507
+ }
3508
+ if (entry.isFile() && isLintableFilePath(child) && !pathIgnoredByPatterns(normalizeIgnoredPath(child, cwd), patterns)) {
3509
+ files.push(child);
3510
+ }
3511
+ }
3512
+ }
3513
+
3514
+ function shouldSkipDirectoryEntry(name) {
3515
+ return name === ".git" || name === ".zig-cache" || name === "node_modules" || name === "vendor" || name === "zig-out";
3516
+ }
3517
+
3518
+ function isPathIgnored(filePath, options = {}) {
3519
+ if (typeof filePath !== "string") {
3520
+ throw new TypeError("filePath must be a string");
3521
+ }
3522
+ if (ignoreDisabled(options)) {
3523
+ return false;
3524
+ }
3525
+
3526
+ const cwd = options.cwd ?? process.cwd();
3527
+ const normalized = normalizeIgnoredPath(filePath, cwd);
3528
+ const patterns = ignorePatternsForOptions(options, cwd, filePath);
3529
+ return pathIgnoredByPatterns(normalized, patterns);
3530
+ }
3531
+
3532
+ function ignoreDisabled(options = {}) {
3533
+ return options.noIgnore || options.ignore === false;
3534
+ }
3535
+
3536
+ function ignorePatternsForOptions(options, cwd, filePath) {
3537
+ const patterns = [];
3538
+ for (const pattern of normalizeIgnorePatterns(options.ignorePatterns)) {
3539
+ patterns.push(pattern);
3540
+ }
3541
+ patterns.push(...ignorePatternsFromConfig(options.baseConfig));
3542
+
3543
+ const ignorePath = options.ignorePath ?? ".eslintignore";
3544
+ if (ignorePath) {
3545
+ patterns.push(...readIgnoreFile(resolvePath(cwd, ignorePath)));
3546
+ }
3547
+ patterns.push(...ignorePatternsFromFileConfig(options, filePath));
3548
+ patterns.push(...ignorePatternsFromConfig(options.overrideConfig));
3549
+ return patterns;
3550
+ }
3551
+
3552
+ function ignorePatternsFromFileConfig(options, filePath) {
3553
+ if (options.noConfig) {
3554
+ return [];
3555
+ }
3556
+
3557
+ const configPath = filePath ? configPathForFile(options, filePath) : configPathForOptions(options);
3558
+ if (!configPath) {
3559
+ return [];
3560
+ }
3561
+
3562
+ return ignorePatternsFromConfig(readConfig(configPath, options.cwd));
3563
+ }
3564
+
3565
+ function ignorePatternsFromConfig(config) {
3566
+ if (!config) {
3567
+ return [];
3568
+ }
3569
+ if (Array.isArray(config)) {
3570
+ return config.flatMap((entry) => ignorePatternsFromConfig(entry));
3571
+ }
3572
+
3573
+ const flatConfigIgnores = config.files ? [] : normalizeIgnorePatterns(config.ignores);
3574
+ return [
3575
+ ...normalizeIgnorePatterns(config.ignorePatterns),
3576
+ ...flatConfigIgnores
3577
+ ];
3578
+ }
3579
+
3580
+ function normalizeIgnorePatterns(patterns) {
3581
+ if (!patterns) {
3582
+ return [];
3583
+ }
3584
+ const values = Array.isArray(patterns) ? patterns : [patterns];
3585
+ for (const value of values) {
3586
+ if (typeof value !== "string") {
3587
+ throw new TypeError("ignorePatterns must be a string or an array of strings");
3588
+ }
3589
+ }
3590
+ return values;
3591
+ }
3592
+
3593
+ function readIgnoreFile(path) {
3594
+ if (!existsSync(path)) {
3595
+ return [];
3596
+ }
3597
+
3598
+ return readFileSync(path, "utf8")
3599
+ .split(/\r?\n/)
3600
+ .map((line) => line.trim())
3601
+ .filter((line) => line && !line.startsWith("#"));
3602
+ }
3603
+
3604
+ function normalizeIgnoredPath(filePath, cwd) {
3605
+ const root = resolvePath(cwd);
3606
+ const absolute = normalizeESLintFilePath(filePath, root);
3607
+ const relative =
3608
+ absolute === root || absolute.startsWith(`${root}/`) || absolute.startsWith(`${root}\\`)
3609
+ ? absolute.slice(root.length).replace(/^[/\\]/, "")
3610
+ : absolute;
3611
+ return normalizePath(relative);
3612
+ }
3613
+
3614
+ function normalizeIgnoredPattern(pattern) {
3615
+ return normalizePath(pattern.replace(/^!/, "").replace(/^\//, ""));
3616
+ }
3617
+
3618
+ function pathIgnoredByPatterns(target, patterns) {
3619
+ let ignored = false;
3620
+ for (const pattern of patterns) {
3621
+ const negated = pattern.startsWith("!");
3622
+ if (matchesIgnorePattern(target, normalizeIgnoredPattern(pattern))) {
3623
+ ignored = !negated;
3624
+ }
3625
+ }
3626
+ return ignored;
3627
+ }
3628
+
3629
+ function matchesIgnorePattern(target, pattern) {
3630
+ if (pattern.endsWith("/**")) {
3631
+ const prefix = pattern.slice(0, -3);
3632
+ return target === prefix || target.startsWith(`${prefix}/`) || target.includes(`/${prefix}/`);
3633
+ }
3634
+ if (pattern.startsWith("**/")) {
3635
+ const suffix = pattern.slice(3);
3636
+ if (!hasGlobSyntax(suffix)) {
3637
+ return target.endsWith(suffix) || target.includes(`/${suffix}`);
3638
+ }
3639
+ }
3640
+ if (!hasGlobSyntax(pattern)) {
3641
+ return target === pattern || target.endsWith(`/${pattern}`) || target.startsWith(`${pattern}/`);
3642
+ }
3643
+
3644
+ const expression = new RegExp(`(^|/)${globPatternRegExpSource(pattern)}$`);
3645
+ return expression.test(target);
3646
+ }
3647
+
3648
+ function matchesConfigFilePattern(target, pattern) {
3649
+ const expression = new RegExp(`^${globPatternRegExpSource(pattern)}$`);
3650
+ return expression.test(target);
3651
+ }
3652
+
3653
+ function hasGlobSyntax(pattern) {
3654
+ return /[*?[\]{}]/.test(pattern);
3655
+ }
3656
+
3657
+ function normalizePath(path) {
3658
+ return path.replaceAll("\\", "/").replace(/^\.\//, "");
3659
+ }
3660
+
3661
+ function globPatternRegExpSource(pattern) {
3662
+ let source = "";
3663
+ for (let index = 0; index < pattern.length; index += 1) {
3664
+ const char = pattern[index];
3665
+ if (char === "*") {
3666
+ if (pattern[index + 1] === "*") {
3667
+ if (pattern[index + 2] === "/") {
3668
+ source += "(?:.*/)?";
3669
+ index += 2;
3670
+ } else {
3671
+ source += ".*";
3672
+ index += 1;
3673
+ }
3674
+ } else {
3675
+ source += "[^/]*";
3676
+ }
3677
+ continue;
3678
+ }
3679
+ if (char === "?") {
3680
+ source += "[^/]";
3681
+ continue;
3682
+ }
3683
+ if (char === "{") {
3684
+ const end = pattern.indexOf("}", index + 1);
3685
+ if (end !== -1) {
3686
+ const parts = pattern.slice(index + 1, end).split(",");
3687
+ source += `(?:${parts.map(escapeRegExp).join("|")})`;
3688
+ index = end;
3689
+ continue;
3690
+ }
3691
+ }
3692
+ if (char === "[") {
3693
+ const end = pattern.indexOf("]", index + 1);
3694
+ if (end !== -1) {
3695
+ const raw = pattern.slice(index + 1, end);
3696
+ if (raw.length > 0) {
3697
+ const negated = raw[0] === "!" || raw[0] === "^";
3698
+ const body = raw.slice(negated ? 1 : 0);
3699
+ if (body.length > 0) {
3700
+ source += `[${negated ? "^" : ""}${escapeCharacterClass(body)}]`;
3701
+ index = end;
3702
+ continue;
3703
+ }
3704
+ }
3705
+ }
3706
+ }
3707
+ source += escapeRegExp(char);
3708
+ }
3709
+ return source;
3710
+ }
3711
+
3712
+ function escapeRegExp(value) {
3713
+ return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
3714
+ }
3715
+
3716
+ function escapeCharacterClass(value) {
3717
+ return value.replace(/[\\\]]/g, "\\$&");
3718
+ }
3719
+
3720
+ function getErrorResults(results) {
3721
+ if (!Array.isArray(results)) {
3722
+ throw new Error("'results' must be an array");
3723
+ }
3724
+
3725
+ const filtered = [];
3726
+ for (const result of results) {
3727
+ const messages = (result.messages ?? []).filter((message) => message.severity === 2);
3728
+ if (messages.length === 0) continue;
3729
+ filtered.push({
3730
+ ...result,
3731
+ messages,
3732
+ suppressedMessages: (result.suppressedMessages ?? []).filter((message) => message.severity === 2),
3733
+ errorCount: messages.length,
3734
+ warningCount: 0,
3735
+ fixableWarningCount: 0
3736
+ });
3737
+ }
3738
+ return filtered;
3739
+ }
3740
+
3741
+ function ruleMetaForRuleId(ruleId) {
3742
+ return {
3743
+ docs: {
3744
+ url: ruleDocsUrl(ruleId)
3745
+ }
3746
+ };
3747
+ }
3748
+
3749
+ function createBuiltinRule(ruleId) {
3750
+ return {
3751
+ meta: ruleMetaForRuleId(ruleId),
3752
+ create() {
3753
+ return {};
3754
+ }
3755
+ };
3756
+ }
3757
+
3758
+ function ruleDocsUrl(ruleId) {
3759
+ if (ruleId.startsWith("@typescript-eslint/")) {
3760
+ return `https://typescript-eslint.io/rules/${ruleId.slice("@typescript-eslint/".length)}/`;
3761
+ }
3762
+ if (ruleId.startsWith("eslint-comments/")) {
3763
+ return `https://mysticatea.github.io/eslint-plugin-eslint-comments/rules/${ruleId.slice("eslint-comments/".length)}.html`;
3764
+ }
3765
+ if (ruleId.startsWith("import/")) {
3766
+ return `https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/${ruleId.slice("import/".length)}.md`;
3767
+ }
3768
+ if (ruleId.startsWith("jsx-a11y/")) {
3769
+ return `https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/${ruleId.slice("jsx-a11y/".length)}.md`;
3770
+ }
3771
+ if (ruleId.startsWith("react-hooks/")) {
3772
+ return `https://react.dev/reference/eslint-plugin-react-hooks/lints/${ruleId.slice("react-hooks/".length)}`;
3773
+ }
3774
+ if (ruleId.startsWith("react/")) {
3775
+ return `https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/${ruleId.slice("react/".length)}.md`;
3776
+ }
3777
+ return `https://eslint.org/docs/latest/rules/${ruleId}`;
3778
+ }
3779
+
3780
+ function resultsToCLIEngineReport(results) {
3781
+ const report = {
3782
+ results,
3783
+ errorCount: 0,
3784
+ fatalErrorCount: 0,
3785
+ warningCount: 0,
3786
+ fixableErrorCount: 0,
3787
+ fixableWarningCount: 0,
3788
+ usedDeprecatedRules: []
3789
+ };
3790
+
3791
+ for (const result of results) {
3792
+ report.errorCount += result.errorCount ?? 0;
3793
+ report.fatalErrorCount += result.fatalErrorCount ?? 0;
3794
+ report.warningCount += result.warningCount ?? 0;
3795
+ report.fixableErrorCount += result.fixableErrorCount ?? 0;
3796
+ report.fixableWarningCount += result.fixableWarningCount ?? 0;
3797
+ }
3798
+
3799
+ return report;
3800
+ }
3801
+
3802
+ function emptyESLintResult(filePath, source) {
3803
+ const result = {
3804
+ filePath,
3805
+ messages: [],
3806
+ suppressedMessages: [],
3807
+ errorCount: 0,
3808
+ fatalErrorCount: 0,
3809
+ warningCount: 0,
3810
+ fixableErrorCount: 0,
3811
+ fixableWarningCount: 0,
3812
+ usedDeprecatedRules: []
3813
+ };
3814
+ if (source != null) {
3815
+ result.source = source;
3816
+ }
3817
+ return result;
3818
+ }
3819
+
3820
+ function diagnosticToESLintMessage(diagnostic, ruleSeverities) {
3821
+ return {
3822
+ ruleId: diagnostic.ruleId,
3823
+ severity: ruleSeverities?.get(diagnostic.ruleId) ?? (diagnostic.severity === "error" ? 2 : 1),
3824
+ message: diagnostic.message,
3825
+ line: diagnostic.line,
3826
+ column: diagnostic.column,
3827
+ nodeType: null
3828
+ };
3829
+ }
3830
+
3831
+ function finalizeESLintResult(result) {
3832
+ result.errorCount = 0;
3833
+ result.warningCount = 0;
3834
+ result.fixableErrorCount = 0;
3835
+ result.fixableWarningCount = 0;
3836
+ for (const message of result.messages) {
3837
+ if (message.severity === 2) {
3838
+ result.errorCount += 1;
3839
+ if (ruleFixItems(message.fix).length > 0) {
3840
+ result.fixableErrorCount += 1;
3841
+ }
3842
+ } else {
3843
+ result.warningCount += 1;
3844
+ if (ruleFixItems(message.fix).length > 0) {
3845
+ result.fixableWarningCount += 1;
3846
+ }
3847
+ }
3848
+ }
3849
+ }
3850
+
3851
+ function maybeFilterQuietResults(results, options = {}) {
3852
+ if (!options.quiet) {
3853
+ return results;
3854
+ }
3855
+
3856
+ for (const result of results) {
3857
+ result.messages = (result.messages ?? []).filter((message) => message.severity === 2);
3858
+ result.suppressedMessages = (result.suppressedMessages ?? []).filter((message) => message.severity === 2);
3859
+ result.fixableWarningCount = 0;
3860
+ finalizeESLintResult(result);
3861
+ }
3862
+ return results;
3863
+ }
3864
+
3865
+ function formatESLintResults(results) {
3866
+ const lines = [];
3867
+ let errorCount = 0;
3868
+ let warningCount = 0;
3869
+
3870
+ for (const result of results) {
3871
+ if (result.messages.length === 0) continue;
3872
+ lines.push(result.filePath);
3873
+ for (const message of result.messages) {
3874
+ const severity = message.severity === 2 ? "error" : "warning";
3875
+ lines.push(` ${message.line}:${message.column} ${severity} ${message.message} ${message.ruleId}`);
3876
+ }
3877
+ errorCount += result.errorCount;
3878
+ warningCount += result.warningCount;
3879
+ }
3880
+
3881
+ if (errorCount || warningCount) {
3882
+ lines.push("");
3883
+ lines.push(`x ${errorCount + warningCount} problem${errorCount + warningCount === 1 ? "" : "s"} (${errorCount} error${errorCount === 1 ? "" : "s"}, ${warningCount} warning${warningCount === 1 ? "" : "s"})`);
3884
+ }
3885
+
3886
+ return lines.join("\n");
3887
+ }
3888
+
3889
+ function formatResultsByName(input, name = "stylish", metadata = {}) {
3890
+ const results = formatterResults(input);
3891
+ if (name === "json") {
3892
+ return JSON.stringify(input);
3893
+ }
3894
+ if (name === "json-with-metadata") {
3895
+ return JSON.stringify({
3896
+ results,
3897
+ metadata: {
3898
+ rulesMeta: typeof metadata.rulesMeta === "function" ? metadata.rulesMeta(results) : rulesMetaForResults(results)
3899
+ }
3900
+ });
3901
+ }
3902
+ if (name === "compact") {
3903
+ return formatCompactResults(results);
3904
+ }
3905
+ if (name === "unix") {
3906
+ return formatUnixResults(results);
3907
+ }
3908
+ return formatESLintResults(results);
3909
+ }
3910
+
3911
+ function formatterResults(input) {
3912
+ if (Array.isArray(input)) {
3913
+ return input;
3914
+ }
3915
+ if (input && typeof input === "object" && Array.isArray(input.results)) {
3916
+ return input.results;
3917
+ }
3918
+ if (input && typeof input === "object" && Array.isArray(input.diagnostics)) {
3919
+ return reportToESLintResults(input, {
3920
+ filePaths: (input.filePaths ?? []).map((filePath) => normalizeESLintFilePath(filePath))
3921
+ });
3922
+ }
3923
+ throw new Error("'results' must be an array or lint report");
3924
+ }
3925
+
3926
+ function formatCompactResults(results) {
3927
+ const lines = [];
3928
+ for (const result of results) {
3929
+ for (const message of result.messages ?? []) {
3930
+ lines.push(`${result.filePath}: line ${message.line}, col ${message.column}, ${message.severity === 2 ? "Error" : "Warning"} - ${message.message} (${message.ruleId ?? ""})`);
3931
+ }
3932
+ }
3933
+ return lines.join("\n");
3934
+ }
3935
+
3936
+ function formatUnixResults(results) {
3937
+ const lines = [];
3938
+ for (const result of results) {
3939
+ for (const message of result.messages ?? []) {
3940
+ lines.push(`${result.filePath}:${message.line}:${message.column}: ${message.message} [${message.severity === 2 ? "Error" : "Warning"}/${message.ruleId ?? ""}]`);
3941
+ }
3942
+ }
3943
+ return lines.join("\n");
3944
+ }
3945
+
3946
+ function rulesMetaForResults(results, ruleMetaForMessage) {
3947
+ if (!Array.isArray(results)) {
3948
+ throw new Error("'results' must be an array");
3949
+ }
3950
+
3951
+ const meta = {};
3952
+ for (const result of results) {
3953
+ for (const message of [...(result.messages ?? []), ...(result.suppressedMessages ?? [])]) {
3954
+ if (message.ruleId) {
3955
+ meta[message.ruleId] = ruleMetaForMessage?.(message.ruleId, result, message) ?? ruleMetaForRuleId(message.ruleId);
3956
+ }
3957
+ }
3958
+ }
3959
+ return meta;
3960
+ }
3961
+
3962
+ function ruleSeverityMap(rules) {
3963
+ if (!rules) return undefined;
3964
+
3965
+ const severities = new Map();
3966
+ for (const [rule, config] of Object.entries(rules)) {
3967
+ const severity = ruleConfigSeverity(config);
3968
+ severities.set(rule, severity);
3969
+ }
3970
+ return severities;
3971
+ }
3972
+
3973
+ function ruleSeverityMapForOptions(options, filePath) {
3974
+ return ruleSeverityMap(calculatedConfig(options, filePath).rules);
3975
+ }
3976
+
3977
+ function ruleConfigSeverity(config) {
3978
+ const severity = Array.isArray(config) ? config[0] : config;
3979
+ if (severity === false || severity === 0) return 0;
3980
+ if (severity === true || severity === 2) return 2;
3981
+ if (severity === 1) return 1;
3982
+ if (typeof severity === "string") {
3983
+ switch (severity.toLowerCase()) {
3984
+ case "off":
3985
+ case "0":
3986
+ return 0;
3987
+ case "warn":
3988
+ case "warning":
3989
+ case "1":
3990
+ return 1;
3991
+ case "error":
3992
+ case "2":
3993
+ return 2;
3994
+ default:
3995
+ return 1;
3996
+ }
3997
+ }
3998
+ return 1;
3999
+ }
4000
+
4001
+ module.exports = {
4002
+ CLIEngine,
4003
+ ESLint,
4004
+ Linter,
4005
+ RuleTester,
4006
+ SourceCode,
4007
+ UtooLint,
4008
+ default: UtooLint,
4009
+ lintFiles,
4010
+ lintText,
4011
+ loadESLint,
4012
+ platformPackageName,
4013
+ resolveBinary,
4014
+ run,
4015
+ runFishlint,
4016
+ translateFishlintArgs,
4017
+ version
4018
+ };