eslint 4.1.1 → 4.4.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 (55) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/bin/eslint.js +5 -4
  3. package/conf/category-list.json +2 -2
  4. package/conf/config-schema.js +3 -1
  5. package/conf/eslint-recommended.js +12 -14
  6. package/lib/cli-engine.js +4 -3
  7. package/lib/cli.js +12 -1
  8. package/lib/config/config-file.js +5 -5
  9. package/lib/config/config-initializer.js +123 -14
  10. package/lib/config/config-validator.js +43 -14
  11. package/lib/config/plugins.js +13 -1
  12. package/lib/linter.js +26 -15
  13. package/lib/rule-context.js +53 -41
  14. package/lib/rules/arrow-parens.js +5 -2
  15. package/lib/rules/comma-dangle.js +40 -40
  16. package/lib/rules/curly.js +1 -1
  17. package/lib/rules/dot-notation.js +9 -0
  18. package/lib/rules/getter-return.js +176 -0
  19. package/lib/rules/id-blacklist.js +7 -3
  20. package/lib/rules/id-match.js +8 -4
  21. package/lib/rules/indent-legacy.js +2 -2
  22. package/lib/rules/indent.js +354 -349
  23. package/lib/rules/key-spacing.js +2 -2
  24. package/lib/rules/multiline-ternary.js +8 -2
  25. package/lib/rules/no-cond-assign.js +7 -3
  26. package/lib/rules/no-constant-condition.js +62 -6
  27. package/lib/rules/no-debugger.js +6 -1
  28. package/lib/rules/no-else-return.js +1 -1
  29. package/lib/rules/no-extra-parens.js +24 -11
  30. package/lib/rules/no-inner-declarations.js +8 -4
  31. package/lib/rules/no-multi-spaces.js +53 -115
  32. package/lib/rules/no-regex-spaces.js +4 -4
  33. package/lib/rules/no-restricted-globals.js +50 -9
  34. package/lib/rules/no-restricted-properties.js +19 -11
  35. package/lib/rules/no-sync.js +15 -3
  36. package/lib/rules/no-tabs.js +8 -4
  37. package/lib/rules/no-underscore-dangle.js +28 -1
  38. package/lib/rules/object-curly-newline.js +18 -0
  39. package/lib/rules/object-curly-spacing.js +1 -1
  40. package/lib/rules/padded-blocks.js +2 -2
  41. package/lib/rules/padding-line-between-statements.js +1 -1
  42. package/lib/rules/prefer-destructuring.js +70 -32
  43. package/lib/rules/prefer-numeric-literals.js +36 -7
  44. package/lib/rules/prefer-reflect.js +8 -4
  45. package/lib/rules/prefer-template.js +2 -2
  46. package/lib/rules/space-infix-ops.js +1 -1
  47. package/lib/rules/spaced-comment.js +2 -2
  48. package/lib/rules/valid-jsdoc.js +15 -7
  49. package/lib/testers/rule-tester.js +23 -30
  50. package/lib/testers/test-parser.js +48 -0
  51. package/lib/util/ajv.js +29 -0
  52. package/lib/util/npm-util.js +9 -8
  53. package/lib/util/source-code-fixer.js +47 -19
  54. package/package.json +11 -7
  55. package/conf/json-schema-schema.json +0 -150
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @fileoverview This option sets a specific tab width for your code
3
3
  *
4
- * This rule has been ported and modified from nodeca.
4
+ * @author Teddy Katz
5
5
  * @author Vitaly Puzrin
6
6
  * @author Gyandeep Singh
7
7
  */
@@ -14,6 +14,7 @@
14
14
 
15
15
  const lodash = require("lodash");
16
16
  const astUtils = require("../ast-utils");
17
+ const createTree = require("functional-red-black-tree");
17
18
 
18
19
  //------------------------------------------------------------------------------
19
20
  // Rule Definition
