eslint 4.7.2 → 4.11.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 (139) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.md +34 -19
  3. package/conf/default-cli-options.js +7 -4
  4. package/conf/eslint-recommended.js +2 -0
  5. package/lib/ast-utils.js +83 -42
  6. package/lib/cli-engine.js +53 -17
  7. package/lib/cli.js +17 -9
  8. package/lib/code-path-analysis/code-path-analyzer.js +8 -4
  9. package/lib/code-path-analysis/code-path-segment.js +43 -41
  10. package/lib/code-path-analysis/code-path-state.js +7 -2
  11. package/lib/config/autoconfig.js +14 -12
  12. package/lib/config/config-file.js +8 -51
  13. package/lib/config/config-initializer.js +10 -6
  14. package/lib/config/config-ops.js +21 -21
  15. package/lib/config/config-rule.js +24 -24
  16. package/lib/config/config-validator.js +38 -36
  17. package/lib/config/plugins.js +8 -35
  18. package/lib/config.js +12 -8
  19. package/lib/formatters/html-template-message.html +1 -1
  20. package/lib/formatters/html-template-page.html +3 -1
  21. package/lib/formatters/html.js +2 -1
  22. package/lib/formatters/junit.js +21 -15
  23. package/lib/formatters/tap.js +5 -3
  24. package/lib/ignored-paths.js +5 -3
  25. package/lib/linter.js +42 -42
  26. package/lib/logging.js +2 -2
  27. package/lib/options.js +12 -0
  28. package/lib/rules/.eslintrc.yml +2 -2
  29. package/lib/rules/array-bracket-newline.js +39 -25
  30. package/lib/rules/array-bracket-spacing.js +28 -28
  31. package/lib/rules/array-callback-return.js +13 -9
  32. package/lib/rules/array-element-newline.js +8 -8
  33. package/lib/rules/arrow-body-style.js +12 -6
  34. package/lib/rules/arrow-parens.js +4 -2
  35. package/lib/rules/block-spacing.js +1 -1
  36. package/lib/rules/brace-style.js +14 -14
  37. package/lib/rules/callback-return.js +2 -1
  38. package/lib/rules/capitalized-comments.js +2 -1
  39. package/lib/rules/comma-style.js +3 -1
  40. package/lib/rules/computed-property-spacing.js +22 -22
  41. package/lib/rules/consistent-return.js +4 -4
  42. package/lib/rules/consistent-this.js +4 -2
  43. package/lib/rules/curly.js +13 -9
  44. package/lib/rules/dot-notation.js +56 -35
  45. package/lib/rules/func-call-spacing.js +4 -2
  46. package/lib/rules/generator-star-spacing.js +3 -3
  47. package/lib/rules/getter-return.js +2 -1
  48. package/lib/rules/indent-legacy.js +25 -14
  49. package/lib/rules/indent.js +101 -91
  50. package/lib/rules/key-spacing.js +5 -3
  51. package/lib/rules/lines-around-comment.js +33 -4
  52. package/lib/rules/lines-around-directive.js +16 -12
  53. package/lib/rules/lines-between-class-members.js +91 -0
  54. package/lib/rules/max-len.js +2 -3
  55. package/lib/rules/max-statements-per-line.js +5 -3
  56. package/lib/rules/multiline-comment-style.js +294 -0
  57. package/lib/rules/new-cap.js +2 -1
  58. package/lib/rules/newline-after-var.js +8 -6
  59. package/lib/rules/newline-before-return.js +13 -9
  60. package/lib/rules/no-alert.js +7 -15
  61. package/lib/rules/no-await-in-loop.js +17 -9
  62. package/lib/rules/no-bitwise.js +5 -3
  63. package/lib/rules/no-catch-shadow.js +4 -2
  64. package/lib/rules/no-console.js +2 -1
  65. package/lib/rules/no-constant-condition.js +2 -2
  66. package/lib/rules/no-control-regex.js +2 -1
  67. package/lib/rules/no-else-return.js +60 -19
  68. package/lib/rules/no-empty-character-class.js +11 -11
  69. package/lib/rules/no-extra-parens.js +22 -11
  70. package/lib/rules/no-extra-semi.js +5 -3
  71. package/lib/rules/no-global-assign.js +4 -2
  72. package/lib/rules/no-implicit-coercion.js +6 -6
  73. package/lib/rules/no-implied-eval.js +2 -1
  74. package/lib/rules/no-label-var.js +4 -2
  75. package/lib/rules/no-lone-blocks.js +3 -3
  76. package/lib/rules/no-lonely-if.js +2 -1
  77. package/lib/rules/no-loop-func.js +10 -7
  78. package/lib/rules/no-mixed-requires.js +8 -4
  79. package/lib/rules/no-native-reassign.js +4 -2
  80. package/lib/rules/no-param-reassign.js +4 -2
  81. package/lib/rules/no-regex-spaces.js +1 -1
  82. package/lib/rules/no-restricted-imports.js +86 -17
  83. package/lib/rules/no-restricted-modules.js +84 -15
  84. package/lib/rules/no-restricted-properties.js +10 -10
  85. package/lib/rules/no-return-await.js +6 -6
  86. package/lib/rules/no-self-assign.js +4 -2
  87. package/lib/rules/no-sequences.js +6 -4
  88. package/lib/rules/no-trailing-spaces.js +14 -8
  89. package/lib/rules/no-unneeded-ternary.js +3 -1
  90. package/lib/rules/no-unreachable.js +4 -2
  91. package/lib/rules/no-unused-labels.js +2 -1
  92. package/lib/rules/no-use-before-define.js +13 -11
  93. package/lib/rules/no-useless-call.js +1 -25
  94. package/lib/rules/no-useless-computed-key.js +2 -1
  95. package/lib/rules/no-useless-escape.js +31 -23
  96. package/lib/rules/no-useless-return.js +14 -8
  97. package/lib/rules/no-var.js +11 -0
  98. package/lib/rules/no-whitespace-before-property.js +4 -2
  99. package/lib/rules/object-curly-newline.js +9 -2
  100. package/lib/rules/object-curly-spacing.js +20 -20
  101. package/lib/rules/object-shorthand.js +47 -35
  102. package/lib/rules/operator-assignment.js +9 -9
  103. package/lib/rules/operator-linebreak.js +15 -11
  104. package/lib/rules/padding-line-between-statements.js +6 -4
  105. package/lib/rules/prefer-arrow-callback.js +12 -10
  106. package/lib/rules/prefer-const.js +18 -10
  107. package/lib/rules/prefer-destructuring.js +4 -2
  108. package/lib/rules/prefer-numeric-literals.js +4 -2
  109. package/lib/rules/prefer-promise-reject-errors.js +16 -16
  110. package/lib/rules/prefer-rest-params.js +4 -2
  111. package/lib/rules/prefer-spread.js +1 -25
  112. package/lib/rules/prefer-template.js +33 -29
  113. package/lib/rules/quote-props.js +8 -8
  114. package/lib/rules/require-jsdoc.js +11 -18
  115. package/lib/rules/semi-style.js +44 -19
  116. package/lib/rules/semi.js +5 -3
  117. package/lib/rules/sort-imports.js +11 -6
  118. package/lib/rules/space-unary-ops.js +67 -69
  119. package/lib/rules/strict.js +8 -8
  120. package/lib/rules/valid-jsdoc.js +39 -33
  121. package/lib/rules/valid-typeof.js +4 -4
  122. package/lib/rules/wrap-iife.js +4 -4
  123. package/lib/rules/yoda.js +9 -7
  124. package/lib/testers/rule-tester.js +63 -40
  125. package/lib/token-store/backward-token-cursor.js +5 -3
  126. package/lib/token-store/forward-token-cursor.js +5 -3
  127. package/lib/token-store/utils.js +8 -4
  128. package/lib/util/apply-disable-directives.js +56 -27
  129. package/lib/util/glob.js +1 -1
  130. package/lib/util/naming.js +112 -0
  131. package/lib/util/node-event-generator.js +13 -27
  132. package/lib/util/safe-emitter.js +54 -0
  133. package/lib/util/source-code-fixer.js +4 -2
  134. package/lib/util/source-code.js +70 -65
  135. package/messages/no-config-found.txt +1 -1
  136. package/package.json +8 -8
  137. package/lib/internal-rules/.eslintrc.yml +0 -3
  138. package/lib/internal-rules/internal-consistent-docs-description.js +0 -130
  139. package/lib/internal-rules/internal-no-invalid-meta.js +0 -188
