From 83eb22651c2706b4565d35ec54898039cbc17742 Mon Sep 17 00:00:00 2001 From: Carlux Date: Mon, 20 Sep 2021 21:18:52 +0300 Subject: [PATCH] [New] `jsx-max-props-per-line`: add `single` and `multi` options --- CHANGELOG.md | 2 + docs/rules/jsx-max-props-per-line.md | 10 ++ lib/rules/jsx-max-props-per-line.js | 81 +++++++--- tests/lib/rules/jsx-max-props-per-line.js | 181 +++++++++++++++++++++- 4 files changed, 252 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f67fd5098..890b64c093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Added * add [`no-namespace`] rule ([#2640] @yacinehmito @ljharb) +* [`jsx-max-props-per-line`]: add `single` and `multi` options ([#3078] @SIL0RAK) ### Fixed * [`display-name`]: Get rid of false position on component detection ([#2759] @iiison) @@ -14,6 +15,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Changed * [`no-access-state-in-setstate`]: passing test for “don't error if it's not a React Component” ([#1873] @kentcdodds) +[#3078]: https://github.com/yannickcr/eslint-plugin-react/pull/3078 [#2640]: https://github.com/yannickcr/eslint-plugin-react/pull/2640 [#2759]: https://github.com/yannickcr/eslint-plugin-react/pull/2759 [#1873]: https://github.com/yannickcr/eslint-plugin-react/pull/1873 diff --git a/docs/rules/jsx-max-props-per-line.md b/docs/rules/jsx-max-props-per-line.md index d40180c716..5bae2076cf 100644 --- a/docs/rules/jsx-max-props-per-line.md +++ b/docs/rules/jsx-max-props-per-line.md @@ -39,6 +39,12 @@ Examples of **correct** code for this rule: ... "react/jsx-max-props-per-line": [, { "maximum": , "when": }] ... + +// OR + +... +"react/jsx-max-props-per-line": [, { "maximum": { single multi: } }] +... ``` ### `maximum` @@ -62,8 +68,12 @@ Examples of **correct** code for this rule: />; ``` +Maximum can be specified as object `{ single: 1, multi: 1 }` to specify maximum allowed number of props for single line and multiple line tags. + ### `when` + _when only applied if `maximum` is specified as number._ + Possible values: - `always` (default) - Always check for max props per line. - `multiline` - Only check for max props per line when jsx tag spans multiple lines. diff --git a/lib/rules/jsx-max-props-per-line.js b/lib/rules/jsx-max-props-per-line.js index d02dbd8c5f..43a693b54c 100644 --- a/lib/rules/jsx-max-props-per-line.js +++ b/lib/rules/jsx-max-props-per-line.js @@ -8,6 +8,13 @@ const docsUrl = require('../util/docsUrl'); const report = require('../util/report'); +function getPropName(context, propNode) { + if (propNode.type === 'JSXSpreadAttribute') { + return context.getSourceCode().getText(propNode.argument); + } + return propNode.name.name; +} + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -29,37 +36,61 @@ module.exports = { messages, schema: [{ - type: 'object', - properties: { - maximum: { - type: 'integer', - minimum: 1 + anyOf: [{ + type: 'object', + properties: { + maximum: { + type: 'object', + properties: { + single: { + type: 'integer', + minimum: 1 + }, + multi: { + type: 'integer', + minimum: 1 + } + } + } }, - when: { - type: 'string', - enum: ['always', 'multiline'] - } - } + additionalProperties: false + }, { + type: 'object', + properties: { + maximum: { + type: 'number', + minimum: 1 + }, + when: { + type: 'string', + enum: ['always', 'multiline'] + } + }, + additionalProperties: false + }] }] }, create(context) { const configuration = context.options[0] || {}; const maximum = configuration.maximum || 1; - const when = configuration.when || 'always'; - function getPropName(propNode) { - if (propNode.type === 'JSXSpreadAttribute') { - return context.getSourceCode().getText(propNode.argument); + const maxConfig = typeof maximum === 'number' + ? { + single: configuration.when === 'multiline' ? Infinity : maximum, + multi: maximum } - return propNode.name.name; - } + : { + single: maximum.single || Infinity, + multi: maximum.multi || Infinity + }; function generateFixFunction(line, max) { const sourceCode = context.getSourceCode(); const output = []; const front = line[0].range[0]; const back = line[line.length - 1].range[1]; + for (let i = 0; i < line.length; i += max) { const nodes = line.slice(i, i + max); output.push(nodes.reduce((prev, curr) => { @@ -69,7 +100,9 @@ module.exports = { return `${prev} ${sourceCode.getText(curr)}`; }, '')); } + const code = output.join('\n'); + return function fix(fixer) { return fixer.replaceTextRange([front, back], code); }; @@ -81,7 +114,9 @@ module.exports = { return; } - if (when === 'multiline' && node.loc.start.line === node.loc.end.line) { + const isSingleLineTag = node.loc.start.line === node.loc.end.line; + + if ((isSingleLineTag ? maxConfig.single : maxConfig.multi) === Infinity) { return; } @@ -98,14 +133,18 @@ module.exports = { }); linePartitionedProps.forEach((propsInLine) => { - if (propsInLine.length > maximum) { - const name = getPropName(propsInLine[maximum]); + const maxPropsCountPerLine = isSingleLineTag && propsInLine[0].loc.start.line === node.loc.start.line + ? maxConfig.single + : maxConfig.multi; + + if (propsInLine.length > maxPropsCountPerLine) { + const name = getPropName(context, propsInLine[maxPropsCountPerLine]); report(context, messages.newLine, 'newLine', { - node: propsInLine[maximum], + node: propsInLine[maxPropsCountPerLine], data: { prop: name }, - fix: generateFixFunction(propsInLine, maximum) + fix: generateFixFunction(propsInLine, maxPropsCountPerLine) }); } }); diff --git a/tests/lib/rules/jsx-max-props-per-line.js b/tests/lib/rules/jsx-max-props-per-line.js index 86e8d9203c..503b0e132a 100644 --- a/tests/lib/rules/jsx-max-props-per-line.js +++ b/tests/lib/rules/jsx-max-props-per-line.js @@ -60,7 +60,70 @@ ruleTester.run('jsx-max-props-per-line', rule, { '/>' ].join('\n'), options: [{maximum: 2}] - }], + }, { + code: [ + '' + ].join('\n'), + options: [{maximum: {multi: 2}}] + }, { + code: [ + '' + ].join('\n'), + options: [{maximum: {multi: 2, single: 1}}] + }, { + code: '', + options: [{maximum: {multi: 2, single: 3}}] + }, { + code: '', + options: [{maximum: {single: 2}}] + }, { + code: [ + '' + ].join('\n'), + options: [{maximum: {multi: 2, single: 1}}] + }, { + code: '', + options: [{maximum: {multi: 2}}] + }, { + code: [ + '' + ].join('\n'), + options: [{maximum: {single: 1}}] + }, { + code: [ + '' + ].join('\n'), + options: [{maximum: {single: 2, multi: 2}}] + }, { + code: [ + '' + ].join('\n'), + options: [{maximum: 2}] + }, { + code: [ + '' + ].join('\n'), + options: [{maximum: 1, when: 'multiline'}] + } + ], invalid: [{ code: ';', @@ -266,5 +329,121 @@ ruleTester.run('jsx-max-props-per-line', rule, { messageId: 'newLine', data: {prop: 'baz'} }] + }, + { + code: '', + output: [ + '' + ].join('\n'), + options: [{maximum: {single: 1, multi: 1}}], + errors: [{ + messageId: 'newLine', + data: {prop: 'bar'} + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{maximum: {single: 1, multi: 1}}], + errors: [{ + messageId: 'newLine', + data: {prop: 'bar'} + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{maximum: {single: 1, multi: 1}}], + errors: [{ + messageId: 'newLine', + data: {prop: 'baz'} + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{maximum: {single: 1, multi: 2}}], + errors: [ + { + messageId: 'newLine', + data: {prop: 'bor'} + }] + }, { + code: '', + output: [ + '' + ].join('\n'), + options: [{maximum: {single: 3, multi: 2}}], + errors: [ + { + messageId: 'newLine', + data: {prop: 'bor'} + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{maximum: {multi: 2}}], + errors: [{ + messageId: 'newLine', + data: {prop: 'baz'} + }] + }, { + code: [ + '' + ].join('\n'), + output: [ + '' + ].join('\n'), + options: [{maximum: {multi: 2, single: 1}}], + errors: [{ + messageId: 'newLine', + data: {prop: 'baz'} + }] }] });