@@ -114,6 +115,69 @@ const KNOWN_NODES = new Set([
114
115
  * and report the token if the two values are not equal.
115
116
  */
116
117
 
118
+
119
+ /**
120
+ * A mutable balanced binary search tree that stores (key, value) pairs. The keys are numeric, and must be unique.
121
+ * This is intended to be a generic wrapper around a balanced binary search tree library, so that the underlying implementation
122
+ * can easily be swapped out.
123
+ */
124
+ class BinarySearchTree {
125
+
126
+ /**
127
+ * Creates an empty tree
128
+ */
129
+ constructor() {
130
+ this._rbTree = createTree();
131
+ }
132
+
133
+ /**
134
+ * Inserts an entry into the tree.
135
+ * @param {number} key The entry's key
136
+ * @param {*} value The entry's value
137
+ * @returns {void}
138
+ */
139
+ insert(key, value) {
140
+ const iterator = this._rbTree.find(key);
141
+
142
+ if (iterator.valid) {
143
+ this._rbTree = iterator.update(value);
144
+ } else {
145
+ this._rbTree = this._rbTree.insert(key, value);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Finds the entry with the largest key less than or equal to the provided key
151
+ * @param {number} key The provided key
152
+ * @returns {{key: number, value: *}|null} The found entry, or null if no such entry exists.
153
+ */
154
+ findLe(key) {
155
+ const iterator = this._rbTree.le(key);
156
+
157
+ return iterator && { key: iterator.key, value: iterator.value };
158
+ }
159
+
160
+ /**
161
+ * Deletes all of the keys in the interval [start, end)
162
+ * @param {number} start The start of the range
163
+ * @param {number} end The end of the range
164
+ * @returns {void}
165
+ */
166
+ deleteRange(start, end) {
167
+
168
+ // Exit without traversing the tree if the range has zero size.
169
+ if (start === end) {
170
+ return;
171
+ }
172
+ const iterator = this._rbTree.ge(start);
173
+
174
+ while (iterator.valid && iterator.key < end) {
175
+ this._rbTree = this._rbTree.remove(iterator.key);
176
+ iterator.next();
177
+ }
178
+ }
179
+ }
180
+
117
181
  /**
118
182
  * A helper class to get token-based info related to indentation
119
183
  */
@@ -135,14 +199,6 @@ class TokenInfo {
135
199
  }, new Map());
136
200
  }
137
201
 
138
- /**
139
- * Gets all tokens and comments
140
- * @returns {Token[]} A list of all tokens and comments
141
- */
142
- getAllTokens() {
143
- return this.sourceCode.tokensAndComments;
144
- }
145
-
146
202
  /**
147
203
  * Gets the first token on a given token's line
148
204
  * @param {Token|ASTNode} token a node or token
@@ -178,50 +234,28 @@ class OffsetStorage {
178
234
 
179
235
  /**
180
236
  * @param {TokenInfo} tokenInfo a TokenInfo instance
181
- * @param {string} indentType The desired type of indentation (either "space" or "tab")
182
237
  * @param {number} indentSize The desired size of each indentation level
183
238
  */
184
- constructor(tokenInfo, indentType, indentSize) {
185
- this.tokenInfo = tokenInfo;
186
- this.indentType = indentType;
187
- this.indentSize = indentSize;
239
+ constructor(tokenInfo, indentSize) {
240
+ this._tokenInfo = tokenInfo;
241
+ this._indentSize = indentSize;
188
242
 
189
- /*
190
- * desiredOffsets, lockedFirstTokens, and desiredIndentCache conceptually map tokens to something else.
191
- * However, they're implemented as objects with range indices as keys because this improves performance as of Node 7.
192
- * This uses the assumption that no two tokens start at the same index in the program.
193
- *
194
- * The values of the desiredOffsets map are objects with the schema { offset: number, from: Token|null }.
195
- * These objects should not be mutated or exposed outside of OffsetStorage.
196
- */
197
- const NO_OFFSET = { offset: 0, from: null };
198
-
199
- this.desiredOffsets = tokenInfo.getAllTokens().reduce((desiredOffsets, token) => {
200
- desiredOffsets[token.range[0]] = NO_OFFSET;
243
+ this._tree = new BinarySearchTree();
244
+ this._tree.insert(0, { offset: 0, from: null, force: false });
201
245
 
202
- return desiredOffsets;
203
- }, Object.create(null));
204
- this.lockedFirstTokens = Object.create(null);
205
- this.desiredIndentCache = Object.create(null);
206
- this.ignoredTokens = new WeakSet();
246
+ this._lockedFirstTokens = new WeakMap();
247
+ this._desiredIndentCache = new WeakMap();
248
+ this._ignoredTokens = new WeakSet();
207
249
  }
208
250
 
209
- /**
210
- * Sets the indent of one token to match the indent of another.
211
- * @param {Token} baseToken The first token
212
- * @param {Token} offsetToken The second token, whose indent should be matched to the first token
213
- * @returns {void}
214
- */
215
- matchIndentOf(baseToken, offsetToken) {
216
- if (baseToken !== offsetToken) {
217
- this.desiredOffsets[offsetToken.range[0]] = { offset: 0, from: baseToken };
218
- }
251
+ _getOffsetDescriptor(token) {
252
+ return this._tree.findLe(token.range[0]).value;
219
253
  }
220
254
 
221
255
  /**
222
256
  * Sets the offset column of token B to match the offset column of token A.
223
- * **WARNING**: This is different from matchIndentOf because it matches a *column*, even if baseToken is not
224
- * the first token on its line. In most cases, `matchIndentOf` should be used instead.
257
+ * **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In
258
+ * most cases, `setDesiredOffset` should be used instead.
225
259
  * @param {Token} baseToken The first token
226
260
  * @param {Token} offsetToken The second token, whose offset should be matched to the first token
227
261
  * @returns {void}
@@ -235,7 +269,7 @@ class OffsetStorage {
235
269
  * element. The desired indentation of each of these tokens is computed based on the desired indentation
236
270
  * of the "first" element, rather than through the normal offset mechanism.
237
271
  */
238
- this.lockedFirstTokens[offsetToken.range[0]] = baseToken;
272
+ this._lockedFirstTokens.set(offsetToken, baseToken);
239
273
  }
240
274
 
241
275
  /**
@@ -291,36 +325,84 @@ class OffsetStorage {
291
325
  * in the second case.
292
326
  *
293
327
  * @param {Token} token The token
294
- * @param {Token} offsetFrom The token that `token` should be offset from
328
+ * @param {Token} fromToken The token that `token` should be offset from
295
329
  * @param {number} offset The desired indent level
296
330
  * @returns {void}
297
331
  */
298
- setDesiredOffset(token, offsetFrom, offset) {
299
- if (offsetFrom && token.loc.start.line === offsetFrom.loc.start.line) {
300
- this.matchIndentOf(offsetFrom, token);
301
- } else {
302
- this.desiredOffsets[token.range[0]] = { offset, from: offsetFrom };
303
- }
332
+ setDesiredOffset(token, fromToken, offset) {
333
+ return this.setDesiredOffsets(token.range, fromToken, offset);
304
334
  }
305
335
 
306
336
  /**
307
- * Sets the desired offset of multiple tokens
308
- * @param {Token[]} tokens A list of tokens. These tokens should be consecutive.
309
- * @param {Token} offsetFrom The token that this is offset from
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
310
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.
311
360
  * @returns {void}
312
361
  */
313
- setDesiredOffsets(tokens, offsetFrom, offset) {
362
+ setDesiredOffsets(range, fromToken, offset, force) {
314
363
 
315
364
  /*
316
- * TODO: (not-an-aardvark) This function is the main performance holdup for this rule. It works
317
- * by setting the desired offset of each token to the given amount relative to the parent, but it's
318
- * frequently called with a large list of tokens, and it takes time to set the offset for each token
319
- * individually. Since the tokens are always consecutive, it might be possible to improve performance
320
- * here by changing the data structure used to store offsets (e.g. allowing a *range* of tokens to
321
- * be offset rather than offsetting each token individually).
365
+ * Offset ranges are stored as a collection of nodes, where each node maps a numeric key to an offset
366
+ * descriptor. The tree for the example above would have the following nodes:
367
+ *
368
+ * * key: 0, value: { offset: 0, from: null }
369
+ * * key: 15, value: { offset: 1, from: barToken }
370
+ * * key: 30, value: { offset: 1, from: fooToken }
371
+ * * key: 43, value: { offset: 2, from: barToken }
372
+ * * key: 820, value: { offset: 1, from: bazToken }
373
+ *
374
+ * To find the offset descriptor for any given token, one needs to find the node with the largest key
375
+ * which is <= token.start. To make this operation fast, the nodes are stored in a balanced binary
376
+ * search tree indexed by key.
322
377
  */
323
- tokens.forEach(token => this.setDesiredOffset(token, offsetFrom, offset));
378
+
379
+ const descriptorToInsert = { offset, from: fromToken, force };
380
+
381
+ const descriptorAfterRange = this._tree.findLe(range[1]).value;
382
+
383
+ const fromTokenIsInRange = fromToken && fromToken.range[0] >= range[0] && fromToken.range[1] <= range[1];
384
+ const fromTokenDescriptor = fromTokenIsInRange && this._getOffsetDescriptor(fromToken);
385
+
386
+ // First, remove any existing nodes in the range from the tree.
387
+ this._tree.deleteRange(range[0] + 1, range[1]);
388
+
389
+ // Insert a new node into the tree for this range
390
+ this._tree.insert(range[0], descriptorToInsert);
391
+
392
+ /*
393
+ * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously,
394
+ * even if it's in the current range.
395
+ */
396
+ if (fromTokenIsInRange) {
397
+ this._tree.insert(fromToken.range[0], fromTokenDescriptor);
398
+ this._tree.insert(fromToken.range[1], descriptorToInsert);
399
+ }
400
+
401
+ /*
402
+ * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following
403
+ * tokens the same as it was before.
404
+ */
405
+ this._tree.insert(range[1], descriptorAfterRange);
324
406
  }
325
407
 
326
408
  /**
@@ -329,30 +411,37 @@ class OffsetStorage {
329
411
  * @returns {number} The desired indent of the token
330
412
  */
331
413
  getDesiredIndent(token) {
332
- if (!(token.range[0] in this.desiredIndentCache)) {
414
+ if (!this._desiredIndentCache.has(token)) {
333
415
 
334
- if (this.ignoredTokens.has(token)) {
416
+ if (this._ignoredTokens.has(token)) {
335
417
 
336
418
  // If the token is ignored, use the actual indent of the token as the desired indent.
337
419
  // This ensures that no errors are reported for this token.
338
- this.desiredIndentCache[token.range[0]] = this.tokenInfo.getTokenIndent(token).length / this.indentSize;
339
- } else if (token.range[0] in this.lockedFirstTokens) {
340
- const firstToken = this.lockedFirstTokens[token.range[0]];
420
+ this._desiredIndentCache.set(token, this._tokenInfo.getTokenIndent(token).length / this._indentSize);
421
+ } else if (this._lockedFirstTokens.has(token)) {
422
+ const firstToken = this._lockedFirstTokens.get(token);
341
423
 
342
- this.desiredIndentCache[token.range[0]] =
424
+ this._desiredIndentCache.set(
425
+ token,
343
426
 
344
427
  // (indentation for the first element's line)
345
- this.getDesiredIndent(this.tokenInfo.getFirstTokenOfLine(firstToken)) +
428
+ this.getDesiredIndent(this._tokenInfo.getFirstTokenOfLine(firstToken)) +
346
429
 
347
430
  // (space between the start of the first element's line and the first element)
348
- (firstToken.loc.start.column - this.tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column) / this.indentSize;
431
+ (firstToken.loc.start.column - this._tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column) / this._indentSize
432
+ );
349
433
  } else {
350
- const offsetInfo = this.desiredOffsets[token.range[0]];
351
-
352
- this.desiredIndentCache[token.range[0]] = offsetInfo.offset + (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : 0);
434
+ const offsetInfo = this._getOffsetDescriptor(token);
435
+ const offset = (
436
+ offsetInfo.from &&
437
+ offsetInfo.from.loc.start.line === token.loc.start.line &&
438
+ !offsetInfo.force
439
+ ) ? 0 : offsetInfo.offset;
440
+
441
+ this._desiredIndentCache.set(token, offset + (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : 0));
353
442
  }
354
443
  }
355
- return this.desiredIndentCache[token.range[0]];
444
+ return this._desiredIndentCache.get(token);
356
445
  }
357
446
 
358
447
  /**
@@ -361,8 +450,8 @@ class OffsetStorage {
361
450
  * @returns {void}
362
451
  */
363
452
  ignoreToken(token) {
364
- if (this.tokenInfo.isFirstTokenOfLine(token)) {
365
- this.ignoredTokens.add(token);
453
+ if (this._tokenInfo.isFirstTokenOfLine(token)) {
454
+ this._ignoredTokens.add(token);
366
455
  }
367
456
  }
368
457
 
@@ -372,19 +461,7 @@ class OffsetStorage {
372
461
  * @returns {Token} The token that the given token depends on, or `null` if the given token is at the top level
373
462
  */
374
463
  getFirstDependency(token) {
375
- return this.desiredOffsets[token.range[0]].from;
376
- }
377
-
378
- /**
379
- * Increases the offset for a token from its parent by the given amount
380
- * @param {Token} token The token whose offset should be increased
381
- * @param {number} amount The number of indent levels that the offset should increase by
382
- * @returns {void}
383
- */
384
- increaseOffset(token, amount) {
385
- const currentOffsetInfo = this.desiredOffsets[token.range[0]];
386
-
387
- this.desiredOffsets[token.range[0]] = { offset: currentOffsetInfo.offset + amount, from: currentOffsetInfo.from };
464
+ return this._getOffsetDescriptor(token).from;
388
465
  }
389
466
  }
390
467
 
@@ -501,6 +578,7 @@ module.exports = {
501
578
  },
502
579
  ArrayExpression: ELEMENT_LIST_SCHEMA,
503
580
  ObjectExpression: ELEMENT_LIST_SCHEMA,
581
+ ImportDeclaration: ELEMENT_LIST_SCHEMA,
504
582
  flatTernaryExpressions: {
505
583
  type: "boolean"
506
584
  }
@@ -539,8 +617,7 @@ module.exports = {
539
617
  MemberExpression: 1,
540
618
  ArrayExpression: 1,
541
619
  ObjectExpression: 1,
542
- ArrayPattern: 1,
543
- ObjectPattern: 1,
620
+ ImportDeclaration: 1,
544
621
  flatTernaryExpressions: false
545
622
  };
546
623
 
@@ -548,7 +625,7 @@ module.exports = {
548
625
  if (context.options[0] === "tab") {
549
626
  indentSize = 1;
550
627
  indentType = "tab";
551
- } else if (typeof context.options[0] === "number") {
628
+ } else {
552
629
  indentSize = context.options[0];
553
630
  indentType = "space";
554
631
  }
@@ -568,7 +645,7 @@ module.exports = {
568
645
 
569
646
  const sourceCode = context.getSourceCode();
570
647
  const tokenInfo = new TokenInfo(sourceCode);
571
- const offsets = new OffsetStorage(tokenInfo, indentType, indentSize);
648
+ const offsets = new OffsetStorage(tokenInfo, indentSize);
572
649
  const parameterParens = new WeakSet();
573
650
 
574
651
  /**
@@ -600,10 +677,10 @@ module.exports = {
600
677
 
601
678
  /**
602
679
  * Reports a given indent violation
603
- * @param {Token} token Node violating the indent rule
680
+ * @param {Token} token Token violating the indent rule
604
681
  * @param {int} neededIndentLevel Expected indentation level
605
- * @param {int} gottenSpaces Indentation space count in the actual node/code
606
- * @param {int} gottenTabs Indentation tab count in the actual node/code
682
+ * @param {int} gottenSpaces Actual number of indentation spaces for the token
683
+ * @param {int} gottenTabs Actual number of indentation tabs for the token
607
684
  * @returns {void}
608
685
  */
609
686
  function report(token, neededIndentLevel) {
@@ -678,15 +755,6 @@ module.exports = {
678
755
  return (statement.type === "ExpressionStatement" || statement.type === "VariableDeclaration") && statement.parent.type === "Program";
679
756
  }
680
757
 
681
- /**
682
- * Gets all tokens and comments for a node
683
- * @param {ASTNode} node The node
684
- * @returns {Token[]} A list of tokens and comments
685
- */
686
- function getTokensAndComments(node) {
687
- return sourceCode.getTokens(node, { includeComments: true });
688
- }
689
-
690
758
  /**
691
759
  * Check indentation for lists of elements (arrays, objects, function params)
692
760
  * @param {ASTNode[]} elements List of elements that should be offset
@@ -712,13 +780,12 @@ module.exports = {
712
780
  }
713
781
 
714
782
  // Run through all the tokens in the list, and offset them by one indent level (mainly for comments, other things will end up overridden)
715
- // FIXME: (not-an-aardvark) This isn't performant at all.
716
783
  offsets.setDesiredOffsets(
717
- sourceCode.getTokensBetween(startToken, endToken, { includeComments: true }),
784
+ [startToken.range[1], endToken.range[0]],
718
785
  startToken,
719
786
  offset === "first" ? 1 : offset
720
787
  );
721
- offsets.matchIndentOf(startToken, endToken);
788
+ offsets.setDesiredOffset(endToken, startToken, 0);
722
789
 
723
790
  // If the preference is "first" but there is no first element (e.g. sparse arrays w/ empty first slot), fall back to 1 level.
724
791
  if (offset === "first" && elements.length && !elements[0]) {
@@ -738,52 +805,12 @@ module.exports = {
738
805
  const firstTokenOfPreviousElement = previousElement && getFirstToken(previousElement);
739
806
 
740
807
  if (previousElement && sourceCode.getLastToken(previousElement).loc.start.line > startToken.loc.end.line) {
741
- offsets.matchIndentOf(firstTokenOfPreviousElement, getFirstToken(element));
808
+ offsets.setDesiredOffsets(element.range, firstTokenOfPreviousElement, 0);
742
809
  }
743
810
  }
744
811
  });
745
812
  }
746
813
 
747
- /**
748
- * Check indentation for blocks and class bodies
749
- * @param {ASTNode} node The BlockStatement or ClassBody node to indent
750
- * @returns {void}
751
- */
752
- function addBlockIndent(node) {
753
-
754
- let blockIndentLevel;
755
-
756
- if (node.parent && isOuterIIFE(node.parent)) {
757
- blockIndentLevel = options.outerIIFEBody;
758
- } else if (node.parent && (node.parent.type === "FunctionExpression" || node.parent.type === "ArrowFunctionExpression")) {
759
- blockIndentLevel = options.FunctionExpression.body;
760
- } else if (node.parent && node.parent.type === "FunctionDeclaration") {
761
- blockIndentLevel = options.FunctionDeclaration.body;
762
- } else {
763
- blockIndentLevel = 1;
764
- }
765
-
766
- /*
767
- * For blocks that aren't lone statements, ensure that the opening curly brace
768
- * is aligned with the parent.
769
- */
770
- if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
771
- offsets.matchIndentOf(sourceCode.getFirstToken(node.parent), sourceCode.getFirstToken(node));
772
- }
773
- addElementListIndent(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), blockIndentLevel);
774
- }
775
-
776
- /**
777
- * Check indent for array block content or object block content
778
- * @param {ASTNode} node node to examine
779
- * @returns {void}
780
- */
781
- function addArrayOrObjectIndent(node) {
782
- const tokens = getTokensAndComments(node);
783
-
784
- addElementListIndent(node.elements || node.properties, tokens[0], tokens[tokens.length - 1], options[node.type]);
785
- }
786
-
787
814
  /**
788
815
  * Check and decide whether to check for indentation for blockless nodes
789
816
  * Scenarios are for or while statements without braces around them
@@ -794,16 +821,18 @@ module.exports = {
794
821
  if (node.type !== "BlockStatement") {
795
822
  const lastParentToken = sourceCode.getTokenBefore(node, astUtils.isNotOpeningParenToken);
796
823
 
797
- let bodyTokens = getTokensAndComments(node);
824
+ let firstBodyToken = sourceCode.getFirstToken(node);
825
+ let lastBodyToken = sourceCode.getLastToken(node);
798
826
 
799
827
  while (
800
- astUtils.isOpeningParenToken(sourceCode.getTokenBefore(bodyTokens[0])) &&
801
- astUtils.isClosingParenToken(sourceCode.getTokenAfter(bodyTokens[bodyTokens.length - 1]))
828
+ astUtils.isOpeningParenToken(sourceCode.getTokenBefore(firstBodyToken)) &&
829
+ astUtils.isClosingParenToken(sourceCode.getTokenAfter(lastBodyToken))
802
830
  ) {
803
- bodyTokens = [sourceCode.getTokenBefore(bodyTokens[0])].concat(bodyTokens).concat(sourceCode.getTokenAfter(bodyTokens[bodyTokens.length - 1]));
831
+ firstBodyToken = sourceCode.getTokenBefore(firstBodyToken);
832
+ lastBodyToken = sourceCode.getTokenAfter(lastBodyToken);
804
833
  }
805
834
 
806
- offsets.setDesiredOffsets(bodyTokens, lastParentToken, 1);
835
+ offsets.setDesiredOffsets([firstBodyToken.range[0], lastBodyToken.range[1]], lastParentToken, 1);
807
836
 
808
837
  /*
809
838
  * For blockless nodes with semicolon-first style, don't indent the semicolon.
@@ -813,60 +842,12 @@ module.exports = {
813
842
  */
814
843
  const lastToken = sourceCode.getLastToken(node);
815
844
 
816
- if (astUtils.isSemicolonToken(lastToken)) {
817
- offsets.matchIndentOf(lastParentToken, lastToken);
845
+ if (node.type !== "EmptyStatement" && astUtils.isSemicolonToken(lastToken)) {
846
+ offsets.setDesiredOffset(lastToken, lastParentToken, 0);
818
847
  }
819
848
  }
820
849
  }
821
850
 
822
- /**
823
- * Checks the indentation of a function's parameters
824
- * @param {ASTNode} node The node
825
- * @param {number} paramsIndent The indentation level option for the parameters
826
- * @returns {void}
827
- */
828
- function addFunctionParamsIndent(node, paramsIndent) {
829
- const openingParen = node.params.length ? sourceCode.getTokenBefore(node.params[0]) : sourceCode.getTokenBefore(node.body, 1);
830
-
831
- if (!openingParen) {
832
-
833
- // If there is no opening paren (e.g. for an arrow function with a single parameter), don't indent anything.
834
- return;
835
- }
836
-
837
- const closingParen = sourceCode.getTokenBefore(node.body);
838
- const nodeTokens = getTokensAndComments(node);
839
- const openingParenIndex = lodash.sortedIndexBy(nodeTokens, openingParen, token => token.range[0]);
840
- const closingParenIndex = lodash.sortedIndexBy(nodeTokens, closingParen, token => token.range[0]);
841
-
842
- parameterParens.add(nodeTokens[openingParenIndex]);
843
- parameterParens.add(nodeTokens[closingParenIndex]);
844
-
845
- addElementListIndent(node.params, nodeTokens[openingParenIndex], nodeTokens[closingParenIndex], paramsIndent);
846
- }
847
-
848
- /**
849
- * Adds indentation for the right-hand side of binary/logical expressions.
850
- * @param {ASTNode} node A BinaryExpression or LogicalExpression node
851
- * @returns {void}
852
- */
853
- function addBinaryOrLogicalExpressionIndent(node) {
854
- const tokens = getTokensAndComments(node);
855
- const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
856
- const firstTokenAfterOperator = sourceCode.getTokenAfter(operator);
857
- const tokensAfterOperator = tokens.slice(lodash.sortedIndexBy(tokens, firstTokenAfterOperator, token => token.range[0]));
858
-
859
- /*
860
- * For backwards compatibility, don't check BinaryExpression indents, e.g.
861
- * var foo = bar &&
862
- * baz;
863
- */
864
-
865
- offsets.ignoreToken(operator);
866
- offsets.ignoreToken(tokensAfterOperator[0]);
867
- offsets.setDesiredOffsets(tokensAfterOperator, tokensAfterOperator[0], 1);
868
- }
869
-
870
851
  /**
871
852
  * Checks the indentation for nodes that are like function calls (`CallExpression` and `NewExpression`)
872
853
  * @param {ASTNode} node A CallExpression or NewExpression node
@@ -884,26 +865,11 @@ module.exports = {
884
865
 
885
866
  parameterParens.add(openingParen);
886
867
  parameterParens.add(closingParen);
887
- offsets.matchIndentOf(sourceCode.getLastToken(node.callee), openingParen);
868
+ offsets.setDesiredOffset(openingParen, sourceCode.getTokenBefore(openingParen), 0);
888
869
 
889
870
  addElementListIndent(node.arguments, openingParen, closingParen, options.CallExpression.arguments);
890
871
  }
891
872
 
892
- /**
893
- * Checks the indentation of ClassDeclarations and ClassExpressions
894
- * @param {ASTNode} node A ClassDeclaration or ClassExpression node
895
- * @returns {void}
896
- */
897
- function addClassIndent(node) {
898
- if (node.superClass) {
899
- const classToken = sourceCode.getFirstToken(node);
900
- const extendsToken = sourceCode.getTokenBefore(node.superClass, astUtils.isNotOpeningParenToken);
901
-
902
- offsets.setDesiredOffset(extendsToken, classToken, 1);
903
- offsets.setDesiredOffsets(sourceCode.getTokensBetween(extendsToken, node.body, { includeComments: true }), classToken, 1);
904
- }
905
- }
906
-
907
873
  /**
908
874
  * Checks the indentation of parenthesized values, given a list of tokens in a program
909
875
  * @param {Token[]} tokens A list of tokens
@@ -936,10 +902,9 @@ module.exports = {
936
902
  offsets.setDesiredOffset(token, leftParen, 1);
937
903
  }
938
904
  });
939
- offsets.setDesiredOffset(sourceCode.getTokenAfter(leftParen), leftParen, 1);
940
905
  }
941
906
 
942
- offsets.matchIndentOf(leftParen, rightParen);
907
+ offsets.setDesiredOffset(rightParen, leftParen, 0);
943
908
  });
944
909
  }
945
910
 
@@ -950,7 +915,7 @@ module.exports = {
950
915
  * @returns {void}
951
916
  */
952
917
  function ignoreUnknownNode(node) {
953
- const unknownNodeTokens = new Set(getTokensAndComments(node));
918
+ const unknownNodeTokens = new Set(sourceCode.getTokens(node, { includeComments: true }));
954
919
 
955
920
  unknownNodeTokens.forEach(token => {
956
921
  if (!unknownNodeTokens.has(offsets.getFirstDependency(token))) {
@@ -959,7 +924,7 @@ module.exports = {
959
924
  if (token === firstTokenOfLine) {
960
925
  offsets.ignoreToken(token);
961
926
  } else {
962
- offsets.matchIndentOf(firstTokenOfLine, token);
927
+ offsets.setDesiredOffset(token, firstTokenOfLine, 0);
963
928
  }
964
929
  }
965
930
  });
@@ -996,11 +961,34 @@ module.exports = {
996
961
  }
997
962
 
998
963
  return {
999
- ArrayExpression: addArrayOrObjectIndent,
1000
- ArrayPattern: addArrayOrObjectIndent,
964
+ "ArrayExpression, ArrayPattern"(node) {
965
+ const openingBracket = sourceCode.getFirstToken(node);
966
+ const closingBracket = sourceCode.getTokenAfter(lodash.findLast(node.elements) || openingBracket, astUtils.isClosingBracketToken);
967
+
968
+ addElementListIndent(node.elements, openingBracket, closingBracket, options.ArrayExpression);
969
+ },
970
+
971
+ "ObjectExpression, ObjectPattern"(node) {
972
+ const openingCurly = sourceCode.getFirstToken(node);
973
+ const closingCurly = sourceCode.getTokenAfter(
974
+ node.properties.length ? node.properties[node.properties.length - 1] : openingCurly,
975
+ astUtils.isClosingBraceToken
976
+ );
977
+
978
+ addElementListIndent(node.properties, openingCurly, closingCurly, options.ObjectExpression);
979
+ },
1001
980
 
1002
981
  ArrowFunctionExpression(node) {
1003
- addFunctionParamsIndent(node, options.FunctionExpression.parameters);
982
+ const firstToken = sourceCode.getFirstToken(node);
983
+
984
+ if (astUtils.isOpeningParenToken(firstToken)) {
985
+ const openingParen = firstToken;
986
+ const closingParen = sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken);
987
+
988
+ parameterParens.add(openingParen);
989
+ parameterParens.add(closingParen);
990
+ addElementListIndent(node.params, openingParen, closingParen, options.FunctionExpression.parameters);
991
+ }
1004
992
  addBlocklessNodeIndent(node.body);
1005
993
 
1006
994
  let arrowToken;
@@ -1010,30 +998,67 @@ module.exports = {
1010
998
  } else {
1011
999
  arrowToken = sourceCode.getFirstToken(node, astUtils.isArrowToken);
1012
1000
  }
1013
- offsets.matchIndentOf(sourceCode.getFirstToken(node), arrowToken);
1001
+ offsets.setDesiredOffset(arrowToken, sourceCode.getFirstToken(node), 0);
1014
1002
  },
1015
1003
 
1016
1004
  AssignmentExpression(node) {
1017
1005
  const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
1018
- const nodeTokens = getTokensAndComments(node);
1019
- const tokensFromOperator = nodeTokens.slice(lodash.sortedIndexBy(nodeTokens, operator, token => token.range[0]));
1020
1006
 
1021
- offsets.setDesiredOffsets(tokensFromOperator, sourceCode.getFirstToken(node.left), 1);
1022
- offsets.ignoreToken(tokensFromOperator[0]);
1023
- offsets.ignoreToken(tokensFromOperator[1]);
1007
+ offsets.setDesiredOffsets([operator.range[0], node.range[1]], sourceCode.getLastToken(node.left), 1);
1008
+ offsets.ignoreToken(operator);
1009
+ offsets.ignoreToken(sourceCode.getTokenAfter(operator));
1024
1010
  },
1025
1011
 
1026
- BinaryExpression: addBinaryOrLogicalExpressionIndent,
1012
+ "BinaryExpression, LogicalExpression"(node) {
1013
+ const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);
1014
+
1015
+ /*
1016
+ * For backwards compatibility, don't check BinaryExpression indents, e.g.
1017
+ * var foo = bar &&
1018
+ * baz;
1019
+ */
1020
+
1021
+ const tokenAfterOperator = sourceCode.getTokenAfter(operator);
1022
+
1023
+ offsets.ignoreToken(operator);
1024
+ offsets.ignoreToken(tokenAfterOperator);
1025
+ offsets.setDesiredOffset(tokenAfterOperator, operator, 0);
1026
+ offsets.setDesiredOffsets([tokenAfterOperator.range[1], node.range[1]], tokenAfterOperator, 1);
1027
+ },
1027
1028
 
1028
- BlockStatement: addBlockIndent,
1029
+ "BlockStatement, ClassBody"(node) {
1030
+
1031
+ let blockIndentLevel;
1032
+
1033
+ if (node.parent && isOuterIIFE(node.parent)) {
1034
+ blockIndentLevel = options.outerIIFEBody;
1035
+ } else if (node.parent && (node.parent.type === "FunctionExpression" || node.parent.type === "ArrowFunctionExpression")) {
1036
+ blockIndentLevel = options.FunctionExpression.body;
1037
+ } else if (node.parent && node.parent.type === "FunctionDeclaration") {
1038
+ blockIndentLevel = options.FunctionDeclaration.body;
1039
+ } else {
1040
+ blockIndentLevel = 1;
1041
+ }
1042
+
1043
+ /*
1044
+ * For blocks that aren't lone statements, ensure that the opening curly brace
1045
+ * is aligned with the parent.
1046
+ */
1047
+ if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
1048
+ offsets.setDesiredOffset(sourceCode.getFirstToken(node), sourceCode.getFirstToken(node.parent), 0);
1049
+ }
1050
+ addElementListIndent(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), blockIndentLevel);
1051
+ },
1029
1052
 
1030
1053
  CallExpression: addFunctionCallIndent,
1031
1054
 
1032
- ClassBody: addBlockIndent,
1033
1055
 
1034
- ClassDeclaration: addClassIndent,
1056
+ "ClassDeclaration[superClass], ClassExpression[superClass]"(node) {
1057
+ const classToken = sourceCode.getFirstToken(node);
1058
+ const extendsToken = sourceCode.getTokenBefore(node.superClass, astUtils.isNotOpeningParenToken);
1035
1059
 
1036
- ClassExpression: addClassIndent,
1060
+ offsets.setDesiredOffsets([extendsToken.range[0], node.body.range[0]], classToken, 1);
1061
+ },
1037
1062
 
1038
1063
  ConditionalExpression(node) {
1039
1064
  const firstToken = sourceCode.getFirstToken(node);
@@ -1050,13 +1075,14 @@ module.exports = {
1050
1075
  const questionMarkToken = sourceCode.getFirstTokenBetween(node.test, node.consequent, token => token.type === "Punctuator" && token.value === "?");
1051
1076
  const colonToken = sourceCode.getFirstTokenBetween(node.consequent, node.alternate, token => token.type === "Punctuator" && token.value === ":");
1052
1077
 
1053
- const consequentTokens = sourceCode.getTokensBetween(questionMarkToken, colonToken, { includeComments: true });
1054
- const alternateTokens = sourceCode.getTokensAfter(colonToken, token => token.range[1] <= node.range[1]);
1078
+ const firstConsequentToken = sourceCode.getTokenAfter(questionMarkToken, { includeComments: true });
1079
+ const lastConsequentToken = sourceCode.getTokenBefore(colonToken, { includeComments: true });
1080
+ const firstAlternateToken = sourceCode.getTokenAfter(colonToken);
1055
1081
 
1056
1082
  offsets.setDesiredOffset(questionMarkToken, firstToken, 1);
1057
1083
  offsets.setDesiredOffset(colonToken, firstToken, 1);
1058
1084
 
1059
- offsets.setDesiredOffset(consequentTokens[0], firstToken, 1);
1085
+ offsets.setDesiredOffset(firstConsequentToken, firstToken, 1);
1060
1086
 
1061
1087
  /*
1062
1088
  * The alternate and the consequent should usually have the same indentation.
@@ -1068,8 +1094,8 @@ module.exports = {
1068
1094
  * baz // as a result, `baz` is offset by 1 rather than 2
1069
1095
  * )
1070
1096
  */
1071
- if (consequentTokens[consequentTokens.length - 1].loc.end.line === alternateTokens[0].loc.start.line) {
1072
- offsets.matchIndentOf(consequentTokens[0], alternateTokens[0]);
1097
+ if (lastConsequentToken.loc.end.line === firstAlternateToken.loc.start.line) {
1098
+ offsets.setDesiredOffset(firstAlternateToken, firstConsequentToken, 0);
1073
1099
  } else {
1074
1100
 
1075
1101
  /**
@@ -1081,21 +1107,19 @@ module.exports = {
1081
1107
  * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up
1082
1108
  * having no expected indentation.
1083
1109
  */
1084
- offsets.setDesiredOffset(alternateTokens[0], firstToken, 1);
1110
+ offsets.setDesiredOffset(firstAlternateToken, firstToken, 1);
1085
1111
  }
1086
1112
 
1087
- offsets.setDesiredOffsets(consequentTokens, consequentTokens[0], 0);
1088
- offsets.setDesiredOffsets(alternateTokens, alternateTokens[0], 0);
1113
+ offsets.setDesiredOffsets([questionMarkToken.range[1], colonToken.range[0]], firstConsequentToken, 0);
1114
+ offsets.setDesiredOffsets([colonToken.range[1], node.range[1]], firstAlternateToken, 0);
1089
1115
  }
1090
1116
  },
1091
1117
 
1092
- DoWhileStatement: node => addBlocklessNodeIndent(node.body),
1118
+ "DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement": node => addBlocklessNodeIndent(node.body),
1093
1119
 
1094
1120
  ExportNamedDeclaration(node) {
1095
1121
  if (node.declaration === null) {
1096
- const tokensInNode = getTokensAndComments(node);
1097
1122
  const closingCurly = sourceCode.getLastToken(node, astUtils.isClosingBraceToken);
1098
- const closingCurlyIndex = lodash.sortedIndexBy(tokensInNode, closingCurly, token => token.range[0]);
1099
1123
 
1100
1124
  // Indent the specifiers in `export {foo, bar, baz}`
1101
1125
  addElementListIndent(node.specifiers, sourceCode.getFirstToken(node, { skip: 1 }), closingCurly, 1);
@@ -1103,36 +1127,33 @@ module.exports = {
1103
1127
  if (node.source) {
1104
1128
 
1105
1129
  // Indent everything after and including the `from` token in `export {foo, bar, baz} from 'qux'`
1106
- offsets.setDesiredOffsets(tokensInNode.slice(closingCurlyIndex + 1), sourceCode.getFirstToken(node), 1);
1130
+ offsets.setDesiredOffsets([closingCurly.range[1], node.range[1]], sourceCode.getFirstToken(node), 1);
1107
1131
  }
1108
1132
  }
1109
1133
  },
1110
1134
 
1111
- ForInStatement: node => addBlocklessNodeIndent(node.body),
1112
-
1113
- ForOfStatement: node => addBlocklessNodeIndent(node.body),
1114
-
1115
1135
  ForStatement(node) {
1116
1136
  const forOpeningParen = sourceCode.getFirstToken(node, 1);
1117
1137
 
1118
1138
  if (node.init) {
1119
- offsets.setDesiredOffsets(getTokensAndComments(node.init), forOpeningParen, 1);
1139
+ offsets.setDesiredOffsets(node.init.range, forOpeningParen, 1);
1120
1140
  }
1121
1141
  if (node.test) {
1122
- offsets.setDesiredOffsets(getTokensAndComments(node.test), forOpeningParen, 1);
1142
+ offsets.setDesiredOffsets(node.test.range, forOpeningParen, 1);
1123
1143
  }
1124
1144
  if (node.update) {
1125
- offsets.setDesiredOffsets(getTokensAndComments(node.update), forOpeningParen, 1);
1145
+ offsets.setDesiredOffsets(node.update.range, forOpeningParen, 1);
1126
1146
  }
1127
1147
  addBlocklessNodeIndent(node.body);
1128
1148
  },
1129
1149
 
1130
- FunctionDeclaration(node) {
1131
- addFunctionParamsIndent(node, options.FunctionDeclaration.parameters);
1132
- },
1150
+ "FunctionDeclaration, FunctionExpression"(node) {
1151
+ const closingParen = sourceCode.getTokenBefore(node.body);
1152
+ const openingParen = sourceCode.getTokenBefore(node.params.length ? node.params[0] : closingParen);
1133
1153
 
1134
- FunctionExpression(node) {
1135
- addFunctionParamsIndent(node, options.FunctionExpression.parameters);
1154
+ parameterParens.add(openingParen);
1155
+ parameterParens.add(closingParen);
1156
+ addElementListIndent(node.params, openingParen, closingParen, options[node.type].parameters);
1136
1157
  },
1137
1158
 
1138
1159
  IfStatement(node) {
@@ -1147,34 +1168,32 @@ module.exports = {
1147
1168
  const openingCurly = sourceCode.getFirstToken(node, astUtils.isOpeningBraceToken);
1148
1169
  const closingCurly = sourceCode.getLastToken(node, astUtils.isClosingBraceToken);
1149
1170
 
1150
- addElementListIndent(node.specifiers.filter(specifier => specifier.type === "ImportSpecifier"), openingCurly, closingCurly, 1);
1171
+ addElementListIndent(node.specifiers.filter(specifier => specifier.type === "ImportSpecifier"), openingCurly, closingCurly, options.ImportDeclaration);
1151
1172
  }
1152
1173
 
1153
1174
  const fromToken = sourceCode.getLastToken(node, token => token.type === "Identifier" && token.value === "from");
1154
1175
 
1155
1176
  if (fromToken) {
1156
- const tokensToOffset = sourceCode.getTokensBetween(fromToken, sourceCode.getLastToken(node), 1);
1157
-
1158
- offsets.setDesiredOffsets(tokensToOffset, sourceCode.getFirstToken(node), 1);
1177
+ offsets.setDesiredOffsets([fromToken.range[0], node.range[1]], sourceCode.getFirstToken(node), 1);
1159
1178
  }
1160
1179
  },
1161
1180
 
1162
- LogicalExpression: addBinaryOrLogicalExpressionIndent,
1163
-
1164
1181
  "MemberExpression, JSXMemberExpression"(node) {
1165
1182
  const firstNonObjectToken = sourceCode.getFirstTokenBetween(node.object, node.property, astUtils.isNotClosingParenToken);
1166
1183
  const secondNonObjectToken = sourceCode.getTokenAfter(firstNonObjectToken);
1167
1184
 
1168
- const tokenBeforeObject = sourceCode.getTokenBefore(node.object, token => astUtils.isNotOpeningParenToken(token) || parameterParens.has(token));
1169
- const firstObjectToken = tokenBeforeObject ? sourceCode.getTokenAfter(tokenBeforeObject) : sourceCode.ast.tokens[0];
1185
+ const objectParenCount = sourceCode.getTokensBetween(node.object, node.property, { filter: astUtils.isClosingParenToken }).length;
1186
+ const firstObjectToken = objectParenCount
1187
+ ? sourceCode.getTokenBefore(node.object, { skip: objectParenCount - 1 })
1188
+ : sourceCode.getFirstToken(node.object);
1170
1189
  const lastObjectToken = sourceCode.getTokenBefore(firstNonObjectToken);
1171
1190
  const firstPropertyToken = node.computed ? firstNonObjectToken : secondNonObjectToken;
1172
1191
 
1173
1192
  if (node.computed) {
1174
1193
 
1175
1194
  // For computed MemberExpressions, match the closing bracket with the opening bracket.
1176
- offsets.matchIndentOf(firstNonObjectToken, sourceCode.getLastToken(node));
1177
- offsets.setDesiredOffsets(getTokensAndComments(node.property), firstNonObjectToken, 1);
1195
+ offsets.setDesiredOffset(sourceCode.getLastToken(node), firstNonObjectToken, 0);
1196
+ offsets.setDesiredOffsets(node.property.range, firstNonObjectToken, 1);
1178
1197
  }
1179
1198
 
1180
1199
  /*
@@ -1207,8 +1226,8 @@ module.exports = {
1207
1226
  offsets.ignoreToken(secondNonObjectToken);
1208
1227
 
1209
1228
  // To ignore the property indentation, ensure that the property tokens depend on the ignored tokens.
1210
- offsets.matchIndentOf(offsetBase, firstNonObjectToken);
1211
- offsets.matchIndentOf(firstNonObjectToken, secondNonObjectToken);
1229
+ offsets.setDesiredOffset(firstNonObjectToken, offsetBase, 0);
1230
+ offsets.setDesiredOffset(secondNonObjectToken, firstNonObjectToken, 0);
1212
1231
  }
1213
1232
  },
1214
1233
 
@@ -1220,11 +1239,8 @@ module.exports = {
1220
1239
  }
1221
1240
  },
1222
1241
 
1223
- ObjectExpression: addArrayOrObjectIndent,
1224
- ObjectPattern: addArrayOrObjectIndent,
1225
-
1226
1242
  Property(node) {
1227
- if (!node.computed && !node.shorthand && !node.method && node.kind === "init") {
1243
+ if (!node.shorthand && !node.method && node.kind === "init") {
1228
1244
  const colon = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isColonToken);
1229
1245
 
1230
1246
  offsets.ignoreToken(sourceCode.getTokenAfter(colon));
@@ -1232,41 +1248,38 @@ module.exports = {
1232
1248
  },
1233
1249
 
1234
1250
  SwitchStatement(node) {
1235
- const tokens = getTokensAndComments(node);
1236
- const openingCurlyIndex = tokens.findIndex(token => token.range[0] >= node.discriminant.range[1] && astUtils.isOpeningBraceToken(token));
1251
+ const openingCurly = sourceCode.getTokenAfter(node.discriminant, astUtils.isOpeningBraceToken);
1252
+ const closingCurly = sourceCode.getLastToken(node);
1253
+ const caseKeywords = node.cases.map(switchCase => sourceCode.getFirstToken(switchCase));
1237
1254
 
1238
- offsets.setDesiredOffsets(tokens.slice(openingCurlyIndex + 1, -1), tokens[openingCurlyIndex], options.SwitchCase);
1255
+ offsets.setDesiredOffsets([openingCurly.range[1], closingCurly.range[0]], openingCurly, options.SwitchCase);
1239
1256
 
1240
- const caseKeywords = new WeakSet(node.cases.map(switchCase => sourceCode.getFirstToken(switchCase)));
1241
- const lastCaseKeyword = node.cases.length && sourceCode.getFirstToken(node.cases[node.cases.length - 1]);
1242
- const casesWithBlocks = new WeakSet(
1243
- node.cases
1244
- .filter(switchCase => switchCase.consequent.length === 1 && switchCase.consequent[0].type === "BlockStatement")
1245
- .map(switchCase => sourceCode.getFirstToken(switchCase))
1246
- );
1247
- let lastAnchor = tokens[openingCurlyIndex];
1257
+ node.cases.forEach((switchCase, index) => {
1258
+ const caseKeyword = caseKeywords[index];
1248
1259
 
1249
- tokens.slice(openingCurlyIndex + 1, -1).forEach(token => {
1250
- if (caseKeywords.has(token)) {
1251
- lastAnchor = token;
1252
- } else if (lastAnchor === lastCaseKeyword && (token.type === "Line" || token.type === "Block")) {
1253
- offsets.ignoreToken(token);
1254
- } else if (!casesWithBlocks.has(lastAnchor)) {
1255
- offsets.setDesiredOffset(token, lastAnchor, 1);
1260
+ if (!(switchCase.consequent.length === 1 && switchCase.consequent[0].type === "BlockStatement")) {
1261
+ const tokenAfterCurrentCase = index === node.cases.length - 1 ? closingCurly : caseKeywords[index + 1];
1262
+
1263
+ offsets.setDesiredOffsets([caseKeyword.range[1], tokenAfterCurrentCase.range[0]], caseKeyword, 1);
1256
1264
  }
1257
1265
  });
1266
+
1267
+ if (node.cases.length) {
1268
+ sourceCode.getTokensBetween(
1269
+ node.cases[node.cases.length - 1],
1270
+ closingCurly,
1271
+ { includeComments: true, filter: astUtils.isCommentToken }
1272
+ ).forEach(token => offsets.ignoreToken(token));
1273
+ }
1258
1274
  },
1259
1275
 
1260
1276
  TemplateLiteral(node) {
1261
- const tokens = getTokensAndComments(node);
1262
-
1263
- offsets.setDesiredOffsets(getTokensAndComments(node.quasis[0]), tokens[0], 0);
1264
1277
  node.expressions.forEach((expression, index) => {
1265
1278
  const previousQuasi = node.quasis[index];
1266
1279
  const nextQuasi = node.quasis[index + 1];
1267
1280
  const tokenToAlignFrom = previousQuasi.loc.start.line === previousQuasi.loc.end.line ? sourceCode.getFirstToken(previousQuasi) : null;
1268
1281
 
1269
- offsets.setDesiredOffsets(sourceCode.getTokensBetween(previousQuasi, nextQuasi), tokenToAlignFrom, 1);
1282
+ offsets.setDesiredOffsets([previousQuasi.range[1], nextQuasi.range[0]], tokenToAlignFrom, 1);
1270
1283
  offsets.setDesiredOffset(sourceCode.getFirstToken(nextQuasi), tokenToAlignFrom, 0);
1271
1284
  });
1272
1285
  },
@@ -1274,7 +1287,33 @@ module.exports = {
1274
1287
  VariableDeclaration(node) {
1275
1288
  const variableIndent = options.VariableDeclarator.hasOwnProperty(node.kind) ? options.VariableDeclarator[node.kind] : DEFAULT_VARIABLE_INDENT;
1276
1289
 
1277
- offsets.setDesiredOffsets(getTokensAndComments(node), sourceCode.getFirstToken(node), variableIndent);
1290
+ if (node.declarations[node.declarations.length - 1].loc.start.line > node.loc.start.line) {
1291
+
1292
+ /*
1293
+ * VariableDeclarator indentation is a bit different from other forms of indentation, in that the
1294
+ * indentation of an opening bracket sometimes won't match that of a closing bracket. For example,
1295
+ * the following indentations are correct:
1296
+ *
1297
+ * var foo = {
1298
+ * ok: true
1299
+ * };
1300
+ *
1301
+ * var foo = {
1302
+ * ok: true,
1303
+ * },
1304
+ * bar = 1;
1305
+ *
1306
+ * Account for when exiting the AST (after indentations have already been set for the nodes in
1307
+ * the declaration) by manually increasing the indentation level of the tokens in this declarator
1308
+ * on the same line as the start of the declaration, provided that there are declarators that
1309
+ * follow this one.
1310
+ */
1311
+ const firstToken = sourceCode.getFirstToken(node);
1312
+
1313
+ offsets.setDesiredOffsets(node.range, firstToken, variableIndent, true);
1314
+ } else {
1315
+ offsets.setDesiredOffsets(node.range, sourceCode.getFirstToken(node), variableIndent);
1316
+ }
1278
1317
  const lastToken = sourceCode.getLastToken(node);
1279
1318
 
1280
1319
  if (astUtils.isSemicolonToken(lastToken)) {
@@ -1289,52 +1328,17 @@ module.exports = {
1289
1328
 
1290
1329
  offsets.ignoreToken(equalOperator);
1291
1330
  offsets.ignoreToken(tokenAfterOperator);
1292
- offsets.matchIndentOf(equalOperator, tokenAfterOperator);
1293
- offsets.matchIndentOf(sourceCode.getFirstToken(node), equalOperator);
1331
+ offsets.setDesiredOffsets([tokenAfterOperator.range[0], node.range[1]], equalOperator, 1);
1332
+ offsets.setDesiredOffset(equalOperator, sourceCode.getLastToken(node.id), 0);
1294
1333
  }
1295
1334
  },
1296
1335
 
1297
- "VariableDeclarator:exit"(node) {
1298
-
1299
- /*
1300
- * VariableDeclarator indentation is a bit different from other forms of indentation, in that the
1301
- * indentation of an opening bracket sometimes won't match that of a closing bracket. For example,
1302
- * the following indentations are correct:
1303
- *
1304
- * var foo = {
1305
- * ok: true
1306
- * };
1307
- *
1308
- * var foo = {
1309
- * ok: true,
1310
- * },
1311
- * bar = 1;
1312
- *
1313
- * Account for when exiting the AST (after indentations have already been set for the nodes in
1314
- * the declaration) by manually increasing the indentation level of the tokens in the first declarator if the
1315
- * parent declaration has more than one declarator.
1316
- */
1317
- if (node.parent.declarations.length > 1 && node.parent.declarations[0] === node && node.init) {
1318
- const valueTokens = new Set(getTokensAndComments(node.init));
1319
-
1320
- valueTokens.forEach(token => {
1321
- if (!valueTokens.has(offsets.getFirstDependency(token))) {
1322
- offsets.increaseOffset(token, options.VariableDeclarator[node.parent.kind]);
1323
- }
1324
- });
1325
- }
1326
- },
1327
-
1328
- WhileStatement: node => addBlocklessNodeIndent(node.body),
1329
-
1330
1336
  "*:exit": checkForUnknownNode,
1331
1337
 
1332
1338
  "JSXAttribute[value]"(node) {
1333
1339
  const equalsToken = sourceCode.getFirstTokenBetween(node.name, node.value, token => token.type === "Punctuator" && token.value === "=");
1334
- const firstNameToken = sourceCode.getFirstToken(node.name);
1335
1340
 
1336
- offsets.setDesiredOffset(equalsToken, firstNameToken, 1);
1337
- offsets.setDesiredOffset(sourceCode.getFirstToken(node.value), firstNameToken, 1);
1341
+ offsets.setDesiredOffsets([equalsToken.range[0], node.value.range[1]], sourceCode.getFirstToken(node.name), 1);
1338
1342
  },
1339
1343
 
1340
1344
  JSXElement(node) {
@@ -1349,30 +1353,31 @@ module.exports = {
1349
1353
 
1350
1354
  if (node.selfClosing) {
1351
1355
  closingToken = sourceCode.getLastToken(node, { skip: 1 });
1352
- offsets.matchIndentOf(closingToken, sourceCode.getLastToken(node));
1356
+ offsets.setDesiredOffset(sourceCode.getLastToken(node), closingToken, 0);
1353
1357
  } else {
1354
1358
  closingToken = sourceCode.getLastToken(node);
1355
1359
  }
1356
- offsets.setDesiredOffsets(getTokensAndComments(node.name), sourceCode.getFirstToken(node));
1360
+ offsets.setDesiredOffsets(node.name.range, sourceCode.getFirstToken(node));
1357
1361
  addElementListIndent(node.attributes, firstToken, closingToken, 1);
1358
1362
  },
1359
1363
 
1360
1364
  JSXClosingElement(node) {
1361
1365
  const firstToken = sourceCode.getFirstToken(node);
1362
1366
 
1363
- offsets.setDesiredOffsets(getTokensAndComments(node.name), firstToken, 1);
1364
- offsets.matchIndentOf(firstToken, sourceCode.getLastToken(node));
1367
+ offsets.setDesiredOffsets(node.name.range, firstToken, 1);
1368
+ offsets.setDesiredOffset(sourceCode.getLastToken(node), firstToken, 0);
1365
1369
  },
1366
1370
 
1367
1371
  JSXExpressionContainer(node) {
1368
1372
  const openingCurly = sourceCode.getFirstToken(node);
1369
- const firstExpressionToken = sourceCode.getFirstToken(node.expression);
1373
+ const closingCurly = sourceCode.getLastToken(node);
1370
1374
 
1371
- if (firstExpressionToken) {
1372
- offsets.setDesiredOffset(firstExpressionToken, openingCurly, 1);
1373
- }
1374
-
1375
- offsets.matchIndentOf(openingCurly, sourceCode.getLastToken(node));
1375
+ offsets.setDesiredOffsets(
1376
+ [openingCurly.range[1], closingCurly.range[0]],
1377
+ openingCurly,
1378
+ 1
1379
+ );
1380
+ offsets.setDesiredOffset(closingCurly, openingCurly, 0);
1376
1381
  },
1377
1382
 
1378
1383
  "Program:exit"() {