@utoo/lint 0.3.6 → 0.3.7

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/README.md CHANGED
@@ -248,6 +248,26 @@ Raw reports include active diagnostics, suppressed diagnostics, fixed outputs,
248
248
  and an exit code. The compatibility layer also exports `Linter`, `CLIEngine`,
249
249
  `RuleTester`, and `SourceCode` APIs.
250
250
 
251
+ Native JSON diagnostics and ESLint-compatible messages include one-based `line`,
252
+ `column`, `endLine`, and `endColumn` positions for source spans. Columns count
253
+ UTF-16 code units, and the end position is exclusive. Suppressed diagnostics keep
254
+ the same range. I/O errors without a source span omit the end positions; messages
255
+ from older native binaries may also omit them.
256
+
257
+ `ESLint` and `CLIEngine` also discover classic `.eslintrc.*` files (or
258
+ `eslintConfig` in `package.json`) when no native config applies. Legacy
259
+ `extends`, directory cascades, `root`, `overrides`, and ignore patterns are
260
+ resolved per source filename. Precedence is `baseConfig`, project config,
261
+ then `overrideConfig`; `useEslintrc: false` disables discovery. Native config
262
+ files still take priority. The lower-level `lintFiles`, `lintText`, and native
263
+ CLI keep their native-only discovery behavior.
264
+
265
+ Legacy configuration resolution uses `@eslint/eslintrc`. If the project has
266
+ ESLint 8 installed, its configuration resolver is used instead to support
267
+ shared configs that patch ESLint's module resolution. Neither path uses ESLint
268
+ to parse or lint source files: rules still run in utoo-lint. Enabled rules that
269
+ utoo-lint does not support remain errors, not silently skipped checks.
270
+
251
271
  The Node wrapper captures up to 64 MiB from each native stdout and stderr
252
272
  stream by default. Programmatic `run`, `runCli`, and `runFishlint` calls can set
253
273
  `maxBuffer` to another positive byte count. Set `UTOO_LINT_MAX_BUFFER` to apply
package/index.cjs CHANGED
@@ -5,6 +5,7 @@ const { tmpdir } = require("node:os");
5
5
  const { dirname, extname, isAbsolute, join, relative, resolve: resolvePath } = require("node:path");
6
6
 
7
7
  const { platformPackageName, resolveBinary } = require("./lib/binary.cjs");
8
+ const { createLegacyConfigResolver } = require("./lib/legacy-config.cjs");
8
9
  const {
9
10
  findConfigPath: findConfigPathFromDirectory,
10
11
  readConfig
@@ -15,6 +16,8 @@ const version = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8")
15
16
  const LINTABLE_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]);
16
17
  const MAX_AUTOFIX_PASSES = 10;
17
18
  const CONFIG_DISCOVERY_CACHE = Symbol("configDiscoveryCache");
