Skip to content

Commit

Permalink
Merge pull request #1539 from jseminck/jsx-indent-bug
Browse files Browse the repository at this point in the history
Fix alignment bug in jsx-indent (and extract duplicate code)
  • Loading branch information
ljharb committed Nov 18, 2017
2 parents 27b8279 + acc4f24 commit 771f534
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 80 deletions.
31 changes: 4 additions & 27 deletions lib/rules/jsx-closing-tag-location.js
Expand Up @@ -4,6 +4,8 @@
*/
'use strict';

const astUtil = require('../util/ast');

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
Expand All @@ -18,31 +20,6 @@ module.exports = {
},

create: function(context) {
const sourceCode = context.getSourceCode();

/**
* Checks if the node is the first in its line, excluding whitespace.
* @param {ASTNode} node The node to check
* @return {Boolean} true if its the first node in its line
*/
function isNodeFirstInLine(node) {
let token = node;
let lines;
do {
token = sourceCode.getTokenBefore(token);
lines = token.type === 'JSXText'
? token.value.split('\n')
: null;
} while (
token.type === 'JSXText' &&
/^\s*$/.test(lines[lines.length - 1])
);

const startLine = node.loc.start.line;
const endLine = token ? token.loc.end.line : -1;
return startLine !== endLine;
}

return {
JSXClosingElement: function(node) {
if (!node.parent) {
Expand All @@ -59,7 +36,7 @@ module.exports = {
}

let message;
if (!isNodeFirstInLine(node)) {
if (!astUtil.isNodeFirstInLine(context, node)) {
message = 'Closing tag of a multiline JSX expression must be on its own line.';
} else {
message = 'Expected closing tag to match indentation of opening.';
Expand All @@ -71,7 +48,7 @@ module.exports = {
message,
fix: function(fixer) {
const indent = Array(opening.loc.start.column + 1).join(' ');
if (isNodeFirstInLine(node)) {
if (astUtil.isNodeFirstInLine(context, node)) {
return fixer.replaceTextRange(
[node.range[0] - node.loc.start.column, node.range[0]],
indent
Expand Down
18 changes: 3 additions & 15 deletions lib/rules/jsx-indent-props.js
Expand Up @@ -29,6 +29,8 @@
*/
'use strict';

const astUtil = require('../util/ast');

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -136,20 +138,6 @@ module.exports = {
return indent ? indent[0].length : 0;
}

/**
* Checks node is the first in its own start line. By default it looks by start line.
* @param {ASTNode} node The node to check
* @param {Boolean} [byEndLocation] Lookup based on start position or end
* @return {Boolean} true if its the first in the its start line
*/
function isNodeFirstInLine(node, byEndLocation) {
const firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node);
const startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line;
const endLine = firstToken ? firstToken.loc.end.line : -1;

return startLine !== endLine;
}

/**
* Check indent for nodes list
* @param {ASTNode[]} nodes list of node objects
Expand All @@ -161,7 +149,7 @@ module.exports = {
const nodeIndent = getNodeIndent(node, false, excludeCommas);
if (
node.type !== 'ArrayExpression' && node.type !== 'ObjectExpression' &&
nodeIndent !== indent && isNodeFirstInLine(node)
nodeIndent !== indent && astUtil.isNodeFirstInLine(context, node)
) {
report(node, indent, nodeIndent);
}
Expand Down
20 changes: 3 additions & 17 deletions lib/rules/jsx-indent.js
Expand Up @@ -29,6 +29,8 @@
*/
'use strict';

const astUtil = require('../util/ast');

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
Expand Down Expand Up @@ -152,22 +154,6 @@ module.exports = {
return indent ? indent[0].length : 0;
}

/**
* Checks node is the first in its own start line. By default it looks by start line.
* @param {ASTNode} node The node to check
* @return {Boolean} true if its the first in the its start line
*/
function isNodeFirstInLine(node) {
let token = node;
do {
token = sourceCode.getTokenBefore(token);
} while (token.type === 'JSXText' && /^\s*$/.test(token.value));
const startLine = node.loc.start.line;
const endLine = token ? token.loc.end.line : -1;

return startLine !== endLine;
}

/**
* Check if the node is the right member of a logical expression
* @param {ASTNode} node The node to check
Expand Down Expand Up @@ -209,7 +195,7 @@ module.exports = {
const isCorrectAlternateInCondExp = isAlternateInConditionalExp(node) && (nodeIndent - indent) === 0;
if (
nodeIndent !== indent &&
isNodeFirstInLine(node) &&
astUtil.isNodeFirstInLine(context, node) &&
!isCorrectRightInLogicalExp &&
!isCorrectAlternateInCondExp
) {
Expand Down
69 changes: 48 additions & 21 deletions lib/util/ast.js
Expand Up @@ -3,6 +3,30 @@
*/
'use strict';

/**
* Find a return statment in the current node
*
* @param {ASTNode} ASTnode The AST node being checked
*/
function findReturnStatement(node) {
if (
(!node.value || !node.value.body || !node.value.body.body) &&
(!node.body || !node.body.body)
) {
return false;
}

const bodyNodes = (node.value ? node.value.body.body : node.body.body);

let i = bodyNodes.length - 1;
for (; i >= 0; i--) {
if (bodyNodes[i].type === 'ReturnStatement') {
return bodyNodes[i];
}
}
return false;
}

/**
* Get properties name
* @param {Object} node - Property.
Expand Down Expand Up @@ -36,31 +60,34 @@ function getComponentProperties(node) {
}

/**
* Find a return statment in the current node
*
* @param {ASTNode} ASTnode The AST node being checked
*/
function findReturnStatement(node) {
if (
(!node.value || !node.value.body || !node.value.body.body) &&
(!node.body || !node.body.body)
) {
return false;
}

const bodyNodes = (node.value ? node.value.body.body : node.body.body);
* Checks if the node is the first in its line, excluding whitespace.
* @param {Object} context The node to check
* @param {ASTNode} node The node to check
* @return {Boolean} true if its the first node in its line
*/
function isNodeFirstInLine(context, node) {
const sourceCode = context.getSourceCode();
let token = node;
let lines;
do {
token = sourceCode.getTokenBefore(token);
lines = token.type === 'JSXText'
? token.value.split('\n')
: null;
} while (
token.type === 'JSXText' &&
/^\s*$/.test(lines[lines.length - 1])
);

let i = bodyNodes.length - 1;
for (; i >= 0; i--) {
if (bodyNodes[i].type === 'ReturnStatement') {
return bodyNodes[i];
}
}
return false;
const startLine = node.loc.start.line;
const endLine = token ? token.loc.end.line : -1;
return startLine !== endLine;
}


module.exports = {
findReturnStatement: findReturnStatement,
getPropertyName: getPropertyName,
getComponentProperties: getComponentProperties,
findReturnStatement: findReturnStatement
isNodeFirstInLine: isNodeFirstInLine
};
11 changes: 11 additions & 0 deletions tests/lib/rules/jsx-indent.js
Expand Up @@ -963,5 +963,16 @@ ruleTester.run('jsx-indent', rule, {
errors: [
{message: 'Expected indentation of 4 space characters but found 0.'}
]
}, {
code: [
'<p>',
' <div>',
' <SelfClosingTag />Text',
' </div>',
'</p>'
].join('\n'),
errors: [
{message: 'Expected indentation of 4 space characters but found 2.'}
]
}]
});

0 comments on commit 771f534

Please sign in to comment.