@utoo/lint 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -2,10 +2,14 @@ import { spawnSync } from "node:child_process";
2
2
  import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import { writeFile as writeFileAsync } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
- import { dirname, extname, isAbsolute, join, resolve as resolvePath } from "node:path";
5
+ import { dirname, extname, isAbsolute, join, relative, resolve as resolvePath } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
8
  import { resolveBinary } from "./lib/binary.js";
9
+ import {
10
+ findConfigPath as findConfigPathFromDirectory,
11
+ readConfig
12
+ } from "./lib/config-loader.js";
9
13
 
10
14
  export { platformPackageName, resolveBinary } from "./lib/binary.js";
11
15
 
@@ -69,20 +73,6 @@ const FISHLINT_DROP_VALUE_FLAGS = new Set([
69
73
  "-E",
70
74
  "-o"
71
75
  ]);
72
- const CONFIG_FILENAMES = ["utoo.json", "utoo-lint.json"];
73
- const JAVASCRIPT_CONFIG_EXTENSIONS = new Set([".js", ".mjs", ".cjs"]);
74
- const JAVASCRIPT_CONFIG_LOADER_SCRIPT = `
75
- import { pathToFileURL } from "node:url";
76
-
77
- const configPath = process.argv[1];
78
- const loaded = await import(pathToFileURL(configPath).href);
79
- const value = loaded.default ?? loaded;
80
- const json = JSON.stringify(value);
81
- if (json === undefined) {
82
- throw new TypeError("config did not export a JSON-serializable value");
83
- }
84
- process.stdout.write(json);
85
- `;
86
76
  const BUILTIN_RULE_IDS = [
87
77
  "accessor-pairs",
88
78
  "array-callback-return",
@@ -420,6 +410,33 @@ const BUILTIN_RULE_IDS = [
420
410
  "@typescript-eslint/prefer-namespace-keyword",
421
411
  "@typescript-eslint/restrict-plus-operands"
422
412
  ];
413
+ const NATIVE_ONLY_RULE_IDS = [
414
+ "@alipay/ant/disallow-typos",
415
+ "@alipay/ant/exhaustive-deps",
416
+ "@alipay/ant/jsx-handler-names",
417
+ "@alipay/ant/no-deprecated-dependence",
418
+ "@alipay/ant/no-deprecated-variable",
419
+ "@alipay/ant/no-import-files-from-pages-in-common",
420
+ "@alipay/ant/no-negative-conditionals",
421
+ "@alipay/ant/no-import-src",
422
+ "@alipay/ant/no-phantom-dependencies",
423
+ "@alipay/ant/no-too-large-file",
424
+ "@alipay/ant/prefer-elseif-end-with-else",
425
+ "@alipay/ant/prefer-catch-unsafe-func-call",
426
+ "@alipay/ant/prefer-click-with-debounce",
427
+ "@alipay/ant/prefer-import-as-required",
428
+ "@alipay/ant/no-spread-params",
429
+ "@alipay/ant/prefer-managed-resource",
430
+ "@alipay/ant/prefer-safe-image-renderer",
431
+ "@alipay/ant/prefer-import-from-stdlib",
432
+ "@alipay/spmLint/use-labeled-spm",
433
+ "@alipay/spmLint/valid-manual-click",
434
+ "@alipay/spmLint/valid-manual-expo",
435
+ "@alipay/spmLint/valid-manual-param",
436
+ "@alipay/spmLint/valid-manual-pv",
437
+ "semantic-errors"
438
+ ];
439
+ const NATIVE_RULE_IDS = [...BUILTIN_RULE_IDS, ...NATIVE_ONLY_RULE_IDS];
423
440
  const FIXABLE_BUILTIN_RULE_IDS = new Set(["no-extra-semi", "@typescript-eslint/no-extra-semi"]);
424
441
  const BUILTIN_RULES = new Map(BUILTIN_RULE_IDS.map((ruleId) => [ruleId, createBuiltinRule(ruleId)]));
425
442
 
@@ -2633,24 +2650,40 @@ export function runCli(args = [], options = {}) {
2633
2650
 
2634
2651
  const cliOptions = {
2635
2652
  ...options,
2636
- ...parsed.options
2653
+ ...parsed.options,
2654
+ extraArgs: parsed.passthroughArgs,
2655
+ preserveNativeDefaults: Boolean(parsed.options.noConfig)
2637
2656
  };
2638
2657
  const targets = parsed.targets.length > 0 ? parsed.targets : defaultLintTargets(cliOptions);
2639
- const ignoredDiagnostics = ignoredLintPathDiagnostics(targets, cliOptions);
2640
- const lintPaths = filteredLintPaths(targets, cliOptions);
2641
- if (lintPaths.length === 0) {
2642
- return completedRunResult(ignoredDiagnostics.length > 0 ? 1 : 0, "", "", options);
2658
+ const report = lintFiles(targets, cliOptions);
2659
+ report.diagnostics = normalizeReportDiagnostics(report.diagnostics, cliOptions);
2660
+ report.exitCode = exitCodeForDiagnostics(report.diagnostics);
2661
+ const status = report.exitCode ?? exitCodeForDiagnostics(report.diagnostics);
2662
+ if (cliOptions.format === "json" || parsed.passthroughArgs.includes("--json")) {
2663
+ return completedRunResult(status, `${JSON.stringify(publicCliReport(report))}\n`, report.stderr ?? "", options);
2643
2664
  }
2665
+ return completedRunResult(status, "", `${report.stderr ?? ""}${formatNativeTextReport(report)}`, options);
2666
+ }
2644
2667
 
2645
- return withTemporaryConfig(cliOptions, (resolvedOptions) => {
2646
- const cliArgs = buildRunCliArgs(lintPaths, resolvedOptions, parsed.passthroughArgs);
2647
- return run(cliArgs, {
2648
- ...resolvedOptions,
2649
- stdio: options.stdio,
2650
- encoding: options.encoding,
2651
- input: options.input
2652
- });
2653
- });
2668
+ function publicCliReport(report) {
2669
+ return {
2670
+ files: report.files ?? 0,
2671
+ filePaths: report.filePaths ?? [],
2672
+ diagnostics: report.diagnostics ?? [],
2673
+ outputs: report.outputs ?? []
2674
+ };
2675
+ }
2676
+
2677
+ function formatNativeTextReport(report) {
2678
+ const lines = [];
2679
+ for (const diagnostic of report.diagnostics ?? []) {
2680
+ const rule = diagnostic.ruleId ? ` [${diagnostic.ruleId}]` : "";
2681
+ lines.push(
2682
+ `${diagnostic.filePath}:${diagnostic.line ?? 0}:${diagnostic.column ?? 0}: ${diagnostic.severity ?? "error"}: ${diagnostic.message}${rule}`
2683
+ );
2684
+ }
2685
+ lines.push(`${report.files ?? 0} file(s) checked, ${(report.diagnostics ?? []).length} diagnostic(s)`);
2686
+ return `${lines.join("\n")}\n`;
2654
2687
  }
2655
2688
 
2656
2689
  export function runFishlint(args = [], options = {}) {
@@ -2870,11 +2903,30 @@ export function lintFiles(paths, options = {}) {
2870
2903
  const ignoredDiagnostics = ignoredLintPathDiagnostics(targets, options);
2871
2904
  const lintPaths = filteredLintPaths(targets, options);
2872
2905
  if (lintPaths.length === 0) {
2873
- return { files: 0, filePaths: [], diagnostics: ignoredDiagnostics, exitCode: ignoredDiagnostics.length > 0 ? 1 : 0 };
2906
+ return { files: 0, filePaths: [], diagnostics: ignoredDiagnostics, exitCode: exitCodeForDiagnostics(ignoredDiagnostics) };
2907
+ }
2908
+
2909
+ const configRuns = nativeConfigRunsForFiles(expandNativeConfigRunPaths(lintPaths, options), options);
2910
+ if (configRuns) {
2911
+ const report = mergeNativeReports(
2912
+ configRuns.map(({ paths: runPaths, options: runOptions }) => runNativeLintReport(runPaths, runOptions))
2913
+ );
2914
+ return finalizeLintReport(report, ignoredDiagnostics, options);
2874
2915
  }
2875
2916
 
2917
+ const report = runNativeLintReport(lintPaths, options);
2918
+ return finalizeLintReport(report, ignoredDiagnostics, options);
2919
+ }
2920
+
2921
+ function expandNativeConfigRunPaths(paths, options) {
2922
+ const cwd = options.cwd ?? process.cwd();
2923
+ const patterns = ignoreDisabled(options) ? [] : ignorePatternsForOptions(options, cwd);
2924
+ return paths.flatMap((path) => expandLintTarget(path, cwd, patterns) ?? [path]);
2925
+ }
2926
+
2927
+ function runNativeLintReport(paths, options) {
2876
2928
  return withTemporaryConfig(options, (resolvedOptions) => {
2877
- const cliArgs = buildLintArgs(lintPaths, resolvedOptions);
2929
+ const cliArgs = buildLintArgs(paths, resolvedOptions);
2878
2930
  const result = run(cliArgs, { ...resolvedOptions, stdio: undefined, encoding: "utf8" });
2879
2931
 
2880
2932
  if (result.error) {
@@ -2897,17 +2949,6 @@ export function lintFiles(paths, options = {}) {
2897
2949
  }
2898
2950
 
2899
2951
  report.exitCode = status;
2900
- report.diagnostics = [
2901
- ...(report.diagnostics ?? []),
2902
- ...ignoredDiagnostics
2903
- ];
2904
- if (!resolvedOptions.deferDiagnosticConfigFiltering) {
2905
- report.filePaths = normalizeReportFilePaths(report.filePaths, resolvedOptions);
2906
- report.diagnostics = normalizeDiagnosticFilePaths(report.diagnostics, resolvedOptions);
2907
- report.outputs = normalizeReportOutputs(report.outputs, resolvedOptions);
2908
- report.diagnostics = normalizeReportDiagnostics(report.diagnostics, resolvedOptions);
2909
- report.exitCode = exitCodeForDiagnostics(report.diagnostics);
2910
- }
2911
2952
  if (stderr) {
2912
2953
  Object.defineProperty(report, "stderr", {
2913
2954
  value: stderr,
@@ -2919,6 +2960,122 @@ export function lintFiles(paths, options = {}) {
2919
2960
  });
2920
2961
  }
2921
2962
 
2963
+ function finalizeLintReport(report, ignoredDiagnostics, options) {
2964
+ report.diagnostics = [
2965
+ ...(report.diagnostics ?? []),
2966
+ ...ignoredDiagnostics
2967
+ ];
2968
+ if (!options.deferDiagnosticConfigFiltering) {
2969
+ report.filePaths = normalizeReportFilePaths(report.filePaths, options);
2970
+ report.diagnostics = normalizeDiagnosticFilePaths(report.diagnostics, options);
2971
+ report.outputs = normalizeReportOutputs(report.outputs, options);
2972
+ report.diagnostics = normalizeReportDiagnostics(report.diagnostics, options);
2973
+ report.exitCode = exitCodeForDiagnostics(report.diagnostics);
2974
+ }
2975
+ return report;
2976
+ }
2977
+
2978
+ function nativeConfigRunsForFiles(paths, options) {
2979
+ if (options.noConfig && !options.baseConfig && !options.overrideConfig) {
2980
+ return undefined;
2981
+ }
2982
+
2983
+ const groups = new Map();
2984
+ let hasConfigSource = Boolean(options.baseConfig || options.overrideConfig);
2985
+ for (const filePath of paths) {
2986
+ const matchPath = paths.length === 1
2987
+ ? options.filePath ?? options.filename ?? filePath
2988
+ : filePath;
2989
+ const fileConfigPath = options.noConfig ? undefined : configPathForFile(options, matchPath);
2990
+ const configured = Boolean(options.baseConfig || options.overrideConfig || fileConfigPath);
2991
+ hasConfigSource ||= configured;
2992
+ let rules = configured ? calculatedConfig({ ...options, rules: undefined }, matchPath).rules : undefined;
2993
+ if (configured && options.rules) {
2994
+ rules = selectedRulesWithConfigOptions(rules, options.rules);
2995
+ }
2996
+ const signature = configured ? stableConfigSignature(rules) : "<native-defaults>";
2997
+ if (!groups.has(signature)) {
2998
+ groups.set(signature, { paths: [], rules, configured });
2999
+ }
3000
+ groups.get(signature).paths.push(filePath);
3001
+ }
3002
+
3003
+ if (!hasConfigSource) {
3004
+ return undefined;
3005
+ }
3006
+
3007
+ return [...groups.values()].map((group) => {
3008
+ if (!group.configured) {
3009
+ return {
3010
+ paths: group.paths,
3011
+ options: {
3012
+ ...options,
3013
+ config: undefined,
3014
+ noConfig: true,
3015
+ baseConfig: undefined,
3016
+ overrideConfig: undefined
3017
+ }
3018
+ };
3019
+ }
3020
+
3021
+ return {
3022
+ paths: group.paths,
3023
+ options: {
3024
+ ...options,
3025
+ config: undefined,
3026
+ noConfig: true,
3027
+ baseConfig: undefined,
3028
+ overrideConfig: { rules: allDisabledNativeRules(group.rules) },
3029
+ forceMaterializedConfig: true
3030
+ }
3031
+ };
3032
+ });
3033
+ }
3034
+
3035
+ function allDisabledNativeRules(rules) {
3036
+ return {
3037
+ ...Object.fromEntries(NATIVE_RULE_IDS.map((rule) => [rule, "off"])),
3038
+ ...rules
3039
+ };
3040
+ }
3041
+
3042
+ function selectedRulesWithConfigOptions(configRules, selectedRules) {
3043
+ const selected = rulesFromNativeRuleList(selectedRules);
3044
+ return Object.fromEntries(
3045
+ Object.keys(selected).map((rule) => [
3046
+ rule,
3047
+ configRules?.[rule] != null && ruleConfigSeverity(configRules[rule]) > 0
3048
+ ? configRules[rule]
3049
+ : selected[rule]
3050
+ ])
3051
+ );
3052
+ }
3053
+
3054
+ function stableConfigSignature(value) {
3055
+ if (Array.isArray(value)) {
3056
+ return `[${value.map((item) => stableConfigSignature(item)).join(",")}]`;
3057
+ }
3058
+ if (value && typeof value === "object") {
3059
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableConfigSignature(value[key])}`).join(",")}}`;
3060
+ }
3061
+ return JSON.stringify(value);
3062
+ }
3063
+
3064
+ function mergeNativeReports(reports) {
3065
+ const report = {
3066
+ files: reports.reduce((count, item) => count + (item.files ?? 0), 0),
3067
+ filePaths: reports.flatMap((item) => item.filePaths ?? []),
3068
+ diagnostics: reports.flatMap((item) => item.diagnostics ?? []),
3069
+ outputs: reports.flatMap((item) => item.outputs ?? []),
3070
+ exitCode: reports.some((item) => item.exitCode) ? 1 : 0
3071
+ };
3072
+ const stderr = reports.map((item) => item.stderr ?? "").join("");
3073
+ if (stderr) {
3074
+ Object.defineProperty(report, "stderr", { value: stderr, enumerable: false });
3075
+ }
3076
+ return report;
3077
+ }
3078
+
2922
3079
  export function lintText(code, options = {}) {
2923
3080
  if (typeof code !== "string") {
2924
3081
  throw new TypeError("code must be a string");
@@ -2940,7 +3097,7 @@ export function lintText(code, options = {}) {
2940
3097
  files: 0,
2941
3098
  filePaths: [],
2942
3099
  diagnostics,
2943
- exitCode: diagnostics.length > 0 ? 1 : 0
3100
+ exitCode: exitCodeForDiagnostics(diagnostics)
2944
3101
  };
2945
3102
  }
2946
3103
 
@@ -2977,22 +3134,28 @@ function buildLintArgs(paths, options) {
2977
3134
  });
2978
3135
  }
2979
3136
 
2980
- function buildRunCliArgs(paths, options, passthroughArgs) {
2981
- return buildNativeLintArgs(paths, {
2982
- ...options,
2983
- extraArgs: passthroughArgs
2984
- });
2985
- }
2986
-
2987
3137
  function buildNativeLintArgs(paths, options) {
2988
3138
  const cliArgs = [];
2989
3139
 
2990
- if (options.config) {
2991
- cliArgs.push(`--config=${options.config}`);
2992
- }
2993
3140
  if (options.noConfig) {
2994
3141
  cliArgs.push("--no-config");
2995
3142
  }
3143
+ if (!options.rules && !options.forceMaterializedConfig && (options.config || options.baseConfig || options.overrideConfig)) {
3144
+ const configForRuleSelection = options.noConfig
3145
+ ? undefined
3146
+ : options.config ? readConfig(options.config, options.cwd) : undefined;
3147
+ const enabledRules = enabledRuleNamesFromConfigs(
3148
+ configForRuleSelection,
3149
+ options.baseConfig,
3150
+ options.overrideConfig
3151
+ );
3152
+ if (enabledRules.length > 0) {
3153
+ cliArgs.push(`--rules=${enabledRules.join(",")}`);
3154
+ }
3155
+ }
3156
+ if (options.config) {
3157
+ cliArgs.push(`--config=${options.config}`);
3158
+ }
2996
3159
  if (options.rules) {
2997
3160
  const rules = Array.isArray(options.rules) ? options.rules.join(",") : options.rules;
2998
3161
  if (rules) {
@@ -3078,10 +3241,10 @@ function calculatedConfig(options = {}, filePath) {
3078
3241
  return {
3079
3242
  ...configData,
3080
3243
  rules: {
3081
- ...rulesFromNativeRuleList(options.rules),
3082
3244
  ...rulesFromConfig(options.baseConfig, filePath, options.cwd),
3083
3245
  ...rulesFromFileConfig(options, filePath),
3084
- ...rulesFromConfig(options.overrideConfig, filePath, options.cwd)
3246
+ ...rulesFromConfig(options.overrideConfig, filePath, options.cwd),
3247
+ ...rulesFromNativeRuleList(options.rules)
3085
3248
  }
3086
3249
  };
3087
3250
  }
@@ -3199,7 +3362,7 @@ function rulesFromFileConfig(options, filePath) {
3199
3362
  }
3200
3363
 
3201
3364
  const config = readConfig(configPath, options.cwd);
3202
- return rulesFromConfig(config, filePath, options.cwd);
3365
+ return rulesFromConfig(config, filePath, dirname(configPath));
3203
3366
  }
3204
3367
 
3205
3368
  function configDataFromFileConfig(options, filePath) {
@@ -3213,7 +3376,7 @@ function configDataFromFileConfig(options, filePath) {
3213
3376
  }
3214
3377
 
3215
3378
  const config = readConfig(configPath, options.cwd);
3216
- return configDataFromConfig(config, filePath, options.cwd);
3379
+ return configDataFromConfig(config, filePath, dirname(configPath));
3217
3380
  }
3218
3381
 
3219
3382
  function lintTargetsForInput(paths, options = {}) {
@@ -3234,7 +3397,16 @@ function defaultLintTargets(options = {}) {
3234
3397
  }
3235
3398
 
3236
3399
  const targets = filePatternsFromConfig(readConfig(configPath, options.cwd));
3237
- return targets.length > 0 ? targets : ["."];
3400
+ return targets.length > 0
3401
+ ? targets.map((target) => resolveConfigPattern(target, dirname(configPath)))
3402
+ : ["."];
3403
+ }
3404
+
3405
+ function resolveConfigPattern(pattern, configDirectory) {
3406
+ if (isAbsolute(pattern)) {
3407
+ return pattern;
3408
+ }
3409
+ return resolvePath(configDirectory, pattern.replace(/^[/\\]/, ""));
3238
3410
  }
3239
3411
 
3240
3412
  function filePatternsFromConfig(config) {
@@ -3288,6 +3460,9 @@ function configPathForOptions(options) {
3288
3460
  }
3289
3461
 
3290
3462
  function configPathForFile(options, filePath) {
3463
+ if (options.noConfig) {
3464
+ return undefined;
3465
+ }
3291
3466
  if (options.config) {
3292
3467
  return resolvePath(options.cwd ?? process.cwd(), options.config);
3293
3468
  }
@@ -3307,66 +3482,18 @@ function configSearchDirectoryForFile(filePath, cwd) {
3307
3482
  }
3308
3483
 
3309
3484
  function configPathFromDirectory(directory) {
3310
- let current = resolvePath(directory);
3311
- while (true) {
3312
- for (const candidate of CONFIG_FILENAMES) {
3313
- const path = resolvePath(current, candidate);
3314
- if (existsSync(path)) {
3315
- return path;
3316
- }
3317
- }
3318
- const parent = dirname(current);
3319
- if (parent === current) {
3320
- return undefined;
3321
- }
3322
- current = parent;
3323
- }
3324
- }
3325
-
3326
- function readConfig(path, cwd) {
3327
- if (isJavaScriptConfigPath(path)) {
3328
- return readJavaScriptConfig(path, cwd);
3329
- }
3330
-
3331
- try {
3332
- return JSON.parse(readFileSync(path, "utf8"));
3333
- } catch (error) {
3334
- throw new Error(`utoo-lint unable to read config ${path}: ${error.message}`);
3335
- }
3336
- }
3337
-
3338
- function readJavaScriptConfig(path, cwd) {
3339
- const result = spawnSync(process.execPath, ["--input-type=module", "--eval", JAVASCRIPT_CONFIG_LOADER_SCRIPT, path], {
3340
- cwd: cwd ?? process.cwd(),
3341
- encoding: "utf8"
3342
- });
3343
- if (result.error) {
3344
- throw result.error;
3345
- }
3346
- if (result.status !== 0) {
3347
- throw new Error(`utoo-lint unable to read config ${path}: ${(result.stderr ?? "").trim() || "JavaScript config loader failed"}`);
3348
- }
3349
- try {
3350
- return JSON.parse(result.stdout);
3351
- } catch (error) {
3352
- throw new Error(`utoo-lint unable to read config ${path}: ${error.message}`);
3353
- }
3354
- }
3355
-
3356
- function isJavaScriptConfigPath(path) {
3357
- return JAVASCRIPT_CONFIG_EXTENSIONS.has(extname(path));
3485
+ return findConfigPathFromDirectory(directory);
3358
3486
  }
3359
3487
 
3360
3488
  function withTemporaryConfig(options, callback) {
3361
3489
  const fileConfigPath = !options.noConfig ? configPathForOptions(options) : undefined;
3362
- const shouldMaterializeFileConfig = fileConfigPath && isJavaScriptConfigPath(fileConfigPath);
3490
+ const shouldMaterializeFileConfig = Boolean(fileConfigPath);
3363
3491
  const fileConfig = shouldMaterializeFileConfig ? readConfig(fileConfigPath, options.cwd) : undefined;
3364
- const inlineRules = {
3365
- ...rulesFromConfig(fileConfig),
3366
- ...rulesFromConfig(options.baseConfig),
3367
- ...rulesFromConfig(options.overrideConfig)
3368
- };
3369
- if (Object.keys(inlineRules).length === 0) {
3492
+ const configs = [options.baseConfig, fileConfig, options.overrideConfig];
3493
+ const rules = shouldMaterializeFileConfig
3494
+ ? materializedRulesFromConfigs(...configs)
3495
+ : runtimeRulesFromConfigs(...configs);
3496
+ if (Object.keys(rules).length === 0) {
3370
3497
  if (shouldMaterializeFileConfig) {
3371
3498
  return callback({
3372
3499
  ...options,
@@ -3376,10 +3503,9 @@ function withTemporaryConfig(options, callback) {
3376
3503
  }
3377
3504
  return callback(options);
3378
3505
  }
3379
-
3380
- const rules = runtimeRulesFromConfigs(fileConfig, options.baseConfig, options.overrideConfig);
3381
- const enabledRules = enabledRuleNamesFromConfigs(fileConfig, options.baseConfig, options.overrideConfig);
3382
- if (!hasRuleOptions(rules)) {
3506
+ const enabledRules = enabledRuleNamesFromConfigs(...configs);
3507
+ const hasExplicitOffRules = Object.values(rules).some((value) => ruleConfigSeverity(value) === 0);
3508
+ if (!shouldMaterializeFileConfig && !hasRuleOptions(rules) && !hasExplicitOffRules && !options.forceMaterializedConfig) {
3383
3509
  return callback({
3384
3510
  ...options,
3385
3511
  config: shouldMaterializeFileConfig ? undefined : options.config,
@@ -3389,13 +3515,14 @@ function withTemporaryConfig(options, callback) {
3389
3515
  }
3390
3516
 
3391
3517
  const tmp = mkdtempSync(join(tmpdir(), "utoo-lint-config-"));
3392
- const configPath = join(tmp, "utoo.json");
3518
+ const configPath = join(tmp, "utlint.config.json");
3393
3519
  try {
3394
3520
  writeFileSync(configPath, JSON.stringify({ rules }));
3395
3521
  return callback({
3396
3522
  ...options,
3397
3523
  config: shouldMaterializeFileConfig ? configPath : options.config ?? configPath,
3398
- noConfig: false
3524
+ noConfig: false,
3525
+ rules: options.forceMaterializedConfig ? undefined : options.rules
3399
3526
  });
3400
3527
  } finally {
3401
3528
  rmSync(tmp, { recursive: true, force: true });
@@ -3416,6 +3543,10 @@ function runtimeRulesFromConfigs(...configs) {
3416
3543
  return rules;
3417
3544
  }
3418
3545
 
3546
+ function materializedRulesFromConfigs(...configs) {
3547
+ return runtimeRulesFromConfigs(...configs);
3548
+ }
3549
+
3419
3550
  function runtimeRulesFromConfig(config) {
3420
3551
  if (!config) {
3421
3552
  return {};
@@ -3429,9 +3560,7 @@ function runtimeRulesFromConfig(config) {
3429
3560
 
3430
3561
  const rules = {};
3431
3562
  for (const [rule, value] of Object.entries(rulesFromConfig(config))) {
3432
- if (ruleConfigSeverity(value) > 0) {
3433
- rules[rule] = value;
3434
- }
3563
+ rules[rule] = value;
3435
3564
  }
3436
3565
  return rules;
3437
3566
  }
@@ -3512,19 +3641,19 @@ function reportToESLintResults(report, textOptions = {}) {
3512
3641
  }
3513
3642
 
3514
3643
  function normalizeReportDiagnostics(diagnostics, options = {}) {
3515
- const filterUnconfiguredRules = hasRuleConfigSource(options);
3516
3644
  return (diagnostics ?? []).flatMap((diagnostic) => {
3517
3645
  if (!diagnostic?.ruleId) {
3518
3646
  return [diagnostic];
3519
3647
  }
3520
- if (diagnostic.ruleId === "io") {
3648
+ if (diagnostic.ruleId === "io" || diagnostic.ruleId === "parse") {
3521
3649
  return [diagnostic];
3522
3650
  }
3523
3651
 
3524
3652
  const filePath = normalizeESLintFilePath(diagnostic.filePath, options.cwd);
3653
+ const filterUnconfiguredRules = hasRuleConfigSource(options, filePath);
3525
3654
  const ruleSeverities = ruleSeverityMapForOptions(options, filePath);
3526
3655
  const severity = ruleSeverities?.get(diagnostic.ruleId);
3527
- if (filterUnconfiguredRules && !ruleSeverities?.has(diagnostic.ruleId)) {
3656
+ if (!options.preserveNativeDefaults && filterUnconfiguredRules && !ruleSeverities?.has(diagnostic.ruleId)) {
3528
3657
  return [];
3529
3658
  }
3530
3659
  if (severity === 0) {
@@ -3555,15 +3684,17 @@ function normalizeDiagnosticFilePaths(diagnostics, options = {}) {
3555
3684
  }));
3556
3685
  }
3557
3686
 
3558
- function hasRuleConfigSource(options = {}) {
3559
- if (options.noConfig || options.baseConfig || options.overrideConfig || options.rules) {
3687
+ function hasRuleConfigSource(options = {}, filePath) {
3688
+ if (options.baseConfig || options.overrideConfig || options.rules) {
3560
3689
  return true;
3561
3690
  }
3562
- return !options.noConfig && Boolean(configPathForOptions(options));
3691
+ return !options.noConfig && Boolean(
3692
+ filePath ? configPathForFile(options, filePath) : configPathForOptions(options)
3693
+ );
3563
3694
  }
3564
3695
 
3565
3696
  function exitCodeForDiagnostics(diagnostics) {
3566
- return (diagnostics?.length ?? 0) > 0 ? 1 : 0;
3697
+ return diagnostics?.some((diagnostic) => diagnostic?.severity === "error" || diagnostic?.severity === 2) ? 1 : 0;
3567
3698
  }
3568
3699
 
3569
3700
  function throwOnUnmatchedPatternDiagnostics(report, options = {}) {
@@ -3883,7 +4014,21 @@ function ignorePatternsFromFileConfig(options, filePath) {
3883
4014
  return [];
3884
4015
  }
3885
4016
 
3886
- return ignorePatternsFromConfig(readConfig(configPath, options.cwd));
4017
+ const cwd = options.cwd ?? process.cwd();
4018
+ return ignorePatternsFromConfig(readConfig(configPath, options.cwd)).map((pattern) =>
4019
+ rebaseConfigPattern(pattern, dirname(configPath), cwd)
4020
+ );
4021
+ }
4022
+
4023
+ function rebaseConfigPattern(pattern, configDirectory, cwd) {
4024
+ const negated = pattern.startsWith("!");
4025
+ const value = negated ? pattern.slice(1) : pattern;
4026
+ const absolute = resolveConfigPattern(value, configDirectory);
4027
+ const relativePattern = normalizePath(relative(resolvePath(cwd), absolute));
4028
+ const rebased = relativePattern === ".." || relativePattern.startsWith("../")
4029
+ ? normalizePath(absolute)
4030
+ : relativePattern;
4031
+ return negated ? `!${rebased}` : rebased;
3887
4032
  }
3888
4033
 
3889
4034
  function ignorePatternsFromConfig(config) {
@@ -0,0 +1,87 @@
1
+ const { spawnSync } = require("node:child_process");
2
+ const { readFileSync, statSync } = require("node:fs");
3
+ const { dirname, extname, join, resolve: resolvePath } = require("node:path");
4
+
5
+ const CONFIG_FILENAMES = [
6
+ "utlint.config.ts",
7
+ "utlint.config.json",
8
+ "utoo.json",
9
+ "utoo-lint.json"
10
+ ];
11
+
12
+ const TYPESCRIPT_CONFIG_EXTENSIONS = new Set([".ts", ".mts", ".cts"]);
13
+ const JAVASCRIPT_CONFIG_EXTENSIONS = new Set([".js", ".mjs", ".cjs"]);
14
+ const loaderPath = join(__dirname, "load-config.js");
15
+
16
+ function findConfigPath(directory) {
17
+ let current = resolvePath(directory);
18
+ while (true) {
19
+ for (const filename of CONFIG_FILENAMES) {
20
+ const candidate = resolvePath(current, filename);
21
+ if (isFile(candidate)) {
22
+ return candidate;
23
+ }
24
+ }
25
+ const parent = dirname(current);
26
+ if (parent === current) {
27
+ return undefined;
28
+ }
29
+ current = parent;
30
+ }
31
+ }
32
+
33
+ function isFile(path) {
34
+ try {
35
+ return statSync(path).isFile();
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ function readConfig(path, cwd) {
42
+ const configPath = resolvePath(cwd ?? process.cwd(), path);
43
+ if (isExecutableConfigPath(configPath)) {
44
+ return readExecutableConfig(configPath, cwd);
45
+ }
46
+
47
+ try {
48
+ return JSON.parse(readFileSync(configPath, "utf8"));
49
+ } catch (error) {
50
+ throw configReadError(configPath, error.message);
51
+ }
52
+ }
53
+
54
+ function isExecutableConfigPath(path) {
55
+ const extension = extname(path);
56
+ return TYPESCRIPT_CONFIG_EXTENSIONS.has(extension) || JAVASCRIPT_CONFIG_EXTENSIONS.has(extension);
57
+ }
58
+
59
+ function readExecutableConfig(path, cwd) {
60
+ const result = spawnSync(process.execPath, [loaderPath, path], {
61
+ cwd: cwd ?? process.cwd(),
62
+ encoding: "utf8",
63
+ stdio: ["ignore", "pipe", "pipe", "pipe"]
64
+ });
65
+ if (result.error) {
66
+ throw configReadError(path, result.error.message);
67
+ }
68
+ if (result.status !== 0) {
69
+ throw configReadError(path, (result.stderr ?? "").trim() || "config loader failed");
70
+ }
71
+ try {
72
+ return JSON.parse(result.output?.[3] ?? "");
73
+ } catch (error) {
74
+ throw configReadError(path, error.message);
75
+ }
76
+ }
77
+
78
+ function configReadError(path, message) {
79
+ return new Error(`utoo-lint unable to read config ${path}: ${message}`);
80
+ }
81
+
82
+ module.exports = {
83
+ CONFIG_FILENAMES,
84
+ findConfigPath,
85
+ isExecutableConfigPath,
86
+ readConfig
87
+ };