@@ -54,46 +54,67 @@ module.exports = {
54
54
  allowPattern = new RegExp(options.allowPattern);
55
55
  }
56
56
 
57
+ /**
58
+ * Check if the property is valid dot notation
59
+ * @param {ASTNode} node The dot notation node
60
+ * @param {string} value Value which is to be checked
61
+ * @returns {void}
62
+ */
63
+ function checkComputedProperty(node, value) {
64
+ if (
65
+ validIdentifier.test(value) &&
66
+ (allowKeywords || keywords.indexOf(String(value)) === -1) &&
67
+ !(allowPattern && allowPattern.test(value))
68
+ ) {
69
+ const formattedValue = node.property.type === "Literal" ? JSON.stringify(value) : `\`${value}\``;
70
+
71
+ context.report({
72
+ node: node.property,
73
+ message: "[{{propertyValue}}] is better written in dot notation.",
74
+ data: {
75
+ propertyValue: formattedValue
76
+ },
77
+ fix(fixer) {
78
+ const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
79
+ const rightBracket = sourceCode.getLastToken(node);
80
+
81
+ if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) {
82
+
83
+ // Don't perform any fixes if there are comments inside the brackets.
84
+ return null;
85
+ }
86
+
87
+ const tokenAfterProperty = sourceCode.getTokenAfter(rightBracket);
88
+ const needsSpaceAfterProperty = tokenAfterProperty &&
89
+ rightBracket.range[1] === tokenAfterProperty.range[0] &&
90
+ !astUtils.canTokensBeAdjacent(String(value), tokenAfterProperty);
91
+
92
+ const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
93
+ const textAfterProperty = needsSpaceAfterProperty ? " " : "";
94
+
95
+ return fixer.replaceTextRange(
96
+ [leftBracket.range[0], rightBracket.range[1]],
97
+ `${textBeforeDot}.${value}${textAfterProperty}`
98
+ );
99
+ }
100
+ });
101
+ }
102
+ }
103
+
57
104
  return {
58
105
  MemberExpression(node) {
59
106
  if (
60
107
  node.computed &&
61
- node.property.type === "Literal" &&
62
- validIdentifier.test(node.property.value) &&
63
- (allowKeywords || keywords.indexOf(String(node.property.value)) === -1)
108
+ node.property.type === "Literal"
64
109
  ) {
65
- if (!(allowPattern && allowPattern.test(node.property.value))) {
66
- context.report({
67
- node: node.property,
68
- message: "[{{propertyValue}}] is better written in dot notation.",
69
- data: {
70
- propertyValue: JSON.stringify(node.property.value)
71
- },
72
- fix(fixer) {
73
- const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
74
- const rightBracket = sourceCode.getLastToken(node);
75
-
76
- if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) {
77
-
78
- // Don't perform any fixes if there are comments inside the brackets.
79
- return null;
80
- }
81
-
82
- const tokenAfterProperty = sourceCode.getTokenAfter(rightBracket);
83
- const needsSpaceAfterProperty = tokenAfterProperty &&
84
- rightBracket.range[1] === tokenAfterProperty.range[0] &&
85
- !astUtils.canTokensBeAdjacent(String(node.property.value), tokenAfterProperty);
86
-
87
- const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
88
- const textAfterProperty = needsSpaceAfterProperty ? " " : "";
89
-
90
- return fixer.replaceTextRange(
91
- [leftBracket.range[0], rightBracket.range[1]],
92
- `${textBeforeDot}.${node.property.value}${textAfterProperty}`
93
- );
94
- }
95
- });
96
- }
110
+ checkComputedProperty(node, node.property.value);
111
+ }
112
+ if (
113
+ node.computed &&
114
+ node.property.type === "TemplateLiteral" &&
115
+ node.property.expressions.length === 0
116
+ ) {
117
+ checkComputedProperty(node, node.property.quasis[0].value.cooked);
97
118
  }
98
119
  if (
99
120
  !allowKeywords &&
@@ -118,8 +118,10 @@ module.exports = {
118
118
  message: "Unexpected space between function name and paren.",
119
119
  fix(fixer) {
120
120
 
121
- // Only autofix if there is no newline
122
- // https://github.com/eslint/eslint/issues/7787
121
+ /*
122
+ * Only autofix if there is no newline
123
+ * https://github.com/eslint/eslint/issues/7787
124
+ */
123
125
  if (!hasNewline) {
124
126
  return fixer.removeRange([prevToken.range[1], parenToken.range[0]]);
125
127
  }
@@ -68,7 +68,7 @@ module.exports = {
68
68
 
69
69
  /**
70
70
  * Returns resolved option definitions based on an option and defaults
71
- *
71
+ *
72
72
  * @param {any} option - The option object or string value
73
73
  * @param {Object} defaults - The defaults to use if options are not present
74
74
  * @returns {Object} the resolved object definition
@@ -121,7 +121,7 @@ module.exports = {
121
121
 
122
122
  /**
123
123
  * Checks the spacing between two tokens before or after the star token.
124
- *
124
+ *
125
125
  * @param {string} kind Either "named", "anonymous", or "method"
126
126
  * @param {string} side Either "before" or "after".
127
127
  * @param {Token} leftToken `function` keyword token if side is "before", or
@@ -161,7 +161,7 @@ module.exports = {
161
161
 
162
162
  /**
163
163
  * Enforces the spacing around the star if node is a generator function.
164
- *
164
+ *
165
165
  * @param {ASTNode} node A function expression or declaration node.
166
166
  * @returns {void}
167
167
  */
@@ -102,7 +102,8 @@ module.exports = {
102
102
  }
103
103
  }
104
104
 
105
- /** Checks whether a node means a getter function.
105
+ /**
106
+ * Checks whether a node means a getter function.
106
107
  * @param {ASTNode} node - a node to check.
107
108
  * @returns {boolean} if node means a getter, return true; else return false.
108
109
  */
@@ -274,8 +274,10 @@ module.exports = {
274
274
  foundStatement = `${actualSpaces} ${foundSpacesWord} and ${actualTabs} ${foundTabsWord}`; // e.g. "1 space and 2 tabs"
275
275
  } else if (actualSpaces > 0) {
276
276
 
277
- // Abbreviate the message if the expected indentation is also spaces.
278
- // e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
277
+ /*
278
+ * Abbreviate the message if the expected indentation is also spaces.
279
+ * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
280
+ */
279
281
  foundStatement = indentType === "space" ? actualSpaces : `${actualSpaces} ${foundSpacesWord}`;
280
282
  } else if (actualTabs > 0) {
281
283
  foundStatement = indentType === "tab" ? actualTabs : `${actualTabs} ${foundTabsWord}`;
@@ -323,8 +325,8 @@ module.exports = {
323
325
  * @param {ASTNode|Token} node Node to examine
324
326
  * @param {boolean} [byLastLine=false] get indent of node's last line
325
327
  * @returns {Object} The node's indent. Contains keys `space` and `tab`, representing the indent of each character. Also
326
- contains keys `goodChar` and `badChar`, where `goodChar` is the amount of the user's desired indentation character, and
327
- `badChar` is the amount of the other indentation character.
328
+ * contains keys `goodChar` and `badChar`, where `goodChar` is the amount of the user's desired indentation character, and
329
+ * `badChar` is the amount of the other indentation character.
328
330
  */
329
331
  function getNodeIndent(node, byLastLine) {
330
332
  const token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node);
@@ -445,8 +447,10 @@ module.exports = {
445
447
  */
446
448
  function checkLastReturnStatementLineIndent(node, firstLineIndent) {
447
449
 
448
- // in case if return statement ends with ');' we have traverse back to ')'
449
- // otherwise we'll measure indent for ';' and replace ')'
450
+ /*
451
+ * in case if return statement ends with ');' we have traverse back to ')'
452
+ * otherwise we'll measure indent for ';' and replace ')'
453
+ */
450
454
  const lastToken = sourceCode.getLastToken(node, astUtils.isClosingParenToken);
451
455
  const textBeforeClosingParenthesis = sourceCode.getText(lastToken, lastToken.loc.start.column).slice(0, -1);
452
456
 
@@ -645,8 +649,10 @@ module.exports = {
645
649
  }
646
650
  }
647
651
 
648
- // function body indent should be indent + indent size, unless this
649
- // is a FunctionDeclaration, FunctionExpression, or outer IIFE and the corresponding options are enabled.
652
+ /*
653
+ * function body indent should be indent + indent size, unless this
654
+ * is a FunctionDeclaration, FunctionExpression, or outer IIFE and the corresponding options are enabled.
655
+ */
650
656
  let functionOffset = indentSize;
651
657
 
652
658
  if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) {
@@ -733,7 +739,9 @@ module.exports = {
733
739
  } else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
734
740
  const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements;
735
741
 
736
- if (parentElements[0] && parentElements[0].loc.start.line === parent.loc.start.line && parentElements[0].loc.end.line !== parent.loc.start.line) {
742
+ if (parentElements[0] &&
743
+ parentElements[0].loc.start.line === parent.loc.start.line &&
744
+ parentElements[0].loc.end.line !== parent.loc.start.line) {
737
745
 
738
746
  /*
739
747
  * If the first element of the array spans multiple lines, don't increase the expected indentation of the rest.
@@ -797,7 +805,8 @@ module.exports = {
797
805
  }
798
806
  }
799
807
 
800
- checkLastNodeLineIndent(node, nodeIndent + (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0));
808
+ checkLastNodeLineIndent(node, nodeIndent +
809
+ (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0));
801
810
  }
802
811
 
803
812
  /**
@@ -1024,10 +1033,12 @@ module.exports = {
1024
1033
  return;
1025
1034
  }
1026
1035
 
1027
- // The typical layout of variable declarations and assignments
1028
- // alter the expectation of correct indentation. Skip them.
1029
- // TODO: Add appropriate configuration options for variable
1030
- // declarations and assignments.
1036
+ /*
1037
+ * The typical layout of variable declarations and assignments
1038
+ * alter the expectation of correct indentation. Skip them.
1039
+ * TODO: Add appropriate configuration options for variable
1040
+ * declarations and assignments.
1041
+ */
1031
1042
  if (getParentNodeByType(node, "VariableDeclarator", ["FunctionExpression", "ArrowFunctionExpression"])) {
1032
1043
  return;
1033
1044
  }
@@ -200,19 +200,19 @@ class TokenInfo {
200
200
  }
201
201
 
202
202
  /**
203
- * Gets the first token on a given token's line
204
- * @param {Token|ASTNode} token a node or token
205
- * @returns {Token} The first token on the given line
206
- */
203
+ * Gets the first token on a given token's line
204
+ * @param {Token|ASTNode} token a node or token
205
+ * @returns {Token} The first token on the given line
206
+ */
207
207
  getFirstTokenOfLine(token) {
208
208
  return this.firstTokensByLineNumber.get(token.loc.start.line);
209
209
  }
210
210
 
211
211
  /**
212
- * Determines whether a token is the first token in its line
213
- * @param {Token} token The token
214
- * @returns {boolean} `true` if the token is the first on its line
215
- */
212
+ * Determines whether a token is the first token in its line
213
+ * @param {Token} token The token
214
+ * @returns {boolean} `true` if the token is the first on its line
215
+ */
216
216
  isFirstTokenOfLine(token) {
217
217
  return this.getFirstTokenOfLine(token) === token;
218
218
  }
@@ -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 });
@@ -334,31 +336,31 @@ class OffsetStorage {
334
336
  }
335
337
 
336
338
  /**
337
- * Sets the desired offset of all tokens in a range
338
- * It's common for node listeners in this file to need to apply the same offset to a large, contiguous range of tokens.
339
- * Moreover, the offset of any given token is usually updated multiple times (roughly once for each node that contains
340
- * it). This means that the offset of each token is updated O(AST depth) times.
341
- * It would not be performant to store and update the offsets for each token independently, because the rule would end
342
- * up having a time complexity of O(number of tokens * AST depth), which is quite slow for large files.
343
- *
344
- * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following
345
- * list could represent the state of the offset tree at a given point:
346
- *
347
- * * Tokens starting in the interval [0, 15) are aligned with the beginning of the file
348
- * * Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token
349
- * * Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token
350
- * * Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token
351
- * * Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token
352
- *
353
- * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using:
354
- * `setDesiredOffsets([30, 43], fooToken, 1);`
355
- *
356
- * @param {[number, number]} range A [start, end] pair. All tokens with range[0] <= token.start < range[1] will have the offset applied.
357
- * @param {Token} fromToken The token that this is offset from
358
- * @param {number} offset The desired indent level
359
- * @param {boolean} force `true` if this offset should not use the normal collapsing behavior. This should almost always be false.
360
- * @returns {void}
361
- */
339
+ * Sets the desired offset of all tokens in a range
340
+ * It's common for node listeners in this file to need to apply the same offset to a large, contiguous range of tokens.
341
+ * Moreover, the offset of any given token is usually updated multiple times (roughly once for each node that contains
342
+ * it). This means that the offset of each token is updated O(AST depth) times.
343
+ * It would not be performant to store and update the offsets for each token independently, because the rule would end
344
+ * up having a time complexity of O(number of tokens * AST depth), which is quite slow for large files.
345
+ *
346
+ * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following
347
+ * list could represent the state of the offset tree at a given point:
348
+ *
349
+ * * Tokens starting in the interval [0, 15) are aligned with the beginning of the file
350
+ * * Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token
351
+ * * Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token
352
+ * * Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token
353
+ * * Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token
354
+ *
355
+ * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using:
356
+ * `setDesiredOffsets([30, 43], fooToken, 1);`
357
+ *
358
+ * @param {[number, number]} range A [start, end] pair. All tokens with range[0] <= token.start < range[1] will have the offset applied.
359
+ * @param {Token} fromToken The token that this is offset from
360
+ * @param {number} offset The desired indent level
361
+ * @param {boolean} force `true` if this offset should not use the normal collapsing behavior. This should almost always be false.
362
+ * @returns {void}
363
+ */
362
364
  setDesiredOffsets(range, fromToken, offset, force) {
363
365
 
364
366
  /*
@@ -406,18 +408,23 @@ class OffsetStorage {
406
408
  }
407
409
 
408
410
  /**
409
- * Gets the desired indent of a token
410
- * @param {Token} token The token
411
- * @returns {number} The desired indent of the token
412
- */
411
+ * Gets the desired indent of a token
412
+ * @param {Token} token The token
413
+ * @returns {string} The desired indent of the token
414
+ */
413
415
  getDesiredIndent(token) {
414
416
  if (!this._desiredIndentCache.has(token)) {
415
417
 
416
418
  if (this._ignoredTokens.has(token)) {
417
419
 
418
- // If the token is ignored, use the actual indent of the token as the desired indent.
419
- // This ensures that no errors are reported for this token.
420
- this._desiredIndentCache.set(token, this._tokenInfo.getTokenIndent(token).length / this._indentSize);
420
+ /*
421
+ * If the token is ignored, use the actual indent of the token as the desired indent.
422
+ * This ensures that no errors are reported for this token.
423
+ */
424
+ this._desiredIndentCache.set(
425
+ token,
426
+ this._tokenInfo.getTokenIndent(token)
427
+ );
421
428
  } else if (this._lockedFirstTokens.has(token)) {
422
429
  const firstToken = this._lockedFirstTokens.get(token);
423
430
 
@@ -428,7 +435,7 @@ class OffsetStorage {
428
435
  this.getDesiredIndent(this._tokenInfo.getFirstTokenOfLine(firstToken)) +
429
436
 
430
437
  // (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
438
+ this._indentType.repeat(firstToken.loc.start.column - this._tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column)
432
439
  );
433
440
  } else {
434
441
  const offsetInfo = this._getOffsetDescriptor(token);
@@ -436,19 +443,22 @@ class OffsetStorage {
436
443
  offsetInfo.from &&
437
444
  offsetInfo.from.loc.start.line === token.loc.start.line &&
438
445
  !offsetInfo.force
439
- ) ? 0 : offsetInfo.offset;
446
+ ) ? 0 : offsetInfo.offset * this._indentSize;
440
447
 
441
- this._desiredIndentCache.set(token, offset + (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : 0));
448
+ this._desiredIndentCache.set(
449
+ token,
450
+ (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : "") + this._indentType.repeat(offset)
451
+ );
442
452
  }
443
453
  }
444
454
  return this._desiredIndentCache.get(token);
445
455
  }
446
456
 
447
457
  /**
448
- * Ignores a token, preventing it from being reported.
449
- * @param {Token} token The token
450
- * @returns {void}
451
- */
458
+ * Ignores a token, preventing it from being reported.
459
+ * @param {Token} token The token
460
+ * @returns {void}
461
+ */
452
462
  ignoreToken(token) {
453
463
  if (this._tokenInfo.isFirstTokenOfLine(token)) {
454
464
  this._ignoredTokens.add(token);
@@ -655,7 +665,7 @@ module.exports = {
655
665
 
656
666
  const sourceCode = context.getSourceCode();
657
667
  const tokenInfo = new TokenInfo(sourceCode);
658
- const offsets = new OffsetStorage(tokenInfo, indentSize);
668
+ const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t");
659
669
  const parameterParens = new WeakSet();
660
670
 
661
671
  /**
@@ -673,8 +683,10 @@ module.exports = {
673
683
 
674
684
  if (actualSpaces > 0) {
675
685
 
676
- // Abbreviate the message if the expected indentation is also spaces.
677
- // e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
686
+ /*
687
+ * Abbreviate the message if the expected indentation is also spaces.
688
+ * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
689
+ */
678
690
  foundStatement = indentType === "space" ? actualSpaces : `${actualSpaces} ${foundSpacesWord}`;
679
691
  } else if (actualTabs > 0) {
680
692
  foundStatement = indentType === "tab" ? actualTabs : `${actualTabs} ${foundTabsWord}`;
@@ -688,27 +700,24 @@ module.exports = {
688
700
  /**
689
701
  * Reports a given indent violation
690
702
  * @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
703
+ * @param {string} neededIndent Expected indentation string
694
704
  * @returns {void}
695
705
  */
696
- function report(token, neededIndentLevel) {
706
+ function report(token, neededIndent) {
697
707
  const actualIndent = Array.from(tokenInfo.getTokenIndent(token));
698
708
  const numSpaces = actualIndent.filter(char => char === " ").length;
699
709
  const numTabs = actualIndent.filter(char => char === "\t").length;
700
- const neededChars = neededIndentLevel * indentSize;
701
710
 
702
711
  context.report({
703
712
  node: token,
704
- message: createErrorMessage(neededChars, numSpaces, numTabs),
713
+ message: createErrorMessage(neededIndent.length, numSpaces, numTabs),
705
714
  loc: {
706
715
  start: { line: token.loc.start.line, column: 0 },
707
716
  end: { line: token.loc.start.line, column: token.loc.start.column }
708
717
  },
709
718
  fix(fixer) {
710
719
  const range = [token.range[0] - token.loc.start.column, token.range[0]];
711
- const newText = (indentType === "space" ? " " : "\t").repeat(neededChars);
720
+ const newText = neededIndent;
712
721
 
713
722
  return fixer.replaceTextRange(range, newText);
714
723
  }
@@ -718,14 +727,13 @@ module.exports = {
718
727
  /**
719
728
  * Checks if a token's indentation is correct
720
729
  * @param {Token} token Token to examine
721
- * @param {int} desiredIndentLevel needed indent level
730
+ * @param {string} desiredIndent Desired indentation of the string
722
731
  * @returns {boolean} `true` if the token's indentation is correct
723
732
  */
724
- function validateTokenIndent(token, desiredIndentLevel) {
733
+ function validateTokenIndent(token, desiredIndent) {
725
734
  const indentation = tokenInfo.getTokenIndent(token);
726
- const expectedChar = indentType === "space" ? " " : "\t";
727
735
 
728
- return indentation === expectedChar.repeat(desiredIndentLevel * indentSize) ||
736
+ return indentation === desiredIndent ||
729
737
 
730
738
  // To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs.
731
739
  indentation.includes(" ") && indentation.includes("\t");
@@ -766,20 +774,20 @@ module.exports = {
766
774
  }
767
775
 
768
776
  /**
769
- * Check indentation for lists of elements (arrays, objects, function params)
770
- * @param {ASTNode[]} elements List of elements that should be offset
771
- * @param {Token} startToken The start token of the list that element should be aligned against, e.g. '['
772
- * @param {Token} endToken The end token of the list, e.g. ']'
773
- * @param {number|string} offset The amount that the elements should be offset
774
- * @returns {void}
775
- */
777
+ * Check indentation for lists of elements (arrays, objects, function params)
778
+ * @param {ASTNode[]} elements List of elements that should be offset
779
+ * @param {Token} startToken The start token of the list that element should be aligned against, e.g. '['
780
+ * @param {Token} endToken The end token of the list, e.g. ']'
781
+ * @param {number|string} offset The amount that the elements should be offset
782
+ * @returns {void}
783
+ */
776
784
  function addElementListIndent(elements, startToken, endToken, offset) {
777
785
 
778
786
  /**
779
- * Gets the first token of a given element, including surrounding parentheses.
780
- * @param {ASTNode} element A node in the `elements` list
781
- * @returns {Token} The first token of this element
782
- */
787
+ * Gets the first token of a given element, including surrounding parentheses.
788
+ * @param {ASTNode} element A node in the `elements` list
789
+ * @returns {Token} The first token of this element
790
+ */
783
791
  function getFirstToken(element) {
784
792
  let token = sourceCode.getTokenBefore(element);
785
793
 
@@ -868,10 +876,10 @@ module.exports = {
868
876
  }
869
877
 
870
878
  /**
871
- * Checks the indentation for nodes that are like function calls (`CallExpression` and `NewExpression`)
872
- * @param {ASTNode} node A CallExpression or NewExpression node
873
- * @returns {void}
874
- */
879
+ * Checks the indentation for nodes that are like function calls (`CallExpression` and `NewExpression`)
880
+ * @param {ASTNode} node A CallExpression or NewExpression node
881
+ * @returns {void}
882
+ */
875
883
  function addFunctionCallIndent(node) {
876
884
  let openingParen;
877
885
 
@@ -890,10 +898,10 @@ module.exports = {
890
898
  }
891
899
 
892
900
  /**
893
- * Checks the indentation of parenthesized values, given a list of tokens in a program
894
- * @param {Token[]} tokens A list of tokens
895
- * @returns {void}
896
- */
901
+ * Checks the indentation of parenthesized values, given a list of tokens in a program
902
+ * @param {Token[]} tokens A list of tokens
903
+ * @returns {void}
904
+ */
897
905
  function addParensIndent(tokens) {
898
906
  const parenStack = [];
899
907
  const parenPairs = [];
@@ -928,11 +936,11 @@ module.exports = {
928
936
  }
929
937
 
930
938
  /**
931
- * Ignore all tokens within an unknown node whose offset do not depend
932
- * on another token's offset within the unknown node
933
- * @param {ASTNode} node Unknown Node
934
- * @returns {void}
935
- */
939
+ * Ignore all tokens within an unknown node whose offset do not depend
940
+ * on another token's offset within the unknown node
941
+ * @param {ASTNode} node Unknown Node
942
+ * @returns {void}
943
+ */
936
944
  function ignoreNode(node) {
937
945
  const unknownNodeTokens = new Set(sourceCode.getTokens(node, { includeComments: true }));
938
946
 
@@ -1019,10 +1027,10 @@ module.exports = {
1019
1027
  const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
1020
1028
 
1021
1029
  /*
1022
- * For backwards compatibility, don't check BinaryExpression indents, e.g.
1023
- * var foo = bar &&
1024
- * baz;
1025
- */
1030
+ * For backwards compatibility, don't check BinaryExpression indents, e.g.
1031
+ * var foo = bar &&
1032
+ * baz;
1033
+ */
1026
1034
 
1027
1035
  const tokenAfterOperator = sourceCode.getTokenAfter(operator);
1028
1036
 
@@ -1241,7 +1249,9 @@ module.exports = {
1241
1249
  NewExpression(node) {
1242
1250
 
1243
1251
  // 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))) {
1252
+ if (node.arguments.length > 0 ||
1253
+ astUtils.isClosingParenToken(sourceCode.getLastToken(node)) &&
1254
+ astUtils.isOpeningParenToken(sourceCode.getLastToken(node, 1))) {
1245
1255
  addFunctionCallIndent(node);
1246
1256
  }
1247
1257
  },
@@ -329,9 +329,11 @@ module.exports = {
329
329
  return true;
330
330
  }
331
331
 
332
- // Check that the first comment is adjacent to the end of the group, the
333
- // last comment is adjacent to the candidate property, and that successive
334
- // comments are adjacent to each other.
332
+ /*
333
+ * Check that the first comment is adjacent to the end of the group, the
334
+ * last comment is adjacent to the candidate property, and that successive
335
+ * comments are adjacent to each other.
336
+ */
335
337
  const leadingComments = sourceCode.getCommentsBefore(candidate);
336
338
 
337
339
  if (