Skip to content

Commit 771f534

Browse files
authored
Merge pull request #1539 from jseminck/jsx-indent-bug
Fix alignment bug in jsx-indent (and extract duplicate code)
2 parents 27b8279 + acc4f24 commit 771f534

File tree

5 files changed

+69
-80
lines changed

5 files changed

+69
-80
lines changed

lib/rules/jsx-closing-tag-location.js

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55
'use strict';
66

7+
const astUtil = require('../util/ast');
8+
79
// ------------------------------------------------------------------------------
810
// Rule Definition
911
// ------------------------------------------------------------------------------
@@ -18,31 +20,6 @@ module.exports = {
1820
},
1921

2022
create: function(context) {
21-
const sourceCode = context.getSourceCode();
22-
23-
/**
24-
* Checks if the node is the first in its line, excluding whitespace.
25-
* @param {ASTNode} node The node to check
26-
* @return {Boolean} true if its the first node in its line
27-
*/
28-
function isNodeFirstInLine(node) {
29-
let token = node;
30-
let lines;
31-
do {
32-
token = sourceCode.getTokenBefore(token);
33-
lines = token.type === 'JSXText'
34-
? token.value.split('\n')
35-
: null;
36-
} while (
37-
token.type === 'JSXText' &&
38-
/^\s*$/.test(lines[lines.length - 1])
39-
);
40-
41-
const startLine = node.loc.start.line;
42-
const endLine = token ? token.loc.end.line : -1;
43-
return startLine !== endLine;
44-
}
45-
4623
return {
4724
JSXClosingElement: function(node) {
4825
if (!node.parent) {
@@ -59,7 +36,7 @@ module.exports = {
5936
}
6037

6138
let message;
62-
if (!isNodeFirstInLine(node)) {
39+
if (!astUtil.isNodeFirstInLine(context, node)) {
6340
message = 'Closing tag of a multiline JSX expression must be on its own line.';
6441
} else {
6542
message = 'Expected closing tag to match indentation of opening.';
@@ -71,7 +48,7 @@ module.exports = {
7148
message,
7249
fix: function(fixer) {
7350
const indent = Array(opening.loc.start.column + 1).join(' ');
74-
if (isNodeFirstInLine(node)) {
51+
if (astUtil.isNodeFirstInLine(context, node)) {
7552
return fixer.replaceTextRange(
7653
[node.range[0] - node.loc.start.column, node.range[0]],
7754
indent

lib/rules/jsx-indent-props.js

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
*/
3030
'use strict';
3131

32+
const astUtil = require('../util/ast');
33+
3234
// ------------------------------------------------------------------------------
3335
// Rule Definition
3436
// ------------------------------------------------------------------------------
@@ -136,20 +138,6 @@ module.exports = {
136138
return indent ? indent[0].length : 0;
137139
}
138140

139-
/**
140-
* Checks node is the first in its own start line. By default it looks by start line.
141-
* @param {ASTNode} node The node to check
142-
* @param {Boolean} [byEndLocation] Lookup based on start position or end
143-
* @return {Boolean} true if its the first in the its start line
144-
*/
145-
function isNodeFirstInLine(node, byEndLocation) {
146-
const firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node);
147-
const startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line;
148-
const endLine = firstToken ? firstToken.loc.end.line : -1;
149-
150-
return startLine !== endLine;
151-
}
152-
153141
/**
154142
* Check indent for nodes list
155143
* @param {ASTNode[]} nodes list of node objects
@@ -161,7 +149,7 @@ module.exports = {
161149
const nodeIndent = getNodeIndent(node, false, excludeCommas);
162150
if (
163151
node.type !== 'ArrayExpression' && node.type !== 'ObjectExpression' &&
164-
nodeIndent !== indent && isNodeFirstInLine(node)
152+
nodeIndent !== indent && astUtil.isNodeFirstInLine(context, node)
165153
) {
166154
report(node, indent, nodeIndent);
167155
}

lib/rules/jsx-indent.js

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
*/
3030
'use strict';
3131

32+
const astUtil = require('../util/ast');
33+
3234
// ------------------------------------------------------------------------------
3335
// Rule Definition
3436
// ------------------------------------------------------------------------------
@@ -152,22 +154,6 @@ module.exports = {
152154
return indent ? indent[0].length : 0;
153155
}
154156

155-
/**
156-
* Checks node is the first in its own start line. By default it looks by start line.
157-
* @param {ASTNode} node The node to check
158-
* @return {Boolean} true if its the first in the its start line
159-
*/
160-
function isNodeFirstInLine(node) {
161-
let token = node;
162-
do {
163-
token = sourceCode.getTokenBefore(token);
164-
} while (token.type === 'JSXText' && /^\s*$/.test(token.value));
165-
const startLine = node.loc.start.line;
166-
const endLine = token ? token.loc.end.line : -1;
167-
168-
return startLine !== endLine;
169-
}
170-
171157
/**
172158
* Check if the node is the right member of a logical expression
173159
* @param {ASTNode} node The node to check
@@ -209,7 +195,7 @@ module.exports = {
209195
const isCorrectAlternateInCondExp = isAlternateInConditionalExp(node) && (nodeIndent - indent) === 0;
210196
if (
211197
nodeIndent !== indent &&
212-
isNodeFirstInLine(node) &&
198+
astUtil.isNodeFirstInLine(context, node) &&
213199
!isCorrectRightInLogicalExp &&
214200
!isCorrectAlternateInCondExp
215201
) {

lib/util/ast.js

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,30 @@
33
*/
44
'use strict';
55

6+
/**
7+
* Find a return statment in the current node
8+
*
9+
* @param {ASTNode} ASTnode The AST node being checked
10+
*/
11+
function findReturnStatement(node) {
12+
if (
13+
(!node.value || !node.value.body || !node.value.body.body) &&
14+
(!node.body || !node.body.body)
15+
) {
16+
return false;
17+
}
18+
19+
const bodyNodes = (node.value ? node.value.body.body : node.body.body);
20+
21+
let i = bodyNodes.length - 1;
22+
for (; i >= 0; i--) {
23+
if (bodyNodes[i].type === 'ReturnStatement') {
24+
return bodyNodes[i];
25+
}
26+
}
27+
return false;
28+
}
29+
630
/**
731
* Get properties name
832
* @param {Object} node - Property.
@@ -36,31 +60,34 @@ function getComponentProperties(node) {
3660
}
3761

3862
/**
39-
* Find a return statment in the current node
40-
*
41-
* @param {ASTNode} ASTnode The AST node being checked
42-
*/
43-
function findReturnStatement(node) {
44-
if (
45-
(!node.value || !node.value.body || !node.value.body.body) &&
46-
(!node.body || !node.body.body)
47-
) {
48-
return false;
49-
}
50-
51-
const bodyNodes = (node.value ? node.value.body.body : node.body.body);
63+
* Checks if the node is the first in its line, excluding whitespace.
64+
* @param {Object} context The node to check
65+
* @param {ASTNode} node The node to check
66+
* @return {Boolean} true if its the first node in its line
67+
*/
68+
function isNodeFirstInLine(context, node) {
69+
const sourceCode = context.getSourceCode();
70+
let token = node;
71+
let lines;
72+
do {
73+
token = sourceCode.getTokenBefore(token);
74+
lines = token.type === 'JSXText'
75+
? token.value.split('\n')
76+
: null;
77+
} while (
78+
token.type === 'JSXText' &&
79+
/^\s*$/.test(lines[lines.length - 1])
80+
);
5281

53-
let i = bodyNodes.length - 1;
54-
for (; i >= 0; i--) {
55-
if (bodyNodes[i].type === 'ReturnStatement') {
56-
return bodyNodes[i];
57-
}
58-
}
59-
return false;
82+
const startLine = node.loc.start.line;
83+
const endLine = token ? token.loc.end.line : -1;
84+
return startLine !== endLine;
6085
}
6186

87+
6288
module.exports = {
89+
findReturnStatement: findReturnStatement,
6390
getPropertyName: getPropertyName,
6491
getComponentProperties: getComponentProperties,
65-
findReturnStatement: findReturnStatement
92+
isNodeFirstInLine: isNodeFirstInLine
6693
};

tests/lib/rules/jsx-indent.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,5 +963,16 @@ ruleTester.run('jsx-indent', rule, {
963963
errors: [
964964
{message: 'Expected indentation of 4 space characters but found 0.'}
965965
]
966+
}, {
967+
code: [
968+
'<p>',
969+
' <div>',
970+
' <SelfClosingTag />Text',
971+
' </div>',
972+
'</p>'
973+
].join('\n'),
974+
errors: [
975+
{message: 'Expected indentation of 4 space characters but found 2.'}
976+
]
966977
}]
967978
});

0 commit comments

Comments
 (0)