@utoo/lint 0.2.2 → 0.2.3

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
@@ -64,6 +64,21 @@ lists autofix coverage by rule.
64
64
 
65
65
  ## Configuration
66
66
 
67
+ `utoo-lint` supports two canonical project config formats:
68
+
69
+ | Config | Use it when | Raw native binary |
70
+ | --- | --- | --- |
71
+ | `utlint.config.ts` | You want typed authoring, imports, or computed values. | No |
72
+ | `utlint.config.json` | You want a static, runtime-independent config. | Yes |
73
+
74
+ The npm CLI discovers either format automatically. It can also select one
75
+ explicitly:
76
+
77
+ ```bash
78
+ pnpm exec utoo-lint --config=utlint.config.ts src
79
+ pnpm exec utoo-lint --config=utlint.config.json src
80
+ ```
81
+
67
82
  For a static config readable by both the npm CLI and raw native binary, create
68
83
  `utlint.config.json`:
69
84
 
package/index.cjs CHANGED
@@ -484,7 +484,7 @@ class UtooLint {
484
484
  }
485
485
 
486
486
  async lintFiles(patterns, options = {}) {
487
- const mergedOptions = mergeLintOptions(this.options, options);
487
+ const mergedOptions = withConfigCache(mergeLintOptions(this.options, options));
488
488
  const customRuleConfig = [mergedOptions.baseConfig, mergedOptions.overrideConfig].filter(Boolean);
489
489
  const customRuleFilterMap = customRuleMapForConfig(customRuleConfig, new Map());
490
490
  const nativeOptions = lintOptionsWithoutCustomRules(mergedOptions, customRuleFilterMap);
@@ -504,7 +504,7 @@ class UtooLint {
504
504
  throw new TypeError("code must be a string");
505
505
  }
506
506
 
507
- const mergedOptions = mergeLintOptions(this.options, options);
507
+ const mergedOptions = withConfigCache(mergeLintOptions(this.options, options));
508
508
  const filePath = normalizeESLintFilePath(textFilePathForOptions(options, "<text>"), mergedOptions.cwd);
509
509
  const customRuleConfig = [mergedOptions.baseConfig, mergedOptions.overrideConfig].filter(Boolean);
510
510
  const customRuleMap = customRuleMapForConfig(customRuleConfig, new Map(), filePath, mergedOptions.cwd);
@@ -528,7 +528,7 @@ class UtooLint {
528
528
  }
529
529
 
530
530
  async calculateConfigForFile(filePath) {
531
- return publicCalculatedConfig(eslintConstructorOptions(this.options), filePath);
531
+ return publicCalculatedConfig(withConfigCache(eslintConstructorOptions(this.options)), filePath);
532
532
  }
533
533
 
534
534
  async findConfigFile(filePath) {
@@ -610,7 +610,7 @@ class CLIEngine {
610
610
  }
611
611
 
612
612
  executeOnFiles(patterns) {
613
- const mergedOptions = eslintConstructorOptions(this.options);
613
+ const mergedOptions = withConfigCache(eslintConstructorOptions(this.options));
614
614
  const report = lintFiles(patterns, mergedOptions);
615
615
  throwOnUnmatchedPatternDiagnostics(report, mergedOptions);
616
616
  const results = maybeFilterQuietResults(reportToESLintResults(report, {
@@ -630,10 +630,10 @@ class CLIEngine {
630
630
  typeof filePathOrOptions === "object" && filePathOrOptions !== null
631
631
  ? filePathOrOptions.filePath ?? filePathOrOptions.filename ?? "input.js"
632
632
  : filePathOrOptions;
633
- const mergedOptions = {
633
+ const mergedOptions = withConfigCache({
634
634
  ...eslintConstructorOptions(this.options),
635
635
  ...textOptions
636
- };
636
+ });
637
637
  const report = lintText(code, {
638
638
  ...mergedOptions,
639
639
  filePath
@@ -670,7 +670,7 @@ class CLIEngine {
670
670
  }
671
671
 
672
672
  getConfigForFile(filePath) {
673
- return publicCalculatedConfig(eslintConstructorOptions(this.options), filePath);
673
+ return publicCalculatedConfig(withConfigCache(eslintConstructorOptions(this.options)), filePath);
674
674
  }
675
675
  }
676
676
 
@@ -812,12 +812,12 @@ function runCli(args = [], options = {}) {
812
812
  return run(parsed.passthroughArgs, options);
813
813
  }
814
814
 
815
- const cliOptions = {
815
+ const cliOptions = withConfigCache({
816
816
  ...options,
817
817
  ...parsed.options,
818
818
  extraArgs: parsed.passthroughArgs,
819
819
  preserveNativeDefaults: Boolean(parsed.options.noConfig)
820
- };
820
+ });
821
821
  const targets = parsed.targets.length > 0 ? parsed.targets : defaultLintTargets(cliOptions);
822
822
  const report = lintFiles(targets, cliOptions);
823
823
  report.diagnostics = normalizeReportDiagnostics(report.diagnostics, cliOptions);
@@ -1063,6 +1063,7 @@ function booleanFlagValue(arg) {
1063
1063
  }
1064
1064
 
1065
1065
  function lintFiles(paths, options = {}) {
1066
+ options = withConfigCache(options);
1066
1067
  const targets = lintTargetsForInput(paths, options);
1067
1068
  const ignoredDiagnostics = ignoredLintPathDiagnostics(targets, options);
1068
1069
  const lintPaths = filteredLintPaths(targets, options);
@@ -1241,6 +1242,7 @@ function mergeNativeReports(reports) {
1241
1242
  }
1242
1243
 
1243
1244
  function lintText(code, options = {}) {
1245
+ options = withConfigCache(options);
1244
1246
  if (typeof code !== "string") {
1245
1247
  throw new TypeError("code must be a string");
1246
1248
  }
@@ -1307,7 +1309,7 @@ function buildNativeLintArgs(paths, options) {
1307
1309
  if (!options.rules && !options.forceMaterializedConfig && (options.config || options.baseConfig || options.overrideConfig)) {
1308
1310
  const configForRuleSelection = options.noConfig
1309
1311
  ? undefined
1310
- : options.config ? readConfig(options.config, options.cwd) : undefined;
1312
+ : options.config ? readConfig(options.config, options.cwd, options.configCache) : undefined;
1311
1313
  const enabledRules = enabledRuleNamesFromConfigs(
1312
1314
  configForRuleSelection,
1313
1315
  options.baseConfig,
@@ -1353,6 +1355,12 @@ function mergeLintOptions(base, override) {
1353
1355
  };
1354
1356
  }
1355
1357
 
1358
+ function withConfigCache(options) {
1359
+ // Executable configs are otherwise re-evaluated at every per-file lookup.
1360
+ // Keep the cache on the options object so one public invocation shares it.
1361
+ return options.configCache ? options : { ...options, configCache: new Map() };
1362
+ }
1363
+
1356
1364
  function eslintConstructorOptions(options) {
1357
1365
  const mapped = {
1358
1366
  cwd: options.cwd,
@@ -1525,7 +1533,7 @@ function rulesFromFileConfig(options, filePath) {
1525
1533
  return {};
1526
1534
  }
1527
1535
 
1528
- const config = readConfig(configPath, options.cwd);
1536
+ const config = readConfig(configPath, options.cwd, options.configCache);
1529
1537
  return rulesFromConfig(config, filePath, dirname(configPath));
1530
1538
  }
1531
1539
 
@@ -1539,7 +1547,7 @@ function configDataFromFileConfig(options, filePath) {
1539
1547
  return {};
1540
1548
  }
1541
1549
 
1542
- const config = readConfig(configPath, options.cwd);
1550
+ const config = readConfig(configPath, options.cwd, options.configCache);
1543
1551
  return configDataFromConfig(config, filePath, dirname(configPath));
1544
1552
  }
1545
1553
 
@@ -1560,7 +1568,7 @@ function defaultLintTargets(options = {}) {
1560
1568
  return ["."];
1561
1569
  }
1562
1570
 
1563
- const targets = filePatternsFromConfig(readConfig(configPath, options.cwd));
1571
+ const targets = filePatternsFromConfig(readConfig(configPath, options.cwd, options.configCache));
1564
1572
  return targets.length > 0
1565
1573
  ? targets.map((target) => resolveConfigPattern(target, dirname(configPath)))
1566
1574
  : ["."];
@@ -1652,7 +1660,9 @@ function configPathFromDirectory(directory) {
1652
1660
  function withTemporaryConfig(options, callback) {
1653
1661
  const fileConfigPath = !options.noConfig ? configPathForOptions(options) : undefined;
1654
1662
  const shouldMaterializeFileConfig = Boolean(fileConfigPath);
1655
- const fileConfig = shouldMaterializeFileConfig ? readConfig(fileConfigPath, options.cwd) : undefined;
1663
+ const fileConfig = shouldMaterializeFileConfig
1664
+ ? readConfig(fileConfigPath, options.cwd, options.configCache)
1665
+ : undefined;
1656
1666
  const configs = [options.baseConfig, fileConfig, options.overrideConfig];
1657
1667
  const rules = shouldMaterializeFileConfig
1658
1668
  ? materializedRulesFromConfigs(...configs)
@@ -4012,7 +4022,7 @@ function ignorePatternsFromFileConfig(options, filePath) {
4012
4022
  }
4013
4023
 
4014
4024
  const cwd = options.cwd ?? process.cwd();
4015
- return ignorePatternsFromConfig(readConfig(configPath, options.cwd)).map((pattern) =>
4025
+ return ignorePatternsFromConfig(readConfig(configPath, options.cwd, options.configCache)).map((pattern) =>
4016
4026
  rebaseConfigPattern(pattern, dirname(configPath), cwd)
4017
4027
  );
4018
4028
  }
package/index.js CHANGED
@@ -487,7 +487,7 @@ export class UtooLint {
487
487
  }
488
488
 
489
489
  async lintFiles(patterns, options = {}) {
490
- const mergedOptions = mergeLintOptions(this.options, options);
490
+ const mergedOptions = withConfigCache(mergeLintOptions(this.options, options));
491
491
  const customRuleConfig = [mergedOptions.baseConfig, mergedOptions.overrideConfig].filter(Boolean);
492
492
  const customRuleFilterMap = customRuleMapForConfig(customRuleConfig, new Map());
493
493
  const nativeOptions = lintOptionsWithoutCustomRules(mergedOptions, customRuleFilterMap);
@@ -507,7 +507,7 @@ export class UtooLint {
507
507
  throw new TypeError("code must be a string");
508
508
  }
509
509
 
510
- const mergedOptions = mergeLintOptions(this.options, options);
510
+ const mergedOptions = withConfigCache(mergeLintOptions(this.options, options));
511
511
  const filePath = normalizeESLintFilePath(textFilePathForOptions(options, "<text>"), mergedOptions.cwd);
512
512
  const customRuleConfig = [mergedOptions.baseConfig, mergedOptions.overrideConfig].filter(Boolean);
513
513
  const customRuleMap = customRuleMapForConfig(customRuleConfig, new Map(), filePath, mergedOptions.cwd);
@@ -531,7 +531,7 @@ export class UtooLint {
531
531
  }
532
532
 
533
533
  async calculateConfigForFile(filePath) {
534
- return publicCalculatedConfig(eslintConstructorOptions(this.options), filePath);
534
+ return publicCalculatedConfig(withConfigCache(eslintConstructorOptions(this.options)), filePath);
535
535
  }
536
536
 
537
537
  async findConfigFile(filePath) {
@@ -2446,7 +2446,7 @@ export class CLIEngine {
2446
2446
  }
2447
2447
 
2448
2448
  executeOnFiles(patterns) {
2449
- const mergedOptions = eslintConstructorOptions(this.options);
2449
+ const mergedOptions = withConfigCache(eslintConstructorOptions(this.options));
2450
2450
  const report = lintFiles(patterns, mergedOptions);
2451
2451
  throwOnUnmatchedPatternDiagnostics(report, mergedOptions);
2452
2452
  const results = maybeFilterQuietResults(reportToESLintResults(report, {
@@ -2466,10 +2466,10 @@ export class CLIEngine {
2466
2466
  typeof filePathOrOptions === "object" && filePathOrOptions !== null
2467
2467
  ? filePathOrOptions.filePath ?? filePathOrOptions.filename ?? "input.js"
2468
2468
  : filePathOrOptions;
2469
- const mergedOptions = {
2469
+ const mergedOptions = withConfigCache({
2470
2470
  ...eslintConstructorOptions(this.options),
2471
2471
  ...textOptions
2472
- };
2472
+ });
2473
2473
  const report = lintText(code, {
2474
2474
  ...mergedOptions,
2475
2475
  filePath
@@ -2506,7 +2506,7 @@ export class CLIEngine {
2506
2506
  }
2507
2507
 
2508
2508
  getConfigForFile(filePath) {
2509
- return publicCalculatedConfig(eslintConstructorOptions(this.options), filePath);
2509
+ return publicCalculatedConfig(withConfigCache(eslintConstructorOptions(this.options)), filePath);
2510
2510
  }
2511
2511
  }
2512
2512
 
@@ -2648,12 +2648,12 @@ export function runCli(args = [], options = {}) {
2648
2648
  return run(parsed.passthroughArgs, options);
2649
2649
  }
2650
2650
 
2651
- const cliOptions = {
2651
+ const cliOptions = withConfigCache({
2652
2652
  ...options,
2653
2653
  ...parsed.options,
2654
2654
  extraArgs: parsed.passthroughArgs,
2655
2655
  preserveNativeDefaults: Boolean(parsed.options.noConfig)
2656
- };
2656
+ });
2657
2657
  const targets = parsed.targets.length > 0 ? parsed.targets : defaultLintTargets(cliOptions);
2658
2658
  const report = lintFiles(targets, cliOptions);
2659
2659
  report.diagnostics = normalizeReportDiagnostics(report.diagnostics, cliOptions);
@@ -2899,6 +2899,7 @@ function booleanFlagValue(arg) {
2899
2899
  }
2900
2900
 
2901
2901
  export function lintFiles(paths, options = {}) {
2902
+ options = withConfigCache(options);
2902
2903
  const targets = lintTargetsForInput(paths, options);
2903
2904
  const ignoredDiagnostics = ignoredLintPathDiagnostics(targets, options);
2904
2905
  const lintPaths = filteredLintPaths(targets, options);
@@ -3077,6 +3078,7 @@ function mergeNativeReports(reports) {
3077
3078
  }
3078
3079
 
3079
3080
  export function lintText(code, options = {}) {
3081
+ options = withConfigCache(options);
3080
3082
  if (typeof code !== "string") {
3081
3083
  throw new TypeError("code must be a string");
3082
3084
  }
@@ -3143,7 +3145,7 @@ function buildNativeLintArgs(paths, options) {
3143
3145
  if (!options.rules && !options.forceMaterializedConfig && (options.config || options.baseConfig || options.overrideConfig)) {
3144
3146
  const configForRuleSelection = options.noConfig
3145
3147
  ? undefined
3146
- : options.config ? readConfig(options.config, options.cwd) : undefined;
3148
+ : options.config ? readConfig(options.config, options.cwd, options.configCache) : undefined;
3147
3149
  const enabledRules = enabledRuleNamesFromConfigs(
3148
3150
  configForRuleSelection,
3149
3151
  options.baseConfig,
@@ -3189,6 +3191,12 @@ function mergeLintOptions(base, override) {
3189
3191
  };
3190
3192
  }
3191
3193
 
3194
+ function withConfigCache(options) {
3195
+ // Executable configs are otherwise re-evaluated at every per-file lookup.
3196
+ // Keep the cache on the options object so one public invocation shares it.
3197
+ return options.configCache ? options : { ...options, configCache: new Map() };
3198
+ }
3199
+
3192
3200
  function eslintConstructorOptions(options) {
3193
3201
  const mapped = {
3194
3202
  cwd: options.cwd,
@@ -3361,7 +3369,7 @@ function rulesFromFileConfig(options, filePath) {
3361
3369
  return {};
3362
3370
  }
3363
3371
 
3364
- const config = readConfig(configPath, options.cwd);
3372
+ const config = readConfig(configPath, options.cwd, options.configCache);
3365
3373
  return rulesFromConfig(config, filePath, dirname(configPath));
3366
3374
  }
3367
3375
 
@@ -3375,7 +3383,7 @@ function configDataFromFileConfig(options, filePath) {
3375
3383
  return {};
3376
3384
  }
3377
3385
 
3378
- const config = readConfig(configPath, options.cwd);
3386
+ const config = readConfig(configPath, options.cwd, options.configCache);
3379
3387
  return configDataFromConfig(config, filePath, dirname(configPath));
3380
3388
  }
3381
3389
 
@@ -3396,7 +3404,7 @@ function defaultLintTargets(options = {}) {
3396
3404
  return ["."];
3397
3405
  }
3398
3406
 
3399
- const targets = filePatternsFromConfig(readConfig(configPath, options.cwd));
3407
+ const targets = filePatternsFromConfig(readConfig(configPath, options.cwd, options.configCache));
3400
3408
  return targets.length > 0
3401
3409
  ? targets.map((target) => resolveConfigPattern(target, dirname(configPath)))
3402
3410
  : ["."];
@@ -3488,7 +3496,9 @@ function configPathFromDirectory(directory) {
3488
3496
  function withTemporaryConfig(options, callback) {
3489
3497
  const fileConfigPath = !options.noConfig ? configPathForOptions(options) : undefined;
3490
3498
  const shouldMaterializeFileConfig = Boolean(fileConfigPath);
3491
- const fileConfig = shouldMaterializeFileConfig ? readConfig(fileConfigPath, options.cwd) : undefined;
3499
+ const fileConfig = shouldMaterializeFileConfig
3500
+ ? readConfig(fileConfigPath, options.cwd, options.configCache)
3501
+ : undefined;
3492
3502
  const configs = [options.baseConfig, fileConfig, options.overrideConfig];
3493
3503
  const rules = shouldMaterializeFileConfig
3494
3504
  ? materializedRulesFromConfigs(...configs)
@@ -4015,7 +4025,7 @@ function ignorePatternsFromFileConfig(options, filePath) {
4015
4025
  }
4016
4026
 
4017
4027
  const cwd = options.cwd ?? process.cwd();
4018
- return ignorePatternsFromConfig(readConfig(configPath, options.cwd)).map((pattern) =>
4028
+ return ignorePatternsFromConfig(readConfig(configPath, options.cwd, options.configCache)).map((pattern) =>
4019
4029
  rebaseConfigPattern(pattern, dirname(configPath), cwd)
4020
4030
  );
4021
4031
  }
@@ -38,17 +38,25 @@ function isFile(path) {
38
38
  }
39
39
  }
40
40
 
41
- function readConfig(path, cwd) {
41
+ function readConfig(path, cwd, cache) {
42
42
  const configPath = resolvePath(cwd ?? process.cwd(), path);
43
- if (isExecutableConfigPath(configPath)) {
44
- return readExecutableConfig(configPath, cwd);
43
+ if (cache?.has(configPath)) {
44
+ return cache.get(configPath);
45
45
  }
46
46
 
47
- try {
48
- return JSON.parse(readFileSync(configPath, "utf8"));
49
- } catch (error) {
50
- throw configReadError(configPath, error.message);
47
+ let config;
48
+ if (isExecutableConfigPath(configPath)) {
49
+ config = readExecutableConfig(configPath, cwd);
50
+ } else {
51
+ try {
52
+ config = JSON.parse(readFileSync(configPath, "utf8"));
53
+ } catch (error) {
54
+ throw configReadError(configPath, error.message);
55
+ }
51
56
  }
57
+
58
+ cache?.set(configPath, config);
59
+ return config;
52
60
  }
53
61
 
54
62
  function isExecutableConfigPath(path) {
@@ -39,17 +39,25 @@ function isFile(path) {
39
39
  }
40
40
  }
41
41
 
42
- export function readConfig(path, cwd) {
42
+ export function readConfig(path, cwd, cache) {
43
43
  const configPath = resolvePath(cwd ?? process.cwd(), path);
44
- if (isExecutableConfigPath(configPath)) {
45
- return readExecutableConfig(configPath, cwd);
44
+ if (cache?.has(configPath)) {
45
+ return cache.get(configPath);
46
46
  }
47
47
 
48
- try {
49
- return JSON.parse(readFileSync(configPath, "utf8"));
50
- } catch (error) {
51
- throw configReadError(configPath, error.message);
48
+ let config;
49
+ if (isExecutableConfigPath(configPath)) {
50
+ config = readExecutableConfig(configPath, cwd);
51
+ } else {
52
+ try {
53
+ config = JSON.parse(readFileSync(configPath, "utf8"));
54
+ } catch (error) {
55
+ throw configReadError(configPath, error.message);
56
+ }
52
57
  }
58
+
59
+ cache?.set(configPath, config);
60
+ return config;
53
61
  }
54
62
 
55
63
  export function isExecutableConfigPath(path) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utoo/lint",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Experimental JavaScript and TypeScript linter powered by Yuku",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -73,11 +73,11 @@
73
73
  "use-at-your-own-risk.js"
74
74
  ],
75
75
  "optionalDependencies": {
76
- "@utoo/lint-darwin-arm64": "0.2.2",
77
- "@utoo/lint-darwin-x64": "0.2.2",
78
- "@utoo/lint-linux-arm64": "0.2.2",
79
- "@utoo/lint-linux-x64": "0.2.2",
80
- "@utoo/lint-win32-x64": "0.2.2"
76
+ "@utoo/lint-darwin-arm64": "0.2.3",
77
+ "@utoo/lint-darwin-x64": "0.2.3",
78
+ "@utoo/lint-linux-arm64": "0.2.3",
79
+ "@utoo/lint-linux-x64": "0.2.3",
80
+ "@utoo/lint-win32-x64": "0.2.3"
81
81
  },
82
82
  "dependencies": {
83
83
  "jiti": "2.7.0"