eslint 8.0.0-beta.1 → 8.0.1
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 -3
- package/lib/config/rule-validator.js +24 -7
- package/lib/linter/apply-disable-directives.js +80 -11
- package/lib/linter/config-comment-parser.js +1 -1
- package/lib/linter/linter.js +27 -4
- package/lib/rules/class-methods-use-this.js +51 -8
- package/lib/rules/complexity.js +47 -55
- package/lib/rules/lines-between-class-members.js +50 -2
- package/lib/rules/max-classes-per-file.js +31 -7
- package/lib/rules/no-new-func.js +34 -6
- package/lib/rules/no-undef-init.js +9 -63
- package/lib/rules/require-atomic-updates.js +21 -8
- package/lib/rules/semi.js +66 -5
- package/lib/rules/space-before-blocks.js +14 -2
- package/lib/rules/switch-colon-spacing.js +1 -13
- package/lib/rules/utils/ast-utils.js +15 -1
- package/lib/shared/types.js +3 -3
- package/package.json +3 -3
package/README.md
CHANGED
@@ -296,9 +296,9 @@ The following companies, organizations, and individuals support ESLint's ongoing
|
|
296
296
|
<!--sponsorsstart-->
|
297
297
|
<h3>Platinum Sponsors</h3>
|
298
298
|
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/photomatt/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
|
299
|
-
<p><a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://coinbase.com"><img src="https://avatars.githubusercontent.com/u/1885080?v=4" alt="Coinbase" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
|
300
|
-
<p><a href="https://
|
301
|
-
<p><a href="https://troypoint.com"><img src="https://images.opencollective.com/troypoint/080f96f/avatar.png" alt="TROYPOINT" height="32"></a> <a href="https://mobilen.nu"><img src="https://images.opencollective.com/mobilen/e19860d/logo.png" alt="Mobilen" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="null"><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://www.practiceignition.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
|
299
|
+
<p><a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://coinbase.com"><img src="https://avatars.githubusercontent.com/u/1885080?v=4" alt="Coinbase" height="96"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
|
300
|
+
<p><a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a></p><h3>Bronze Sponsors</h3>
|
301
|
+
<p><a href="https://launchdarkly.com"><img src="https://images.opencollective.com/launchdarkly/574bb9e/logo.png" alt="launchdarkly" height="32"></a> <a href="https://troypoint.com"><img src="https://images.opencollective.com/troypoint/080f96f/avatar.png" alt="TROYPOINT" height="32"></a> <a href="https://mobilen.nu"><img src="https://images.opencollective.com/mobilen/e19860d/logo.png" alt="Mobilen" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="null"><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://www.practiceignition.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
|
302
302
|
<!--sponsorsend-->
|
303
303
|
|
304
304
|
## <a name="technology-sponsors"></a>Technology Sponsors
|
@@ -35,16 +35,33 @@ function findRuleDefinition(ruleId, config) {
|
|
35
35
|
pluginName = ruleIdParts.join("/");
|
36
36
|
}
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
}
|
38
|
+
const errorMessageHeader = `Key "rules": Key "${ruleId}"`;
|
39
|
+
let errorMessage = `${errorMessageHeader}: Could not find plugin "${pluginName}".`;
|
41
40
|
|
42
|
-
if
|
43
|
-
|
44
|
-
|
41
|
+
// if the plugin exists then we need to check if the rule exists
|
42
|
+
if (config.plugins && config.plugins[pluginName]) {
|
43
|
+
|
44
|
+
const plugin = config.plugins[pluginName];
|
45
|
+
|
46
|
+
// first check for exact rule match
|
47
|
+
if (plugin.rules && plugin.rules[ruleName]) {
|
48
|
+
return config.plugins[pluginName].rules[ruleName];
|
49
|
+
}
|
45
50
|
|
46
|
-
|
51
|
+
errorMessage = `${errorMessageHeader}: Could not find "${ruleName}" in plugin "${pluginName}".`;
|
52
|
+
|
53
|
+
// otherwise, let's see if we can find the rule name elsewhere
|
54
|
+
for (const [otherPluginName, otherPlugin] of Object.entries(config.plugins)) {
|
55
|
+
if (otherPlugin.rules && otherPlugin.rules[ruleName]) {
|
56
|
+
errorMessage += ` Did you mean "${otherPluginName}/${ruleName}"?`;
|
57
|
+
break;
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
// falls through to throw error
|
62
|
+
}
|
47
63
|
|
64
|
+
throw new TypeError(errorMessage);
|
48
65
|
}
|
49
66
|
|
50
67
|
/**
|
@@ -46,26 +46,95 @@ function groupByParentComment(directives) {
|
|
46
46
|
* @returns {{ description, fix, position }[]} Details for later creation of output Problems.
|
47
47
|
*/
|
48
48
|
function createIndividualDirectivesRemoval(directives, commentToken) {
|
49
|
-
|
49
|
+
|
50
|
+
/*
|
51
|
+
* `commentToken.value` starts right after `//` or `/*`.
|
52
|
+
* All calculated offsets will be relative to this index.
|
53
|
+
*/
|
54
|
+
const commentValueStart = commentToken.range[0] + "//".length;
|
55
|
+
|
56
|
+
// Find where the list of rules starts. `\S+` matches with the directive name (e.g. `eslint-disable-line`)
|
57
|
+
const listStartOffset = /^\s*\S+\s+/u.exec(commentToken.value)[0].length;
|
58
|
+
|
59
|
+
/*
|
60
|
+
* Get the list text without any surrounding whitespace. In order to preserve the original
|
61
|
+
* formatting, we don't want to change that whitespace.
|
62
|
+
*
|
63
|
+
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
64
|
+
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
65
|
+
*/
|
50
66
|
const listText = commentToken.value
|
51
|
-
.slice(
|
52
|
-
.split(/\s-{2,}\s/u)[0] // remove
|
53
|
-
.trimRight();
|
54
|
-
|
67
|
+
.slice(listStartOffset) // remove directive name and all whitespace before the list
|
68
|
+
.split(/\s-{2,}\s/u)[0] // remove `-- comment`, if it exists
|
69
|
+
.trimRight(); // remove all whitespace after the list
|
70
|
+
|
71
|
+
/*
|
72
|
+
* We can assume that `listText` contains multiple elements.
|
73
|
+
* Otherwise, this function wouldn't be called - if there is
|
74
|
+
* only one rule in the list, then the whole comment must be removed.
|
75
|
+
*/
|
55
76
|
|
56
77
|
return directives.map(directive => {
|
57
78
|
const { ruleId } = directive;
|
58
|
-
|
59
|
-
const
|
60
|
-
const
|
61
|
-
const
|
79
|
+
|
80
|
+
const regex = new RegExp(String.raw`(?:^|\s*,\s*)${escapeRegExp(ruleId)}(?:\s*,\s*|$)`, "u");
|
81
|
+
const match = regex.exec(listText);
|
82
|
+
const matchedText = match[0];
|
83
|
+
const matchStartOffset = listStartOffset + match.index;
|
84
|
+
const matchEndOffset = matchStartOffset + matchedText.length;
|
85
|
+
|
86
|
+
const firstIndexOfComma = matchedText.indexOf(",");
|
87
|
+
const lastIndexOfComma = matchedText.lastIndexOf(",");
|
88
|
+
|
89
|
+
let removalStartOffset, removalEndOffset;
|
90
|
+
|
91
|
+
if (firstIndexOfComma !== lastIndexOfComma) {
|
92
|
+
|
93
|
+
/*
|
94
|
+
* Since there are two commas, this must one of the elements in the middle of the list.
|
95
|
+
* Matched range starts where the previous rule name ends, and ends where the next rule name starts.
|
96
|
+
*
|
97
|
+
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
98
|
+
* ^^^^^^^^^^^^^^
|
99
|
+
*
|
100
|
+
* We want to remove only the content between the two commas, and also one of the commas.
|
101
|
+
*
|
102
|
+
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
103
|
+
* ^^^^^^^^^^^
|
104
|
+
*/
|
105
|
+
removalStartOffset = matchStartOffset + firstIndexOfComma;
|
106
|
+
removalEndOffset = matchStartOffset + lastIndexOfComma;
|
107
|
+
|
108
|
+
} else {
|
109
|
+
|
110
|
+
/*
|
111
|
+
* This is either the first element or the last element.
|
112
|
+
*
|
113
|
+
* If this is the first element, matched range starts where the first rule name starts
|
114
|
+
* and ends where the second rule name starts. This is exactly the range we want
|
115
|
+
* to remove so that the second rule name will start where the first one was starting
|
116
|
+
* and thus preserve the original formatting.
|
117
|
+
*
|
118
|
+
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
119
|
+
* ^^^^^^^^^^^
|
120
|
+
*
|
121
|
+
* Similarly, if this is the last element, we've already matched the range we want to
|
122
|
+
* remove. The previous rule name will end where the last one was ending, relative
|
123
|
+
* to the content on the right side.
|
124
|
+
*
|
125
|
+
* // eslint-disable-line rule-one , rule-two , rule-three -- comment
|
126
|
+
* ^^^^^^^^^^^^^
|
127
|
+
*/
|
128
|
+
removalStartOffset = matchStartOffset;
|
129
|
+
removalEndOffset = matchEndOffset;
|
130
|
+
}
|
62
131
|
|
63
132
|
return {
|
64
133
|
description: `'${ruleId}'`,
|
65
134
|
fix: {
|
66
135
|
range: [
|
67
|
-
|
68
|
-
|
136
|
+
commentValueStart + removalStartOffset,
|
137
|
+
commentValueStart + removalEndOffset
|
69
138
|
],
|
70
139
|
text: ""
|
71
140
|
},
|
@@ -15,7 +15,7 @@ const levn = require("levn"),
|
|
15
15
|
Legacy: {
|
16
16
|
ConfigOps
|
17
17
|
}
|
18
|
-
} = require("@eslint/eslintrc/universal");
|
18
|
+
} = require("@eslint/eslintrc/universal");
|
19
19
|
|
20
20
|
const debug = require("debug")("eslint:config-comment-parser");
|
21
21
|
|
package/lib/linter/linter.js
CHANGED
@@ -24,7 +24,7 @@ const
|
|
24
24
|
ConfigValidator,
|
25
25
|
environments: BuiltInEnvironments
|
26
26
|
}
|
27
|
-
} = require("@eslint/eslintrc/universal"),
|
27
|
+
} = require("@eslint/eslintrc/universal"),
|
28
28
|
Traverser = require("../shared/traverser"),
|
29
29
|
{ SourceCode } = require("../source-code"),
|
30
30
|
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
|
@@ -955,13 +955,31 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
|
|
955
955
|
|
956
956
|
const ruleListeners = createRuleListeners(rule, ruleContext);
|
957
957
|
|
958
|
+
/**
|
959
|
+
* Include `ruleId` in error logs
|
960
|
+
* @param {Function} ruleListener A rule method that listens for a node.
|
961
|
+
* @returns {Function} ruleListener wrapped in error handler
|
962
|
+
*/
|
963
|
+
function addRuleErrorHandler(ruleListener) {
|
964
|
+
return function ruleErrorHandler(...listenerArgs) {
|
965
|
+
try {
|
966
|
+
return ruleListener(...listenerArgs);
|
967
|
+
} catch (e) {
|
968
|
+
e.ruleId = ruleId;
|
969
|
+
throw e;
|
970
|
+
}
|
971
|
+
};
|
972
|
+
}
|
973
|
+
|
958
974
|
// add all the selectors from the rule as listeners
|
959
975
|
Object.keys(ruleListeners).forEach(selector => {
|
976
|
+
const ruleListener = timing.enabled
|
977
|
+
? timing.time(ruleId, ruleListeners[selector])
|
978
|
+
: ruleListeners[selector];
|
979
|
+
|
960
980
|
emitter.on(
|
961
981
|
selector,
|
962
|
-
|
963
|
-
? timing.time(ruleId, ruleListeners[selector])
|
964
|
-
: ruleListeners[selector]
|
982
|
+
addRuleErrorHandler(ruleListener)
|
965
983
|
);
|
966
984
|
});
|
967
985
|
});
|
@@ -1223,6 +1241,11 @@ class Linter {
|
|
1223
1241
|
debug("Parser Options:", parserOptions);
|
1224
1242
|
debug("Parser Path:", parserName);
|
1225
1243
|
debug("Settings:", settings);
|
1244
|
+
|
1245
|
+
if (err.ruleId) {
|
1246
|
+
err.message += `\nRule: "${err.ruleId}"`;
|
1247
|
+
}
|
1248
|
+
|
1226
1249
|
throw err;
|
1227
1250
|
}
|
1228
1251
|
|
@@ -33,6 +33,10 @@ module.exports = {
|
|
33
33
|
items: {
|
34
34
|
type: "string"
|
35
35
|
}
|
36
|
+
},
|
37
|
+
enforceForClassFields: {
|
38
|
+
type: "boolean",
|
39
|
+
default: true
|
36
40
|
}
|
37
41
|
},
|
38
42
|
additionalProperties: false
|
@@ -44,10 +48,27 @@ module.exports = {
|
|
44
48
|
},
|
45
49
|
create(context) {
|
46
50
|
const config = Object.assign({}, context.options[0]);
|
51
|
+
const enforceForClassFields = config.enforceForClassFields !== false;
|
47
52
|
const exceptMethods = new Set(config.exceptMethods || []);
|
48
53
|
|
49
54
|
const stack = [];
|
50
55
|
|
56
|
+
/**
|
57
|
+
* Push `this` used flag initialized with `false` onto the stack.
|
58
|
+
* @returns {void}
|
59
|
+
*/
|
60
|
+
function pushContext() {
|
61
|
+
stack.push(false);
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Pop `this` used flag from the stack.
|
66
|
+
* @returns {boolean | undefined} `this` used flag
|
67
|
+
*/
|
68
|
+
function popContext() {
|
69
|
+
return stack.pop();
|
70
|
+
}
|
71
|
+
|
51
72
|
/**
|
52
73
|
* Initializes the current context to false and pushes it onto the stack.
|
53
74
|
* These booleans represent whether 'this' has been used in the context.
|
@@ -55,7 +76,7 @@ module.exports = {
|
|
55
76
|
* @private
|
56
77
|
*/
|
57
78
|
function enterFunction() {
|
58
|
-
|
79
|
+
pushContext();
|
59
80
|
}
|
60
81
|
|
61
82
|
/**
|
@@ -69,7 +90,7 @@ module.exports = {
|
|
69
90
|
case "MethodDefinition":
|
70
91
|
return !node.static && node.kind !== "constructor";
|
71
92
|
case "PropertyDefinition":
|
72
|
-
return !node.static;
|
93
|
+
return !node.static && enforceForClassFields;
|
73
94
|
default:
|
74
95
|
return false;
|
75
96
|
}
|
@@ -82,8 +103,19 @@ module.exports = {
|
|
82
103
|
* @private
|
83
104
|
*/
|
84
105
|
function isIncludedInstanceMethod(node) {
|
85
|
-
|
86
|
-
(node.computed
|
106
|
+
if (isInstanceMethod(node)) {
|
107
|
+
if (node.computed) {
|
108
|
+
return true;
|
109
|
+
}
|
110
|
+
|
111
|
+
const hashIfNeeded = node.key.type === "PrivateIdentifier" ? "#" : "";
|
112
|
+
const name = node.key.type === "Literal"
|
113
|
+
? astUtils.getStaticStringValue(node.key)
|
114
|
+
: (node.key.name || "");
|
115
|
+
|
116
|
+
return !exceptMethods.has(hashIfNeeded + name);
|
117
|
+
}
|
118
|
+
return false;
|
87
119
|
}
|
88
120
|
|
89
121
|
/**
|
@@ -95,7 +127,7 @@ module.exports = {
|
|
95
127
|
* @private
|
96
128
|
*/
|
97
129
|
function exitFunction(node) {
|
98
|
-
const methodUsesThis =
|
130
|
+
const methodUsesThis = popContext();
|
99
131
|
|
100
132
|
if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
|
101
133
|
context.report({
|
@@ -125,10 +157,21 @@ module.exports = {
|
|
125
157
|
"FunctionDeclaration:exit": exitFunction,
|
126
158
|
FunctionExpression: enterFunction,
|
127
159
|
"FunctionExpression:exit": exitFunction,
|
128
|
-
|
129
|
-
|
160
|
+
|
161
|
+
/*
|
162
|
+
* Class field value are implicit functions.
|
163
|
+
*/
|
164
|
+
"PropertyDefinition:exit": popContext,
|
165
|
+
"PropertyDefinition > *.key:exit": pushContext,
|
166
|
+
|
130
167
|
ThisExpression: markThisUsed,
|
131
|
-
Super: markThisUsed
|
168
|
+
Super: markThisUsed,
|
169
|
+
...(
|
170
|
+
enforceForClassFields && {
|
171
|
+
"PropertyDefinition > ArrowFunctionExpression.value": enterFunction,
|
172
|
+
"PropertyDefinition > ArrowFunctionExpression.value:exit": exitFunction
|
173
|
+
}
|
174
|
+
)
|
132
175
|
};
|
133
176
|
}
|
134
177
|
};
|
package/lib/rules/complexity.js
CHANGED
@@ -74,60 +74,16 @@ module.exports = {
|
|
74
74
|
// Helpers
|
75
75
|
//--------------------------------------------------------------------------
|
76
76
|
|
77
|
-
// Using a stack to store complexity
|
78
|
-
const
|
77
|
+
// Using a stack to store complexity per code path
|
78
|
+
const complexities = [];
|
79
79
|
|
80
80
|
/**
|
81
|
-
*
|
82
|
-
* @returns {void}
|
83
|
-
* @private
|
84
|
-
*/
|
85
|
-
function startFunction() {
|
86
|
-
fns.push(1);
|
87
|
-
}
|
88
|
-
|
89
|
-
/**
|
90
|
-
* Evaluate the node at the end of function
|
91
|
-
* @param {ASTNode} node node to evaluate
|
92
|
-
* @returns {void}
|
93
|
-
* @private
|
94
|
-
*/
|
95
|
-
function endFunction(node) {
|
96
|
-
const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
|
97
|
-
const complexity = fns.pop();
|
98
|
-
|
99
|
-
if (complexity > THRESHOLD) {
|
100
|
-
context.report({
|
101
|
-
node,
|
102
|
-
messageId: "complex",
|
103
|
-
data: { name, complexity, max: THRESHOLD }
|
104
|
-
});
|
105
|
-
}
|
106
|
-
}
|
107
|
-
|
108
|
-
/**
|
109
|
-
* Increase the complexity of the function in context
|
81
|
+
* Increase the complexity of the code path in context
|
110
82
|
* @returns {void}
|
111
83
|
* @private
|
112
84
|
*/
|
113
85
|
function increaseComplexity() {
|
114
|
-
|
115
|
-
fns[fns.length - 1]++;
|
116
|
-
}
|
117
|
-
}
|
118
|
-
|
119
|
-
/**
|
120
|
-
* Increase the switch complexity in context
|
121
|
-
* @param {ASTNode} node node to evaluate
|
122
|
-
* @returns {void}
|
123
|
-
* @private
|
124
|
-
*/
|
125
|
-
function increaseSwitchComplexity(node) {
|
126
|
-
|
127
|
-
// Avoiding `default`
|
128
|
-
if (node.test) {
|
129
|
-
increaseComplexity();
|
130
|
-
}
|
86
|
+
complexities[complexities.length - 1]++;
|
131
87
|
}
|
132
88
|
|
133
89
|
//--------------------------------------------------------------------------
|
@@ -135,13 +91,14 @@ module.exports = {
|
|
135
91
|
//--------------------------------------------------------------------------
|
136
92
|
|
137
93
|
return {
|
138
|
-
FunctionDeclaration: startFunction,
|
139
|
-
FunctionExpression: startFunction,
|
140
|
-
ArrowFunctionExpression: startFunction,
|
141
|
-
"FunctionDeclaration:exit": endFunction,
|
142
|
-
"FunctionExpression:exit": endFunction,
|
143
|
-
"ArrowFunctionExpression:exit": endFunction,
|
144
94
|
|
95
|
+
onCodePathStart() {
|
96
|
+
|
97
|
+
// The initial complexity is 1, representing one execution path in the CodePath
|
98
|
+
complexities.push(1);
|
99
|
+
},
|
100
|
+
|
101
|
+
// Each branching in the code adds 1 to the complexity
|
145
102
|
CatchClause: increaseComplexity,
|
146
103
|
ConditionalExpression: increaseComplexity,
|
147
104
|
LogicalExpression: increaseComplexity,
|
@@ -149,14 +106,49 @@ module.exports = {
|
|
149
106
|
ForInStatement: increaseComplexity,
|
150
107
|
ForOfStatement: increaseComplexity,
|
151
108
|
IfStatement: increaseComplexity,
|
152
|
-
SwitchCase: increaseSwitchComplexity,
|
153
109
|
WhileStatement: increaseComplexity,
|
154
110
|
DoWhileStatement: increaseComplexity,
|
155
111
|
|
112
|
+
// Avoid `default`
|
113
|
+
"SwitchCase[test]": increaseComplexity,
|
114
|
+
|
115
|
+
// Logical assignment operators have short-circuiting behavior
|
156
116
|
AssignmentExpression(node) {
|
157
117
|
if (astUtils.isLogicalAssignmentOperator(node.operator)) {
|
158
118
|
increaseComplexity();
|
159
119
|
}
|
120
|
+
},
|
121
|
+
|
122
|
+
onCodePathEnd(codePath, node) {
|
123
|
+
const complexity = complexities.pop();
|
124
|
+
|
125
|
+
/*
|
126
|
+
* This rule only evaluates complexity of functions, so "program" is excluded.
|
127
|
+
* Class field initializers are implicit functions. Therefore, they shouldn't contribute
|
128
|
+
* to the enclosing function's complexity, but their own complexity should be evaluated.
|
129
|
+
*/
|
130
|
+
if (
|
131
|
+
codePath.origin !== "function" &&
|
132
|
+
codePath.origin !== "class-field-initializer"
|
133
|
+
) {
|
134
|
+
return;
|
135
|
+
}
|
136
|
+
|
137
|
+
if (complexity > THRESHOLD) {
|
138
|
+
const name = codePath.origin === "class-field-initializer"
|
139
|
+
? "class field initializer"
|
140
|
+
: astUtils.getFunctionNameWithKind(node);
|
141
|
+
|
142
|
+
context.report({
|
143
|
+
node,
|
144
|
+
messageId: "complex",
|
145
|
+
data: {
|
146
|
+
name: upperCaseFirst(name),
|
147
|
+
complexity,
|
148
|
+
max: THRESHOLD
|
149
|
+
}
|
150
|
+
});
|
151
|
+
}
|
160
152
|
}
|
161
153
|
};
|
162
154
|
|
@@ -4,6 +4,10 @@
|
|
4
4
|
*/
|
5
5
|
"use strict";
|
6
6
|
|
7
|
+
//------------------------------------------------------------------------------
|
8
|
+
// Requirements
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
|
7
11
|
const astUtils = require("./utils/ast-utils");
|
8
12
|
|
9
13
|
//------------------------------------------------------------------------------
|
@@ -52,6 +56,51 @@ module.exports = {
|
|
52
56
|
|
53
57
|
const sourceCode = context.getSourceCode();
|
54
58
|
|
59
|
+
/**
|
60
|
+
* Gets a pair of tokens that should be used to check lines between two class member nodes.
|
61
|
+
*
|
62
|
+
* In most cases, this returns the very last token of the current node and
|
63
|
+
* the very first token of the next node.
|
64
|
+
* For example:
|
65
|
+
*
|
66
|
+
* class C {
|
67
|
+
* x = 1; // curLast: `;` nextFirst: `in`
|
68
|
+
* in = 2
|
69
|
+
* }
|
70
|
+
*
|
71
|
+
* There is only one exception. If the given node ends with a semicolon, and it looks like
|
72
|
+
* a semicolon-less style's semicolon - one that is not on the same line as the preceding
|
73
|
+
* token, but is on the line where the next class member starts - this returns the preceding
|
74
|
+
* token and the semicolon as boundary tokens.
|
75
|
+
* For example:
|
76
|
+
*
|
77
|
+
* class C {
|
78
|
+
* x = 1 // curLast: `1` nextFirst: `;`
|
79
|
+
* ;in = 2
|
80
|
+
* }
|
81
|
+
* When determining the desired layout of the code, we should treat this semicolon as
|
82
|
+
* a part of the next class member node instead of the one it technically belongs to.
|
83
|
+
* @param {ASTNode} curNode Current class member node.
|
84
|
+
* @param {ASTNode} nextNode Next class member node.
|
85
|
+
* @returns {Token} The actual last token of `node`.
|
86
|
+
* @private
|
87
|
+
*/
|
88
|
+
function getBoundaryTokens(curNode, nextNode) {
|
89
|
+
const lastToken = sourceCode.getLastToken(curNode);
|
90
|
+
const prevToken = sourceCode.getTokenBefore(lastToken);
|
91
|
+
const nextToken = sourceCode.getFirstToken(nextNode); // skip possible lone `;` between nodes
|
92
|
+
|
93
|
+
const isSemicolonLessStyle = (
|
94
|
+
astUtils.isSemicolonToken(lastToken) &&
|
95
|
+
!astUtils.isTokenOnSameLine(prevToken, lastToken) &&
|
96
|
+
astUtils.isTokenOnSameLine(lastToken, nextToken)
|
97
|
+
);
|
98
|
+
|
99
|
+
return isSemicolonLessStyle
|
100
|
+
? { curLast: prevToken, nextFirst: lastToken }
|
101
|
+
: { curLast: lastToken, nextFirst: nextToken };
|
102
|
+
}
|
103
|
+
|
55
104
|
/**
|
56
105
|
* Return the last token among the consecutive tokens that have no exceed max line difference in between, before the first token in the next member.
|
57
106
|
* @param {Token} prevLastToken The last token in the previous member node.
|
@@ -100,8 +149,7 @@ module.exports = {
|
|
100
149
|
|
101
150
|
for (let i = 0; i < body.length - 1; i++) {
|
102
151
|
const curFirst = sourceCode.getFirstToken(body[i]);
|
103
|
-
const curLast =
|
104
|
-
const nextFirst = sourceCode.getFirstToken(body[i + 1]);
|
152
|
+
const { curLast, nextFirst } = getBoundaryTokens(body[i], body[i + 1]);
|
105
153
|
const isMulti = !astUtils.isTokenOnSameLine(curFirst, curLast);
|
106
154
|
const skip = !isMulti && options[1].exceptAfterSingleLine;
|
107
155
|
const beforePadding = findLastConsecutiveTokenAfter(curLast, nextFirst, 1);
|
@@ -25,8 +25,25 @@ module.exports = {
|
|
25
25
|
|
26
26
|
schema: [
|
27
27
|
{
|
28
|
-
|
29
|
-
|
28
|
+
oneOf: [
|
29
|
+
{
|
30
|
+
type: "integer",
|
31
|
+
minimum: 1
|
32
|
+
},
|
33
|
+
{
|
34
|
+
type: "object",
|
35
|
+
properties: {
|
36
|
+
ignoreExpressions: {
|
37
|
+
type: "boolean"
|
38
|
+
},
|
39
|
+
max: {
|
40
|
+
type: "integer",
|
41
|
+
minimum: 1
|
42
|
+
}
|
43
|
+
},
|
44
|
+
additionalProperties: false
|
45
|
+
}
|
46
|
+
]
|
30
47
|
}
|
31
48
|
],
|
32
49
|
|
@@ -35,8 +52,10 @@ module.exports = {
|
|
35
52
|
}
|
36
53
|
},
|
37
54
|
create(context) {
|
38
|
-
|
39
|
-
const
|
55
|
+
const [option = {}] = context.options;
|
56
|
+
const [ignoreExpressions, max] = typeof option === "number"
|
57
|
+
? [false, option || 1]
|
58
|
+
: [option.ignoreExpressions, option.max || 1];
|
40
59
|
|
41
60
|
let classCount = 0;
|
42
61
|
|
@@ -45,19 +64,24 @@ module.exports = {
|
|
45
64
|
classCount = 0;
|
46
65
|
},
|
47
66
|
"Program:exit"(node) {
|
48
|
-
if (classCount >
|
67
|
+
if (classCount > max) {
|
49
68
|
context.report({
|
50
69
|
node,
|
51
70
|
messageId: "maximumExceeded",
|
52
71
|
data: {
|
53
72
|
classCount,
|
54
|
-
max
|
73
|
+
max
|
55
74
|
}
|
56
75
|
});
|
57
76
|
}
|
58
77
|
},
|
59
|
-
"ClassDeclaration
|
78
|
+
"ClassDeclaration"() {
|
60
79
|
classCount++;
|
80
|
+
},
|
81
|
+
"ClassExpression"() {
|
82
|
+
if (!ignoreExpressions) {
|
83
|
+
classCount++;
|
84
|
+
}
|
61
85
|
}
|
62
86
|
};
|
63
87
|
}
|
package/lib/rules/no-new-func.js
CHANGED
@@ -5,6 +5,18 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const astUtils = require("./utils/ast-utils");
|
13
|
+
|
14
|
+
//------------------------------------------------------------------------------
|
15
|
+
// Helpers
|
16
|
+
//------------------------------------------------------------------------------
|
17
|
+
|
18
|
+
const callMethods = new Set(["apply", "bind", "call"]);
|
19
|
+
|
8
20
|
//------------------------------------------------------------------------------
|
9
21
|
// Rule Definition
|
10
22
|
//------------------------------------------------------------------------------
|
@@ -37,14 +49,30 @@ module.exports = {
|
|
37
49
|
variable.references.forEach(ref => {
|
38
50
|
const node = ref.identifier;
|
39
51
|
const { parent } = node;
|
52
|
+
let evalNode;
|
53
|
+
|
54
|
+
if (parent) {
|
55
|
+
if (node === parent.callee && (
|
56
|
+
parent.type === "NewExpression" ||
|
57
|
+
parent.type === "CallExpression"
|
58
|
+
)) {
|
59
|
+
evalNode = parent;
|
60
|
+
} else if (
|
61
|
+
parent.type === "MemberExpression" &&
|
62
|
+
node === parent.object &&
|
63
|
+
callMethods.has(astUtils.getStaticPropertyName(parent))
|
64
|
+
) {
|
65
|
+
const maybeCallee = parent.parent.type === "ChainExpression" ? parent.parent : parent;
|
66
|
+
|
67
|
+
if (maybeCallee.parent.type === "CallExpression" && maybeCallee.parent.callee === maybeCallee) {
|
68
|
+
evalNode = maybeCallee.parent;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
40
72
|
|
41
|
-
if (
|
42
|
-
parent &&
|
43
|
-
(parent.type === "NewExpression" || parent.type === "CallExpression") &&
|
44
|
-
node === parent.callee
|
45
|
-
) {
|
73
|
+
if (evalNode) {
|
46
74
|
context.report({
|
47
|
-
node:
|
75
|
+
node: evalNode,
|
48
76
|
messageId: "noFunctionConstructor"
|
49
77
|
});
|
50
78
|
}
|
@@ -33,91 +33,37 @@ module.exports = {
|
|
33
33
|
|
34
34
|
const sourceCode = context.getSourceCode();
|
35
35
|
|
36
|
-
/**
|
37
|
-
* Get the node of init target.
|
38
|
-
* @param {ASTNode} node The node to get.
|
39
|
-
* @throws {Error} (Unreachable.)
|
40
|
-
* @returns {ASTNode} The node of init target.
|
41
|
-
*/
|
42
|
-
function getIdNode(node) {
|
43
|
-
switch (node.type) {
|
44
|
-
case "VariableDeclarator":
|
45
|
-
return node.id;
|
46
|
-
case "PropertyDefinition":
|
47
|
-
return node.key;
|
48
|
-
default:
|
49
|
-
throw new Error("unreachable");
|
50
|
-
}
|
51
|
-
}
|
52
|
-
|
53
|
-
/**
|
54
|
-
* Get the node of init value.
|
55
|
-
* @param {ASTNode} node The node to get.
|
56
|
-
* @throws {Error} (Unreachable.)
|
57
|
-
* @returns {ASTNode} The node of init value.
|
58
|
-
*/
|
59
|
-
function getInitNode(node) {
|
60
|
-
switch (node.type) {
|
61
|
-
case "VariableDeclarator":
|
62
|
-
return node.init;
|
63
|
-
case "PropertyDefinition":
|
64
|
-
return node.value;
|
65
|
-
default:
|
66
|
-
throw new Error("unreachable");
|
67
|
-
}
|
68
|
-
}
|
69
|
-
|
70
|
-
/**
|
71
|
-
* Get the parent kind of the node.
|
72
|
-
* @param {ASTNode} node The node to get.
|
73
|
-
* @throws {Error} (Unreachable.)
|
74
|
-
* @returns {string} The parent kind.
|
75
|
-
*/
|
76
|
-
function getParentKind(node) {
|
77
|
-
switch (node.type) {
|
78
|
-
case "VariableDeclarator":
|
79
|
-
return node.parent.kind;
|
80
|
-
case "PropertyDefinition":
|
81
|
-
return "field";
|
82
|
-
default:
|
83
|
-
throw new Error("unreachable");
|
84
|
-
}
|
85
|
-
}
|
86
|
-
|
87
36
|
return {
|
88
37
|
|
89
|
-
|
90
|
-
const
|
91
|
-
|
92
|
-
initNode = getInitNode(node),
|
93
|
-
initIsUndefined = initNode && initNode.type === "Identifier" && initNode.name === "undefined",
|
94
|
-
parentKind = getParentKind(node),
|
38
|
+
VariableDeclarator(node) {
|
39
|
+
const name = sourceCode.getText(node.id),
|
40
|
+
init = node.init && node.init.name,
|
95
41
|
scope = context.getScope(),
|
96
42
|
undefinedVar = astUtils.getVariableByName(scope, "undefined"),
|
97
43
|
shadowed = undefinedVar && undefinedVar.defs.length > 0,
|
98
|
-
lastToken = sourceCode.getLastToken(node
|
44
|
+
lastToken = sourceCode.getLastToken(node);
|
99
45
|
|
100
|
-
if (
|
46
|
+
if (init === "undefined" && node.parent.kind !== "const" && !shadowed) {
|
101
47
|
context.report({
|
102
48
|
node,
|
103
49
|
messageId: "unnecessaryUndefinedInit",
|
104
50
|
data: { name },
|
105
51
|
fix(fixer) {
|
106
|
-
if (
|
52
|
+
if (node.parent.kind === "var") {
|
107
53
|
return null;
|
108
54
|
}
|
109
55
|
|
110
|
-
if (
|
56
|
+
if (node.id.type === "ArrayPattern" || node.id.type === "ObjectPattern") {
|
111
57
|
|
112
58
|
// Don't fix destructuring assignment to `undefined`.
|
113
59
|
return null;
|
114
60
|
}
|
115
61
|
|
116
|
-
if (sourceCode.commentsExistBetween(
|
62
|
+
if (sourceCode.commentsExistBetween(node.id, lastToken)) {
|
117
63
|
return null;
|
118
64
|
}
|
119
65
|
|
120
|
-
return fixer.removeRange([
|
66
|
+
return fixer.removeRange([node.id.range[1], node.range[1]]);
|
121
67
|
}
|
122
68
|
});
|
123
69
|
}
|
@@ -179,7 +179,8 @@ module.exports = {
|
|
179
179
|
schema: [],
|
180
180
|
|
181
181
|
messages: {
|
182
|
-
nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`."
|
182
|
+
nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`.",
|
183
|
+
nonAtomicObjectUpdate: "Possible race condition: `{{value}}` might be assigned based on an outdated state of `{{object}}`."
|
183
184
|
}
|
184
185
|
},
|
185
186
|
|
@@ -275,13 +276,25 @@ module.exports = {
|
|
275
276
|
const variable = reference.resolved;
|
276
277
|
|
277
278
|
if (segmentInfo.isOutdated(codePath.currentSegments, variable)) {
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
279
|
+
if (node.parent.left === reference.identifier) {
|
280
|
+
context.report({
|
281
|
+
node: node.parent,
|
282
|
+
messageId: "nonAtomicUpdate",
|
283
|
+
data: {
|
284
|
+
value: variable.name
|
285
|
+
}
|
286
|
+
});
|
287
|
+
} else {
|
288
|
+
context.report({
|
289
|
+
node: node.parent,
|
290
|
+
messageId: "nonAtomicObjectUpdate",
|
291
|
+
data: {
|
292
|
+
value: sourceCode.getText(node.parent.left),
|
293
|
+
object: variable.name
|
294
|
+
}
|
295
|
+
});
|
296
|
+
}
|
297
|
+
|
285
298
|
}
|
286
299
|
}
|
287
300
|
}
|
package/lib/rules/semi.js
CHANGED
@@ -77,6 +77,8 @@ module.exports = {
|
|
77
77
|
create(context) {
|
78
78
|
|
79
79
|
const OPT_OUT_PATTERN = /^[-[(/+`]/u; // One of [(/+-`
|
80
|
+
const unsafeClassFieldNames = new Set(["get", "set", "static"]);
|
81
|
+
const unsafeClassFieldFollowers = new Set(["*", "in", "instanceof"]);
|
80
82
|
const options = context.options[1];
|
81
83
|
const never = context.options[0] === "never";
|
82
84
|
const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock);
|
@@ -165,6 +167,55 @@ module.exports = {
|
|
165
167
|
);
|
166
168
|
}
|
167
169
|
|
170
|
+
/**
|
171
|
+
* Checks if a given PropertyDefinition node followed by a semicolon
|
172
|
+
* can safely remove that semicolon. It is not to safe to remove if
|
173
|
+
* the class field name is "get", "set", or "static", or if
|
174
|
+
* followed by a generator method.
|
175
|
+
* @param {ASTNode} node The node to check.
|
176
|
+
* @returns {boolean} `true` if the node cannot have the semicolon
|
177
|
+
* removed.
|
178
|
+
*/
|
179
|
+
function maybeClassFieldAsiHazard(node) {
|
180
|
+
|
181
|
+
if (node.type !== "PropertyDefinition") {
|
182
|
+
return false;
|
183
|
+
}
|
184
|
+
|
185
|
+
/*
|
186
|
+
* Computed property names and non-identifiers are always safe
|
187
|
+
* as they can be distinguished from keywords easily.
|
188
|
+
*/
|
189
|
+
const needsNameCheck = !node.computed && node.key.type === "Identifier";
|
190
|
+
|
191
|
+
/*
|
192
|
+
* Certain names are problematic unless they also have a
|
193
|
+
* a way to distinguish between keywords and property
|
194
|
+
* names.
|
195
|
+
*/
|
196
|
+
if (needsNameCheck && unsafeClassFieldNames.has(node.key.name)) {
|
197
|
+
|
198
|
+
/*
|
199
|
+
* Special case: If the field name is `static`,
|
200
|
+
* it is only valid if the field is marked as static,
|
201
|
+
* so "static static" is okay but "static" is not.
|
202
|
+
*/
|
203
|
+
const isStaticStatic = node.static && node.key.name === "static";
|
204
|
+
|
205
|
+
/*
|
206
|
+
* For other unsafe names, we only care if there is no
|
207
|
+
* initializer. No initializer = hazard.
|
208
|
+
*/
|
209
|
+
if (!isStaticStatic && !node.value) {
|
210
|
+
return true;
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
const followingToken = sourceCode.getTokenAfter(node);
|
215
|
+
|
216
|
+
return unsafeClassFieldFollowers.has(followingToken.value);
|
217
|
+
}
|
218
|
+
|
168
219
|
/**
|
169
220
|
* Check whether a given node is on the same line with the next token.
|
170
221
|
* @param {Node} node A statement node to check.
|
@@ -203,9 +254,6 @@ module.exports = {
|
|
203
254
|
if (isEndOfArrowBlock(sourceCode.getLastToken(node, 1))) {
|
204
255
|
return false;
|
205
256
|
}
|
206
|
-
if (t === "PropertyDefinition") {
|
207
|
-
return Boolean(t.value);
|
208
|
-
}
|
209
257
|
|
210
258
|
return true;
|
211
259
|
}
|
@@ -235,10 +283,19 @@ module.exports = {
|
|
235
283
|
if (isRedundantSemi(sourceCode.getLastToken(node))) {
|
236
284
|
return true; // `;;` or `;}`
|
237
285
|
}
|
286
|
+
if (maybeClassFieldAsiHazard(node)) {
|
287
|
+
return false;
|
288
|
+
}
|
238
289
|
if (isOnSameLineWithNextToken(node)) {
|
239
290
|
return false; // One liner.
|
240
291
|
}
|
241
|
-
|
292
|
+
|
293
|
+
// continuation characters should not apply to class fields
|
294
|
+
if (
|
295
|
+
node.type !== "PropertyDefinition" &&
|
296
|
+
beforeStatementContinuationChars === "never" &&
|
297
|
+
!maybeAsiHazardAfter(node)
|
298
|
+
) {
|
242
299
|
return true; // ASI works. This statement doesn't connect to the next.
|
243
300
|
}
|
244
301
|
if (!maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) {
|
@@ -278,7 +335,11 @@ module.exports = {
|
|
278
335
|
if (never) {
|
279
336
|
if (isSemi && canRemoveSemicolon(node)) {
|
280
337
|
report(node, true);
|
281
|
-
} else if (
|
338
|
+
} else if (
|
339
|
+
!isSemi && beforeStatementContinuationChars === "always" &&
|
340
|
+
node.type !== "PropertyDefinition" &&
|
341
|
+
maybeAsiHazardBefore(sourceCode.getTokenAfter(node))
|
342
|
+
) {
|
282
343
|
report(node);
|
283
344
|
}
|
284
345
|
} else {
|
@@ -107,13 +107,25 @@ module.exports = {
|
|
107
107
|
* Checks whether the spacing before the given block is already controlled by another rule:
|
108
108
|
* - `arrow-spacing` checks spaces after `=>`.
|
109
109
|
* - `keyword-spacing` checks spaces after keywords in certain contexts.
|
110
|
+
* - `switch-colon-spacing` checks spaces after `:` of switch cases.
|
110
111
|
* @param {Token} precedingToken first token before the block.
|
111
112
|
* @param {ASTNode|Token} node `BlockStatement` node or `{` token of a `SwitchStatement` node.
|
112
113
|
* @returns {boolean} `true` if requiring or disallowing spaces before the given block could produce conflicts with other rules.
|
113
114
|
*/
|
114
115
|
function isConflicted(precedingToken, node) {
|
115
|
-
return
|
116
|
-
astUtils.
|
116
|
+
return (
|
117
|
+
astUtils.isArrowToken(precedingToken) ||
|
118
|
+
(
|
119
|
+
astUtils.isKeywordToken(precedingToken) &&
|
120
|
+
!isFunctionBody(node)
|
121
|
+
) ||
|
122
|
+
(
|
123
|
+
astUtils.isColonToken(precedingToken) &&
|
124
|
+
node.parent &&
|
125
|
+
node.parent.type === "SwitchCase" &&
|
126
|
+
precedingToken === astUtils.getSwitchCaseColonToken(node.parent, sourceCode)
|
127
|
+
)
|
128
|
+
);
|
117
129
|
}
|
118
130
|
|
119
131
|
/**
|
@@ -50,18 +50,6 @@ module.exports = {
|
|
50
50
|
const beforeSpacing = options.before === true; // false by default
|
51
51
|
const afterSpacing = options.after !== false; // true by default
|
52
52
|
|
53
|
-
/**
|
54
|
-
* Get the colon token of the given SwitchCase node.
|
55
|
-
* @param {ASTNode} node The SwitchCase node to get.
|
56
|
-
* @returns {Token} The colon token of the node.
|
57
|
-
*/
|
58
|
-
function getColonToken(node) {
|
59
|
-
if (node.test) {
|
60
|
-
return sourceCode.getTokenAfter(node.test, astUtils.isColonToken);
|
61
|
-
}
|
62
|
-
return sourceCode.getFirstToken(node, 1);
|
63
|
-
}
|
64
|
-
|
65
53
|
/**
|
66
54
|
* Check whether the spacing between the given 2 tokens is valid or not.
|
67
55
|
* @param {Token} left The left token to check.
|
@@ -114,7 +102,7 @@ module.exports = {
|
|
114
102
|
|
115
103
|
return {
|
116
104
|
SwitchCase(node) {
|
117
|
-
const colonToken =
|
105
|
+
const colonToken = astUtils.getSwitchCaseColonToken(node, sourceCode);
|
118
106
|
const beforeToken = sourceCode.getTokenBefore(colonToken);
|
119
107
|
const afterToken = sourceCode.getTokenAfter(colonToken);
|
120
108
|
|
@@ -756,6 +756,19 @@ function isLogicalAssignmentOperator(operator) {
|
|
756
756
|
return LOGICAL_ASSIGNMENT_OPERATORS.has(operator);
|
757
757
|
}
|
758
758
|
|
759
|
+
/**
|
760
|
+
* Get the colon token of the given SwitchCase node.
|
761
|
+
* @param {ASTNode} node The SwitchCase node to get.
|
762
|
+
* @param {SourceCode} sourceCode The source code object to get tokens.
|
763
|
+
* @returns {Token} The colon token of the node.
|
764
|
+
*/
|
765
|
+
function getSwitchCaseColonToken(node, sourceCode) {
|
766
|
+
if (node.test) {
|
767
|
+
return sourceCode.getTokenAfter(node.test, isColonToken);
|
768
|
+
}
|
769
|
+
return sourceCode.getFirstToken(node, 1);
|
770
|
+
}
|
771
|
+
|
759
772
|
//------------------------------------------------------------------------------
|
760
773
|
// Public Interface
|
761
774
|
//------------------------------------------------------------------------------
|
@@ -1872,5 +1885,6 @@ module.exports = {
|
|
1872
1885
|
isSpecificMemberAccess,
|
1873
1886
|
equalLiteralValue,
|
1874
1887
|
isSameReference,
|
1875
|
-
isLogicalAssignmentOperator
|
1888
|
+
isLogicalAssignmentOperator,
|
1889
|
+
getSwitchCaseColonToken
|
1876
1890
|
};
|
package/lib/shared/types.js
CHANGED
@@ -21,7 +21,7 @@ module.exports = {};
|
|
21
21
|
/**
|
22
22
|
* @typedef {Object} ParserOptions
|
23
23
|
* @property {EcmaFeatures} [ecmaFeatures] The optional features.
|
24
|
-
* @property {3|5|6|7|8|9|10|11|12|2015|2016|2017|2018|2019|2020|2021} [ecmaVersion] The ECMAScript version (or revision number).
|
24
|
+
* @property {3|5|6|7|8|9|10|11|12|13|2015|2016|2017|2018|2019|2020|2021|2022} [ecmaVersion] The ECMAScript version (or revision number).
|
25
25
|
* @property {"script"|"module"} [sourceType] The source code type.
|
26
26
|
*/
|
27
27
|
|
@@ -83,12 +83,12 @@ module.exports = {};
|
|
83
83
|
|
84
84
|
/**
|
85
85
|
* @typedef {Object} LintMessage
|
86
|
-
* @property {number} column The 1-based column number.
|
86
|
+
* @property {number|undefined} column The 1-based column number.
|
87
87
|
* @property {number} [endColumn] The 1-based column number of the end location.
|
88
88
|
* @property {number} [endLine] The 1-based line number of the end location.
|
89
89
|
* @property {boolean} fatal If `true` then this is a fatal error.
|
90
90
|
* @property {{range:[number,number], text:string}} [fix] Information for autofix.
|
91
|
-
* @property {number} line The 1-based line number.
|
91
|
+
* @property {number|undefined} line The 1-based line number.
|
92
92
|
* @property {string} message The error message.
|
93
93
|
* @property {string|null} ruleId The ID of the rule which makes this message.
|
94
94
|
* @property {0|1|2} severity The severity of this message.
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint",
|
3
|
-
"version": "8.0.
|
3
|
+
"version": "8.0.1",
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
6
6
|
"bin": {
|
@@ -47,7 +47,7 @@
|
|
47
47
|
"homepage": "https://eslint.org",
|
48
48
|
"bugs": "https://github.com/eslint/eslint/issues/",
|
49
49
|
"dependencies": {
|
50
|
-
"@eslint/eslintrc": "^1.0.
|
50
|
+
"@eslint/eslintrc": "^1.0.3",
|
51
51
|
"@humanwhocodes/config-array": "^0.6.0",
|
52
52
|
"ajv": "^6.10.0",
|
53
53
|
"chalk": "^4.0.0",
|
@@ -59,7 +59,7 @@
|
|
59
59
|
"eslint-scope": "^6.0.0",
|
60
60
|
"eslint-utils": "^3.0.0",
|
61
61
|
"eslint-visitor-keys": "^3.0.0",
|
62
|
-
"espree": "^
|
62
|
+
"espree": "^9.0.0",
|
63
63
|
"esquery": "^1.4.0",
|
64
64
|
"esutils": "^2.0.2",
|
65
65
|
"fast-deep-equal": "^3.1.3",
|