19
+ const LEGACY_CONFIG_ENABLED = Symbol("legacyConfigEnabled");
20
+ const LEGACY_CONFIG_RESOLVER = Symbol("legacyConfigResolver");
18
21
  const JS_KEYWORDS = new Set([
19
22
  "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete",
20
23
  "do", "else", "export", "extends", "finally", "for", "function", "if", "import", "in",
@@ -415,6 +418,7 @@ const BUILTIN_RULE_IDS = [
415
418
  "@typescript-eslint/no-duplicate-enum-values",
416
419
  "@typescript-eslint/no-extra-semi",
417
420
  "@typescript-eslint/no-extra-non-null-assertion",
421
+ "@typescript-eslint/no-explicit-any",
418
422
  "@typescript-eslint/no-inferrable-types",
419
423
  "@typescript-eslint/no-invalid-void-type",
420
424
  "@typescript-eslint/no-loop-func",
@@ -472,6 +476,7 @@ const NATIVE_ONLY_RULE_IDS = [
472
476
  ];
473
477
  const NATIVE_RULE_IDS = [...BUILTIN_RULE_IDS, ...NATIVE_ONLY_RULE_IDS];
474
478
  const FIXABLE_BUILTIN_RULE_IDS = new Set([
479
+ "@typescript-eslint/no-explicit-any",
475
480
  "jest/no-deprecated-functions",
476
481
  "jest/no-jasmine-globals",
477
482
  "no-extra-semi",
@@ -479,6 +484,7 @@ const FIXABLE_BUILTIN_RULE_IDS = new Set([
479
484
  "unused-imports/no-unused-imports"
480
485
  ]);
481
486
  const SUGGESTION_BUILTIN_RULE_IDS = new Set([
487
+ "@typescript-eslint/no-explicit-any",
482
488
  "jest/no-focused-tests"
483
489
  ]);
484
490
  const BUILTIN_RULES = new Map(BUILTIN_RULE_IDS.map((ruleId) => [ruleId, createBuiltinRule(ruleId)]));
@@ -579,6 +585,9 @@ class UtooLint {
579
585
 
580
586
  async findConfigFile(filePath) {
581
587
  const options = withConfigCache(eslintConstructorOptions(this.options));
588
+ if (options.legacyConfigFile) {
589
+ return resolvePath(options.cwd ?? process.cwd(), options.legacyConfigFile);
590
+ }
582
591
  if (options.noConfig) {
583
592
  return undefined;
584
593
  }
@@ -1207,6 +1216,9 @@ function lintFiles(paths, options = {}) {
1207
1216
  if (usesDefaultTargets) {
1208
1217
  lintPaths = filterDefaultConfigLintPaths(lintPaths, options);
1209
1218
  }
1219
+ if (options[LEGACY_CONFIG_ENABLED] && (!options.noConfig || options.legacyConfigFile) && !options.deferDiagnosticConfigFiltering) {
1220
+ lintPaths = expandNativeConfigRunPaths(lintPaths, options).filter((filePath) => !isPathIgnored(filePath, options));
1221
+ }
1210
1222
  if (lintPaths.length === 0) {
1211
1223
  return { files: 0, filePaths: [], diagnostics: ignoredDiagnostics, suppressedDiagnostics: [], exitCode: exitCodeForDiagnostics(ignoredDiagnostics) };
1212
1224
  }
@@ -1230,6 +1242,10 @@ function lintFiles(paths, options = {}) {
1230
1242
  }
1231
1243
 
1232
1244
  function nativeFlatConfigOptions(options) {
1245
+ if (defersLegacyIgnores(options)) {
1246
+ // A legacy config may cascade from a nested directory even with baseConfig.
1247
+ return undefined;
1248
+ }
1233
1249
  if (options.rules || options.extraArgs?.some((arg) => arg.startsWith("--") && arg !== "--fix" && arg !== "--fix-dry-run")) {
1234
1250
  return undefined;
1235
1251
  }
@@ -1301,7 +1317,7 @@ function nativeSerializableConfig(config) {
1301
1317
 
1302
1318
  function expandNativeConfigRunPaths(paths, options) {
1303
1319
  const cwd = options.cwd ?? process.cwd();
1304
- const patterns = ignoreDisabled(options) ? [] : ignorePatternsForOptions(options, cwd);
1320
+ const patterns = ignoreDisabled(options) || defersLegacyIgnores(options) ? [] : ignorePatternsForOptions(options, cwd);
1305
1321
  return paths.flatMap((path) => expandLintTarget(path, cwd, patterns) ?? [path]);
1306
1322
  }
1307
1323
 
@@ -1359,7 +1375,7 @@ function finalizeLintReport(report, ignoredDiagnostics, options) {
1359
1375
  }
1360
1376
 
1361
1377
  function nativeConfigRunsForFiles(paths, options) {
1362
- if (options.noConfig && !options.baseConfig && !options.overrideConfig) {
1378
+ if (options.noConfig && !options.legacyConfigFile && !options.baseConfig && !options.overrideConfig) {
1363
1379
  return undefined;
1364
1380
  }
1365
1381
 
@@ -1370,7 +1386,7 @@ function nativeConfigRunsForFiles(paths, options) {
1370
1386
  ? options.filePath ?? options.filename ?? filePath
1371
1387
  : filePath;
1372
1388
  const fileConfigPath = options.noConfig ? undefined : configPathForFile(options, matchPath);
1373
- const configured = Boolean(options.baseConfig || options.overrideConfig || fileConfigPath);
1389
+ const configured = Boolean(options.baseConfig || options.overrideConfig || fileConfigPath || legacyConfigForFile(options, matchPath));
1374
1390
  hasConfigSource ||= configured;
1375
1391
  const calculated = configured ? calculatedConfig({ ...options, rules: undefined }, matchPath) : {};
1376
1392
  let rules = calculated.rules;
@@ -1473,7 +1489,9 @@ function isPureRuleSeverity(config) {
1473
1489
  function allDisabledNativeRules(rules) {
1474
1490
  return {
1475
1491
  ...Object.fromEntries(NATIVE_RULE_IDS.map((rule) => [rule, "off"])),
1476
- ...rules
1492
+ ...Object.fromEntries(Object.entries(rules ?? {}).filter(
1493
+ ([rule, value]) => NATIVE_RULE_IDS.includes(rule) || ruleConfigSeverity(value) > 0
1494
+ ))
1477
1495
  };
1478
1496
  }
1479
1497
 
@@ -1657,12 +1675,14 @@ function withConfigCache(options) {
1657
1675
 
1658
1676
  function eslintConstructorOptions(options) {
1659
1677
  const mapped = {
1678
+ [LEGACY_CONFIG_ENABLED]: true,
1660
1679
  cwd: options.cwd,
1661
1680
  threads: options.threads,
1662
1681
  binary: options.binary,
1663
1682
  env: options.env,
1664
1683
  extraArgs: options.extraArgs,
1665
- config: options.config ?? options.configFile,
1684
+ config: options.config,
1685
+ legacyConfigFile: options.config == null ? options.configFile : undefined,
1666
1686
  ignorePath: options.ignorePath,
1667
1687
  ignorePatterns: options.ignorePatterns,
1668
1688
  noIgnore: options.noIgnore ?? options.ignore === false,
@@ -1679,14 +1699,14 @@ function eslintConstructorOptions(options) {
1679
1699
  }
1680
1700
  if (typeof options.overrideConfigFile === "string") {
1681
1701
  mapped.config = options.overrideConfigFile;
1702
+ mapped.legacyConfigFile = undefined;
1703
+ }
1704
+ if (mapped.config && /(?:^|[/\\\\])\.eslintrc(?:\.(?:js|cjs|json|yaml|yml))?$/.test(mapped.config)) {
1705
+ mapped.legacyConfigFile = mapped.config;
1706
+ mapped.config = undefined;
1682
1707
  }
1683
1708
  if (options.overrideConfig) {
1684
1709
  mapped.overrideConfig = Array.isArray(options.overrideConfig) ? options.overrideConfig : { ...options.overrideConfig };
1685
- if (!Array.isArray(mapped.overrideConfig) && !mapped.overrideConfig.rules && options.baseConfig?.rules) {
1686
- mapped.overrideConfig.rules = options.baseConfig.rules;
1687
- }
1688
- } else if (options.baseConfig?.rules) {
1689
- mapped.overrideConfig = { rules: options.baseConfig.rules };
1690
1710
  }
1691
1711
  return mapped;
1692
1712
  }
@@ -1818,13 +1838,13 @@ function rulesFromConfig(config, filePath, cwd) {
1818
1838
  }
1819
1839
 
1820
1840
  function rulesFromFileConfig(options, filePath) {
1821
- if (options.noConfig) {
1841
+ if (options.noConfig && !options.legacyConfigFile) {
1822
1842
  return {};
1823
1843
  }
1824
1844
 
1825
1845
  const configPath = filePath ? configPathForFile(options, filePath) : configPathForOptions(options);
1826
1846
  if (!configPath) {
1827
- return {};
1847
+ return legacyConfigForFile(options, filePath)?.rules ?? {};
1828
1848
  }
1829
1849
 
1830
1850
  const config = readConfig(configPath, options.cwd, options.configCache);
@@ -1832,19 +1852,40 @@ function rulesFromFileConfig(options, filePath) {
1832
1852
  }
1833
1853
 
1834
1854
  function configDataFromFileConfig(options, filePath) {
1835
- if (options.noConfig) {
1855
+ if (options.noConfig && !options.legacyConfigFile) {
1836
1856
  return {};
1837
1857
  }
1838
1858
 
1839
1859
  const configPath = filePath ? configPathForFile(options, filePath) : configPathForOptions(options);
1840
1860
  if (!configPath) {
1841
- return {};
1861
+ return configDataFromConfig(legacyConfigForFile(options, filePath), filePath, options.cwd);
1842
1862
  }
1843
1863
 
1844
1864
  const config = readConfig(configPath, options.cwd, options.configCache);
1845
1865
  return configDataFromConfig(config, filePath, dirname(configPath));
1846
1866
  }
1847
1867
 
1868
+ function legacyConfigForFile(options, filePath) {
1869
+ if (!options[LEGACY_CONFIG_ENABLED] || (options.noConfig && !options.legacyConfigFile)) return undefined;
1870
+ if (filePath ? configPathForFile(options, filePath) : configPathForOptions(options)) return undefined;
1871
+ const cwd = resolvePath(options.cwd ?? process.cwd());
1872
+ let resolver = options.configCache?.get(LEGACY_CONFIG_RESOLVER);
1873
+ if (!resolver) {
1874
+ resolver = createLegacyConfigResolver(cwd, {
1875
+ configFile: options.legacyConfigFile,
1876
+ useEslintrc: !options.noConfig,
1877
+ ignorePath: options.ignorePath,
1878
+ ignorePatterns: [
1879
+ ...ignorePatternsFromConfig(options.baseConfig),
1880
+ ...normalizeIgnorePatterns(options.ignorePatterns),
1881
+ ...ignorePatternsFromConfig(options.overrideConfig)
1882
+ ]
1883
+ });
1884
+ options.configCache?.set(LEGACY_CONFIG_RESOLVER, resolver);
1885
+ }
1886
+ return resolver(normalizeESLintFilePath(filePath ?? options.filePath ?? options.filename ?? "__placeholder__.js", cwd));
1887
+ }
1888
+
1848
1889
  function lintTargetsForInput(paths, options = {}) {
1849
1890
  if (paths == null) {
1850
1891
  return defaultLintTargets(options);
@@ -1988,6 +2029,7 @@ function normalizeConfigPatterns(patterns) {
1988
2029
  }
1989
2030
 
1990
2031
  function configPathForOptions(options) {
2032
+ if (options.legacyConfigFile) return undefined;
1991
2033
  if (options.config) {
1992
2034
  return resolvePath(options.cwd ?? process.cwd(), options.config);
1993
2035
  }
@@ -1997,6 +2039,7 @@ function configPathForOptions(options) {
1997
2039
  }
1998
2040
 
1999
2041
  function configPathForFile(options, filePath) {
2042
+ if (options.legacyConfigFile) return undefined;
2000
2043
  if (options.noConfig) {
2001
2044
  return undefined;
2002
2045
  }
@@ -4286,7 +4329,7 @@ function isLintableFilePath(filePath) {
4286
4329
  function filteredLintPaths(paths, options = {}) {
4287
4330
  const values = normalizeStringArray(Array.isArray(paths) ? paths : [paths], "paths");
4288
4331
  const cwd = options.cwd ?? process.cwd();
4289
- const patterns = ignoreDisabled(options) ? [] : ignorePatternsForOptions(options, cwd);
4332
+ const patterns = ignoreDisabled(options) || defersLegacyIgnores(options) ? [] : ignorePatternsForOptions(options, cwd);
4290
4333
  if (patterns.length === 0 && options.errorOnUnmatchedPattern !== false && !values.some(hasGlobMagic)) {
4291
4334
  return values;
4292
4335
  }
@@ -4405,7 +4448,7 @@ function ignoredLintPathDiagnostics(paths, options = {}) {
4405
4448
 
4406
4449
  const cwd = options.cwd ?? process.cwd();
4407
4450
  const patterns = ignorePatternsForOptions(options, cwd);
4408
- if (patterns.length === 0) {
4451
+ if (patterns.length === 0 && !options[LEGACY_CONFIG_ENABLED]) {
4409
4452
  return [];
4410
4453
  }
4411
4454
 
@@ -4422,7 +4465,7 @@ function ignoredLintPathDiagnostics(paths, options = {}) {
4422
4465
  continue;
4423
4466
  }
4424
4467
 
4425
- if (pathIgnoredByPatterns(normalizeIgnoredPath(target, cwd), patterns)) {
4468
+ if (isPathIgnored(filePath, options)) {
4426
4469
  diagnostics.push(ignoredFileDiagnostic(filePath));
4427
4470
  }
4428
4471
  }
@@ -4494,11 +4537,19 @@ function isPathIgnored(filePath, options = {}) {
4494
4537
  }
4495
4538
 
4496
4539
  const cwd = options.cwd ?? process.cwd();
4540
+ const legacyConfig = legacyConfigForFile(options, filePath);
4541
+ if (legacyConfig) {
4542
+ return Boolean(legacyConfig.isIgnored?.(normalizeESLintFilePath(filePath, cwd)));
4543
+ }
4497
4544
  const normalized = normalizeIgnoredPath(filePath, cwd);
4498
4545
  const patterns = ignorePatternsForOptions(options, cwd, filePath);
4499
4546
  return pathIgnoredByPatterns(normalized, patterns);
4500
4547
  }
4501
4548
 
4549
+ function defersLegacyIgnores(options) {
4550
+ return options[LEGACY_CONFIG_ENABLED] && (options.legacyConfigFile || (!options.noConfig && !configPathForOptions(options)));
4551
+ }
4552
+
4502
4553
  function ignoreDisabled(options = {}) {
4503
4554
  return options.noIgnore || options.ignore === false;
4504
4555
  }
@@ -4876,6 +4927,12 @@ function diagnosticToESLintMessage(diagnostic, ruleSeverities) {
4876
4927
  column: diagnostic.column,
4877
4928
  nodeType: null
4878
4929
  };
4930
+ if (diagnostic.endLine != null) {
4931
+ message.endLine = diagnostic.endLine;
4932
+ }
4933
+ if (diagnostic.endColumn != null) {
4934
+ message.endColumn = diagnostic.endColumn;
4935
+ }
4879
4936
  const fixes = (diagnostic.fixes ?? []).map((fix) => ({
4880
4937
  range: fix.range,
4881
4938
  text: fix.text
package/index.js CHANGED
@@ -6,6 +6,7 @@ import { dirname, extname, isAbsolute, join, relative, resolve as resolvePath }
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
8
  import { resolveBinary } from "./lib/binary.js";
9
+ import { createLegacyConfigResolver } from "./lib/legacy-config.cjs";
9
10
  import {
10
11
  findConfigPath as findConfigPathFromDirectory,
11
12
  readConfig
@@ -18,6 +19,8 @@ export const version = JSON.parse(readFileSync(new URL("./package.json", import.
18
19
  const LINTABLE_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]);
19
20
  const MAX_AUTOFIX_PASSES = 10;
20
21
  const CONFIG_DISCOVERY_CACHE = Symbol("configDiscoveryCache");
22
+ const LEGACY_CONFIG_ENABLED = Symbol("legacyConfigEnabled");
23
+ const LEGACY_CONFIG_RESOLVER = Symbol("legacyConfigResolver");
21
24
  const JS_KEYWORDS = new Set([
22
25
  "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete",
23
26
  "do", "else", "export", "extends", "finally", "for", "function", "if", "import", "in",
@@ -418,6 +421,7 @@ const BUILTIN_RULE_IDS = [
418
421
  "@typescript-eslint/no-duplicate-enum-values",
419
422
  "@typescript-eslint/no-extra-semi",
420
423
  "@typescript-eslint/no-extra-non-null-assertion",
424
+ "@typescript-eslint/no-explicit-any",
421
425
  "@typescript-eslint/no-inferrable-types",
422
426
  "@typescript-eslint/no-invalid-void-type",
423
427
  "@typescript-eslint/no-loop-func",
@@ -475,6 +479,7 @@ const NATIVE_ONLY_RULE_IDS = [
475
479
  ];
476
480
  const NATIVE_RULE_IDS = [...BUILTIN_RULE_IDS, ...NATIVE_ONLY_RULE_IDS];
477
481
  const FIXABLE_BUILTIN_RULE_IDS = new Set([
482
+ "@typescript-eslint/no-explicit-any",
478
483
  "jest/no-deprecated-functions",
479
484
  "jest/no-jasmine-globals",
480
485
  "no-extra-semi",
@@ -482,6 +487,7 @@ const FIXABLE_BUILTIN_RULE_IDS = new Set([
482
487
  "unused-imports/no-unused-imports"
483
488
  ]);
484
489
  const SUGGESTION_BUILTIN_RULE_IDS = new Set([
490
+ "@typescript-eslint/no-explicit-any",
485
491
  "jest/no-focused-tests"
486
492
  ]);
487
493
  const BUILTIN_RULES = new Map(BUILTIN_RULE_IDS.map((ruleId) => [ruleId, createBuiltinRule(ruleId)]));
@@ -582,6 +588,9 @@ export class UtooLint {
582
588
 
583
589
  async findConfigFile(filePath) {
584
590
  const options = withConfigCache(eslintConstructorOptions(this.options));
591
+ if (options.legacyConfigFile) {
592
+ return resolvePath(options.cwd ?? process.cwd(), options.legacyConfigFile);
593
+ }
585
594
  if (options.noConfig) {
586
595
  return undefined;
587
596
  }
@@ -3106,6 +3115,9 @@ export function lintFiles(paths, options = {}) {
3106
3115
  if (usesDefaultTargets) {
3107
3116
  lintPaths = filterDefaultConfigLintPaths(lintPaths, options);
3108
3117
  }
3118
+ if (options[LEGACY_CONFIG_ENABLED] && (!options.noConfig || options.legacyConfigFile) && !options.deferDiagnosticConfigFiltering) {
3119
+ lintPaths = expandNativeConfigRunPaths(lintPaths, options).filter((filePath) => !isPathIgnored(filePath, options));
3120
+ }
3109
3121
  if (lintPaths.length === 0) {
3110
3122
  return { files: 0, filePaths: [], diagnostics: ignoredDiagnostics, suppressedDiagnostics: [], exitCode: exitCodeForDiagnostics(ignoredDiagnostics) };
3111
3123
  }
@@ -3129,6 +3141,10 @@ export function lintFiles(paths, options = {}) {
3129
3141
  }
3130
3142
 
3131
3143
  function nativeFlatConfigOptions(options) {
3144
+ if (defersLegacyIgnores(options)) {
3145
+ // A legacy config may cascade from a nested directory even with baseConfig.
3146
+ return undefined;
3147
+ }
3132
3148
  if (options.rules || options.extraArgs?.some((arg) => arg.startsWith("--") && arg !== "--fix" && arg !== "--fix-dry-run")) {
3133
3149
  return undefined;
3134
3150
  }
@@ -3200,7 +3216,7 @@ function nativeSerializableConfig(config) {
3200
3216
 
3201
3217
  function expandNativeConfigRunPaths(paths, options) {
3202
3218
  const cwd = options.cwd ?? process.cwd();
3203
- const patterns = ignoreDisabled(options) ? [] : ignorePatternsForOptions(options, cwd);
3219
+ const patterns = ignoreDisabled(options) || defersLegacyIgnores(options) ? [] : ignorePatternsForOptions(options, cwd);
3204
3220
  return paths.flatMap((path) => expandLintTarget(path, cwd, patterns) ?? [path]);
3205
3221
  }
3206
3222
 
@@ -3258,7 +3274,7 @@ function finalizeLintReport(report, ignoredDiagnostics, options) {
3258
3274
  }
3259
3275
 
3260
3276
  function nativeConfigRunsForFiles(paths, options) {
3261
- if (options.noConfig && !options.baseConfig && !options.overrideConfig) {
3277
+ if (options.noConfig && !options.legacyConfigFile && !options.baseConfig && !options.overrideConfig) {
3262
3278
  return undefined;
3263
3279
  }
3264
3280
 
@@ -3269,7 +3285,7 @@ function nativeConfigRunsForFiles(paths, options) {
3269
3285
  ? options.filePath ?? options.filename ?? filePath
3270
3286
  : filePath;
3271
3287
  const fileConfigPath = options.noConfig ? undefined : configPathForFile(options, matchPath);
3272
- const configured = Boolean(options.baseConfig || options.overrideConfig || fileConfigPath);
3288
+ const configured = Boolean(options.baseConfig || options.overrideConfig || fileConfigPath || legacyConfigForFile(options, matchPath));
3273
3289
  hasConfigSource ||= configured;
3274
3290
  const calculated = configured ? calculatedConfig({ ...options, rules: undefined }, matchPath) : {};
3275
3291
  let rules = calculated.rules;
@@ -3372,7 +3388,9 @@ function isPureRuleSeverity(config) {
3372
3388
  function allDisabledNativeRules(rules) {
3373
3389
  return {
3374
3390
  ...Object.fromEntries(NATIVE_RULE_IDS.map((rule) => [rule, "off"])),
3375
- ...rules
3391
+ ...Object.fromEntries(Object.entries(rules ?? {}).filter(
3392
+ ([rule, value]) => NATIVE_RULE_IDS.includes(rule) || ruleConfigSeverity(value) > 0
3393
+ ))
3376
3394
  };
3377
3395
  }
3378
3396
 
@@ -3556,12 +3574,14 @@ function withConfigCache(options) {
3556
3574
 
3557
3575
  function eslintConstructorOptions(options) {
3558
3576
  const mapped = {
3577
+ [LEGACY_CONFIG_ENABLED]: true,
3559
3578
  cwd: options.cwd,
3560
3579
  threads: options.threads,
3561
3580
  binary: options.binary,
3562
3581
  env: options.env,
3563
3582
  extraArgs: options.extraArgs,
3564
- config: options.config ?? options.configFile,
3583
+ config: options.config,
3584
+ legacyConfigFile: options.config == null ? options.configFile : undefined,
3565
3585
  ignorePath: options.ignorePath,
3566
3586
  ignorePatterns: options.ignorePatterns,
3567
3587
  noIgnore: options.noIgnore ?? options.ignore === false,
@@ -3578,14 +3598,14 @@ function eslintConstructorOptions(options) {
3578
3598
  }
3579
3599
  if (typeof options.overrideConfigFile === "string") {
3580
3600
  mapped.config = options.overrideConfigFile;
3601
+ mapped.legacyConfigFile = undefined;
3602
+ }
3603
+ if (mapped.config && /(?:^|[/\\\\])\.eslintrc(?:\.(?:js|cjs|json|yaml|yml))?$/.test(mapped.config)) {
3604
+ mapped.legacyConfigFile = mapped.config;
3605
+ mapped.config = undefined;
3581
3606
  }
3582
3607
  if (options.overrideConfig) {
3583
3608
  mapped.overrideConfig = Array.isArray(options.overrideConfig) ? options.overrideConfig : { ...options.overrideConfig };
3584
- if (!Array.isArray(mapped.overrideConfig) && !mapped.overrideConfig.rules && options.baseConfig?.rules) {
3585
- mapped.overrideConfig.rules = options.baseConfig.rules;
3586
- }
3587
- } else if (options.baseConfig?.rules) {
3588
- mapped.overrideConfig = { rules: options.baseConfig.rules };
3589
3609
  }
3590
3610
  return mapped;
3591
3611
  }
@@ -3717,13 +3737,13 @@ function rulesFromConfig(config, filePath, cwd) {
3717
3737
  }
3718
3738
 
3719
3739
  function rulesFromFileConfig(options, filePath) {
3720
- if (options.noConfig) {
3740
+ if (options.noConfig && !options.legacyConfigFile) {
3721
3741
  return {};
3722
3742
  }
3723
3743
 
3724
3744
  const configPath = filePath ? configPathForFile(options, filePath) : configPathForOptions(options);
3725
3745
  if (!configPath) {
3726
- return {};
3746
+ return legacyConfigForFile(options, filePath)?.rules ?? {};
3727
3747
  }
3728
3748
 
3729
3749
  const config = readConfig(configPath, options.cwd, options.configCache);
@@ -3731,19 +3751,40 @@ function rulesFromFileConfig(options, filePath) {
3731
3751
  }
3732
3752
 
3733
3753
  function configDataFromFileConfig(options, filePath) {
3734
- if (options.noConfig) {
3754
+ if (options.noConfig && !options.legacyConfigFile) {
3735
3755
  return {};
3736
3756
  }
3737
3757
 
3738
3758
  const configPath = filePath ? configPathForFile(options, filePath) : configPathForOptions(options);
3739
3759
  if (!configPath) {
3740
- return {};
3760
+ return configDataFromConfig(legacyConfigForFile(options, filePath), filePath, options.cwd);
3741
3761
  }
3742
3762
 
3743
3763
  const config = readConfig(configPath, options.cwd, options.configCache);
3744
3764
  return configDataFromConfig(config, filePath, dirname(configPath));
3745
3765
  }
3746
3766
 
3767
+ function legacyConfigForFile(options, filePath) {
3768
+ if (!options[LEGACY_CONFIG_ENABLED] || (options.noConfig && !options.legacyConfigFile)) return undefined;
3769
+ if (filePath ? configPathForFile(options, filePath) : configPathForOptions(options)) return undefined;
3770
+ const cwd = resolvePath(options.cwd ?? process.cwd());
3771
+ let resolver = options.configCache?.get(LEGACY_CONFIG_RESOLVER);
3772
+ if (!resolver) {
3773
+ resolver = createLegacyConfigResolver(cwd, {
3774
+ configFile: options.legacyConfigFile,
3775
+ useEslintrc: !options.noConfig,
3776
+ ignorePath: options.ignorePath,
3777
+ ignorePatterns: [
3778
+ ...ignorePatternsFromConfig(options.baseConfig),
3779
+ ...normalizeIgnorePatterns(options.ignorePatterns),
3780
+ ...ignorePatternsFromConfig(options.overrideConfig)
3781
+ ]
3782
+ });
3783
+ options.configCache?.set(LEGACY_CONFIG_RESOLVER, resolver);
3784
+ }
3785
+ return resolver(normalizeESLintFilePath(filePath ?? options.filePath ?? options.filename ?? "__placeholder__.js", cwd));
3786
+ }
3787
+
3747
3788
  function lintTargetsForInput(paths, options = {}) {
3748
3789
  if (paths == null) {
3749
3790
  return defaultLintTargets(options);
@@ -3887,6 +3928,7 @@ function normalizeConfigPatterns(patterns) {
3887
3928
  }
3888
3929
 
3889
3930
  function configPathForOptions(options) {
3931
+ if (options.legacyConfigFile) return undefined;
3890
3932
  if (options.config) {
3891
3933
  return resolvePath(options.cwd ?? process.cwd(), options.config);
3892
3934
  }
@@ -3896,6 +3938,7 @@ function configPathForOptions(options) {
3896
3938
  }
3897
3939
 
3898
3940
  function configPathForFile(options, filePath) {
3941
+ if (options.legacyConfigFile) return undefined;
3899
3942
  if (options.noConfig) {
3900
3943
  return undefined;
3901
3944
  }
@@ -4289,7 +4332,7 @@ function isLintableFilePath(filePath) {
4289
4332
  function filteredLintPaths(paths, options = {}) {
4290
4333
  const values = normalizeStringArray(Array.isArray(paths) ? paths : [paths], "paths");
4291
4334
  const cwd = options.cwd ?? process.cwd();
4292
- const patterns = ignoreDisabled(options) ? [] : ignorePatternsForOptions(options, cwd);
4335
+ const patterns = ignoreDisabled(options) || defersLegacyIgnores(options) ? [] : ignorePatternsForOptions(options, cwd);
4293
4336
  if (patterns.length === 0 && options.errorOnUnmatchedPattern !== false && !values.some(hasGlobMagic)) {
4294
4337
  return values;
4295
4338
  }
@@ -4408,7 +4451,7 @@ function ignoredLintPathDiagnostics(paths, options = {}) {
4408
4451
 
4409
4452
  const cwd = options.cwd ?? process.cwd();
4410
4453
  const patterns = ignorePatternsForOptions(options, cwd);
4411
- if (patterns.length === 0) {
4454
+ if (patterns.length === 0 && !options[LEGACY_CONFIG_ENABLED]) {
4412
4455
  return [];
4413
4456
  }
4414
4457
 
@@ -4425,7 +4468,7 @@ function ignoredLintPathDiagnostics(paths, options = {}) {
4425
4468
  continue;
4426
4469
  }
4427
4470
 
4428
- if (pathIgnoredByPatterns(normalizeIgnoredPath(target, cwd), patterns)) {
4471
+ if (isPathIgnored(filePath, options)) {
4429
4472
  diagnostics.push(ignoredFileDiagnostic(filePath));
4430
4473
  }
4431
4474
  }
@@ -4497,11 +4540,19 @@ function isPathIgnored(filePath, options = {}) {
4497
4540
  }
4498
4541
 
4499
4542
  const cwd = options.cwd ?? process.cwd();
4543
+ const legacyConfig = legacyConfigForFile(options, filePath);
4544
+ if (legacyConfig) {
4545
+ return Boolean(legacyConfig.isIgnored?.(normalizeESLintFilePath(filePath, cwd)));
4546
+ }
4500
4547
  const normalized = normalizeIgnoredPath(filePath, cwd);
4501
4548
  const patterns = ignorePatternsForOptions(options, cwd, filePath);
4502
4549
  return pathIgnoredByPatterns(normalized, patterns);
4503
4550
  }
4504
4551
 
4552
+ function defersLegacyIgnores(options) {
4553
+ return options[LEGACY_CONFIG_ENABLED] && (options.legacyConfigFile || (!options.noConfig && !configPathForOptions(options)));
4554
+ }
4555
+
4505
4556
  function ignoreDisabled(options = {}) {
4506
4557
  return options.noIgnore || options.ignore === false;
4507
4558
  }
@@ -4879,6 +4930,12 @@ function diagnosticToESLintMessage(diagnostic, ruleSeverities) {
4879
4930
  column: diagnostic.column,
4880
4931
  nodeType: null
4881
4932
  };
4933
+ if (diagnostic.endLine != null) {
4934
+ message.endLine = diagnostic.endLine;
4935
+ }
4936
+ if (diagnostic.endColumn != null) {
4937
+ message.endColumn = diagnostic.endColumn;
4938
+ }
4882
4939
  const fixes = (diagnostic.fixes ?? []).map((fix) => ({
4883
4940
  range: fix.range,
4884
4941
  text: fix.text
@@ -0,0 +1,76 @@
1
+ const { createRequire } = require("node:module");
2
+ const { dirname, join, resolve } = require("node:path");
3
+
4
+ // The same reviewed aliases used by the ESLint config migrator.
5
+ const RULE_ALIASES = new Map([
6
+ ["no-native-reassign", "no-global-assign"],
7
+ ["@typescript-eslint/no-invalid-this", "no-invalid-this"]
8
+ ]);
9
+
10
+ // Resolve configuration only. Parsing, rule execution, and fixes stay native.
11
+ function createLegacyConfigResolver(cwd, { ignorePath, ignorePatterns, configFile, useEslintrc = true } = {}) {
12
+ const resolvedIgnorePath = ignorePath ? resolve(cwd, ignorePath) : undefined;
13
+ const resolvedConfigFile = configFile ? resolve(cwd, configFile) : undefined;
14
+ const projectRequire = createRequire(join(cwd, "package.json"));
15
+ let eslintPackage;
16
+ try {
17
+ eslintPackage = projectRequire.resolve("eslint/package.json");
18
+ } catch (error) {
19
+ if (error.code !== "MODULE_NOT_FOUND") throw error;
20
+ }
21
+
22
+ let load;
23
+ if (eslintPackage) {
24
+ const manifest = projectRequire(eslintPackage);
25
+ // Some shared configs patch ESLint's module-resolution internals. Use their
26
+ // own ESLint 8 config resolver when installed, and never recurse into an
27
+ // eslint package alias that points back to @utoo/lint.
28
+ const major = Number(manifest.version.split(".")[0]);
29
+ if (manifest.name === "eslint" && major === 8) {
30
+ const { CLIEngine } = projectRequire(join(dirname(eslintPackage), "lib/cli-engine/cli-engine.js"));
31
+ const engine = new CLIEngine({ cwd, ignorePath: resolvedIgnorePath, ignorePattern: ignorePatterns, configFile: resolvedConfigFile, useEslintrc });
32
+ load = (filePath) => {
33
+ try {
34
+ return { ...engine.getConfigForFile(filePath), isIgnored: (path) => engine.isPathIgnored(path) };
35
+ } catch (error) {
36
+ if (error.messageTemplate === "no-config-found") return undefined;
37
+ throw error;
38
+ }
39
+ };
40
+ }
41
+ }
42
+
43
+ if (!load) {
44
+ const { Legacy } = require("@eslint/eslintrc");
45
+ const eslintJs = require("@eslint/js");
46
+ const factory = new Legacy.CascadingConfigArrayFactory({
47
+ cwd,
48
+ specificConfigPath: resolvedConfigFile,
49
+ useEslintrc,
50
+ ignorePath: resolvedIgnorePath,
51
+ cliConfig: ignorePatterns?.length ? { ignorePatterns } : undefined,
52
+ getEslintRecommendedConfig: () => eslintJs.configs.recommended,
53
+ getEslintAllConfig: () => eslintJs.configs.all
54
+ });
55
+ load = (filePath) => {
56
+ const configs = factory.getConfigArrayForFile(filePath, { ignoreNotFoundError: true });
57
+ if (!configs.some((entry) => entry.type === "config" && entry.filePath)) return undefined;
58
+ const config = configs.extractConfig(filePath);
59
+ return { ...config.toCompatibleObjectAsConfigFileContent(), isIgnored: config.ignores };
60
+ };
61
+ }
62
+
63
+ const configs = new Map();
64
+ return (filePath) => {
65
+ if (!configs.has(filePath)) {
66
+ const config = load(filePath);
67
+ if (config) {
68
+ config.rules = Object.fromEntries(Object.entries(config.rules).map(([rule, value]) => [RULE_ALIASES.get(rule) ?? rule, value]));
69
+ }
70
+ configs.set(filePath, config);
71
+ }
72
+ return configs.get(filePath);
73
+ };
74
+ }
75
+
76
+ module.exports = { createLegacyConfigResolver };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utoo/lint",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "High-performance JavaScript and TypeScript linter powered by Yuku",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -76,11 +76,11 @@
76
76
  "use-at-your-own-risk.js"
77
77
  ],
78
78
  "optionalDependencies": {
79
- "@utoo/lint-darwin-arm64": "0.3.6",
80
- "@utoo/lint-darwin-x64": "0.3.6",
81
- "@utoo/lint-linux-arm64": "0.3.6",
82
- "@utoo/lint-linux-x64": "0.3.6",
83
- "@utoo/lint-win32-x64": "0.3.6"
79
+ "@utoo/lint-darwin-arm64": "0.3.7",
80
+ "@utoo/lint-darwin-x64": "0.3.7",
81
+ "@utoo/lint-linux-arm64": "0.3.7",
82
+ "@utoo/lint-linux-x64": "0.3.7",
83
+ "@utoo/lint-win32-x64": "0.3.7"
84
84
  },
85
85
  "dependencies": {
86
86
  "@eslint/eslintrc": "2.1.4",