eslint 3.15.0 → 3.17.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.
Files changed (118) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/conf/{eslint.json → eslint-recommended.js} +87 -71
  3. package/lib/ast-utils.js +182 -80
  4. package/lib/code-path-analysis/code-path-state.js +2 -2
  5. package/lib/config/autoconfig.js +3 -3
  6. package/lib/config/config-file.js +14 -7
  7. package/lib/config/config-initializer.js +1 -1
  8. package/lib/config.js +3 -2
  9. package/lib/eslint.js +4 -4
  10. package/lib/internal-rules/internal-no-invalid-meta.js +2 -40
  11. package/lib/rules/array-callback-return.js +15 -5
  12. package/lib/rules/arrow-body-style.js +7 -4
  13. package/lib/rules/arrow-spacing.js +7 -6
  14. package/lib/rules/block-spacing.js +2 -2
  15. package/lib/rules/brace-style.js +2 -6
  16. package/lib/rules/capitalized-comments.js +8 -7
  17. package/lib/rules/comma-spacing.js +3 -3
  18. package/lib/rules/complexity.js +14 -8
  19. package/lib/rules/consistent-return.js +18 -11
  20. package/lib/rules/constructor-super.js +3 -3
  21. package/lib/rules/curly.js +11 -7
  22. package/lib/rules/default-case.js +3 -3
  23. package/lib/rules/eqeqeq.js +15 -6
  24. package/lib/rules/func-call-spacing.js +10 -13
  25. package/lib/rules/func-names.js +20 -5
  26. package/lib/rules/generator-star-spacing.js +18 -19
  27. package/lib/rules/id-blacklist.js +2 -2
  28. package/lib/rules/id-length.js +3 -3
  29. package/lib/rules/id-match.js +2 -2
  30. package/lib/rules/indent.js +7 -6
  31. package/lib/rules/key-spacing.js +12 -16
  32. package/lib/rules/keyword-spacing.js +21 -17
  33. package/lib/rules/line-comment-position.js +16 -6
  34. package/lib/rules/linebreak-style.js +7 -1
  35. package/lib/rules/lines-around-comment.js +23 -4
  36. package/lib/rules/lines-around-directive.js +3 -3
  37. package/lib/rules/max-lines.js +2 -2
  38. package/lib/rules/max-params.js +17 -4
  39. package/lib/rules/max-statements-per-line.js +7 -6
  40. package/lib/rules/max-statements.js +11 -10
  41. package/lib/rules/newline-after-var.js +7 -2
  42. package/lib/rules/newline-per-chained-call.js +3 -1
  43. package/lib/rules/no-compare-neg-zero.js +53 -0
  44. package/lib/rules/no-cond-assign.js +3 -3
  45. package/lib/rules/no-else-return.js +13 -1
  46. package/lib/rules/no-empty-function.js +9 -16
  47. package/lib/rules/no-extend-native.js +3 -3
  48. package/lib/rules/no-extra-bind.js +3 -4
  49. package/lib/rules/no-extra-boolean-cast.js +8 -0
  50. package/lib/rules/no-extra-parens.js +1 -2
  51. package/lib/rules/no-extra-semi.js +13 -1
  52. package/lib/rules/no-inner-declarations.js +4 -4
  53. package/lib/rules/no-invalid-regexp.js +2 -1
  54. package/lib/rules/no-irregular-whitespace.js +7 -1
  55. package/lib/rules/no-lone-blocks.js +10 -10
  56. package/lib/rules/no-mixed-operators.js +1 -7
  57. package/lib/rules/no-multi-spaces.js +4 -1
  58. package/lib/rules/no-multi-str.js +7 -3
  59. package/lib/rules/no-multiple-empty-lines.js +2 -4
  60. package/lib/rules/no-param-reassign.js +29 -6
  61. package/lib/rules/no-restricted-properties.js +2 -0
  62. package/lib/rules/no-return-assign.js +7 -14
  63. package/lib/rules/no-return-await.js +1 -1
  64. package/lib/rules/no-sequences.js +7 -6
  65. package/lib/rules/no-trailing-spaces.js +8 -2
  66. package/lib/rules/no-undefined.js +45 -6
  67. package/lib/rules/no-unexpected-multiline.js +9 -8
  68. package/lib/rules/no-unneeded-ternary.js +5 -1
  69. package/lib/rules/no-unused-labels.js +17 -2
  70. package/lib/rules/no-unused-vars.js +13 -27
  71. package/lib/rules/no-use-before-define.js +1 -1
  72. package/lib/rules/no-useless-computed-key.js +8 -3
  73. package/lib/rules/no-useless-concat.js +10 -7
  74. package/lib/rules/no-useless-escape.js +2 -2
  75. package/lib/rules/no-useless-return.js +14 -9
  76. package/lib/rules/no-var.js +1 -3
  77. package/lib/rules/no-whitespace-before-property.js +5 -16
  78. package/lib/rules/nonblock-statement-body-position.js +114 -0
  79. package/lib/rules/object-curly-newline.js +2 -2
  80. package/lib/rules/object-curly-spacing.js +7 -25
  81. package/lib/rules/object-property-newline.js +3 -3
  82. package/lib/rules/object-shorthand.js +4 -3
  83. package/lib/rules/operator-assignment.js +2 -2
  84. package/lib/rules/operator-linebreak.js +8 -10
  85. package/lib/rules/padded-blocks.js +39 -30
  86. package/lib/rules/prefer-destructuring.js +1 -1
  87. package/lib/rules/prefer-spread.js +1 -1
  88. package/lib/rules/prefer-template.js +1 -1
  89. package/lib/rules/quotes.js +10 -6
  90. package/lib/rules/semi-spacing.js +4 -0
  91. package/lib/rules/semi.js +13 -1
  92. package/lib/rules/space-before-function-paren.js +8 -5
  93. package/lib/rules/space-unary-ops.js +19 -1
  94. package/lib/rules/spaced-comment.js +2 -2
  95. package/lib/rules/strict.js +10 -4
  96. package/lib/rules/unicode-bom.js +1 -1
  97. package/lib/rules/wrap-iife.js +5 -5
  98. package/lib/rules/yoda.js +4 -9
  99. package/lib/testers/rule-tester.js +46 -9
  100. package/lib/token-store/backward-token-comment-cursor.js +57 -0
  101. package/lib/token-store/backward-token-cursor.js +56 -0
  102. package/lib/token-store/cursor.js +76 -0
  103. package/lib/token-store/cursors.js +92 -0
  104. package/lib/token-store/decorative-cursor.js +39 -0
  105. package/lib/token-store/filter-cursor.js +43 -0
  106. package/lib/token-store/forward-token-comment-cursor.js +57 -0
  107. package/lib/token-store/forward-token-cursor.js +61 -0
  108. package/lib/token-store/index.js +604 -0
  109. package/lib/token-store/limit-cursor.js +40 -0
  110. package/lib/token-store/padded-token-cursor.js +38 -0
  111. package/lib/token-store/skip-cursor.js +42 -0
  112. package/lib/token-store/utils.js +100 -0
  113. package/lib/util/fix-tracker.js +121 -0
  114. package/lib/util/source-code-fixer.js +35 -39
  115. package/lib/util/source-code.js +129 -16
  116. package/messages/extend-config-missing.txt +3 -0
  117. package/package.json +5 -6
  118. package/lib/token-store.js +0 -203
