@@ -75,16 +75,18 @@ module.exports = {
75
75
* Responsible for fixing the indentation issue fix
76
76
* @param {ASTNode } node Node violating the indent rule
77
77
* @param {Number } needed Expected indentation character count
78
+ * @param {Boolean } isEndOfTag is used to indent the right thing
78
79
* @returns {Function } function to be executed by the fixer
79
80
* @private
80
81
*/
81
- function getFixerFunction ( node , needed ) {
82
+ function getFixerFunction ( node , needed , isEndOfTag ) {
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
+ var rangeToReplace = [ node . start - node . loc . start . column , node . start ] ;
86
+ if ( isEndOfTag ) {
87
+ rangeToReplace = [ node . end - node . loc . end . column , node . end - 1 ] ;
88
+ }
89
+ return fixer . replaceTextRange ( rangeToReplace , indent ) ;
88
90
} ;
89
91
}
90
92
@@ -93,46 +95,32 @@ module.exports = {
93
95
* @param {ASTNode } node Node violating the indent rule
94
96
* @param {Number } needed Expected indentation character count
95
97
* @param {Number } gotten Indentation character count in the actual node/code
96
- * @param {Object } loc Error line and column location
98
+ * @param {Boolean } isEndOfTag which is used in the fixer function
97
99
*/
98
- function report ( node , needed , gotten , loc ) {
100
+ function report ( node , needed , gotten , isEndOfTag ) {
99
101
var msgContext = {
100
102
needed : needed ,
101
103
type : indentType ,
102
104
characters : needed === 1 ? 'character' : 'characters' ,
103
105
gotten : gotten
104
106
} ;
105
107
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
- }
108
+ context . report ( {
109
+ node : node ,
110
+ message : MESSAGE ,
111
+ data : msgContext ,
112
+ fix : getFixerFunction ( node , needed , isEndOfTag )
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,73 @@ 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
+ report ( node , startIndent , endIndent , true ) ;
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 ) {
0 commit comments