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