Skip to content

Commit 2141cb5

Browse files
author
Kent C. Dodds
committed
[Fix] jsx-indent with tabs (fixes jsx-eslint#1057)
1 parent c97dd0f commit 2141cb5

File tree

2 files changed

+64
-27
lines changed

2 files changed

+64
-27
lines changed

lib/rules/jsx-indent.js

+34-26
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ module.exports = {
154154
}
155155

156156
/**
157-
* Checks node is the first in its own start line. By default it looks by start line.
157+
* Checks if the node is the first in its own start line. By default it looks by start line.
158+
* One exception is closing tags with preceeding whitespace
158159
* @param {ASTNode} node The node to check
159160
* @return {Boolean} true if its the first in the its start line
160161
*/
@@ -165,8 +166,9 @@ module.exports = {
165166
} while (token.type === 'JSXText' && /^\s*$/.test(token.value));
166167
var startLine = node.loc.start.line;
167168
var endLine = token ? token.loc.end.line : -1;
169+
var whitespaceOnly = token ? /\n\s*$/.test(token.value) : false;
168170

169-
return startLine !== endLine;
171+
return startLine !== endLine || whitespaceOnly;
170172
}
171173

172174
/**
@@ -218,41 +220,47 @@ module.exports = {
218220
}
219221
}
220222

223+
function getOpeningElementIndent(node) {
224+
var prevToken = sourceCode.getTokenBefore(node);
225+
// Use the parent in a list or an array
226+
if (prevToken.type === 'JSXText' || prevToken.type === 'Punctuator' && prevToken.value === ',') {
227+
prevToken = sourceCode.getNodeByRangeIndex(prevToken.start);
228+
prevToken = prevToken.type === 'Literal' ? prevToken.parent : prevToken;
229+
// Use the first non-punctuator token in a conditional expression
230+
} else if (prevToken.type === 'Punctuator' && prevToken.value === ':') {
231+
do {
232+
prevToken = sourceCode.getTokenBefore(prevToken);
233+
} while (prevToken.type === 'Punctuator');
234+
prevToken = sourceCode.getNodeByRangeIndex(prevToken.start);
235+
while (prevToken.parent && prevToken.parent.type !== 'ConditionalExpression') {
236+
prevToken = prevToken.parent;
237+
}
238+
}
239+
prevToken = prevToken.type === 'JSXExpressionContainer' ? prevToken.expression : prevToken;
240+
241+
var parentElementIndent = getNodeIndent(prevToken);
242+
var indent = (
243+
prevToken.loc.start.line === node.loc.start.line ||
244+
isRightInLogicalExp(node) ||
245+
isAlternateInConditionalExp(node)
246+
) ? 0 : indentSize;
247+
return parentElementIndent + indent;
248+
}
249+
221250
return {
222251
JSXOpeningElement: function(node) {
223252
var prevToken = sourceCode.getTokenBefore(node);
224253
if (!prevToken) {
225254
return;
226255
}
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);
256+
var indent = getOpeningElementIndent(node, prevToken);
257+
checkNodesIndent(node, indent);
250258
},
251259
JSXClosingElement: function(node) {
252260
if (!node.parent) {
253261
return;
254262
}
255-
var peerElementIndent = getNodeIndent(node.parent.openingElement);
263+
var peerElementIndent = getOpeningElementIndent(node.parent.openingElement);
256264
checkNodesIndent(node, peerElementIndent);
257265
},
258266
JSXExpressionContainer: function(node) {

tests/lib/rules/jsx-indent.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,34 @@ ruleTester.run('jsx-indent', rule, {
459459
options: ['tab'],
460460
parserOptions: parserOptions,
461461
errors: [{message: 'Expected indentation of 1 tab character but found 0.'}]
462+
}, {
463+
code: [
464+
'function MyComponent(props) {',
465+
'\treturn (',
466+
' <div',
467+
'\t\t\tclassName="foo-bar"',
468+
'\t\t\tid="thing"',
469+
' >',
470+
' Hello world!',
471+
' </div>',
472+
'\t)',
473+
'}'
474+
].join('\n'),
475+
output: [
476+
'function MyComponent(props) {',
477+
'\treturn (',
478+
'\t\t<div',
479+
'\t\t\tclassName="foo-bar"',
480+
'\t\t\tid="thing"',
481+
'\t\t>',
482+
'\t\t\tHello world!',
483+
'\t\t</div>',
484+
'\t)',
485+
'}'
486+
].join('\n'),
487+
options: ['tab'],
488+
parserOptions: parserOptions,
489+
errors: [{message: 'Expected indentation of 2 tab characters but found 0.'}]
462490
}, {
463491
code: [
464492
'function App() {',
@@ -730,7 +758,8 @@ ruleTester.run('jsx-indent', rule, {
730758
].join('\n'),
731759
parserOptions: parserOptions,
732760
errors: [
733-
{message: 'Expected indentation of 4 space characters but found 0.'}
761+
{message: 'Expected indentation of 2 tab characters but found 0.'},
762+
{message: 'Expected indentation of 2 tab characters but found 0.'}
734763
]
735764
}, {
736765
// Multiline ternary

0 commit comments

Comments
 (0)