@utoo/lint 0.2.1 → 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/README.md +57 -11
- package/bin/fishlint.js +429 -64
- package/bin/migrate.js +8 -6
- package/index.cjs +275 -130
- package/index.d.ts +5 -1
- package/index.js +275 -130
- package/lib/config-loader.cjs +87 -0
- package/lib/config-loader.js +81 -0
- package/lib/load-config.js +23 -0
- package/package.json +15 -6
- package/schema.json +1 -1
package/bin/fishlint.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdtempSync, realpathSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { extname, join } from "node:path";
|
|
5
|
+
import { basename, dirname, extname, join, relative, resolve as resolvePath } from "node:path";
|
|
6
6
|
|
|
7
7
|
import { resolveBinary } from "../lib/binary.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
findConfigPath as findConfigPathFromDirectory,
|
|
10
|
+
isExecutableConfigPath,
|
|
11
|
+
readConfig as readSharedConfig
|
|
12
|
+
} from "../lib/config-loader.js";
|
|
13
|
+
import { Linter, translateFishlintArgs, version } from "../index.js";
|
|
9
14
|
|
|
10
15
|
const values = process.argv.slice(2);
|
|
11
16
|
const command = values[0];
|
|
@@ -44,20 +49,7 @@ const FISHLINT_PASSTHROUGH_DROP_FLAGS = new Set([
|
|
|
44
49
|
"-v"
|
|
45
50
|
]);
|
|
46
51
|
const FORMAT_VALUE_FLAGS = new Set(["--config", "--ignore-path", "--parser", "--plugin"]);
|
|
47
|
-
const
|
|
48
|
-
const JAVASCRIPT_CONFIG_EXTENSIONS = new Set([".js", ".mjs", ".cjs"]);
|
|
49
|
-
const JAVASCRIPT_CONFIG_LOADER_SCRIPT = `
|
|
50
|
-
import { pathToFileURL } from "node:url";
|
|
51
|
-
|
|
52
|
-
const configPath = process.argv[1];
|
|
53
|
-
const loaded = await import(pathToFileURL(configPath).href);
|
|
54
|
-
const value = loaded.default ?? loaded;
|
|
55
|
-
const json = JSON.stringify(value);
|
|
56
|
-
if (json === undefined) {
|
|
57
|
-
throw new TypeError("config did not export a JSON-serializable value");
|
|
58
|
-
}
|
|
59
|
-
process.stdout.write(json);
|
|
60
|
-
`;
|
|
52
|
+
const ESLINT_CONFIG_FILENAMES = ["eslint.config.js", "eslint.config.mjs", "eslint.config.cjs"];
|
|
61
53
|
const STYLELINT_VALUE_FLAGS = new Set([
|
|
62
54
|
"--cache-location",
|
|
63
55
|
"--config",
|
|
@@ -80,7 +72,7 @@ if (isHelpRequest(values)) {
|
|
|
80
72
|
}
|
|
81
73
|
|
|
82
74
|
if (command === "setup" || command === "setuplint") {
|
|
83
|
-
console.warn(`utoo-lint: fishlint ${command} is treated as a no-op; configure utoo-lint with
|
|
75
|
+
console.warn(`utoo-lint: fishlint ${command} is treated as a no-op; configure utoo-lint with utlint.config.json.`);
|
|
84
76
|
process.exit(0);
|
|
85
77
|
}
|
|
86
78
|
|
|
@@ -143,14 +135,53 @@ try {
|
|
|
143
135
|
process.exit(1);
|
|
144
136
|
}
|
|
145
137
|
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
138
|
+
const selectedConfig = loadSelectedConfig(nativeValues);
|
|
139
|
+
const ruleResolution = createRuleResolution(selectedConfig, ruleOverrides.rules, selectedRulesFromArgs(args));
|
|
140
|
+
if (Array.isArray(selectedConfig.config)) {
|
|
141
|
+
const grouped = runFlatConfigGroups(
|
|
142
|
+
binary,
|
|
143
|
+
args,
|
|
144
|
+
selectedConfig,
|
|
145
|
+
ruleOverrides.rules,
|
|
146
|
+
ruleResolution.selectedRules,
|
|
147
|
+
input
|
|
148
|
+
);
|
|
149
|
+
if (grouped.error) {
|
|
150
|
+
console.error(`utoo-lint: failed to run native binary: ${grouped.error.message}`);
|
|
151
|
+
cleanupStdinInput(input);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
if (!grouped.report) {
|
|
155
|
+
if (grouped.stderr) {
|
|
156
|
+
process.stderr.write(rewriteStdinPath(grouped.stderr, input));
|
|
157
|
+
}
|
|
158
|
+
if (grouped.stdout) {
|
|
159
|
+
writeOutput(rewriteStdinPath(grouped.stdout, input), output.file);
|
|
160
|
+
}
|
|
161
|
+
cleanupStdinInput(input);
|
|
162
|
+
process.exit(grouped.status ?? 1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const reportWithSeverity = normalizeReportSeverities(grouped.report, ruleResolution, input);
|
|
166
|
+
const reportWithIgnored = appendDiagnostics(reportWithSeverity, ignoredFiltered.diagnostics);
|
|
167
|
+
const diagnosticCounts = diagnosticCountsFromReport(reportWithIgnored);
|
|
168
|
+
const filteredReport = quiet.enabled ? filterQuietReport(reportWithIgnored) : reportWithIgnored;
|
|
169
|
+
const rewrittenReport = rewriteReportStdinPaths(filteredReport, input);
|
|
170
|
+
if (grouped.stderr) {
|
|
171
|
+
process.stderr.write(rewriteStdinPath(grouped.stderr, input));
|
|
172
|
+
}
|
|
173
|
+
writeOutput(formatReportOutput(rewrittenReport, nativeValues), output.file);
|
|
174
|
+
cleanupStdinInput(input);
|
|
175
|
+
process.exit(eslintExitStatusFromCounts(diagnosticCounts, maxWarnings.value));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const ruleConfig = materializeRuntimeConfig(nativeValues, args, ruleOverrides.rules, selectedConfig);
|
|
149
179
|
const needsReportOutput =
|
|
150
180
|
quiet.enabled ||
|
|
181
|
+
maxWarnings.value != null ||
|
|
151
182
|
ignoredFiltered.diagnostics.length > 0 ||
|
|
152
183
|
usesJsonWithMetadataFormat(nativeValues) ||
|
|
153
|
-
|
|
184
|
+
ruleResolution.configuredRules.size > 0;
|
|
154
185
|
const result = spawnSync(binary, needsReportOutput ? withJsonFormat(ruleConfig.args) : ruleConfig.args, { encoding: "utf8" });
|
|
155
186
|
|
|
156
187
|
if (result.error) {
|
|
@@ -172,12 +203,13 @@ if (needsReportOutput) {
|
|
|
172
203
|
writeOutput(rewriteStdinPath(rawStdout, input), output.file);
|
|
173
204
|
exitStatus = result.status ?? 1;
|
|
174
205
|
} else {
|
|
175
|
-
const reportWithSeverity = normalizeReportSeverities(report,
|
|
206
|
+
const reportWithSeverity = normalizeReportSeverities(report, ruleResolution, input);
|
|
176
207
|
const reportWithIgnored = appendDiagnostics(reportWithSeverity, ignoredFiltered.diagnostics);
|
|
208
|
+
const diagnosticCounts = diagnosticCountsFromReport(reportWithIgnored);
|
|
177
209
|
const filteredReport = quiet.enabled ? filterQuietReport(reportWithIgnored) : reportWithIgnored;
|
|
178
210
|
const rewrittenReport = rewriteReportStdinPaths(filteredReport, input);
|
|
179
211
|
writeOutput(formatReportOutput(rewrittenReport, nativeValues), output.file);
|
|
180
|
-
exitStatus = eslintExitStatusFromCounts(
|
|
212
|
+
exitStatus = eslintExitStatusFromCounts(diagnosticCounts, maxWarnings.value);
|
|
181
213
|
}
|
|
182
214
|
} else {
|
|
183
215
|
writeOutput(rewriteStdinPath(rawStdout, input), output.file);
|
|
@@ -951,16 +983,29 @@ function loadPrintableConfig(args, ruleOverrides = {}) {
|
|
|
951
983
|
};
|
|
952
984
|
}
|
|
953
985
|
|
|
954
|
-
function materializeRuntimeConfig(args, translatedArgs, ruleOverrides) {
|
|
955
|
-
const configPath =
|
|
956
|
-
const shouldMaterialize =
|
|
986
|
+
function materializeRuntimeConfig(args, translatedArgs, ruleOverrides, selectedConfig = loadSelectedConfig(args)) {
|
|
987
|
+
const { configPath, config } = selectedConfig;
|
|
988
|
+
const shouldMaterialize =
|
|
989
|
+
(config && typeof config === "object" && Object.keys(config.rules ?? {}).length > 0) ||
|
|
990
|
+
(typeof configPath === "string" && isExecutableConfigPath(configPath)) ||
|
|
991
|
+
Array.isArray(config) ||
|
|
992
|
+
Object.keys(ruleOverrides).length > 0;
|
|
957
993
|
if (!shouldMaterialize) {
|
|
958
|
-
return {
|
|
994
|
+
return {
|
|
995
|
+
args: configPath ? withConfigArg(translatedArgs, resolvePath(configPath)) : translatedArgs
|
|
996
|
+
};
|
|
959
997
|
}
|
|
960
998
|
|
|
961
999
|
const directory = mkdtempSync(join(tmpdir(), "utoo-fishlint-rule-"));
|
|
962
|
-
const file = join(directory, "
|
|
963
|
-
|
|
1000
|
+
const file = join(directory, "utlint.config.json");
|
|
1001
|
+
const runtimeConfig = loadRuntimeConfig(config, ruleOverrides);
|
|
1002
|
+
writeFileSync(file, JSON.stringify({
|
|
1003
|
+
...runtimeConfig,
|
|
1004
|
+
rules: {
|
|
1005
|
+
...disabledNativeRules(),
|
|
1006
|
+
...(runtimeConfig.rules ?? {})
|
|
1007
|
+
}
|
|
1008
|
+
}));
|
|
964
1009
|
|
|
965
1010
|
return {
|
|
966
1011
|
args: withConfigArg(translatedArgs, file),
|
|
@@ -968,12 +1013,11 @@ function materializeRuntimeConfig(args, translatedArgs, ruleOverrides) {
|
|
|
968
1013
|
};
|
|
969
1014
|
}
|
|
970
1015
|
|
|
971
|
-
function loadRuntimeConfig(
|
|
972
|
-
const config = configPath ? readConfig(configPath) : {};
|
|
1016
|
+
function loadRuntimeConfig(config, ruleOverrides = {}) {
|
|
973
1017
|
if (Array.isArray(config)) {
|
|
974
1018
|
return {
|
|
975
1019
|
rules: {
|
|
976
|
-
...
|
|
1020
|
+
...runtimeRulesFromConfig(config),
|
|
977
1021
|
...ruleOverrides
|
|
978
1022
|
}
|
|
979
1023
|
};
|
|
@@ -987,43 +1031,270 @@ function loadRuntimeConfig(configPath, ruleOverrides = {}) {
|
|
|
987
1031
|
};
|
|
988
1032
|
}
|
|
989
1033
|
|
|
990
|
-
function
|
|
991
|
-
|
|
992
|
-
|
|
1034
|
+
function loadSelectedConfig(args) {
|
|
1035
|
+
const configPath = findConfigPath(args);
|
|
1036
|
+
return {
|
|
1037
|
+
configPath,
|
|
1038
|
+
config: configPath ? readConfig(configPath) : {}
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function runtimeRulesFromConfig(config) {
|
|
1043
|
+
if (!config) {
|
|
1044
|
+
return {};
|
|
1045
|
+
}
|
|
1046
|
+
if (Array.isArray(config)) {
|
|
1047
|
+
return config.reduce((rules, entry) => ({
|
|
1048
|
+
...rules,
|
|
1049
|
+
...runtimeRulesFromConfig(entry)
|
|
1050
|
+
}), {});
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
return Object.fromEntries(
|
|
1054
|
+
Object.entries(rulesFromConfig(config)).filter(([, value]) => ruleConfigSeverity(value) > 0)
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function createRuleResolution(selectedConfig, ruleOverrides, selectedRules = new Set()) {
|
|
1059
|
+
const configuredRules = new Set(Object.keys(rulesFromConfig(selectedConfig.config)));
|
|
1060
|
+
for (const rule of selectedRules) {
|
|
1061
|
+
configuredRules.add(rule);
|
|
1062
|
+
}
|
|
1063
|
+
for (const rule of Object.keys(ruleOverrides)) {
|
|
1064
|
+
configuredRules.add(rule);
|
|
993
1065
|
}
|
|
1066
|
+
return {
|
|
1067
|
+
config: selectedConfig.config,
|
|
1068
|
+
configDirectory: selectedConfig.configPath
|
|
1069
|
+
? dirname(canonicalFilePath(selectedConfig.configPath))
|
|
1070
|
+
: process.cwd(),
|
|
1071
|
+
configuredRules,
|
|
1072
|
+
ruleOverrides,
|
|
1073
|
+
selectedRules
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function selectedRulesFromArgs(args) {
|
|
1078
|
+
const rules = new Set();
|
|
1079
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1080
|
+
const arg = args[index];
|
|
1081
|
+
const value = arg === "--rules" ? args[++index] : arg.startsWith("--rules=") ? arg.slice("--rules=".length) : undefined;
|
|
1082
|
+
if (!value) {
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
for (const rule of value.split(",")) {
|
|
1086
|
+
if (rule.trim()) {
|
|
1087
|
+
rules.add(rule.trim());
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
return rules;
|
|
1092
|
+
}
|
|
994
1093
|
|
|
1094
|
+
function runFlatConfigGroups(binary, translatedArgs, selectedConfig, ruleOverrides, selectedRules, input) {
|
|
1095
|
+
const plan = flatConfigExecutionPlan(translatedArgs, selectedConfig, ruleOverrides, selectedRules, input);
|
|
1096
|
+
const report = { files: 0, filePaths: [], diagnostics: [], outputs: [] };
|
|
1097
|
+
const stderr = [];
|
|
1098
|
+
|
|
1099
|
+
for (const group of plan.groups) {
|
|
1100
|
+
const runtimeConfig = createRuntimeConfig(group.rules);
|
|
1101
|
+
const result = spawnSync(binary, withJsonFormat([
|
|
1102
|
+
...withoutConfigAndTargets(translatedArgs),
|
|
1103
|
+
`--config=${runtimeConfig.file}`,
|
|
1104
|
+
...group.files
|
|
1105
|
+
]), { encoding: "utf8" });
|
|
1106
|
+
cleanupRuleConfig(runtimeConfig);
|
|
1107
|
+
if (result.error) {
|
|
1108
|
+
return { error: result.error };
|
|
1109
|
+
}
|
|
1110
|
+
if (result.stderr) {
|
|
1111
|
+
stderr.push(result.stderr);
|
|
1112
|
+
}
|
|
1113
|
+
const groupReport = parseJsonReport(result.stdout ?? "");
|
|
1114
|
+
if (!groupReport) {
|
|
1115
|
+
return {
|
|
1116
|
+
status: result.status,
|
|
1117
|
+
stdout: result.stdout ?? "",
|
|
1118
|
+
stderr: stderr.join("")
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
mergeFlatConfigReport(report, groupReport);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
report.filePaths = report.filePaths.filter((file) => !plan.globallyIgnored.has(canonicalFilePath(file)));
|
|
1125
|
+
report.diagnostics = report.diagnostics.filter((diagnostic) =>
|
|
1126
|
+
!plan.globallyIgnored.has(canonicalFilePath(diagnostic.filePath))
|
|
1127
|
+
);
|
|
1128
|
+
report.outputs = report.outputs.filter((output) =>
|
|
1129
|
+
!plan.globallyIgnored.has(canonicalFilePath(output.filePath))
|
|
1130
|
+
);
|
|
1131
|
+
report.files = report.filePaths.length;
|
|
1132
|
+
return { report, stderr: stderr.join("") };
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function flatConfigExecutionPlan(translatedArgs, selectedConfig, ruleOverrides, selectedRules, input) {
|
|
1136
|
+
const configDirectory = dirname(canonicalFilePath(selectedConfig.configPath));
|
|
1137
|
+
const targetFiles = explicitTargetFiles(translatedArgs);
|
|
1138
|
+
const globallyIgnored = new Set();
|
|
1139
|
+
const groups = new Map();
|
|
1140
|
+
|
|
1141
|
+
for (const file of targetFiles) {
|
|
1142
|
+
const canonical = canonicalFilePath(file);
|
|
1143
|
+
const configFilePath = file === input.file && input.displayPath
|
|
1144
|
+
? canonicalFilePath(input.displayPath)
|
|
1145
|
+
: canonical;
|
|
1146
|
+
const relativePath = normalizePath(relative(configDirectory, configFilePath));
|
|
1147
|
+
if (isGloballyIgnoredByFlatConfig(selectedConfig.config, relativePath)) {
|
|
1148
|
+
globallyIgnored.add(canonical);
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
const rules = {
|
|
1152
|
+
...rulesForFile(selectedConfig.config, relativePath),
|
|
1153
|
+
...ruleOverrides
|
|
1154
|
+
};
|
|
1155
|
+
for (const rule of selectedRules) {
|
|
1156
|
+
if (!Object.hasOwn(ruleOverrides, rule) && ruleConfigSeverity(rules[rule]) === 0) {
|
|
1157
|
+
rules[rule] = "warn";
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
const signature = stableStringify(rules);
|
|
1161
|
+
const group = groups.get(signature) ?? { rules, files: [] };
|
|
1162
|
+
group.files.push(file);
|
|
1163
|
+
groups.set(signature, group);
|
|
1164
|
+
}
|
|
1165
|
+
return { globallyIgnored, groups: [...groups.values()] };
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function explicitTargetFiles(args) {
|
|
1169
|
+
const files = [];
|
|
1170
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1171
|
+
const arg = args[index];
|
|
1172
|
+
if (arg === "--config" || arg === "-c") {
|
|
1173
|
+
index += 1;
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
if (arg.startsWith("-")) {
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
collectExecutionFiles(arg, files);
|
|
1180
|
+
}
|
|
1181
|
+
return files;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
function collectExecutionFiles(target, files) {
|
|
1185
|
+
let stat;
|
|
995
1186
|
try {
|
|
996
|
-
|
|
997
|
-
} catch
|
|
998
|
-
|
|
999
|
-
|
|
1187
|
+
stat = statSync(target);
|
|
1188
|
+
} catch {
|
|
1189
|
+
files.push(target);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
if (stat.isFile()) {
|
|
1193
|
+
files.push(target);
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (stat.isDirectory()) {
|
|
1197
|
+
collectExecutionDirectoryFiles(target, files);
|
|
1000
1198
|
}
|
|
1001
1199
|
}
|
|
1002
1200
|
|
|
1003
|
-
function
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1201
|
+
function collectExecutionDirectoryFiles(directory, files) {
|
|
1202
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
1203
|
+
if (entry.isDirectory() && [".git", ".zig-cache", "node_modules", "vendor", "zig-out"].includes(entry.name)) {
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
const path = join(directory, entry.name);
|
|
1207
|
+
if (entry.isDirectory()) {
|
|
1208
|
+
collectExecutionDirectoryFiles(path, files);
|
|
1209
|
+
} else if (entry.isFile() && isExistingLintFile(path)) {
|
|
1210
|
+
files.push(path);
|
|
1211
|
+
}
|
|
1010
1212
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function withoutConfigAndTargets(args) {
|
|
1216
|
+
const values = [];
|
|
1217
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1218
|
+
const arg = args[index];
|
|
1219
|
+
if (arg === "--config" || arg === "-c") {
|
|
1220
|
+
index += 1;
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
if (arg.startsWith("--config=")) {
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
if (!arg.startsWith("-")) {
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
values.push(arg);
|
|
1230
|
+
}
|
|
1231
|
+
return values;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function createRuntimeConfig(rules) {
|
|
1235
|
+
const directory = mkdtempSync(join(tmpdir(), "utoo-fishlint-flat-"));
|
|
1236
|
+
const file = join(directory, "utlint.config.json");
|
|
1237
|
+
writeFileSync(file, JSON.stringify({ rules: { ...disabledNativeRules(), ...rules } }));
|
|
1238
|
+
return { directory, file };
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
function disabledNativeRules() {
|
|
1242
|
+
return Object.fromEntries([...new Linter().getRules().keys()].map((rule) => [rule, "off"]));
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function mergeFlatConfigReport(report, groupReport) {
|
|
1246
|
+
for (const file of groupReport.filePaths ?? []) {
|
|
1247
|
+
if (!report.filePaths.includes(file)) {
|
|
1248
|
+
report.filePaths.push(file);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
report.diagnostics.push(...(groupReport.diagnostics ?? []));
|
|
1252
|
+
report.outputs.push(...(groupReport.outputs ?? []));
|
|
1253
|
+
report.files = report.filePaths.length;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
function stableStringify(value) {
|
|
1257
|
+
if (Array.isArray(value)) {
|
|
1258
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
1259
|
+
}
|
|
1260
|
+
if (value && typeof value === "object") {
|
|
1261
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
|
|
1262
|
+
}
|
|
1263
|
+
return JSON.stringify(value);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function isGloballyIgnoredByFlatConfig(config, filePath) {
|
|
1267
|
+
let ignored = false;
|
|
1268
|
+
for (const entry of config) {
|
|
1269
|
+
if (!isGlobalIgnoreEntry(entry)) {
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
for (const pattern of normalizeConfigPatterns(entry.ignores)) {
|
|
1273
|
+
const negated = pattern.startsWith("!");
|
|
1274
|
+
if (matchesIgnorePattern(filePath, normalizeIgnoredPattern(pattern))) {
|
|
1275
|
+
ignored = !negated;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return ignored;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
function isGlobalIgnoreEntry(entry) {
|
|
1283
|
+
if (!entry || typeof entry !== "object" || !entry.ignores) {
|
|
1284
|
+
return false;
|
|
1014
1285
|
}
|
|
1286
|
+
return Object.keys(entry).every((key) => key === "name" || key === "ignores");
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
function readConfig(path) {
|
|
1015
1290
|
try {
|
|
1016
|
-
return
|
|
1291
|
+
return readSharedConfig(resolvePath(path), process.cwd());
|
|
1017
1292
|
} catch (error) {
|
|
1018
|
-
console.error(
|
|
1293
|
+
console.error(error.message);
|
|
1019
1294
|
process.exit(2);
|
|
1020
1295
|
}
|
|
1021
1296
|
}
|
|
1022
1297
|
|
|
1023
|
-
function isJavaScriptConfigPath(path) {
|
|
1024
|
-
return typeof path === "string" && JAVASCRIPT_CONFIG_EXTENSIONS.has(extname(path));
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
1298
|
function rulesFromConfig(config) {
|
|
1028
1299
|
if (!config) {
|
|
1029
1300
|
return {};
|
|
@@ -1041,7 +1312,7 @@ function withConfigArg(args, file) {
|
|
|
1041
1312
|
const values = [];
|
|
1042
1313
|
for (let index = 0; index < args.length; index += 1) {
|
|
1043
1314
|
const arg = args[index];
|
|
1044
|
-
if (arg === "-c") {
|
|
1315
|
+
if (arg === "-c" || arg === "--config") {
|
|
1045
1316
|
index += 1;
|
|
1046
1317
|
continue;
|
|
1047
1318
|
}
|
|
@@ -1071,11 +1342,13 @@ function findConfigPath(args) {
|
|
|
1071
1342
|
console.error(`utoo-lint: fishlint ${arg} requires a path`);
|
|
1072
1343
|
process.exit(2);
|
|
1073
1344
|
}
|
|
1345
|
+
configEnabled = true;
|
|
1074
1346
|
index += 1;
|
|
1075
1347
|
continue;
|
|
1076
1348
|
}
|
|
1077
1349
|
if (arg.startsWith("--config=")) {
|
|
1078
1350
|
configPath = arg.slice("--config=".length);
|
|
1351
|
+
configEnabled = true;
|
|
1079
1352
|
}
|
|
1080
1353
|
}
|
|
1081
1354
|
|
|
@@ -1085,12 +1358,26 @@ function findConfigPath(args) {
|
|
|
1085
1358
|
if (configPath) {
|
|
1086
1359
|
return configPath;
|
|
1087
1360
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1361
|
+
|
|
1362
|
+
const config = findConfigPathFromDirectory(process.cwd());
|
|
1363
|
+
if (config) {
|
|
1364
|
+
return config;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
let directory = process.cwd();
|
|
1368
|
+
while (true) {
|
|
1369
|
+
for (const filename of ESLINT_CONFIG_FILENAMES) {
|
|
1370
|
+
const candidate = resolvePath(directory, filename);
|
|
1371
|
+
if (existsSync(candidate)) {
|
|
1372
|
+
return candidate;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
const parent = dirname(directory);
|
|
1376
|
+
if (parent === directory) {
|
|
1377
|
+
return undefined;
|
|
1091
1378
|
}
|
|
1379
|
+
directory = parent;
|
|
1092
1380
|
}
|
|
1093
|
-
return undefined;
|
|
1094
1381
|
}
|
|
1095
1382
|
|
|
1096
1383
|
function writeOutput(text, file) {
|
|
@@ -1251,8 +1538,8 @@ function filterQuietReport(report) {
|
|
|
1251
1538
|
};
|
|
1252
1539
|
}
|
|
1253
1540
|
|
|
1254
|
-
function normalizeReportSeverities(report,
|
|
1255
|
-
if (
|
|
1541
|
+
function normalizeReportSeverities(report, ruleResolution, input) {
|
|
1542
|
+
if (ruleResolution.configuredRules.size === 0) {
|
|
1256
1543
|
return report;
|
|
1257
1544
|
}
|
|
1258
1545
|
|
|
@@ -1263,7 +1550,10 @@ function normalizeReportSeverities(report, ruleSeverities) {
|
|
|
1263
1550
|
return [diagnostic];
|
|
1264
1551
|
}
|
|
1265
1552
|
|
|
1266
|
-
const severity =
|
|
1553
|
+
const severity = severityForDiagnostic(ruleResolution, diagnostic, input);
|
|
1554
|
+
if (severity === undefined && ruleResolution.configuredRules.has(diagnostic.ruleId)) {
|
|
1555
|
+
return [];
|
|
1556
|
+
}
|
|
1267
1557
|
if (severity === 0) {
|
|
1268
1558
|
return [];
|
|
1269
1559
|
}
|
|
@@ -1275,6 +1565,81 @@ function normalizeReportSeverities(report, ruleSeverities) {
|
|
|
1275
1565
|
};
|
|
1276
1566
|
}
|
|
1277
1567
|
|
|
1568
|
+
function severityForDiagnostic(ruleResolution, diagnostic, input) {
|
|
1569
|
+
if (Object.hasOwn(ruleResolution.ruleOverrides, diagnostic.ruleId)) {
|
|
1570
|
+
return ruleConfigSeverity(ruleResolution.ruleOverrides[diagnostic.ruleId]);
|
|
1571
|
+
}
|
|
1572
|
+
if (ruleResolution.selectedRules.has(diagnostic.ruleId)) {
|
|
1573
|
+
return 1;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
const filePath = diagnostic.filePath === input.file && input.displayPath
|
|
1577
|
+
? input.displayPath
|
|
1578
|
+
: diagnostic.filePath;
|
|
1579
|
+
const relativePath = normalizePath(relative(ruleResolution.configDirectory, canonicalFilePath(filePath)));
|
|
1580
|
+
const rules = rulesForFile(ruleResolution.config, relativePath);
|
|
1581
|
+
return Object.hasOwn(rules, diagnostic.ruleId)
|
|
1582
|
+
? ruleConfigSeverity(rules[diagnostic.ruleId])
|
|
1583
|
+
: undefined;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
function rulesForFile(config, filePath) {
|
|
1587
|
+
if (!config) {
|
|
1588
|
+
return {};
|
|
1589
|
+
}
|
|
1590
|
+
if (Array.isArray(config)) {
|
|
1591
|
+
return config.reduce((rules, entry) => ({
|
|
1592
|
+
...rules,
|
|
1593
|
+
...rulesForFile(entry, filePath)
|
|
1594
|
+
}), {});
|
|
1595
|
+
}
|
|
1596
|
+
if (!configAppliesToFile(config, filePath)) {
|
|
1597
|
+
return {};
|
|
1598
|
+
}
|
|
1599
|
+
return rulesFromConfig(config);
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
function configAppliesToFile(config, filePath) {
|
|
1603
|
+
const files = normalizeConfigPatterns(config.files);
|
|
1604
|
+
if (files.length > 0 && !files.some((pattern) => matchesConfigFilePattern(filePath, normalizeIgnoredPattern(pattern)))) {
|
|
1605
|
+
return false;
|
|
1606
|
+
}
|
|
1607
|
+
return !isIgnoredTarget(filePath, normalizeConfigPatterns(config.ignores));
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
function normalizeConfigPatterns(patterns) {
|
|
1611
|
+
if (!patterns) {
|
|
1612
|
+
return [];
|
|
1613
|
+
}
|
|
1614
|
+
const values = Array.isArray(patterns) ? patterns : [patterns];
|
|
1615
|
+
return values.flatMap((value) => {
|
|
1616
|
+
if (typeof value === "string") {
|
|
1617
|
+
return [value];
|
|
1618
|
+
}
|
|
1619
|
+
return Array.isArray(value) ? normalizeConfigPatterns(value) : [];
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
function matchesConfigFilePattern(target, pattern) {
|
|
1624
|
+
if (!hasGlobSyntax(pattern)) {
|
|
1625
|
+
return target === pattern || target.startsWith(`${pattern}/`) || target.endsWith(`/${pattern}`);
|
|
1626
|
+
}
|
|
1627
|
+
return new RegExp(`^${globPatternRegExpSource(pattern)}$`).test(target);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
function canonicalFilePath(path) {
|
|
1631
|
+
const absolute = resolvePath(path);
|
|
1632
|
+
try {
|
|
1633
|
+
return realpathSync.native(absolute);
|
|
1634
|
+
} catch {
|
|
1635
|
+
try {
|
|
1636
|
+
return join(realpathSync.native(dirname(absolute)), basename(absolute));
|
|
1637
|
+
} catch {
|
|
1638
|
+
return absolute;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1278
1643
|
function rewriteReportStdinPaths(report, input) {
|
|
1279
1644
|
if (!input.file || !input.displayPath) {
|
|
1280
1645
|
return report;
|
package/bin/migrate.js
CHANGED
|
@@ -18,6 +18,7 @@ const IGNORED_RULES = new Map([
|
|
|
18
18
|
]);
|
|
19
19
|
const SCHEMA_URL = "https://raw.githubusercontent.com/fireairforce/utoo-lint/main/npm/utoo-lint/schema.json";
|
|
20
20
|
const JAVASCRIPT_CONFIG_LOADER_SCRIPT = `
|
|
21
|
+
import { writeFileSync } from "node:fs";
|
|
21
22
|
import { pathToFileURL } from "node:url";
|
|
22
23
|
|
|
23
24
|
const loaded = await import(pathToFileURL(process.argv[1]).href);
|
|
@@ -57,7 +58,7 @@ const json = JSON.stringify(strip(loaded.default ?? loaded));
|
|
|
57
58
|
if (json === undefined) {
|
|
58
59
|
throw new TypeError("config did not export a migratable value");
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
+
writeFileSync(3, json);
|
|
61
62
|
`;
|
|
62
63
|
|
|
63
64
|
export async function runMigrate(args) {
|
|
@@ -115,7 +116,7 @@ function parseEslintMigrateArgs(args) {
|
|
|
115
116
|
force: false,
|
|
116
117
|
from: undefined,
|
|
117
118
|
help: false,
|
|
118
|
-
output: "
|
|
119
|
+
output: "utlint.config.json",
|
|
119
120
|
print: false,
|
|
120
121
|
report: "text"
|
|
121
122
|
};
|
|
@@ -249,7 +250,8 @@ function loadEslintConfig(path, cwd) {
|
|
|
249
250
|
if (JAVASCRIPT_CONFIG_EXTENSIONS.has(extname(path))) {
|
|
250
251
|
const result = spawnSync(process.execPath, ["--input-type=module", "--eval", JAVASCRIPT_CONFIG_LOADER_SCRIPT, path], {
|
|
251
252
|
cwd,
|
|
252
|
-
encoding: "utf8"
|
|
253
|
+
encoding: "utf8",
|
|
254
|
+
stdio: ["ignore", "pipe", "pipe", "pipe"]
|
|
253
255
|
});
|
|
254
256
|
if (result.error) {
|
|
255
257
|
throw result.error;
|
|
@@ -257,7 +259,7 @@ function loadEslintConfig(path, cwd) {
|
|
|
257
259
|
if (result.status !== 0) {
|
|
258
260
|
throw new Error(`utoo-lint migrate eslint: unable to load ${path}: ${(result.stderr ?? "").trim() || "JavaScript config loader failed"}`);
|
|
259
261
|
}
|
|
260
|
-
return parseJson(result.
|
|
262
|
+
return parseJson(result.output?.[3] ?? "", path);
|
|
261
263
|
}
|
|
262
264
|
return parseJson(readFileSync(path, "utf8"), path);
|
|
263
265
|
}
|
|
@@ -335,7 +337,7 @@ function printMigrateHelp() {
|
|
|
335
337
|
utoo-lint migrate eslint [options]
|
|
336
338
|
|
|
337
339
|
Sources:
|
|
338
|
-
eslint Convert an ESLint config into
|
|
340
|
+
eslint Convert an ESLint config into utlint.config.json
|
|
339
341
|
|
|
340
342
|
Run "utoo-lint migrate eslint --help" for source-specific options.`);
|
|
341
343
|
}
|
|
@@ -346,7 +348,7 @@ function printEslintMigrateHelp() {
|
|
|
346
348
|
|
|
347
349
|
Options:
|
|
348
350
|
--from=PATH Read ESLint config from PATH
|
|
349
|
-
--output=PATH, -o PATH Write utoo config to PATH (default:
|
|
351
|
+
--output=PATH, -o PATH Write utoo config to PATH (default: utlint.config.json)
|
|
350
352
|
--force Overwrite the output file when it exists
|
|
351
353
|
--print Print the generated config instead of writing it
|
|
352
354
|
--report=text|json Select migration report format
|