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 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
@@ -48,7 +48,6 @@ process.once("uncaughtException", err => {
48
48
  console.error(`\nESLint: ${pkg.version}.\n${template(err.messageData || {})}`);
49
49
  } else {
50
50
 
51
- console.error(err.message);
52
51
  console.error(err.stack);
53
52
  }
54
53
 
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
@@ -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
 
@@ -33,11 +33,31 @@ module.exports = {
33
33
  url: "https://eslint.org/docs/rules/func-names"
34
34
  },
35
35
 
36
- schema: [
37
- {
38
- enum: ["always", "as-needed", "never"]
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
- const never = context.options[0] === "never";
49
- const asNeeded = context.options[0] === "as-needed";
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 name = astUtils.getFunctionNameWithKind(node);
158
+ const config = getConfigForNode(node);
98
159
 
99
- if (never) {
160
+ if (config === "never") {
100
161
  if (hasName) {
101
- context.report({
102
- node,
103
- messageId: "named",
104
- data: { name }
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 && (asNeeded ? !hasInferredName(node) : !isObjectOrClassMethod(node))) {
109
- context.report({
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(error => {
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([node, location, "Irregular whitespace not allowed."]);
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([node, location, "Irregular whitespace not allowed."]);
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;
@@ -8,7 +8,9 @@
8
8
  //------------------------------------------------------------------------------
9
9
  // Helpers
10
10
  //------------------------------------------------------------------------------
11
- const regex = /\t/;
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
- context.getSourceCode().getLines().forEach((line, index) => {
32
- const match = regex.exec(line);
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 + 1
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
  /**
@@ -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 (isPreviousNodeDeclaration && previousNode.kind === type) {
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
- if (hasOuterVariables) {
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 checkingMixedDestructuring = options.destructuring !== "all";
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 && (checkingMixedDestructuring || nodesToReport.length === 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
- seenSegments = new Set(),
139
- openAssignmentsWithoutReads = new Set(),
140
- openAssignmentsWithReads = new Set()
198
+ stateBySegmentStart = new WeakMap(),
199
+ stateBySegmentEnd = new WeakMap()
141
200
  } = {}
142
201
  ) {
143
- if (seenSegments.has(codePathSegment)) {
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
- (node.operator === "=" ? openAssignmentsWithoutReads : openAssignmentsWithReads).add(node);
211
+ currentState.enterAssignment(node);
153
212
  } else {
154
- openAssignmentsWithoutReads.delete(node);
155
- openAssignmentsWithReads.delete(node);
213
+ currentState.exitAssignment(node);
156
214
  }
157
215
  } else if (!entering && (node.type === "AwaitExpression" || node.type === "YieldExpression")) {
158
- [...openAssignmentsWithReads]
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
- [...openAssignmentsWithoutReads]
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 tokens = sourceCode.getTokensBetween(left, right, 1);
56
-
57
- for (let i = 1, l = tokens.length - 1; i < l; ++i) {
58
- const op = tokens[i];
59
-
60
- if (
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
- const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode);
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(null, null, "Rule should not modify AST.");
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(message, null, "Error should be a string, object, or RegExp.");
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:text-fixer");
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.5.0",
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 release",
15
- "ci-release": "node Makefile.js ciRelease",
16
- "alpharelease": "node Makefile.js prerelease -- alpha",
17
- "betarelease": "node Makefile.js prerelease -- beta",
18
- "rcrelease": "node Makefile.js prerelease -- rc",
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": "^3.1.0",
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.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": "^4.0.3",
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.11.1",
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",