@@ -5,14 +5,16 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
8
12
  const astUtils = require("../ast-utils");
9
13
 
10
14
  //------------------------------------------------------------------------------
11
15
  // Rule Definition
12
16
  //------------------------------------------------------------------------------
13
17
 
14
- const LINEBREAK_REGEX = /\r\n|\r|\n|\u2028|\u2029/g;
15
-
16
18
  module.exports = {
17
19
  meta: {
18
20
  docs: {
@@ -85,7 +87,7 @@ module.exports = {
85
87
  if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") {
86
88
 
87
89
  // If there is a comment before and after the operator, don't do a fix.
88
- if (sourceCode.getTokenOrCommentBefore(operatorToken) !== tokenBefore && sourceCode.getTokenOrCommentAfter(operatorToken) !== tokenAfter) {
90
+ if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore && sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) {
89
91
  return null;
90
92
  }
91
93
 
@@ -100,6 +102,7 @@ module.exports = {
100
102
  newTextBefore = textAfter;
101
103
  newTextAfter = textBefore;
102
104
  } else {
105
+ const LINEBREAK_REGEX = astUtils.createGlobalLinebreakMatcher();
103
106
 
104
107
  // Otherwise, if no linebreak is desired and no comments interfere, replace the linebreaks with empty strings.
105
108
  newTextBefore = desiredStyle === "before" || textBefore.trim() ? textBefore : textBefore.replace(LINEBREAK_REGEX, "");
@@ -129,19 +132,14 @@ module.exports = {
129
132
  * @returns {void}
130
133
  */
131
134
  function validateNode(node, leftSide) {
132
- let leftToken = sourceCode.getLastToken(leftSide);
133
- let operatorToken = sourceCode.getTokenAfter(leftToken);
134
135
 
135
136
  // When the left part of a binary expression is a single expression wrapped in
136
137
  // parentheses (ex: `(a) + b`), leftToken will be the last token of the expression
137
138
  // and operatorToken will be the closing parenthesis.
138
139
  // The leftToken should be the last closing parenthesis, and the operatorToken
139
140
  // should be the token right after that.
140
- while (operatorToken.value === ")") {
141
- leftToken = operatorToken;
142
- operatorToken = sourceCode.getTokenAfter(operatorToken);
143
- }
144
-
141
+ const operatorToken = sourceCode.getTokenAfter(leftSide, astUtils.isNotClosingParenToken);
142
+ const leftToken = sourceCode.getTokenBefore(operatorToken);
145
143
  const rightToken = sourceCode.getTokenAfter(operatorToken);
146
144
  const operator = operatorToken.value;
147
145
  const operatorStyleOverride = styleOverrides[operator];
@@ -90,23 +90,32 @@ module.exports = {
90
90
  return node.type === "Line" || node.type === "Block";
91
91
  }
92
92
 
93
+ /**
94
+ * Checks if there is padding between two tokens
95
+ * @param {Token} first The first token
96
+ * @param {Token} second The second token
97
+ * @returns {boolean} True if there is at least a line between the tokens
98
+ */
99
+ function isPaddingBetweenTokens(first, second) {
100
+ return second.loc.start.line - first.loc.end.line >= 2;
101
+ }
102
+
103
+
93
104
  /**
94
105
  * Checks if the given token has a blank line after it.
95
106
  * @param {Token} token The token to check.
96
107
  * @returns {boolean} Whether or not the token is followed by a blank line.
97
108
  */
98
- function isTokenTopPadded(token) {
99
- const tokenStartLine = token.loc.start.line,
100
- expectedFirstLine = tokenStartLine + 2;
101
- let first = token;
109
+ function getFirstBlockToken(token) {
110
+ let prev = token,
111
+ first = token;
102
112
 
103
113
  do {
104
- first = sourceCode.getTokenOrCommentAfter(first);
105
- } while (isComment(first) && first.loc.start.line === tokenStartLine);
106
-
107
- const firstLine = first.loc.start.line;
114
+ prev = first;
115
+ first = sourceCode.getTokenAfter(first, { includeComments: true });
116
+ } while (isComment(first) && first.loc.start.line === prev.loc.end.line);
108
117
 
109
- return expectedFirstLine <= firstLine;
118
+ return first;
110
119
  }
111
120
 
112
121
  /**
@@ -114,18 +123,16 @@ module.exports = {
114
123
  * @param {Token} token The token to check
115
124
  * @returns {boolean} Whether or not the token is preceeded by a blank line
116
125
  */
117
- function isTokenBottomPadded(token) {
118
- const blockEnd = token.loc.end.line,
119
- expectedLastLine = blockEnd - 2;
120
- let last = token;
126
+ function getLastBlockToken(token) {
127
+ let last = token,
128
+ next = token;
121
129
 
122
130
  do {
123
- last = sourceCode.getTokenOrCommentBefore(last);
124
- } while (isComment(last) && last.loc.end.line === blockEnd);
125
-
126
- const lastLine = last.loc.end.line;
131
+ next = last;
132
+ last = sourceCode.getTokenBefore(last, { includeComments: true });
133
+ } while (isComment(last) && last.loc.end.line === next.loc.start.line);
127
134
 
128
- return lastLine <= expectedLastLine;
135
+ return last;
129
136
  }
130
137
 
131
138
  /**
@@ -155,17 +162,21 @@ module.exports = {
155
162
  */
156
163
  function checkPadding(node) {
157
164
  const openBrace = getOpenBrace(node),
165
+ firstBlockToken = getFirstBlockToken(openBrace),
166
+ tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, { includeComments: true }),
158
167
  closeBrace = sourceCode.getLastToken(node),
159
- blockHasTopPadding = isTokenTopPadded(openBrace),
160
- blockHasBottomPadding = isTokenBottomPadded(closeBrace);
168
+ lastBlockToken = getLastBlockToken(closeBrace),
169
+ tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, { includeComments: true }),
170
+ blockHasTopPadding = isPaddingBetweenTokens(tokenBeforeFirst, firstBlockToken),
171
+ blockHasBottomPadding = isPaddingBetweenTokens(lastBlockToken, tokenAfterLast);
161
172
 
162
173
  if (requirePaddingFor(node)) {
163
174
  if (!blockHasTopPadding) {
164
175
  context.report({
165
176
  node,
166
- loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column },
177
+ loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column },
167
178
  fix(fixer) {
168
- return fixer.insertTextAfter(openBrace, "\n");
179
+ return fixer.insertTextAfter(tokenBeforeFirst, "\n");
169
180
  },
170
181
  message: ALWAYS_MESSAGE
171
182
  });
@@ -173,36 +184,34 @@ module.exports = {
173
184
  if (!blockHasBottomPadding) {
174
185
  context.report({
175
186
  node,
176
- loc: { line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 },
187
+ loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 },
177
188
  fix(fixer) {
178
- return fixer.insertTextBefore(closeBrace, "\n");
189
+ return fixer.insertTextBefore(tokenAfterLast, "\n");
179
190
  },
180
191
  message: ALWAYS_MESSAGE
181
192
  });
182
193
  }
183
194
  } else {
184
195
  if (blockHasTopPadding) {
185
- const nextToken = sourceCode.getTokenOrCommentAfter(openBrace);
186
196
 
187
197
  context.report({
188
198
  node,
189
- loc: { line: openBrace.loc.start.line, column: openBrace.loc.start.column },
199
+ loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column },
190
200
  fix(fixer) {
191
- return fixer.replaceTextRange([openBrace.end, nextToken.start - nextToken.loc.start.column], "\n");
201
+ return fixer.replaceTextRange([tokenBeforeFirst.end, firstBlockToken.start - firstBlockToken.loc.start.column], "\n");
192
202
  },
193
203
  message: NEVER_MESSAGE
194
204
  });
195
205
  }
196
206
 
197
207
  if (blockHasBottomPadding) {
198
- const previousToken = sourceCode.getTokenOrCommentBefore(closeBrace);
199
208
 
200
209
  context.report({
201
210
  node,
202
- loc: { line: closeBrace.loc.end.line, column: closeBrace.loc.end.column - 1 },
211
+ loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 },
203
212
  message: NEVER_MESSAGE,
204
213
  fix(fixer) {
205
- return fixer.replaceTextRange([previousToken.end, closeBrace.start - closeBrace.loc.start.column], "\n");
214
+ return fixer.replaceTextRange([lastBlockToken.end, tokenAfterLast.start - tokenAfterLast.loc.start.column], "\n");
206
215
  }
207
216
  });
208
217
  }
@@ -89,7 +89,7 @@ module.exports = {
89
89
  * @returns {void}
90
90
  */
91
91
  function report(reportNode, type) {
92
- context.report({ node: reportNode, message: `Use ${type} destructuring` });
92
+ context.report({ node: reportNode, message: "Use {{type}} destructuring.", data: { type } });
93
93
  }
94
94
 
95
95
  /**
@@ -108,7 +108,7 @@ module.exports = {
108
108
  return null;
109
109
  }
110
110
 
111
- const propertyDot = sourceCode.getTokensBetween(applied, node.callee.property).find(token => token.value === ".");
111
+ const propertyDot = sourceCode.getFirstTokenBetween(applied, node.callee.property, token => token.value === ".");
112
112
 
113
113
  return fixer.replaceTextRange([propertyDot.range[0], node.range[1]], `(...${sourceCode.getText(node.arguments[1])})`);
114
114
  }
@@ -157,7 +157,7 @@ module.exports = {
157
157
  }
158
158
 
159
159
  if (isConcatenation(currentNode) && hasStringLiteral(currentNode) && hasNonStringLiteral(currentNode)) {
160
- const plusSign = sourceCode.getTokensBetween(currentNode.left, currentNode.right).find(token => token.value === "+");
160
+ const plusSign = sourceCode.getFirstTokenBetween(currentNode.left, currentNode.right, token => token.value === "+");
161
161
  const textBeforePlus = getTextBetween(currentNode.left, plusSign);
162
162
  const textAfterPlus = getTextBetween(plusSign, currentNode.right);
163
163
  const leftEndsWithCurly = endsWithTemplateCurly(currentNode.left);
@@ -33,6 +33,9 @@ const QUOTE_SETTINGS = {
33
33
  }
34
34
  };
35
35
 
36
+ // An unescaped newline is a newline preceded by an even number of backslashes.
37
+ const UNESCAPED_LINEBREAK_PATTERN = new RegExp(String.raw`(^|[^\\])(\\\\)*[${Array.from(astUtils.LINEBREAKS).join("")}]`);
38
+
36
39
  /**
37
40
  * Switches quoting of javascript string between ' " and `
38
41
  * escaping and unescaping as necessary.
@@ -254,15 +257,16 @@ module.exports = {
254
257
  TemplateLiteral(node) {
255
258
 
256
259
  // If backticks are expected or it's a tagged template, then this shouldn't throw an errors
257
- if (allowTemplateLiterals || quoteOption === "backtick" || node.parent.type === "TaggedTemplateExpression") {
260
+ if (
261
+ allowTemplateLiterals ||
262
+ quoteOption === "backtick" ||
263
+ node.parent.type === "TaggedTemplateExpression" && node === node.parent.quasi
264
+ ) {
258
265
  return;
259
266
  }
260
267
 
261
- /*
262
- * A warning should be produced if the template literal only has one TemplateElement, and has no unescaped newlines.
263
- * An unescaped newline is a newline preceded by an even number of backslashes.
264
- */
265
- const shouldWarn = node.quasis.length === 1 && !/(^|[^\\])(\\\\)*[\r\n\u2028\u2029]/.test(node.quasis[0].value.raw);
268
+ // A warning should be produced if the template literal only has one TemplateElement, and has no unescaped newlines.
269
+ const shouldWarn = node.quasis.length === 1 && !UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw);
266
270
 
267
271
  if (shouldWarn) {
268
272
  context.report({
@@ -206,6 +206,10 @@ module.exports = {
206
206
  DebuggerStatement: checkNode,
207
207
  ReturnStatement: checkNode,
208
208
  ThrowStatement: checkNode,
209
+ ImportDeclaration: checkNode,
210
+ ExportNamedDeclaration: checkNode,
211
+ ExportAllDeclaration: checkNode,
212
+ ExportDefaultDeclaration: checkNode,
209
213
  ForStatement(node) {
210
214
  if (node.init) {
211
215
  checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node);
package/lib/rules/semi.js CHANGED
@@ -4,6 +4,12 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
+ //------------------------------------------------------------------------------
8
+ // Requirements
9
+ //------------------------------------------------------------------------------
10
+
11
+ const FixTracker = require("../util/fix-tracker");
12
+
7
13
  //------------------------------------------------------------------------------
8
14
  // Rule Definition
9
15
  //------------------------------------------------------------------------------
@@ -85,7 +91,13 @@ module.exports = {
85
91
  message = "Extra semicolon.";
86
92
  loc = loc.start;
87
93
  fix = function(fixer) {
88
- return fixer.remove(lastToken);
94
+
95
+ // Expand the replacement range to include the surrounding
96
+ // tokens to avoid conflicting with no-extra-semi.
97
+ // https://github.com/eslint/eslint/issues/7928
98
+ return new FixTracker(fixer, sourceCode)
99
+ .retainSurroundingTokens(lastToken)
100
+ .remove(lastToken);
89
101
  };
90
102
  }
91
103
 
@@ -4,6 +4,12 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
+ //------------------------------------------------------------------------------
8
+ // Requirements
9
+ //------------------------------------------------------------------------------
10
+
11
+ const astUtils = require("../ast-utils");
12
+
7
13
  //------------------------------------------------------------------------------
8
14
  // Rule Definition
9
15
  //------------------------------------------------------------------------------
@@ -104,7 +110,7 @@ module.exports = {
104
110
  const isAnonymousGenerator = node.generator && !isNamed;
105
111
  const isNormalArrow = isArrow && !node.async;
106
112
  const isArrowWithoutParens = isArrow && sourceCode.getFirstToken(node, 1).value !== "(";
107
- let forbidSpacing, requireSpacing, rightToken;
113
+ let forbidSpacing, requireSpacing;
108
114
 
109
115
  // isAnonymousGenerator → `generator-star-spacing` should warn it. E.g. `function* () {}`
110
116
  // isNormalArrow → ignore always.
@@ -124,10 +130,7 @@ module.exports = {
124
130
  requireSpacing = requireAnonymousFunctionSpacing;
125
131
  }
126
132
 
127
- rightToken = sourceCode.getFirstToken(node);
128
- while (rightToken.value !== "(") {
129
- rightToken = sourceCode.getTokenAfter(rightToken);
130
- }
133
+ const rightToken = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken);
131
134
  const leftToken = sourceCode.getTokenBefore(rightToken);
132
135
  const location = leftToken.loc.end;
133
136
 
@@ -68,6 +68,21 @@ module.exports = {
68
68
  return node.argument && node.argument.type && node.argument.type === "ObjectExpression";
69
69
  }
70
70
 
71
+ /**
72
+ * Check if it is safe to remove the spaces between the two tokens in
73
+ * the context of a non-word prefix unary operator. For example, `+ +1`
74
+ * cannot safely be changed to `++1`.
75
+ * @param {Token} firstToken The operator for a non-word prefix unary operator
76
+ * @param {Token} secondToken The first token of its operand
77
+ * @returns {boolean} Whether or not the spacing between the tokens can be removed
78
+ */
79
+ function canRemoveSpacesBetween(firstToken, secondToken) {
80
+ return !(
81
+ (firstToken.value === "+" && secondToken.value[0] === "+") ||
82
+ (firstToken.value === "-" && secondToken.value[0] === "-")
83
+ );
84
+ }
85
+
71
86
  /**
72
87
  * Checks if an override exists for a given operator.
73
88
  * @param {ASTnode} node AST node
@@ -244,7 +259,10 @@ module.exports = {
244
259
  operator: firstToken.value
245
260
  },
246
261
  fix(fixer) {
247
- return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
262
+ if (canRemoveSpacesBetween(firstToken, secondToken)) {
263
+ return fixer.removeRange([firstToken.range[1], secondToken.range[0]]);
264
+ }
265
+ return null;
248
266
  }
249
267
  });
250
268
  }
@@ -5,6 +5,7 @@
5
5
  "use strict";
6
6
 
7
7
  const lodash = require("lodash");
8
+ const astUtils = require("../ast-utils");
8
9
 
9
10
  //------------------------------------------------------------------------------
10
11
  // Helpers
@@ -88,8 +89,7 @@ function createExceptionsPattern(exceptions) {
88
89
  pattern += exceptions.map(escapeAndRepeat).join("|");
89
90
  pattern += ")";
90
91
  }
91
-
92
- pattern += "(?:$|[\n\r]))";
92
+ pattern += `(?:$|[${Array.from(astUtils.LINEBREAKS).join("")}]))`;
93
93
  }
94
94
 
95
95
  return pattern;
@@ -9,6 +9,8 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
+ const astUtils = require("../ast-utils");
13
+
12
14
  //------------------------------------------------------------------------------
13
15
  // Helpers
14
16
  //------------------------------------------------------------------------------
@@ -23,7 +25,7 @@ const messages = {
23
25
  implied: "'use strict' is unnecessary when implied strict mode is enabled.",
24
26
  unnecessaryInClasses: "'use strict' is unnecessary inside of classes.",
25
27
  nonSimpleParameterList: "'use strict' directive inside a function with non-simple parameter list throws a syntax error since ES2016.",
26
- wrap: "Wrap this function in a function with 'use strict' directive."
28
+ wrap: "Wrap {{name}} in a function with 'use strict' directive."
27
29
  };
28
30
 
29
31
  /**
@@ -188,7 +190,11 @@ module.exports = {
188
190
  if (isSimpleParameterList(node.params)) {
189
191
  context.report({ node, message: messages.function });
190
192
  } else {
191
- context.report({ node, message: messages.wrap });
193
+ context.report({
194
+ node,
195
+ message: messages.wrap,
196
+ data: { name: astUtils.getFunctionNameWithKind(node) }
197
+ });
192
198
  }
193
199
  }
194
200
 
@@ -212,8 +218,8 @@ module.exports = {
212
218
  */
213
219
  function enterFunction(node) {
214
220
  const isBlock = node.body.type === "BlockStatement",
215
- useStrictDirectives = isBlock ?
216
- getUseStrictDirectives(node.body.body) : [];
221
+ useStrictDirectives = isBlock
222
+ ? getUseStrictDirectives(node.body.body) : [];
217
223
 
218
224
  if (mode === "function") {
219
225
  enterFunctionInFunctionMode(node, useStrictDirectives);
@@ -45,7 +45,7 @@ module.exports = {
45
45
  loc: location,
46
46
  message: "Expected Unicode BOM (Byte Order Mark).",
47
47
  fix(fixer) {
48
- return fixer.insertTextBefore(node, "\uFEFF");
48
+ return fixer.insertTextBeforeRange([0, 1], "\uFEFF");
49
49
  }
50
50
  });
51
51
  } else if (sourceCode.hasBOM && (requireBOM === "never")) {
@@ -5,6 +5,10 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
8
12
  const astUtils = require("../ast-utils");
9
13
 
10
14
  //------------------------------------------------------------------------------
@@ -51,11 +55,7 @@ module.exports = {
51
55
  * @private
52
56
  */
53
57
  function wrapped(node) {
54
- const previousToken = sourceCode.getTokenBefore(node),
55
- nextToken = sourceCode.getTokenAfter(node);
56
-
57
- return previousToken && previousToken.value === "(" &&
58
- nextToken && nextToken.value === ")";
58
+ return astUtils.isParenthesised(sourceCode, node);
59
59
  }
60
60
 
61
61
  /**
package/lib/rules/yoda.js CHANGED
@@ -235,12 +235,7 @@ module.exports = {
235
235
  * paren token.
236
236
  */
237
237
  function isParenWrapped() {
238
- let tokenBefore, tokenAfter;
239
-
240
- return ((tokenBefore = sourceCode.getTokenBefore(node)) &&
241
- tokenBefore.value === "(" &&
242
- (tokenAfter = sourceCode.getTokenAfter(node)) &&
243
- tokenAfter.value === ")");
238
+ return astUtils.isParenthesised(sourceCode, node);
244
239
  }
245
240
 
246
241
  return (node.type === "LogicalExpression" &&
@@ -269,11 +264,11 @@ module.exports = {
269
264
  * @returns {string} A string representation of the node with the sides and operator flipped
270
265
  */
271
266
  function getFlippedString(node) {
272
- const operatorToken = sourceCode.getTokensBetween(node.left, node.right).find(token => token.value === node.operator);
267
+ const operatorToken = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
273
268
  const textBeforeOperator = sourceCode.getText().slice(sourceCode.getTokenBefore(operatorToken).range[1], operatorToken.range[0]);
274
269
  const textAfterOperator = sourceCode.getText().slice(operatorToken.range[1], sourceCode.getTokenAfter(operatorToken).range[0]);
275
- const leftText = sourceCode.getText().slice(sourceCode.getFirstToken(node).range[0], sourceCode.getTokenBefore(operatorToken).range[1]);
276
- const rightText = sourceCode.getText().slice(sourceCode.getTokenAfter(operatorToken).range[0], sourceCode.getLastToken(node).range[1]);
270
+ const leftText = sourceCode.getText().slice(node.range[0], sourceCode.getTokenBefore(operatorToken).range[1]);
271
+ const rightText = sourceCode.getText().slice(sourceCode.getTokenAfter(operatorToken).range[0], node.range[1]);
277
272
 
278
273
  return rightText + textBeforeOperator + OPERATOR_FLIP_MAP[operatorToken.value] + textAfterOperator + leftText;
279
274
  }
@@ -147,6 +147,12 @@ function RuleTester(testerConfig) {
147
147
  lodash.cloneDeep(defaultConfig),
148
148
  testerConfig
149
149
  );
150
+
151
+ /**
152
+ * Rule definitions to define before tests.
153
+ * @type {Object}
154
+ */
155
+ this.rules = {};
150
156
  }
151
157
 
152
158
  /**
@@ -239,7 +245,7 @@ RuleTester.prototype = {
239
245
  * @returns {void}
240
246
  */
241
247
  defineRule(name, rule) {
242
- eslint.defineRule(name, rule);
248
+ this.rules[name] = rule;
243
249
  },
244
250
 
245
251
  /**
@@ -419,6 +425,28 @@ RuleTester.prototype = {
419
425
  assertASTDidntChange(result.beforeAST, result.afterAST);
420
426
  }
421
427
 
428
+ /**
429
+ * Asserts that the message matches its expected value. If the expected
430
+ * value is a regular expression, it is checked against the actual
431
+ * value.
432
+ * @param {string} actual Actual value
433
+ * @param {string|RegExp} expected Expected value
434
+ * @returns {void}
435
+ * @private
436
+ */
437
+ function assertMessageMatches(actual, expected) {
438
+ if (expected instanceof RegExp) {
439
+
440
+ // assert.js doesn't have a built-in RegExp match function
441
+ assert.ok(
442
+ expected.test(actual),
443
+ `Expected '${actual}' to match ${expected}`
444
+ );
445
+ } else {
446
+ assert.equal(actual, expected);
447
+ }
448
+ }
449
+
422
450
  /**
423
451
  * Check if the template is invalid or not
424
452
  * all invalid cases go through this.
@@ -448,10 +476,10 @@ RuleTester.prototype = {
448
476
  assert.ok(!("fatal" in messages[i]), `A fatal parsing error occurred: ${messages[i].message}`);
449
477
  assert.equal(messages[i].ruleId, ruleName, "Error rule name should be the same as the name of the rule being tested");
450
478
 
451
- if (typeof item.errors[i] === "string") {
479
+ if (typeof item.errors[i] === "string" || item.errors[i] instanceof RegExp) {
452
480
 
453
481
  // Just an error message.
454
- assert.equal(messages[i].message, item.errors[i]);
482
+ assertMessageMatches(messages[i].message, item.errors[i]);
455
483
  } else if (typeof item.errors[i] === "object") {
456
484
 
457
485
  /*
@@ -460,7 +488,7 @@ RuleTester.prototype = {
460
488
  * column.
461
489
  */
462
490
  if (item.errors[i].message) {
463
- assert.equal(messages[i].message, item.errors[i].message);
491
+ assertMessageMatches(messages[i].message, item.errors[i].message);
464
492
  }
465
493
 
466
494
  if (item.errors[i].type) {
@@ -484,17 +512,24 @@ RuleTester.prototype = {
484
512
  }
485
513
  } else {
486
514
 
487
- // Only string or object errors are valid.
488
- assert.fail(messages[i], null, "Error should be a string or object.");
515
+ // Message was an unexpected type
516
+ assert.fail(messages[i], null, "Error should be a string, object, or RegExp.");
489
517
  }
490
518
  }
519
+ }
491
520
 
492
- if (item.hasOwnProperty("output")) {
521
+ if (item.hasOwnProperty("output")) {
522
+ if (item.output === null) {
523
+ assert.strictEqual(
524
+ messages.filter(message => message.fix).length,
525
+ 0,
526
+ "Expected no autofixes to be suggested"
527
+ );
528
+ } else {
493
529
  const fixResult = SourceCodeFixer.applyFixes(eslint.getSourceCode(), messages);
494
530
 
495
531
  assert.equal(fixResult.output, item.output, "Output is incorrect.");
496
532
  }
497
-
498
533
  }
499
534
 
500
535
  assertASTDidntChange(result.beforeAST, result.afterAST);
@@ -507,7 +542,8 @@ RuleTester.prototype = {
507
542
  RuleTester.describe(ruleName, () => {
508
543
  RuleTester.describe("valid", () => {
509
544
  test.valid.forEach(valid => {
510
- RuleTester.it(valid.code || valid, () => {
545
+ RuleTester.it(typeof valid === "object" ? valid.code : valid, () => {
546
+ eslint.defineRules(this.rules);
511
547
  testValidTemplate(ruleName, valid);
512
548
  });
513
549
  });
@@ -516,6 +552,7 @@ RuleTester.prototype = {
516
552
  RuleTester.describe("invalid", () => {
517
553
  test.invalid.forEach(invalid => {
518
554
  RuleTester.it(invalid.code, () => {
555
+ eslint.defineRules(this.rules);
519
556
  testInvalidTemplate(ruleName, invalid);
520
557
  });
521
558
  });