From ffdf69ae51147617c2740be36ef0982d2e169bc8 Mon Sep 17 00:00:00 2001 From: toshi-toma Date: Tue, 14 Jan 2020 22:59:06 +0900 Subject: [PATCH] [Fix] `jsx-indent`: Does not check indents for JSXText Fixes #2467. Fixes #2484. Fixes #1136. --- lib/rules/jsx-indent.js | 42 +++++++++++- package.json | 3 +- tests/lib/rules/jsx-indent.js | 125 +++++++++++++++++++++++++++++++--- 3 files changed, 159 insertions(+), 11 deletions(-) diff --git a/lib/rules/jsx-indent.js b/lib/rules/jsx-indent.js index fa81f8f673..bbdeaabce0 100644 --- a/lib/rules/jsx-indent.js +++ b/lib/rules/jsx-indent.js @@ -30,6 +30,8 @@ 'use strict'; +const matchAll = require('string.prototype.matchall'); + const astUtil = require('../util/ast'); const docsUrl = require('../util/docsUrl'); @@ -97,6 +99,11 @@ module.exports = { function getFixerFunction(node, needed) { return function fix(fixer) { const indent = Array(needed + 1).join(indentChar); + if (node.type === 'JSXText' || node.type === 'Literal') { + const regExp = /\n[\t ]*(\S)/g; + const fixedText = node.raw.replace(regExp, (match, p1) => `\n${indent}${p1}`); + return fixer.replaceText(node, fixedText); + } return fixer.replaceTextRange( [node.range[0] - node.loc.start.column, node.range[0]], indent @@ -290,6 +297,29 @@ module.exports = { } } + /** + * Check indent for Literal Node or JSXText Node + * @param {ASTNode} node The node to check + * @param {Number} indent needed indent + */ + function checkLiteralNodeIndent(node, indent) { + const value = node.value; + const regExp = indentType === 'space' ? /\n( *)[\t ]*\S/g : /\n(\t*)[\t ]*\S/g; + const nodeIndentsPerLine = Array.from( + matchAll(value, regExp), + match => (match[1] ? match[1].length : 0) + ); + const hasFirstInLineNode = nodeIndentsPerLine.length > 0; + if ( + hasFirstInLineNode && + !nodeIndentsPerLine.every(actualIndent => actualIndent === indent) + ) { + nodeIndentsPerLine.forEach((nodeIndent) => { + report(node, indent, nodeIndent); + }); + } + } + function handleOpeningElement(node) { const sourceCode = context.getSourceCode(); let prevToken = sourceCode.getTokenBefore(node); @@ -340,6 +370,14 @@ module.exports = { checkNodesIndent(firstInLine, indent); } + function handleLiteral(node) { + if (!node.parent) { + return; + } + const parentNodeIndent = getNodeIndent(node.parent); + checkLiteralNodeIndent(node, parentNodeIndent + indentSize); + } + return { JSXOpeningElement: handleOpeningElement, JSXOpeningFragment: handleOpeningElement, @@ -352,7 +390,9 @@ module.exports = { } const parentNodeIndent = getNodeIndent(node.parent); checkNodesIndent(node, parentNodeIndent + indentSize); - } + }, + Literal: handleLiteral, + JSXText: handleLiteral }; } }; diff --git a/package.json b/package.json index b314901aba..53f5eaeafc 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "object.fromentries": "^2.0.2", "object.values": "^1.1.1", "prop-types": "^15.7.2", - "resolve": "^1.14.2" + "resolve": "^1.14.2", + "string.prototype.matchall": "^4.0.2" }, "devDependencies": { "@types/eslint": "^6.1.3", diff --git a/tests/lib/rules/jsx-indent.js b/tests/lib/rules/jsx-indent.js index af01578abc..9a245f1e18 100644 --- a/tests/lib/rules/jsx-indent.js +++ b/tests/lib/rules/jsx-indent.js @@ -300,22 +300,21 @@ ruleTester.run('jsx-indent', rule, { ].join('\n'), parser: parsers.BABEL_ESLINT }, { - // Literals indentation is not touched code: [ '
', - 'bar
', - ' bar', - ' bar {foo}', - 'bar
', + ' bar
', + ' bar', + ' bar {foo}', + ' bar
', '
' ].join('\n') }, { code: [ '<>', - 'bar <>', - ' bar', - ' bar {foo}', - 'bar ', + ' bar <>', + ' bar', + ' bar {foo}', + ' bar ', '' ].join('\n'), parser: parsers.BABEL_ESLINT @@ -956,9 +955,53 @@ const Component = () => ( } `, options: [2, {indentLogicalExpressions: true}] + }, { + code: [ + '', + ' text', + '' + ].join('\n') + }, { + code: [ + '', + ' text', + ' text', + ' text', + '' + ].join('\n') + }, { + code: [ + '', + '\ttext', + '' + ].join('\n'), + options: ['tab'] }], invalid: [{ + code: [ + '
', + 'bar
', + ' bar', + ' bar {foo}', + ' bar
', + '
' + ].join('\n'), + output: [ + '
', + ' bar
', + ' bar', + ' bar {foo}', + ' bar
', + '
' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 4 space characters but found 0.'}, + {message: 'Expected indentation of 4 space characters but found 3.'}, + {message: 'Expected indentation of 4 space characters but found 3.'}, + {message: 'Expected indentation of 4 space characters but found 3.'} + ] + }, { code: [ '', ' ', @@ -1883,5 +1926,69 @@ const Component = () => ( errors: [ {message: 'Expected indentation of 8 space characters but found 4.'} ] + }, { + code: [ + '
', + 'text', + '
' + ].join('\n'), + output: [ + '
', + ' text', + '
' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 4 space characters but found 0.'} + ] + }, { + code: [ + '
', + ' text', + 'text', + '
' + ].join('\n'), + output: [ + '
', + ' text', + ' text', + '
' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 4 space characters but found 2.'}, + {message: 'Expected indentation of 4 space characters but found 0.'} + ] + }, { + code: [ + '
', + '\t text', + ' \t text', + '
' + ].join('\n'), + output: [ + '
', + ' text', + ' text', + '
' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 4 space characters but found 0.'}, + {message: 'Expected indentation of 4 space characters but found 2.'} + ] + }, { + code: [ + '
', + '\t\ttext', + '
' + ].join('\n'), + parser: parsers.BABEL_ESLINT, + options: ['tab'], + output: [ + '
', + '\ttext', + '
' + ].join('\n'), + errors: [ + {message: 'Expected indentation of 1 tab character but found 2.'} + ] }] });