From 27d7e54283bf62d8ef8308d83400cc463d819ac3 Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Sat, 3 Mar 2018 16:53:35 -0500 Subject: [PATCH] Update: give a node at least the indentation of its parent (fixes #9995) This updates the `indent` rule to ensure that the tokens in a non-ignored node are always indented at least as much as the first token of the node. This fixes an issue where some tokens would not have enough indentation if their node's listener did not explicitly give them an offset. --- lib/rules/indent.js | 61 +++++++++++++++++++++------------------ tests/lib/rules/indent.js | 39 +++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 28 deletions(-) diff --git a/lib/rules/indent.js b/lib/rules/indent.js index acc52463075..1e1a04f0888 100644 --- a/lib/rules/indent.js +++ b/lib/rules/indent.js @@ -980,6 +980,8 @@ module.exports = { return !node || node.loc.start.line === token.loc.start.line; } + const ignoredNodeFirstTokens = new Set(); + const baseOffsetListeners = { "ArrayExpression, ArrayPattern"(node) { const openingBracket = sourceCode.getFirstToken(node); @@ -1010,15 +1012,6 @@ module.exports = { addElementListIndent(node.params, openingParen, closingParen, options.FunctionExpression.parameters); } addBlocklessNodeIndent(node.body); - - let arrowToken; - - if (node.params.length) { - arrowToken = sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isArrowToken); - } else { - arrowToken = sourceCode.getFirstToken(node, astUtils.isArrowToken); - } - offsets.setDesiredOffset(arrowToken, sourceCode.getFirstToken(node), 0); }, AssignmentExpression(node) { @@ -1128,9 +1121,6 @@ module.exports = { */ offsets.setDesiredOffset(firstAlternateToken, firstToken, 1); } - - offsets.setDesiredOffsets([questionMarkToken.range[1], colonToken.range[0]], firstConsequentToken, 0); - offsets.setDesiredOffsets([colonToken.range[1], node.range[1]], firstAlternateToken, 0); } }, @@ -1272,20 +1262,9 @@ module.exports = { SwitchStatement(node) { const openingCurly = sourceCode.getTokenAfter(node.discriminant, astUtils.isOpeningBraceToken); const closingCurly = sourceCode.getLastToken(node); - const caseKeywords = node.cases.map(switchCase => sourceCode.getFirstToken(switchCase)); offsets.setDesiredOffsets([openingCurly.range[1], closingCurly.range[0]], openingCurly, options.SwitchCase); - node.cases.forEach((switchCase, index) => { - const caseKeyword = caseKeywords[index]; - - if (!(switchCase.consequent.length === 1 && switchCase.consequent[0].type === "BlockStatement")) { - const tokenAfterCurrentCase = index === node.cases.length - 1 ? closingCurly : caseKeywords[index + 1]; - - offsets.setDesiredOffsets([caseKeyword.range[1], tokenAfterCurrentCase.range[0]], caseKeyword, 1); - } - }); - if (node.cases.length) { sourceCode.getTokensBetween( node.cases[node.cases.length - 1], @@ -1295,6 +1274,15 @@ module.exports = { } }, + SwitchCase(node) { + if (!(node.consequent.length === 1 && node.consequent[0].type === "BlockStatement")) { + const caseKeyword = sourceCode.getFirstToken(node); + const tokenAfterCurrentCase = sourceCode.getTokenAfter(node); + + offsets.setDesiredOffsets([caseKeyword.range[1], tokenAfterCurrentCase.range[0]], caseKeyword, 1); + } + }, + TemplateLiteral(node) { node.expressions.forEach((expression, index) => { const previousQuasi = node.quasis[index]; @@ -1385,7 +1373,6 @@ module.exports = { const firstToken = sourceCode.getFirstToken(node); offsets.setDesiredOffsets(node.name.range, firstToken, 1); - offsets.setDesiredOffset(sourceCode.getLastToken(node), firstToken, 0); }, JSXExpressionContainer(node) { @@ -1397,7 +1384,15 @@ module.exports = { openingCurly, 1 ); - offsets.setDesiredOffset(closingCurly, openingCurly, 0); + }, + + "*"(node) { + const firstToken = sourceCode.getFirstToken(node); + + // Ensure that the children of every node are indented at least as much as the first token. + if (firstToken && !ignoredNodeFirstTokens.has(firstToken)) { + offsets.setDesiredOffsets(node.range, firstToken, 0); + } } }; @@ -1406,7 +1401,8 @@ module.exports = { /* * To ignore the indentation of a node: * 1. Don't call the node's listener when entering it (if it has a listener) - * 2. Call `ignoreNode` on the node sometime after exiting it and before validating offsets. + * 2. Don't set any offsets against the first token of the node. + * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets. */ const offsetListeners = lodash.mapValues( baseOffsetListeners, @@ -1434,7 +1430,16 @@ module.exports = { // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set. const ignoredNodes = new Set(); - const addToIgnoredNodes = ignoredNodes.add.bind(ignoredNodes); + + /** + * Ignores a node + * @param {ASTNode} node The node to ignore + * @returns {void} + */ + function addToIgnoredNodes(node) { + ignoredNodes.add(node); + ignoredNodeFirstTokens.add(sourceCode.getFirstToken(node)); + } const ignoredNodeListeners = options.ignoredNodes.reduce( (listeners, ignoredSelector) => Object.assign(listeners, { [ignoredSelector]: addToIgnoredNodes }), @@ -1457,7 +1462,7 @@ module.exports = { // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it. if (!KNOWN_NODES.has(node.type)) { - ignoredNodes.add(node); + addToIgnoredNodes(node); } }, "Program:exit"() { diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js index 3a6bc0c9937..471aa104442 100644 --- a/tests/lib/rules/indent.js +++ b/tests/lib/rules/indent.js @@ -2302,6 +2302,17 @@ ruleTester.run("indent", rule, { `, options: [4] }, + unIndent` + foo && + !bar( + ) + `, + unIndent` + foo && + ![].map(() => { + bar(); + }) + `, { code: unIndent` foo = @@ -7539,6 +7550,34 @@ ruleTester.run("indent", rule, { options: [4], errors: expectedErrors([2, 4, 8, "Identifier"]) }, + { + code: unIndent` + foo && + !bar( + ) + `, + output: unIndent` + foo && + !bar( + ) + `, + errors: expectedErrors([3, 4, 0, "Punctuator"]) + }, + { + code: unIndent` + foo && + ![].map(() => { + bar(); + }) + `, + output: unIndent` + foo && + ![].map(() => { + bar(); + }) + `, + errors: expectedErrors([[3, 8, 4, "Identifier"], [4, 4, 0, "Punctuator"]]) + }, { code: unIndent` [