@@ -121,8 +121,6 @@ module.exports = {
121
121
} ,
122
122
123
123
create ( context ) {
124
-
125
- const MESSAGE = "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}." ;
126
124
const DEFAULT_VARIABLE_INDENT = 1 ;
127
125
const DEFAULT_PARAMETER_INDENT = null ; // For backwards compatibility, don't check parameter indentation unless specified in the config
128
126
const DEFAULT_FUNCTION_BODY_INDENT = 1 ;
@@ -192,108 +190,85 @@ module.exports = {
192
190
}
193
191
}
194
192
195
- const indentPattern = {
196
- normal : indentType === "space" ? / ^ + / : / ^ \t + / ,
197
- excludeCommas : indentType === "space" ? / ^ [ , ] + / : / ^ [ \t , ] + /
198
- } ;
199
-
200
193
const caseIndentStore = { } ;
201
194
202
195
/**
203
- * Reports a given indent violation and properly pluralizes the message
196
+ * Creates an error message for a line, given the expected/actual indentation.
197
+ * @param {int } expectedAmount The expected amount of indentation characters for this line
198
+ * @param {int } actualSpaces The actual number of indentation spaces that were found on this line
199
+ * @param {int } actualTabs The actual number of indentation tabs that were found on this line
200
+ * @returns {string } An error message for this line
201
+ */
202
+ function createErrorMessage ( expectedAmount , actualSpaces , actualTabs ) {
203
+ const expectedStatement = `${ expectedAmount } ${ indentType } ${ expectedAmount === 1 ? "" : "s" } ` ; // e.g. "2 tabs"
204
+ const foundSpacesWord = `space${ actualSpaces === 1 ? "" : "s" } ` ; // e.g. "space"
205
+ const foundTabsWord = `tab${ actualTabs === 1 ? "" : "s" } ` ; // e.g. "tabs"
206
+ let foundStatement ;
207
+
208
+ if ( actualSpaces > 0 && actualTabs > 0 ) {
209
+ foundStatement = `${ actualSpaces } ${ foundSpacesWord } and ${ actualTabs } ${ foundTabsWord } ` ; // e.g. "1 space and 2 tabs"
210
+ } else if ( actualSpaces > 0 ) {
211
+
212
+ // Abbreviate the message if the expected indentation is also spaces.
213
+ // e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
214
+ foundStatement = indentType === "space" ? actualSpaces : `${ actualSpaces } ${ foundSpacesWord } ` ;
215
+ } else if ( actualTabs > 0 ) {
216
+ foundStatement = indentType === "tab" ? actualTabs : `${ actualTabs } ${ foundTabsWord } ` ;
217
+ } else {
218
+ foundStatement = "0" ;
219
+ }
220
+
221
+ return `Expected indentation of ${ expectedStatement } but found ${ foundStatement } .` ;
222
+ }
223
+
224
+ /**
225
+ * Reports a given indent violation
204
226
* @param {ASTNode } node Node violating the indent rule
205
227
* @param {int } needed Expected indentation character count
206
- * @param {int } gotten Indentation character count in the actual node/code
228
+ * @param {int } gottenSpaces Indentation space count in the actual node/code
229
+ * @param {int } gottenTabs Indentation tab count in the actual node/code
207
230
* @param {Object= } loc Error line and column location
208
231
* @param {boolean } isLastNodeCheck Is the error for last node check
209
232
* @returns {void }
210
233
*/
211
- function report ( node , needed , gotten , loc , isLastNodeCheck ) {
212
- const msgContext = {
213
- needed,
214
- type : indentType ,
215
- characters : needed === 1 ? "character" : "characters" ,
216
- gotten
217
- } ;
218
- const indentChar = indentType === "space" ? " " : "\t" ;
219
-
220
- /**
221
- * Responsible for fixing the indentation issue fix
222
- * @returns {Function } function to be executed by the fixer
223
- * @private
224
- */
225
- function getFixerFunction ( ) {
226
- let rangeToFix = [ ] ;
227
-
228
- if ( needed > gotten ) {
229
- const spaces = indentChar . repeat ( needed - gotten ) ;
230
-
231
- if ( isLastNodeCheck === true ) {
232
- rangeToFix = [
233
- node . range [ 1 ] - 1 ,
234
- node . range [ 1 ] - 1
235
- ] ;
236
- } else {
237
- rangeToFix = [
238
- node . range [ 0 ] ,
239
- node . range [ 0 ]
240
- ] ;
241
- }
234
+ function report ( node , needed , gottenSpaces , gottenTabs , loc , isLastNodeCheck ) {
242
235
243
- return function ( fixer ) {
244
- return fixer . insertTextBeforeRange ( rangeToFix , spaces ) ;
245
- } ;
246
- } else {
247
- if ( isLastNodeCheck === true ) {
248
- rangeToFix = [
249
- node . range [ 1 ] - ( gotten - needed ) - 1 ,
250
- node . range [ 1 ] - 1
251
- ] ;
252
- } else {
253
- rangeToFix = [
254
- node . range [ 0 ] - ( gotten - needed ) ,
255
- node . range [ 0 ]
256
- ] ;
257
- }
236
+ const desiredIndent = ( indentType === "space" ? " " : "\t" ) . repeat ( needed ) ;
258
237
259
- return function ( fixer ) {
260
- return fixer . removeRange ( rangeToFix ) ;
261
- } ;
262
- }
263
- }
238
+ const textRange = isLastNodeCheck
239
+ ? [ node . range [ 1 ] - gottenSpaces - gottenTabs - 1 , node . range [ 1 ] - 1 ]
240
+ : [ node . range [ 0 ] - gottenSpaces - gottenTabs , node . range [ 0 ] ] ;
264
241
265
- if ( loc ) {
266
- context . report ( {
267
- node,
268
- loc,
269
- message : MESSAGE ,
270
- data : msgContext ,
271
- fix : getFixerFunction ( )
272
- } ) ;
273
- } else {
274
- context . report ( {
275
- node,
276
- message : MESSAGE ,
277
- data : msgContext ,
278
- fix : getFixerFunction ( )
279
- } ) ;
280
- }
242
+ context . report ( {
243
+ node,
244
+ loc,
245
+ message : createErrorMessage ( needed , gottenSpaces , gottenTabs ) ,
246
+ fix : fixer => fixer . replaceTextRange ( textRange , desiredIndent )
247
+ } ) ;
281
248
}
282
249
283
250
/**
284
251
* Get the actual indent of node
285
252
* @param {ASTNode|Token } node Node to examine
286
253
* @param {boolean } [byLastLine=false] get indent of node's last line
287
254
* @param {boolean } [excludeCommas=false] skip comma on start of line
288
- * @returns {int } Indent
255
+ * @returns {Object } The node's indent. Contains keys `space` and `tab`, representing the indent of each character. Also
256
+ contains keys `goodChar` and `badChar`, where `goodChar` is the amount of the user's desired indentation character, and
257
+ `badChar` is the amount of the other indentation character.
289
258
*/
290
- function getNodeIndent ( node , byLastLine , excludeCommas ) {
259
+ function getNodeIndent ( node , byLastLine ) {
291
260
const token = byLastLine ? sourceCode . getLastToken ( node ) : sourceCode . getFirstToken ( node ) ;
292
- const src = sourceCode . getText ( token , token . loc . start . column ) ;
293
- const regExp = excludeCommas ? indentPattern . excludeCommas : indentPattern . normal ;
294
- const indent = regExp . exec ( src ) ;
295
-
296
- return indent ? indent [ 0 ] . length : 0 ;
261
+ const srcCharsBeforeNode = sourceCode . getText ( token , token . loc . start . column ) . split ( "" ) ;
262
+ const indentChars = srcCharsBeforeNode . slice ( 0 , srcCharsBeforeNode . findIndex ( char => char !== " " && char !== "\t" ) ) ;
263
+ const spaces = indentChars . filter ( char => char === " " ) . length ;
264
+ const tabs = indentChars . filter ( char => char === "\t" ) . length ;
265
+
266
+ return {
267
+ space : spaces ,
268
+ tab : tabs ,
269
+ goodChar : indentType === "space" ? spaces : tabs ,
270
+ badChar : indentType === "space" ? tabs : spaces
271
+ } ;
297
272
}
298
273
299
274
/**
@@ -313,27 +288,29 @@ module.exports = {
313
288
/**
314
289
* Check indent for node
315
290
* @param {ASTNode } node Node to check
316
- * @param {int } indent needed indent
291
+ * @param {int } neededIndent needed indent
317
292
* @param {boolean } [excludeCommas=false] skip comma on start of line
318
293
* @returns {void }
319
294
*/
320
- function checkNodeIndent ( node , indent , excludeCommas ) {
321
- const nodeIndent = getNodeIndent ( node , false , excludeCommas ) ;
295
+ function checkNodeIndent ( node , neededIndent ) {
296
+ const actualIndent = getNodeIndent ( node , false ) ;
322
297
323
298
if (
324
- node . type !== "ArrayExpression" && node . type !== "ObjectExpression" &&
325
- nodeIndent !== indent && isNodeFirstInLine ( node )
299
+ node . type !== "ArrayExpression" &&
300
+ node . type !== "ObjectExpression" &&
301
+ ( actualIndent . goodChar !== neededIndent || actualIndent . badChar !== 0 ) &&
302
+ isNodeFirstInLine ( node )
326
303
) {
327
- report ( node , indent , nodeIndent ) ;
304
+ report ( node , neededIndent , actualIndent . space , actualIndent . tab ) ;
328
305
}
329
306
330
307
if ( node . type === "IfStatement" && node . alternate ) {
331
308
const elseToken = sourceCode . getTokenBefore ( node . alternate ) ;
332
309
333
- checkNodeIndent ( elseToken , indent , excludeCommas ) ;
310
+ checkNodeIndent ( elseToken , neededIndent ) ;
334
311
335
312
if ( ! isNodeFirstInLine ( node . alternate ) ) {
336
- checkNodeIndent ( node . alternate , indent , excludeCommas ) ;
313
+ checkNodeIndent ( node . alternate , neededIndent ) ;
337
314
}
338
315
}
339
316
}
@@ -345,8 +322,8 @@ module.exports = {
345
322
* @param {boolean } [excludeCommas=false] skip comma on start of line
346
323
* @returns {void }
347
324
*/
348
- function checkNodesIndent ( nodes , indent , excludeCommas ) {
349
- nodes . forEach ( node => checkNodeIndent ( node , indent , excludeCommas ) ) ;
325
+ function checkNodesIndent ( nodes , indent ) {
326
+ nodes . forEach ( node => checkNodeIndent ( node , indent ) ) ;
350
327
}
351
328
352
329
/**
@@ -359,11 +336,12 @@ module.exports = {
359
336
const lastToken = sourceCode . getLastToken ( node ) ;
360
337
const endIndent = getNodeIndent ( lastToken , true ) ;
361
338
362
- if ( endIndent !== lastLineIndent && isNodeFirstInLine ( node , true ) ) {
339
+ if ( ( endIndent . goodChar !== lastLineIndent || endIndent . badChar !== 0 ) && isNodeFirstInLine ( node , true ) ) {
363
340
report (
364
341
node ,
365
342
lastLineIndent ,
366
- endIndent ,
343
+ endIndent . space ,
344
+ endIndent . tab ,
367
345
{ line : lastToken . loc . start . line , column : lastToken . loc . start . column } ,
368
346
true
369
347
) ;
@@ -379,11 +357,12 @@ module.exports = {
379
357
function checkFirstNodeLineIndent ( node , firstLineIndent ) {
380
358
const startIndent = getNodeIndent ( node , false ) ;
381
359
382
- if ( startIndent !== firstLineIndent && isNodeFirstInLine ( node ) ) {
360
+ if ( ( startIndent . goodChar !== firstLineIndent || startIndent . badChar !== 0 ) && isNodeFirstInLine ( node ) ) {
383
361
report (
384
362
node ,
385
363
firstLineIndent ,
386
- startIndent ,
364
+ startIndent . space ,
365
+ startIndent . tab ,
387
366
{ line : node . loc . start . line , column : node . loc . start . column }
388
367
) ;
389
368
}
@@ -526,25 +505,25 @@ module.exports = {
526
505
calleeNode . parent . type === "ArrayExpression" ) ) {
527
506
528
507
// If function is part of array or object, comma can be put at left
529
- indent = getNodeIndent ( calleeNode , false , false ) ;
508
+ indent = getNodeIndent ( calleeNode , false , false ) . goodChar ;
530
509
} else {
531
510
532
511
// If function is standalone, simple calculate indent
533
- indent = getNodeIndent ( calleeNode ) ;
512
+ indent = getNodeIndent ( calleeNode ) . goodChar ;
534
513
}
535
514
536
515
if ( calleeNode . parent . type === "CallExpression" ) {
537
516
const calleeParent = calleeNode . parent ;
538
517
539
518
if ( calleeNode . type !== "FunctionExpression" && calleeNode . type !== "ArrowFunctionExpression" ) {
540
519
if ( calleeParent && calleeParent . loc . start . line < node . loc . start . line ) {
541
- indent = getNodeIndent ( calleeParent ) ;
520
+ indent = getNodeIndent ( calleeParent ) . goodChar ;
542
521
}
543
522
} else {
544
523
if ( isArgBeforeCalleeNodeMultiline ( calleeNode ) &&
545
524
calleeParent . callee . loc . start . line === calleeParent . callee . loc . end . line &&
546
525
! isNodeFirstInLine ( calleeNode ) ) {
547
- indent = getNodeIndent ( calleeParent ) ;
526
+ indent = getNodeIndent ( calleeParent ) . goodChar ;
548
527
}
549
528
}
550
529
}
@@ -644,7 +623,7 @@ module.exports = {
644
623
effectiveParent = parent . parent ;
645
624
}
646
625
}
647
- nodeIndent = getNodeIndent ( effectiveParent ) ;
626
+ nodeIndent = getNodeIndent ( effectiveParent ) . goodChar ;
648
627
if ( parentVarNode && parentVarNode . loc . start . line !== node . loc . start . line ) {
649
628
if ( parent . type !== "VariableDeclarator" || parentVarNode === parentVarNode . parent . declarations [ 0 ] ) {
650
629
if ( parent . type === "VariableDeclarator" && parentVarNode . loc . start . line === effectiveParent . loc . start . line ) {
@@ -668,7 +647,7 @@ module.exports = {
668
647
669
648
checkFirstNodeLineIndent ( node , nodeIndent ) ;
670
649
} else {
671
- nodeIndent = getNodeIndent ( node ) ;
650
+ nodeIndent = getNodeIndent ( node ) . goodChar ;
672
651
elementsIndent = nodeIndent + indentSize ;
673
652
}
674
653
@@ -680,8 +659,7 @@ module.exports = {
680
659
elementsIndent += indentSize * options . VariableDeclarator [ parentVarNode . parent . kind ] ;
681
660
}
682
661
683
- // Comma can be placed before property name
684
- checkNodesIndent ( elements , elementsIndent , true ) ;
662
+ checkNodesIndent ( elements , elementsIndent ) ;
685
663
686
664
if ( elements . length > 0 ) {
687
665
@@ -737,9 +715,9 @@ module.exports = {
737
715
] ;
738
716
739
717
if ( node . parent && statementsWithProperties . indexOf ( node . parent . type ) !== - 1 && isNodeBodyBlock ( node ) ) {
740
- indent = getNodeIndent ( node . parent ) ;
718
+ indent = getNodeIndent ( node . parent ) . goodChar ;
741
719
} else {
742
- indent = getNodeIndent ( node ) ;
720
+ indent = getNodeIndent ( node ) . goodChar ;
743
721
}
744
722
745
723
if ( node . type === "IfStatement" && node . consequent . type !== "BlockStatement" ) {
@@ -785,13 +763,12 @@ module.exports = {
785
763
*/
786
764
function checkIndentInVariableDeclarations ( node ) {
787
765
const elements = filterOutSameLineVars ( node ) ;
788
- const nodeIndent = getNodeIndent ( node ) ;
766
+ const nodeIndent = getNodeIndent ( node ) . goodChar ;
789
767
const lastElement = elements [ elements . length - 1 ] ;
790
768
791
769
const elementsIndent = nodeIndent + indentSize * options . VariableDeclarator [ node . kind ] ;
792
770
793
- // Comma can be placed before declaration
794
- checkNodesIndent ( elements , elementsIndent , true ) ;
771
+ checkNodesIndent ( elements , elementsIndent ) ;
795
772
796
773
// Only check the last line if there is any token after the last item
797
774
if ( sourceCode . getLastToken ( node ) . loc . end . line <= lastElement . loc . end . line ) {
@@ -803,7 +780,7 @@ module.exports = {
803
780
if ( tokenBeforeLastElement . value === "," ) {
804
781
805
782
// Special case for comma-first syntax where the semicolon is indented
806
- checkLastNodeLineIndent ( node , getNodeIndent ( tokenBeforeLastElement ) ) ;
783
+ checkLastNodeLineIndent ( node , getNodeIndent ( tokenBeforeLastElement ) . goodChar ) ;
807
784
} else {
808
785
checkLastNodeLineIndent ( node , elementsIndent - indentSize ) ;
809
786
}
@@ -835,7 +812,7 @@ module.exports = {
835
812
return caseIndentStore [ switchNode . loc . start . line ] ;
836
813
} else {
837
814
if ( typeof switchIndent === "undefined" ) {
838
- switchIndent = getNodeIndent ( switchNode ) ;
815
+ switchIndent = getNodeIndent ( switchNode ) . goodChar ;
839
816
}
840
817
841
818
if ( switchNode . cases . length > 0 && options . SwitchCase === 0 ) {
@@ -854,7 +831,7 @@ module.exports = {
854
831
if ( node . body . length > 0 ) {
855
832
856
833
// Root nodes should have no indent
857
- checkNodesIndent ( node . body , getNodeIndent ( node ) ) ;
834
+ checkNodesIndent ( node . body , getNodeIndent ( node ) . goodChar ) ;
858
835
}
859
836
} ,
860
837
@@ -913,7 +890,7 @@ module.exports = {
913
890
return ;
914
891
}
915
892
916
- const propertyIndent = getNodeIndent ( node ) + indentSize * options . MemberExpression ;
893
+ const propertyIndent = getNodeIndent ( node ) . goodChar + indentSize * options . MemberExpression ;
917
894
918
895
const checkNodes = [ node . property ] ;
919
896
@@ -929,7 +906,7 @@ module.exports = {
929
906
SwitchStatement ( node ) {
930
907
931
908
// Switch is not a 'BlockStatement'
932
- const switchIndent = getNodeIndent ( node ) ;
909
+ const switchIndent = getNodeIndent ( node ) . goodChar ;
933
910
const caseIndent = expectedCaseIndent ( node , switchIndent ) ;
934
911
935
912
checkNodesIndent ( node . cases , caseIndent ) ;
0 commit comments