@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.js
CHANGED
|
@@ -10,8 +10,12 @@ import { createLegacyConfigResolver } from "./lib/legacy-config.cjs";
|
|
|
10
10
|
import { formatESLintResults } from "./lib/stylish-formatter.cjs";
|
|
11
11
|
import {
|
|
12
12
|
findConfigPath as findConfigPathFromDirectory,
|
|
13
|
+
isExecutableConfigPath,
|
|
13
14
|
readConfig
|
|
14
15
|
} from "./lib/config-loader.js";
|
|
16
|
+
import configModule from "./lib/config-module.cjs";
|
|
17
|
+
import estreeParser from "./lib/estree-parser.cjs";
|
|
18
|
+
import pluginRunner from "./lib/plugin-runner.cjs";
|
|
15
19
|
|
|
16
20
|
export { platformPackageName, resolveBinary } from "./lib/binary.js";
|
|
17
21
|
|
|
@@ -28,10 +32,6 @@ const JS_KEYWORDS = new Set([
|
|
|
28
32
|
"instanceof", "let", "new", "return", "super", "switch", "this", "throw", "try", "typeof",
|
|
29
33
|
"var", "void", "while", "with", "yield"
|
|
30
34
|
]);
|
|
31
|
-
const AST_TRAVERSAL_SKIP_KEYS = new Set([
|
|
32
|
-
"type", "parent", "loc", "range", "tokens", "comments", "leadingComments", "trailingComments",
|
|
33
|
-
"innerComments", "raw", "value", "name", "regex", "bigint", "errors"
|
|
34
|
-
]);
|
|
35
35
|
const RULE_TESTER_INITIAL_CONFIG = { rules: {} };
|
|
36
36
|
let ruleTesterDefaultConfig = { rules: {} };
|
|
37
37
|
let ruleTesterDescribe = null;
|
|
@@ -400,6 +400,10 @@ const BUILTIN_RULE_IDS = [
|
|
|
400
400
|
"react/void-dom-elements-no-children",
|
|
401
401
|
"react-hooks/exhaustive-deps",
|
|
402
402
|
"react-hooks/rules-of-hooks",
|
|
403
|
+
"react-hooks/use-memo",
|
|
404
|
+
"react-hooks/void-use-memo",
|
|
405
|
+
"react-hooks/set-state-in-render",
|
|
406
|
+
"react-hooks/purity",
|
|
403
407
|
"unused-imports/no-unused-imports",
|
|
404
408
|
"@typescript-eslint/adjacent-overload-signatures",
|
|
405
409
|
"@typescript-eslint/array-type",
|
|
@@ -479,6 +483,7 @@ const NATIVE_ONLY_RULE_IDS = [
|
|
|
479
483
|
"semantic-errors"
|
|
480
484
|
];
|
|
481
485
|
const NATIVE_RULE_IDS = [...BUILTIN_RULE_IDS, ...NATIVE_ONLY_RULE_IDS];
|
|
486
|
+
const NATIVE_RULE_ID_SET = new Set(NATIVE_RULE_IDS);
|
|
482
487
|
const FIXABLE_BUILTIN_RULE_IDS = new Set([
|
|
483
488
|
"@typescript-eslint/no-explicit-any",
|
|
484
489
|
"jest/no-deprecated-functions",
|
|
@@ -606,15 +611,15 @@ export class UtooLint {
|
|
|
606
611
|
throw new Error("'results' must be an array");
|
|
607
612
|
}
|
|
608
613
|
const options = eslintConstructorOptions(this.options);
|
|
609
|
-
const customRuleConfig = [options.baseConfig, options.overrideConfig].filter(Boolean);
|
|
610
614
|
const rulesByFilePath = new Map();
|
|
615
|
+
const cachedOptions = withConfigCache(options);
|
|
611
616
|
return rulesMetaForResults(results, (ruleId, result) => {
|
|
612
|
-
if (
|
|
617
|
+
if (typeof result.filePath !== "string") {
|
|
613
618
|
return undefined;
|
|
614
619
|
}
|
|
615
620
|
const filePath = normalizeESLintFilePath(result.filePath, options.cwd);
|
|
616
621
|
if (!rulesByFilePath.has(filePath)) {
|
|
617
|
-
rulesByFilePath.set(filePath,
|
|
622
|
+
rulesByFilePath.set(filePath, jsPluginRuleMapForFile(cachedOptions, filePath));
|
|
618
623
|
}
|
|
619
624
|
return rulesByFilePath.get(filePath).get(ruleId)?.meta;
|
|
620
625
|
});
|
|
@@ -665,14 +670,14 @@ export class Linter {
|
|
|
665
670
|
const verifyOptions = typeof options === "string" ? { filename: options } : { ...options };
|
|
666
671
|
const filePath = verifyOptions.filename ?? verifyOptions.filePath ?? "input.js";
|
|
667
672
|
const normalizedFilePath = normalizeESLintFilePath(filePath, verifyOptions.cwd);
|
|
673
|
+
const customRuleMap = customRuleMapForConfig(config, this.rules, normalizedFilePath, verifyOptions.cwd);
|
|
674
|
+
const customRuleFilterMap = customRuleMapForConfig(config, this.rules);
|
|
675
|
+
const customRules = customRuleEntriesForConfig(config, customRuleMap, normalizedFilePath, verifyOptions.cwd);
|
|
668
676
|
const sourceCode = createLinterSourceCode(
|
|
669
677
|
code,
|
|
670
678
|
parserForConfig(config, this.parsers, normalizedFilePath, verifyOptions.cwd),
|
|
671
|
-
parserOptionsForConfig(config, normalizedFilePath, verifyOptions.cwd)
|
|
679
|
+
{ ...parserOptionsForConfig(config, normalizedFilePath, verifyOptions.cwd), requireParser: customRules.length > 0 }
|
|
672
680
|
);
|
|
673
|
-
const customRuleMap = customRuleMapForConfig(config, this.rules, normalizedFilePath, verifyOptions.cwd);
|
|
674
|
-
const customRuleFilterMap = customRuleMapForConfig(config, this.rules);
|
|
675
|
-
const customRules = customRuleEntriesForConfig(config, customRuleMap, normalizedFilePath, verifyOptions.cwd);
|
|
676
681
|
const nativeConfig = configWithoutCustomRules(config, customRuleFilterMap);
|
|
677
682
|
const lintOptions = {
|
|
678
683
|
cwd: verifyOptions.cwd,
|
|
@@ -702,10 +707,10 @@ export class Linter {
|
|
|
702
707
|
];
|
|
703
708
|
this.times = { passes: [] };
|
|
704
709
|
this.fixPassCount = 0;
|
|
705
|
-
return [
|
|
710
|
+
return sortLintMessages([
|
|
706
711
|
...nativeRuleFilter.messages,
|
|
707
712
|
...customRuleFilter.messages
|
|
708
|
-
];
|
|
713
|
+
]);
|
|
709
714
|
}
|
|
710
715
|
|
|
711
716
|
verifyAndFix(code, config = {}, options = {}) {
|
|
@@ -714,7 +719,7 @@ export class Linter {
|
|
|
714
719
|
let fixPassCount = 0;
|
|
715
720
|
for (let pass = 0; pass < MAX_AUTOFIX_PASSES; pass += 1) {
|
|
716
721
|
const messages = this.verify(output, config, options);
|
|
717
|
-
const fixedOutput =
|
|
722
|
+
const fixedOutput = applyMessageFixes(output, messages);
|
|
718
723
|
if (fixedOutput === output) {
|
|
719
724
|
this.fixPassCount = fixPassCount;
|
|
720
725
|
return {
|
|
@@ -797,9 +802,17 @@ function parserForConfig(config, parsers, filePath, cwd) {
|
|
|
797
802
|
|
|
798
803
|
function parserOptionsForConfig(config, filePath, cwd) {
|
|
799
804
|
const configData = configDataFromConfig(config, filePath, cwd);
|
|
800
|
-
|
|
805
|
+
const languageOptions = configData.languageOptions ?? {};
|
|
806
|
+
const parserOptions = {
|
|
801
807
|
...(configData.parserOptions ?? {}),
|
|
802
|
-
...(
|
|
808
|
+
...(languageOptions.parserOptions ?? {})
|
|
809
|
+
};
|
|
810
|
+
return {
|
|
811
|
+
...parserOptions,
|
|
812
|
+
sourceType: languageOptions.sourceType ?? parserOptions.sourceType,
|
|
813
|
+
ecmaVersion: languageOptions.ecmaVersion ?? parserOptions.ecmaVersion,
|
|
814
|
+
ecmaFeatures: parserOptions.ecmaFeatures ?? {},
|
|
815
|
+
globals: { ...(configData.globals ?? {}), ...(languageOptions.globals ?? {}) },
|
|
803
816
|
filename: filePath,
|
|
804
817
|
filePath,
|
|
805
818
|
cwd
|
|
@@ -845,13 +858,176 @@ function pluginRulesForConfig(config, filePath, cwd) {
|
|
|
845
858
|
continue;
|
|
846
859
|
}
|
|
847
860
|
for (const [ruleName, rule] of Object.entries(plugin.rules)) {
|
|
848
|
-
|
|
861
|
+
const ruleId = `${pluginName}/${ruleName}`;
|
|
862
|
+
// A native rule takes precedence over a plugin rule mounted under the
|
|
863
|
+
// same id, so a config copied from ESLint keeps its native speed.
|
|
864
|
+
if (NATIVE_RULE_ID_SET.has(ruleId)) {
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
rules.set(ruleId, rule);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return rules;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const pluginRuleIdsByConfig = new WeakMap();
|
|
875
|
+
|
|
876
|
+
function jsPluginRuleIdsFromConfigs(...configs) {
|
|
877
|
+
const ids = new Set();
|
|
878
|
+
for (const config of configs) {
|
|
879
|
+
if (!config || typeof config !== "object") {
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
let ruleIds = pluginRuleIdsByConfig.get(config);
|
|
883
|
+
if (!ruleIds) {
|
|
884
|
+
ruleIds = [...pluginRulesForConfig(config).keys()];
|
|
885
|
+
pluginRuleIdsByConfig.set(config, ruleIds);
|
|
886
|
+
}
|
|
887
|
+
for (const ruleId of ruleIds) {
|
|
888
|
+
ids.add(ruleId);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
return ids;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function withoutJsPluginRules(rules, jsPluginRuleIds) {
|
|
895
|
+
if (!rules || typeof rules !== "object" || jsPluginRuleIds.size === 0) {
|
|
896
|
+
return rules;
|
|
897
|
+
}
|
|
898
|
+
const filtered = {};
|
|
899
|
+
for (const [ruleId, ruleConfig] of Object.entries(rules)) {
|
|
900
|
+
if (!jsPluginRuleIds.has(ruleId)) {
|
|
901
|
+
filtered[ruleId] = ruleConfig;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return filtered;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function configDeclaresPlugins(config) {
|
|
908
|
+
if (Array.isArray(config)) {
|
|
909
|
+
return config.some((entry) => configDeclaresPlugins(entry));
|
|
910
|
+
}
|
|
911
|
+
return Boolean(
|
|
912
|
+
config
|
|
913
|
+
&& typeof config === "object"
|
|
914
|
+
&& config.plugins
|
|
915
|
+
&& typeof config.plugins === "object"
|
|
916
|
+
&& !Array.isArray(config.plugins)
|
|
917
|
+
&& Object.keys(config.plugins).length > 0
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Cheap pre-check so runs without plugins skip the per-file plugin work. Every
|
|
922
|
+
// config the native run consulted was read through `options.configCache`.
|
|
923
|
+
function anyConfigDeclaresPlugins(options) {
|
|
924
|
+
if (configDeclaresPlugins(options.baseConfig) || configDeclaresPlugins(options.overrideConfig)) {
|
|
925
|
+
return true;
|
|
926
|
+
}
|
|
927
|
+
if (options.noConfig) {
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
let sawConfig = false;
|
|
931
|
+
for (const config of options.configCache?.values() ?? []) {
|
|
932
|
+
if (config && typeof config === "object") {
|
|
933
|
+
sawConfig = true;
|
|
934
|
+
if (configDeclaresPlugins(config)) {
|
|
935
|
+
return true;
|
|
849
936
|
}
|
|
850
937
|
}
|
|
851
938
|
}
|
|
939
|
+
if (sawConfig) {
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
const configPath = configPathForOptions(options);
|
|
943
|
+
return Boolean(configPath) && configDeclaresPlugins(readConfig(configPath, options.cwd, options.configCache));
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// The native engine receives the JSON form of a config module. Plugin objects
|
|
947
|
+
// (and their `create()` functions) only survive an in-process import, so a
|
|
948
|
+
// config module that mounts plugins is loaded again in this process.
|
|
949
|
+
function liveConfigForPath(configPath, options) {
|
|
950
|
+
const config = readConfig(configPath, options.cwd, options.configCache);
|
|
951
|
+
if (!isExecutableConfigPath(configPath) || !configDeclaresPlugins(config)) {
|
|
952
|
+
return config;
|
|
953
|
+
}
|
|
954
|
+
return configModule.readConfigModule(configPath, options.cwd);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function pluginConfigPathForFile(options, matchPath, perFileConfig = true) {
|
|
958
|
+
if (options.noConfig) {
|
|
959
|
+
return undefined;
|
|
960
|
+
}
|
|
961
|
+
return perFileConfig && matchPath ? configPathForFile(options, matchPath) : configPathForOptions(options);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function pluginConfigSourcesForFile(options, matchPath, perFileConfig = true) {
|
|
965
|
+
const cwd = options.cwd ?? process.cwd();
|
|
966
|
+
const sources = [];
|
|
967
|
+
if (options.baseConfig) {
|
|
968
|
+
sources.push({ config: options.baseConfig, cwd });
|
|
969
|
+
}
|
|
970
|
+
const configPath = pluginConfigPathForFile(options, matchPath, perFileConfig);
|
|
971
|
+
if (configPath) {
|
|
972
|
+
sources.push({ config: liveConfigForPath(configPath, options), cwd: dirname(configPath) });
|
|
973
|
+
}
|
|
974
|
+
if (options.overrideConfig) {
|
|
975
|
+
sources.push({ config: options.overrideConfig, cwd });
|
|
976
|
+
}
|
|
977
|
+
return sources;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function jsPluginRuleIdsForOptions(options, matchPath) {
|
|
981
|
+
const configs = [options.baseConfig, options.overrideConfig];
|
|
982
|
+
if (!options.noConfig) {
|
|
983
|
+
const configPath = matchPath ? configPathForFile(options, matchPath) : configPathForOptions(options);
|
|
984
|
+
if (configPath) {
|
|
985
|
+
configs.push(readConfig(configPath, options.cwd, options.configCache));
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return jsPluginRuleIdsFromConfigs(...configs);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function jsPluginRuleMapForFile(options, matchPath, perFileConfig = true) {
|
|
992
|
+
const rules = new Map();
|
|
993
|
+
for (const { config, cwd } of pluginConfigSourcesForFile(options, matchPath, perFileConfig)) {
|
|
994
|
+
for (const [ruleId, rule] of pluginRulesForConfig(config, matchPath, cwd)) {
|
|
995
|
+
rules.set(ruleId, rule);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
852
998
|
return rules;
|
|
853
999
|
}
|
|
854
1000
|
|
|
1001
|
+
function pluginRuleEntriesForFile(options, matchPath, ruleMap, perFileConfig = true) {
|
|
1002
|
+
const entries = new Map();
|
|
1003
|
+
for (const { config, cwd } of pluginConfigSourcesForFile(options, matchPath, perFileConfig)) {
|
|
1004
|
+
for (const [ruleId, ruleConfig] of Object.entries(rulesFromConfig(config, matchPath, cwd))) {
|
|
1005
|
+
if (!ruleMap.has(ruleId)) {
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
const severity = ruleConfigSeverity(ruleConfig);
|
|
1009
|
+
if (severity === 0) {
|
|
1010
|
+
entries.delete(ruleId);
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
entries.set(ruleId, {
|
|
1014
|
+
ruleId,
|
|
1015
|
+
rule: ruleMap.get(ruleId),
|
|
1016
|
+
severity,
|
|
1017
|
+
options: Array.isArray(ruleConfig) ? ruleConfig.slice(1) : []
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (options.rules) {
|
|
1022
|
+
// `--rules` disables everything first and then enables only the listed
|
|
1023
|
+
// rules, keeping configured severities and options like the native path.
|
|
1024
|
+
return Object.keys(rulesFromNativeRuleList(options.rules))
|
|
1025
|
+
.filter((ruleId) => ruleMap.has(ruleId))
|
|
1026
|
+
.map((ruleId) => entries.get(ruleId) ?? { ruleId, rule: ruleMap.get(ruleId), severity: 1, options: [] });
|
|
1027
|
+
}
|
|
1028
|
+
return [...entries.values()];
|
|
1029
|
+
}
|
|
1030
|
+
|
|
855
1031
|
function matchingConfigEntries(config, filePath = undefined, cwd = undefined) {
|
|
856
1032
|
if (!config) {
|
|
857
1033
|
return [];
|
|
@@ -903,7 +1079,7 @@ function appendCustomLintTextMessages(results, code, filePath, config, rules, op
|
|
|
903
1079
|
}
|
|
904
1080
|
let result = results.find((item) => item.filePath === filePath);
|
|
905
1081
|
const effectiveCode = typeof result?.output === "string" ? result.output : code;
|
|
906
|
-
const sourceCode = createLinterSourceCode(effectiveCode);
|
|
1082
|
+
const sourceCode = createLinterSourceCode(effectiveCode, null, { ...parserOptionsForConfig(config, filePath, options.cwd), requireParser: true });
|
|
907
1083
|
const messages = runCustomLinterRules(customRules, sourceCode, {
|
|
908
1084
|
cwd: options.cwd,
|
|
909
1085
|
filename: filePath,
|
|
@@ -918,13 +1094,74 @@ function appendCustomLintTextMessages(results, code, filePath, config, rules, op
|
|
|
918
1094
|
result = emptyESLintResult(filePath, effectiveCode);
|
|
919
1095
|
results.push(result);
|
|
920
1096
|
}
|
|
921
|
-
result
|
|
1097
|
+
if (relintAfterCustomFixes(result, effectiveCode, filePath, customRules, config, rules, options)) {
|
|
1098
|
+
return results;
|
|
1099
|
+
}
|
|
1100
|
+
result.messages = sortLintMessages([...result.messages, ...filtered.messages]);
|
|
922
1101
|
result.suppressedMessages.push(...filtered.suppressedMessages);
|
|
923
1102
|
applyResultFixes(result, effectiveCode, options);
|
|
924
1103
|
finalizeESLintResult(result);
|
|
925
1104
|
return results;
|
|
926
1105
|
}
|
|
927
1106
|
|
|
1107
|
+
// When custom fixes change a file, the native messages computed earlier are
|
|
1108
|
+
// stale. Re-lint the fixed text with native rules and custom rules together
|
|
1109
|
+
// until neither changes it, then rebuild the result from that final pass.
|
|
1110
|
+
function relintAfterCustomFixes(result, code, filePath, customRules, config, rules, options) {
|
|
1111
|
+
if (!options.fix) {
|
|
1112
|
+
return false;
|
|
1113
|
+
}
|
|
1114
|
+
const nativeOptions = {
|
|
1115
|
+
...lintOptionsWithoutCustomRules(options, rules),
|
|
1116
|
+
filePath,
|
|
1117
|
+
filename: undefined,
|
|
1118
|
+
fix: true,
|
|
1119
|
+
useDefaultConfigTargets: undefined,
|
|
1120
|
+
pluginTextFilePath: undefined
|
|
1121
|
+
};
|
|
1122
|
+
const parserOptions = { ...parserOptionsForConfig(config, filePath, options.cwd), requireParser: true };
|
|
1123
|
+
const customLintOptions = {
|
|
1124
|
+
cwd: options.cwd,
|
|
1125
|
+
filename: filePath,
|
|
1126
|
+
config: calculatedConfig({ cwd: options.cwd, noConfig: true, baseConfig: options.baseConfig, overrideConfig: options.overrideConfig }, filePath)
|
|
1127
|
+
};
|
|
1128
|
+
const lintCustom = (text) => {
|
|
1129
|
+
const sourceCode = createLinterSourceCode(text, null, parserOptions);
|
|
1130
|
+
return applyDisableDirectives(runCustomLinterRules(customRules, sourceCode, customLintOptions), sourceCode);
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
let output = code;
|
|
1134
|
+
let custom = lintCustom(output);
|
|
1135
|
+
let fixedOutput = applyMessageFixes(output, custom.messages);
|
|
1136
|
+
if (fixedOutput === output) {
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
let nativeReport;
|
|
1140
|
+
for (let pass = 0; pass < MAX_AUTOFIX_PASSES && fixedOutput !== output; pass += 1) {
|
|
1141
|
+
nativeReport = lintText(fixedOutput, nativeOptions);
|
|
1142
|
+
output = nativeReport.outputs?.find((fixed) => fixed.filePath === filePath)?.output ?? fixedOutput;
|
|
1143
|
+
custom = lintCustom(output);
|
|
1144
|
+
fixedOutput = applyMessageFixes(output, custom.messages);
|
|
1145
|
+
}
|
|
1146
|
+
if (output !== fixedOutput) {
|
|
1147
|
+
output = fixedOutput;
|
|
1148
|
+
nativeReport = lintText(output, nativeOptions);
|
|
1149
|
+
custom = lintCustom(output);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const nativeResult = reportToESLintResults(nativeReport, {
|
|
1153
|
+
source: result.source,
|
|
1154
|
+
filePath,
|
|
1155
|
+
includeEmptyTextResult: true,
|
|
1156
|
+
ruleSeverityForFile: (path) => ruleSeverityMapForOptions(nativeOptions, path)
|
|
1157
|
+
})[0];
|
|
1158
|
+
result.messages = sortLintMessages([...(nativeResult?.messages ?? []), ...custom.messages]);
|
|
1159
|
+
result.suppressedMessages = [...(nativeResult?.suppressedMessages ?? []), ...custom.suppressedMessages];
|
|
1160
|
+
result.output = output;
|
|
1161
|
+
finalizeESLintResult(result);
|
|
1162
|
+
return true;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
928
1165
|
function appendCustomLintFileMessages(results, config, options) {
|
|
929
1166
|
for (const result of results) {
|
|
930
1167
|
const filePath = result.filePath;
|
|
@@ -941,7 +1178,7 @@ function appendCustomLintFileMessages(results, config, options) {
|
|
|
941
1178
|
continue;
|
|
942
1179
|
}
|
|
943
1180
|
}
|
|
944
|
-
const sourceCode = createLinterSourceCode(code);
|
|
1181
|
+
const sourceCode = createLinterSourceCode(code, null, { ...parserOptionsForConfig(config, filePath, options.cwd), requireParser: true });
|
|
945
1182
|
const messages = runCustomLinterRules(customRules, sourceCode, {
|
|
946
1183
|
cwd: options.cwd,
|
|
947
1184
|
filename: filePath,
|
|
@@ -951,7 +1188,10 @@ function appendCustomLintFileMessages(results, config, options) {
|
|
|
951
1188
|
if (filtered.messages.length === 0 && filtered.suppressedMessages.length === 0) {
|
|
952
1189
|
continue;
|
|
953
1190
|
}
|
|
954
|
-
result
|
|
1191
|
+
if (relintAfterCustomFixes(result, code, filePath, customRules, config, rules, options)) {
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
result.messages = sortLintMessages([...result.messages, ...filtered.messages]);
|
|
955
1195
|
result.suppressedMessages.push(...filtered.suppressedMessages);
|
|
956
1196
|
applyResultFixes(result, code, options);
|
|
957
1197
|
finalizeESLintResult(result);
|
|
@@ -959,11 +1199,22 @@ function appendCustomLintFileMessages(results, config, options) {
|
|
|
959
1199
|
return results;
|
|
960
1200
|
}
|
|
961
1201
|
|
|
1202
|
+
// ESLint orders a file's messages by position; native and plugin messages
|
|
1203
|
+
// arrive as two separate batches.
|
|
1204
|
+
function sortLintMessages(messages) {
|
|
1205
|
+
return messages
|
|
1206
|
+
.map((message, index) => ({ message, index }))
|
|
1207
|
+
.sort((left, right) => (left.message.line ?? 0) - (right.message.line ?? 0)
|
|
1208
|
+
|| (left.message.column ?? 0) - (right.message.column ?? 0)
|
|
1209
|
+
|| left.index - right.index)
|
|
1210
|
+
.map(({ message }) => message);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
962
1213
|
function applyResultFixes(result, code, options) {
|
|
963
1214
|
if (!options.fix) {
|
|
964
1215
|
return;
|
|
965
1216
|
}
|
|
966
|
-
const output =
|
|
1217
|
+
const output = applyMessageFixes(code, result.messages);
|
|
967
1218
|
if (output !== code) {
|
|
968
1219
|
result.output = output;
|
|
969
1220
|
}
|
|
@@ -976,15 +1227,40 @@ function runCustomLinterRules(ruleEntries, sourceCode, options) {
|
|
|
976
1227
|
|
|
977
1228
|
const messages = [];
|
|
978
1229
|
const program = sourceCode.ast ?? { type: "Program", range: [0, sourceCode.text.length] };
|
|
1230
|
+
const hasAst = Array.isArray(program.body);
|
|
1231
|
+
const listenersBySelector = new Map();
|
|
979
1232
|
for (const ruleEntry of ruleEntries) {
|
|
1233
|
+
if (typeof ruleEntry.rule !== "function" && typeof ruleEntry.rule?.create !== "function") {
|
|
1234
|
+
throw new Error(
|
|
1235
|
+
`utoo-lint: rule "${ruleEntry.ruleId}" has no create() function. ` +
|
|
1236
|
+
"ESLint plugins must be mounted from a utlint.config.ts (or .js) module so their rule objects are available at lint time."
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
980
1239
|
const context = createCustomRuleContext(ruleEntry, sourceCode, options, messages);
|
|
981
1240
|
const listeners = customRuleListeners(ruleEntry.rule, context);
|
|
982
|
-
if (
|
|
983
|
-
traverseCustomRuleAst(program, listeners, context, sourceCode.visitorKeys);
|
|
984
|
-
} else {
|
|
1241
|
+
if (!hasAst) {
|
|
985
1242
|
runCustomRuleTokenListeners(program, sourceCode.tokens, listeners, context);
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
for (const [selector, listener] of Object.entries(listeners)) {
|
|
1246
|
+
if (typeof listener !== "function") {
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
if (!listenersBySelector.has(selector)) {
|
|
1250
|
+
listenersBySelector.set(selector, []);
|
|
1251
|
+
}
|
|
1252
|
+
listenersBySelector.get(selector).push({
|
|
1253
|
+
ruleId: ruleEntry.ruleId,
|
|
1254
|
+
listener(node) {
|
|
1255
|
+
context.setCurrentNode(node);
|
|
1256
|
+
listener(node);
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
986
1259
|
}
|
|
987
1260
|
}
|
|
1261
|
+
if (hasAst && listenersBySelector.size > 0) {
|
|
1262
|
+
pluginRunner.runRuleListeners(program, listenersBySelector, { visitorKeys: sourceCode.visitorKeys });
|
|
1263
|
+
}
|
|
988
1264
|
return messages;
|
|
989
1265
|
}
|
|
990
1266
|
|
|
@@ -1140,231 +1416,6 @@ function disableDirectiveMatchesRule(directive, ruleId) {
|
|
|
1140
1416
|
return directive.ruleId == null || directive.ruleId === ruleId;
|
|
1141
1417
|
}
|
|
1142
1418
|
|
|
1143
|
-
function traverseCustomRuleAst(node, listeners, context, visitorKeys, seen = new Set(), ancestors = [], siblings = {}) {
|
|
1144
|
-
if (!node || typeof node !== "object" || typeof node.type !== "string" || seen.has(node)) {
|
|
1145
|
-
return;
|
|
1146
|
-
}
|
|
1147
|
-
seen.add(node);
|
|
1148
|
-
for (const listener of customRuleMatchingListeners(listeners, node, false, ancestors, siblings, visitorKeys)) {
|
|
1149
|
-
context.setCurrentNode(node);
|
|
1150
|
-
listener(node);
|
|
1151
|
-
}
|
|
1152
|
-
const children = customRuleChildNodes(node, visitorKeys);
|
|
1153
|
-
for (const [index, child] of children.entries()) {
|
|
1154
|
-
traverseCustomRuleAst(child, listeners, context, visitorKeys, seen, [...ancestors, node], {
|
|
1155
|
-
previous: children[index - 1] ?? null,
|
|
1156
|
-
previousAll: children.slice(0, index)
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
for (const listener of customRuleMatchingListeners(listeners, node, true, ancestors, siblings, visitorKeys)) {
|
|
1160
|
-
context.setCurrentNode(node);
|
|
1161
|
-
listener(node);
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
function customRuleMatchingListeners(listeners, node, exit, ancestors = [], siblings = {}, visitorKeys = null) {
|
|
1166
|
-
const matches = [];
|
|
1167
|
-
for (const [selector, listener] of Object.entries(listeners)) {
|
|
1168
|
-
if (typeof listener !== "function" || !customRuleSelectorMatches(selector, node, exit, ancestors, siblings, visitorKeys)) {
|
|
1169
|
-
continue;
|
|
1170
|
-
}
|
|
1171
|
-
matches.push(listener);
|
|
1172
|
-
}
|
|
1173
|
-
return matches;
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
function customRuleSelectorMatches(selector, node, exit, ancestors = [], siblings = {}, visitorKeys = null) {
|
|
1177
|
-
const suffix = ":exit";
|
|
1178
|
-
const isExit = selector.endsWith(suffix);
|
|
1179
|
-
if (isExit !== exit) {
|
|
1180
|
-
return false;
|
|
1181
|
-
}
|
|
1182
|
-
const expression = isExit ? selector.slice(0, -suffix.length) : selector;
|
|
1183
|
-
const childSelector = customRuleSplitTopLevelSelector(expression, ">");
|
|
1184
|
-
if (childSelector) {
|
|
1185
|
-
const parent = ancestors.at(-1);
|
|
1186
|
-
return Boolean(parent)
|
|
1187
|
-
&& customRuleSimpleSelectorMatches(childSelector[0].trim(), parent, visitorKeys)
|
|
1188
|
-
&& customRuleSimpleSelectorMatches(childSelector[1].trim(), node, visitorKeys);
|
|
1189
|
-
}
|
|
1190
|
-
const adjacentSelector = customRuleSplitTopLevelSelector(expression, "+");
|
|
1191
|
-
if (adjacentSelector) {
|
|
1192
|
-
return Boolean(siblings.previous)
|
|
1193
|
-
&& customRuleSimpleSelectorMatches(adjacentSelector[0].trim(), siblings.previous, visitorKeys)
|
|
1194
|
-
&& customRuleSimpleSelectorMatches(adjacentSelector[1].trim(), node, visitorKeys);
|
|
1195
|
-
}
|
|
1196
|
-
const siblingSelector = customRuleSplitTopLevelSelector(expression, "~");
|
|
1197
|
-
if (siblingSelector) {
|
|
1198
|
-
return (siblings.previousAll ?? []).some((sibling) => customRuleSimpleSelectorMatches(siblingSelector[0].trim(), sibling, visitorKeys))
|
|
1199
|
-
&& customRuleSimpleSelectorMatches(siblingSelector[1].trim(), node, visitorKeys);
|
|
1200
|
-
}
|
|
1201
|
-
const descendantSelector = customRuleSplitTopLevelDescendantSelector(expression);
|
|
1202
|
-
if (descendantSelector) {
|
|
1203
|
-
return ancestors.some((ancestor) => customRuleSimpleSelectorMatches(descendantSelector[0].trim(), ancestor, visitorKeys))
|
|
1204
|
-
&& customRuleSimpleSelectorMatches(descendantSelector[1].trim(), node, visitorKeys);
|
|
1205
|
-
}
|
|
1206
|
-
return customRuleSimpleSelectorMatches(expression, node, visitorKeys);
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
function customRuleSimpleSelectorMatches(expression, node, visitorKeys = null) {
|
|
1210
|
-
const hasSelector = expression.match(/^(.+?):has\((.+)\)$/u);
|
|
1211
|
-
if (hasSelector) {
|
|
1212
|
-
return customRuleSimpleSelectorMatches(hasSelector[1].trim(), node, visitorKeys)
|
|
1213
|
-
&& customRuleDescendants(node, visitorKeys).some((descendant) => (
|
|
1214
|
-
customRuleSplitSelectorList(hasSelector[2]).some((selector) => customRuleSimpleSelectorMatches(selector.trim(), descendant, visitorKeys))
|
|
1215
|
-
));
|
|
1216
|
-
}
|
|
1217
|
-
const notSelector = expression.match(/^(.+?):not\((.+)\)$/u);
|
|
1218
|
-
if (notSelector) {
|
|
1219
|
-
return customRuleSimpleSelectorMatches(notSelector[1].trim(), node, visitorKeys)
|
|
1220
|
-
&& !customRuleSimpleSelectorMatches(notSelector[2].trim(), node, visitorKeys);
|
|
1221
|
-
}
|
|
1222
|
-
const matchesSelector = expression.match(/^(.*?):matches\((.+)\)$/u);
|
|
1223
|
-
if (matchesSelector) {
|
|
1224
|
-
const prefix = matchesSelector[1].trim();
|
|
1225
|
-
if (prefix && !customRuleSimpleSelectorMatches(prefix, node, visitorKeys)) {
|
|
1226
|
-
return false;
|
|
1227
|
-
}
|
|
1228
|
-
return customRuleSplitSelectorList(matchesSelector[2]).some((selector) => (
|
|
1229
|
-
customRuleSimpleSelectorMatches(selector.trim(), node, visitorKeys)
|
|
1230
|
-
));
|
|
1231
|
-
}
|
|
1232
|
-
if (expression === node.type) {
|
|
1233
|
-
return true;
|
|
1234
|
-
}
|
|
1235
|
-
const match = expression.match(/^([A-Za-z_$][\w$-]*|\*)?(?:\[([^\]]+)\])?$/u);
|
|
1236
|
-
if (!match) {
|
|
1237
|
-
return false;
|
|
1238
|
-
}
|
|
1239
|
-
const [, type = "*", attribute] = match;
|
|
1240
|
-
if (type !== "*" && type !== node.type) {
|
|
1241
|
-
return false;
|
|
1242
|
-
}
|
|
1243
|
-
return attribute ? customRuleAttributeSelectorMatches(node, attribute.trim()) : type === "*" || type === node.type;
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
function customRuleDescendants(node, visitorKeys, seen = new Set()) {
|
|
1247
|
-
const descendants = [];
|
|
1248
|
-
for (const child of customRuleChildNodes(node, visitorKeys)) {
|
|
1249
|
-
if (seen.has(child)) {
|
|
1250
|
-
continue;
|
|
1251
|
-
}
|
|
1252
|
-
seen.add(child);
|
|
1253
|
-
descendants.push(child, ...customRuleDescendants(child, visitorKeys, seen));
|
|
1254
|
-
}
|
|
1255
|
-
return descendants;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
function customRuleSplitTopLevelSelector(expression, separator) {
|
|
1259
|
-
let state = customRuleSelectorScanState();
|
|
1260
|
-
for (let index = 0; index < expression.length; index += 1) {
|
|
1261
|
-
state = customRuleUpdateSelectorScanState(state, expression[index]);
|
|
1262
|
-
if (state.depth === 0 && !state.quote && expression[index] === separator) {
|
|
1263
|
-
return [expression.slice(0, index), expression.slice(index + 1)];
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
return null;
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
function customRuleSplitTopLevelDescendantSelector(expression) {
|
|
1270
|
-
let state = customRuleSelectorScanState();
|
|
1271
|
-
for (let index = 0; index < expression.length; index += 1) {
|
|
1272
|
-
state = customRuleUpdateSelectorScanState(state, expression[index]);
|
|
1273
|
-
if (state.depth === 0 && !state.quote && /\s/u.test(expression[index])) {
|
|
1274
|
-
const left = expression.slice(0, index).trim();
|
|
1275
|
-
const right = expression.slice(index).trim();
|
|
1276
|
-
return left && right ? [left, right] : null;
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
return null;
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
function customRuleSplitSelectorList(value) {
|
|
1283
|
-
const selectors = [];
|
|
1284
|
-
let state = customRuleSelectorScanState();
|
|
1285
|
-
let start = 0;
|
|
1286
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
1287
|
-
state = customRuleUpdateSelectorScanState(state, value[index]);
|
|
1288
|
-
if (state.depth === 0 && !state.quote && value[index] === ",") {
|
|
1289
|
-
selectors.push(value.slice(start, index));
|
|
1290
|
-
start = index + 1;
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
selectors.push(value.slice(start));
|
|
1294
|
-
return selectors.filter((selector) => selector.trim());
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
function customRuleSelectorScanState() {
|
|
1298
|
-
return { depth: 0, quote: null, escaped: false };
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
function customRuleUpdateSelectorScanState(state, char) {
|
|
1302
|
-
if (state.escaped) {
|
|
1303
|
-
return { ...state, escaped: false };
|
|
1304
|
-
}
|
|
1305
|
-
if (char === "\\") {
|
|
1306
|
-
return { ...state, escaped: true };
|
|
1307
|
-
}
|
|
1308
|
-
if (state.quote) {
|
|
1309
|
-
return char === state.quote ? { ...state, quote: null } : state;
|
|
1310
|
-
}
|
|
1311
|
-
if (char === "\"" || char === "'") {
|
|
1312
|
-
return { ...state, quote: char };
|
|
1313
|
-
}
|
|
1314
|
-
if (char === "[" || char === "(") {
|
|
1315
|
-
return { ...state, depth: state.depth + 1 };
|
|
1316
|
-
}
|
|
1317
|
-
if ((char === "]" || char === ")") && state.depth > 0) {
|
|
1318
|
-
return { ...state, depth: state.depth - 1 };
|
|
1319
|
-
}
|
|
1320
|
-
return state;
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
function customRuleAttributeSelectorMatches(node, attribute) {
|
|
1324
|
-
const match = attribute.match(/^([\w$.-]+)\s*(!=|=)\s*(.+)$/u);
|
|
1325
|
-
if (!match) {
|
|
1326
|
-
return customRuleValueByPath(node, attribute) != null;
|
|
1327
|
-
}
|
|
1328
|
-
const [, path, operator, rawExpected] = match;
|
|
1329
|
-
const actual = customRuleValueByPath(node, path);
|
|
1330
|
-
const expected = customRuleSelectorValue(rawExpected);
|
|
1331
|
-
return operator === "="
|
|
1332
|
-
? String(actual) === expected
|
|
1333
|
-
: String(actual) !== expected;
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
function customRuleSelectorValue(raw) {
|
|
1337
|
-
const value = raw.trim();
|
|
1338
|
-
const quote = value[0];
|
|
1339
|
-
if ((quote === "\"" || quote === "'") && value.at(-1) === quote) {
|
|
1340
|
-
return value.slice(1, -1);
|
|
1341
|
-
}
|
|
1342
|
-
return value;
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
function customRuleValueByPath(node, path) {
|
|
1346
|
-
return path.split(".").reduce((value, key) => (
|
|
1347
|
-
value && typeof value === "object" ? value[key] : undefined
|
|
1348
|
-
), node);
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
function customRuleChildNodes(node, visitorKeys) {
|
|
1352
|
-
const keys = Array.isArray(visitorKeys?.[node.type])
|
|
1353
|
-
? visitorKeys[node.type]
|
|
1354
|
-
: Object.keys(node).filter((key) => !AST_TRAVERSAL_SKIP_KEYS.has(key));
|
|
1355
|
-
const children = [];
|
|
1356
|
-
for (const key of keys) {
|
|
1357
|
-
const value = node[key];
|
|
1358
|
-
const values = Array.isArray(value) ? value : [value];
|
|
1359
|
-
for (const child of values) {
|
|
1360
|
-
if (child && typeof child === "object" && typeof child.type === "string") {
|
|
1361
|
-
children.push(child);
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
return children;
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
1419
|
function runCustomRuleTokenListeners(program, tokens, listeners, context) {
|
|
1369
1420
|
if (typeof listeners.Program === "function") {
|
|
1370
1421
|
context.setCurrentNode(program);
|
|
@@ -1399,7 +1450,7 @@ function createCustomRuleContext(ruleEntry, sourceCode, options, messages) {
|
|
|
1399
1450
|
let currentNode = sourceCode.ast ?? null;
|
|
1400
1451
|
const context = {
|
|
1401
1452
|
id: ruleEntry.ruleId,
|
|
1402
|
-
options: ruleEntry.options,
|
|
1453
|
+
options: pluginRunner.applyRuleOptionDefaults(ruleEntry.rule, ruleEntry.options),
|
|
1403
1454
|
filename,
|
|
1404
1455
|
physicalFilename: filename,
|
|
1405
1456
|
cwd,
|
|
@@ -1617,7 +1668,7 @@ export class SourceCode {
|
|
|
1617
1668
|
this.text = textOrConfig.text;
|
|
1618
1669
|
this.ast = textOrConfig.ast ?? null;
|
|
1619
1670
|
this.parserServices = textOrConfig.parserServices ?? {};
|
|
1620
|
-
this
|
|
1671
|
+
defineScopeManager(this, textOrConfig.scopeManager ?? null);
|
|
1621
1672
|
this.visitorKeys = textOrConfig.visitorKeys ?? null;
|
|
1622
1673
|
this.hasBOM = Boolean(textOrConfig.hasBOM);
|
|
1623
1674
|
} else {
|
|
@@ -1809,7 +1860,17 @@ export class SourceCode {
|
|
|
1809
1860
|
}
|
|
1810
1861
|
|
|
1811
1862
|
getAncestors(node) {
|
|
1812
|
-
|
|
1863
|
+
if (!node) {
|
|
1864
|
+
return [];
|
|
1865
|
+
}
|
|
1866
|
+
if (node.parent !== undefined) {
|
|
1867
|
+
const ancestors = [];
|
|
1868
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
1869
|
+
ancestors.unshift(current);
|
|
1870
|
+
}
|
|
1871
|
+
return ancestors;
|
|
1872
|
+
}
|
|
1873
|
+
return sourceAncestorsForNode(this.ast, node) ?? [];
|
|
1813
1874
|
}
|
|
1814
1875
|
|
|
1815
1876
|
getDeclaredVariables(node) {
|
|
@@ -1817,10 +1878,20 @@ export class SourceCode {
|
|
|
1817
1878
|
}
|
|
1818
1879
|
|
|
1819
1880
|
getScope(node) {
|
|
1820
|
-
|
|
1821
|
-
|
|
1881
|
+
const scopeManager = this.scopeManager;
|
|
1882
|
+
if (!scopeManager || typeof scopeManager.acquire !== "function") {
|
|
1883
|
+
return null;
|
|
1884
|
+
}
|
|
1885
|
+
if (node) {
|
|
1886
|
+
const inner = node.type !== "Program";
|
|
1887
|
+
for (let current = node; current; current = current.parent) {
|
|
1888
|
+
const scope = scopeManager.acquire(current, inner);
|
|
1889
|
+
if (scope) {
|
|
1890
|
+
return scope.type === "function-expression-name" ? scope.childScopes[0] : scope;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1822
1893
|
}
|
|
1823
|
-
return
|
|
1894
|
+
return scopeManager.scopes?.[0] ?? scopeManager.globalScope ?? null;
|
|
1824
1895
|
}
|
|
1825
1896
|
|
|
1826
1897
|
markVariableAsUsed(name, node) {
|
|
@@ -1856,6 +1927,31 @@ export class SourceCode {
|
|
|
1856
1927
|
}
|
|
1857
1928
|
}
|
|
1858
1929
|
|
|
1930
|
+
function defineScopeManager(sourceCode, scopeManager) {
|
|
1931
|
+
if (typeof scopeManager !== "function") {
|
|
1932
|
+
sourceCode.scopeManager = scopeManager;
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
// Scope analysis is comparatively expensive; run it only when a rule asks.
|
|
1936
|
+
let computed = false;
|
|
1937
|
+
let value = null;
|
|
1938
|
+
Object.defineProperty(sourceCode, "scopeManager", {
|
|
1939
|
+
configurable: true,
|
|
1940
|
+
enumerable: true,
|
|
1941
|
+
get() {
|
|
1942
|
+
if (!computed) {
|
|
1943
|
+
computed = true;
|
|
1944
|
+
value = scopeManager() ?? null;
|
|
1945
|
+
}
|
|
1946
|
+
return value;
|
|
1947
|
+
},
|
|
1948
|
+
set(next) {
|
|
1949
|
+
computed = true;
|
|
1950
|
+
value = next;
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1859
1955
|
function sourceLineStartIndices(text) {
|
|
1860
1956
|
const indices = [0];
|
|
1861
1957
|
for (let index = 0; index < text.length; index += 1) {
|
|
@@ -2145,12 +2241,10 @@ export class RuleTester {
|
|
|
2145
2241
|
RuleTester[testCase.only ? "itOnly" : "it"](testCase.name ?? testCase.code, () => {
|
|
2146
2242
|
const config = ruleTesterConfig(ruleName, this.config, testCase, "error");
|
|
2147
2243
|
const options = ruleTesterOptions(testCase);
|
|
2148
|
-
const
|
|
2149
|
-
? linter.verifyAndFix(testCase.code, config, options)
|
|
2150
|
-
: { messages: linter.verify(testCase.code, config, options), output: testCase.code };
|
|
2151
|
-
const messages = result.messages;
|
|
2244
|
+
const messages = linter.verify(testCase.code, config, options);
|
|
2152
2245
|
assertRuleTesterErrors(testCase, messages, rule);
|
|
2153
|
-
|
|
2246
|
+
// Like ESLint's RuleTester, `output` is the result of one autofix pass.
|
|
2247
|
+
assertRuleTesterOutput(testCase, Object.hasOwn(testCase, "output") ? applyMessageFixes(testCase.code, messages) : testCase.code);
|
|
2154
2248
|
});
|
|
2155
2249
|
}
|
|
2156
2250
|
});
|
|
@@ -2297,6 +2391,47 @@ function applyRuleFixes(code, fix) {
|
|
|
2297
2391
|
.reduce((output, item) => output.slice(0, item.range[0]) + item.text + output.slice(item.range[1]), code);
|
|
2298
2392
|
}
|
|
2299
2393
|
|
|
2394
|
+
// Applies one non-overlapping fix per message, like ESLint's SourceCodeFixer.
|
|
2395
|
+
// Fixes that overlap an earlier one are left for a later pass.
|
|
2396
|
+
function applyMessageFixes(code, messages) {
|
|
2397
|
+
const fixes = [];
|
|
2398
|
+
for (const message of messages) {
|
|
2399
|
+
const items = ruleFixItems(message?.fix)
|
|
2400
|
+
.slice()
|
|
2401
|
+
.sort((left, right) => left.range[0] - right.range[0] || left.range[1] - right.range[1]);
|
|
2402
|
+
if (items.length === 0) {
|
|
2403
|
+
continue;
|
|
2404
|
+
}
|
|
2405
|
+
let text = "";
|
|
2406
|
+
let cursor = items[0].range[0];
|
|
2407
|
+
let valid = true;
|
|
2408
|
+
for (const item of items) {
|
|
2409
|
+
if (item.range[0] < cursor) {
|
|
2410
|
+
valid = false;
|
|
2411
|
+
break;
|
|
2412
|
+
}
|
|
2413
|
+
text += code.slice(cursor, item.range[0]) + item.text;
|
|
2414
|
+
cursor = item.range[1];
|
|
2415
|
+
}
|
|
2416
|
+
if (valid) {
|
|
2417
|
+
fixes.push({ range: [items[0].range[0], cursor], text });
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
fixes.sort((left, right) => left.range[0] - right.range[0] || left.range[1] - right.range[1]);
|
|
2421
|
+
let output = "";
|
|
2422
|
+
let position = 0;
|
|
2423
|
+
let lastEnd = -Infinity;
|
|
2424
|
+
for (const fix of fixes) {
|
|
2425
|
+
if (lastEnd >= fix.range[0]) {
|
|
2426
|
+
continue;
|
|
2427
|
+
}
|
|
2428
|
+
output += code.slice(position, fix.range[0]) + fix.text;
|
|
2429
|
+
position = fix.range[1];
|
|
2430
|
+
lastEnd = fix.range[1];
|
|
2431
|
+
}
|
|
2432
|
+
return output + code.slice(position);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2300
2435
|
function ruleFixItems(fix) {
|
|
2301
2436
|
if (!fix) {
|
|
2302
2437
|
return [];
|
|
@@ -2349,10 +2484,29 @@ function createLinterSourceCode(text, parser = null, parserOptions = {}) {
|
|
|
2349
2484
|
});
|
|
2350
2485
|
}
|
|
2351
2486
|
|
|
2352
|
-
function
|
|
2353
|
-
|
|
2487
|
+
function parseSourceCodeWithDefaultParser(text, parserOptions) {
|
|
2488
|
+
let parsed;
|
|
2489
|
+
try {
|
|
2490
|
+
parsed = estreeParser.parseForESLint(text, parserOptions);
|
|
2491
|
+
} catch (error) {
|
|
2492
|
+
if (parserOptions.requireParser) {
|
|
2493
|
+
throw error;
|
|
2494
|
+
}
|
|
2354
2495
|
return null;
|
|
2355
2496
|
}
|
|
2497
|
+
return new SourceCode({
|
|
2498
|
+
text,
|
|
2499
|
+
ast: parsed.ast,
|
|
2500
|
+
parserServices: parsed.services ?? {},
|
|
2501
|
+
scopeManager: parsed.scopeManager ?? null,
|
|
2502
|
+
visitorKeys: parsed.visitorKeys ?? null
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
function parseSourceCode(text, parser, parserOptions = {}) {
|
|
2507
|
+
if (!parser) {
|
|
2508
|
+
return parserOptions.tokensOnly ? null : parseSourceCodeWithDefaultParser(text, parserOptions);
|
|
2509
|
+
}
|
|
2356
2510
|
if (typeof parser.parseForESLint === "function") {
|
|
2357
2511
|
const result = parser.parseForESLint(text, parserOptions) ?? {};
|
|
2358
2512
|
return new SourceCode({
|
|
@@ -3124,21 +3278,210 @@ export function lintFiles(paths, options = {}) {
|
|
|
3124
3278
|
}
|
|
3125
3279
|
|
|
3126
3280
|
const nativeFlatOptions = nativeFlatConfigOptions(options);
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3281
|
+
const report = nativeFlatOptions
|
|
3282
|
+
? runNativeLintReport(lintPaths, nativeFlatOptions)
|
|
3283
|
+
: runNativeConfigRunReports(lintPaths, options);
|
|
3284
|
+
// The flat-config path resolves one config for the whole run; only the
|
|
3285
|
+
// per-file path has already discovered (and cached) nested configs.
|
|
3286
|
+
appendPluginRuleDiagnostics(report, options, { perFileConfig: !nativeFlatOptions });
|
|
3287
|
+
return finalizeLintReport(report, ignoredDiagnostics, options);
|
|
3288
|
+
}
|
|
3131
3289
|
|
|
3290
|
+
function runNativeConfigRunReports(lintPaths, options) {
|
|
3132
3291
|
const configRuns = nativeConfigRunsForFiles(expandNativeConfigRunPaths(lintPaths, options), options);
|
|
3133
3292
|
if (configRuns) {
|
|
3134
|
-
|
|
3293
|
+
return mergeNativeReports(
|
|
3135
3294
|
configRuns.map(({ paths: runPaths, options: runOptions }) => runNativeLintReport(runPaths, runOptions))
|
|
3136
3295
|
);
|
|
3137
|
-
return finalizeLintReport(report, ignoredDiagnostics, options);
|
|
3138
3296
|
}
|
|
3139
3297
|
|
|
3140
|
-
|
|
3141
|
-
|
|
3298
|
+
return runNativeLintReport(lintPaths, options);
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
function pluginFixModeForOptions(options) {
|
|
3302
|
+
const extraArgs = Array.isArray(options.extraArgs) ? options.extraArgs : [];
|
|
3303
|
+
if (options.fix || extraArgs.includes("--fix-dry-run")) {
|
|
3304
|
+
return "dry-run";
|
|
3305
|
+
}
|
|
3306
|
+
return extraArgs.includes("--fix") ? "write" : null;
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
function pluginMessageToDiagnostic(message, filePath) {
|
|
3310
|
+
const toFix = (fix) => ({ range: [fix.range[0], fix.range[1]], text: fix.text });
|
|
3311
|
+
const diagnostic = {
|
|
3312
|
+
filePath,
|
|
3313
|
+
line: message.line,
|
|
3314
|
+
column: message.column,
|
|
3315
|
+
severity: message.severity === 2 ? "error" : "warning",
|
|
3316
|
+
message: message.message,
|
|
3317
|
+
ruleId: message.ruleId,
|
|
3318
|
+
fixes: ruleFixItems(message.fix).map(toFix),
|
|
3319
|
+
suggestions: (message.suggestions ?? []).map((suggestion) => ({
|
|
3320
|
+
desc: suggestion.desc,
|
|
3321
|
+
fix: ruleFixItems(suggestion.fix).map(toFix)
|
|
3322
|
+
}))
|
|
3323
|
+
};
|
|
3324
|
+
if (typeof message.endLine === "number") {
|
|
3325
|
+
diagnostic.endLine = message.endLine;
|
|
3326
|
+
}
|
|
3327
|
+
if (typeof message.endColumn === "number") {
|
|
3328
|
+
diagnostic.endColumn = message.endColumn;
|
|
3329
|
+
}
|
|
3330
|
+
return diagnostic;
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
function sortReportDiagnostics(report) {
|
|
3334
|
+
const order = new Map((report.filePaths ?? []).map((filePath, index) => [filePath, index]));
|
|
3335
|
+
const rank = (diagnostic) => order.get(diagnostic.filePath) ?? Number.MAX_SAFE_INTEGER;
|
|
3336
|
+
const compare = (left, right) => rank(left) - rank(right)
|
|
3337
|
+
|| (left.line ?? 0) - (right.line ?? 0)
|
|
3338
|
+
|| (left.column ?? 0) - (right.column ?? 0);
|
|
3339
|
+
report.diagnostics = [...(report.diagnostics ?? [])].sort(compare);
|
|
3340
|
+
report.suppressedDiagnostics = [...(report.suppressedDiagnostics ?? [])].sort(compare);
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
// Runs the ESLint plugin rules a config mounts and merges their diagnostics
|
|
3344
|
+
// into the native report. Plugin rules never reach the native binary; see
|
|
3345
|
+
// `withoutJsPluginRules`.
|
|
3346
|
+
function appendPluginRuleDiagnostics(report, options, { perFileConfig = true } = {}) {
|
|
3347
|
+
if (options.noConfig && !options.baseConfig && !options.overrideConfig) {
|
|
3348
|
+
return report;
|
|
3349
|
+
}
|
|
3350
|
+
const filePaths = report.filePaths ?? [];
|
|
3351
|
+
if (filePaths.length === 0 || !anyConfigDeclaresPlugins(options)) {
|
|
3352
|
+
return report;
|
|
3353
|
+
}
|
|
3354
|
+
const cwd = options.cwd ?? process.cwd();
|
|
3355
|
+
const singleMatchPath = filePaths.length === 1
|
|
3356
|
+
? options.filePath ?? options.filename ?? options.pluginTextFilePath
|
|
3357
|
+
: undefined;
|
|
3358
|
+
const fixMode = pluginFixModeForOptions(options);
|
|
3359
|
+
let appended = false;
|
|
3360
|
+
|
|
3361
|
+
for (const filePath of filePaths) {
|
|
3362
|
+
const matchPath = singleMatchPath ?? filePath;
|
|
3363
|
+
const ruleMap = jsPluginRuleMapForFile(options, matchPath, perFileConfig);
|
|
3364
|
+
if (ruleMap.size === 0) {
|
|
3365
|
+
continue;
|
|
3366
|
+
}
|
|
3367
|
+
const ruleEntries = pluginRuleEntriesForFile(options, matchPath, ruleMap, perFileConfig);
|
|
3368
|
+
if (ruleEntries.length === 0) {
|
|
3369
|
+
continue;
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
// The native report echoes targets as given, so relative paths resolve
|
|
3373
|
+
// against the lint cwd rather than the process cwd.
|
|
3374
|
+
const absolutePath = normalizeESLintFilePath(filePath, cwd);
|
|
3375
|
+
const outputIndex = (report.outputs ?? []).findIndex((fixed) => fixed.filePath === filePath);
|
|
3376
|
+
let code = outputIndex === -1 ? undefined : report.outputs[outputIndex].output;
|
|
3377
|
+
if (typeof code !== "string") {
|
|
3378
|
+
try {
|
|
3379
|
+
code = readFileSync(absolutePath, "utf8");
|
|
3380
|
+
} catch {
|
|
3381
|
+
continue;
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
const config = pluginConfigSourcesForFile(options, matchPath, perFileConfig).map((source) => source.config);
|
|
3386
|
+
const parserOptions = { ...parserOptionsForConfig(config, matchPath, cwd), filePath: matchPath, requireParser: true };
|
|
3387
|
+
const lintOptions = {
|
|
3388
|
+
cwd,
|
|
3389
|
+
filename: matchPath,
|
|
3390
|
+
config: calculatedConfig({ cwd, noConfig: true, baseConfig: config }, matchPath)
|
|
3391
|
+
};
|
|
3392
|
+
const lintOnce = (text) => {
|
|
3393
|
+
const sourceCode = createLinterSourceCode(text, null, parserOptions);
|
|
3394
|
+
return applyDisableDirectives(runCustomLinterRules(ruleEntries, sourceCode, lintOptions), sourceCode);
|
|
3395
|
+
};
|
|
3396
|
+
|
|
3397
|
+
let filtered = lintOnce(code);
|
|
3398
|
+
let relintedFromReport = false;
|
|
3399
|
+
if (fixMode) {
|
|
3400
|
+
let changed = false;
|
|
3401
|
+
for (let pass = 0; pass < MAX_AUTOFIX_PASSES; pass += 1) {
|
|
3402
|
+
const output = applyMessageFixes(code, filtered.messages);
|
|
3403
|
+
if (output === code) {
|
|
3404
|
+
break;
|
|
3405
|
+
}
|
|
3406
|
+
changed = true;
|
|
3407
|
+
code = output;
|
|
3408
|
+
filtered = lintOnce(code);
|
|
3409
|
+
}
|
|
3410
|
+
if (changed) {
|
|
3411
|
+
// Plugin fixes changed the text, so the native diagnostics for this
|
|
3412
|
+
// file describe stale source. Re-lint the fixed text through the whole
|
|
3413
|
+
// pipeline (native rules, plugin rules, further fixes) and replace this
|
|
3414
|
+
// file's diagnostics with the result.
|
|
3415
|
+
const depth = options.pluginFixDepth ?? 0;
|
|
3416
|
+
if (depth < MAX_AUTOFIX_PASSES) {
|
|
3417
|
+
const relinted = lintText(code, {
|
|
3418
|
+
...options,
|
|
3419
|
+
filePath: matchPath,
|
|
3420
|
+
filename: undefined,
|
|
3421
|
+
config: pluginConfigPathForFile(options, matchPath, perFileConfig) ?? options.config,
|
|
3422
|
+
fix: true,
|
|
3423
|
+
extraArgs: (options.extraArgs ?? []).filter((arg) => arg !== "--fix" && arg !== "--fix-dry-run"),
|
|
3424
|
+
useDefaultConfigTargets: undefined,
|
|
3425
|
+
pluginTextFilePath: undefined,
|
|
3426
|
+
pluginFixDepth: depth + 1
|
|
3427
|
+
});
|
|
3428
|
+
code = relinted.outputs?.find((fixed) => fixed.filePath === matchPath)?.output ?? code;
|
|
3429
|
+
filtered = {
|
|
3430
|
+
messages: relinted.diagnostics ?? [],
|
|
3431
|
+
suppressedMessages: relinted.suppressedDiagnostics ?? []
|
|
3432
|
+
};
|
|
3433
|
+
report.diagnostics = (report.diagnostics ?? []).filter((diagnostic) => diagnostic.filePath !== filePath);
|
|
3434
|
+
report.suppressedDiagnostics = (report.suppressedDiagnostics ?? []).filter((diagnostic) => diagnostic.filePath !== filePath);
|
|
3435
|
+
relintedFromReport = true;
|
|
3436
|
+
}
|
|
3437
|
+
if (fixMode === "write") {
|
|
3438
|
+
writeFileSync(absolutePath, code);
|
|
3439
|
+
} else if (outputIndex === -1) {
|
|
3440
|
+
report.outputs = [...(report.outputs ?? []), { filePath, output: code }];
|
|
3441
|
+
} else {
|
|
3442
|
+
report.outputs[outputIndex] = { ...report.outputs[outputIndex], output: code };
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
if (relintedFromReport) {
|
|
3448
|
+
// Already native-shaped diagnostics from the nested lint; only the
|
|
3449
|
+
// file path needs to match this report.
|
|
3450
|
+
report.diagnostics = [
|
|
3451
|
+
...(report.diagnostics ?? []),
|
|
3452
|
+
...filtered.messages.map((diagnostic) => ({ ...diagnostic, filePath }))
|
|
3453
|
+
];
|
|
3454
|
+
report.suppressedDiagnostics = [
|
|
3455
|
+
...(report.suppressedDiagnostics ?? []),
|
|
3456
|
+
...filtered.suppressedMessages.map((diagnostic) => ({ ...diagnostic, filePath }))
|
|
3457
|
+
];
|
|
3458
|
+
appended = true;
|
|
3459
|
+
continue;
|
|
3460
|
+
}
|
|
3461
|
+
if (filtered.messages.length === 0 && filtered.suppressedMessages.length === 0) {
|
|
3462
|
+
continue;
|
|
3463
|
+
}
|
|
3464
|
+
report.diagnostics = [
|
|
3465
|
+
...(report.diagnostics ?? []),
|
|
3466
|
+
...filtered.messages.map((message) => pluginMessageToDiagnostic(message, filePath))
|
|
3467
|
+
];
|
|
3468
|
+
report.suppressedDiagnostics = [
|
|
3469
|
+
...(report.suppressedDiagnostics ?? []),
|
|
3470
|
+
...filtered.suppressedMessages.map((message) => ({
|
|
3471
|
+
...pluginMessageToDiagnostic(message, filePath),
|
|
3472
|
+
suppression: {
|
|
3473
|
+
kind: message.suppressions?.[0]?.kind ?? "directive",
|
|
3474
|
+
justification: message.suppressions?.[0]?.justification ?? ""
|
|
3475
|
+
}
|
|
3476
|
+
}))
|
|
3477
|
+
];
|
|
3478
|
+
appended = true;
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
if (appended) {
|
|
3482
|
+
sortReportDiagnostics(report);
|
|
3483
|
+
}
|
|
3484
|
+
return report;
|
|
3142
3485
|
}
|
|
3143
3486
|
|
|
3144
3487
|
function nativeFlatConfigOptions(options) {
|
|
@@ -3168,6 +3511,10 @@ function nativeFlatConfigOptions(options) {
|
|
|
3168
3511
|
return undefined;
|
|
3169
3512
|
}
|
|
3170
3513
|
|
|
3514
|
+
const jsPluginRuleIds = jsPluginRuleIdsFromConfigs(config);
|
|
3515
|
+
if (jsPluginRuleIds.size > 0) {
|
|
3516
|
+
config = configWithoutCustomRules(config, jsPluginRuleIds);
|
|
3517
|
+
}
|
|
3171
3518
|
if (configCanUseNativeRulesFastPath(config)) {
|
|
3172
3519
|
return undefined;
|
|
3173
3520
|
}
|
|
@@ -3294,6 +3641,9 @@ function nativeConfigRunsForFiles(paths, options) {
|
|
|
3294
3641
|
if (configured && options.rules) {
|
|
3295
3642
|
rules = selectedRulesWithConfigOptions(rules, options.rules);
|
|
3296
3643
|
}
|
|
3644
|
+
if (configured) {
|
|
3645
|
+
rules = withoutJsPluginRules(rules, jsPluginRuleIdsForOptions(options, matchPath));
|
|
3646
|
+
}
|
|
3297
3647
|
const signature = configured ? stableConfigSignature({ rules, settings }) : "<native-defaults>";
|
|
3298
3648
|
if (!groups.has(signature)) {
|
|
3299
3649
|
groups.set(signature, { paths: [], rules, settings, configured });
|
|
@@ -3466,7 +3816,8 @@ export function lintText(code, options = {}) {
|
|
|
3466
3816
|
cwd: options.cwd,
|
|
3467
3817
|
config: options.config ?? discoveredConfig,
|
|
3468
3818
|
noConfig: options.noConfig,
|
|
3469
|
-
deferDiagnosticConfigFiltering: true
|
|
3819
|
+
deferDiagnosticConfigFiltering: true,
|
|
3820
|
+
pluginTextFilePath: requestedPath
|
|
3470
3821
|
});
|
|
3471
3822
|
report.filePaths = (report.filePaths ?? []).map((filePath) => (filePath === tempFile ? requestedPath : filePath));
|
|
3472
3823
|
report.outputs = (report.outputs ?? []).map((fixed) => ({
|
|
@@ -3508,11 +3859,12 @@ function buildNativeLintArgs(paths, options) {
|
|
|
3508
3859
|
const configForRuleSelection = options.noConfig
|
|
3509
3860
|
? undefined
|
|
3510
3861
|
: options.config ? readConfig(options.config, options.cwd, options.configCache) : undefined;
|
|
3862
|
+
const jsPluginRuleIds = jsPluginRuleIdsFromConfigs(configForRuleSelection, options.baseConfig, options.overrideConfig);
|
|
3511
3863
|
const enabledRules = enabledRuleNamesFromConfigs(
|
|
3512
3864
|
configForRuleSelection,
|
|
3513
3865
|
options.baseConfig,
|
|
3514
3866
|
options.overrideConfig
|
|
3515
|
-
);
|
|
3867
|
+
).filter((rule) => !jsPluginRuleIds.has(rule));
|
|
3516
3868
|
if (enabledRules.length > 0) {
|
|
3517
3869
|
cliArgs.push(`--rules=${enabledRules.join(",")}`);
|
|
3518
3870
|
}
|
|
@@ -3990,9 +4342,11 @@ function withTemporaryConfig(options, callback) {
|
|
|
3990
4342
|
? readConfig(fileConfigPath, options.cwd, options.configCache)
|
|
3991
4343
|
: undefined;
|
|
3992
4344
|
const configs = [options.baseConfig, fileConfig, options.overrideConfig];
|
|
3993
|
-
const
|
|
3994
|
-
|
|
3995
|
-
: runtimeRulesFromConfigs(...configs)
|
|
4345
|
+
const jsPluginRuleIds = jsPluginRuleIdsFromConfigs(...configs);
|
|
4346
|
+
const rules = withoutJsPluginRules(
|
|
4347
|
+
shouldMaterializeFileConfig ? materializedRulesFromConfigs(...configs) : runtimeRulesFromConfigs(...configs),
|
|
4348
|
+
jsPluginRuleIds
|
|
4349
|
+
);
|
|
3996
4350
|
const settings = configs.reduce(
|
|
3997
4351
|
(result, config) => ({
|
|
3998
4352
|
...result,
|
|
@@ -4011,7 +4365,7 @@ function withTemporaryConfig(options, callback) {
|
|
|
4011
4365
|
}
|
|
4012
4366
|
return callback(options);
|
|
4013
4367
|
}
|
|
4014
|
-
const enabledRules = enabledRuleNamesFromConfigs(...configs);
|
|
4368
|
+
const enabledRules = enabledRuleNamesFromConfigs(...configs).filter((rule) => !jsPluginRuleIds.has(rule));
|
|
4015
4369
|
const hasExplicitOffRules = Object.values(rules).some((value) => ruleConfigSeverity(value) === 0);
|
|
4016
4370
|
if (!shouldMaterializeFileConfig && !hasSettings && !hasRuleOptions(rules) && !hasExplicitOffRules && !options.forceMaterializedConfig) {
|
|
4017
4371
|
return callback({
|
|
@@ -4197,7 +4551,7 @@ function applyCompatibilityDisableDirectives(result, textOptions) {
|
|
|
4197
4551
|
return;
|
|
4198
4552
|
}
|
|
4199
4553
|
|
|
4200
|
-
const filtered = applyDisableDirectives(result.messages, createLinterSourceCode(source));
|
|
4554
|
+
const filtered = applyDisableDirectives(result.messages, createLinterSourceCode(source, null, { tokensOnly: true }));
|
|
4201
4555
|
result.messages = filtered.messages;
|
|
4202
4556
|
result.suppressedMessages.push(...filtered.suppressedMessages);
|
|
4203
4557
|
}
|
|
@@ -4931,6 +5285,12 @@ function diagnosticToESLintMessage(diagnostic, ruleSeverities) {
|
|
|
4931
5285
|
column: diagnostic.column,
|
|
4932
5286
|
nodeType: null
|
|
4933
5287
|
};
|
|
5288
|
+
if (typeof diagnostic.endLine === "number") {
|
|
5289
|
+
message.endLine = diagnostic.endLine;
|
|
5290
|
+
}
|
|
5291
|
+
if (typeof diagnostic.endColumn === "number") {
|
|
5292
|
+
message.endColumn = diagnostic.endColumn;
|
|
5293
|
+
}
|
|
4934
5294
|
if (diagnostic.endLine != null) {
|
|
4935
5295
|
message.endLine = diagnostic.endLine;
|
|
4936
5296
|
}
|