eslint 5.5.0 → 5.8.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/CHANGELOG.md +54 -0
- package/bin/eslint.js +0 -1
- package/lib/linter.js +9 -0
- package/lib/rules/camelcase.js +36 -8
- package/lib/rules/func-names.js +77 -20
- package/lib/rules/no-constant-condition.js +2 -0
- package/lib/rules/no-extra-bind.js +22 -0
- package/lib/rules/no-irregular-whitespace.js +4 -8
- package/lib/rules/no-tabs.js +23 -6
- package/lib/rules/no-unused-vars.js +1 -1
- package/lib/rules/one-var.js +11 -1
- package/lib/rules/padding-line-between-statements.js +3 -0
- package/lib/rules/prefer-const.js +63 -4
- package/lib/rules/require-atomic-updates.js +87 -30
- package/lib/rules/space-infix-ops.js +16 -25
- package/lib/testers/rule-tester.js +2 -2
- package/lib/util/source-code-fixer.js +1 -1
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,57 @@
|
|
1
|
+
v5.8.0 - October 26, 2018
|
2
|
+
|
3
|
+
* 9152417 Fix: deprecation warning in RuleTester using Node v11 (#11009) (Teddy Katz)
|
4
|
+
* e349a03 Docs: Update issue templates to ask for PRs (#11012) (Nicholas C. Zakas)
|
5
|
+
* 3d88b38 Chore: avoid using legacy report API in no-irregular-whitespace (#11013) (Teddy Katz)
|
6
|
+
* 5a31a92 Build: compile espree's deps to ES5 when generating site (fixes #11014) (#11015) (Teddy Katz)
|
7
|
+
* 3943635 Update: Create Linter.version API (fixes #9271) (#11010) (Nicholas C. Zakas)
|
8
|
+
* a940cf4 Docs: Mention version for config glob patterns (fixes #8793) (Nicholas C. Zakas)
|
9
|
+
* 6e1c530 Build: run tests on Node 11 (#11008) (Teddy Katz)
|
10
|
+
* 58ff359 Docs: add instructions for npm 2FA (refs #10631) (#10992) (Teddy Katz)
|
11
|
+
* 2f87bb3 Upgrade: eslint-release@1.0.0 (refs #10631) (#10991) (Teddy Katz)
|
12
|
+
* 57ef0fd Fix: prefer-const when using destructuring assign (fixes #8308) (#10924) (Nicholas C. Zakas)
|
13
|
+
* 577cbf1 Chore: Add typescript-specific edge case tests to space-infix-ops (#10986) (Bence Dányi)
|
14
|
+
* d45b184 Chore: Using deconstruction assignment for shelljs (#10974) (ZYSzys)
|
15
|
+
|
16
|
+
v5.7.0 - October 12, 2018
|
17
|
+
|
18
|
+
* 6cb63fd Update: Add iife to padding-line-between-statements (fixes #10853) (#10916) (Kevin Partington)
|
19
|
+
* 5fd1bda Update: no-tabs allowIndentationTabs option (fixes #10256) (#10925) (Kevin Partington)
|
20
|
+
* d12be69 Fix: no-extra-bind No autofix if arg may have side effect (fixes #10846) (#10918) (Kevin Partington)
|
21
|
+
* 847372f Fix: no-unused-vars false pos. with markVariableAsUsed (fixes #10952) (#10954) (Roy Sutton)
|
22
|
+
* 4132de7 Chore: Simplify space-infix-ops (#10935) (Bence Dányi)
|
23
|
+
* 543edfa Fix: Fix error with one-var (fixes #10937) (#10938) (Justin Krup)
|
24
|
+
* 95c4cb1 Docs: Fix typo for no-unsafe-finally (#10945) (Sergio Santoro)
|
25
|
+
* 5fe0e1a Fix: no-invalid-regexp disallows \ at end of pattern (fixes #10861) (#10920) (Toru Nagashima)
|
26
|
+
* f85547a Docs: Add 'When Not To Use' section to space-infix-ops (#10931) (Bence Dányi)
|
27
|
+
* 3dccac4 Docs: Update working-with-parsers link (#10929) (Azeem Bande-Ali)
|
28
|
+
* 557a8bb Docs: Remove old note about caching, add a new one (fixes #10739) (#10913) (Zac)
|
29
|
+
* fe8111a Chore: Add more test cases to space-infix-ops (#10936) (Bence Dányi)
|
30
|
+
* 066f7e0 Update: camelcase rule ignoreList added (#10783) (Julien Martin)
|
31
|
+
* 70bde69 Upgrade: table to version 5 (#10903) (Rouven Weßling)
|
32
|
+
* 2e52bca Chore: Update issue templates (#10900) (Nicholas C. Zakas)
|
33
|
+
|
34
|
+
v5.6.1 - September 28, 2018
|
35
|
+
|
36
|
+
* 9b26bdb Fix: avoid exponential require-atomic-updates traversal (fixes #10893) (#10894) (Teddy Katz)
|
37
|
+
* 9432b10 Fix: make separateRequires work in consecutive mode (fixes #10784) (#10886) (Pig Fang)
|
38
|
+
* e51868d Upgrade: debug@4 (fixes #10854) (#10887) (薛定谔的猫)
|
39
|
+
* d3f3994 Docs: add information about reporting security issues (#10889) (Teddy Katz)
|
40
|
+
* cc458f4 Build: fix failing tests on master (#10890) (Teddy Katz)
|
41
|
+
* a6ebfd3 Docs: clarify defaultAssignment option, fix no-unneeded-ternary examples (#10874) (CoffeeTableEspresso)
|
42
|
+
* 9d52541 Fix: Remove duplicate error message on crash (fixes #8964) (#10865) (Nicholas C. Zakas)
|
43
|
+
* 4eb9a49 Docs: Update quotes.md (#10862) (The Jared Wilcurt)
|
44
|
+
* 9159e9b Docs: Update complexity.md (#10867) (Szymon Przybylski)
|
45
|
+
* 14f4e46 Docs: Use Linter instead of linter in Nodejs API page (#10864) (Nicholas C. Zakas)
|
46
|
+
* b3e3cb1 Chore: Update debug log name to match filename (#10863) (Nicholas C. Zakas)
|
47
|
+
|
48
|
+
v5.6.0 - September 14, 2018
|
49
|
+
|
50
|
+
* c5b688e Update: Added generators option to func-names (fixes #9511) (#10697) (Oscar Barrett)
|
51
|
+
* 7da36d5 Fix: respect generator function expressions in no-constant-condition (#10827) (Julian Rosse)
|
52
|
+
* 0a65844 Chore: quote enable avoidEscape option in eslint-config-eslint (#10626) (薛定谔的猫)
|
53
|
+
* 32f41bd Chore: Add configuration wrapper markdown for the bug report template (#10669) (Iulian Onofrei)
|
54
|
+
|
1
55
|
v5.5.0 - August 31, 2018
|
2
56
|
|
3
57
|
* 6e110e6 Fix: camelcase duplicate warning bug (fixes #10801) (#10802) (Julian Rosse)
|
package/bin/eslint.js
CHANGED
package/lib/linter.js
CHANGED
@@ -888,6 +888,15 @@ module.exports = class Linter {
|
|
888
888
|
this.environments = new Environments();
|
889
889
|
}
|
890
890
|
|
891
|
+
/**
|
892
|
+
* Getter for package version.
|
893
|
+
* @static
|
894
|
+
* @returns {string} The version from package.json.
|
895
|
+
*/
|
896
|
+
static get version() {
|
897
|
+
return pkg.version;
|
898
|
+
}
|
899
|
+
|
891
900
|
/**
|
892
901
|
* Configuration object for the `verify` API. A JS representation of the eslintrc files.
|
893
902
|
* @typedef {Object} ESLintConfig
|
package/lib/rules/camelcase.js
CHANGED
@@ -27,6 +27,16 @@ module.exports = {
|
|
27
27
|
},
|
28
28
|
properties: {
|
29
29
|
enum: ["always", "never"]
|
30
|
+
},
|
31
|
+
allow: {
|
32
|
+
type: "array",
|
33
|
+
items: [
|
34
|
+
{
|
35
|
+
type: "string"
|
36
|
+
}
|
37
|
+
],
|
38
|
+
minItems: 0,
|
39
|
+
uniqueItems: true
|
30
40
|
}
|
31
41
|
},
|
32
42
|
additionalProperties: false
|
@@ -40,6 +50,15 @@ module.exports = {
|
|
40
50
|
|
41
51
|
create(context) {
|
42
52
|
|
53
|
+
const options = context.options[0] || {};
|
54
|
+
let properties = options.properties || "";
|
55
|
+
const ignoreDestructuring = options.ignoreDestructuring || false;
|
56
|
+
const allow = options.allow || [];
|
57
|
+
|
58
|
+
if (properties !== "always" && properties !== "never") {
|
59
|
+
properties = "always";
|
60
|
+
}
|
61
|
+
|
43
62
|
//--------------------------------------------------------------------------
|
44
63
|
// Helpers
|
45
64
|
//--------------------------------------------------------------------------
|
@@ -60,6 +79,18 @@ module.exports = {
|
|
60
79
|
return name.indexOf("_") > -1 && name !== name.toUpperCase();
|
61
80
|
}
|
62
81
|
|
82
|
+
/**
|
83
|
+
* Checks if a string match the ignore list
|
84
|
+
* @param {string} name The string to check.
|
85
|
+
* @returns {boolean} if the string is ignored
|
86
|
+
* @private
|
87
|
+
*/
|
88
|
+
function isAllowed(name) {
|
89
|
+
return allow.findIndex(
|
90
|
+
entry => name === entry || name.match(new RegExp(entry))
|
91
|
+
) !== -1;
|
92
|
+
}
|
93
|
+
|
63
94
|
/**
|
64
95
|
* Checks if a parent of a node is an ObjectPattern.
|
65
96
|
* @param {ASTNode} node The node to check.
|
@@ -93,14 +124,6 @@ module.exports = {
|
|
93
124
|
}
|
94
125
|
}
|
95
126
|
|
96
|
-
const options = context.options[0] || {};
|
97
|
-
let properties = options.properties || "";
|
98
|
-
const ignoreDestructuring = options.ignoreDestructuring || false;
|
99
|
-
|
100
|
-
if (properties !== "always" && properties !== "never") {
|
101
|
-
properties = "always";
|
102
|
-
}
|
103
|
-
|
104
127
|
return {
|
105
128
|
|
106
129
|
Identifier(node) {
|
@@ -112,6 +135,11 @@ module.exports = {
|
|
112
135
|
const name = node.name.replace(/^_+|_+$/g, ""),
|
113
136
|
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
|
114
137
|
|
138
|
+
// First, we ignore the node if it match the ignore list
|
139
|
+
if (isAllowed(name)) {
|
140
|
+
return;
|
141
|
+
}
|
142
|
+
|
115
143
|
// MemberExpressions get special rules
|
116
144
|
if (node.parent.type === "MemberExpression") {
|
117
145
|
|
package/lib/rules/func-names.js
CHANGED
@@ -33,11 +33,31 @@ module.exports = {
|
|
33
33
|
url: "https://eslint.org/docs/rules/func-names"
|
34
34
|
},
|
35
35
|
|
36
|
-
schema:
|
37
|
-
{
|
38
|
-
|
39
|
-
|
40
|
-
|
36
|
+
schema: {
|
37
|
+
definitions: {
|
38
|
+
value: {
|
39
|
+
enum: [
|
40
|
+
"always",
|
41
|
+
"as-needed",
|
42
|
+
"never"
|
43
|
+
]
|
44
|
+
}
|
45
|
+
},
|
46
|
+
items: [
|
47
|
+
{
|
48
|
+
$ref: "#/definitions/value"
|
49
|
+
},
|
50
|
+
{
|
51
|
+
type: "object",
|
52
|
+
properties: {
|
53
|
+
generators: {
|
54
|
+
$ref: "#/definitions/value"
|
55
|
+
}
|
56
|
+
},
|
57
|
+
additionalProperties: false
|
58
|
+
}
|
59
|
+
]
|
60
|
+
},
|
41
61
|
messages: {
|
42
62
|
unnamed: "Unexpected unnamed {{name}}.",
|
43
63
|
named: "Unexpected named {{name}}."
|
@@ -45,8 +65,23 @@ module.exports = {
|
|
45
65
|
},
|
46
66
|
|
47
67
|
create(context) {
|
48
|
-
|
49
|
-
|
68
|
+
|
69
|
+
/**
|
70
|
+
* Returns the config option for the given node.
|
71
|
+
* @param {ASTNode} node - A node to get the config for.
|
72
|
+
* @returns {string} The config option.
|
73
|
+
*/
|
74
|
+
function getConfigForNode(node) {
|
75
|
+
if (
|
76
|
+
node.generator &&
|
77
|
+
context.options.length > 1 &&
|
78
|
+
context.options[1].generators
|
79
|
+
) {
|
80
|
+
return context.options[1].generators;
|
81
|
+
}
|
82
|
+
|
83
|
+
return context.options[0] || "always";
|
84
|
+
}
|
50
85
|
|
51
86
|
/**
|
52
87
|
* Determines whether the current FunctionExpression node is a get, set, or
|
@@ -83,6 +118,32 @@ module.exports = {
|
|
83
118
|
(parent.type === "AssignmentPattern" && parent.right === node);
|
84
119
|
}
|
85
120
|
|
121
|
+
/**
|
122
|
+
* Reports that an unnamed function should be named
|
123
|
+
* @param {ASTNode} node - The node to report in the event of an error.
|
124
|
+
* @returns {void}
|
125
|
+
*/
|
126
|
+
function reportUnexpectedUnnamedFunction(node) {
|
127
|
+
context.report({
|
128
|
+
node,
|
129
|
+
messageId: "unnamed",
|
130
|
+
data: { name: astUtils.getFunctionNameWithKind(node) }
|
131
|
+
});
|
132
|
+
}
|
133
|
+
|
134
|
+
/**
|
135
|
+
* Reports that a named function should be unnamed
|
136
|
+
* @param {ASTNode} node - The node to report in the event of an error.
|
137
|
+
* @returns {void}
|
138
|
+
*/
|
139
|
+
function reportUnexpectedNamedFunction(node) {
|
140
|
+
context.report({
|
141
|
+
node,
|
142
|
+
messageId: "named",
|
143
|
+
data: { name: astUtils.getFunctionNameWithKind(node) }
|
144
|
+
});
|
145
|
+
}
|
146
|
+
|
86
147
|
return {
|
87
148
|
"FunctionExpression:exit"(node) {
|
88
149
|
|
@@ -94,23 +155,19 @@ module.exports = {
|
|
94
155
|
}
|
95
156
|
|
96
157
|
const hasName = Boolean(node.id && node.id.name);
|
97
|
-
const
|
158
|
+
const config = getConfigForNode(node);
|
98
159
|
|
99
|
-
if (never) {
|
160
|
+
if (config === "never") {
|
100
161
|
if (hasName) {
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
162
|
+
reportUnexpectedNamedFunction(node);
|
163
|
+
}
|
164
|
+
} else if (config === "as-needed") {
|
165
|
+
if (!hasName && !hasInferredName(node)) {
|
166
|
+
reportUnexpectedUnnamedFunction(node);
|
106
167
|
}
|
107
168
|
} else {
|
108
|
-
if (!hasName &&
|
109
|
-
|
110
|
-
node,
|
111
|
-
messageId: "unnamed",
|
112
|
-
data: { name }
|
113
|
-
});
|
169
|
+
if (!hasName && !isObjectOrClassMethod(node)) {
|
170
|
+
reportUnexpectedUnnamedFunction(node);
|
114
171
|
}
|
115
172
|
}
|
116
173
|
}
|
@@ -207,6 +207,8 @@ module.exports = {
|
|
207
207
|
"ForStatement:exit": checkConstantConditionLoopInSet,
|
208
208
|
FunctionDeclaration: enterFunction,
|
209
209
|
"FunctionDeclaration:exit": exitFunction,
|
210
|
+
FunctionExpression: enterFunction,
|
211
|
+
"FunctionExpression:exit": exitFunction,
|
210
212
|
YieldExpression: () => loopsInCurrentScope.clear()
|
211
213
|
};
|
212
214
|
|
@@ -10,6 +10,12 @@
|
|
10
10
|
|
11
11
|
const astUtils = require("../util/ast-utils");
|
12
12
|
|
13
|
+
//------------------------------------------------------------------------------
|
14
|
+
// Helpers
|
15
|
+
//------------------------------------------------------------------------------
|
16
|
+
|
17
|
+
const SIDE_EFFECT_FREE_NODE_TYPES = new Set(["Literal", "Identifier", "ThisExpression", "FunctionExpression"]);
|
18
|
+
|
13
19
|
//------------------------------------------------------------------------------
|
14
20
|
// Rule Definition
|
15
21
|
//------------------------------------------------------------------------------
|
@@ -35,6 +41,18 @@ module.exports = {
|
|
35
41
|
create(context) {
|
36
42
|
let scopeInfo = null;
|
37
43
|
|
44
|
+
/**
|
45
|
+
* Checks if a node is free of side effects.
|
46
|
+
*
|
47
|
+
* This check is stricter than it needs to be, in order to keep the implementation simple.
|
48
|
+
*
|
49
|
+
* @param {ASTNode} node A node to check.
|
50
|
+
* @returns {boolean} True if the node is known to be side-effect free, false otherwise.
|
51
|
+
*/
|
52
|
+
function isSideEffectFree(node) {
|
53
|
+
return SIDE_EFFECT_FREE_NODE_TYPES.has(node.type);
|
54
|
+
}
|
55
|
+
|
38
56
|
/**
|
39
57
|
* Reports a given function node.
|
40
58
|
*
|
@@ -48,6 +66,10 @@ module.exports = {
|
|
48
66
|
messageId: "unexpected",
|
49
67
|
loc: node.parent.property.loc.start,
|
50
68
|
fix(fixer) {
|
69
|
+
if (node.parent.parent.arguments.length && !isSideEffectFree(node.parent.parent.arguments[0])) {
|
70
|
+
return null;
|
71
|
+
}
|
72
|
+
|
51
73
|
const firstTokenToRemove = context.getSourceCode()
|
52
74
|
.getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken);
|
53
75
|
|
@@ -81,9 +81,7 @@ module.exports = {
|
|
81
81
|
const locStart = node.loc.start;
|
82
82
|
const locEnd = node.loc.end;
|
83
83
|
|
84
|
-
errors = errors.filter(
|
85
|
-
const errorLoc = error[1];
|
86
|
-
|
84
|
+
errors = errors.filter(({ loc: errorLoc }) => {
|
87
85
|
if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) {
|
88
86
|
if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) {
|
89
87
|
return false;
|
@@ -157,7 +155,7 @@ module.exports = {
|
|
157
155
|
column: match.index
|
158
156
|
};
|
159
157
|
|
160
|
-
errors.push(
|
158
|
+
errors.push({ node, message: "Irregular whitespace not allowed.", loc: location });
|
161
159
|
}
|
162
160
|
});
|
163
161
|
}
|
@@ -182,7 +180,7 @@ module.exports = {
|
|
182
180
|
column: sourceLines[lineIndex].length
|
183
181
|
};
|
184
182
|
|
185
|
-
errors.push(
|
183
|
+
errors.push({ node, message: "Irregular whitespace not allowed.", loc: location });
|
186
184
|
lastLineIndex = lineIndex;
|
187
185
|
}
|
188
186
|
}
|
@@ -224,9 +222,7 @@ module.exports = {
|
|
224
222
|
}
|
225
223
|
|
226
224
|
// If we have any errors remaining report on them
|
227
|
-
errors.forEach(error =>
|
228
|
-
context.report(...error);
|
229
|
-
});
|
225
|
+
errors.forEach(error => context.report(error));
|
230
226
|
};
|
231
227
|
} else {
|
232
228
|
nodes.Program = noop;
|
package/lib/rules/no-tabs.js
CHANGED
@@ -8,7 +8,9 @@
|
|
8
8
|
//------------------------------------------------------------------------------
|
9
9
|
// Helpers
|
10
10
|
//------------------------------------------------------------------------------
|
11
|
-
|
11
|
+
|
12
|
+
const tabRegex = /\t+/g;
|
13
|
+
const anyNonWhitespaceRegex = /\S/;
|
12
14
|
|
13
15
|
//------------------------------------------------------------------------------
|
14
16
|
// Public Interface
|
@@ -22,21 +24,36 @@ module.exports = {
|
|
22
24
|
recommended: false,
|
23
25
|
url: "https://eslint.org/docs/rules/no-tabs"
|
24
26
|
},
|
25
|
-
schema: [
|
27
|
+
schema: [{
|
28
|
+
type: "object",
|
29
|
+
properties: {
|
30
|
+
allowIndentationTabs: {
|
31
|
+
type: "boolean"
|
32
|
+
}
|
33
|
+
},
|
34
|
+
additionalProperties: false
|
35
|
+
}]
|
26
36
|
},
|
27
37
|
|
28
38
|
create(context) {
|
39
|
+
const sourceCode = context.getSourceCode();
|
40
|
+
const allowIndentationTabs = context.options && context.options[0] && context.options[0].allowIndentationTabs;
|
41
|
+
|
29
42
|
return {
|
30
43
|
Program(node) {
|
31
|
-
|
32
|
-
|
44
|
+
sourceCode.getLines().forEach((line, index) => {
|
45
|
+
let match;
|
46
|
+
|
47
|
+
while ((match = tabRegex.exec(line)) !== null) {
|
48
|
+
if (allowIndentationTabs && !anyNonWhitespaceRegex.test(line.slice(0, match.index))) {
|
49
|
+
continue;
|
50
|
+
}
|
33
51
|
|
34
|
-
if (match) {
|
35
52
|
context.report({
|
36
53
|
node,
|
37
54
|
loc: {
|
38
55
|
line: index + 1,
|
39
|
-
column: match.index
|
56
|
+
column: match.index
|
40
57
|
},
|
41
58
|
message: "Unexpected tab character."
|
42
59
|
});
|
@@ -469,7 +469,7 @@ module.exports = {
|
|
469
469
|
const posteriorParams = params.slice(params.indexOf(variable) + 1);
|
470
470
|
|
471
471
|
// If any used parameters occur after this parameter, do not report.
|
472
|
-
return !posteriorParams.some(v => v.references.length > 0);
|
472
|
+
return !posteriorParams.some(v => v.references.length > 0 || v.eslintUsed);
|
473
473
|
}
|
474
474
|
|
475
475
|
/**
|
package/lib/rules/one-var.js
CHANGED
@@ -314,6 +314,11 @@ module.exports = {
|
|
314
314
|
function splitDeclarations(declaration) {
|
315
315
|
return fixer => declaration.declarations.map(declarator => {
|
316
316
|
const tokenAfterDeclarator = sourceCode.getTokenAfter(declarator);
|
317
|
+
|
318
|
+
if (tokenAfterDeclarator === null) {
|
319
|
+
return null;
|
320
|
+
}
|
321
|
+
|
317
322
|
const afterComma = sourceCode.getTokenAfter(tokenAfterDeclarator, { includeComments: true });
|
318
323
|
|
319
324
|
if (tokenAfterDeclarator.value !== ",") {
|
@@ -384,8 +389,13 @@ module.exports = {
|
|
384
389
|
if (nodeIndex > 0) {
|
385
390
|
const previousNode = parent.body[nodeIndex - 1];
|
386
391
|
const isPreviousNodeDeclaration = previousNode.type === "VariableDeclaration";
|
392
|
+
const declarationsWithPrevious = declarations.concat(previousNode.declarations || []);
|
387
393
|
|
388
|
-
if (
|
394
|
+
if (
|
395
|
+
isPreviousNodeDeclaration &&
|
396
|
+
previousNode.kind === type &&
|
397
|
+
!(declarationsWithPrevious.some(isRequire) && !declarationsWithPrevious.every(isRequire))
|
398
|
+
) {
|
389
399
|
const previousDeclCounts = countDeclarations(previousNode.declarations);
|
390
400
|
|
391
401
|
if (options[type].initialized === MODE_CONSECUTIVE && options[type].uninitialized === MODE_CONSECUTIVE) {
|
@@ -353,6 +353,9 @@ const StatementTypes = {
|
|
353
353
|
node.type === "ExpressionStatement" &&
|
354
354
|
!isDirectivePrologue(node, sourceCode)
|
355
355
|
},
|
356
|
+
iife: {
|
357
|
+
test: isIIFEStatement
|
358
|
+
},
|
356
359
|
"multiline-block-like": {
|
357
360
|
test: (node, sourceCode) =>
|
358
361
|
node.loc.start.line !== node.loc.end.line &&
|
@@ -57,6 +57,7 @@ function canBecomeVariableDeclaration(identifier) {
|
|
57
57
|
* @returns {boolean} Indicates if the variable is from outer scope or function parameters.
|
58
58
|
*/
|
59
59
|
function isOuterVariableInDestructing(name, initScope) {
|
60
|
+
|
60
61
|
if (initScope.through.find(ref => ref.resolved && ref.resolved.name === name)) {
|
61
62
|
return true;
|
62
63
|
}
|
@@ -96,6 +97,54 @@ function getDestructuringHost(reference) {
|
|
96
97
|
return node;
|
97
98
|
}
|
98
99
|
|
100
|
+
/**
|
101
|
+
* Determines if a destructuring assignment node contains
|
102
|
+
* any MemberExpression nodes. This is used to determine if a
|
103
|
+
* variable that is only written once using destructuring can be
|
104
|
+
* safely converted into a const declaration.
|
105
|
+
* @param {ASTNode} node The ObjectPattern or ArrayPattern node to check.
|
106
|
+
* @returns {boolean} True if the destructuring pattern contains
|
107
|
+
* a MemberExpression, false if not.
|
108
|
+
*/
|
109
|
+
function hasMemberExpressionAssignment(node) {
|
110
|
+
switch (node.type) {
|
111
|
+
case "ObjectPattern":
|
112
|
+
return node.properties.some(prop => {
|
113
|
+
if (prop) {
|
114
|
+
|
115
|
+
/*
|
116
|
+
* Spread elements have an argument property while
|
117
|
+
* others have a value property. Because different
|
118
|
+
* parsers use different node types for spread elements,
|
119
|
+
* we just check if there is an argument property.
|
120
|
+
*/
|
121
|
+
return hasMemberExpressionAssignment(prop.argument || prop.value);
|
122
|
+
}
|
123
|
+
|
124
|
+
return false;
|
125
|
+
});
|
126
|
+
|
127
|
+
case "ArrayPattern":
|
128
|
+
return node.elements.some(element => {
|
129
|
+
if (element) {
|
130
|
+
return hasMemberExpressionAssignment(element);
|
131
|
+
}
|
132
|
+
|
133
|
+
return false;
|
134
|
+
});
|
135
|
+
|
136
|
+
case "AssignmentPattern":
|
137
|
+
return hasMemberExpressionAssignment(node.left);
|
138
|
+
|
139
|
+
case "MemberExpression":
|
140
|
+
return true;
|
141
|
+
|
142
|
+
// no default
|
143
|
+
}
|
144
|
+
|
145
|
+
return false;
|
146
|
+
}
|
147
|
+
|
99
148
|
/**
|
100
149
|
* Gets an identifier node of a given variable.
|
101
150
|
*
|
@@ -148,7 +197,8 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
|
|
148
197
|
|
149
198
|
if (destructuringHost !== null && destructuringHost.left !== void 0) {
|
150
199
|
const leftNode = destructuringHost.left;
|
151
|
-
let hasOuterVariables = false
|
200
|
+
let hasOuterVariables = false,
|
201
|
+
hasNonIdentifiers = false;
|
152
202
|
|
153
203
|
if (leftNode.type === "ObjectPattern") {
|
154
204
|
const properties = leftNode.properties;
|
@@ -157,16 +207,23 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
|
|
157
207
|
.filter(prop => prop.value)
|
158
208
|
.map(prop => prop.value.name)
|
159
209
|
.some(name => isOuterVariableInDestructing(name, variable.scope));
|
210
|
+
|
211
|
+
hasNonIdentifiers = hasMemberExpressionAssignment(leftNode);
|
212
|
+
|
160
213
|
} else if (leftNode.type === "ArrayPattern") {
|
161
214
|
const elements = leftNode.elements;
|
162
215
|
|
163
216
|
hasOuterVariables = elements
|
164
217
|
.map(element => element && element.name)
|
165
218
|
.some(name => isOuterVariableInDestructing(name, variable.scope));
|
219
|
+
|
220
|
+
hasNonIdentifiers = hasMemberExpressionAssignment(leftNode);
|
166
221
|
}
|
167
|
-
|
222
|
+
|
223
|
+
if (hasOuterVariables || hasNonIdentifiers) {
|
168
224
|
return null;
|
169
225
|
}
|
226
|
+
|
170
227
|
}
|
171
228
|
|
172
229
|
writer = reference;
|
@@ -192,9 +249,11 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
|
|
192
249
|
if (!shouldBeConst) {
|
193
250
|
return null;
|
194
251
|
}
|
252
|
+
|
195
253
|
if (isReadBeforeInit) {
|
196
254
|
return variable.defs[0].name;
|
197
255
|
}
|
256
|
+
|
198
257
|
return writer.identifier;
|
199
258
|
}
|
200
259
|
|
@@ -295,7 +354,7 @@ module.exports = {
|
|
295
354
|
create(context) {
|
296
355
|
const options = context.options[0] || {};
|
297
356
|
const sourceCode = context.getSourceCode();
|
298
|
-
const
|
357
|
+
const shouldMatchAnyDestructuredVariable = options.destructuring !== "all";
|
299
358
|
const ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true;
|
300
359
|
const variables = [];
|
301
360
|
|
@@ -316,7 +375,7 @@ module.exports = {
|
|
316
375
|
function checkGroup(nodes) {
|
317
376
|
const nodesToReport = nodes.filter(Boolean);
|
318
377
|
|
319
|
-
if (nodes.length && (
|
378
|
+
if (nodes.length && (shouldMatchAnyDestructuredVariable || nodesToReport.length === nodes.length)) {
|
320
379
|
const varDeclParent = findUp(nodes[0], "VariableDeclaration", parentNode => parentNode.type.endsWith("Statement"));
|
321
380
|
const shouldFix = varDeclParent &&
|
322
381
|
|
@@ -119,6 +119,66 @@ module.exports = {
|
|
119
119
|
});
|
120
120
|
}
|
121
121
|
|
122
|
+
const alreadyReportedAssignments = new WeakSet();
|
123
|
+
|
124
|
+
class AssignmentTrackerState {
|
125
|
+
constructor({ openAssignmentsWithoutReads = new Set(), openAssignmentsWithReads = new Set() } = {}) {
|
126
|
+
this.openAssignmentsWithoutReads = openAssignmentsWithoutReads;
|
127
|
+
this.openAssignmentsWithReads = openAssignmentsWithReads;
|
128
|
+
}
|
129
|
+
|
130
|
+
copy() {
|
131
|
+
return new AssignmentTrackerState({
|
132
|
+
openAssignmentsWithoutReads: new Set(this.openAssignmentsWithoutReads),
|
133
|
+
openAssignmentsWithReads: new Set(this.openAssignmentsWithReads)
|
134
|
+
});
|
135
|
+
}
|
136
|
+
|
137
|
+
merge(other) {
|
138
|
+
const initialAssignmentsWithoutReadsCount = this.openAssignmentsWithoutReads.size;
|
139
|
+
const initialAssignmentsWithReadsCount = this.openAssignmentsWithReads.size;
|
140
|
+
|
141
|
+
other.openAssignmentsWithoutReads.forEach(assignment => this.openAssignmentsWithoutReads.add(assignment));
|
142
|
+
other.openAssignmentsWithReads.forEach(assignment => this.openAssignmentsWithReads.add(assignment));
|
143
|
+
|
144
|
+
return this.openAssignmentsWithoutReads.size > initialAssignmentsWithoutReadsCount ||
|
145
|
+
this.openAssignmentsWithReads.size > initialAssignmentsWithReadsCount;
|
146
|
+
}
|
147
|
+
|
148
|
+
enterAssignment(assignmentExpression) {
|
149
|
+
(assignmentExpression.operator === "=" ? this.openAssignmentsWithoutReads : this.openAssignmentsWithReads).add(assignmentExpression);
|
150
|
+
}
|
151
|
+
|
152
|
+
exitAssignment(assignmentExpression) {
|
153
|
+
this.openAssignmentsWithoutReads.delete(assignmentExpression);
|
154
|
+
this.openAssignmentsWithReads.delete(assignmentExpression);
|
155
|
+
}
|
156
|
+
|
157
|
+
exitAwaitOrYield(node, surroundingFunction) {
|
158
|
+
return [...this.openAssignmentsWithReads]
|
159
|
+
.filter(assignment => !isLocalVariableWithoutEscape(assignment.left, surroundingFunction))
|
160
|
+
.forEach(assignment => {
|
161
|
+
if (!alreadyReportedAssignments.has(assignment)) {
|
162
|
+
reportAssignment(assignment);
|
163
|
+
alreadyReportedAssignments.add(assignment);
|
164
|
+
}
|
165
|
+
});
|
166
|
+
}
|
167
|
+
|
168
|
+
exitIdentifierOrMemberExpression(node) {
|
169
|
+
[...this.openAssignmentsWithoutReads]
|
170
|
+
.filter(assignment => (
|
171
|
+
assignment.left !== node &&
|
172
|
+
assignment.left.type === node.type &&
|
173
|
+
astUtils.equalTokens(assignment.left, node, sourceCode)
|
174
|
+
))
|
175
|
+
.forEach(assignment => {
|
176
|
+
this.openAssignmentsWithoutReads.delete(assignment);
|
177
|
+
this.openAssignmentsWithReads.add(assignment);
|
178
|
+
});
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
122
182
|
/**
|
123
183
|
* If the control flow graph of a function enters an assignment expression, then does the
|
124
184
|
* both of the following steps in order (possibly with other steps in between) before exiting the
|
@@ -135,54 +195,51 @@ module.exports = {
|
|
135
195
|
codePathSegment,
|
136
196
|
surroundingFunction,
|
137
197
|
{
|
138
|
-
|
139
|
-
|
140
|
-
openAssignmentsWithReads = new Set()
|
198
|
+
stateBySegmentStart = new WeakMap(),
|
199
|
+
stateBySegmentEnd = new WeakMap()
|
141
200
|
} = {}
|
142
201
|
) {
|
143
|
-
if (
|
144
|
-
|
145
|
-
// An AssignmentExpression can't contain loops, so it's not necessary to reenter them with new state.
|
146
|
-
return;
|
202
|
+
if (!stateBySegmentStart.has(codePathSegment)) {
|
203
|
+
stateBySegmentStart.set(codePathSegment, new AssignmentTrackerState());
|
147
204
|
}
|
148
205
|
|
206
|
+
const currentState = stateBySegmentStart.get(codePathSegment).copy();
|
207
|
+
|
149
208
|
expressionsByCodePathSegment.get(codePathSegment).forEach(({ entering, node }) => {
|
150
209
|
if (node.type === "AssignmentExpression") {
|
151
210
|
if (entering) {
|
152
|
-
|
211
|
+
currentState.enterAssignment(node);
|
153
212
|
} else {
|
154
|
-
|
155
|
-
openAssignmentsWithReads.delete(node);
|
213
|
+
currentState.exitAssignment(node);
|
156
214
|
}
|
157
215
|
} else if (!entering && (node.type === "AwaitExpression" || node.type === "YieldExpression")) {
|
158
|
-
|
159
|
-
.filter(assignment => !isLocalVariableWithoutEscape(assignment.left, surroundingFunction))
|
160
|
-
.forEach(reportAssignment);
|
161
|
-
|
162
|
-
openAssignmentsWithReads.clear();
|
216
|
+
currentState.exitAwaitOrYield(node, surroundingFunction);
|
163
217
|
} else if (!entering && (node.type === "Identifier" || node.type === "MemberExpression")) {
|
164
|
-
|
165
|
-
.filter(assignment => (
|
166
|
-
assignment.left !== node &&
|
167
|
-
assignment.left.type === node.type &&
|
168
|
-
astUtils.equalTokens(assignment.left, node, sourceCode)
|
169
|
-
))
|
170
|
-
.forEach(assignment => {
|
171
|
-
openAssignmentsWithoutReads.delete(assignment);
|
172
|
-
openAssignmentsWithReads.add(assignment);
|
173
|
-
});
|
218
|
+
currentState.exitIdentifierOrMemberExpression(node);
|
174
219
|
}
|
175
220
|
});
|
176
221
|
|
222
|
+
stateBySegmentEnd.set(codePathSegment, currentState);
|
223
|
+
|
177
224
|
codePathSegment.nextSegments.forEach(nextSegment => {
|
225
|
+
if (stateBySegmentStart.has(nextSegment)) {
|
226
|
+
if (!stateBySegmentStart.get(nextSegment).merge(currentState)) {
|
227
|
+
|
228
|
+
/*
|
229
|
+
* This segment has already been processed with the given set of inputs;
|
230
|
+
* no need to do it again. After no new state is available to process
|
231
|
+
* for any control flow segment in the graph, the analysis reaches a fixpoint and
|
232
|
+
* traversal stops.
|
233
|
+
*/
|
234
|
+
return;
|
235
|
+
}
|
236
|
+
} else {
|
237
|
+
stateBySegmentStart.set(nextSegment, currentState.copy());
|
238
|
+
}
|
178
239
|
findOutdatedReads(
|
179
240
|
nextSegment,
|
180
241
|
surroundingFunction,
|
181
|
-
{
|
182
|
-
seenSegments: new Set(seenSegments).add(codePathSegment),
|
183
|
-
openAssignmentsWithoutReads: new Set(openAssignmentsWithoutReads),
|
184
|
-
openAssignmentsWithReads: new Set(openAssignmentsWithReads)
|
185
|
-
}
|
242
|
+
{ stateBySegmentStart, stateBySegmentEnd }
|
186
243
|
);
|
187
244
|
});
|
188
245
|
}
|
@@ -34,37 +34,25 @@ module.exports = {
|
|
34
34
|
|
35
35
|
create(context) {
|
36
36
|
const int32Hint = context.options[0] ? context.options[0].int32Hint === true : false;
|
37
|
-
|
38
|
-
const OPERATORS = [
|
39
|
-
"*", "/", "%", "+", "-", "<<", ">>", ">>>", "<", "<=", ">", ">=", "in",
|
40
|
-
"instanceof", "==", "!=", "===", "!==", "&", "^", "|", "&&", "||", "=",
|
41
|
-
"+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=", "|=",
|
42
|
-
"?", ":", ",", "**"
|
43
|
-
];
|
44
|
-
|
45
37
|
const sourceCode = context.getSourceCode();
|
46
38
|
|
47
39
|
/**
|
48
40
|
* Returns the first token which violates the rule
|
49
41
|
* @param {ASTNode} left - The left node of the main node
|
50
42
|
* @param {ASTNode} right - The right node of the main node
|
43
|
+
* @param {string} op - The operator of the main node
|
51
44
|
* @returns {Object} The violator token or null
|
52
45
|
* @private
|
53
46
|
*/
|
54
|
-
function getFirstNonSpacedToken(left, right) {
|
55
|
-
const
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
(op.type === "Punctuator" || op.type === "Keyword") &&
|
62
|
-
OPERATORS.indexOf(op.value) >= 0 &&
|
63
|
-
(tokens[i - 1].range[1] >= op.range[0] || op.range[1] >= tokens[i + 1].range[0])
|
64
|
-
) {
|
65
|
-
return op;
|
66
|
-
}
|
47
|
+
function getFirstNonSpacedToken(left, right, op) {
|
48
|
+
const operator = sourceCode.getFirstTokenBetween(left, right, token => token.value === op);
|
49
|
+
const prev = sourceCode.getTokenBefore(operator);
|
50
|
+
const next = sourceCode.getTokenAfter(operator);
|
51
|
+
|
52
|
+
if (!sourceCode.isSpaceBetweenTokens(prev, operator) || !sourceCode.isSpaceBetweenTokens(operator, next)) {
|
53
|
+
return operator;
|
67
54
|
}
|
55
|
+
|
68
56
|
return null;
|
69
57
|
}
|
70
58
|
|
@@ -110,7 +98,10 @@ module.exports = {
|
|
110
98
|
const leftNode = (node.left.typeAnnotation) ? node.left.typeAnnotation : node.left;
|
111
99
|
const rightNode = node.right;
|
112
100
|
|
113
|
-
|
101
|
+
// search for = in AssignmentPattern nodes
|
102
|
+
const operator = node.operator || "=";
|
103
|
+
|
104
|
+
const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, operator);
|
114
105
|
|
115
106
|
if (nonSpacedNode) {
|
116
107
|
if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) {
|
@@ -126,8 +117,8 @@ module.exports = {
|
|
126
117
|
* @private
|
127
118
|
*/
|
128
119
|
function checkConditional(node) {
|
129
|
-
const nonSpacedConsequesntNode = getFirstNonSpacedToken(node.test, node.consequent);
|
130
|
-
const nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate);
|
120
|
+
const nonSpacedConsequesntNode = getFirstNonSpacedToken(node.test, node.consequent, "?");
|
121
|
+
const nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate, ":");
|
131
122
|
|
132
123
|
if (nonSpacedConsequesntNode) {
|
133
124
|
report(node, nonSpacedConsequesntNode);
|
@@ -147,7 +138,7 @@ module.exports = {
|
|
147
138
|
const rightNode = node.init;
|
148
139
|
|
149
140
|
if (rightNode) {
|
150
|
-
const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode);
|
141
|
+
const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, "=");
|
151
142
|
|
152
143
|
if (nonSpacedNode) {
|
153
144
|
report(node, nonSpacedNode);
|
@@ -397,7 +397,7 @@ class RuleTester {
|
|
397
397
|
*/
|
398
398
|
function assertASTDidntChange(beforeAST, afterAST) {
|
399
399
|
if (!lodash.isEqual(beforeAST, afterAST)) {
|
400
|
-
assert.fail(
|
400
|
+
assert.fail("Rule should not modify AST.");
|
401
401
|
}
|
402
402
|
}
|
403
403
|
|
@@ -551,7 +551,7 @@ class RuleTester {
|
|
551
551
|
} else {
|
552
552
|
|
553
553
|
// Message was an unexpected type
|
554
|
-
assert.fail(
|
554
|
+
assert.fail(`Error should be a string, object, or RegExp, but found (${util.inspect(message)})`);
|
555
555
|
}
|
556
556
|
}
|
557
557
|
}
|
@@ -8,7 +8,7 @@
|
|
8
8
|
// Requirements
|
9
9
|
//------------------------------------------------------------------------------
|
10
10
|
|
11
|
-
const debug = require("debug")("eslint:
|
11
|
+
const debug = require("debug")("eslint:source-code-fixer");
|
12
12
|
|
13
13
|
//------------------------------------------------------------------------------
|
14
14
|
// Helpers
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint",
|
3
|
-
"version": "5.
|
3
|
+
"version": "5.8.0",
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
6
6
|
"bin": {
|
@@ -11,11 +11,11 @@
|
|
11
11
|
"test": "node Makefile.js test",
|
12
12
|
"lint": "node Makefile.js lint",
|
13
13
|
"fuzz": "node Makefile.js fuzz",
|
14
|
-
"release": "node Makefile.js
|
15
|
-
"
|
16
|
-
"
|
17
|
-
"
|
18
|
-
"
|
14
|
+
"generate-release": "node Makefile.js generateRelease",
|
15
|
+
"generate-alpharelease": "node Makefile.js generatePrerelease -- alpha",
|
16
|
+
"generate-betarelease": "node Makefile.js generatePrerelease -- beta",
|
17
|
+
"generate-rcrelease": "node Makefile.js generatePrerelease -- rc",
|
18
|
+
"publish-release": "node Makefile.js publishRelease",
|
19
19
|
"docs": "node Makefile.js docs",
|
20
20
|
"gensite": "node Makefile.js gensite",
|
21
21
|
"browserify": "node Makefile.js browserify",
|
@@ -39,7 +39,7 @@
|
|
39
39
|
"ajv": "^6.5.3",
|
40
40
|
"chalk": "^2.1.0",
|
41
41
|
"cross-spawn": "^6.0.5",
|
42
|
-
"debug": "^
|
42
|
+
"debug": "^4.0.1",
|
43
43
|
"doctrine": "^2.1.0",
|
44
44
|
"eslint-scope": "^4.0.0",
|
45
45
|
"eslint-utils": "^1.3.1",
|
@@ -66,12 +66,12 @@
|
|
66
66
|
"path-is-inside": "^1.0.2",
|
67
67
|
"pluralize": "^7.0.0",
|
68
68
|
"progress": "^2.0.0",
|
69
|
-
"regexpp": "^2.0.
|
69
|
+
"regexpp": "^2.0.1",
|
70
70
|
"require-uncached": "^1.0.3",
|
71
71
|
"semver": "^5.5.1",
|
72
72
|
"strip-ansi": "^4.0.0",
|
73
73
|
"strip-json-comments": "^2.0.1",
|
74
|
-
"table": "^
|
74
|
+
"table": "^5.0.2",
|
75
75
|
"text-table": "^0.2.0"
|
76
76
|
},
|
77
77
|
"devDependencies": {
|
@@ -90,7 +90,7 @@
|
|
90
90
|
"eslint-plugin-eslint-plugin": "^1.2.0",
|
91
91
|
"eslint-plugin-node": "^7.0.1",
|
92
92
|
"eslint-plugin-rulesdir": "^0.1.0",
|
93
|
-
"eslint-release": "^0.
|
93
|
+
"eslint-release": "^1.0.0",
|
94
94
|
"eslint-rule-composer": "^0.3.0",
|
95
95
|
"eslump": "^1.6.2",
|
96
96
|
"esprima": "^4.0.1",
|