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