@utoo/lint 0.3.8 → 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 +638 -278
- package/index.d.ts +11 -0
- package/index.js +638 -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;
|
|
@@ -397,6 +397,10 @@ const BUILTIN_RULE_IDS = [
|
|
|
397
397
|
"react/void-dom-elements-no-children",
|
|
398
398
|
"react-hooks/exhaustive-deps",
|
|
399
399
|
"react-hooks/rules-of-hooks",
|
|
400
|
+
"react-hooks/use-memo",
|
|
401
|
+
"react-hooks/void-use-memo",
|
|
402
|
+
"react-hooks/set-state-in-render",
|
|
403
|
+
"react-hooks/purity",
|
|
400
404
|
"unused-imports/no-unused-imports",
|
|
401
405
|
"@typescript-eslint/adjacent-overload-signatures",
|
|
402
406
|
"@typescript-eslint/array-type",
|
|
@@ -476,6 +480,7 @@ const NATIVE_ONLY_RULE_IDS = [
|
|
|
476
480
|
"semantic-errors"
|
|
477
481
|
];
|
|
478
482
|
const NATIVE_RULE_IDS = [...BUILTIN_RULE_IDS, ...NATIVE_ONLY_RULE_IDS];
|
|
483
|
+
const NATIVE_RULE_ID_SET = new Set(NATIVE_RULE_IDS);
|
|
479
484
|
const FIXABLE_BUILTIN_RULE_IDS = new Set([
|
|
480
485
|
"@typescript-eslint/no-explicit-any",
|
|
481
486
|
"jest/no-deprecated-functions",
|
|
@@ -603,15 +608,15 @@ class UtooLint {
|
|
|
603
608
|
throw new Error("'results' must be an array");
|
|
604
609
|
}
|
|
605
610
|
const options = eslintConstructorOptions(this.options);
|
|
606
|
-
const customRuleConfig = [options.baseConfig, options.overrideConfig].filter(Boolean);
|
|
607
611
|
const rulesByFilePath = new Map();
|
|
612
|
+
const cachedOptions = withConfigCache(options);
|
|
608
613
|
return rulesMetaForResults(results, (ruleId, result) => {
|
|
609
|
-
if (
|
|
614
|
+
if (typeof result.filePath !== "string") {
|
|
610
615
|
return undefined;
|
|
611
616
|
}
|
|
612
617
|
const filePath = normalizeESLintFilePath(result.filePath, options.cwd);
|
|
613
618
|
if (!rulesByFilePath.has(filePath)) {
|
|
614
|
-
rulesByFilePath.set(filePath,
|
|
619
|
+
rulesByFilePath.set(filePath, jsPluginRuleMapForFile(cachedOptions, filePath));
|
|
615
620
|
}
|
|
616
621
|
return rulesByFilePath.get(filePath).get(ruleId)?.meta;
|
|
617
622
|
});
|
|
@@ -1225,21 +1230,210 @@ function lintFiles(paths, options = {}) {
|
|
|
1225
1230
|
}
|
|
1226
1231
|
|
|
1227
1232
|
const nativeFlatOptions = nativeFlatConfigOptions(options);
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
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
|
+
}
|
|
1232
1241
|
|
|
1242
|
+
function runNativeConfigRunReports(lintPaths, options) {
|
|
1233
1243
|
const configRuns = nativeConfigRunsForFiles(expandNativeConfigRunPaths(lintPaths, options), options);
|
|
1234
1244
|
if (configRuns) {
|
|
1235
|
-
|
|
1245
|
+
return mergeNativeReports(
|
|
1236
1246
|
configRuns.map(({ paths: runPaths, options: runOptions }) => runNativeLintReport(runPaths, runOptions))
|
|
1237
1247
|
);
|
|
1238
|
-
return finalizeLintReport(report, ignoredDiagnostics, options);
|
|
1239
1248
|
}
|
|
1240
1249
|
|
|
1241
|
-
|
|
1242
|
-
|
|
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;
|
|
1243
1437
|
}
|
|
1244
1438
|
|
|
1245
1439
|
function nativeFlatConfigOptions(options) {
|
|
@@ -1269,6 +1463,10 @@ function nativeFlatConfigOptions(options) {
|
|
|
1269
1463
|
return undefined;
|
|
1270
1464
|
}
|
|
1271
1465
|
|
|
1466
|
+
const jsPluginRuleIds = jsPluginRuleIdsFromConfigs(config);
|
|
1467
|
+
if (jsPluginRuleIds.size > 0) {
|
|
1468
|
+
config = configWithoutCustomRules(config, jsPluginRuleIds);
|
|
1469
|
+
}
|
|
1272
1470
|
if (configCanUseNativeRulesFastPath(config)) {
|
|
1273
1471
|
return undefined;
|
|
1274
1472
|
}
|
|
@@ -1395,6 +1593,9 @@ function nativeConfigRunsForFiles(paths, options) {
|
|
|
1395
1593
|
if (configured && options.rules) {
|
|
1396
1594
|
rules = selectedRulesWithConfigOptions(rules, options.rules);
|
|
1397
1595
|
}
|
|
1596
|
+
if (configured) {
|
|
1597
|
+
rules = withoutJsPluginRules(rules, jsPluginRuleIdsForOptions(options, matchPath));
|
|
1598
|
+
}
|
|
1398
1599
|
const signature = configured ? stableConfigSignature({ rules, settings }) : "<native-defaults>";
|
|
1399
1600
|
if (!groups.has(signature)) {
|
|
1400
1601
|
groups.set(signature, { paths: [], rules, settings, configured });
|
|
@@ -1567,7 +1768,8 @@ function lintText(code, options = {}) {
|
|
|
1567
1768
|
cwd: options.cwd,
|
|
1568
1769
|
config: options.config ?? discoveredConfig,
|
|
1569
1770
|
noConfig: options.noConfig,
|
|
1570
|
-
deferDiagnosticConfigFiltering: true
|
|
1771
|
+
deferDiagnosticConfigFiltering: true,
|
|
1772
|
+
pluginTextFilePath: requestedPath
|
|
1571
1773
|
});
|
|
1572
1774
|
report.filePaths = (report.filePaths ?? []).map((filePath) => (filePath === tempFile ? requestedPath : filePath));
|
|
1573
1775
|
report.outputs = (report.outputs ?? []).map((fixed) => ({
|
|
@@ -1609,11 +1811,12 @@ function buildNativeLintArgs(paths, options) {
|
|
|
1609
1811
|
const configForRuleSelection = options.noConfig
|
|
1610
1812
|
? undefined
|
|
1611
1813
|
: options.config ? readConfig(options.config, options.cwd, options.configCache) : undefined;
|
|
1814
|
+
const jsPluginRuleIds = jsPluginRuleIdsFromConfigs(configForRuleSelection, options.baseConfig, options.overrideConfig);
|
|
1612
1815
|
const enabledRules = enabledRuleNamesFromConfigs(
|
|
1613
1816
|
configForRuleSelection,
|
|
1614
1817
|
options.baseConfig,
|
|
1615
1818
|
options.overrideConfig
|
|
1616
|
-
);
|
|
1819
|
+
).filter((rule) => !jsPluginRuleIds.has(rule));
|
|
1617
1820
|
if (enabledRules.length > 0) {
|
|
1618
1821
|
cliArgs.push(`--rules=${enabledRules.join(",")}`);
|
|
1619
1822
|
}
|
|
@@ -2091,9 +2294,11 @@ function withTemporaryConfig(options, callback) {
|
|
|
2091
2294
|
? readConfig(fileConfigPath, options.cwd, options.configCache)
|
|
2092
2295
|
: undefined;
|
|
2093
2296
|
const configs = [options.baseConfig, fileConfig, options.overrideConfig];
|
|
2094
|
-
const
|
|
2095
|
-
|
|
2096
|
-
: runtimeRulesFromConfigs(...configs)
|
|
2297
|
+
const jsPluginRuleIds = jsPluginRuleIdsFromConfigs(...configs);
|
|
2298
|
+
const rules = withoutJsPluginRules(
|
|
2299
|
+
shouldMaterializeFileConfig ? materializedRulesFromConfigs(...configs) : runtimeRulesFromConfigs(...configs),
|
|
2300
|
+
jsPluginRuleIds
|
|
2301
|
+
);
|
|
2097
2302
|
const settings = configs.reduce(
|
|
2098
2303
|
(result, config) => ({
|
|
2099
2304
|
...result,
|
|
@@ -2112,7 +2317,7 @@ function withTemporaryConfig(options, callback) {
|
|
|
2112
2317
|
}
|
|
2113
2318
|
return callback(options);
|
|
2114
2319
|
}
|
|
2115
|
-
const enabledRules = enabledRuleNamesFromConfigs(...configs);
|
|
2320
|
+
const enabledRules = enabledRuleNamesFromConfigs(...configs).filter((rule) => !jsPluginRuleIds.has(rule));
|
|
2116
2321
|
const hasExplicitOffRules = Object.values(rules).some((value) => ruleConfigSeverity(value) === 0);
|
|
2117
2322
|
if (!shouldMaterializeFileConfig && !hasSettings && !hasRuleOptions(rules) && !hasExplicitOffRules && !options.forceMaterializedConfig) {
|
|
2118
2323
|
return callback({
|
|
@@ -2241,14 +2446,14 @@ class Linter {
|
|
|
2241
2446
|
const verifyOptions = typeof options === "string" ? { filename: options } : { ...options };
|
|
2242
2447
|
const filePath = verifyOptions.filename ?? verifyOptions.filePath ?? "input.js";
|
|
2243
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);
|
|
2244
2452
|
const sourceCode = createLinterSourceCode(
|
|
2245
2453
|
code,
|
|
2246
2454
|
parserForConfig(config, this.parsers, normalizedFilePath, verifyOptions.cwd),
|
|
2247
|
-
parserOptionsForConfig(config, normalizedFilePath, verifyOptions.cwd)
|
|
2455
|
+
{ ...parserOptionsForConfig(config, normalizedFilePath, verifyOptions.cwd), requireParser: customRules.length > 0 }
|
|
2248
2456
|
);
|
|
2249
|
-
const customRuleMap = customRuleMapForConfig(config, this.rules, normalizedFilePath, verifyOptions.cwd);
|
|
2250
|
-
const customRuleFilterMap = customRuleMapForConfig(config, this.rules);
|
|
2251
|
-
const customRules = customRuleEntriesForConfig(config, customRuleMap, normalizedFilePath, verifyOptions.cwd);
|
|
2252
2457
|
const nativeConfig = configWithoutCustomRules(config, customRuleFilterMap);
|
|
2253
2458
|
const lintOptions = {
|
|
2254
2459
|
cwd: verifyOptions.cwd,
|
|
@@ -2278,10 +2483,10 @@ class Linter {
|
|
|
2278
2483
|
];
|
|
2279
2484
|
this.times = { passes: [] };
|
|
2280
2485
|
this.fixPassCount = 0;
|
|
2281
|
-
return [
|
|
2486
|
+
return sortLintMessages([
|
|
2282
2487
|
...nativeRuleFilter.messages,
|
|
2283
2488
|
...customRuleFilter.messages
|
|
2284
|
-
];
|
|
2489
|
+
]);
|
|
2285
2490
|
}
|
|
2286
2491
|
|
|
2287
2492
|
verifyAndFix(code, config = {}, options = {}) {
|
|
@@ -2290,7 +2495,7 @@ class Linter {
|
|
|
2290
2495
|
let fixPassCount = 0;
|
|
2291
2496
|
for (let pass = 0; pass < MAX_AUTOFIX_PASSES; pass += 1) {
|
|
2292
2497
|
const messages = this.verify(output, config, options);
|
|
2293
|
-
const fixedOutput =
|
|
2498
|
+
const fixedOutput = applyMessageFixes(output, messages);
|
|
2294
2499
|
if (fixedOutput === output) {
|
|
2295
2500
|
this.fixPassCount = fixPassCount;
|
|
2296
2501
|
return {
|
|
@@ -2373,9 +2578,17 @@ function parserForConfig(config, parsers, filePath, cwd) {
|
|
|
2373
2578
|
|
|
2374
2579
|
function parserOptionsForConfig(config, filePath, cwd) {
|
|
2375
2580
|
const configData = configDataFromConfig(config, filePath, cwd);
|
|
2376
|
-
|
|
2581
|
+
const languageOptions = configData.languageOptions ?? {};
|
|
2582
|
+
const parserOptions = {
|
|
2377
2583
|
...(configData.parserOptions ?? {}),
|
|
2378
|
-
...(
|
|
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 ?? {}) },
|
|
2379
2592
|
filename: filePath,
|
|
2380
2593
|
filePath,
|
|
2381
2594
|
cwd
|
|
@@ -2421,13 +2634,176 @@ function pluginRulesForConfig(config, filePath, cwd) {
|
|
|
2421
2634
|
continue;
|
|
2422
2635
|
}
|
|
2423
2636
|
for (const [ruleName, rule] of Object.entries(plugin.rules)) {
|
|
2424
|
-
|
|
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);
|
|
2425
2644
|
}
|
|
2426
2645
|
}
|
|
2427
2646
|
}
|
|
2428
2647
|
return rules;
|
|
2429
2648
|
}
|
|
2430
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
|
+
|
|
2431
2807
|
function matchingConfigEntries(config, filePath = undefined, cwd = undefined) {
|
|
2432
2808
|
if (!config) {
|
|
2433
2809
|
return [];
|
|
@@ -2479,7 +2855,7 @@ function appendCustomLintTextMessages(results, code, filePath, config, rules, op
|
|
|
2479
2855
|
}
|
|
2480
2856
|
let result = results.find((item) => item.filePath === filePath);
|
|
2481
2857
|
const effectiveCode = typeof result?.output === "string" ? result.output : code;
|
|
2482
|
-
const sourceCode = createLinterSourceCode(effectiveCode);
|
|
2858
|
+
const sourceCode = createLinterSourceCode(effectiveCode, null, { ...parserOptionsForConfig(config, filePath, options.cwd), requireParser: true });
|
|
2483
2859
|
const messages = runCustomLinterRules(customRules, sourceCode, {
|
|
2484
2860
|
cwd: options.cwd,
|
|
2485
2861
|
filename: filePath,
|
|
@@ -2494,13 +2870,74 @@ function appendCustomLintTextMessages(results, code, filePath, config, rules, op
|
|
|
2494
2870
|
result = emptyESLintResult(filePath, effectiveCode);
|
|
2495
2871
|
results.push(result);
|
|
2496
2872
|
}
|
|
2497
|
-
result
|
|
2873
|
+
if (relintAfterCustomFixes(result, effectiveCode, filePath, customRules, config, rules, options)) {
|
|
2874
|
+
return results;
|
|
2875
|
+
}
|
|
2876
|
+
result.messages = sortLintMessages([...result.messages, ...filtered.messages]);
|
|
2498
2877
|
result.suppressedMessages.push(...filtered.suppressedMessages);
|
|
2499
2878
|
applyResultFixes(result, effectiveCode, options);
|
|
2500
2879
|
finalizeESLintResult(result);
|
|
2501
2880
|
return results;
|
|
2502
2881
|
}
|
|
2503
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
|
+
|
|
2504
2941
|
function appendCustomLintFileMessages(results, config, options) {
|
|
2505
2942
|
for (const result of results) {
|
|
2506
2943
|
const filePath = result.filePath;
|
|
@@ -2517,7 +2954,7 @@ function appendCustomLintFileMessages(results, config, options) {
|
|
|
2517
2954
|
continue;
|
|
2518
2955
|
}
|
|
2519
2956
|
}
|
|
2520
|
-
const sourceCode = createLinterSourceCode(code);
|
|
2957
|
+
const sourceCode = createLinterSourceCode(code, null, { ...parserOptionsForConfig(config, filePath, options.cwd), requireParser: true });
|
|
2521
2958
|
const messages = runCustomLinterRules(customRules, sourceCode, {
|
|
2522
2959
|
cwd: options.cwd,
|
|
2523
2960
|
filename: filePath,
|
|
@@ -2527,7 +2964,10 @@ function appendCustomLintFileMessages(results, config, options) {
|
|
|
2527
2964
|
if (filtered.messages.length === 0 && filtered.suppressedMessages.length === 0) {
|
|
2528
2965
|
continue;
|
|
2529
2966
|
}
|
|
2530
|
-
result
|
|
2967
|
+
if (relintAfterCustomFixes(result, code, filePath, customRules, config, rules, options)) {
|
|
2968
|
+
continue;
|
|
2969
|
+
}
|
|
2970
|
+
result.messages = sortLintMessages([...result.messages, ...filtered.messages]);
|
|
2531
2971
|
result.suppressedMessages.push(...filtered.suppressedMessages);
|
|
2532
2972
|
applyResultFixes(result, code, options);
|
|
2533
2973
|
finalizeESLintResult(result);
|
|
@@ -2535,11 +2975,22 @@ function appendCustomLintFileMessages(results, config, options) {
|
|
|
2535
2975
|
return results;
|
|
2536
2976
|
}
|
|
2537
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
|
+
|
|
2538
2989
|
function applyResultFixes(result, code, options) {
|
|
2539
2990
|
if (!options.fix) {
|
|
2540
2991
|
return;
|
|
2541
2992
|
}
|
|
2542
|
-
const output =
|
|
2993
|
+
const output = applyMessageFixes(code, result.messages);
|
|
2543
2994
|
if (output !== code) {
|
|
2544
2995
|
result.output = output;
|
|
2545
2996
|
}
|
|
@@ -2552,14 +3003,39 @@ function runCustomLinterRules(ruleEntries, sourceCode, options) {
|
|
|
2552
3003
|
|
|
2553
3004
|
const messages = [];
|
|
2554
3005
|
const program = sourceCode.ast ?? { type: "Program", range: [0, sourceCode.text.length] };
|
|
3006
|
+
const hasAst = Array.isArray(program.body);
|
|
3007
|
+
const listenersBySelector = new Map();
|
|
2555
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
|
+
}
|
|
2556
3015
|
const context = createCustomRuleContext(ruleEntry, sourceCode, options, messages);
|
|
2557
3016
|
const listeners = customRuleListeners(ruleEntry.rule, context);
|
|
2558
|
-
if (
|
|
2559
|
-
traverseCustomRuleAst(program, listeners, context, sourceCode.visitorKeys);
|
|
2560
|
-
} else {
|
|
3017
|
+
if (!hasAst) {
|
|
2561
3018
|
runCustomRuleTokenListeners(program, sourceCode.tokens, listeners, context);
|
|
3019
|
+
continue;
|
|
2562
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 });
|
|
2563
3039
|
}
|
|
2564
3040
|
return messages;
|
|
2565
3041
|
}
|
|
@@ -2716,231 +3192,6 @@ function disableDirectiveMatchesRule(directive, ruleId) {
|
|
|
2716
3192
|
return directive.ruleId == null || directive.ruleId === ruleId;
|
|
2717
3193
|
}
|
|
2718
3194
|
|
|
2719
|
-
function traverseCustomRuleAst(node, listeners, context, visitorKeys, seen = new Set(), ancestors = [], siblings = {}) {
|
|
2720
|
-
if (!node || typeof node !== "object" || typeof node.type !== "string" || seen.has(node)) {
|
|
2721
|
-
return;
|
|
2722
|
-
}
|
|
2723
|
-
seen.add(node);
|
|
2724
|
-
for (const listener of customRuleMatchingListeners(listeners, node, false, ancestors, siblings, visitorKeys)) {
|
|
2725
|
-
context.setCurrentNode(node);
|
|
2726
|
-
listener(node);
|
|
2727
|
-
}
|
|
2728
|
-
const children = customRuleChildNodes(node, visitorKeys);
|
|
2729
|
-
for (const [index, child] of children.entries()) {
|
|
2730
|
-
traverseCustomRuleAst(child, listeners, context, visitorKeys, seen, [...ancestors, node], {
|
|
2731
|
-
previous: children[index - 1] ?? null,
|
|
2732
|
-
previousAll: children.slice(0, index)
|
|
2733
|
-
});
|
|
2734
|
-
}
|
|
2735
|
-
for (const listener of customRuleMatchingListeners(listeners, node, true, ancestors, siblings, visitorKeys)) {
|
|
2736
|
-
context.setCurrentNode(node);
|
|
2737
|
-
listener(node);
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
|
-
|
|
2741
|
-
function customRuleMatchingListeners(listeners, node, exit, ancestors = [], siblings = {}, visitorKeys = null) {
|
|
2742
|
-
const matches = [];
|
|
2743
|
-
for (const [selector, listener] of Object.entries(listeners)) {
|
|
2744
|
-
if (typeof listener !== "function" || !customRuleSelectorMatches(selector, node, exit, ancestors, siblings, visitorKeys)) {
|
|
2745
|
-
continue;
|
|
2746
|
-
}
|
|
2747
|
-
matches.push(listener);
|
|
2748
|
-
}
|
|
2749
|
-
return matches;
|
|
2750
|
-
}
|
|
2751
|
-
|
|
2752
|
-
function customRuleSelectorMatches(selector, node, exit, ancestors = [], siblings = {}, visitorKeys = null) {
|
|
2753
|
-
const suffix = ":exit";
|
|
2754
|
-
const isExit = selector.endsWith(suffix);
|
|
2755
|
-
if (isExit !== exit) {
|
|
2756
|
-
return false;
|
|
2757
|
-
}
|
|
2758
|
-
const expression = isExit ? selector.slice(0, -suffix.length) : selector;
|
|
2759
|
-
const childSelector = customRuleSplitTopLevelSelector(expression, ">");
|
|
2760
|
-
if (childSelector) {
|
|
2761
|
-
const parent = ancestors.at(-1);
|
|
2762
|
-
return Boolean(parent)
|
|
2763
|
-
&& customRuleSimpleSelectorMatches(childSelector[0].trim(), parent, visitorKeys)
|
|
2764
|
-
&& customRuleSimpleSelectorMatches(childSelector[1].trim(), node, visitorKeys);
|
|
2765
|
-
}
|
|
2766
|
-
const adjacentSelector = customRuleSplitTopLevelSelector(expression, "+");
|
|
2767
|
-
if (adjacentSelector) {
|
|
2768
|
-
return Boolean(siblings.previous)
|
|
2769
|
-
&& customRuleSimpleSelectorMatches(adjacentSelector[0].trim(), siblings.previous, visitorKeys)
|
|
2770
|
-
&& customRuleSimpleSelectorMatches(adjacentSelector[1].trim(), node, visitorKeys);
|
|
2771
|
-
}
|
|
2772
|
-
const siblingSelector = customRuleSplitTopLevelSelector(expression, "~");
|
|
2773
|
-
if (siblingSelector) {
|
|
2774
|
-
return (siblings.previousAll ?? []).some((sibling) => customRuleSimpleSelectorMatches(siblingSelector[0].trim(), sibling, visitorKeys))
|
|
2775
|
-
&& customRuleSimpleSelectorMatches(siblingSelector[1].trim(), node, visitorKeys);
|
|
2776
|
-
}
|
|
2777
|
-
const descendantSelector = customRuleSplitTopLevelDescendantSelector(expression);
|
|
2778
|
-
if (descendantSelector) {
|
|
2779
|
-
return ancestors.some((ancestor) => customRuleSimpleSelectorMatches(descendantSelector[0].trim(), ancestor, visitorKeys))
|
|
2780
|
-
&& customRuleSimpleSelectorMatches(descendantSelector[1].trim(), node, visitorKeys);
|
|
2781
|
-
}
|
|
2782
|
-
return customRuleSimpleSelectorMatches(expression, node, visitorKeys);
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
|
-
function customRuleSimpleSelectorMatches(expression, node, visitorKeys = null) {
|
|
2786
|
-
const hasSelector = expression.match(/^(.+?):has\((.+)\)$/u);
|
|
2787
|
-
if (hasSelector) {
|
|
2788
|
-
return customRuleSimpleSelectorMatches(hasSelector[1].trim(), node, visitorKeys)
|
|
2789
|
-
&& customRuleDescendants(node, visitorKeys).some((descendant) => (
|
|
2790
|
-
customRuleSplitSelectorList(hasSelector[2]).some((selector) => customRuleSimpleSelectorMatches(selector.trim(), descendant, visitorKeys))
|
|
2791
|
-
));
|
|
2792
|
-
}
|
|
2793
|
-
const notSelector = expression.match(/^(.+?):not\((.+)\)$/u);
|
|
2794
|
-
if (notSelector) {
|
|
2795
|
-
return customRuleSimpleSelectorMatches(notSelector[1].trim(), node, visitorKeys)
|
|
2796
|
-
&& !customRuleSimpleSelectorMatches(notSelector[2].trim(), node, visitorKeys);
|
|
2797
|
-
}
|
|
2798
|
-
const matchesSelector = expression.match(/^(.*?):matches\((.+)\)$/u);
|
|
2799
|
-
if (matchesSelector) {
|
|
2800
|
-
const prefix = matchesSelector[1].trim();
|
|
2801
|
-
if (prefix && !customRuleSimpleSelectorMatches(prefix, node, visitorKeys)) {
|
|
2802
|
-
return false;
|
|
2803
|
-
}
|
|
2804
|
-
return customRuleSplitSelectorList(matchesSelector[2]).some((selector) => (
|
|
2805
|
-
customRuleSimpleSelectorMatches(selector.trim(), node, visitorKeys)
|
|
2806
|
-
));
|
|
2807
|
-
}
|
|
2808
|
-
if (expression === node.type) {
|
|
2809
|
-
return true;
|
|
2810
|
-
}
|
|
2811
|
-
const match = expression.match(/^([A-Za-z_$][\w$-]*|\*)?(?:\[([^\]]+)\])?$/u);
|
|
2812
|
-
if (!match) {
|
|
2813
|
-
return false;
|
|
2814
|
-
}
|
|
2815
|
-
const [, type = "*", attribute] = match;
|
|
2816
|
-
if (type !== "*" && type !== node.type) {
|
|
2817
|
-
return false;
|
|
2818
|
-
}
|
|
2819
|
-
return attribute ? customRuleAttributeSelectorMatches(node, attribute.trim()) : type === "*" || type === node.type;
|
|
2820
|
-
}
|
|
2821
|
-
|
|
2822
|
-
function customRuleDescendants(node, visitorKeys, seen = new Set()) {
|
|
2823
|
-
const descendants = [];
|
|
2824
|
-
for (const child of customRuleChildNodes(node, visitorKeys)) {
|
|
2825
|
-
if (seen.has(child)) {
|
|
2826
|
-
continue;
|
|
2827
|
-
}
|
|
2828
|
-
seen.add(child);
|
|
2829
|
-
descendants.push(child, ...customRuleDescendants(child, visitorKeys, seen));
|
|
2830
|
-
}
|
|
2831
|
-
return descendants;
|
|
2832
|
-
}
|
|
2833
|
-
|
|
2834
|
-
function customRuleSplitTopLevelSelector(expression, separator) {
|
|
2835
|
-
let state = customRuleSelectorScanState();
|
|
2836
|
-
for (let index = 0; index < expression.length; index += 1) {
|
|
2837
|
-
state = customRuleUpdateSelectorScanState(state, expression[index]);
|
|
2838
|
-
if (state.depth === 0 && !state.quote && expression[index] === separator) {
|
|
2839
|
-
return [expression.slice(0, index), expression.slice(index + 1)];
|
|
2840
|
-
}
|
|
2841
|
-
}
|
|
2842
|
-
return null;
|
|
2843
|
-
}
|
|
2844
|
-
|
|
2845
|
-
function customRuleSplitTopLevelDescendantSelector(expression) {
|
|
2846
|
-
let state = customRuleSelectorScanState();
|
|
2847
|
-
for (let index = 0; index < expression.length; index += 1) {
|
|
2848
|
-
state = customRuleUpdateSelectorScanState(state, expression[index]);
|
|
2849
|
-
if (state.depth === 0 && !state.quote && /\s/u.test(expression[index])) {
|
|
2850
|
-
const left = expression.slice(0, index).trim();
|
|
2851
|
-
const right = expression.slice(index).trim();
|
|
2852
|
-
return left && right ? [left, right] : null;
|
|
2853
|
-
}
|
|
2854
|
-
}
|
|
2855
|
-
return null;
|
|
2856
|
-
}
|
|
2857
|
-
|
|
2858
|
-
function customRuleSplitSelectorList(value) {
|
|
2859
|
-
const selectors = [];
|
|
2860
|
-
let state = customRuleSelectorScanState();
|
|
2861
|
-
let start = 0;
|
|
2862
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
2863
|
-
state = customRuleUpdateSelectorScanState(state, value[index]);
|
|
2864
|
-
if (state.depth === 0 && !state.quote && value[index] === ",") {
|
|
2865
|
-
selectors.push(value.slice(start, index));
|
|
2866
|
-
start = index + 1;
|
|
2867
|
-
}
|
|
2868
|
-
}
|
|
2869
|
-
selectors.push(value.slice(start));
|
|
2870
|
-
return selectors.filter((selector) => selector.trim());
|
|
2871
|
-
}
|
|
2872
|
-
|
|
2873
|
-
function customRuleSelectorScanState() {
|
|
2874
|
-
return { depth: 0, quote: null, escaped: false };
|
|
2875
|
-
}
|
|
2876
|
-
|
|
2877
|
-
function customRuleUpdateSelectorScanState(state, char) {
|
|
2878
|
-
if (state.escaped) {
|
|
2879
|
-
return { ...state, escaped: false };
|
|
2880
|
-
}
|
|
2881
|
-
if (char === "\\") {
|
|
2882
|
-
return { ...state, escaped: true };
|
|
2883
|
-
}
|
|
2884
|
-
if (state.quote) {
|
|
2885
|
-
return char === state.quote ? { ...state, quote: null } : state;
|
|
2886
|
-
}
|
|
2887
|
-
if (char === "\"" || char === "'") {
|
|
2888
|
-
return { ...state, quote: char };
|
|
2889
|
-
}
|
|
2890
|
-
if (char === "[" || char === "(") {
|
|
2891
|
-
return { ...state, depth: state.depth + 1 };
|
|
2892
|
-
}
|
|
2893
|
-
if ((char === "]" || char === ")") && state.depth > 0) {
|
|
2894
|
-
return { ...state, depth: state.depth - 1 };
|
|
2895
|
-
}
|
|
2896
|
-
return state;
|
|
2897
|
-
}
|
|
2898
|
-
|
|
2899
|
-
function customRuleAttributeSelectorMatches(node, attribute) {
|
|
2900
|
-
const match = attribute.match(/^([\w$.-]+)\s*(!=|=)\s*(.+)$/u);
|
|
2901
|
-
if (!match) {
|
|
2902
|
-
return customRuleValueByPath(node, attribute) != null;
|
|
2903
|
-
}
|
|
2904
|
-
const [, path, operator, rawExpected] = match;
|
|
2905
|
-
const actual = customRuleValueByPath(node, path);
|
|
2906
|
-
const expected = customRuleSelectorValue(rawExpected);
|
|
2907
|
-
return operator === "="
|
|
2908
|
-
? String(actual) === expected
|
|
2909
|
-
: String(actual) !== expected;
|
|
2910
|
-
}
|
|
2911
|
-
|
|
2912
|
-
function customRuleSelectorValue(raw) {
|
|
2913
|
-
const value = raw.trim();
|
|
2914
|
-
const quote = value[0];
|
|
2915
|
-
if ((quote === "\"" || quote === "'") && value.at(-1) === quote) {
|
|
2916
|
-
return value.slice(1, -1);
|
|
2917
|
-
}
|
|
2918
|
-
return value;
|
|
2919
|
-
}
|
|
2920
|
-
|
|
2921
|
-
function customRuleValueByPath(node, path) {
|
|
2922
|
-
return path.split(".").reduce((value, key) => (
|
|
2923
|
-
value && typeof value === "object" ? value[key] : undefined
|
|
2924
|
-
), node);
|
|
2925
|
-
}
|
|
2926
|
-
|
|
2927
|
-
function customRuleChildNodes(node, visitorKeys) {
|
|
2928
|
-
const keys = Array.isArray(visitorKeys?.[node.type])
|
|
2929
|
-
? visitorKeys[node.type]
|
|
2930
|
-
: Object.keys(node).filter((key) => !AST_TRAVERSAL_SKIP_KEYS.has(key));
|
|
2931
|
-
const children = [];
|
|
2932
|
-
for (const key of keys) {
|
|
2933
|
-
const value = node[key];
|
|
2934
|
-
const values = Array.isArray(value) ? value : [value];
|
|
2935
|
-
for (const child of values) {
|
|
2936
|
-
if (child && typeof child === "object" && typeof child.type === "string") {
|
|
2937
|
-
children.push(child);
|
|
2938
|
-
}
|
|
2939
|
-
}
|
|
2940
|
-
}
|
|
2941
|
-
return children;
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
3195
|
function runCustomRuleTokenListeners(program, tokens, listeners, context) {
|
|
2945
3196
|
if (typeof listeners.Program === "function") {
|
|
2946
3197
|
context.setCurrentNode(program);
|
|
@@ -2975,7 +3226,7 @@ function createCustomRuleContext(ruleEntry, sourceCode, options, messages) {
|
|
|
2975
3226
|
let currentNode = sourceCode.ast ?? null;
|
|
2976
3227
|
const context = {
|
|
2977
3228
|
id: ruleEntry.ruleId,
|
|
2978
|
-
options: ruleEntry.options,
|
|
3229
|
+
options: pluginRunner.applyRuleOptionDefaults(ruleEntry.rule, ruleEntry.options),
|
|
2979
3230
|
filename,
|
|
2980
3231
|
physicalFilename: filename,
|
|
2981
3232
|
cwd,
|
|
@@ -3193,7 +3444,7 @@ class SourceCode {
|
|
|
3193
3444
|
this.text = textOrConfig.text;
|
|
3194
3445
|
this.ast = textOrConfig.ast ?? null;
|
|
3195
3446
|
this.parserServices = textOrConfig.parserServices ?? {};
|
|
3196
|
-
this
|
|
3447
|
+
defineScopeManager(this, textOrConfig.scopeManager ?? null);
|
|
3197
3448
|
this.visitorKeys = textOrConfig.visitorKeys ?? null;
|
|
3198
3449
|
this.hasBOM = Boolean(textOrConfig.hasBOM);
|
|
3199
3450
|
} else {
|
|
@@ -3385,7 +3636,17 @@ class SourceCode {
|
|
|
3385
3636
|
}
|
|
3386
3637
|
|
|
3387
3638
|
getAncestors(node) {
|
|
3388
|
-
|
|
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) ?? [];
|
|
3389
3650
|
}
|
|
3390
3651
|
|
|
3391
3652
|
getDeclaredVariables(node) {
|
|
@@ -3393,10 +3654,20 @@ class SourceCode {
|
|
|
3393
3654
|
}
|
|
3394
3655
|
|
|
3395
3656
|
getScope(node) {
|
|
3396
|
-
|
|
3397
|
-
|
|
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
|
+
}
|
|
3398
3669
|
}
|
|
3399
|
-
return
|
|
3670
|
+
return scopeManager.scopes?.[0] ?? scopeManager.globalScope ?? null;
|
|
3400
3671
|
}
|
|
3401
3672
|
|
|
3402
3673
|
markVariableAsUsed(name, node) {
|
|
@@ -3432,6 +3703,31 @@ class SourceCode {
|
|
|
3432
3703
|
}
|
|
3433
3704
|
}
|
|
3434
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
|
+
|
|
3435
3731
|
function sourceLineStartIndices(text) {
|
|
3436
3732
|
const indices = [0];
|
|
3437
3733
|
for (let index = 0; index < text.length; index += 1) {
|
|
@@ -3721,12 +4017,10 @@ class RuleTester {
|
|
|
3721
4017
|
RuleTester[testCase.only ? "itOnly" : "it"](testCase.name ?? testCase.code, () => {
|
|
3722
4018
|
const config = ruleTesterConfig(ruleName, this.config, testCase, "error");
|
|
3723
4019
|
const options = ruleTesterOptions(testCase);
|
|
3724
|
-
const
|
|
3725
|
-
? linter.verifyAndFix(testCase.code, config, options)
|
|
3726
|
-
: { messages: linter.verify(testCase.code, config, options), output: testCase.code };
|
|
3727
|
-
const messages = result.messages;
|
|
4020
|
+
const messages = linter.verify(testCase.code, config, options);
|
|
3728
4021
|
assertRuleTesterErrors(testCase, messages, rule);
|
|
3729
|
-
|
|
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);
|
|
3730
4024
|
});
|
|
3731
4025
|
}
|
|
3732
4026
|
});
|
|
@@ -3873,6 +4167,47 @@ function applyRuleFixes(code, fix) {
|
|
|
3873
4167
|
.reduce((output, item) => output.slice(0, item.range[0]) + item.text + output.slice(item.range[1]), code);
|
|
3874
4168
|
}
|
|
3875
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
|
+
|
|
3876
4211
|
function ruleFixItems(fix) {
|
|
3877
4212
|
if (!fix) {
|
|
3878
4213
|
return [];
|
|
@@ -3925,10 +4260,29 @@ function createLinterSourceCode(text, parser = null, parserOptions = {}) {
|
|
|
3925
4260
|
});
|
|
3926
4261
|
}
|
|
3927
4262
|
|
|
3928
|
-
function
|
|
3929
|
-
|
|
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
|
+
}
|
|
3930
4271
|
return null;
|
|
3931
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
|
+
}
|
|
3932
4286
|
if (typeof parser.parseForESLint === "function") {
|
|
3933
4287
|
const result = parser.parseForESLint(text, parserOptions) ?? {};
|
|
3934
4288
|
return new SourceCode({
|
|
@@ -4194,7 +4548,7 @@ function applyCompatibilityDisableDirectives(result, textOptions) {
|
|
|
4194
4548
|
return;
|
|
4195
4549
|
}
|
|
4196
4550
|
|
|
4197
|
-
const filtered = applyDisableDirectives(result.messages, createLinterSourceCode(source));
|
|
4551
|
+
const filtered = applyDisableDirectives(result.messages, createLinterSourceCode(source, null, { tokensOnly: true }));
|
|
4198
4552
|
result.messages = filtered.messages;
|
|
4199
4553
|
result.suppressedMessages.push(...filtered.suppressedMessages);
|
|
4200
4554
|
}
|
|
@@ -4928,6 +5282,12 @@ function diagnosticToESLintMessage(diagnostic, ruleSeverities) {
|
|
|
4928
5282
|
column: diagnostic.column,
|
|
4929
5283
|
nodeType: null
|
|
4930
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
|
+
}
|
|
4931
5291
|
if (diagnostic.endLine != null) {
|
|
4932
5292
|
message.endLine = diagnostic.endLine;
|
|
4933
5293
|
}
|