@utoo/lint 0.2.9 → 0.3.5

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.js CHANGED
@@ -17,6 +17,7 @@ export const version = JSON.parse(readFileSync(new URL("./package.json", import.
17
17
 
18
18
  const LINTABLE_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]);
19
19
  const MAX_AUTOFIX_PASSES = 10;
20
+ const CONFIG_DISCOVERY_CACHE = Symbol("configDiscoveryCache");
20
21
  const JS_KEYWORDS = new Set([
21
22
  "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete",
22
23
  "do", "else", "export", "extends", "finally", "for", "function", "if", "import", "in",
@@ -80,6 +81,7 @@ const BUILTIN_RULE_IDS = [
80
81
  "block-scoped-var",
81
82
  "camelcase",
82
83
  "capitalized-comments",
84
+ "class-methods-use-this",
83
85
  "complexity",
84
86
  "consistent-return",
85
87
  "consistent-this",
@@ -171,6 +173,7 @@ const BUILTIN_RULE_IDS = [
171
173
  "no-inline-comments",
172
174
  "no-inner-declarations",
173
175
  "no-invalid-regexp",
176
+ "no-invalid-this",
174
177
  "no-irregular-whitespace",
175
178
  "no-iterator",
176
179
  "no-label-var",
@@ -179,6 +182,7 @@ const BUILTIN_RULE_IDS = [
179
182
  "no-lonely-if",
180
183
  "no-loop-func",
181
184
  "no-loss-of-precision",
185
+ "no-magic-numbers",
182
186
  "no-mixed-spaces-and-tabs",
183
187
  "no-misleading-character-class",
184
188
  "no-multi-assign",
@@ -237,6 +241,8 @@ const BUILTIN_RULE_IDS = [
237
241
  "no-underscore-dangle",
238
242
  "no-undefined",
239
243
  "no-unneeded-ternary",
244
+ "no-unexpected-multiline",
245
+ "no-unmodified-loop-condition",
240
246
  "no-unreachable",
241
247
  "no-unreachable-loop",
242
248
  "no-unsafe-finally",
@@ -247,6 +253,7 @@ const BUILTIN_RULE_IDS = [
247
253
  "no-unused-private-class-members",
248
254
  "no-unused-vars",
249
255
  "no-use-before-define",
256
+ "no-useless-assignment",
250
257
  "no-useless-backreference",
251
258
  "no-useless-call",
252
259
  "no-useless-catch",
@@ -308,6 +315,19 @@ const BUILTIN_RULE_IDS = [
308
315
  "import/no-named-as-default-member",
309
316
  "import/no-unresolved",
310
317
  "import/no-self-import",
318
+ "jest/no-conditional-expect",
319
+ "jest/no-deprecated-functions",
320
+ "jest/no-export",
321
+ "jest/no-focused-tests",
322
+ "jest/no-identical-title",
323
+ "jest/no-interpolation-in-snapshots",
324
+ "jest/no-jasmine-globals",
325
+ "jest/no-mocks-import",
326
+ "jest/no-standalone-expect",
327
+ "jest/valid-describe-callback",
328
+ "jest/valid-expect-in-promise",
329
+ "jest/valid-expect",
330
+ "jest/valid-title",
311
331
  "promise/no-promise-in-callback",
312
332
  "promise/no-return-in-finally",
313
333
  "promise/no-return-wrap",
@@ -352,9 +372,12 @@ const BUILTIN_RULE_IDS = [
352
372
  "react/no-danger",
353
373
  "react/no-danger-with-children",
354
374
  "react/no-deprecated",
375
+ "react/no-direct-mutation-state",
355
376
  "react/no-find-dom-node",
377
+ "react/no-forward-ref",
356
378
  "react/no-is-mounted",
357
379
  "react/no-multi-comp",
380
+ "react/no-unstable-nested-components",
358
381
  "react/no-redundant-should-component-update",
359
382
  "react/no-render-return-value",
360
383
  "react/no-string-refs",
@@ -452,10 +475,15 @@ const NATIVE_ONLY_RULE_IDS = [
452
475
  ];
453
476
  const NATIVE_RULE_IDS = [...BUILTIN_RULE_IDS, ...NATIVE_ONLY_RULE_IDS];
454
477
  const FIXABLE_BUILTIN_RULE_IDS = new Set([
478
+ "jest/no-deprecated-functions",
479
+ "jest/no-jasmine-globals",
455
480
  "no-extra-semi",
456
481
  "@typescript-eslint/no-extra-semi",
457
482
  "unused-imports/no-unused-imports"
458
483
  ]);
484
+ const SUGGESTION_BUILTIN_RULE_IDS = new Set([
485
+ "jest/no-focused-tests"
486
+ ]);
459
487
  const BUILTIN_RULES = new Map(BUILTIN_RULE_IDS.map((ruleId) => [ruleId, createBuiltinRule(ruleId)]));
460
488
 
461
489
  export class UtooLint {
@@ -545,7 +573,7 @@ export class UtooLint {
545
573
  }
546
574
 
547
575
  async isPathIgnored(filePath) {
548
- return isPathIgnored(filePath, mergeLintOptions(this.options, {}));
576
+ return isPathIgnored(filePath, withConfigCache(mergeLintOptions(this.options, {})));
549
577
  }
550
578
 
551
579
  async calculateConfigForFile(filePath) {
@@ -553,7 +581,7 @@ export class UtooLint {
553
581
  }
554
582
 
555
583
  async findConfigFile(filePath) {
556
- const options = eslintConstructorOptions(this.options);
584
+ const options = withConfigCache(eslintConstructorOptions(this.options));
557
585
  if (options.noConfig) {
558
586
  return undefined;
559
587
  }
@@ -652,15 +680,20 @@ export class Linter {
652
680
  filename: filePath,
653
681
  config: calculatedConfig({ cwd: verifyOptions.cwd, noConfig: true, overrideConfig: config }, normalizedFilePath)
654
682
  });
683
+ const nativeRuleFilter = applyDisableDirectives(
684
+ (report.diagnostics ?? []).map((diagnostic) => diagnosticToESLintMessage(diagnostic, ruleSeverities)),
685
+ sourceCode
686
+ );
655
687
  const customRuleFilter = applyDisableDirectives(customRuleMessages, sourceCode);
656
688
  this.suppressedMessages = [
657
689
  ...(report.suppressedDiagnostics ?? []).map((diagnostic) => suppressedDiagnosticToESLintMessage(diagnostic, ruleSeverities)),
690
+ ...nativeRuleFilter.suppressedMessages,
658
691
  ...customRuleFilter.suppressedMessages
659
692
  ];
660
693
  this.times = { passes: [] };
661
694
  this.fixPassCount = 0;
662
695
  return [
663
- ...(report.diagnostics ?? []).map((diagnostic) => diagnosticToESLintMessage(diagnostic, ruleSeverities)),
696
+ ...nativeRuleFilter.messages,
664
697
  ...customRuleFilter.messages
665
698
  ];
666
699
  }
@@ -765,7 +798,7 @@ function parserOptionsForConfig(config, filePath, cwd) {
765
798
 
766
799
  function customRuleEntriesForConfig(config, rules, filePath, cwd) {
767
800
  const entries = new Map();
768
- for (const [ruleId, ruleConfig] of ruleConfigEntries(config, filePath, cwd)) {
801
+ for (const [ruleId, ruleConfig] of Object.entries(rulesFromConfig(config, filePath, cwd))) {
769
802
  if (!rules.has(ruleId)) {
770
803
  continue;
771
804
  }
@@ -822,19 +855,6 @@ function matchingConfigEntries(config, filePath = undefined, cwd = undefined) {
822
855
  return [config];
823
856
  }
824
857
 
825
- function ruleConfigEntries(config, filePath = undefined, cwd = undefined) {
826
- if (Array.isArray(config)) {
827
- return config.flatMap((item) => ruleConfigEntries(item, filePath, cwd));
828
- }
829
- if (!config || typeof config !== "object" || !config.rules || typeof config.rules !== "object") {
830
- return [];
831
- }
832
- if (!configAppliesToFile(config, filePath, cwd)) {
833
- return [];
834
- }
835
- return Object.entries(config.rules);
836
- }
837
-
838
858
  function configWithoutCustomRules(config, rules) {
839
859
  if (Array.isArray(config)) {
840
860
  return config.map((item) => configWithoutCustomRules(item, rules));
@@ -964,6 +984,7 @@ function applyDisableDirectives(messages, sourceCode) {
964
984
  }
965
985
  const directives = sourceCode.getDisableDirectives()
966
986
  .filter((directive) => directive.node?.loc?.start?.line)
987
+ .filter((directive) => directive.type !== "eslint-disable-line" || directive.node.loc.start.line === directive.node.loc.end.line)
967
988
  .sort((left, right) => left.node.loc.start.line - right.node.loc.start.line);
968
989
  const utlintDirectives = sourceCode.getAllComments()
969
990
  .map((comment) => utlintDirectiveFromComment(comment, sourceCode))
@@ -976,6 +997,10 @@ function applyDisableDirectives(messages, sourceCode) {
976
997
  const kept = [];
977
998
  const suppressed = [];
978
999
  for (const message of messages) {
1000
+ if (!message.ruleId || message.ruleId === "parse" || message.ruleId === "io") {
1001
+ kept.push(message);
1002
+ continue;
1003
+ }
979
1004
  const directive = utlintDirectiveForMessage(message, utlintDirectives) ?? disableDirectiveForMessage(message, directives);
980
1005
  if (directive) {
981
1006
  suppressed.push({
@@ -2135,10 +2160,7 @@ function mergeRuleTesterConfig(baseConfig, overrideConfig) {
2135
2160
  return {
2136
2161
  ...baseConfig,
2137
2162
  ...overrideConfig,
2138
- rules: {
2139
- ...(baseConfig.rules ?? {}),
2140
- ...(overrideConfig.rules ?? {})
2141
- }
2163
+ rules: mergeRuleConfigMaps(baseConfig.rules, overrideConfig.rules)
2142
2164
  };
2143
2165
  }
2144
2166
 
@@ -2589,7 +2611,7 @@ export class CLIEngine {
2589
2611
  }
2590
2612
 
2591
2613
  isPathIgnored(filePath) {
2592
- return isPathIgnored(filePath, eslintConstructorOptions(this.options));
2614
+ return isPathIgnored(filePath, withConfigCache(eslintConstructorOptions(this.options)));
2593
2615
  }
2594
2616
 
2595
2617
  getConfigForFile(filePath) {
@@ -2597,6 +2619,20 @@ export class CLIEngine {
2597
2619
  }
2598
2620
  }
2599
2621
 
2622
+ const DEFAULT_NATIVE_MAX_BUFFER = 64 * 1024 * 1024;
2623
+
2624
+ function nativeMaxBuffer(options, env) {
2625
+ const configured = options.maxBuffer ?? env.UTOO_LINT_MAX_BUFFER;
2626
+ if (configured === undefined || configured === "") {
2627
+ return DEFAULT_NATIVE_MAX_BUFFER;
2628
+ }
2629
+ const value = typeof configured === "number" ? configured : Number(configured);
2630
+ if (!Number.isSafeInteger(value) || value <= 0) {
2631
+ throw new TypeError("maxBuffer and UTOO_LINT_MAX_BUFFER must be a positive integer byte count");
2632
+ }
2633
+ return value;
2634
+ }
2635
+
2600
2636
  export function run(args = [], options = {}) {
2601
2637
  const cliArgs = normalizeStringArray(args, "args");
2602
2638
  const env = options.env ? { ...process.env, ...options.env } : process.env;
@@ -2607,7 +2643,8 @@ export function run(args = [], options = {}) {
2607
2643
  env,
2608
2644
  encoding: options.encoding ?? "utf8",
2609
2645
  input: options.input,
2610
- stdio: options.stdio
2646
+ stdio: options.stdio,
2647
+ maxBuffer: nativeMaxBuffer(options, env)
2611
2648
  });
2612
2649
  }
2613
2650
 
@@ -2749,8 +2786,9 @@ export function runCli(args = [], options = {}) {
2749
2786
  extraArgs: parsed.passthroughArgs,
2750
2787
  preserveNativeDefaults: Boolean(parsed.options.noConfig)
2751
2788
  });
2752
- const targets = parsed.targets.length > 0 ? parsed.targets : defaultLintTargets(cliOptions);
2753
- const report = lintFiles(targets, cliOptions);
2789
+ const usesDefaultTargets = parsed.targets.length === 0;
2790
+ const targets = usesDefaultTargets ? defaultLintTargets(cliOptions) : parsed.targets;
2791
+ const report = lintFiles(targets, { ...cliOptions, useDefaultConfigTargets: usesDefaultTargets });
2754
2792
  report.diagnostics = normalizeReportDiagnostics(report.diagnostics, cliOptions);
2755
2793
  report.exitCode = exitCodeForDiagnostics(report.diagnostics);
2756
2794
  const status = report.exitCode ?? exitCodeForDiagnostics(report.diagnostics);
@@ -2858,7 +2896,8 @@ export function runFishlint(args = [], options = {}) {
2858
2896
  env,
2859
2897
  encoding: options.encoding ?? "utf8",
2860
2898
  input: options.input,
2861
- stdio: options.stdio
2899
+ stdio: options.stdio,
2900
+ maxBuffer: nativeMaxBuffer(options, env)
2862
2901
  });
2863
2902
  }
2864
2903
 
@@ -3060,13 +3099,23 @@ function booleanFlagValue(arg) {
3060
3099
 
3061
3100
  export function lintFiles(paths, options = {}) {
3062
3101
  options = withConfigCache(options);
3102
+ const usesDefaultTargets = paths == null || options.useDefaultConfigTargets;
3063
3103
  const targets = lintTargetsForInput(paths, options);
3064
3104
  const ignoredDiagnostics = ignoredLintPathDiagnostics(targets, options);
3065
- const lintPaths = filteredLintPaths(targets, options);
3105
+ let lintPaths = filteredLintPaths(targets, options);
3106
+ if (usesDefaultTargets) {
3107
+ lintPaths = filterDefaultConfigLintPaths(lintPaths, options);
3108
+ }
3066
3109
  if (lintPaths.length === 0) {
3067
3110
  return { files: 0, filePaths: [], diagnostics: ignoredDiagnostics, suppressedDiagnostics: [], exitCode: exitCodeForDiagnostics(ignoredDiagnostics) };
3068
3111
  }
3069
3112
 
3113
+ const nativeFlatOptions = nativeFlatConfigOptions(options);
3114
+ if (nativeFlatOptions) {
3115
+ const report = runNativeLintReport(lintPaths, nativeFlatOptions);
3116
+ return finalizeLintReport(report, ignoredDiagnostics, options);
3117
+ }
3118
+
3070
3119
  const configRuns = nativeConfigRunsForFiles(expandNativeConfigRunPaths(lintPaths, options), options);
3071
3120
  if (configRuns) {
3072
3121
  const report = mergeNativeReports(
@@ -3079,6 +3128,76 @@ export function lintFiles(paths, options = {}) {
3079
3128
  return finalizeLintReport(report, ignoredDiagnostics, options);
3080
3129
  }
3081
3130
 
3131
+ function nativeFlatConfigOptions(options) {
3132
+ if (options.rules || options.extraArgs?.some((arg) => arg.startsWith("--") && arg !== "--fix" && arg !== "--fix-dry-run")) {
3133
+ return undefined;
3134
+ }
3135
+
3136
+ const fileConfigPath = options.noConfig ? undefined : configPathForOptions(options);
3137
+ const inlineConfigs = [options.baseConfig, options.overrideConfig].filter(Boolean);
3138
+ if (fileConfigPath && inlineConfigs.length > 0) {
3139
+ return undefined;
3140
+ }
3141
+
3142
+ let config;
3143
+ let configRoot;
3144
+ if (fileConfigPath) {
3145
+ config = readConfig(fileConfigPath, options.cwd, options.configCache);
3146
+ configRoot = dirname(fileConfigPath);
3147
+ } else if (inlineConfigs.length > 0) {
3148
+ config = inlineConfigs.length === 1 ? inlineConfigs[0] : inlineConfigs;
3149
+ configRoot = resolvePath(options.cwd ?? process.cwd());
3150
+ } else {
3151
+ return undefined;
3152
+ }
3153
+
3154
+ if (configCanUseNativeRulesFastPath(config)) {
3155
+ return undefined;
3156
+ }
3157
+
3158
+ const serializedConfig = nativeSerializableConfig(config);
3159
+ return {
3160
+ ...options,
3161
+ nativeFlatConfigData: Array.isArray(serializedConfig) ? serializedConfig : [serializedConfig],
3162
+ nativeConfigRoot: configRoot,
3163
+ nativeConfigCwd: resolvePath(options.cwd ?? process.cwd())
3164
+ };
3165
+ }
3166
+
3167
+ function configCanUseNativeRulesFastPath(config) {
3168
+ const entries = Array.isArray(config) ? config : [config];
3169
+ let hasEnabledRule = false;
3170
+ for (const entry of entries) {
3171
+ if (!entry || typeof entry !== "object" || Object.keys(entry.settings ?? {}).length > 0) {
3172
+ return false;
3173
+ }
3174
+ for (const ruleConfig of Object.values(entry.rules ?? {})) {
3175
+ if (ruleConfigSeverity(ruleConfig) === 0) {
3176
+ continue;
3177
+ }
3178
+ if (!isPureRuleSeverity(ruleConfig)) {
3179
+ return false;
3180
+ }
3181
+ hasEnabledRule = true;
3182
+ }
3183
+ }
3184
+ return hasEnabledRule;
3185
+ }
3186
+
3187
+ function nativeSerializableConfig(config) {
3188
+ if (Array.isArray(config)) {
3189
+ return config.map(nativeSerializableConfig);
3190
+ }
3191
+ if (!config || typeof config !== "object") {
3192
+ return config;
3193
+ }
3194
+ return Object.fromEntries(
3195
+ ["$schema", "name", "files", "ignores", "rules", "settings"]
3196
+ .filter((key) => config[key] !== undefined)
3197
+ .map((key) => [key, config[key]])
3198
+ );
3199
+ }
3200
+
3082
3201
  function expandNativeConfigRunPaths(paths, options) {
3083
3202
  const cwd = options.cwd ?? process.cwd();
3084
3203
  const patterns = ignoreDisabled(options) ? [] : ignorePatternsForOptions(options, cwd);
@@ -3152,13 +3271,15 @@ function nativeConfigRunsForFiles(paths, options) {
3152
3271
  const fileConfigPath = options.noConfig ? undefined : configPathForFile(options, matchPath);
3153
3272
  const configured = Boolean(options.baseConfig || options.overrideConfig || fileConfigPath);
3154
3273
  hasConfigSource ||= configured;
3155
- let rules = configured ? calculatedConfig({ ...options, rules: undefined }, matchPath).rules : undefined;
3274
+ const calculated = configured ? calculatedConfig({ ...options, rules: undefined }, matchPath) : {};
3275
+ let rules = calculated.rules;
3276
+ const settings = calculated.settings;
3156
3277
  if (configured && options.rules) {
3157
3278
  rules = selectedRulesWithConfigOptions(rules, options.rules);
3158
3279
  }
3159
- const signature = configured ? stableConfigSignature(rules) : "<native-defaults>";
3280
+ const signature = configured ? stableConfigSignature({ rules, settings }) : "<native-defaults>";
3160
3281
  if (!groups.has(signature)) {
3161
- groups.set(signature, { paths: [], rules, configured });
3282
+ groups.set(signature, { paths: [], rules, settings, configured });
3162
3283
  }
3163
3284
  groups.get(signature).paths.push(filePath);
3164
3285
  }
@@ -3181,6 +3302,22 @@ function nativeConfigRunsForFiles(paths, options) {
3181
3302
  };
3182
3303
  }
3183
3304
 
3305
+ const selectedRules = severityOnlyNativeRuleNames(group.rules, group.settings);
3306
+ if (selectedRules) {
3307
+ return {
3308
+ paths: group.paths,
3309
+ options: {
3310
+ ...options,
3311
+ config: undefined,
3312
+ noConfig: true,
3313
+ baseConfig: undefined,
3314
+ overrideConfig: undefined,
3315
+ rules: selectedRules,
3316
+ forceMaterializedConfig: undefined
3317
+ }
3318
+ };
3319
+ }
3320
+
3184
3321
  return {
3185
3322
  paths: group.paths,
3186
3323
  options: {
@@ -3188,13 +3325,50 @@ function nativeConfigRunsForFiles(paths, options) {
3188
3325
  config: undefined,
3189
3326
  noConfig: true,
3190
3327
  baseConfig: undefined,
3191
- overrideConfig: { rules: allDisabledNativeRules(group.rules) },
3328
+ overrideConfig: {
3329
+ rules: allDisabledNativeRules(group.rules),
3330
+ ...(group.settings ? { settings: group.settings } : {})
3331
+ },
3192
3332
  forceMaterializedConfig: true
3193
3333
  }
3194
3334
  };
3195
3335
  });
3196
3336
  }
3197
3337
 
3338
+ function severityOnlyNativeRuleNames(rules, settings) {
3339
+ if (Object.keys(settings ?? {}).length > 0) {
3340
+ return undefined;
3341
+ }
3342
+
3343
+ const selected = [];
3344
+ for (const [rule, config] of Object.entries(rules ?? {})) {
3345
+ if (ruleConfigSeverity(config) === 0) {
3346
+ continue;
3347
+ }
3348
+ if (!isPureRuleSeverity(config)) {
3349
+ return undefined;
3350
+ }
3351
+ selected.push(rule);
3352
+ }
3353
+ return selected.length > 0 ? selected : undefined;
3354
+ }
3355
+
3356
+ function isPureRuleSeverity(config) {
3357
+ if (Array.isArray(config)) {
3358
+ if (config.length !== 1) {
3359
+ return false;
3360
+ }
3361
+ [config] = config;
3362
+ }
3363
+ if (typeof config === "boolean") {
3364
+ return true;
3365
+ }
3366
+ if (typeof config === "number") {
3367
+ return config === 0 || config === 1 || config === 2;
3368
+ }
3369
+ return typeof config === "string" && ["off", "warn", "warning", "error", "0", "1", "2"].includes(config.toLowerCase());
3370
+ }
3371
+
3198
3372
  function allDisabledNativeRules(rules) {
3199
3373
  return {
3200
3374
  ...Object.fromEntries(NATIVE_RULE_IDS.map((rule) => [rule, "off"])),
@@ -3311,7 +3485,7 @@ function buildNativeLintArgs(paths, options) {
3311
3485
  if (options.noConfig) {
3312
3486
  cliArgs.push("--no-config");
3313
3487
  }
3314
- if (!options.rules && !options.forceMaterializedConfig && (options.config || options.baseConfig || options.overrideConfig)) {
3488
+ if (!options.nativeFlatConfigData && !options.rules && !options.forceMaterializedConfig && (options.config || options.baseConfig || options.overrideConfig)) {
3315
3489
  const configForRuleSelection = options.noConfig
3316
3490
  ? undefined
3317
3491
  : options.config ? readConfig(options.config, options.cwd, options.configCache) : undefined;
@@ -3327,6 +3501,12 @@ function buildNativeLintArgs(paths, options) {
3327
3501
  if (options.config) {
3328
3502
  cliArgs.push(`--config=${options.config}`);
3329
3503
  }
3504
+ if (options.nativeConfigRoot) {
3505
+ cliArgs.push(`--config-root=${options.nativeConfigRoot}`);
3506
+ }
3507
+ if (options.nativeConfigCwd) {
3508
+ cliArgs.push(`--config-cwd=${options.nativeConfigCwd}`);
3509
+ }
3330
3510
  if (options.rules) {
3331
3511
  const rules = Array.isArray(options.rules) ? options.rules.join(",") : options.rules;
3332
3512
  if (rules) {
@@ -3361,9 +3541,17 @@ function mergeLintOptions(base, override) {
3361
3541
  }
3362
3542
 
3363
3543
  function withConfigCache(options) {
3364
- // Executable configs are otherwise re-evaluated at every per-file lookup.
3365
- // Keep the cache on the options object so one public invocation shares it.
3366
- return options.configCache ? options : { ...options, configCache: new Map() };
3544
+ if (options.configCache && options[CONFIG_DISCOVERY_CACHE]) {
3545
+ return options;
3546
+ }
3547
+
3548
+ return {
3549
+ ...options,
3550
+ // Executable configs are otherwise re-evaluated at every per-file lookup.
3551
+ configCache: options.configCache ?? new Map(),
3552
+ // Discovery results must not survive into a later public invocation.
3553
+ [CONFIG_DISCOVERY_CACHE]: new Map()
3554
+ };
3367
3555
  }
3368
3556
 
3369
3557
  function eslintConstructorOptions(options) {
@@ -3417,12 +3605,12 @@ function calculatedConfig(options = {}, filePath) {
3417
3605
  );
3418
3606
  return {
3419
3607
  ...configData,
3420
- rules: {
3421
- ...rulesFromConfig(options.baseConfig, filePath, options.cwd),
3422
- ...rulesFromFileConfig(options, filePath),
3423
- ...rulesFromConfig(options.overrideConfig, filePath, options.cwd),
3424
- ...rulesFromNativeRuleList(options.rules)
3425
- }
3608
+ rules: mergeRuleConfigMaps(
3609
+ rulesFromConfig(options.baseConfig, filePath, options.cwd),
3610
+ rulesFromFileConfig(options, filePath),
3611
+ rulesFromConfig(options.overrideConfig, filePath, options.cwd),
3612
+ rulesFromNativeRuleList(options.rules)
3613
+ )
3426
3614
  };
3427
3615
  }
3428
3616
 
@@ -3517,10 +3705,10 @@ function rulesFromConfig(config, filePath, cwd) {
3517
3705
  return {};
3518
3706
  }
3519
3707
  if (Array.isArray(config)) {
3520
- return config.reduce((rules, entry) => ({
3521
- ...rules,
3522
- ...rulesFromConfig(entry, filePath, cwd)
3523
- }), {});
3708
+ return config.reduce(
3709
+ (rules, entry) => mergeRuleConfigMaps(rules, rulesFromConfig(entry, filePath, cwd)),
3710
+ {}
3711
+ );
3524
3712
  }
3525
3713
  if (!configAppliesToFile(config, filePath, cwd)) {
3526
3714
  return {};
@@ -3573,12 +3761,64 @@ function defaultLintTargets(options = {}) {
3573
3761
  return ["."];
3574
3762
  }
3575
3763
 
3576
- const targets = filePatternsFromConfig(readConfig(configPath, options.cwd, options.configCache));
3764
+ const config = readConfig(configPath, options.cwd, options.configCache);
3765
+ if (configHasUnscopedRules(config)) {
3766
+ return ["."];
3767
+ }
3768
+
3769
+ const targets = filePatternsFromConfig(config);
3577
3770
  return targets.length > 0
3578
3771
  ? targets.map((target) => resolveConfigPattern(target, dirname(configPath)))
3579
3772
  : ["."];
3580
3773
  }
3581
3774
 
3775
+ function filterDefaultConfigLintPaths(paths, options) {
3776
+ const configPath = configPathForOptions(options);
3777
+ if (!configPath) {
3778
+ return paths;
3779
+ }
3780
+ const config = readConfig(configPath, options.cwd, options.configCache);
3781
+ if (!configHasFileSelectors(config)) {
3782
+ return paths;
3783
+ }
3784
+ const cwd = dirname(configPath);
3785
+ return paths.filter((filePath) => configSelectsFile(config, filePath, cwd));
3786
+ }
3787
+
3788
+ function configHasFileSelectors(config) {
3789
+ if (Array.isArray(config)) {
3790
+ return config.some(configHasFileSelectors);
3791
+ }
3792
+ return Boolean(config && typeof config === "object" && normalizeConfigFileSelectors(config.files).length > 0);
3793
+ }
3794
+
3795
+ function configHasUnscopedRules(config) {
3796
+ if (Array.isArray(config)) {
3797
+ return config.some(configHasUnscopedRules);
3798
+ }
3799
+ return Boolean(
3800
+ config &&
3801
+ typeof config === "object" &&
3802
+ normalizeConfigFileSelectors(config.files).length === 0 &&
3803
+ config.rules &&
3804
+ typeof config.rules === "object" &&
3805
+ Object.keys(config.rules).length > 0
3806
+ );
3807
+ }
3808
+
3809
+ function configSelectsFile(config, filePath, cwd) {
3810
+ if (Array.isArray(config)) {
3811
+ return config.some((entry) => configSelectsFile(entry, filePath, cwd));
3812
+ }
3813
+ if (!config || typeof config !== "object") {
3814
+ return false;
3815
+ }
3816
+ const selectors = normalizeConfigFileSelectors(config.files);
3817
+ return selectors.length > 0
3818
+ ? configAppliesToFile(config, filePath, cwd)
3819
+ : configHasUnscopedRules(config) && configAppliesToFile(config, filePath, cwd);
3820
+ }
3821
+
3582
3822
  function resolveConfigPattern(pattern, configDirectory) {
3583
3823
  if (isAbsolute(pattern)) {
3584
3824
  return pattern;
@@ -3602,8 +3842,10 @@ function configAppliesToFile(config, filePath, cwd) {
3602
3842
  }
3603
3843
 
3604
3844
  const normalized = normalizeIgnoredPath(filePath, cwd ?? process.cwd());
3605
- const files = normalizeConfigPatterns(config.files);
3606
- if (files.length > 0 && !files.some((pattern) => matchesConfigFilePattern(normalized, normalizeIgnoredPattern(pattern)))) {
3845
+ const files = normalizeConfigFileSelectors(config.files);
3846
+ if (files.length > 0 && !files.some((selector) => selector.every(
3847
+ (pattern) => matchesConfigFilePattern(normalized, normalizeIgnoredPattern(pattern))
3848
+ ))) {
3607
3849
  return false;
3608
3850
  }
3609
3851
 
@@ -3611,6 +3853,23 @@ function configAppliesToFile(config, filePath, cwd) {
3611
3853
  return !pathIgnoredByPatterns(normalized, ignores);
3612
3854
  }
3613
3855
 
3856
+ function normalizeConfigFileSelectors(patterns) {
3857
+ if (!patterns) {
3858
+ return [];
3859
+ }
3860
+ const values = Array.isArray(patterns) ? patterns : [patterns];
3861
+ return values.flatMap((value) => {
3862
+ if (typeof value === "string") {
3863
+ return [[value]];
3864
+ }
3865
+ if (Array.isArray(value)) {
3866
+ const selector = normalizeConfigPatterns(value);
3867
+ return selector.length > 0 ? [selector] : [];
3868
+ }
3869
+ return [];
3870
+ });
3871
+ }
3872
+
3614
3873
  function normalizeConfigPatterns(patterns) {
3615
3874
  if (!patterns) {
3616
3875
  return [];
@@ -3633,7 +3892,7 @@ function configPathForOptions(options) {
3633
3892
  }
3634
3893
 
3635
3894
  const cwd = options.cwd ?? process.cwd();
3636
- return configPathFromDirectory(cwd);
3895
+ return configPathFromDirectory(cwd, options);
3637
3896
  }
3638
3897
 
3639
3898
  function configPathForFile(options, filePath) {
@@ -3643,7 +3902,7 @@ function configPathForFile(options, filePath) {
3643
3902
  if (options.config) {
3644
3903
  return resolvePath(options.cwd ?? process.cwd(), options.config);
3645
3904
  }
3646
- return configPathFromDirectory(configSearchDirectoryForFile(filePath, options.cwd));
3905
+ return configPathFromDirectory(configSearchDirectoryForFile(filePath, options.cwd), options);
3647
3906
  }
3648
3907
 
3649
3908
  function configSearchDirectoryForFile(filePath, cwd) {
@@ -3658,11 +3917,29 @@ function configSearchDirectoryForFile(filePath, cwd) {
3658
3917
  return dirname(absolute);
3659
3918
  }
3660
3919
 
3661
- function configPathFromDirectory(directory) {
3662
- return findConfigPathFromDirectory(directory);
3920
+ function configPathFromDirectory(directory, options) {
3921
+ return findConfigPathFromDirectory(directory, options[CONFIG_DISCOVERY_CACHE]);
3663
3922
  }
3664
3923
 
3665
3924
  function withTemporaryConfig(options, callback) {
3925
+ if (options.nativeFlatConfigData !== undefined) {
3926
+ const tmp = mkdtempSync(join(tmpdir(), "utoo-lint-flat-config-"));
3927
+ const configPath = join(tmp, "utlint.config.json");
3928
+ try {
3929
+ writeFileSync(configPath, JSON.stringify(options.nativeFlatConfigData));
3930
+ return callback({
3931
+ ...options,
3932
+ config: configPath,
3933
+ noConfig: false,
3934
+ baseConfig: undefined,
3935
+ overrideConfig: undefined,
3936
+ forceMaterializedConfig: true
3937
+ });
3938
+ } finally {
3939
+ rmSync(tmp, { recursive: true, force: true });
3940
+ }
3941
+ }
3942
+
3666
3943
  const fileConfigPath = !options.noConfig ? configPathForOptions(options) : undefined;
3667
3944
  const shouldMaterializeFileConfig = Boolean(fileConfigPath);
3668
3945
  const fileConfig = shouldMaterializeFileConfig
@@ -3672,7 +3949,15 @@ function withTemporaryConfig(options, callback) {
3672
3949
  const rules = shouldMaterializeFileConfig
3673
3950
  ? materializedRulesFromConfigs(...configs)
3674
3951
  : runtimeRulesFromConfigs(...configs);
3675
- if (Object.keys(rules).length === 0) {
3952
+ const settings = configs.reduce(
3953
+ (result, config) => ({
3954
+ ...result,
3955
+ ...(configDataFromConfig(config, options.filePath ?? options.filename, options.cwd).settings ?? {})
3956
+ }),
3957
+ {}
3958
+ );
3959
+ const hasSettings = Object.keys(settings).length > 0;
3960
+ if (Object.keys(rules).length === 0 && !hasSettings) {
3676
3961
  if (shouldMaterializeFileConfig) {
3677
3962
  return callback({
3678
3963
  ...options,
@@ -3684,7 +3969,7 @@ function withTemporaryConfig(options, callback) {
3684
3969
  }
3685
3970
  const enabledRules = enabledRuleNamesFromConfigs(...configs);
3686
3971
  const hasExplicitOffRules = Object.values(rules).some((value) => ruleConfigSeverity(value) === 0);
3687
- if (!shouldMaterializeFileConfig && !hasRuleOptions(rules) && !hasExplicitOffRules && !options.forceMaterializedConfig) {
3972
+ if (!shouldMaterializeFileConfig && !hasSettings && !hasRuleOptions(rules) && !hasExplicitOffRules && !options.forceMaterializedConfig) {
3688
3973
  return callback({
3689
3974
  ...options,
3690
3975
  config: shouldMaterializeFileConfig ? undefined : options.config,
@@ -3696,7 +3981,7 @@ function withTemporaryConfig(options, callback) {
3696
3981
  const tmp = mkdtempSync(join(tmpdir(), "utoo-lint-config-"));
3697
3982
  const configPath = join(tmp, "utlint.config.json");
3698
3983
  try {
3699
- writeFileSync(configPath, JSON.stringify({ rules }));
3984
+ writeFileSync(configPath, JSON.stringify({ rules, ...(hasSettings ? { settings } : {}) }));
3700
3985
  return callback({
3701
3986
  ...options,
3702
3987
  config: shouldMaterializeFileConfig ? configPath : options.config ?? configPath,
@@ -3715,11 +4000,7 @@ function enabledRuleNames(rules) {
3715
4000
  }
3716
4001
 
3717
4002
  function runtimeRulesFromConfigs(...configs) {
3718
- const rules = {};
3719
- for (const config of configs) {
3720
- Object.assign(rules, runtimeRulesFromConfig(config));
3721
- }
3722
- return rules;
4003
+ return mergeRuleConfigMaps(...configs.map(runtimeRulesFromConfig));
3723
4004
  }
3724
4005
 
3725
4006
  function materializedRulesFromConfigs(...configs) {
@@ -3731,10 +4012,10 @@ function runtimeRulesFromConfig(config) {
3731
4012
  return {};
3732
4013
  }
3733
4014
  if (Array.isArray(config)) {
3734
- return config.reduce((rules, entry) => ({
3735
- ...rules,
3736
- ...runtimeRulesFromConfig(entry)
3737
- }), {});
4015
+ return config.reduce(
4016
+ (rules, entry) => mergeRuleConfigMaps(rules, runtimeRulesFromConfig(entry)),
4017
+ {}
4018
+ );
3738
4019
  }
3739
4020
 
3740
4021
  const rules = {};
@@ -3744,6 +4025,26 @@ function runtimeRulesFromConfig(config) {
3744
4025
  return rules;
3745
4026
  }
3746
4027
 
4028
+ function mergeRuleConfigMaps(...ruleMaps) {
4029
+ const result = {};
4030
+ for (const rules of ruleMaps) {
4031
+ for (const [ruleId, ruleConfig] of Object.entries(rules ?? {})) {
4032
+ const previous = result[ruleId];
4033
+ if (previous !== undefined && isSeverityOnlyRuleConfig(ruleConfig) && Array.isArray(previous) && previous.length > 1) {
4034
+ const severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
4035
+ result[ruleId] = [severity, ...previous.slice(1)];
4036
+ } else {
4037
+ result[ruleId] = ruleConfig;
4038
+ }
4039
+ }
4040
+ }
4041
+ return result;
4042
+ }
4043
+
4044
+ function isSeverityOnlyRuleConfig(ruleConfig) {
4045
+ return !Array.isArray(ruleConfig) || ruleConfig.length === 1;
4046
+ }
4047
+
3747
4048
  function enabledRuleNamesFromConfigs(...configs) {
3748
4049
  const rules = new Set();
3749
4050
  for (const config of configs) {
@@ -3825,12 +4126,38 @@ function reportToESLintResults(report, textOptions = {}) {
3825
4126
  }
3826
4127
 
3827
4128
  for (const result of byFile.values()) {
4129
+ applyCompatibilityDisableDirectives(result, textOptions);
3828
4130
  finalizeESLintResult(result);
3829
4131
  }
3830
4132
 
3831
4133
  return [...byFile.values()];
3832
4134
  }
3833
4135
 
4136
+ function applyCompatibilityDisableDirectives(result, textOptions) {
4137
+ if (result.messages.length === 0) {
4138
+ return;
4139
+ }
4140
+
4141
+ let source = result.source;
4142
+ if (typeof source !== "string" && textOptions.filePath === result.filePath) {
4143
+ source = textOptions.source;
4144
+ }
4145
+ if (typeof source !== "string") {
4146
+ try {
4147
+ source = readFileSync(result.filePath, "utf8");
4148
+ } catch {
4149
+ return;
4150
+ }
4151
+ }
4152
+ if (!source.includes("eslint-")) {
4153
+ return;
4154
+ }
4155
+
4156
+ const filtered = applyDisableDirectives(result.messages, createLinterSourceCode(source));
4157
+ result.messages = filtered.messages;
4158
+ result.suppressedMessages.push(...filtered.suppressedMessages);
4159
+ }
4160
+
3834
4161
  function normalizeReportDiagnostics(diagnostics, options = {}) {
3835
4162
  return (diagnostics ?? []).flatMap((diagnostic) => {
3836
4163
  if (!diagnostic?.ruleId) {
@@ -4058,7 +4385,7 @@ function collectGlobFiles(target, cwd, patterns, expression, files) {
4058
4385
 
4059
4386
  const child = join(target, entry.name);
4060
4387
  if (entry.isDirectory()) {
4061
- if (!pathIgnoredByPatterns(normalizeIgnoredPath(child, cwd), patterns)) {
4388
+ if (shouldTraverseDirectory(normalizeIgnoredPath(child, cwd), patterns)) {
4062
4389
  collectGlobFiles(child, cwd, patterns, expression, files);
4063
4390
  }
4064
4391
  continue;
@@ -4128,7 +4455,7 @@ function expandLintTarget(target, cwd, patterns) {
4128
4455
  if (stat.isFile()) {
4129
4456
  return isLintableFilePath(target) && !pathIgnoredByPatterns(normalizeIgnoredPath(target, cwd), patterns) ? [target] : [];
4130
4457
  }
4131
- if (!stat.isDirectory() || pathIgnoredByPatterns(normalizeIgnoredPath(target, cwd), patterns)) {
4458
+ if (!stat.isDirectory() || !shouldTraverseDirectory(normalizeIgnoredPath(target, cwd), patterns)) {
4132
4459
  return [];
4133
4460
  }
4134
4461
 
@@ -4146,7 +4473,7 @@ function collectLintableFiles(target, cwd, patterns, files) {
4146
4473
 
4147
4474
  const child = join(target, entry.name);
4148
4475
  if (entry.isDirectory()) {
4149
- if (!pathIgnoredByPatterns(normalizeIgnoredPath(child, cwd), patterns)) {
4476
+ if (shouldTraverseDirectory(normalizeIgnoredPath(child, cwd), patterns)) {
4150
4477
  collectLintableFiles(child, cwd, patterns, files);
4151
4478
  }
4152
4479
  continue;
@@ -4279,7 +4606,7 @@ function normalizeIgnoredPath(filePath, cwd) {
4279
4606
  }
4280
4607
 
4281
4608
  function normalizeIgnoredPattern(pattern) {
4282
- return normalizePath(pattern.replace(/^!/, "").replace(/^\//, "")).replace(/\/+$/, "");
4609
+ return normalizePath(pattern.replace(/^!/, "")).replace(/\/+$/, "");
4283
4610
  }
4284
4611
 
4285
4612
  function pathIgnoredByPatterns(target, patterns) {
@@ -4293,10 +4620,55 @@ function pathIgnoredByPatterns(target, patterns) {
4293
4620
  return ignored;
4294
4621
  }
4295
4622
 
4623
+ function shouldTraverseDirectory(target, patterns) {
4624
+ return !pathIgnoredByPatterns(target, patterns) || patterns.some(
4625
+ (pattern) => pattern.startsWith("!") && negatedPatternMayMatchDescendant(target, pattern)
4626
+ );
4627
+ }
4628
+
4629
+ function negatedPatternMayMatchDescendant(target, pattern) {
4630
+ pattern = normalizeIgnoredPattern(pattern);
4631
+ if (pattern.startsWith("/")) {
4632
+ pattern = pattern.slice(1);
4633
+ if (target.startsWith("/")) {
4634
+ target = target.slice(1);
4635
+ }
4636
+ }
4637
+ if (!pattern.includes("/")) {
4638
+ return true;
4639
+ }
4640
+
4641
+ const directoryPattern = pattern.slice(0, pattern.lastIndexOf("/"));
4642
+ if (hasGlobSyntax(directoryPattern)) {
4643
+ const directoryExpression = new RegExp(`^${globPatternRegExpSource(directoryPattern)}(?:/.*)?$`);
4644
+ if (directoryExpression.test(target)) {
4645
+ return true;
4646
+ }
4647
+ }
4648
+
4649
+ const globIndex = pattern.search(/[*?[{]/u);
4650
+ const staticPrefix = (globIndex === -1 ? pattern : pattern.slice(0, globIndex)).replace(/\/+$/u, "");
4651
+ return !staticPrefix ||
4652
+ staticPrefix === target ||
4653
+ staticPrefix.startsWith(`${target}/`) ||
4654
+ target.startsWith(`${staticPrefix}/`);
4655
+ }
4656
+
4296
4657
  function matchesIgnorePattern(target, pattern) {
4658
+ const anchored = pattern.startsWith("/");
4659
+ if (anchored) {
4660
+ pattern = pattern.slice(1);
4661
+ if (target.startsWith("/")) {
4662
+ target = target.slice(1);
4663
+ }
4664
+ }
4297
4665
  if (pattern.endsWith("/**")) {
4298
- const prefix = pattern.slice(0, -3);
4299
- return target === prefix || target.startsWith(`${prefix}/`) || target.includes(`/${prefix}/`);
4666
+ const directoryPattern = pattern.slice(0, -3);
4667
+ if (hasGlobSyntax(directoryPattern)) {
4668
+ const expression = new RegExp(`^${globPatternRegExpSource(directoryPattern)}(?:/.*)?$`);
4669
+ return expression.test(target);
4670
+ }
4671
+ return target === directoryPattern || target.startsWith(`${directoryPattern}/`);
4300
4672
  }
4301
4673
  if (pattern.startsWith("**/")) {
4302
4674
  const suffix = pattern.slice(3);
@@ -4305,10 +4677,14 @@ function matchesIgnorePattern(target, pattern) {
4305
4677
  }
4306
4678
  }
4307
4679
  if (!hasGlobSyntax(pattern)) {
4680
+ if (anchored || pattern.includes("/")) {
4681
+ return target === pattern || target.startsWith(`${pattern}/`);
4682
+ }
4308
4683
  return target === pattern || target.endsWith(`/${pattern}`) || target.startsWith(`${pattern}/`);
4309
4684
  }
4310
4685
 
4311
- const expression = new RegExp(`(^|/)${globPatternRegExpSource(pattern)}$`);
4686
+ const prefix = anchored || pattern.includes("/") ? "^" : "(^|/)";
4687
+ const expression = new RegExp(`${prefix}${globPatternRegExpSource(pattern)}$`);
4312
4688
  return expression.test(target);
4313
4689
  }
4314
4690
 
@@ -4417,6 +4793,9 @@ function ruleMetaForRuleId(ruleId) {
4417
4793
  if (FIXABLE_BUILTIN_RULE_IDS.has(ruleId)) {
4418
4794
  meta.fixable = "code";
4419
4795
  }
4796
+ if (SUGGESTION_BUILTIN_RULE_IDS.has(ruleId)) {
4797
+ meta.hasSuggestions = true;
4798
+ }
4420
4799
  return meta;
4421
4800
  }
4422
4801
 
@@ -4507,16 +4886,33 @@ function diagnosticToESLintMessage(diagnostic, ruleSeverities) {
4507
4886
  if (fixes.length > 0) {
4508
4887
  message.fix = fixes.length === 1 ? fixes[0] : fixes;
4509
4888
  }
4889
+ const suggestions = (diagnostic.suggestions ?? []).map((suggestion) => {
4890
+ const suggestionFixes = (suggestion.fix ?? []).map((fix) => ({
4891
+ range: fix.range,
4892
+ text: fix.text
4893
+ }));
4894
+ return {
4895
+ desc: suggestion.desc,
4896
+ ...(suggestionFixes.length === 0 ? {} : {
4897
+ fix: suggestionFixes.length === 1 ? suggestionFixes[0] : suggestionFixes
4898
+ })
4899
+ };
4900
+ });
4901
+ if (suggestions.length > 0) {
4902
+ message.suggestions = suggestions;
4903
+ }
4510
4904
  return message;
4511
4905
  }
4512
4906
 
4513
4907
  function suppressedDiagnosticToESLintMessage(diagnostic, ruleSeverities) {
4514
4908
  return {
4515
4909
  ...diagnosticToESLintMessage(diagnostic, ruleSeverities),
4516
- suppressions: [{
4517
- kind: diagnostic.suppression?.kind ?? "directive",
4518
- justification: diagnostic.suppression?.justification ?? ""
4519
- }]
4910
+ suppressions: diagnostic.suppressions?.length > 0
4911
+ ? diagnostic.suppressions
4912
+ : [{
4913
+ kind: diagnostic.suppression?.kind ?? "directive",
4914
+ justification: diagnostic.suppression?.justification ?? ""
4915
+ }]
4520
4916
  };
4521
4917
  }
4522
4918