@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.cjs CHANGED
@@ -14,6 +14,7 @@ const version = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8")
14
14
 
15
15
  const LINTABLE_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]);
16
16
  const MAX_AUTOFIX_PASSES = 10;
17
+ const CONFIG_DISCOVERY_CACHE = Symbol("configDiscoveryCache");
17
18
  const JS_KEYWORDS = new Set([
18
19
  "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete",
19
20
  "do", "else", "export", "extends", "finally", "for", "function", "if", "import", "in",
@@ -77,6 +78,7 @@ const BUILTIN_RULE_IDS = [
77
78
  "block-scoped-var",
78
79
  "camelcase",
79
80
  "capitalized-comments",
81
+ "class-methods-use-this",
80
82
  "complexity",
81
83
  "consistent-return",
82
84
  "consistent-this",
@@ -168,6 +170,7 @@ const BUILTIN_RULE_IDS = [
168
170
  "no-inline-comments",
169
171
  "no-inner-declarations",
170
172
  "no-invalid-regexp",
173
+ "no-invalid-this",
171
174
  "no-irregular-whitespace",
172
175
  "no-iterator",
173
176
  "no-label-var",
@@ -176,6 +179,7 @@ const BUILTIN_RULE_IDS = [
176
179
  "no-lonely-if",
177
180
  "no-loop-func",
178
181
  "no-loss-of-precision",
182
+ "no-magic-numbers",
179
183
  "no-mixed-spaces-and-tabs",
180
184
  "no-misleading-character-class",
181
185
  "no-multi-assign",
@@ -234,6 +238,8 @@ const BUILTIN_RULE_IDS = [
234
238
  "no-underscore-dangle",
235
239
  "no-undefined",
236
240
  "no-unneeded-ternary",
241
+ "no-unexpected-multiline",
242
+ "no-unmodified-loop-condition",
237
243
  "no-unreachable",
238
244
  "no-unreachable-loop",
239
245
  "no-unsafe-finally",
@@ -244,6 +250,7 @@ const BUILTIN_RULE_IDS = [
244
250
  "no-unused-private-class-members",
245
251
  "no-unused-vars",
246
252
  "no-use-before-define",
253
+ "no-useless-assignment",
247
254
  "no-useless-backreference",
248
255
  "no-useless-call",
249
256
  "no-useless-catch",
@@ -305,6 +312,19 @@ const BUILTIN_RULE_IDS = [
305
312
  "import/no-named-as-default-member",
306
313
  "import/no-unresolved",
307
314
  "import/no-self-import",
315
+ "jest/no-conditional-expect",
316
+ "jest/no-deprecated-functions",
317
+ "jest/no-export",
318
+ "jest/no-focused-tests",
319
+ "jest/no-identical-title",
320
+ "jest/no-interpolation-in-snapshots",
321
+ "jest/no-jasmine-globals",
322
+ "jest/no-mocks-import",
323
+ "jest/no-standalone-expect",
324
+ "jest/valid-describe-callback",
325
+ "jest/valid-expect-in-promise",
326
+ "jest/valid-expect",
327
+ "jest/valid-title",
308
328
  "promise/no-promise-in-callback",
309
329
  "promise/no-return-in-finally",
310
330
  "promise/no-return-wrap",
@@ -349,9 +369,12 @@ const BUILTIN_RULE_IDS = [
349
369
  "react/no-danger",
350
370
  "react/no-danger-with-children",
351
371
  "react/no-deprecated",
372
+ "react/no-direct-mutation-state",
352
373
  "react/no-find-dom-node",
374
+ "react/no-forward-ref",
353
375
  "react/no-is-mounted",
354
376
  "react/no-multi-comp",
377
+ "react/no-unstable-nested-components",
355
378
  "react/no-redundant-should-component-update",
356
379
  "react/no-render-return-value",
357
380
  "react/no-string-refs",
@@ -449,10 +472,15 @@ const NATIVE_ONLY_RULE_IDS = [
449
472
  ];
450
473
  const NATIVE_RULE_IDS = [...BUILTIN_RULE_IDS, ...NATIVE_ONLY_RULE_IDS];
451
474
  const FIXABLE_BUILTIN_RULE_IDS = new Set([
475
+ "jest/no-deprecated-functions",
476
+ "jest/no-jasmine-globals",
452
477
  "no-extra-semi",
453
478
  "@typescript-eslint/no-extra-semi",
454
479
  "unused-imports/no-unused-imports"
455
480
  ]);
481
+ const SUGGESTION_BUILTIN_RULE_IDS = new Set([
482
+ "jest/no-focused-tests"
483
+ ]);
456
484
  const BUILTIN_RULES = new Map(BUILTIN_RULE_IDS.map((ruleId) => [ruleId, createBuiltinRule(ruleId)]));
457
485
 
458
486
  class UtooLint {
@@ -542,7 +570,7 @@ class UtooLint {
542
570
  }
543
571
 
544
572
  async isPathIgnored(filePath) {
545
- return isPathIgnored(filePath, mergeLintOptions(this.options, {}));
573
+ return isPathIgnored(filePath, withConfigCache(mergeLintOptions(this.options, {})));
546
574
  }
547
575
 
548
576
  async calculateConfigForFile(filePath) {
@@ -550,7 +578,7 @@ class UtooLint {
550
578
  }
551
579
 
552
580
  async findConfigFile(filePath) {
553
- const options = eslintConstructorOptions(this.options);
581
+ const options = withConfigCache(eslintConstructorOptions(this.options));
554
582
  if (options.noConfig) {
555
583
  return undefined;
556
584
  }
@@ -684,7 +712,7 @@ class CLIEngine {
684
712
  }
685
713
 
686
714
  isPathIgnored(filePath) {
687
- return isPathIgnored(filePath, eslintConstructorOptions(this.options));
715
+ return isPathIgnored(filePath, withConfigCache(eslintConstructorOptions(this.options)));
688
716
  }
689
717
 
690
718
  getConfigForFile(filePath) {
@@ -692,6 +720,20 @@ class CLIEngine {
692
720
  }
693
721
  }
694
722
 
723
+ const DEFAULT_NATIVE_MAX_BUFFER = 64 * 1024 * 1024;
724
+
725
+ function nativeMaxBuffer(options, env) {
726
+ const configured = options.maxBuffer ?? env.UTOO_LINT_MAX_BUFFER;
727
+ if (configured === undefined || configured === "") {
728
+ return DEFAULT_NATIVE_MAX_BUFFER;
729
+ }
730
+ const value = typeof configured === "number" ? configured : Number(configured);
731
+ if (!Number.isSafeInteger(value) || value <= 0) {
732
+ throw new TypeError("maxBuffer and UTOO_LINT_MAX_BUFFER must be a positive integer byte count");
733
+ }
734
+ return value;
735
+ }
736
+
695
737
  function run(args = [], options = {}) {
696
738
  const cliArgs = normalizeStringArray(args, "args");
697
739
  const env = options.env ? { ...process.env, ...options.env } : process.env;
@@ -702,7 +744,8 @@ function run(args = [], options = {}) {
702
744
  env,
703
745
  encoding: options.encoding ?? "utf8",
704
746
  input: options.input,
705
- stdio: options.stdio
747
+ stdio: options.stdio,
748
+ maxBuffer: nativeMaxBuffer(options, env)
706
749
  });
707
750
  }
708
751
 
@@ -844,8 +887,9 @@ function runCli(args = [], options = {}) {
844
887
  extraArgs: parsed.passthroughArgs,
845
888
  preserveNativeDefaults: Boolean(parsed.options.noConfig)
846
889
  });
847
- const targets = parsed.targets.length > 0 ? parsed.targets : defaultLintTargets(cliOptions);
848
- const report = lintFiles(targets, cliOptions);
890
+ const usesDefaultTargets = parsed.targets.length === 0;
891
+ const targets = usesDefaultTargets ? defaultLintTargets(cliOptions) : parsed.targets;
892
+ const report = lintFiles(targets, { ...cliOptions, useDefaultConfigTargets: usesDefaultTargets });
849
893
  report.diagnostics = normalizeReportDiagnostics(report.diagnostics, cliOptions);
850
894
  report.exitCode = exitCodeForDiagnostics(report.diagnostics);
851
895
  const status = report.exitCode ?? exitCodeForDiagnostics(report.diagnostics);
@@ -953,7 +997,8 @@ function runFishlint(args = [], options = {}) {
953
997
  env,
954
998
  encoding: options.encoding ?? "utf8",
955
999
  input: options.input,
956
- stdio: options.stdio
1000
+ stdio: options.stdio,
1001
+ maxBuffer: nativeMaxBuffer(options, env)
957
1002
  });
958
1003
  }
959
1004
 
@@ -1155,13 +1200,23 @@ function booleanFlagValue(arg) {
1155
1200
 
1156
1201
  function lintFiles(paths, options = {}) {
1157
1202
  options = withConfigCache(options);
1203
+ const usesDefaultTargets = paths == null || options.useDefaultConfigTargets;
1158
1204
  const targets = lintTargetsForInput(paths, options);
1159
1205
  const ignoredDiagnostics = ignoredLintPathDiagnostics(targets, options);
1160
- const lintPaths = filteredLintPaths(targets, options);
1206
+ let lintPaths = filteredLintPaths(targets, options);
1207
+ if (usesDefaultTargets) {
1208
+ lintPaths = filterDefaultConfigLintPaths(lintPaths, options);
1209
+ }
1161
1210
  if (lintPaths.length === 0) {
1162
1211
  return { files: 0, filePaths: [], diagnostics: ignoredDiagnostics, suppressedDiagnostics: [], exitCode: exitCodeForDiagnostics(ignoredDiagnostics) };
1163
1212
  }
1164
1213
 
1214
+ const nativeFlatOptions = nativeFlatConfigOptions(options);
1215
+ if (nativeFlatOptions) {
1216
+ const report = runNativeLintReport(lintPaths, nativeFlatOptions);
1217
+ return finalizeLintReport(report, ignoredDiagnostics, options);
1218
+ }
1219
+
1165
1220
  const configRuns = nativeConfigRunsForFiles(expandNativeConfigRunPaths(lintPaths, options), options);
1166
1221
  if (configRuns) {
1167
1222
  const report = mergeNativeReports(
@@ -1174,6 +1229,76 @@ function lintFiles(paths, options = {}) {
1174
1229
  return finalizeLintReport(report, ignoredDiagnostics, options);
1175
1230
  }
1176
1231
 
1232
+ function nativeFlatConfigOptions(options) {
1233
+ if (options.rules || options.extraArgs?.some((arg) => arg.startsWith("--") && arg !== "--fix" && arg !== "--fix-dry-run")) {
1234
+ return undefined;
1235
+ }
1236
+
1237
+ const fileConfigPath = options.noConfig ? undefined : configPathForOptions(options);
1238
+ const inlineConfigs = [options.baseConfig, options.overrideConfig].filter(Boolean);
1239
+ if (fileConfigPath && inlineConfigs.length > 0) {
1240
+ return undefined;
1241
+ }
1242
+
1243
+ let config;
1244
+ let configRoot;
1245
+ if (fileConfigPath) {
1246
+ config = readConfig(fileConfigPath, options.cwd, options.configCache);
1247
+ configRoot = dirname(fileConfigPath);
1248
+ } else if (inlineConfigs.length > 0) {
1249
+ config = inlineConfigs.length === 1 ? inlineConfigs[0] : inlineConfigs;
1250
+ configRoot = resolvePath(options.cwd ?? process.cwd());
1251
+ } else {
1252
+ return undefined;
1253
+ }
1254
+
1255
+ if (configCanUseNativeRulesFastPath(config)) {
1256
+ return undefined;
1257
+ }
1258
+
1259
+ const serializedConfig = nativeSerializableConfig(config);
1260
+ return {
1261
+ ...options,
1262
+ nativeFlatConfigData: Array.isArray(serializedConfig) ? serializedConfig : [serializedConfig],
1263
+ nativeConfigRoot: configRoot,
1264
+ nativeConfigCwd: resolvePath(options.cwd ?? process.cwd())
1265
+ };
1266
+ }
1267
+
1268
+ function configCanUseNativeRulesFastPath(config) {
1269
+ const entries = Array.isArray(config) ? config : [config];
1270
+ let hasEnabledRule = false;
1271
+ for (const entry of entries) {
1272
+ if (!entry || typeof entry !== "object" || Object.keys(entry.settings ?? {}).length > 0) {
1273
+ return false;
1274
+ }
1275
+ for (const ruleConfig of Object.values(entry.rules ?? {})) {
1276
+ if (ruleConfigSeverity(ruleConfig) === 0) {
1277
+ continue;
1278
+ }
1279
+ if (!isPureRuleSeverity(ruleConfig)) {
1280
+ return false;
1281
+ }
1282
+ hasEnabledRule = true;
1283
+ }
1284
+ }
1285
+ return hasEnabledRule;
1286
+ }
1287
+
1288
+ function nativeSerializableConfig(config) {
1289
+ if (Array.isArray(config)) {
1290
+ return config.map(nativeSerializableConfig);
1291
+ }
1292
+ if (!config || typeof config !== "object") {
1293
+ return config;
1294
+ }
1295
+ return Object.fromEntries(
1296
+ ["$schema", "name", "files", "ignores", "rules", "settings"]
1297
+ .filter((key) => config[key] !== undefined)
1298
+ .map((key) => [key, config[key]])
1299
+ );
1300
+ }
1301
+
1177
1302
  function expandNativeConfigRunPaths(paths, options) {
1178
1303
  const cwd = options.cwd ?? process.cwd();
1179
1304
  const patterns = ignoreDisabled(options) ? [] : ignorePatternsForOptions(options, cwd);
@@ -1247,13 +1372,15 @@ function nativeConfigRunsForFiles(paths, options) {
1247
1372
  const fileConfigPath = options.noConfig ? undefined : configPathForFile(options, matchPath);
1248
1373
  const configured = Boolean(options.baseConfig || options.overrideConfig || fileConfigPath);
1249
1374
  hasConfigSource ||= configured;
1250
- let rules = configured ? calculatedConfig({ ...options, rules: undefined }, matchPath).rules : undefined;
1375
+ const calculated = configured ? calculatedConfig({ ...options, rules: undefined }, matchPath) : {};
1376
+ let rules = calculated.rules;
1377
+ const settings = calculated.settings;
1251
1378
  if (configured && options.rules) {
1252
1379
  rules = selectedRulesWithConfigOptions(rules, options.rules);
1253
1380
  }
1254
- const signature = configured ? stableConfigSignature(rules) : "<native-defaults>";
1381
+ const signature = configured ? stableConfigSignature({ rules, settings }) : "<native-defaults>";
1255
1382
  if (!groups.has(signature)) {
1256
- groups.set(signature, { paths: [], rules, configured });
1383
+ groups.set(signature, { paths: [], rules, settings, configured });
1257
1384
  }
1258
1385
  groups.get(signature).paths.push(filePath);
1259
1386
  }
@@ -1276,6 +1403,22 @@ function nativeConfigRunsForFiles(paths, options) {
1276
1403
  };
1277
1404
  }
1278
1405
 
1406
+ const selectedRules = severityOnlyNativeRuleNames(group.rules, group.settings);
1407
+ if (selectedRules) {
1408
+ return {
1409
+ paths: group.paths,
1410
+ options: {
1411
+ ...options,
1412
+ config: undefined,
1413
+ noConfig: true,
1414
+ baseConfig: undefined,
1415
+ overrideConfig: undefined,
1416
+ rules: selectedRules,
1417
+ forceMaterializedConfig: undefined
1418
+ }
1419
+ };
1420
+ }
1421
+
1279
1422
  return {
1280
1423
  paths: group.paths,
1281
1424
  options: {
@@ -1283,13 +1426,50 @@ function nativeConfigRunsForFiles(paths, options) {
1283
1426
  config: undefined,
1284
1427
  noConfig: true,
1285
1428
  baseConfig: undefined,
1286
- overrideConfig: { rules: allDisabledNativeRules(group.rules) },
1429
+ overrideConfig: {
1430
+ rules: allDisabledNativeRules(group.rules),
1431
+ ...(group.settings ? { settings: group.settings } : {})
1432
+ },
1287
1433
  forceMaterializedConfig: true
1288
1434
  }
1289
1435
  };
1290
1436
  });
1291
1437
  }
1292
1438
 
1439
+ function severityOnlyNativeRuleNames(rules, settings) {
1440
+ if (Object.keys(settings ?? {}).length > 0) {
1441
+ return undefined;
1442
+ }
1443
+
1444
+ const selected = [];
1445
+ for (const [rule, config] of Object.entries(rules ?? {})) {
1446
+ if (ruleConfigSeverity(config) === 0) {
1447
+ continue;
1448
+ }
1449
+ if (!isPureRuleSeverity(config)) {
1450
+ return undefined;
1451
+ }
1452
+ selected.push(rule);
1453
+ }
1454
+ return selected.length > 0 ? selected : undefined;
1455
+ }
1456
+
1457
+ function isPureRuleSeverity(config) {
1458
+ if (Array.isArray(config)) {
1459
+ if (config.length !== 1) {
1460
+ return false;
1461
+ }
1462
+ [config] = config;
1463
+ }
1464
+ if (typeof config === "boolean") {
1465
+ return true;
1466
+ }
1467
+ if (typeof config === "number") {
1468
+ return config === 0 || config === 1 || config === 2;
1469
+ }
1470
+ return typeof config === "string" && ["off", "warn", "warning", "error", "0", "1", "2"].includes(config.toLowerCase());
1471
+ }
1472
+
1293
1473
  function allDisabledNativeRules(rules) {
1294
1474
  return {
1295
1475
  ...Object.fromEntries(NATIVE_RULE_IDS.map((rule) => [rule, "off"])),
@@ -1406,7 +1586,7 @@ function buildNativeLintArgs(paths, options) {
1406
1586
  if (options.noConfig) {
1407
1587
  cliArgs.push("--no-config");
1408
1588
  }
1409
- if (!options.rules && !options.forceMaterializedConfig && (options.config || options.baseConfig || options.overrideConfig)) {
1589
+ if (!options.nativeFlatConfigData && !options.rules && !options.forceMaterializedConfig && (options.config || options.baseConfig || options.overrideConfig)) {
1410
1590
  const configForRuleSelection = options.noConfig
1411
1591
  ? undefined
1412
1592
  : options.config ? readConfig(options.config, options.cwd, options.configCache) : undefined;
@@ -1422,6 +1602,12 @@ function buildNativeLintArgs(paths, options) {
1422
1602
  if (options.config) {
1423
1603
  cliArgs.push(`--config=${options.config}`);
1424
1604
  }
1605
+ if (options.nativeConfigRoot) {
1606
+ cliArgs.push(`--config-root=${options.nativeConfigRoot}`);
1607
+ }
1608
+ if (options.nativeConfigCwd) {
1609
+ cliArgs.push(`--config-cwd=${options.nativeConfigCwd}`);
1610
+ }
1425
1611
  if (options.rules) {
1426
1612
  const rules = Array.isArray(options.rules) ? options.rules.join(",") : options.rules;
1427
1613
  if (rules) {
@@ -1456,9 +1642,17 @@ function mergeLintOptions(base, override) {
1456
1642
  }
1457
1643
 
1458
1644
  function withConfigCache(options) {
1459
- // Executable configs are otherwise re-evaluated at every per-file lookup.
1460
- // Keep the cache on the options object so one public invocation shares it.
1461
- return options.configCache ? options : { ...options, configCache: new Map() };
1645
+ if (options.configCache && options[CONFIG_DISCOVERY_CACHE]) {
1646
+ return options;
1647
+ }
1648
+
1649
+ return {
1650
+ ...options,
1651
+ // Executable configs are otherwise re-evaluated at every per-file lookup.
1652
+ configCache: options.configCache ?? new Map(),
1653
+ // Discovery results must not survive into a later public invocation.
1654
+ [CONFIG_DISCOVERY_CACHE]: new Map()
1655
+ };
1462
1656
  }
1463
1657
 
1464
1658
  function eslintConstructorOptions(options) {
@@ -1512,12 +1706,12 @@ function calculatedConfig(options = {}, filePath) {
1512
1706
  );
1513
1707
  return {
1514
1708
  ...configData,
1515
- rules: {
1516
- ...rulesFromConfig(options.baseConfig, filePath, options.cwd),
1517
- ...rulesFromFileConfig(options, filePath),
1518
- ...rulesFromConfig(options.overrideConfig, filePath, options.cwd),
1519
- ...rulesFromNativeRuleList(options.rules)
1520
- }
1709
+ rules: mergeRuleConfigMaps(
1710
+ rulesFromConfig(options.baseConfig, filePath, options.cwd),
1711
+ rulesFromFileConfig(options, filePath),
1712
+ rulesFromConfig(options.overrideConfig, filePath, options.cwd),
1713
+ rulesFromNativeRuleList(options.rules)
1714
+ )
1521
1715
  };
1522
1716
  }
1523
1717
 
@@ -1612,10 +1806,10 @@ function rulesFromConfig(config, filePath, cwd) {
1612
1806
  return {};
1613
1807
  }
1614
1808
  if (Array.isArray(config)) {
1615
- return config.reduce((rules, entry) => ({
1616
- ...rules,
1617
- ...rulesFromConfig(entry, filePath, cwd)
1618
- }), {});
1809
+ return config.reduce(
1810
+ (rules, entry) => mergeRuleConfigMaps(rules, rulesFromConfig(entry, filePath, cwd)),
1811
+ {}
1812
+ );
1619
1813
  }
1620
1814
  if (!configAppliesToFile(config, filePath, cwd)) {
1621
1815
  return {};
@@ -1668,12 +1862,64 @@ function defaultLintTargets(options = {}) {
1668
1862
  return ["."];
1669
1863
  }
1670
1864
 
1671
- const targets = filePatternsFromConfig(readConfig(configPath, options.cwd, options.configCache));
1865
+ const config = readConfig(configPath, options.cwd, options.configCache);
1866
+ if (configHasUnscopedRules(config)) {
1867
+ return ["."];
1868
+ }
1869
+
1870
+ const targets = filePatternsFromConfig(config);
1672
1871
  return targets.length > 0
1673
1872
  ? targets.map((target) => resolveConfigPattern(target, dirname(configPath)))
1674
1873
  : ["."];
1675
1874
  }
1676
1875
 
1876
+ function filterDefaultConfigLintPaths(paths, options) {
1877
+ const configPath = configPathForOptions(options);
1878
+ if (!configPath) {
1879
+ return paths;
1880
+ }
1881
+ const config = readConfig(configPath, options.cwd, options.configCache);
1882
+ if (!configHasFileSelectors(config)) {
1883
+ return paths;
1884
+ }
1885
+ const cwd = dirname(configPath);
1886
+ return paths.filter((filePath) => configSelectsFile(config, filePath, cwd));
1887
+ }
1888
+
1889
+ function configHasFileSelectors(config) {
1890
+ if (Array.isArray(config)) {
1891
+ return config.some(configHasFileSelectors);
1892
+ }
1893
+ return Boolean(config && typeof config === "object" && normalizeConfigFileSelectors(config.files).length > 0);
1894
+ }
1895
+
1896
+ function configHasUnscopedRules(config) {
1897
+ if (Array.isArray(config)) {
1898
+ return config.some(configHasUnscopedRules);
1899
+ }
1900
+ return Boolean(
1901
+ config &&
1902
+ typeof config === "object" &&
1903
+ normalizeConfigFileSelectors(config.files).length === 0 &&
1904
+ config.rules &&
1905
+ typeof config.rules === "object" &&
1906
+ Object.keys(config.rules).length > 0
1907
+ );
1908
+ }
1909
+
1910
+ function configSelectsFile(config, filePath, cwd) {
1911
+ if (Array.isArray(config)) {
1912
+ return config.some((entry) => configSelectsFile(entry, filePath, cwd));
1913
+ }
1914
+ if (!config || typeof config !== "object") {
1915
+ return false;
1916
+ }
1917
+ const selectors = normalizeConfigFileSelectors(config.files);
1918
+ return selectors.length > 0
1919
+ ? configAppliesToFile(config, filePath, cwd)
1920
+ : configHasUnscopedRules(config) && configAppliesToFile(config, filePath, cwd);
1921
+ }
1922
+
1677
1923
  function resolveConfigPattern(pattern, configDirectory) {
1678
1924
  if (isAbsolute(pattern)) {
1679
1925
  return pattern;
@@ -1697,8 +1943,10 @@ function configAppliesToFile(config, filePath, cwd) {
1697
1943
  }
1698
1944
 
1699
1945
  const normalized = normalizeIgnoredPath(filePath, cwd ?? process.cwd());
1700
- const files = normalizeConfigPatterns(config.files);
1701
- if (files.length > 0 && !files.some((pattern) => matchesConfigFilePattern(normalized, normalizeIgnoredPattern(pattern)))) {
1946
+ const files = normalizeConfigFileSelectors(config.files);
1947
+ if (files.length > 0 && !files.some((selector) => selector.every(
1948
+ (pattern) => matchesConfigFilePattern(normalized, normalizeIgnoredPattern(pattern))
1949
+ ))) {
1702
1950
  return false;
1703
1951
  }
1704
1952
 
@@ -1706,6 +1954,23 @@ function configAppliesToFile(config, filePath, cwd) {
1706
1954
  return !pathIgnoredByPatterns(normalized, ignores);
1707
1955
  }
1708
1956
 
1957
+ function normalizeConfigFileSelectors(patterns) {
1958
+ if (!patterns) {
1959
+ return [];
1960
+ }
1961
+ const values = Array.isArray(patterns) ? patterns : [patterns];
1962
+ return values.flatMap((value) => {
1963
+ if (typeof value === "string") {
1964
+ return [[value]];
1965
+ }
1966
+ if (Array.isArray(value)) {
1967
+ const selector = normalizeConfigPatterns(value);
1968
+ return selector.length > 0 ? [selector] : [];
1969
+ }
1970
+ return [];
1971
+ });
1972
+ }
1973
+
1709
1974
  function normalizeConfigPatterns(patterns) {
1710
1975
  if (!patterns) {
1711
1976
  return [];
@@ -1728,7 +1993,7 @@ function configPathForOptions(options) {
1728
1993
  }
1729
1994
 
1730
1995
  const cwd = options.cwd ?? process.cwd();
1731
- return configPathFromDirectory(cwd);
1996
+ return configPathFromDirectory(cwd, options);
1732
1997
  }
1733
1998
 
1734
1999
  function configPathForFile(options, filePath) {
@@ -1738,7 +2003,7 @@ function configPathForFile(options, filePath) {
1738
2003
  if (options.config) {
1739
2004
  return resolvePath(options.cwd ?? process.cwd(), options.config);
1740
2005
  }
1741
- return configPathFromDirectory(configSearchDirectoryForFile(filePath, options.cwd));
2006
+ return configPathFromDirectory(configSearchDirectoryForFile(filePath, options.cwd), options);
1742
2007
  }
1743
2008
 
1744
2009
  function configSearchDirectoryForFile(filePath, cwd) {
@@ -1753,11 +2018,29 @@ function configSearchDirectoryForFile(filePath, cwd) {
1753
2018
  return dirname(absolute);
1754
2019
  }
1755
2020
 
1756
- function configPathFromDirectory(directory) {
1757
- return findConfigPathFromDirectory(directory);
2021
+ function configPathFromDirectory(directory, options) {
2022
+ return findConfigPathFromDirectory(directory, options[CONFIG_DISCOVERY_CACHE]);
1758
2023
  }
1759
2024
 
1760
2025
  function withTemporaryConfig(options, callback) {
2026
+ if (options.nativeFlatConfigData !== undefined) {
2027
+ const tmp = mkdtempSync(join(tmpdir(), "utoo-lint-flat-config-"));
2028
+ const configPath = join(tmp, "utlint.config.json");
2029
+ try {
2030
+ writeFileSync(configPath, JSON.stringify(options.nativeFlatConfigData));
2031
+ return callback({
2032
+ ...options,
2033
+ config: configPath,
2034
+ noConfig: false,
2035
+ baseConfig: undefined,
2036
+ overrideConfig: undefined,
2037
+ forceMaterializedConfig: true
2038
+ });
2039
+ } finally {
2040
+ rmSync(tmp, { recursive: true, force: true });
2041
+ }
2042
+ }
2043
+
1761
2044
  const fileConfigPath = !options.noConfig ? configPathForOptions(options) : undefined;
1762
2045
  const shouldMaterializeFileConfig = Boolean(fileConfigPath);
1763
2046
  const fileConfig = shouldMaterializeFileConfig
@@ -1767,7 +2050,15 @@ function withTemporaryConfig(options, callback) {
1767
2050
  const rules = shouldMaterializeFileConfig
1768
2051
  ? materializedRulesFromConfigs(...configs)
1769
2052
  : runtimeRulesFromConfigs(...configs);
1770
- if (Object.keys(rules).length === 0) {
2053
+ const settings = configs.reduce(
2054
+ (result, config) => ({
2055
+ ...result,
2056
+ ...(configDataFromConfig(config, options.filePath ?? options.filename, options.cwd).settings ?? {})
2057
+ }),
2058
+ {}
2059
+ );
2060
+ const hasSettings = Object.keys(settings).length > 0;
2061
+ if (Object.keys(rules).length === 0 && !hasSettings) {
1771
2062
  if (shouldMaterializeFileConfig) {
1772
2063
  return callback({
1773
2064
  ...options,
@@ -1779,7 +2070,7 @@ function withTemporaryConfig(options, callback) {
1779
2070
  }
1780
2071
  const enabledRules = enabledRuleNamesFromConfigs(...configs);
1781
2072
  const hasExplicitOffRules = Object.values(rules).some((value) => ruleConfigSeverity(value) === 0);
1782
- if (!shouldMaterializeFileConfig && !hasRuleOptions(rules) && !hasExplicitOffRules && !options.forceMaterializedConfig) {
2073
+ if (!shouldMaterializeFileConfig && !hasSettings && !hasRuleOptions(rules) && !hasExplicitOffRules && !options.forceMaterializedConfig) {
1783
2074
  return callback({
1784
2075
  ...options,
1785
2076
  config: shouldMaterializeFileConfig ? undefined : options.config,
@@ -1791,7 +2082,7 @@ function withTemporaryConfig(options, callback) {
1791
2082
  const tmp = mkdtempSync(join(tmpdir(), "utoo-lint-config-"));
1792
2083
  const configPath = join(tmp, "utlint.config.json");
1793
2084
  try {
1794
- writeFileSync(configPath, JSON.stringify({ rules }));
2085
+ writeFileSync(configPath, JSON.stringify({ rules, ...(hasSettings ? { settings } : {}) }));
1795
2086
  return callback({
1796
2087
  ...options,
1797
2088
  config: shouldMaterializeFileConfig ? configPath : options.config ?? configPath,
@@ -1810,11 +2101,7 @@ function enabledRuleNames(rules) {
1810
2101
  }
1811
2102
 
1812
2103
  function runtimeRulesFromConfigs(...configs) {
1813
- const rules = {};
1814
- for (const config of configs) {
1815
- Object.assign(rules, runtimeRulesFromConfig(config));
1816
- }
1817
- return rules;
2104
+ return mergeRuleConfigMaps(...configs.map(runtimeRulesFromConfig));
1818
2105
  }
1819
2106
 
1820
2107
  function materializedRulesFromConfigs(...configs) {
@@ -1826,10 +2113,10 @@ function runtimeRulesFromConfig(config) {
1826
2113
  return {};
1827
2114
  }
1828
2115
  if (Array.isArray(config)) {
1829
- return config.reduce((rules, entry) => ({
1830
- ...rules,
1831
- ...runtimeRulesFromConfig(entry)
1832
- }), {});
2116
+ return config.reduce(
2117
+ (rules, entry) => mergeRuleConfigMaps(rules, runtimeRulesFromConfig(entry)),
2118
+ {}
2119
+ );
1833
2120
  }
1834
2121
 
1835
2122
  const rules = {};
@@ -1839,6 +2126,26 @@ function runtimeRulesFromConfig(config) {
1839
2126
  return rules;
1840
2127
  }
1841
2128
 
2129
+ function mergeRuleConfigMaps(...ruleMaps) {
2130
+ const result = {};
2131
+ for (const rules of ruleMaps) {
2132
+ for (const [ruleId, ruleConfig] of Object.entries(rules ?? {})) {
2133
+ const previous = result[ruleId];
2134
+ if (previous !== undefined && isSeverityOnlyRuleConfig(ruleConfig) && Array.isArray(previous) && previous.length > 1) {
2135
+ const severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
2136
+ result[ruleId] = [severity, ...previous.slice(1)];
2137
+ } else {
2138
+ result[ruleId] = ruleConfig;
2139
+ }
2140
+ }
2141
+ }
2142
+ return result;
2143
+ }
2144
+
2145
+ function isSeverityOnlyRuleConfig(ruleConfig) {
2146
+ return !Array.isArray(ruleConfig) || ruleConfig.length === 1;
2147
+ }
2148
+
1842
2149
  function enabledRuleNamesFromConfigs(...configs) {
1843
2150
  const rules = new Set();
1844
2151
  for (const config of configs) {
@@ -1915,15 +2222,20 @@ class Linter {
1915
2222
  filename: filePath,
1916
2223
  config: calculatedConfig({ cwd: verifyOptions.cwd, noConfig: true, overrideConfig: config }, normalizedFilePath)
1917
2224
  });
2225
+ const nativeRuleFilter = applyDisableDirectives(
2226
+ (report.diagnostics ?? []).map((diagnostic) => diagnosticToESLintMessage(diagnostic, ruleSeverities)),
2227
+ sourceCode
2228
+ );
1918
2229
  const customRuleFilter = applyDisableDirectives(customRuleMessages, sourceCode);
1919
2230
  this.suppressedMessages = [
1920
2231
  ...(report.suppressedDiagnostics ?? []).map((diagnostic) => suppressedDiagnosticToESLintMessage(diagnostic, ruleSeverities)),
2232
+ ...nativeRuleFilter.suppressedMessages,
1921
2233
  ...customRuleFilter.suppressedMessages
1922
2234
  ];
1923
2235
  this.times = { passes: [] };
1924
2236
  this.fixPassCount = 0;
1925
2237
  return [
1926
- ...(report.diagnostics ?? []).map((diagnostic) => diagnosticToESLintMessage(diagnostic, ruleSeverities)),
2238
+ ...nativeRuleFilter.messages,
1927
2239
  ...customRuleFilter.messages
1928
2240
  ];
1929
2241
  }
@@ -2028,7 +2340,7 @@ function parserOptionsForConfig(config, filePath, cwd) {
2028
2340
 
2029
2341
  function customRuleEntriesForConfig(config, rules, filePath, cwd) {
2030
2342
  const entries = new Map();
2031
- for (const [ruleId, ruleConfig] of ruleConfigEntries(config, filePath, cwd)) {
2343
+ for (const [ruleId, ruleConfig] of Object.entries(rulesFromConfig(config, filePath, cwd))) {
2032
2344
  if (!rules.has(ruleId)) {
2033
2345
  continue;
2034
2346
  }
@@ -2085,19 +2397,6 @@ function matchingConfigEntries(config, filePath = undefined, cwd = undefined) {
2085
2397
  return [config];
2086
2398
  }
2087
2399
 
2088
- function ruleConfigEntries(config, filePath = undefined, cwd = undefined) {
2089
- if (Array.isArray(config)) {
2090
- return config.flatMap((item) => ruleConfigEntries(item, filePath, cwd));
2091
- }
2092
- if (!config || typeof config !== "object" || !config.rules || typeof config.rules !== "object") {
2093
- return [];
2094
- }
2095
- if (!configAppliesToFile(config, filePath, cwd)) {
2096
- return [];
2097
- }
2098
- return Object.entries(config.rules);
2099
- }
2100
-
2101
2400
  function configWithoutCustomRules(config, rules) {
2102
2401
  if (Array.isArray(config)) {
2103
2402
  return config.map((item) => configWithoutCustomRules(item, rules));
@@ -2227,6 +2526,7 @@ function applyDisableDirectives(messages, sourceCode) {
2227
2526
  }
2228
2527
  const directives = sourceCode.getDisableDirectives()
2229
2528
  .filter((directive) => directive.node?.loc?.start?.line)
2529
+ .filter((directive) => directive.type !== "eslint-disable-line" || directive.node.loc.start.line === directive.node.loc.end.line)
2230
2530
  .sort((left, right) => left.node.loc.start.line - right.node.loc.start.line);
2231
2531
  const utlintDirectives = sourceCode.getAllComments()
2232
2532
  .map((comment) => utlintDirectiveFromComment(comment, sourceCode))
@@ -2239,6 +2539,10 @@ function applyDisableDirectives(messages, sourceCode) {
2239
2539
  const kept = [];
2240
2540
  const suppressed = [];
2241
2541
  for (const message of messages) {
2542
+ if (!message.ruleId || message.ruleId === "parse" || message.ruleId === "io") {
2543
+ kept.push(message);
2544
+ continue;
2545
+ }
2242
2546
  const directive = utlintDirectiveForMessage(message, utlintDirectives) ?? disableDirectiveForMessage(message, directives);
2243
2547
  if (directive) {
2244
2548
  suppressed.push({
@@ -3398,10 +3702,7 @@ function mergeRuleTesterConfig(baseConfig, overrideConfig) {
3398
3702
  return {
3399
3703
  ...baseConfig,
3400
3704
  ...overrideConfig,
3401
- rules: {
3402
- ...(baseConfig.rules ?? {}),
3403
- ...(overrideConfig.rules ?? {})
3404
- }
3705
+ rules: mergeRuleConfigMaps(baseConfig.rules, overrideConfig.rules)
3405
3706
  };
3406
3707
  }
3407
3708
 
@@ -3822,12 +4123,38 @@ function reportToESLintResults(report, textOptions = {}) {
3822
4123
  }
3823
4124
 
3824
4125
  for (const result of byFile.values()) {
4126
+ applyCompatibilityDisableDirectives(result, textOptions);
3825
4127
  finalizeESLintResult(result);
3826
4128
  }
3827
4129
 
3828
4130
  return [...byFile.values()];
3829
4131
  }
3830
4132
 
4133
+ function applyCompatibilityDisableDirectives(result, textOptions) {
4134
+ if (result.messages.length === 0) {
4135
+ return;
4136
+ }
4137
+
4138
+ let source = result.source;
4139
+ if (typeof source !== "string" && textOptions.filePath === result.filePath) {
4140
+ source = textOptions.source;
4141
+ }
4142
+ if (typeof source !== "string") {
4143
+ try {
4144
+ source = readFileSync(result.filePath, "utf8");
4145
+ } catch {
4146
+ return;
4147
+ }
4148
+ }
4149
+ if (!source.includes("eslint-")) {
4150
+ return;
4151
+ }
4152
+
4153
+ const filtered = applyDisableDirectives(result.messages, createLinterSourceCode(source));
4154
+ result.messages = filtered.messages;
4155
+ result.suppressedMessages.push(...filtered.suppressedMessages);
4156
+ }
4157
+
3831
4158
  function normalizeReportDiagnostics(diagnostics, options = {}) {
3832
4159
  return (diagnostics ?? []).flatMap((diagnostic) => {
3833
4160
  if (!diagnostic?.ruleId) {
@@ -4055,7 +4382,7 @@ function collectGlobFiles(target, cwd, patterns, expression, files) {
4055
4382
 
4056
4383
  const child = join(target, entry.name);
4057
4384
  if (entry.isDirectory()) {
4058
- if (!pathIgnoredByPatterns(normalizeIgnoredPath(child, cwd), patterns)) {
4385
+ if (shouldTraverseDirectory(normalizeIgnoredPath(child, cwd), patterns)) {
4059
4386
  collectGlobFiles(child, cwd, patterns, expression, files);
4060
4387
  }
4061
4388
  continue;
@@ -4125,7 +4452,7 @@ function expandLintTarget(target, cwd, patterns) {
4125
4452
  if (stat.isFile()) {
4126
4453
  return isLintableFilePath(target) && !pathIgnoredByPatterns(normalizeIgnoredPath(target, cwd), patterns) ? [target] : [];
4127
4454
  }
4128
- if (!stat.isDirectory() || pathIgnoredByPatterns(normalizeIgnoredPath(target, cwd), patterns)) {
4455
+ if (!stat.isDirectory() || !shouldTraverseDirectory(normalizeIgnoredPath(target, cwd), patterns)) {
4129
4456
  return [];
4130
4457
  }
4131
4458
 
@@ -4143,7 +4470,7 @@ function collectLintableFiles(target, cwd, patterns, files) {
4143
4470
 
4144
4471
  const child = join(target, entry.name);
4145
4472
  if (entry.isDirectory()) {
4146
- if (!pathIgnoredByPatterns(normalizeIgnoredPath(child, cwd), patterns)) {
4473
+ if (shouldTraverseDirectory(normalizeIgnoredPath(child, cwd), patterns)) {
4147
4474
  collectLintableFiles(child, cwd, patterns, files);
4148
4475
  }
4149
4476
  continue;
@@ -4276,7 +4603,7 @@ function normalizeIgnoredPath(filePath, cwd) {
4276
4603
  }
4277
4604
 
4278
4605
  function normalizeIgnoredPattern(pattern) {
4279
- return normalizePath(pattern.replace(/^!/, "").replace(/^\//, "")).replace(/\/+$/, "");
4606
+ return normalizePath(pattern.replace(/^!/, "")).replace(/\/+$/, "");
4280
4607
  }
4281
4608
 
4282
4609
  function pathIgnoredByPatterns(target, patterns) {
@@ -4290,10 +4617,55 @@ function pathIgnoredByPatterns(target, patterns) {
4290
4617
  return ignored;
4291
4618
  }
4292
4619
 
4620
+ function shouldTraverseDirectory(target, patterns) {
4621
+ return !pathIgnoredByPatterns(target, patterns) || patterns.some(
4622
+ (pattern) => pattern.startsWith("!") && negatedPatternMayMatchDescendant(target, pattern)
4623
+ );
4624
+ }
4625
+
4626
+ function negatedPatternMayMatchDescendant(target, pattern) {
4627
+ pattern = normalizeIgnoredPattern(pattern);
4628
+ if (pattern.startsWith("/")) {
4629
+ pattern = pattern.slice(1);
4630
+ if (target.startsWith("/")) {
4631
+ target = target.slice(1);
4632
+ }
4633
+ }
4634
+ if (!pattern.includes("/")) {
4635
+ return true;
4636
+ }
4637
+
4638
+ const directoryPattern = pattern.slice(0, pattern.lastIndexOf("/"));
4639
+ if (hasGlobSyntax(directoryPattern)) {
4640
+ const directoryExpression = new RegExp(`^${globPatternRegExpSource(directoryPattern)}(?:/.*)?$`);
4641
+ if (directoryExpression.test(target)) {
4642
+ return true;
4643
+ }
4644
+ }
4645
+
4646
+ const globIndex = pattern.search(/[*?[{]/u);
4647
+ const staticPrefix = (globIndex === -1 ? pattern : pattern.slice(0, globIndex)).replace(/\/+$/u, "");
4648
+ return !staticPrefix ||
4649
+ staticPrefix === target ||
4650
+ staticPrefix.startsWith(`${target}/`) ||
4651
+ target.startsWith(`${staticPrefix}/`);
4652
+ }
4653
+
4293
4654
  function matchesIgnorePattern(target, pattern) {
4655
+ const anchored = pattern.startsWith("/");
4656
+ if (anchored) {
4657
+ pattern = pattern.slice(1);
4658
+ if (target.startsWith("/")) {
4659
+ target = target.slice(1);
4660
+ }
4661
+ }
4294
4662
  if (pattern.endsWith("/**")) {
4295
- const prefix = pattern.slice(0, -3);
4296
- return target === prefix || target.startsWith(`${prefix}/`) || target.includes(`/${prefix}/`);
4663
+ const directoryPattern = pattern.slice(0, -3);
4664
+ if (hasGlobSyntax(directoryPattern)) {
4665
+ const expression = new RegExp(`^${globPatternRegExpSource(directoryPattern)}(?:/.*)?$`);
4666
+ return expression.test(target);
4667
+ }
4668
+ return target === directoryPattern || target.startsWith(`${directoryPattern}/`);
4297
4669
  }
4298
4670
  if (pattern.startsWith("**/")) {
4299
4671
  const suffix = pattern.slice(3);
@@ -4302,10 +4674,14 @@ function matchesIgnorePattern(target, pattern) {
4302
4674
  }
4303
4675
  }
4304
4676
  if (!hasGlobSyntax(pattern)) {
4677
+ if (anchored || pattern.includes("/")) {
4678
+ return target === pattern || target.startsWith(`${pattern}/`);
4679
+ }
4305
4680
  return target === pattern || target.endsWith(`/${pattern}`) || target.startsWith(`${pattern}/`);
4306
4681
  }
4307
4682
 
4308
- const expression = new RegExp(`(^|/)${globPatternRegExpSource(pattern)}$`);
4683
+ const prefix = anchored || pattern.includes("/") ? "^" : "(^|/)";
4684
+ const expression = new RegExp(`${prefix}${globPatternRegExpSource(pattern)}$`);
4309
4685
  return expression.test(target);
4310
4686
  }
4311
4687
 
@@ -4414,6 +4790,9 @@ function ruleMetaForRuleId(ruleId) {
4414
4790
  if (FIXABLE_BUILTIN_RULE_IDS.has(ruleId)) {
4415
4791
  meta.fixable = "code";
4416
4792
  }
4793
+ if (SUGGESTION_BUILTIN_RULE_IDS.has(ruleId)) {
4794
+ meta.hasSuggestions = true;
4795
+ }
4417
4796
  return meta;
4418
4797
  }
4419
4798
 
@@ -4504,16 +4883,33 @@ function diagnosticToESLintMessage(diagnostic, ruleSeverities) {
4504
4883
  if (fixes.length > 0) {
4505
4884
  message.fix = fixes.length === 1 ? fixes[0] : fixes;
4506
4885
  }
4886
+ const suggestions = (diagnostic.suggestions ?? []).map((suggestion) => {
4887
+ const suggestionFixes = (suggestion.fix ?? []).map((fix) => ({
4888
+ range: fix.range,
4889
+ text: fix.text
4890
+ }));
4891
+ return {
4892
+ desc: suggestion.desc,
4893
+ ...(suggestionFixes.length === 0 ? {} : {
4894
+ fix: suggestionFixes.length === 1 ? suggestionFixes[0] : suggestionFixes
4895
+ })
4896
+ };
4897
+ });
4898
+ if (suggestions.length > 0) {
4899
+ message.suggestions = suggestions;
4900
+ }
4507
4901
  return message;
4508
4902
  }
4509
4903
 
4510
4904
  function suppressedDiagnosticToESLintMessage(diagnostic, ruleSeverities) {
4511
4905
  return {
4512
4906
  ...diagnosticToESLintMessage(diagnostic, ruleSeverities),
4513
- suppressions: [{
4514
- kind: diagnostic.suppression?.kind ?? "directive",
4515
- justification: diagnostic.suppression?.justification ?? ""
4516
- }]
4907
+ suppressions: diagnostic.suppressions?.length > 0
4908
+ ? diagnostic.suppressions
4909
+ : [{
4910
+ kind: diagnostic.suppression?.kind ?? "directive",
4911
+ justification: diagnostic.suppression?.justification ?? ""
4912
+ }]
4517
4913
  };
4518
4914
  }
4519
4915