eslint 4.7.1 → 4.10.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +34 -19
  3. package/conf/default-cli-options.js +2 -1
  4. package/conf/eslint-recommended.js +2 -0
  5. package/lib/ast-utils.js +2 -1
  6. package/lib/cli-engine.js +26 -5
  7. package/lib/cli.js +17 -9
  8. package/lib/code-path-analysis/code-path-segment.js +39 -39
  9. package/lib/code-path-analysis/code-path-state.js +3 -0
  10. package/lib/formatters/html-template-message.html +1 -1
  11. package/lib/formatters/html-template-page.html +3 -1
  12. package/lib/formatters/html.js +2 -1
  13. package/lib/ignored-paths.js +1 -1
  14. package/lib/linter.js +43 -71
  15. package/lib/logging.js +2 -2
  16. package/lib/options.js +12 -0
  17. package/lib/rules/array-bracket-newline.js +19 -5
  18. package/lib/rules/block-spacing.js +1 -1
  19. package/lib/rules/callback-return.js +2 -1
  20. package/lib/rules/capitalized-comments.js +2 -1
  21. package/lib/rules/comma-style.js +3 -1
  22. package/lib/rules/dot-notation.js +56 -35
  23. package/lib/rules/generator-star-spacing.js +3 -3
  24. package/lib/rules/indent-legacy.js +5 -2
  25. package/lib/rules/indent.js +25 -19
  26. package/lib/rules/lines-around-comment.js +33 -4
  27. package/lib/rules/lines-between-class-members.js +91 -0
  28. package/lib/rules/max-len.js +2 -3
  29. package/lib/rules/multiline-comment-style.js +294 -0
  30. package/lib/rules/new-cap.js +2 -1
  31. package/lib/rules/newline-before-return.js +4 -2
  32. package/lib/rules/no-alert.js +7 -15
  33. package/lib/rules/no-catch-shadow.js +1 -1
  34. package/lib/rules/no-constant-condition.js +2 -2
  35. package/lib/rules/no-control-regex.js +2 -1
  36. package/lib/rules/no-else-return.js +43 -8
  37. package/lib/rules/no-extra-parens.js +6 -3
  38. package/lib/rules/no-lonely-if.js +2 -1
  39. package/lib/rules/no-loop-func.js +2 -3
  40. package/lib/rules/no-mixed-requires.js +8 -4
  41. package/lib/rules/no-restricted-imports.js +86 -17
  42. package/lib/rules/no-restricted-modules.js +84 -15
  43. package/lib/rules/no-trailing-spaces.js +1 -1
  44. package/lib/rules/no-unneeded-ternary.js +3 -1
  45. package/lib/rules/no-unused-labels.js +2 -1
  46. package/lib/rules/no-useless-computed-key.js +2 -1
  47. package/lib/rules/no-useless-escape.js +8 -1
  48. package/lib/rules/no-var.js +11 -0
  49. package/lib/rules/object-shorthand.js +6 -2
  50. package/lib/rules/operator-linebreak.js +3 -1
  51. package/lib/rules/padding-line-between-statements.js +2 -2
  52. package/lib/rules/require-jsdoc.js +11 -18
  53. package/lib/rules/sort-imports.js +6 -3
  54. package/lib/rules/space-unary-ops.js +6 -8
  55. package/lib/rules/valid-jsdoc.js +39 -33
  56. package/lib/testers/rule-tester.js +20 -6
  57. package/lib/util/apply-disable-directives.js +56 -27
  58. package/lib/util/node-event-generator.js +6 -20
  59. package/lib/util/safe-emitter.js +54 -0
  60. package/lib/util/source-code.js +73 -67
  61. package/messages/no-config-found.txt +1 -1
  62. package/package.json +3 -4
  63. package/lib/internal-rules/.eslintrc.yml +0 -3
  64. package/lib/internal-rules/internal-consistent-docs-description.js +0 -130
  65. package/lib/internal-rules/internal-no-invalid-meta.js +0 -188
