@utoo/lint 0.3.9 → 0.4.0
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 +3 -0
- package/index.cjs +634 -278
- package/index.d.ts +11 -0
- package/index.js +634 -278
- package/lib/config-module.cjs +54 -0
- package/lib/estree-parser.cjs +662 -0
- package/lib/load-config.js +67 -1
- package/lib/plugin-runner.cjs +342 -0
- package/package.json +12 -7
package/index.cjs
CHANGED
|
@@ -9,8 +9,12 @@ const { createLegacyConfigResolver } = require("./lib/legacy-config.cjs");
|
|
|
9
9
|
const { formatESLintResults } = require("./lib/stylish-formatter.cjs");
|
|
10
10
|
const {
|
|
11
11
|
findConfigPath: findConfigPathFromDirectory,
|
|
12
|
+
isExecutableConfigPath,
|
|
12
13
|
readConfig
|
|
13
14
|
} = require("./lib/config-loader.cjs");
|
|
15
|
+
const configModule = require("./lib/config-module.cjs");
|
|
16
|
+
const estreeParser = require("./lib/estree-parser.cjs");
|
|
17
|
+
const pluginRunner = require("./lib/plugin-runner.cjs");
|
|
14
18
|
|
|
15
19
|
const version = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8")).version;
|
|
16
20
|
|
|
@@ -25,10 +29,6 @@ const JS_KEYWORDS = new Set([
|
|
|
25
29
|
"instanceof", "let", "new", "return", "super", "switch", "this", "throw", "try", "typeof",
|
|
26
30
|
"var", "void", "while", "with", "yield"
|
|
27
31
|
]);
|
|
28
|
-
const AST_TRAVERSAL_SKIP_KEYS = new Set([
|
|
29
|
-
"type", "parent", "loc", "range", "tokens", "comments", "leadingComments", "trailingComments",
|
|
30
|
-
"innerComments", "raw", "value", "name", "regex", "bigint", "errors"
|
|
31
|
-
]);
|
|
32
32
|
const RULE_TESTER_INITIAL_CONFIG = { rules: {} };
|
|
33
33
|
let ruleTesterDefaultConfig = { rules: {} };
|
|
34
34
|
let ruleTesterDescribe = null;
|
|
@@ -480,6 +480,7 @@ const NATIVE_ONLY_RULE_IDS = [
|
|
|
480
480
|
"semantic-errors"
|
|
481
481
|
];
|
|
482
482
|
const NATIVE_RULE_IDS = [...BUILTIN_RULE_IDS, ...NATIVE_ONLY_RULE_IDS];
|
|
483
|
+
const NATIVE_RULE_ID_SET = new Set(NATIVE_RULE_IDS);
|
|
483
484
|
const FIXABLE_BUILTIN_RULE_IDS = new Set([
|
|
484
485
|
"@typescript-eslint/no-explicit-any",
|
|
485
486
|
"jest/no-deprecated-functions",
|
|
@@ -607,15 +608,15 @@ class UtooLint {
|
|
|
607
608
|
throw new Error("'results' must be an array");
|
|
608
609
|
}
|
|
609
610
|
const options = eslintConstructorOptions(this.options);
|
|
610
|
-
const customRuleConfig = [options.baseConfig, options.overrideConfig].filter(Boolean);
|
|
611
611
|
const rulesByFilePath = new Map();
|
|
612
|
+
const cachedOptions = withConfigCache(options);
|
|
612
613
|
return rulesMetaForResults(results, (ruleId, result) => {
|
|
613
|
-
if (
|
|
614
|
+
if (typeof result.filePath !== "string") {
|
|
614
615
|
return undefined;
|
|
615
616
|
}
|
|
616
617
|
const filePath = normalizeESLintFilePath(result.filePath, options.cwd);
|
|
617
618
|
if (!rulesByFilePath.has(filePath)) {
|
|
618
|
-
rulesByFilePath.set(filePath,
|
|
619
|
+
rulesByFilePath.set(filePath, jsPluginRuleMapForFile(cachedOptions, filePath));
|
|
619
620
|
}
|
|
620
621
|
return rulesByFilePath.get(filePath).get(ruleId)?.meta;
|
|
621
622
|
});
|
|
@@ -1229,21 +1230,210 @@ function lintFiles(paths, options = {}) {
|
|
|
1229
1230
|
}
|
|
1230
1231
|
|
|
1231
1232
|
const nativeFlatOptions = nativeFlatConfigOptions(options);
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1233
|
+
const report = nativeFlatOptions
|
|
1234
|
+
? runNativeLintReport(lintPaths, nativeFlatOptions)
|
|
1235
|
+
: runNativeConfigRunReports(lintPaths, options);
|
|
1236
|
+
// The flat-config path resolves one config for the whole run; only the
|
|
1237
|
+
// per-file path has already discovered (and cached) nested configs.
|
|
1238
|
+
appendPluginRuleDiagnostics(report, options, { perFileConfig: !nativeFlatOptions });
|
|
1239
|
+
return finalizeLintReport(report, ignoredDiagnostics, options);
|
|
1240
|
+
}
|
|
1236
1241
|
|
|
1242
|
+
function runNativeConfigRunReports(lintPaths, options) {
|
|
1237
1243
|
const configRuns = nativeConfigRunsForFiles(expandNativeConfigRunPaths(lintPaths, options), options);
|
|
1238
1244
|
if (configRuns) {
|
|
1239
|
-
|
|
1245
|
+
return mergeNativeReports(
|
|
1240
1246
|
configRuns.map(({ paths: runPaths, options: runOptions }) => runNativeLintReport(runPaths, runOptions))
|
|
1241
1247
|
);
|
|
1242
|
-
return finalizeLintReport(report, ignoredDiagnostics, options);
|
|
1243
1248
|
}
|
|
1244
1249
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1250
|
+
return runNativeLintReport(lintPaths, options);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function pluginFixModeForOptions(options) {
|
|
1254
|
+
const extraArgs = Array.isArray(options.extraArgs) ? options.extraArgs : [];
|
|
1255
|
+
if (options.fix || extraArgs.includes("--fix-dry-run")) {
|
|
1256
|
+
return "dry-run";
|
|
1257
|
+
}
|
|
1258
|
+
return extraArgs.includes("--fix") ? "write" : null;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
function pluginMessageToDiagnostic(message, filePath) {
|
|
1262
|
+
const toFix = (fix) => ({ range: [fix.range[0], fix.range[1]], text: fix.text });
|
|
1263
|
+
const diagnostic = {
|
|
1264
|
+
filePath,
|
|
1265
|
+
line: message.line,
|
|
1266
|
+
column: message.column,
|
|
1267
|
+
severity: message.severity === 2 ? "error" : "warning",
|
|
1268
|
+
message: message.message,
|
|
1269
|
+
ruleId: message.ruleId,
|
|
1270
|
+
fixes: ruleFixItems(message.fix).map(toFix),
|
|
1271
|
+
suggestions: (message.suggestions ?? []).map((suggestion) => ({
|
|
1272
|
+
desc: suggestion.desc,
|
|
1273
|
+
fix: ruleFixItems(suggestion.fix).map(toFix)
|
|
1274
|
+
}))
|
|
1275
|
+
};
|
|
1276
|
+
if (typeof message.endLine === "number") {
|
|
1277
|
+
diagnostic.endLine = message.endLine;
|
|
1278
|
+
}
|
|
1279
|
+
if (typeof message.endColumn === "number") {
|
|
1280
|
+
diagnostic.endColumn = message.endColumn;
|
|
1281
|
+
}
|
|
1282
|
+
return diagnostic;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function sortReportDiagnostics(report) {
|
|
1286
|
+
const order = new Map((report.filePaths ?? []).map((filePath, index) => [filePath, index]));
|
|
1287
|
+
const rank = (diagnostic) => order.get(diagnostic.filePath) ?? Number.MAX_SAFE_INTEGER;
|
|
1288
|
+
const compare = (left, right) => rank(left) - rank(right)
|
|
1289
|
+
|| (left.line ?? 0) - (right.line ?? 0)
|
|
1290
|
+
|| (left.column ?? 0) - (right.column ?? 0);
|
|
1291
|
+
report.diagnostics = [...(report.diagnostics ?? [])].sort(compare);
|
|
1292
|
+
report.suppressedDiagnostics = [...(report.suppressedDiagnostics ?? [])].sort(compare);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// Runs the ESLint plugin rules a config mounts and merges their diagnostics
|
|
1296
|
+
// into the native report. Plugin rules never reach the native binary; see
|
|
1297
|
+
// `withoutJsPluginRules`.
|
|
1298
|
+
function appendPluginRuleDiagnostics(report, options, { perFileConfig = true } = {}) {
|
|
1299
|
+
if (options.noConfig && !options.baseConfig && !options.overrideConfig) {
|
|
1300
|
+
return report;
|
|
1301
|
+
}
|
|
1302
|
+
const filePaths = report.filePaths ?? [];
|
|
1303
|
+
if (filePaths.length === 0 || !anyConfigDeclaresPlugins(options)) {
|
|
1304
|
+
return report;
|
|
1305
|
+
}
|
|
1306
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1307
|
+
const singleMatchPath = filePaths.length === 1
|
|
1308
|
+
? options.filePath ?? options.filename ?? options.pluginTextFilePath
|
|
1309
|
+
: undefined;
|
|
1310
|
+
const fixMode = pluginFixModeForOptions(options);
|
|
1311
|
+
let appended = false;
|
|
1312
|
+
|
|
1313
|
+
for (const filePath of filePaths) {
|
|
1314
|
+
const matchPath = singleMatchPath ?? filePath;
|
|
1315
|
+
const ruleMap = jsPluginRuleMapForFile(options, matchPath, perFileConfig);
|
|
1316
|
+
if (ruleMap.size === 0) {
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
const ruleEntries = pluginRuleEntriesForFile(options, matchPath, ruleMap, perFileConfig);
|
|
1320
|
+
if (ruleEntries.length === 0) {
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// The native report echoes targets as given, so relative paths resolve
|
|
1325
|
+
// against the lint cwd rather than the process cwd.
|
|
1326
|
+
const absolutePath = normalizeESLintFilePath(filePath, cwd);
|
|
1327
|
+
const outputIndex = (report.outputs ?? []).findIndex((fixed) => fixed.filePath === filePath);
|
|
1328
|
+
let code = outputIndex === -1 ? undefined : report.outputs[outputIndex].output;
|
|
1329
|
+
if (typeof code !== "string") {
|
|
1330
|
+
try {
|
|
1331
|
+
code = readFileSync(absolutePath, "utf8");
|
|
1332
|
+
} catch {
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
const config = pluginConfigSourcesForFile(options, matchPath, perFileConfig).map((source) => source.config);
|
|
1338
|
+
const parserOptions = { ...parserOptionsForConfig(config, matchPath, cwd), filePath: matchPath, requireParser: true };
|
|
1339
|
+
const lintOptions = {
|
|
1340
|
+
cwd,
|
|
1341
|
+
filename: matchPath,
|
|
1342
|
+
config: calculatedConfig({ cwd, noConfig: true, baseConfig: config }, matchPath)
|
|
1343
|
+
};
|
|
1344
|
+
const lintOnce = (text) => {
|
|
1345
|
+
const sourceCode = createLinterSourceCode(text, null, parserOptions);
|
|
1346
|
+
return applyDisableDirectives(runCustomLinterRules(ruleEntries, sourceCode, lintOptions), sourceCode);
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
let filtered = lintOnce(code);
|
|
1350
|
+
let relintedFromReport = false;
|
|
1351
|
+
if (fixMode) {
|
|
1352
|
+
let changed = false;
|
|
1353
|
+
for (let pass = 0; pass < MAX_AUTOFIX_PASSES; pass += 1) {
|
|
1354
|
+
const output = applyMessageFixes(code, filtered.messages);
|
|
1355
|
+
if (output === code) {
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
changed = true;
|
|
1359
|
+
code = output;
|
|
1360
|
+
filtered = lintOnce(code);
|
|
1361
|
+
}
|
|
1362
|
+
if (changed) {
|
|
1363
|
+
// Plugin fixes changed the text, so the native diagnostics for this
|
|
1364
|
+
// file describe stale source. Re-lint the fixed text through the whole
|
|
1365
|
+
// pipeline (native rules, plugin rules, further fixes) and replace this
|
|
1366
|
+
// file's diagnostics with the result.
|
|
1367
|
+
const depth = options.pluginFixDepth ?? 0;
|
|
1368
|
+
if (depth < MAX_AUTOFIX_PASSES) {
|
|
1369
|
+
const relinted = lintText(code, {
|
|
1370
|
+
...options,
|
|
1371
|
+
filePath: matchPath,
|
|
1372
|
+
filename: undefined,
|
|
1373
|
+
config: pluginConfigPathForFile(options, matchPath, perFileConfig) ?? options.config,
|
|
1374
|
+
fix: true,
|
|
1375
|
+
extraArgs: (options.extraArgs ?? []).filter((arg) => arg !== "--fix" && arg !== "--fix-dry-run"),
|
|
1376
|
+
useDefaultConfigTargets: undefined,
|
|
1377
|
+
pluginTextFilePath: undefined,
|
|
1378
|
+
pluginFixDepth: depth + 1
|
|
1379
|
+
});
|
|
1380
|
+
code = relinted.outputs?.find((fixed) => fixed.filePath === matchPath)?.output ?? code;
|
|
1381
|
+
filtered = {
|
|
1382
|
+
messages: relinted.diagnostics ?? [],
|
|
1383
|
+
suppressedMessages: relinted.suppressedDiagnostics ?? []
|
|
1384
|
+
};
|
|
1385
|
+
report.diagnostics = (report.diagnostics ?? []).filter((diagnostic) => diagnostic.filePath !== filePath);
|
|
1386
|
+
report.suppressedDiagnostics = (report.suppressedDiagnostics ?? []).filter((diagnostic) => diagnostic.filePath !== filePath);
|
|
1387
|
+
relintedFromReport = true;
|
|
1388
|
+
}
|
|
1389
|
+
if (fixMode === "write") {
|
|
1390
|
+
writeFileSync(absolutePath, code);
|
|
1391
|
+
} else if (outputIndex === -1) {
|
|
1392
|
+
report.outputs = [...(report.outputs ?? []), { filePath, output: code }];
|
|
1393
|
+
} else {
|
|
1394
|
+
report.outputs[outputIndex] = { ...report.outputs[outputIndex], output: code };
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
if (relintedFromReport) {
|
|
1400
|
+
// Already native-shaped diagnostics from the nested lint; only the
|
|
1401
|
+
// file path needs to match this report.
|
|
1402
|
+
report.diagnostics = [
|
|
1403
|
+
...(report.diagnostics ?? []),
|
|
1404
|
+
...filtered.messages.map((diagnostic) => ({ ...diagnostic, filePath }))
|
|
1405
|
+
];
|
|
1406
|
+
report.suppressedDiagnostics = [
|
|
1407
|
+
...(report.suppressedDiagnostics ?? []),
|
|
1408
|
+
...filtered.suppressedMessages.map((diagnostic) => ({ ...diagnostic, filePath }))
|
|
1409
|
+
];
|
|
1410
|
+
appended = true;
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
if (filtered.messages.length === 0 && filtered.suppressedMessages.length === 0) {
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
report.diagnostics = [
|
|
1417
|
+
...(report.diagnostics ?? []),
|
|
1418
|
+
...filtered.messages.map((message) => pluginMessageToDiagnostic(message, filePath))
|
|
1419
|
+
];
|
|
1420
|
+
report.suppressedDiagnostics = [
|
|
1421
|
+
...(report.suppressedDiagnostics ?? []),
|
|
1422
|
+
...filtered.suppressedMessages.map((message) => ({
|
|
1423
|
+
...pluginMessageToDiagnostic(message, filePath),
|
|
1424
|
+
suppression: {
|
|
1425
|
+
kind: message.suppressions?.[0]?.kind ?? "directive",
|
|
1426
|
+
justification: message.suppressions?.[0]?.justification ?? ""
|
|
1427
|
+
}
|
|
1428
|
+
}))
|
|
1429
|
+
];
|
|
1430
|
+
appended = true;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
if (appended) {
|
|
1434
|
+
sortReportDiagnostics(report);
|
|
1435
|
+
}
|
|
1436
|
+
return report;
|
|
1247
1437
|
}
|
|
1248
1438
|
|
|
1249
1439
|
function nativeFlatConfigOptions(options) {
|
|
@@ -1273,6 +1463,10 @@ function nativeFlatConfigOptions(options) {
|
|
|
1273
1463
|
return undefined;
|
|
1274
1464
|
}
|
|
1275
1465
|
|
|
1466
|
+
const jsPluginRuleIds = jsPluginRuleIdsFromConfigs(config);
|
|
1467
|
+
if (jsPluginRuleIds.size > 0) {
|
|
1468
|
+
config = configWithoutCustomRules(config, jsPluginRuleIds);
|
|
1469
|
+
}
|
|
1276
1470
|
if (configCanUseNativeRulesFastPath(config)) {
|
|
1277
1471
|
return undefined;
|
|
1278
1472
|
}
|
|
@@ -1399,6 +1593,9 @@ function nativeConfigRunsForFiles(paths, options) {
|
|
|
1399
1593
|
if (configured && options.rules) {
|
|
1400
1594
|
rules = selectedRulesWithConfigOptions(rules, options.rules);
|
|
1401
1595
|
}
|
|
1596
|
+
if (configured) {
|
|
1597
|
+
rules = withoutJsPluginRules(rules, jsPluginRuleIdsForOptions(options, matchPath));
|
|
1598
|
+
}
|
|
1402
1599
|
const signature = configured ? stableConfigSignature({ rules, settings }) : "<native-defaults>";
|
|
1403
1600
|
if (!groups.has(signature)) {
|
|
1404
1601
|
groups.set(signature, { paths: [], rules, settings, configured });
|
|
@@ -1571,7 +1768,8 @@ function lintText(code, options = {}) {
|
|
|
1571
1768
|
cwd: options.cwd,
|
|
1572
1769
|
config: options.config ?? discoveredConfig,
|
|
1573
1770
|
noConfig: options.noConfig,
|
|
1574
|
-
deferDiagnosticConfigFiltering: true
|
|
1771
|
+
deferDiagnosticConfigFiltering: true,
|
|
1772
|
+
pluginTextFilePath: requestedPath
|
|
1575
1773
|
});
|
|
1576
1774
|
report.filePaths = (report.filePaths ?? []).map((filePath) => (filePath === tempFile ? requestedPath : filePath));
|
|
1577
1775
|
report.outputs = (report.outputs ?? []).map((fixed) => ({
|
|
@@ -1613,11 +1811,12 @@ function buildNativeLintArgs(paths, options) {
|
|
|
1613
1811
|
const configForRuleSelection = options.noConfig
|
|
1614
1812
|
? undefined
|
|
1615
1813
|
: options.config ? readConfig(options.config, options.cwd, options.configCache) : undefined;
|
|
1814
|
+
const jsPluginRuleIds = jsPluginRuleIdsFromConfigs(configForRuleSelection, options.baseConfig, options.overrideConfig);
|
|
1616
1815
|
const enabledRules = enabledRuleNamesFromConfigs(
|
|
1617
1816
|
configForRuleSelection,
|
|
1618
1817
|
options.baseConfig,
|
|
1619
1818
|
options.overrideConfig
|
|
1620
|
-
);
|
|
1819
|
+
).filter((rule) => !jsPluginRuleIds.has(rule));
|
|
1621
1820
|
if (enabledRules.length > 0) {
|
|
1622
1821
|
cliArgs.push(`--rules=${enabledRules.join(",")}`);
|
|
1623
1822
|
}
|
|
@@ -2095,9 +2294,11 @@ function withTemporaryConfig(options, callback) {
|
|
|
2095
2294
|
? readConfig(fileConfigPath, options.cwd, options.configCache)
|
|
2096
2295
|
: undefined;
|
|
2097
2296
|
const configs = [options.baseConfig, fileConfig, options.overrideConfig];
|
|
2098
|
-
const
|
|
2099
|
-
|
|
2100
|
-
: runtimeRulesFromConfigs(...configs)
|
|
2297
|
+
const jsPluginRuleIds = jsPluginRuleIdsFromConfigs(...configs);
|
|
2298
|
+
const rules = withoutJsPluginRules(
|
|
2299
|
+
shouldMaterializeFileConfig ? materializedRulesFromConfigs(...configs) : runtimeRulesFromConfigs(...configs),
|
|
2300
|
+
jsPluginRuleIds
|
|
2301
|
+
);
|
|
2101
2302
|
const settings = configs.reduce(
|
|
2102
2303
|
(result, config) => ({
|
|
2103
2304
|
...result,
|
|
@@ -2116,7 +2317,7 @@ function withTemporaryConfig(options, callback) {
|
|
|
2116
2317
|
}
|
|
2117
2318
|
return callback(options);
|
|
2118
2319
|
}
|
|
2119
|
-
const enabledRules = enabledRuleNamesFromConfigs(...configs);
|
|
2320
|
+
const enabledRules = enabledRuleNamesFromConfigs(...configs).filter((rule) => !jsPluginRuleIds.has(rule));
|
|
2120
2321
|
const hasExplicitOffRules = Object.values(rules).some((value) => ruleConfigSeverity(value) === 0);
|
|
2121
2322
|
if (!shouldMaterializeFileConfig && !hasSettings && !hasRuleOptions(rules) && !hasExplicitOffRules && !options.forceMaterializedConfig) {
|
|
2122
2323
|
return callback({
|
|
@@ -2245,14 +2446,14 @@ class Linter {
|
|
|
2245
2446
|
const verifyOptions = typeof options === "string" ? { filename: options } : { ...options };
|
|
2246
2447
|
const filePath = verifyOptions.filename ?? verifyOptions.filePath ?? "input.js";
|
|
2247
2448
|
const normalizedFilePath = normalizeESLintFilePath(filePath, verifyOptions.cwd);
|
|
2449
|
+
const customRuleMap = customRuleMapForConfig(config, this.rules, normalizedFilePath, verifyOptions.cwd);
|
|
2450
|
+
const customRuleFilterMap = customRuleMapForConfig(config, this.rules);
|
|
2451
|
+
const customRules = customRuleEntriesForConfig(config, customRuleMap, normalizedFilePath, verifyOptions.cwd);
|
|
2248
2452
|
const sourceCode = createLinterSourceCode(
|
|
2249
2453
|
code,
|
|
2250
2454
|
parserForConfig(config, this.parsers, normalizedFilePath, verifyOptions.cwd),
|
|
2251
|
-
parserOptionsForConfig(config, normalizedFilePath, verifyOptions.cwd)
|
|
2455
|
+
{ ...parserOptionsForConfig(config, normalizedFilePath, verifyOptions.cwd), requireParser: customRules.length > 0 }
|
|
2252
2456
|
);
|
|
2253
|
-
const customRuleMap = customRuleMapForConfig(config, this.rules, normalizedFilePath, verifyOptions.cwd);
|
|
2254
|
-
const customRuleFilterMap = customRuleMapForConfig(config, this.rules);
|
|
2255
|
-
const customRules = customRuleEntriesForConfig(config, customRuleMap, normalizedFilePath, verifyOptions.cwd);
|
|
2256
2457
|
const nativeConfig = configWithoutCustomRules(config, customRuleFilterMap);
|
|
2257
2458
|
const lintOptions = {
|
|
2258
2459
|
cwd: verifyOptions.cwd,
|
|
@@ -2282,10 +2483,10 @@ class Linter {
|
|
|
2282
2483
|
];
|
|
2283
2484
|
this.times = { passes: [] };
|
|
2284
2485
|
this.fixPassCount = 0;
|
|
2285
|
-
return [
|
|
2486
|
+
return sortLintMessages([
|
|
2286
2487
|
...nativeRuleFilter.messages,
|
|
2287
2488
|
...customRuleFilter.messages
|
|
2288
|
-
];
|
|
2489
|
+
]);
|
|
2289
2490
|
}
|
|
2290
2491
|
|
|
2291
2492
|
verifyAndFix(code, config = {}, options = {}) {
|
|
@@ -2294,7 +2495,7 @@ class Linter {
|
|
|
2294
2495
|
let fixPassCount = 0;
|
|
2295
2496
|
for (let pass = 0; pass < MAX_AUTOFIX_PASSES; pass += 1) {
|
|
2296
2497
|
const messages = this.verify(output, config, options);
|
|
2297
|
-
const fixedOutput =
|
|
2498
|
+
const fixedOutput = applyMessageFixes(output, messages);
|
|
2298
2499
|
if (fixedOutput === output) {
|
|
2299
2500
|
this.fixPassCount = fixPassCount;
|
|
2300
2501
|
return {
|
|
@@ -2377,9 +2578,17 @@ function parserForConfig(config, parsers, filePath, cwd) {
|
|
|
2377
2578
|
|
|
2378
2579
|
function parserOptionsForConfig(config, filePath, cwd) {
|
|
2379
2580
|
const configData = configDataFromConfig(config, filePath, cwd);
|
|
2380
|
-
|
|
2581
|
+
const languageOptions = configData.languageOptions ?? {};
|
|
2582
|
+
const parserOptions = {
|
|
2381
2583
|
...(configData.parserOptions ?? {}),
|
|
2382
|
-
...(
|
|
2584
|
+
...(languageOptions.parserOptions ?? {})
|
|
2585
|
+
};
|
|
2586
|
+
return {
|
|
2587
|
+
...parserOptions,
|
|
2588
|
+
sourceType: languageOptions.sourceType ?? parserOptions.sourceType,
|
|
2589
|
+
ecmaVersion: languageOptions.ecmaVersion ?? parserOptions.ecmaVersion,
|
|
2590
|
+
ecmaFeatures: parserOptions.ecmaFeatures ?? {},
|
|
2591
|
+
globals: { ...(configData.globals ?? {}), ...(languageOptions.globals ?? {}) },
|
|
2383
2592
|
filename: filePath,
|
|
2384
2593
|
filePath,
|
|
2385
2594
|
cwd
|
|
@@ -2425,13 +2634,176 @@ function pluginRulesForConfig(config, filePath, cwd) {
|
|
|
2425
2634
|
continue;
|
|
2426
2635
|
}
|
|
2427
2636
|
for (const [ruleName, rule] of Object.entries(plugin.rules)) {
|
|
2428
|
-
|
|
2637
|
+
const ruleId = `${pluginName}/${ruleName}`;
|
|
2638
|
+
// A native rule takes precedence over a plugin rule mounted under the
|
|
2639
|
+
// same id, so a config copied from ESLint keeps its native speed.
|
|
2640
|
+
if (NATIVE_RULE_ID_SET.has(ruleId)) {
|
|
2641
|
+
continue;
|
|
2642
|
+
}
|
|
2643
|
+
rules.set(ruleId, rule);
|
|
2429
2644
|
}
|
|
2430
2645
|
}
|
|
2431
2646
|
}
|
|
2432
2647
|
return rules;
|
|
2433
2648
|
}
|
|
2434
2649
|
|
|
2650
|
+
const pluginRuleIdsByConfig = new WeakMap();
|
|
2651
|
+
|
|
2652
|
+
function jsPluginRuleIdsFromConfigs(...configs) {
|
|
2653
|
+
const ids = new Set();
|
|
2654
|
+
for (const config of configs) {
|
|
2655
|
+
if (!config || typeof config !== "object") {
|
|
2656
|
+
continue;
|
|
2657
|
+
}
|
|
2658
|
+
let ruleIds = pluginRuleIdsByConfig.get(config);
|
|
2659
|
+
if (!ruleIds) {
|
|
2660
|
+
ruleIds = [...pluginRulesForConfig(config).keys()];
|
|
2661
|
+
pluginRuleIdsByConfig.set(config, ruleIds);
|
|
2662
|
+
}
|
|
2663
|
+
for (const ruleId of ruleIds) {
|
|
2664
|
+
ids.add(ruleId);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
return ids;
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
function withoutJsPluginRules(rules, jsPluginRuleIds) {
|
|
2671
|
+
if (!rules || typeof rules !== "object" || jsPluginRuleIds.size === 0) {
|
|
2672
|
+
return rules;
|
|
2673
|
+
}
|
|
2674
|
+
const filtered = {};
|
|
2675
|
+
for (const [ruleId, ruleConfig] of Object.entries(rules)) {
|
|
2676
|
+
if (!jsPluginRuleIds.has(ruleId)) {
|
|
2677
|
+
filtered[ruleId] = ruleConfig;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
return filtered;
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
function configDeclaresPlugins(config) {
|
|
2684
|
+
if (Array.isArray(config)) {
|
|
2685
|
+
return config.some((entry) => configDeclaresPlugins(entry));
|
|
2686
|
+
}
|
|
2687
|
+
return Boolean(
|
|
2688
|
+
config
|
|
2689
|
+
&& typeof config === "object"
|
|
2690
|
+
&& config.plugins
|
|
2691
|
+
&& typeof config.plugins === "object"
|
|
2692
|
+
&& !Array.isArray(config.plugins)
|
|
2693
|
+
&& Object.keys(config.plugins).length > 0
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
// Cheap pre-check so runs without plugins skip the per-file plugin work. Every
|
|
2698
|
+
// config the native run consulted was read through `options.configCache`.
|
|
2699
|
+
function anyConfigDeclaresPlugins(options) {
|
|
2700
|
+
if (configDeclaresPlugins(options.baseConfig) || configDeclaresPlugins(options.overrideConfig)) {
|
|
2701
|
+
return true;
|
|
2702
|
+
}
|
|
2703
|
+
if (options.noConfig) {
|
|
2704
|
+
return false;
|
|
2705
|
+
}
|
|
2706
|
+
let sawConfig = false;
|
|
2707
|
+
for (const config of options.configCache?.values() ?? []) {
|
|
2708
|
+
if (config && typeof config === "object") {
|
|
2709
|
+
sawConfig = true;
|
|
2710
|
+
if (configDeclaresPlugins(config)) {
|
|
2711
|
+
return true;
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
if (sawConfig) {
|
|
2716
|
+
return false;
|
|
2717
|
+
}
|
|
2718
|
+
const configPath = configPathForOptions(options);
|
|
2719
|
+
return Boolean(configPath) && configDeclaresPlugins(readConfig(configPath, options.cwd, options.configCache));
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
// The native engine receives the JSON form of a config module. Plugin objects
|
|
2723
|
+
// (and their `create()` functions) only survive an in-process import, so a
|
|
2724
|
+
// config module that mounts plugins is loaded again in this process.
|
|
2725
|
+
function liveConfigForPath(configPath, options) {
|
|
2726
|
+
const config = readConfig(configPath, options.cwd, options.configCache);
|
|
2727
|
+
if (!isExecutableConfigPath(configPath) || !configDeclaresPlugins(config)) {
|
|
2728
|
+
return config;
|
|
2729
|
+
}
|
|
2730
|
+
return configModule.readConfigModule(configPath, options.cwd);
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
function pluginConfigPathForFile(options, matchPath, perFileConfig = true) {
|
|
2734
|
+
if (options.noConfig) {
|
|
2735
|
+
return undefined;
|
|
2736
|
+
}
|
|
2737
|
+
return perFileConfig && matchPath ? configPathForFile(options, matchPath) : configPathForOptions(options);
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
function pluginConfigSourcesForFile(options, matchPath, perFileConfig = true) {
|
|
2741
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2742
|
+
const sources = [];
|
|
2743
|
+
if (options.baseConfig) {
|
|
2744
|
+
sources.push({ config: options.baseConfig, cwd });
|
|
2745
|
+
}
|
|
2746
|
+
const configPath = pluginConfigPathForFile(options, matchPath, perFileConfig);
|
|
2747
|
+
if (configPath) {
|
|
2748
|
+
sources.push({ config: liveConfigForPath(configPath, options), cwd: dirname(configPath) });
|
|
2749
|
+
}
|
|
2750
|
+
if (options.overrideConfig) {
|
|
2751
|
+
sources.push({ config: options.overrideConfig, cwd });
|
|
2752
|
+
}
|
|
2753
|
+
return sources;
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
function jsPluginRuleIdsForOptions(options, matchPath) {
|
|
2757
|
+
const configs = [options.baseConfig, options.overrideConfig];
|
|
2758
|
+
if (!options.noConfig) {
|
|
2759
|
+
const configPath = matchPath ? configPathForFile(options, matchPath) : configPathForOptions(options);
|
|
2760
|
+
if (configPath) {
|
|
2761
|
+
configs.push(readConfig(configPath, options.cwd, options.configCache));
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
return jsPluginRuleIdsFromConfigs(...configs);
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
function jsPluginRuleMapForFile(options, matchPath, perFileConfig = true) {
|
|
2768
|
+
const rules = new Map();
|
|
2769
|
+
for (const { config, cwd } of pluginConfigSourcesForFile(options, matchPath, perFileConfig)) {
|
|
2770
|
+
for (const [ruleId, rule] of pluginRulesForConfig(config, matchPath, cwd)) {
|
|
2771
|
+
rules.set(ruleId, rule);
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
return rules;
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
function pluginRuleEntriesForFile(options, matchPath, ruleMap, perFileConfig = true) {
|
|
2778
|
+
const entries = new Map();
|
|
2779
|
+
for (const { config, cwd } of pluginConfigSourcesForFile(options, matchPath, perFileConfig)) {
|
|
2780
|
+
for (const [ruleId, ruleConfig] of Object.entries(rulesFromConfig(config, matchPath, cwd))) {
|
|
2781
|
+
if (!ruleMap.has(ruleId)) {
|
|
2782
|
+
continue;
|
|
2783
|
+
}
|
|
2784
|
+
const severity = ruleConfigSeverity(ruleConfig);
|
|
2785
|
+
if (severity === 0) {
|
|
2786
|
+
entries.delete(ruleId);
|
|
2787
|
+
continue;
|
|
2788
|
+
}
|
|
2789
|
+
entries.set(ruleId, {
|
|
2790
|
+
ruleId,
|
|
2791
|
+
rule: ruleMap.get(ruleId),
|
|
2792
|
+
severity,
|
|
2793
|
+
options: Array.isArray(ruleConfig) ? ruleConfig.slice(1) : []
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
if (options.rules) {
|
|
2798
|
+
// `--rules` disables everything first and then enables only the listed
|
|
2799
|
+
// rules, keeping configured severities and options like the native path.
|
|
2800
|
+
return Object.keys(rulesFromNativeRuleList(options.rules))
|
|
2801
|
+
.filter((ruleId) => ruleMap.has(ruleId))
|
|
2802
|
+
.map((ruleId) => entries.get(ruleId) ?? { ruleId, rule: ruleMap.get(ruleId), severity: 1, options: [] });
|
|
2803
|
+
}
|
|
2804
|
+
return [...entries.values()];
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2435
2807
|
function matchingConfigEntries(config, filePath = undefined, cwd = undefined) {
|
|
2436
2808
|
if (!config) {
|
|
2437
2809
|
return [];
|
|
@@ -2483,7 +2855,7 @@ function appendCustomLintTextMessages(results, code, filePath, config, rules, op
|
|
|
2483
2855
|
}
|
|
2484
2856
|
let result = results.find((item) => item.filePath === filePath);
|
|
2485
2857
|
const effectiveCode = typeof result?.output === "string" ? result.output : code;
|
|
2486
|
-
const sourceCode = createLinterSourceCode(effectiveCode);
|
|
2858
|
+
const sourceCode = createLinterSourceCode(effectiveCode, null, { ...parserOptionsForConfig(config, filePath, options.cwd), requireParser: true });
|
|
2487
2859
|
const messages = runCustomLinterRules(customRules, sourceCode, {
|
|
2488
2860
|
cwd: options.cwd,
|
|
2489
2861
|
filename: filePath,
|
|
@@ -2498,13 +2870,74 @@ function appendCustomLintTextMessages(results, code, filePath, config, rules, op
|
|
|
2498
2870
|
result = emptyESLintResult(filePath, effectiveCode);
|
|
2499
2871
|
results.push(result);
|
|
2500
2872
|
}
|
|
2501
|
-
result
|
|
2873
|
+
if (relintAfterCustomFixes(result, effectiveCode, filePath, customRules, config, rules, options)) {
|
|
2874
|
+
return results;
|
|
2875
|
+
}
|
|
2876
|
+
result.messages = sortLintMessages([...result.messages, ...filtered.messages]);
|
|
2502
2877
|
result.suppressedMessages.push(...filtered.suppressedMessages);
|
|
2503
2878
|
applyResultFixes(result, effectiveCode, options);
|
|
2504
2879
|
finalizeESLintResult(result);
|
|
2505
2880
|
return results;
|
|
2506
2881
|
}
|
|
2507
2882
|
|
|
2883
|
+
// When custom fixes change a file, the native messages computed earlier are
|
|
2884
|
+
// stale. Re-lint the fixed text with native rules and custom rules together
|
|
2885
|
+
// until neither changes it, then rebuild the result from that final pass.
|
|
2886
|
+
function relintAfterCustomFixes(result, code, filePath, customRules, config, rules, options) {
|
|
2887
|
+
if (!options.fix) {
|
|
2888
|
+
return false;
|
|
2889
|
+
}
|
|
2890
|
+
const nativeOptions = {
|
|
2891
|
+
...lintOptionsWithoutCustomRules(options, rules),
|
|
2892
|
+
filePath,
|
|
2893
|
+
filename: undefined,
|
|
2894
|
+
fix: true,
|
|
2895
|
+
useDefaultConfigTargets: undefined,
|
|
2896
|
+
pluginTextFilePath: undefined
|
|
2897
|
+
};
|
|
2898
|
+
const parserOptions = { ...parserOptionsForConfig(config, filePath, options.cwd), requireParser: true };
|
|
2899
|
+
const customLintOptions = {
|
|
2900
|
+
cwd: options.cwd,
|
|
2901
|
+
filename: filePath,
|
|
2902
|
+
config: calculatedConfig({ cwd: options.cwd, noConfig: true, baseConfig: options.baseConfig, overrideConfig: options.overrideConfig }, filePath)
|
|
2903
|
+
};
|
|
2904
|
+
const lintCustom = (text) => {
|
|
2905
|
+
const sourceCode = createLinterSourceCode(text, null, parserOptions);
|
|
2906
|
+
return applyDisableDirectives(runCustomLinterRules(customRules, sourceCode, customLintOptions), sourceCode);
|
|
2907
|
+
};
|
|
2908
|
+
|
|
2909
|
+
let output = code;
|
|
2910
|
+
let custom = lintCustom(output);
|
|
2911
|
+
let fixedOutput = applyMessageFixes(output, custom.messages);
|
|
2912
|
+
if (fixedOutput === output) {
|
|
2913
|
+
return false;
|
|
2914
|
+
}
|
|
2915
|
+
let nativeReport;
|
|
2916
|
+
for (let pass = 0; pass < MAX_AUTOFIX_PASSES && fixedOutput !== output; pass += 1) {
|
|
2917
|
+
nativeReport = lintText(fixedOutput, nativeOptions);
|
|
2918
|
+
output = nativeReport.outputs?.find((fixed) => fixed.filePath === filePath)?.output ?? fixedOutput;
|
|
2919
|
+
custom = lintCustom(output);
|
|
2920
|
+
fixedOutput = applyMessageFixes(output, custom.messages);
|
|
2921
|
+
}
|
|
2922
|
+
if (output !== fixedOutput) {
|
|
2923
|
+
output = fixedOutput;
|
|
2924
|
+
nativeReport = lintText(output, nativeOptions);
|
|
2925
|
+
custom = lintCustom(output);
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
const nativeResult = reportToESLintResults(nativeReport, {
|
|
2929
|
+
source: result.source,
|
|
2930
|
+
filePath,
|
|
2931
|
+
includeEmptyTextResult: true,
|
|
2932
|
+
ruleSeverityForFile: (path) => ruleSeverityMapForOptions(nativeOptions, path)
|
|
2933
|
+
})[0];
|
|
2934
|
+
result.messages = sortLintMessages([...(nativeResult?.messages ?? []), ...custom.messages]);
|
|
2935
|
+
result.suppressedMessages = [...(nativeResult?.suppressedMessages ?? []), ...custom.suppressedMessages];
|
|
2936
|
+
result.output = output;
|
|
2937
|
+
finalizeESLintResult(result);
|
|
2938
|
+
return true;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2508
2941
|
function appendCustomLintFileMessages(results, config, options) {
|
|
2509
2942
|
for (const result of results) {
|
|
2510
2943
|
const filePath = result.filePath;
|
|
@@ -2521,7 +2954,7 @@ function appendCustomLintFileMessages(results, config, options) {
|
|
|
2521
2954
|
continue;
|
|
2522
2955
|
}
|
|
2523
2956
|
}
|
|
2524
|
-
const sourceCode = createLinterSourceCode(code);
|
|
2957
|
+
const sourceCode = createLinterSourceCode(code, null, { ...parserOptionsForConfig(config, filePath, options.cwd), requireParser: true });
|
|
2525
2958
|
const messages = runCustomLinterRules(customRules, sourceCode, {
|
|
2526
2959
|
cwd: options.cwd,
|
|
2527
2960
|
filename: filePath,
|
|
@@ -2531,7 +2964,10 @@ function appendCustomLintFileMessages(results, config, options) {
|
|
|
2531
2964
|
if (filtered.messages.length === 0 && filtered.suppressedMessages.length === 0) {
|
|
2532
2965
|
continue;
|
|
2533
2966
|
}
|
|
2534
|
-
result
|
|
2967
|
+
if (relintAfterCustomFixes(result, code, filePath, customRules, config, rules, options)) {
|
|
2968
|
+
continue;
|
|
2969
|
+
}
|
|
2970
|
+
result.messages = sortLintMessages([...result.messages, ...filtered.messages]);
|
|
2535
2971
|
result.suppressedMessages.push(...filtered.suppressedMessages);
|
|
2536
2972
|
applyResultFixes(result, code, options);
|
|
2537
2973
|
finalizeESLintResult(result);
|
|
@@ -2539,11 +2975,22 @@ function appendCustomLintFileMessages(results, config, options) {
|
|
|
2539
2975
|
return results;
|
|
2540
2976
|
}
|
|
2541
2977
|
|
|
2978
|
+
// ESLint orders a file's messages by position; native and plugin messages
|
|
2979
|
+
// arrive as two separate batches.
|
|
2980
|
+
function sortLintMessages(messages) {
|
|
2981
|
+
return messages
|
|
2982
|
+
.map((message, index) => ({ message, index }))
|
|
2983
|
+
.sort((left, right) => (left.message.line ?? 0) - (right.message.line ?? 0)
|
|
2984
|
+
|| (left.message.column ?? 0) - (right.message.column ?? 0)
|
|
2985
|
+
|| left.index - right.index)
|
|
2986
|
+
.map(({ message }) => message);
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2542
2989
|
function applyResultFixes(result, code, options) {
|
|
2543
2990
|
if (!options.fix) {
|
|
2544
2991
|
return;
|
|
2545
2992
|
}
|
|
2546
|
-
const output =
|
|
2993
|
+
const output = applyMessageFixes(code, result.messages);
|
|
2547
2994
|
if (output !== code) {
|
|
2548
2995
|
result.output = output;
|
|
2549
2996
|
}
|
|
@@ -2556,14 +3003,39 @@ function runCustomLinterRules(ruleEntries, sourceCode, options) {
|
|
|
2556
3003
|
|
|
2557
3004
|
const messages = [];
|
|
2558
3005
|
const program = sourceCode.ast ?? { type: "Program", range: [0, sourceCode.text.length] };
|
|
3006
|
+
const hasAst = Array.isArray(program.body);
|
|
3007
|
+
const listenersBySelector = new Map();
|
|
2559
3008
|
for (const ruleEntry of ruleEntries) {
|
|
3009
|
+
if (typeof ruleEntry.rule !== "function" && typeof ruleEntry.rule?.create !== "function") {
|
|
3010
|
+
throw new Error(
|
|
3011
|
+
`utoo-lint: rule "${ruleEntry.ruleId}" has no create() function. ` +
|
|
3012
|
+
"ESLint plugins must be mounted from a utlint.config.ts (or .js) module so their rule objects are available at lint time."
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
2560
3015
|
const context = createCustomRuleContext(ruleEntry, sourceCode, options, messages);
|
|
2561
3016
|
const listeners = customRuleListeners(ruleEntry.rule, context);
|
|
2562
|
-
if (
|
|
2563
|
-
traverseCustomRuleAst(program, listeners, context, sourceCode.visitorKeys);
|
|
2564
|
-
} else {
|
|
3017
|
+
if (!hasAst) {
|
|
2565
3018
|
runCustomRuleTokenListeners(program, sourceCode.tokens, listeners, context);
|
|
3019
|
+
continue;
|
|
2566
3020
|
}
|
|
3021
|
+
for (const [selector, listener] of Object.entries(listeners)) {
|
|
3022
|
+
if (typeof listener !== "function") {
|
|
3023
|
+
continue;
|
|
3024
|
+
}
|
|
3025
|
+
if (!listenersBySelector.has(selector)) {
|
|
3026
|
+
listenersBySelector.set(selector, []);
|
|
3027
|
+
}
|
|
3028
|
+
listenersBySelector.get(selector).push({
|
|
3029
|
+
ruleId: ruleEntry.ruleId,
|
|
3030
|
+
listener(node) {
|
|
3031
|
+
context.setCurrentNode(node);
|
|
3032
|
+
listener(node);
|
|
3033
|
+
}
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
if (hasAst && listenersBySelector.size > 0) {
|
|
3038
|
+
pluginRunner.runRuleListeners(program, listenersBySelector, { visitorKeys: sourceCode.visitorKeys });
|
|
2567
3039
|
}
|
|
2568
3040
|
return messages;
|
|
2569
3041
|
}
|
|
@@ -2720,231 +3192,6 @@ function disableDirectiveMatchesRule(directive, ruleId) {
|
|
|
2720
3192
|
return directive.ruleId == null || directive.ruleId === ruleId;
|
|
2721
3193
|
}
|
|
2722
3194
|
|
|
2723
|
-
function traverseCustomRuleAst(node, listeners, context, visitorKeys, seen = new Set(), ancestors = [], siblings = {}) {
|
|
2724
|
-
if (!node || typeof node !== "object" || typeof node.type !== "string" || seen.has(node)) {
|
|
2725
|
-
return;
|
|
2726
|
-
}
|
|
2727
|
-
seen.add(node);
|
|
2728
|
-
for (const listener of customRuleMatchingListeners(listeners, node, false, ancestors, siblings, visitorKeys)) {
|
|
2729
|
-
context.setCurrentNode(node);
|
|
2730
|
-
listener(node);
|
|
2731
|
-
}
|
|
2732
|
-
const children = customRuleChildNodes(node, visitorKeys);
|
|
2733
|
-
for (const [index, child] of children.entries()) {
|
|
2734
|
-
traverseCustomRuleAst(child, listeners, context, visitorKeys, seen, [...ancestors, node], {
|
|
2735
|
-
previous: children[index - 1] ?? null,
|
|
2736
|
-
previousAll: children.slice(0, index)
|
|
2737
|
-
});
|
|
2738
|
-
}
|
|
2739
|
-
for (const listener of customRuleMatchingListeners(listeners, node, true, ancestors, siblings, visitorKeys)) {
|
|
2740
|
-
context.setCurrentNode(node);
|
|
2741
|
-
listener(node);
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
function customRuleMatchingListeners(listeners, node, exit, ancestors = [], siblings = {}, visitorKeys = null) {
|
|
2746
|
-
const matches = [];
|
|
2747
|
-
for (const [selector, listener] of Object.entries(listeners)) {
|
|
2748
|
-
if (typeof listener !== "function" || !customRuleSelectorMatches(selector, node, exit, ancestors, siblings, visitorKeys)) {
|
|
2749
|
-
continue;
|
|
2750
|
-
}
|
|
2751
|
-
matches.push(listener);
|
|
2752
|
-
}
|
|
2753
|
-
return matches;
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
|
-
function customRuleSelectorMatches(selector, node, exit, ancestors = [], siblings = {}, visitorKeys = null) {
|
|
2757
|
-
const suffix = ":exit";
|
|
2758
|
-
const isExit = selector.endsWith(suffix);
|
|
2759
|
-
if (isExit !== exit) {
|
|
2760
|
-
return false;
|
|
2761
|
-
}
|
|
2762
|
-
const expression = isExit ? selector.slice(0, -suffix.length) : selector;
|
|
2763
|
-
const childSelector = customRuleSplitTopLevelSelector(expression, ">");
|
|
2764
|
-
if (childSelector) {
|
|
2765
|
-
const parent = ancestors.at(-1);
|
|
2766
|
-
return Boolean(parent)
|
|
2767
|
-
&& customRuleSimpleSelectorMatches(childSelector[0].trim(), parent, visitorKeys)
|
|
2768
|
-
&& customRuleSimpleSelectorMatches(childSelector[1].trim(), node, visitorKeys);
|
|
2769
|
-
}
|
|
2770
|
-
const adjacentSelector = customRuleSplitTopLevelSelector(expression, "+");
|
|
2771
|
-
if (adjacentSelector) {
|
|
2772
|
-
return Boolean(siblings.previous)
|
|
2773
|
-
&& customRuleSimpleSelectorMatches(adjacentSelector[0].trim(), siblings.previous, visitorKeys)
|
|
2774
|
-
&& customRuleSimpleSelectorMatches(adjacentSelector[1].trim(), node, visitorKeys);
|
|
2775
|
-
}
|
|
2776
|
-
const siblingSelector = customRuleSplitTopLevelSelector(expression, "~");
|
|
2777
|
-
if (siblingSelector) {
|
|
2778
|
-
return (siblings.previousAll ?? []).some((sibling) => customRuleSimpleSelectorMatches(siblingSelector[0].trim(), sibling, visitorKeys))
|
|
2779
|
-
&& customRuleSimpleSelectorMatches(siblingSelector[1].trim(), node, visitorKeys);
|
|
2780
|
-
}
|
|
2781
|
-
const descendantSelector = customRuleSplitTopLevelDescendantSelector(expression);
|
|
2782
|
-
if (descendantSelector) {
|
|
2783
|
-
return ancestors.some((ancestor) => customRuleSimpleSelectorMatches(descendantSelector[0].trim(), ancestor, visitorKeys))
|
|
2784
|
-
&& customRuleSimpleSelectorMatches(descendantSelector[1].trim(), node, visitorKeys);
|
|
2785
|
-
}
|
|
2786
|
-
return customRuleSimpleSelectorMatches(expression, node, visitorKeys);
|
|
2787
|
-
}
|
|
2788
|
-
|
|
2789
|
-
function customRuleSimpleSelectorMatches(expression, node, visitorKeys = null) {
|
|
2790
|
-
const hasSelector = expression.match(/^(.+?):has\((.+)\)$/u);
|
|
2791
|
-
if (hasSelector) {
|
|
2792
|
-
return customRuleSimpleSelectorMatches(hasSelector[1].trim(), node, visitorKeys)
|
|
2793
|
-
&& customRuleDescendants(node, visitorKeys).some((descendant) => (
|
|
2794
|
-
customRuleSplitSelectorList(hasSelector[2]).some((selector) => customRuleSimpleSelectorMatches(selector.trim(), descendant, visitorKeys))
|
|
2795
|
-
));
|
|
2796
|
-
}
|
|
2797
|
-
const notSelector = expression.match(/^(.+?):not\((.+)\)$/u);
|
|
2798
|
-
if (notSelector) {
|
|
2799
|
-
return customRuleSimpleSelectorMatches(notSelector[1].trim(), node, visitorKeys)
|
|
2800
|
-
&& !customRuleSimpleSelectorMatches(notSelector[2].trim(), node, visitorKeys);
|
|
2801
|
-
}
|
|
2802
|
-
const matchesSelector = expression.match(/^(.*?):matches\((.+)\)$/u);
|
|
2803
|
-
if (matchesSelector) {
|
|
2804
|
-
const prefix = matchesSelector[1].trim();
|
|
2805
|
-
if (prefix && !customRuleSimpleSelectorMatches(prefix, node, visitorKeys)) {
|
|
2806
|
-
return false;
|
|
2807
|
-
}
|
|
2808
|
-
return customRuleSplitSelectorList(matchesSelector[2]).some((selector) => (
|
|
2809
|
-
customRuleSimpleSelectorMatches(selector.trim(), node, visitorKeys)
|
|
2810
|
-
));
|
|
2811
|
-
}
|
|
2812
|
-
if (expression === node.type) {
|
|
2813
|
-
return true;
|
|
2814
|
-
}
|
|
2815
|
-
const match = expression.match(/^([A-Za-z_$][\w$-]*|\*)?(?:\[([^\]]+)\])?$/u);
|
|
2816
|
-
if (!match) {
|
|
2817
|
-
return false;
|
|
2818
|
-
}
|
|
2819
|
-
const [, type = "*", attribute] = match;
|
|
2820
|
-
if (type !== "*" && type !== node.type) {
|
|
2821
|
-
return false;
|
|
2822
|
-
}
|
|
2823
|
-
return attribute ? customRuleAttributeSelectorMatches(node, attribute.trim()) : type === "*" || type === node.type;
|
|
2824
|
-
}
|
|
2825
|
-
|
|
2826
|
-
function customRuleDescendants(node, visitorKeys, seen = new Set()) {
|
|
2827
|
-
const descendants = [];
|
|
2828
|
-
for (const child of customRuleChildNodes(node, visitorKeys)) {
|
|
2829
|
-
if (seen.has(child)) {
|
|
2830
|
-
continue;
|
|
2831
|
-
}
|
|
2832
|
-
seen.add(child);
|
|
2833
|
-
descendants.push(child, ...customRuleDescendants(child, visitorKeys, seen));
|
|
2834
|
-
}
|
|
2835
|
-
return descendants;
|
|
2836
|
-
}
|
|
2837
|
-
|
|
2838
|
-
function customRuleSplitTopLevelSelector(expression, separator) {
|
|
2839
|
-
let state = customRuleSelectorScanState();
|
|
2840
|
-
for (let index = 0; index < expression.length; index += 1) {
|
|
2841
|
-
state = customRuleUpdateSelectorScanState(state, expression[index]);
|
|
2842
|
-
if (state.depth === 0 && !state.quote && expression[index] === separator) {
|
|
2843
|
-
return [expression.slice(0, index), expression.slice(index + 1)];
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2846
|
-
return null;
|
|
2847
|
-
}
|
|
2848
|
-
|
|
2849
|
-
function customRuleSplitTopLevelDescendantSelector(expression) {
|
|
2850
|
-
let state = customRuleSelectorScanState();
|
|
2851
|
-
for (let index = 0; index < expression.length; index += 1) {
|
|
2852
|
-
state = customRuleUpdateSelectorScanState(state, expression[index]);
|
|
2853
|
-
if (state.depth === 0 && !state.quote && /\s/u.test(expression[index])) {
|
|
2854
|
-
const left = expression.slice(0, index).trim();
|
|
2855
|
-
const right = expression.slice(index).trim();
|
|
2856
|
-
return left && right ? [left, right] : null;
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
return null;
|
|
2860
|
-
}
|
|
2861
|
-
|
|
2862
|
-
function customRuleSplitSelectorList(value) {
|
|
2863
|
-
const selectors = [];
|
|
2864
|
-
let state = customRuleSelectorScanState();
|
|
2865
|
-
let start = 0;
|
|
2866
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
2867
|
-
state = customRuleUpdateSelectorScanState(state, value[index]);
|
|
2868
|
-
if (state.depth === 0 && !state.quote && value[index] === ",") {
|
|
2869
|
-
selectors.push(value.slice(start, index));
|
|
2870
|
-
start = index + 1;
|
|
2871
|
-
}
|
|
2872
|
-
}
|
|
2873
|
-
selectors.push(value.slice(start));
|
|
2874
|
-
return selectors.filter((selector) => selector.trim());
|
|
2875
|
-
}
|
|
2876
|
-
|
|
2877
|
-
function customRuleSelectorScanState() {
|
|
2878
|
-
return { depth: 0, quote: null, escaped: false };
|
|
2879
|
-
}
|
|
2880
|
-
|
|
2881
|
-
function customRuleUpdateSelectorScanState(state, char) {
|
|
2882
|
-
if (state.escaped) {
|
|
2883
|
-
return { ...state, escaped: false };
|
|
2884
|
-
}
|
|
2885
|
-
if (char === "\\") {
|
|
2886
|
-
return { ...state, escaped: true };
|
|
2887
|
-
}
|
|
2888
|
-
if (state.quote) {
|
|
2889
|
-
return char === state.quote ? { ...state, quote: null } : state;
|
|
2890
|
-
}
|
|
2891
|
-
if (char === "\"" || char === "'") {
|
|
2892
|
-
return { ...state, quote: char };
|
|
2893
|
-
}
|
|
2894
|
-
if (char === "[" || char === "(") {
|
|
2895
|
-
return { ...state, depth: state.depth + 1 };
|
|
2896
|
-
}
|
|
2897
|
-
if ((char === "]" || char === ")") && state.depth > 0) {
|
|
2898
|
-
return { ...state, depth: state.depth - 1 };
|
|
2899
|
-
}
|
|
2900
|
-
return state;
|
|
2901
|
-
}
|
|
2902
|
-
|
|
2903
|
-
function customRuleAttributeSelectorMatches(node, attribute) {
|
|
2904
|
-
const match = attribute.match(/^([\w$.-]+)\s*(!=|=)\s*(.+)$/u);
|
|
2905
|
-
if (!match) {
|
|
2906
|
-
return customRuleValueByPath(node, attribute) != null;
|
|
2907
|
-
}
|
|
2908
|
-
const [, path, operator, rawExpected] = match;
|
|
2909
|
-
const actual = customRuleValueByPath(node, path);
|
|
2910
|
-
const expected = customRuleSelectorValue(rawExpected);
|
|
2911
|
-
return operator === "="
|
|
2912
|
-
? String(actual) === expected
|
|
2913
|
-
: String(actual) !== expected;
|
|
2914
|
-
}
|
|
2915
|
-
|
|
2916
|
-
function customRuleSelectorValue(raw) {
|
|
2917
|
-
const value = raw.trim();
|
|
2918
|
-
const quote = value[0];
|
|
2919
|
-
if ((quote === "\"" || quote === "'") && value.at(-1) === quote) {
|
|
2920
|
-
return value.slice(1, -1);
|
|
2921
|
-
}
|
|
2922
|
-
return value;
|
|
2923
|
-
}
|
|
2924
|
-
|
|
2925
|
-
function customRuleValueByPath(node, path) {
|
|
2926
|
-
return path.split(".").reduce((value, key) => (
|
|
2927
|
-
value && typeof value === "object" ? value[key] : undefined
|
|
2928
|
-
), node);
|
|
2929
|
-
}
|
|
2930
|
-
|
|
2931
|
-
function customRuleChildNodes(node, visitorKeys) {
|
|
2932
|
-
const keys = Array.isArray(visitorKeys?.[node.type])
|
|
2933
|
-
? visitorKeys[node.type]
|
|
2934
|
-
: Object.keys(node).filter((key) => !AST_TRAVERSAL_SKIP_KEYS.has(key));
|
|
2935
|
-
const children = [];
|
|
2936
|
-
for (const key of keys) {
|
|
2937
|
-
const value = node[key];
|
|
2938
|
-
const values = Array.isArray(value) ? value : [value];
|
|
2939
|
-
for (const child of values) {
|
|
2940
|
-
if (child && typeof child === "object" && typeof child.type === "string") {
|
|
2941
|
-
children.push(child);
|
|
2942
|
-
}
|
|
2943
|
-
}
|
|
2944
|
-
}
|
|
2945
|
-
return children;
|
|
2946
|
-
}
|
|
2947
|
-
|
|
2948
3195
|
function runCustomRuleTokenListeners(program, tokens, listeners, context) {
|
|
2949
3196
|
if (typeof listeners.Program === "function") {
|
|
2950
3197
|
context.setCurrentNode(program);
|
|
@@ -2979,7 +3226,7 @@ function createCustomRuleContext(ruleEntry, sourceCode, options, messages) {
|
|
|
2979
3226
|
let currentNode = sourceCode.ast ?? null;
|
|
2980
3227
|
const context = {
|
|
2981
3228
|
id: ruleEntry.ruleId,
|
|
2982
|
-
options: ruleEntry.options,
|
|
3229
|
+
options: pluginRunner.applyRuleOptionDefaults(ruleEntry.rule, ruleEntry.options),
|
|
2983
3230
|
filename,
|
|
2984
3231
|
physicalFilename: filename,
|
|
2985
3232
|
cwd,
|
|
@@ -3197,7 +3444,7 @@ class SourceCode {
|
|
|
3197
3444
|
this.text = textOrConfig.text;
|
|
3198
3445
|
this.ast = textOrConfig.ast ?? null;
|
|
3199
3446
|
this.parserServices = textOrConfig.parserServices ?? {};
|
|
3200
|
-
this
|
|
3447
|
+
defineScopeManager(this, textOrConfig.scopeManager ?? null);
|
|
3201
3448
|
this.visitorKeys = textOrConfig.visitorKeys ?? null;
|
|
3202
3449
|
this.hasBOM = Boolean(textOrConfig.hasBOM);
|
|
3203
3450
|
} else {
|
|
@@ -3389,7 +3636,17 @@ class SourceCode {
|
|
|
3389
3636
|
}
|
|
3390
3637
|
|
|
3391
3638
|
getAncestors(node) {
|
|
3392
|
-
|
|
3639
|
+
if (!node) {
|
|
3640
|
+
return [];
|
|
3641
|
+
}
|
|
3642
|
+
if (node.parent !== undefined) {
|
|
3643
|
+
const ancestors = [];
|
|
3644
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
3645
|
+
ancestors.unshift(current);
|
|
3646
|
+
}
|
|
3647
|
+
return ancestors;
|
|
3648
|
+
}
|
|
3649
|
+
return sourceAncestorsForNode(this.ast, node) ?? [];
|
|
3393
3650
|
}
|
|
3394
3651
|
|
|
3395
3652
|
getDeclaredVariables(node) {
|
|
@@ -3397,10 +3654,20 @@ class SourceCode {
|
|
|
3397
3654
|
}
|
|
3398
3655
|
|
|
3399
3656
|
getScope(node) {
|
|
3400
|
-
|
|
3401
|
-
|
|
3657
|
+
const scopeManager = this.scopeManager;
|
|
3658
|
+
if (!scopeManager || typeof scopeManager.acquire !== "function") {
|
|
3659
|
+
return null;
|
|
3660
|
+
}
|
|
3661
|
+
if (node) {
|
|
3662
|
+
const inner = node.type !== "Program";
|
|
3663
|
+
for (let current = node; current; current = current.parent) {
|
|
3664
|
+
const scope = scopeManager.acquire(current, inner);
|
|
3665
|
+
if (scope) {
|
|
3666
|
+
return scope.type === "function-expression-name" ? scope.childScopes[0] : scope;
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3402
3669
|
}
|
|
3403
|
-
return
|
|
3670
|
+
return scopeManager.scopes?.[0] ?? scopeManager.globalScope ?? null;
|
|
3404
3671
|
}
|
|
3405
3672
|
|
|
3406
3673
|
markVariableAsUsed(name, node) {
|
|
@@ -3436,6 +3703,31 @@ class SourceCode {
|
|
|
3436
3703
|
}
|
|
3437
3704
|
}
|
|
3438
3705
|
|
|
3706
|
+
function defineScopeManager(sourceCode, scopeManager) {
|
|
3707
|
+
if (typeof scopeManager !== "function") {
|
|
3708
|
+
sourceCode.scopeManager = scopeManager;
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
// Scope analysis is comparatively expensive; run it only when a rule asks.
|
|
3712
|
+
let computed = false;
|
|
3713
|
+
let value = null;
|
|
3714
|
+
Object.defineProperty(sourceCode, "scopeManager", {
|
|
3715
|
+
configurable: true,
|
|
3716
|
+
enumerable: true,
|
|
3717
|
+
get() {
|
|
3718
|
+
if (!computed) {
|
|
3719
|
+
computed = true;
|
|
3720
|
+
value = scopeManager() ?? null;
|
|
3721
|
+
}
|
|
3722
|
+
return value;
|
|
3723
|
+
},
|
|
3724
|
+
set(next) {
|
|
3725
|
+
computed = true;
|
|
3726
|
+
value = next;
|
|
3727
|
+
}
|
|
3728
|
+
});
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3439
3731
|
function sourceLineStartIndices(text) {
|
|
3440
3732
|
const indices = [0];
|
|
3441
3733
|
for (let index = 0; index < text.length; index += 1) {
|
|
@@ -3725,12 +4017,10 @@ class RuleTester {
|
|
|
3725
4017
|
RuleTester[testCase.only ? "itOnly" : "it"](testCase.name ?? testCase.code, () => {
|
|
3726
4018
|
const config = ruleTesterConfig(ruleName, this.config, testCase, "error");
|
|
3727
4019
|
const options = ruleTesterOptions(testCase);
|
|
3728
|
-
const
|
|
3729
|
-
? linter.verifyAndFix(testCase.code, config, options)
|
|
3730
|
-
: { messages: linter.verify(testCase.code, config, options), output: testCase.code };
|
|
3731
|
-
const messages = result.messages;
|
|
4020
|
+
const messages = linter.verify(testCase.code, config, options);
|
|
3732
4021
|
assertRuleTesterErrors(testCase, messages, rule);
|
|
3733
|
-
|
|
4022
|
+
// Like ESLint's RuleTester, `output` is the result of one autofix pass.
|
|
4023
|
+
assertRuleTesterOutput(testCase, Object.hasOwn(testCase, "output") ? applyMessageFixes(testCase.code, messages) : testCase.code);
|
|
3734
4024
|
});
|
|
3735
4025
|
}
|
|
3736
4026
|
});
|
|
@@ -3877,6 +4167,47 @@ function applyRuleFixes(code, fix) {
|
|
|
3877
4167
|
.reduce((output, item) => output.slice(0, item.range[0]) + item.text + output.slice(item.range[1]), code);
|
|
3878
4168
|
}
|
|
3879
4169
|
|
|
4170
|
+
// Applies one non-overlapping fix per message, like ESLint's SourceCodeFixer.
|
|
4171
|
+
// Fixes that overlap an earlier one are left for a later pass.
|
|
4172
|
+
function applyMessageFixes(code, messages) {
|
|
4173
|
+
const fixes = [];
|
|
4174
|
+
for (const message of messages) {
|
|
4175
|
+
const items = ruleFixItems(message?.fix)
|
|
4176
|
+
.slice()
|
|
4177
|
+
.sort((left, right) => left.range[0] - right.range[0] || left.range[1] - right.range[1]);
|
|
4178
|
+
if (items.length === 0) {
|
|
4179
|
+
continue;
|
|
4180
|
+
}
|
|
4181
|
+
let text = "";
|
|
4182
|
+
let cursor = items[0].range[0];
|
|
4183
|
+
let valid = true;
|
|
4184
|
+
for (const item of items) {
|
|
4185
|
+
if (item.range[0] < cursor) {
|
|
4186
|
+
valid = false;
|
|
4187
|
+
break;
|
|
4188
|
+
}
|
|
4189
|
+
text += code.slice(cursor, item.range[0]) + item.text;
|
|
4190
|
+
cursor = item.range[1];
|
|
4191
|
+
}
|
|
4192
|
+
if (valid) {
|
|
4193
|
+
fixes.push({ range: [items[0].range[0], cursor], text });
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
fixes.sort((left, right) => left.range[0] - right.range[0] || left.range[1] - right.range[1]);
|
|
4197
|
+
let output = "";
|
|
4198
|
+
let position = 0;
|
|
4199
|
+
let lastEnd = -Infinity;
|
|
4200
|
+
for (const fix of fixes) {
|
|
4201
|
+
if (lastEnd >= fix.range[0]) {
|
|
4202
|
+
continue;
|
|
4203
|
+
}
|
|
4204
|
+
output += code.slice(position, fix.range[0]) + fix.text;
|
|
4205
|
+
position = fix.range[1];
|
|
4206
|
+
lastEnd = fix.range[1];
|
|
4207
|
+
}
|
|
4208
|
+
return output + code.slice(position);
|
|
4209
|
+
}
|
|
4210
|
+
|
|
3880
4211
|
function ruleFixItems(fix) {
|
|
3881
4212
|
if (!fix) {
|
|
3882
4213
|
return [];
|
|
@@ -3929,10 +4260,29 @@ function createLinterSourceCode(text, parser = null, parserOptions = {}) {
|
|
|
3929
4260
|
});
|
|
3930
4261
|
}
|
|
3931
4262
|
|
|
3932
|
-
function
|
|
3933
|
-
|
|
4263
|
+
function parseSourceCodeWithDefaultParser(text, parserOptions) {
|
|
4264
|
+
let parsed;
|
|
4265
|
+
try {
|
|
4266
|
+
parsed = estreeParser.parseForESLint(text, parserOptions);
|
|
4267
|
+
} catch (error) {
|
|
4268
|
+
if (parserOptions.requireParser) {
|
|
4269
|
+
throw error;
|
|
4270
|
+
}
|
|
3934
4271
|
return null;
|
|
3935
4272
|
}
|
|
4273
|
+
return new SourceCode({
|
|
4274
|
+
text,
|
|
4275
|
+
ast: parsed.ast,
|
|
4276
|
+
parserServices: parsed.services ?? {},
|
|
4277
|
+
scopeManager: parsed.scopeManager ?? null,
|
|
4278
|
+
visitorKeys: parsed.visitorKeys ?? null
|
|
4279
|
+
});
|
|
4280
|
+
}
|
|
4281
|
+
|
|
4282
|
+
function parseSourceCode(text, parser, parserOptions = {}) {
|
|
4283
|
+
if (!parser) {
|
|
4284
|
+
return parserOptions.tokensOnly ? null : parseSourceCodeWithDefaultParser(text, parserOptions);
|
|
4285
|
+
}
|
|
3936
4286
|
if (typeof parser.parseForESLint === "function") {
|
|
3937
4287
|
const result = parser.parseForESLint(text, parserOptions) ?? {};
|
|
3938
4288
|
return new SourceCode({
|
|
@@ -4198,7 +4548,7 @@ function applyCompatibilityDisableDirectives(result, textOptions) {
|
|
|
4198
4548
|
return;
|
|
4199
4549
|
}
|
|
4200
4550
|
|
|
4201
|
-
const filtered = applyDisableDirectives(result.messages, createLinterSourceCode(source));
|
|
4551
|
+
const filtered = applyDisableDirectives(result.messages, createLinterSourceCode(source, null, { tokensOnly: true }));
|
|
4202
4552
|
result.messages = filtered.messages;
|
|
4203
4553
|
result.suppressedMessages.push(...filtered.suppressedMessages);
|
|
4204
4554
|
}
|
|
@@ -4932,6 +5282,12 @@ function diagnosticToESLintMessage(diagnostic, ruleSeverities) {
|
|
|
4932
5282
|
column: diagnostic.column,
|
|
4933
5283
|
nodeType: null
|
|
4934
5284
|
};
|
|
5285
|
+
if (typeof diagnostic.endLine === "number") {
|
|
5286
|
+
message.endLine = diagnostic.endLine;
|
|
5287
|
+
}
|
|
5288
|
+
if (typeof diagnostic.endColumn === "number") {
|
|
5289
|
+
message.endColumn = diagnostic.endColumn;
|
|
5290
|
+
}
|
|
4935
5291
|
if (diagnostic.endLine != null) {
|
|
4936
5292
|
message.endLine = diagnostic.endLine;
|
|
4937
5293
|
}
|