diff --git a/lib/rules/html-indent.js b/lib/rules/html-indent.js index b4a41d572..f0fdd2491 100644 --- a/lib/rules/html-indent.js +++ b/lib/rules/html-indent.js @@ -387,6 +387,55 @@ function create (context) { return template.getFirstToken(node) } + /** + * Check whether a given token is the first token of: + * + * - ExpressionStatement + * - VExpressionContainer + * - A parameter of CallExpression/NewExpression + * - An element of ArrayExpression + * - An expression of SequenceExpression + * + * @param {Token} token The token to check. + * @param {Node} belongingNode The node that the token is belonging to. + * @returns {boolean} `true` if the token is the first token of an element. + */ + function isBeginningOfElement (token, belongingNode) { + let node = belongingNode + + while (node != null) { + const parent = node.parent + const t = parent && parent.type + if (t != null && (t.endsWith('Statement') || t.endsWith('Declaration'))) { + return parent.range[0] === token.range[0] + } + if (t === 'VExpressionContainer') { + return node.range[0] === token.range[0] + } + if (t === 'CallExpression' || t === 'NewExpression') { + const openParen = template.getTokenAfter(parent.callee, isNotRightParen) + return parent.arguments.some(param => + getFirstAndLastTokens(param, openParen.range[1]).firstToken.range[0] === token.range[0] + ) + } + if (t === 'ArrayExpression') { + return parent.elements.some(element => + element != null && + getFirstAndLastTokens(element).firstToken.range[0] === token.range[0] + ) + } + if (t === 'SequenceExpression') { + return parent.expressions.some(expr => + getFirstAndLastTokens(expr).firstToken.range[0] === token.range[0] + ) + } + + node = parent + } + + return false + } + /** * Ignore all tokens of the given node. * @param {Node} node The node to ignore. @@ -722,8 +771,14 @@ function create (context) { const leftToken = getChainHeadToken(node) const opToken = template.getTokenAfter(node.left, isNotRightParen) const rightToken = template.getTokenAfter(opToken) - - setOffset([opToken, rightToken], 1, leftToken) + const prevToken = template.getTokenBefore(leftToken) + const shouldIndent = ( + prevToken == null || + prevToken.loc.end.line === leftToken.loc.start.line || + isBeginningOfElement(leftToken, node) + ) + + setOffset([opToken, rightToken], shouldIndent ? 1 : 0, leftToken) }, 'AwaitExpression, RestElement, SpreadElement, UnaryExpression' (node) { diff --git a/tests/lib/rules/html-indent.js b/tests/lib/rules/html-indent.js index a426dbf97..40c30ee59 100644 --- a/tests/lib/rules/html-indent.js +++ b/tests/lib/rules/html-indent.js @@ -255,10 +255,10 @@ tester.run('html-indent', rule, { a = b - + - c - + - d + + + c + + + d " > @@ -1307,7 +1307,127 @@ tester.run('html-indent', rule, { // Ignore all :D ignores: ['*'] }] - } + }, + + // https://github.com/vuejs/eslint-plugin-vue/issues/264 + unIndent` + + `, + unIndent` + + `, + unIndent` + + `, + unIndent` + + `, + unIndent` + + `, + unIndent` + + `, + unIndent` + + ` ], invalid: [ @@ -1779,10 +1899,10 @@ tester.run('html-indent', rule, { a = b - + - c - + - d + + + c + + + d " > @@ -1800,10 +1920,10 @@ tester.run('html-indent', rule, { { message: 'Expected indentation of 12 spaces but found 10 spaces.', line: 16 }, { message: 'Expected indentation of 16 spaces but found 10 spaces.', line: 17 }, { message: 'Expected indentation of 16 spaces but found 10 spaces.', line: 18 }, - { message: 'Expected indentation of 20 spaces but found 10 spaces.', line: 19 }, - { message: 'Expected indentation of 20 spaces but found 10 spaces.', line: 20 }, - { message: 'Expected indentation of 20 spaces but found 10 spaces.', line: 21 }, - { message: 'Expected indentation of 20 spaces but found 10 spaces.', line: 22 } + { message: 'Expected indentation of 16 spaces but found 10 spaces.', line: 19 }, + { message: 'Expected indentation of 16 spaces but found 10 spaces.', line: 20 }, + { message: 'Expected indentation of 16 spaces but found 10 spaces.', line: 21 }, + { message: 'Expected indentation of 16 spaces but found 10 spaces.', line: 22 } ] },