@@ -235,10 +235,12 @@ class OffsetStorage {
235
235
  /**
236
236
  * @param {TokenInfo} tokenInfo a TokenInfo instance
237
237
  * @param {number} indentSize The desired size of each indentation level
238
+ * @param {string} indentType The indentation character
238
239
  */
239
- constructor(tokenInfo, indentSize) {
240
+ constructor(tokenInfo, indentSize, indentType) {
240
241
  this._tokenInfo = tokenInfo;
241
242
  this._indentSize = indentSize;
243
+ this._indentType = indentType;
242
244
 
243
245
  this._tree = new BinarySearchTree();
244
246
  this._tree.insert(0, { offset: 0, from: null, force: false });
@@ -408,7 +410,7 @@ class OffsetStorage {
408
410
  /**
409
411
  * Gets the desired indent of a token
410
412
  * @param {Token} token The token
411
- * @returns {number} The desired indent of the token
413
+ * @returns {string} The desired indent of the token
412
414
  */
413
415
  getDesiredIndent(token) {
414
416
  if (!this._desiredIndentCache.has(token)) {
@@ -417,7 +419,10 @@ class OffsetStorage {
417
419
 
418
420
  // If the token is ignored, use the actual indent of the token as the desired indent.
419
421
  // This ensures that no errors are reported for this token.
420
- this._desiredIndentCache.set(token, this._tokenInfo.getTokenIndent(token).length / this._indentSize);
422
+ this._desiredIndentCache.set(
423
+ token,
424
+ this._tokenInfo.getTokenIndent(token)
425
+ );
421
426
  } else if (this._lockedFirstTokens.has(token)) {
422
427
  const firstToken = this._lockedFirstTokens.get(token);
423
428
 
@@ -428,7 +433,7 @@ class OffsetStorage {
428
433
  this.getDesiredIndent(this._tokenInfo.getFirstTokenOfLine(firstToken)) +
429
434
 
430
435
  // (space between the start of the first element's line and the first element)
431
- (firstToken.loc.start.column - this._tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column) / this._indentSize
436
+ this._indentType.repeat(firstToken.loc.start.column - this._tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column)
432
437
  );
433
438
  } else {
434
439
  const offsetInfo = this._getOffsetDescriptor(token);
@@ -436,9 +441,12 @@ class OffsetStorage {
436
441
  offsetInfo.from &&
437
442
  offsetInfo.from.loc.start.line === token.loc.start.line &&
438
443
  !offsetInfo.force
439
- ) ? 0 : offsetInfo.offset;
444
+ ) ? 0 : offsetInfo.offset * this._indentSize;
440
445
 
441
- this._desiredIndentCache.set(token, offset + (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : 0));
446
+ this._desiredIndentCache.set(
447
+ token,
448
+ (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : "") + this._indentType.repeat(offset)
449
+ );
442
450
  }
443
451
  }
444
452
  return this._desiredIndentCache.get(token);
@@ -655,7 +663,7 @@ module.exports = {
655
663
 
656
664
  const sourceCode = context.getSourceCode();
657
665
  const tokenInfo = new TokenInfo(sourceCode);
658
- const offsets = new OffsetStorage(tokenInfo, indentSize);
666
+ const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t");
659
667
  const parameterParens = new WeakSet();
660
668
 
661
669
  /**
@@ -688,27 +696,24 @@ module.exports = {
688
696
  /**
689
697
  * Reports a given indent violation
690
698
  * @param {Token} token Token violating the indent rule
691
- * @param {int} neededIndentLevel Expected indentation level
692
- * @param {int} gottenSpaces Actual number of indentation spaces for the token
693
- * @param {int} gottenTabs Actual number of indentation tabs for the token
699
+ * @param {string} neededIndent Expected indentation string
694
700
  * @returns {void}
695
701
  */
696
- function report(token, neededIndentLevel) {
702
+ function report(token, neededIndent) {
697
703
  const actualIndent = Array.from(tokenInfo.getTokenIndent(token));
698
704
  const numSpaces = actualIndent.filter(char => char === " ").length;
699
705
  const numTabs = actualIndent.filter(char => char === "\t").length;
700
- const neededChars = neededIndentLevel * indentSize;
701
706
 
702
707
  context.report({
703
708
  node: token,
704
- message: createErrorMessage(neededChars, numSpaces, numTabs),
709
+ message: createErrorMessage(neededIndent.length, numSpaces, numTabs),
705
710
  loc: {
706
711
  start: { line: token.loc.start.line, column: 0 },
707
712
  end: { line: token.loc.start.line, column: token.loc.start.column }
708
713
  },
709
714
  fix(fixer) {
710
715
  const range = [token.range[0] - token.loc.start.column, token.range[0]];
711
- const newText = (indentType === "space" ? " " : "\t").repeat(neededChars);
716
+ const newText = neededIndent;
712
717
 
713
718
  return fixer.replaceTextRange(range, newText);
714
719
  }
@@ -718,14 +723,13 @@ module.exports = {
718
723
  /**
719
724
  * Checks if a token's indentation is correct
720
725
  * @param {Token} token Token to examine
721
- * @param {int} desiredIndentLevel needed indent level
726
+ * @param {string} desiredIndent Desired indentation of the string
722
727
  * @returns {boolean} `true` if the token's indentation is correct
723
728
  */
724
- function validateTokenIndent(token, desiredIndentLevel) {
729
+ function validateTokenIndent(token, desiredIndent) {
725
730
  const indentation = tokenInfo.getTokenIndent(token);
726
- const expectedChar = indentType === "space" ? " " : "\t";
727
731
 
728
- return indentation === expectedChar.repeat(desiredIndentLevel * indentSize) ||
732
+ return indentation === desiredIndent ||
729
733
 
730
734
  // To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs.
731
735
  indentation.includes(" ") && indentation.includes("\t");
@@ -1241,7 +1245,9 @@ module.exports = {
1241
1245
  NewExpression(node) {
1242
1246
 
1243
1247
  // Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo`
1244
- if (node.arguments.length > 0 || astUtils.isClosingParenToken(sourceCode.getLastToken(node)) && astUtils.isOpeningParenToken(sourceCode.getLastToken(node, 1))) {
1248
+ if (node.arguments.length > 0 ||
1249
+ astUtils.isClosingParenToken(sourceCode.getLastToken(node)) &&
1250
+ astUtils.isOpeningParenToken(sourceCode.getLastToken(node, 1))) {
1245
1251
  addFunctionCallIndent(node);
1246
1252
  }
1247
1253
  },
@@ -82,6 +82,12 @@ module.exports = {
82
82
  allowBlockEnd: {
83
83
  type: "boolean"
84
84
  },
85
+ allowClassStart: {
86
+ type: "boolean"
87
+ },
88
+ allowClassEnd: {
89
+ type: "boolean"
90
+ },
85
91
  allowObjectStart: {
86
92
  type: "boolean"
87
93
  },
@@ -224,6 +230,24 @@ module.exports = {
224
230
  return isCommentAtParentEnd(token, "ClassBody") || isCommentAtParentEnd(token, "BlockStatement") || isCommentAtParentEnd(token, "SwitchCase") || isCommentAtParentEnd(token, "SwitchStatement");
225
231
  }
226
232
 
233
+ /**
234
+ * Returns whether or not comments are at the class start or not.
235
+ * @param {token} token The Comment token.
236
+ * @returns {boolean} True if the comment is at class start.
237
+ */
238
+ function isCommentAtClassStart(token) {
239
+ return isCommentAtParentStart(token, "ClassBody");
240
+ }
241
+
242
+ /**
243
+ * Returns whether or not comments are at the class end or not.
244
+ * @param {token} token The Comment token.
245
+ * @returns {boolean} True if the comment is at class end.
246
+ */
247
+ function isCommentAtClassEnd(token) {
248
+ return isCommentAtParentEnd(token, "ClassBody");
249
+ }
250
+
227
251
  /**
228
252
  * Returns whether or not comments are at the object start or not.
229
253
  * @param {token} token The Comment token.
@@ -284,15 +308,20 @@ module.exports = {
284
308
  nextLineNum = token.loc.end.line + 1,
285
309
  commentIsNotAlone = codeAroundComment(token);
286
310
 
287
- const blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(token),
288
- blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token),
311
+ const blockStartAllowed = options.allowBlockStart &&
312
+ isCommentAtBlockStart(token) &&
313
+ !(options.allowClassStart === false &&
314
+ isCommentAtClassStart(token)),
315
+ blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token) && !(options.allowClassEnd === false && isCommentAtClassEnd(token)),
316
+ classStartAllowed = options.allowClassStart && isCommentAtClassStart(token),
317
+ classEndAllowed = options.allowClassEnd && isCommentAtClassEnd(token),
289
318
  objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(token),
290
319
  objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(token),
291
320
  arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(token),
292
321
  arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(token);
293
322
 
294
- const exceptionStartAllowed = blockStartAllowed || objectStartAllowed || arrayStartAllowed;
295
- const exceptionEndAllowed = blockEndAllowed || objectEndAllowed || arrayEndAllowed;
323
+ const exceptionStartAllowed = blockStartAllowed || classStartAllowed || objectStartAllowed || arrayStartAllowed;
324
+ const exceptionEndAllowed = blockEndAllowed || classEndAllowed || objectEndAllowed || arrayEndAllowed;
296
325
 
297
326
  // ignore top of the file and bottom of the file
298
327
  if (prevLineNum < 1) {
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @fileoverview Rule to check empty newline between class members
3
+ * @author 薛定谔的猫<hh_2013@foxmail.com>
4
+ */
5
+ "use strict";
6
+
7
+ const astUtils = require("../ast-utils");
8
+
9
+ //------------------------------------------------------------------------------
10
+ // Rule Definition
11
+ //------------------------------------------------------------------------------
12
+
13
+ module.exports = {
14
+ meta: {
15
+ docs: {
16
+ description: "require or disallow an empty line between class members",
17
+ category: "Stylistic Issues",
18
+ recommended: false
19
+ },
20
+
21
+ fixable: "whitespace",
22
+
23
+ schema: [
24
+ {
25
+ enum: ["always", "never"]
26
+ },
27
+ {
28
+ type: "object",
29
+ properties: {
30
+ exceptAfterSingleLine: {
31
+ type: "boolean"
32
+ }
33
+ },
34
+ additionalProperties: false
35
+ }
36
+ ]
37
+ },
38
+
39
+ create(context) {
40
+
41
+ const options = [];
42
+
43
+ options[0] = context.options[0] || "always";
44
+ options[1] = context.options[1] || { exceptAfterSingleLine: false };
45
+
46
+ const ALWAYS_MESSAGE = "Expected blank line between class members.";
47
+ const NEVER_MESSAGE = "Unexpected blank line between class members.";
48
+
49
+ const sourceCode = context.getSourceCode();
50
+
51
+ /**
52
+ * Checks if there is padding between two tokens
53
+ * @param {Token} first The first token
54
+ * @param {Token} second The second token
55
+ * @returns {boolean} True if there is at least a line between the tokens
56
+ */
57
+ function isPaddingBetweenTokens(first, second) {
58
+ return second.loc.start.line - first.loc.end.line >= 2;
59
+ }
60
+
61
+ return {
62
+ ClassBody(node) {
63
+ const body = node.body;
64
+
65
+ for (let i = 0; i < body.length - 1; i++) {
66
+ const curFirst = sourceCode.getFirstToken(body[i]);
67
+ const curLast = sourceCode.getLastToken(body[i]);
68
+ const comments = sourceCode.getCommentsBefore(body[i + 1]);
69
+ const nextFirst = comments.length ? comments[0] : sourceCode.getFirstToken(body[i + 1]);
70
+ const isPadded = isPaddingBetweenTokens(curLast, nextFirst);
71
+ const isMulti = !astUtils.isTokenOnSameLine(curFirst, curLast);
72
+ const skip = !isMulti && options[1].exceptAfterSingleLine;
73
+
74
+
75
+ if ((options[0] === "always" && !skip && !isPadded) ||
76
+ (options[0] === "never" && isPadded)) {
77
+ context.report({
78
+ node: body[i + 1],
79
+ message: isPadded ? NEVER_MESSAGE : ALWAYS_MESSAGE,
80
+ fix(fixer) {
81
+ return isPadded
82
+ ? fixer.replaceTextRange([curLast.range[1], nextFirst.range[0]], "\n")
83
+ : fixer.insertTextAfter(curLast, "\n");
84
+ }
85
+ });
86
+ }
87
+ }
88
+ }
89
+ };
90
+ }
91
+ };
@@ -181,11 +181,10 @@ module.exports = {
181
181
  * Gets the line after the comment and any remaining trailing whitespace is
182
182
  * stripped.
183
183
  * @param {string} line The source line with a trailing comment
184
- * @param {number} lineNumber The one-indexed line number this is on
185
184
  * @param {ASTNode} comment The comment to remove
186
185
  * @returns {string} Line without comment and trailing whitepace
187
186
  */
188
- function stripTrailingComment(line, lineNumber, comment) {
187
+ function stripTrailingComment(line, comment) {
189
188
 
190
189
  // loc.column is zero-indexed
191
190
  return line.slice(0, comment.loc.start.column).replace(/\s+$/, "");
@@ -306,7 +305,7 @@ module.exports = {
306
305
  if (isFullLineComment(line, lineNumber, comment)) {
307
306
  lineIsComment = true;
308
307
  } else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) {
309
- line = stripTrailingComment(line, lineNumber, comment);
308
+ line = stripTrailingComment(line, comment);
310
309
  }
311
310
  }
312
311
  if (ignorePattern && ignorePattern.test(line) ||
@@ -0,0 +1,294 @@
1
+ /**
2
+ * @fileoverview enforce a particular style for multiline comments
3
+ * @author Teddy Katz
4
+ */
5
+ "use strict";
6
+
7
+ const astUtils = require("../ast-utils");
8
+
9
+ //------------------------------------------------------------------------------
10
+ // Rule Definition
11
+ //------------------------------------------------------------------------------
12
+
13
+ module.exports = {
14
+ meta: {
15
+ docs: {
16
+ description: "enforce a particular style for multiline comments",
17
+ category: "Stylistic Issues",
18
+ recommended: false
19
+ },
20
+ fixable: "whitespace",
21
+ schema: [{ enum: ["starred-block", "separate-lines", "bare-block"] }]
22
+ },
23
+
24
+ create(context) {
25
+ const sourceCode = context.getSourceCode();
26
+ const option = context.options[0] || "starred-block";
27
+
28
+ const EXPECTED_BLOCK_ERROR = "Expected a block comment instead of consecutive line comments.";
29
+ const START_NEWLINE_ERROR = "Expected a linebreak after '/*'.";
30
+ const END_NEWLINE_ERROR = "Expected a linebreak before '*/'.";
31
+ const MISSING_STAR_ERROR = "Expected a '*' at the start of this line.";
32
+ const ALIGNMENT_ERROR = "Expected this line to be aligned with the start of the comment.";
33
+ const EXPECTED_LINES_ERROR = "Expected multiple line comments instead of a block comment.";
34
+
35
+ //----------------------------------------------------------------------
36
+ // Helpers
37
+ //----------------------------------------------------------------------
38
+
39
+ /**
40
+ * Gets a list of comment lines in a group
41
+ * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment
42
+ * @returns {string[]} A list of comment lines
43
+ */
44
+ function getCommentLines(commentGroup) {
45
+ if (commentGroup[0].type === "Line") {
46
+ return commentGroup.map(comment => comment.value);
47
+ }
48
+ return commentGroup[0].value
49
+ .split(astUtils.LINEBREAK_MATCHER)
50
+ .map(line => line.replace(/^\s*\*?/, ""));
51
+ }
52
+
53
+ /**
54
+ * Converts a comment into starred-block form
55
+ * @param {Token} firstComment The first comment of the group being converted
56
+ * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment
57
+ * @returns {string} A representation of the comment value in starred-block form, excluding start and end markers
58
+ */
59
+ function convertToStarredBlock(firstComment, commentLinesList) {
60
+ const initialOffset = sourceCode.text.slice(firstComment.range[0] - firstComment.loc.start.column, firstComment.range[0]);
61
+ const starredLines = commentLinesList.map(line => `${initialOffset} *${line}`);
62
+
63
+ return `\n${starredLines.join("\n")}\n${initialOffset} `;
64
+ }
65
+
66
+ /**
67
+ * Converts a comment into separate-line form
68
+ * @param {Token} firstComment The first comment of the group being converted
69
+ * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment
70
+ * @returns {string} A representation of the comment value in separate-line form
71
+ */
72
+ function convertToSeparateLines(firstComment, commentLinesList) {
73
+ const initialOffset = sourceCode.text.slice(firstComment.range[0] - firstComment.loc.start.column, firstComment.range[0]);
74
+ const separateLines = commentLinesList.map(line => `// ${line.trim()}`);
75
+
76
+ return separateLines.join(`\n${initialOffset}`);
77
+ }
78
+
79
+ /**
80
+ * Converts a comment into bare-block form
81
+ * @param {Token} firstComment The first comment of the group being converted
82
+ * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment
83
+ * @returns {string} A representation of the comment value in bare-block form
84
+ */
85
+ function convertToBlock(firstComment, commentLinesList) {
86
+ const initialOffset = sourceCode.text.slice(firstComment.range[0] - firstComment.loc.start.column, firstComment.range[0]);
87
+ const blockLines = commentLinesList.map(line => line.trim());
88
+
89
+ return `/* ${blockLines.join(`\n${initialOffset} `)} */`;
90
+ }
91
+
92
+ /**
93
+ * Check a comment is JSDoc form
94
+ * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment
95
+ * @returns {boolean} if commentGroup is JSDoc form, return true
96
+ */
97
+ function isJSDoc(commentGroup) {
98
+ const lines = commentGroup[0].value.split(astUtils.LINEBREAK_MATCHER);
99
+
100
+ return commentGroup[0].type === "Block" &&
101
+ /^\*\s*$/.test(lines[0]) &&
102
+ lines.slice(1, -1).every(line => /^\s* /.test(line)) &&
103
+ /^\s*$/.test(lines[lines.length - 1]);
104
+ }
105
+
106
+ /**
107
+ * Each method checks a group of comments to see if it's valid according to the given option.
108
+ * @param {Token[]} commentGroup A list of comments that appear together. This will either contain a single
109
+ * block comment or multiple line comments.
110
+ * @returns {void}
111
+ */
112
+ const commentGroupCheckers = {
113
+ "starred-block"(commentGroup) {
114
+ const commentLines = getCommentLines(commentGroup);
115
+
116
+ if (commentLines.some(value => value.includes("*/"))) {
117
+ return;
118
+ }
119
+
120
+ if (commentGroup.length > 1) {
121
+ context.report({
122
+ loc: {
123
+ start: commentGroup[0].loc.start,
124
+ end: commentGroup[commentGroup.length - 1].loc.end
125
+ },
126
+ message: EXPECTED_BLOCK_ERROR,
127
+ fix(fixer) {
128
+ const range = [commentGroup[0].range[0], commentGroup[commentGroup.length - 1].range[1]];
129
+ const starredBlock = `/*${convertToStarredBlock(commentGroup[0], commentLines)}*/`;
130
+
131
+ return commentLines.some(value => value.startsWith("/"))
132
+ ? null
133
+ : fixer.replaceTextRange(range, starredBlock);
134
+ }
135
+ });
136
+ } else {
137
+ const block = commentGroup[0];
138
+ const lines = block.value.split(astUtils.LINEBREAK_MATCHER);
139
+ const expectedLinePrefix = `${sourceCode.text.slice(block.range[0] - block.loc.start.column, block.range[0])} *`;
140
+
141
+ if (!/^\*?\s*$/.test(lines[0])) {
142
+ const start = block.value.startsWith("*") ? block.range[0] + 1 : block.range[0];
143
+
144
+ context.report({
145
+ loc: {
146
+ start: block.loc.start,
147
+ end: { line: block.loc.start.line, column: block.loc.start.column + 2 }
148
+ },
149
+ message: START_NEWLINE_ERROR,
150
+ fix: fixer => fixer.insertTextAfterRange([start, start + 2], `\n${expectedLinePrefix}`)
151
+ });
152
+ }
153
+
154
+ if (!/^\s*$/.test(lines[lines.length - 1])) {
155
+ context.report({
156
+ loc: {
157
+ start: { line: block.loc.end.line, column: block.loc.end.column - 2 },
158
+ end: block.loc.end
159
+ },
160
+ message: END_NEWLINE_ERROR,
161
+ fix: fixer => fixer.replaceTextRange([block.range[1] - 2, block.range[1]], `\n${expectedLinePrefix}/`)
162
+ });
163
+ }
164
+
165
+ for (let lineNumber = block.loc.start.line + 1; lineNumber <= block.loc.end.line; lineNumber++) {
166
+ const lineText = sourceCode.lines[lineNumber - 1];
167
+
168
+ if (!lineText.startsWith(expectedLinePrefix)) {
169
+ context.report({
170
+ loc: {
171
+ start: { line: lineNumber, column: 0 },
172
+ end: { line: lineNumber, column: sourceCode.lines[lineNumber - 1].length }
173
+ },
174
+ message: /^\s*\*/.test(lineText)
175
+ ? ALIGNMENT_ERROR
176
+ : MISSING_STAR_ERROR,
177
+ fix(fixer) {
178
+ const lineStartIndex = sourceCode.getIndexFromLoc({ line: lineNumber, column: 0 });
179
+ const linePrefixLength = lineText.match(/^\s*\*? ?/)[0].length;
180
+ const commentStartIndex = lineStartIndex + linePrefixLength;
181
+
182
+ const replacementText = lineNumber === block.loc.end.line || lineText.length === linePrefixLength
183
+ ? expectedLinePrefix
184
+ : `${expectedLinePrefix} `;
185
+
186
+ return fixer.replaceTextRange([lineStartIndex, commentStartIndex], replacementText);
187
+ }
188
+ });
189
+ }
190
+ }
191
+ }
192
+ },
193
+ "separate-lines"(commentGroup) {
194
+ if (!isJSDoc(commentGroup) && commentGroup[0].type === "Block") {
195
+ const commentLines = getCommentLines(commentGroup);
196
+ const block = commentGroup[0];
197
+ const tokenAfter = sourceCode.getTokenAfter(block, { includeComments: true });
198
+
199
+ if (tokenAfter && block.loc.end.line === tokenAfter.loc.start.line) {
200
+ return;
201
+ }
202
+
203
+ context.report({
204
+ loc: {
205
+ start: block.loc.start,
206
+ end: { line: block.loc.start.line, column: block.loc.start.column + 2 }
207
+ },
208
+ message: EXPECTED_LINES_ERROR,
209
+ fix(fixer) {
210
+ return fixer.replaceText(block, convertToSeparateLines(block, commentLines.filter(line => line)));
211
+ }
212
+ });
213
+ }
214
+ },
215
+ "bare-block"(commentGroup) {
216
+ if (!isJSDoc(commentGroup)) {
217
+ const commentLines = getCommentLines(commentGroup);
218
+
219
+ // disallows consecutive line comments in favor of using a block comment.
220
+ if (commentGroup[0].type === "Line" && commentLines.length > 1 &&
221
+ !commentLines.some(value => value.includes("*/"))) {
222
+ context.report({
223
+ loc: {
224
+ start: commentGroup[0].loc.start,
225
+ end: commentGroup[commentGroup.length - 1].loc.end
226
+ },
227
+ message: EXPECTED_BLOCK_ERROR,
228
+ fix(fixer) {
229
+ const range = [commentGroup[0].range[0], commentGroup[commentGroup.length - 1].range[1]];
230
+ const block = convertToBlock(commentGroup[0], commentLines.filter(line => line));
231
+
232
+ return fixer.replaceTextRange(range, block);
233
+ }
234
+ });
235
+ }
236
+
237
+ // prohibits block comments from having a * at the beginning of each line.
238
+ if (commentGroup[0].type === "Block") {
239
+ const block = commentGroup[0];
240
+ const lines = block.value.split(astUtils.LINEBREAK_MATCHER).filter(line => line.trim());
241
+
242
+ if (lines.length > 0 && lines.every(line => /^\s*\*/.test(line))) {
243
+ context.report({
244
+ loc: {
245
+ start: block.loc.start,
246
+ end: { line: block.loc.start.line, column: block.loc.start.column + 2 }
247
+ },
248
+ message: EXPECTED_BLOCK_ERROR,
249
+ fix(fixer) {
250
+ return fixer.replaceText(block, convertToBlock(block, commentLines.filter(line => line)));
251
+ }
252
+ });
253
+ }
254
+ }
255
+ }
256
+ }
257
+ };
258
+
259
+ //----------------------------------------------------------------------
260
+ // Public
261
+ //----------------------------------------------------------------------
262
+
263
+ return {
264
+ Program() {
265
+ return sourceCode.getAllComments()
266
+ .filter(comment => comment.type !== "Shebang")
267
+ .filter(comment => !astUtils.COMMENTS_IGNORE_PATTERN.test(comment.value))
268
+ .filter(comment => {
269
+ const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
270
+
271
+ return !tokenBefore || tokenBefore.loc.end.line < comment.loc.start.line;
272
+ })
273
+ .reduce((commentGroups, comment, index, commentList) => {
274
+ const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
275
+
276
+ if (
277
+ comment.type === "Line" &&
278
+ index && commentList[index - 1].type === "Line" &&
279
+ tokenBefore && tokenBefore.loc.end.line === comment.loc.start.line - 1 &&
280
+ tokenBefore === commentList[index - 1]
281
+ ) {
282
+ commentGroups[commentGroups.length - 1].push(comment);
283
+ } else {
284
+ commentGroups.push([comment]);
285
+ }
286
+
287
+ return commentGroups;
288
+ }, [])
289
+ .filter(commentGroup => !(commentGroup.length === 1 && commentGroup[0].loc.start.line === commentGroup[0].loc.end.line))
290
+ .forEach(commentGroupCheckers[option]);
291
+ }
292
+ };
293
+ }
294
+ };
@@ -178,7 +178,8 @@ module.exports = {
178
178
 
179
179
  // char has no uppercase variant, so it's non-alphabetic
180
180
  return "non-alpha";
181
- } else if (firstChar === firstCharLower) {
181
+ }
182
+ if (firstChar === firstCharLower) {
182
183
  return "lower";
183
184
  }
184
185
  return "upper";
@@ -59,9 +59,11 @@ module.exports = {
59
59
 
60
60
  if (parentType === "IfStatement") {
61
61
  return isPrecededByTokens(node, ["else", ")"]);
62
- } else if (parentType === "DoWhileStatement") {
62
+ }
63
+ if (parentType === "DoWhileStatement") {
63
64
  return isPrecededByTokens(node, ["do"]);
64
- } else if (parentType === "SwitchCase") {
65
+ }
66
+ if (parentType === "SwitchCase") {
65
67
  return isPrecededByTokens(node, [":"]);
66
68
  }
67
69
  return isPrecededByTokens(node, [")"]);