@@ -73,18 +73,16 @@ module.exports = {
73
73
74
74
/**
75
75
* Responsible for fixing the indentation issue fix
76
- * @param {ASTNode } node Node violating the indent rule
76
+ * @param {Boolean } rangeToReplace is used to specify the range
77
+ * to replace with the correct indentation.
77
78
* @param {Number } needed Expected indentation character count
78
79
* @returns {Function } function to be executed by the fixer
79
80
* @private
80
81
*/
81
- function getFixerFunction ( node , needed ) {
82
+ function getFixerFunction ( rangeToReplace , needed ) {
82
83
return function ( fixer ) {
83
84
var indent = Array ( needed + 1 ) . join ( indentChar ) ;
84
- return fixer . replaceTextRange (
85
- [ node . start - node . loc . start . column , node . start ] ,
86
- indent
87
- ) ;
85
+ return fixer . replaceTextRange ( rangeToReplace , indent ) ;
88
86
} ;
89
87
}
90
88
@@ -93,46 +91,38 @@ module.exports = {
93
91
* @param {ASTNode } node Node violating the indent rule
94
92
* @param {Number } needed Expected indentation character count
95
93
* @param {Number } gotten Indentation character count in the actual node/code
96
- * @param {Object } loc Error line and column location
94
+ * @param {Array } rangeToReplace is used in the fixer.
95
+ * Defaults to the indent of the start of the node
96
+ * @param {Object } loc Error line and column location (defaults to node.loc
97
97
*/
98
- function report ( node , needed , gotten , loc ) {
98
+ function report ( node , needed , gotten , rangeToReplace , loc ) {
99
99
var msgContext = {
100
100
needed : needed ,
101
101
type : indentType ,
102
102
characters : needed === 1 ? 'character' : 'characters' ,
103
103
gotten : gotten
104
104
} ;
105
+ rangeToReplace = rangeToReplace || [ node . start - node . loc . start . column , node . start ] ;
105
106
106
- if ( loc ) {
107
- context . report ( {
108
- node : node ,
109
- loc : loc ,
110
- message : MESSAGE ,
111
- data : msgContext ,
112
- fix : getFixerFunction ( node , needed )
113
- } ) ;
114
- } else {
115
- context . report ( {
116
- node : node ,
117
- message : MESSAGE ,
118
- data : msgContext ,
119
- fix : getFixerFunction ( node , needed )
120
- } ) ;
121
- }
107
+ context . report ( {
108
+ node : node ,
109
+ loc : loc || node . loc ,
110
+ message : MESSAGE ,
111
+ data : msgContext ,
112
+ fix : getFixerFunction ( rangeToReplace , needed )
113
+ } ) ;
122
114
}
123
115
124
116
/**
125
- * Get node indent
126
- * @param {ASTNode } node Node to examine
127
- * @param {Boolean } byLastLine get indent of node's last line
128
- * @param {Boolean } excludeCommas skip comma on start of line
129
- * @return {Number } Indent
117
+ * Get the indentation (of the proper indentType) that exists in the source
118
+ * @param {String } src the source string
119
+ * @param {Boolean } byLastLine whether the line checked should be the last
120
+ * Defaults to the first line
121
+ * @param {Boolean } excludeCommas whether to skip commas in the check
122
+ * Defaults to false
123
+ * @return {Number } the indentation of the indentType that exists on the line
130
124
*/
131
- function getNodeIndent ( node , byLastLine , excludeCommas ) {
132
- byLastLine = byLastLine || false ;
133
- excludeCommas = excludeCommas || false ;
134
-
135
- var src = sourceCode . getText ( node , node . loc . start . column + extraColumnStart ) ;
125
+ function getIndentFromString ( src , byLastLine , excludeCommas ) {
136
126
var lines = src . split ( '\n' ) ;
137
127
if ( byLastLine ) {
138
128
src = lines [ lines . length - 1 ] ;
@@ -154,7 +144,24 @@ module.exports = {
154
144
}
155
145
156
146
/**
157
- * Checks node is the first in its own start line. By default it looks by start line.
147
+ * Get node indent
148
+ * @param {ASTNode } node Node to examine
149
+ * @param {Boolean } byLastLine get indent of node's last line
150
+ * @param {Boolean } excludeCommas skip comma on start of line
151
+ * @return {Number } Indent
152
+ */
153
+ function getNodeIndent ( node , byLastLine , excludeCommas ) {
154
+ byLastLine = byLastLine || false ;
155
+ excludeCommas = excludeCommas || false ;
156
+
157
+ var src = sourceCode . getText ( node , node . loc . start . column + extraColumnStart ) ;
158
+
159
+ return getIndentFromString ( src , byLastLine , excludeCommas ) ;
160
+ }
161
+
162
+ /**
163
+ * Checks if the node is the first in its own start line. By default it looks by start line.
164
+ * One exception is closing tags with preceeding whitespace
158
165
* @param {ASTNode } node The node to check
159
166
* @return {Boolean } true if its the first in the its start line
160
167
*/
@@ -165,8 +172,9 @@ module.exports = {
165
172
} while ( token . type === 'JSXText' && / ^ \s * $ / . test ( token . value ) ) ;
166
173
var startLine = node . loc . start . line ;
167
174
var endLine = token ? token . loc . end . line : - 1 ;
175
+ var whitespaceOnly = token ? / \n \s * $ / . test ( token . value ) : false ;
168
176
169
- return startLine !== endLine ;
177
+ return startLine !== endLine || whitespaceOnly ;
170
178
}
171
179
172
180
/**
@@ -218,41 +226,78 @@ module.exports = {
218
226
}
219
227
}
220
228
229
+ /**
230
+ * Checks the end of the tag (>) to determine whether it's on its own line
231
+ * If so, it verifies the indentation is correct and reports if it is not
232
+ * @param {ASTNode } node The node to check
233
+ * @param {Number } startIndent The indentation of the start of the tag
234
+ */
235
+ function checkTagEndIndent ( node , startIndent ) {
236
+ var source = sourceCode . getText ( node ) ;
237
+ var isTagEndOnOwnLine = / \n \s * \/ ? > $ / . exec ( source ) ;
238
+ if ( isTagEndOnOwnLine ) {
239
+ var endIndent = getIndentFromString ( source , true , false ) ;
240
+ if ( endIndent !== startIndent ) {
241
+ var rangeToReplace = [ node . end - node . loc . end . column , node . end - 1 ] ;
242
+ report ( node , startIndent , endIndent , rangeToReplace ) ;
243
+ }
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Gets what the JSXOpeningElement's indentation should be
249
+ * @param {ASTNode } node The JSXOpeningElement
250
+ * @return {Number } the number of indentation characters it should have
251
+ */
252
+ function getOpeningElementIndent ( node ) {
253
+ var prevToken = sourceCode . getTokenBefore ( node ) ;
254
+ if ( ! prevToken ) {
255
+ return 0 ;
256
+ }
257
+ // Use the parent in a list or an array
258
+ if ( prevToken . type === 'JSXText' || prevToken . type === 'Punctuator' && prevToken . value === ',' ) {
259
+ prevToken = sourceCode . getNodeByRangeIndex ( prevToken . start ) ;
260
+ prevToken = prevToken . type === 'Literal' ? prevToken . parent : prevToken ;
261
+ // Use the first non-punctuator token in a conditional expression
262
+ } else if ( prevToken . type === 'Punctuator' && prevToken . value === ':' ) {
263
+ do {
264
+ prevToken = sourceCode . getTokenBefore ( prevToken ) ;
265
+ } while ( prevToken . type === 'Punctuator' ) ;
266
+ prevToken = sourceCode . getNodeByRangeIndex ( prevToken . start ) ;
267
+ while ( prevToken . parent && prevToken . parent . type !== 'ConditionalExpression' ) {
268
+ prevToken = prevToken . parent ;
269
+ }
270
+ }
271
+ prevToken = prevToken . type === 'JSXExpressionContainer' ? prevToken . expression : prevToken ;
272
+
273
+ var parentElementIndent = getNodeIndent ( prevToken ) ;
274
+ if ( prevToken . type === 'JSXElement' ) {
275
+ parentElementIndent = getOpeningElementIndent ( prevToken . openingElement ) ;
276
+ }
277
+
278
+ var indent = (
279
+ prevToken . loc . start . line === node . loc . start . line ||
280
+ isRightInLogicalExp ( node ) ||
281
+ isAlternateInConditionalExp ( node )
282
+ ) ? 0 : indentSize ;
283
+ return parentElementIndent + indent ;
284
+ }
285
+
221
286
return {
222
287
JSXOpeningElement : function ( node ) {
223
288
var prevToken = sourceCode . getTokenBefore ( node ) ;
224
289
if ( ! prevToken ) {
225
290
return ;
226
291
}
227
- // Use the parent in a list or an array
228
- if ( prevToken . type === 'JSXText' || prevToken . type === 'Punctuator' && prevToken . value === ',' ) {
229
- prevToken = sourceCode . getNodeByRangeIndex ( prevToken . start ) ;
230
- prevToken = prevToken . type === 'Literal' ? prevToken . parent : prevToken ;
231
- // Use the first non-punctuator token in a conditional expression
232
- } else if ( prevToken . type === 'Punctuator' && prevToken . value === ':' ) {
233
- do {
234
- prevToken = sourceCode . getTokenBefore ( prevToken ) ;
235
- } while ( prevToken . type === 'Punctuator' ) ;
236
- prevToken = sourceCode . getNodeByRangeIndex ( prevToken . start ) ;
237
- while ( prevToken . parent && prevToken . parent . type !== 'ConditionalExpression' ) {
238
- prevToken = prevToken . parent ;
239
- }
240
- }
241
- prevToken = prevToken . type === 'JSXExpressionContainer' ? prevToken . expression : prevToken ;
242
-
243
- var parentElementIndent = getNodeIndent ( prevToken ) ;
244
- var indent = (
245
- prevToken . loc . start . line === node . loc . start . line ||
246
- isRightInLogicalExp ( node ) ||
247
- isAlternateInConditionalExp ( node )
248
- ) ? 0 : indentSize ;
249
- checkNodesIndent ( node , parentElementIndent + indent ) ;
292
+ var startIndent = getOpeningElementIndent ( node ) ;
293
+ checkNodesIndent ( node , startIndent ) ;
294
+ checkTagEndIndent ( node , startIndent ) ;
250
295
} ,
251
296
JSXClosingElement : function ( node ) {
252
297
if ( ! node . parent ) {
253
298
return ;
254
299
}
255
- var peerElementIndent = getNodeIndent ( node . parent . openingElement ) ;
300
+ var peerElementIndent = getOpeningElementIndent ( node . parent . openingElement ) ;
256
301
checkNodesIndent ( node , peerElementIndent ) ;
257
302
} ,
258
303
JSXExpressionContainer : function ( node ) {
@@ -261,6 +306,34 @@ module.exports = {
261
306
}
262
307
var parentNodeIndent = getNodeIndent ( node . parent ) ;
263
308
checkNodesIndent ( node , parentNodeIndent + indentSize ) ;
309
+ } ,
310
+ Literal : function ( node ) {
311
+ if ( ! node . parent || node . parent . type !== 'JSXElement' ) {
312
+ return ;
313
+ }
314
+ var parentElementIndent = getOpeningElementIndent ( node . parent . openingElement ) ;
315
+ var expectedIndent = parentElementIndent + indentSize ;
316
+ var source = sourceCode . getText ( node ) ;
317
+ var lines = source . split ( '\n' ) ;
318
+ var currentIndex = 0 ;
319
+ lines . forEach ( function ( line , lineNumber ) {
320
+ if ( line . trim ( ) ) {
321
+ var lineIndent = getIndentFromString ( line ) ;
322
+ if ( lineIndent !== expectedIndent ) {
323
+ var lineStart = source . indexOf ( line , currentIndex ) ;
324
+ var lineIndentStart = line . search ( / \S / ) ;
325
+ var lineIndentEnd = lineStart + lineIndentStart ;
326
+ var rangeToReplace = [ node . start + lineStart , node . start + lineIndentEnd ] ;
327
+ var locLine = lineNumber + node . loc . start . line ;
328
+ var loc = {
329
+ start : { line : locLine , column : lineIndentStart } ,
330
+ end : { line : locLine , column : lineIndentEnd }
331
+ } ;
332
+ report ( node , expectedIndent , lineIndent , rangeToReplace , loc ) ;
333
+ }
334
+ }
335
+ currentIndex += line . length ;
336
+ } ) ;
264
337
}
265
338
} ;
266
339
0 commit comments