diff --git a/CHANGELOG.md b/CHANGELOG.md index a3404276dd..c0d76055ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [`no-unknown-property`]: add `displaystyle` on `` ([#3652][] @lounsbrough) * [`prefer-read-only-props`], [`prop-types`], component detection: allow components to be async functions ([#3654][] @pnodet) * [`no-unknown-property`]: support `onResize` on audio/video tags ([#3662][] @caesar1030) +* [`jsx-wrap-multilines`]: add `never` option to prohibit wrapping parens on multiline JSX ([#3668][] @reedws) ### Fixed * [`jsx-no-leaked-render`]: preserve RHS parens for multiline jsx elements while fixing ([#3623][] @akulsr0) @@ -26,6 +27,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange * [Refactor] [`jsx-props-no-multi-spaces`]: extract type parameters to var ([#3634][] @HenryBrown0) * [Docs] [`jsx-key`]: fix correct example ([#3656][] @developer-bandi) +[#3668]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3668 [#3666]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3666 [#3662]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3662 [#3656]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3656 diff --git a/docs/rules/jsx-wrap-multilines.md b/docs/rules/jsx-wrap-multilines.md index d8a01e32fb..b7593a6990 100644 --- a/docs/rules/jsx-wrap-multilines.md +++ b/docs/rules/jsx-wrap-multilines.md @@ -50,7 +50,7 @@ var Hello = createReactClass({ }); ``` -Examples of **correct** code for this rule: +Examples of **correct** code for this rule, when configured with either `parens` or `parens-new-line`: ```jsx var singleLineJSX =

Hello

@@ -66,6 +66,36 @@ var Hello = createReactClass({ }); ``` +Examples of **incorrect** code for this rule, when configured with `never`: + +```jsx +var singleLineJSX =

Hello

+ +var Hello = createReactClass({ + render: function() { + return ( +
+

Hello {this.props.name}

+
+ ); + } +}); +``` + +Examples of **correct** code for this rule, when configured with `never`: + +```jsx +var singleLineJSX =

Hello

+ +var Hello = createReactClass({ + render: function() { + return
+

Hello {this.props.name}

+
; + } +}); +``` + ### `declaration` Examples of **incorrect** code for this rule, when configured with `{ declaration: "parens" }`: @@ -116,6 +146,30 @@ var hello = ( ); ``` +Examples of **incorrect** code for this rule, when configured with `{ declaration: "never" }`: + +```jsx +var hello = (
+

Hello

+
); +``` + +```jsx +var hello = ( +
+

Hello

+
+); +``` + +Examples of **correct** code for this rule, when configured with `{ declaration: "never" }`. + +```jsx +var hello =
+

Hello

+
; +``` + ### `assignment` Examples of **incorrect** code for this rule, when configured with `{ assignment: "parens" }`. @@ -172,6 +226,33 @@ hello = ( ); ``` +Examples of **incorrect** code for this rule, when configured with `{ assignment: "never" }`. + +```jsx +var hello; +hello = (
+

Hello

+
); +``` + +```jsx +var hello; +hello = ( +
+

Hello

+
+); +``` + +Examples of **correct** code for this rule, when configured with `{ assignment: "never" }`. + +```jsx +var hello; +hello =
+

Hello

+
; +``` + ### `return` Examples of **incorrect** code for this rule, when configured with `{ return: "parens" }`. @@ -234,6 +315,36 @@ function hello() { } ``` +Examples of **incorrect** code for this rule, when configured with `{ return: "never" }`. + +```jsx +function hello() { + return (
+

Hello

+
); +} +``` + +```jsx +function hello() { + return ( +
+

Hello

+
+ ); +} +``` + +Examples of **correct** code for this rule, when configured with `{ return: "never" }`. + +```jsx +function hello() { + return
+

Hello

+
; +} +``` + ### `arrow` Examples of **incorrect** code for this rule, when configured with `{ arrow: "parens" }`. @@ -284,6 +395,30 @@ var hello = () => ( ); ``` +Examples of **incorrect** code for this rule, when configured with `{ arrow: "never" }`. + +```jsx +var hello = () => (
+

World

+
); +``` + +```jsx +var hello = () => ( +
+

World

+
+); +``` + +Examples of **correct** code for this rule, when configured with `{ arrow: "never" }`. + +```jsx +var hello = () =>
+

World

+
; +``` + ### `condition` Examples of **incorrect** code for this rule, when configured with `{ condition: "parens" }`. @@ -346,6 +481,36 @@ Examples of **correct** code for this rule, when configured with `{ condition: " ``` +Examples of **incorrect** code for this rule, when configured with `{ condition: "never" }`. + +```jsx +
+ {foo ? (
+

Hello

+
) : null} +
+``` + +```jsx +
+ {foo ? ( +
+

Hello

+
+ ): null} +
+``` + +Examples of **correct** code for this rule, when configured with `{ condition: "never" }`. + +```jsx +
+ {foo ?
+

Hello

+
: null} +
+``` + ### `logical` Examples of **incorrect** code for this rule, when configured with `{ logical: "parens" }`. @@ -416,6 +581,40 @@ Examples of **correct** code for this rule, when configured with `{ logical: "pa ``` +Examples of **incorrect** code for this rule, when configured with `{ logical: "never" }`. + +```jsx +
+ {foo && + (
+

Hello World

+
) + } +
+``` + +```jsx +
+ {foo && ( +
+

Hello World

+
+ )} +
+``` + +Examples of **correct** code for this rule, when configured with `{ logical: "never" }`. + +```jsx +
+ {foo && +
+

Hello World

+
+ } +
+``` + ### `prop` Examples of **incorrect** code for this rule, when configured with `{ prop: "parens" }`. @@ -477,3 +676,33 @@ Examples of **correct** code for this rule, when configured with `{ prop: "paren

Hello

; ``` + +Examples of **incorrect** code for this rule, when configured with `{ prop: "never" }`. + +```jsx +
+

Hello

+
)}> +

Hello

+; +``` + +```jsx +
+

Hello

+
+)}> +

Hello

+; +``` + +Examples of **correct** code for this rule, when configured with `{ prop: "never" }`. + +```jsx +
+

Hello

+
}> +

Hello

+; +``` diff --git a/lib/rules/jsx-wrap-multilines.js b/lib/rules/jsx-wrap-multilines.js index 5f0a5ec183..eb9359e773 100644 --- a/lib/rules/jsx-wrap-multilines.js +++ b/lib/rules/jsx-wrap-multilines.js @@ -31,6 +31,7 @@ const DEFAULTS = { const messages = { missingParens: 'Missing parentheses around multilines JSX', + extraParens: 'Expected no parentheses around multilines JSX', parensOnNewLines: 'Parentheses around JSX should be on separate lines', }; @@ -51,25 +52,25 @@ module.exports = { // true/false are for backwards compatibility properties: { declaration: { - enum: [true, false, 'ignore', 'parens', 'parens-new-line'], + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], }, assignment: { - enum: [true, false, 'ignore', 'parens', 'parens-new-line'], + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], }, return: { - enum: [true, false, 'ignore', 'parens', 'parens-new-line'], + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], }, arrow: { - enum: [true, false, 'ignore', 'parens', 'parens-new-line'], + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], }, condition: { - enum: [true, false, 'ignore', 'parens', 'parens-new-line'], + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], }, logical: { - enum: [true, false, 'ignore', 'parens', 'parens-new-line'], + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], }, prop: { - enum: [true, false, 'ignore', 'parens', 'parens-new-line'], + enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'], }, }, additionalProperties: false, @@ -184,6 +185,15 @@ module.exports = { } } } + + if (option === 'never' && isParenthesized(context, node)) { + const tokenBefore = sourceCode.getTokenBefore(node); + const tokenAfter = sourceCode.getTokenAfter(node); + report(node, 'extraParens', (fixer) => fixer.replaceTextRange( + [tokenBefore.range[0], tokenAfter.range[1]], + sourceCode.getText(node) + )); + } } // -------------------------------------------------------------------------- diff --git a/tests/lib/rules/jsx-wrap-multilines.js b/tests/lib/rules/jsx-wrap-multilines.js index d91fb2c78b..d92cecf145 100644 --- a/tests/lib/rules/jsx-wrap-multilines.js +++ b/tests/lib/rules/jsx-wrap-multilines.js @@ -36,6 +36,16 @@ const OPTIONS_ALL_NEW_LINES = { prop: 'parens-new-line', }; +const OPTIONS_ALL_NEVER = { + declaration: 'never', + assignment: 'never', + return: 'never', + arrow: 'never', + condition: 'never', + logical: 'never', + prop: 'never', +}; + const RETURN_SINGLE_LINE = ` var Hello = createReactClass({ render: function() { @@ -630,9 +640,18 @@ ruleTester.run('jsx-wrap-multilines', rule, { { code: RETURN_SINGLE_LINE, }, + { + code: RETURN_SINGLE_LINE, + options: [OPTIONS_ALL_NEVER], + }, + { + code: RETURN_SINGLE_LINE_FRAGMENT, + features: ['fragment'], + }, { code: RETURN_SINGLE_LINE_FRAGMENT, features: ['fragment'], + options: [OPTIONS_ALL_NEVER], }, { code: RETURN_PAREN, @@ -654,6 +673,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { code: RETURN_NO_PAREN, options: [{ return: 'ignore' }], }, + { + code: RETURN_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + }, { code: RETURN_NO_PAREN, options: [{ return: false }], @@ -661,6 +684,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { { code: DECLARATION_TERNARY_SINGLE_LINE, }, + { + code: DECLARATION_TERNARY_SINGLE_LINE, + options: [OPTIONS_ALL_NEVER], + }, { code: DECLARATION_TERNARY_SINGLE_LINE_FRAGMENT, features: ['fragment'], @@ -680,6 +707,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { code: DECLARATION_TERNARY_NO_PAREN, options: [{ declaration: 'ignore' }], }, + { + code: DECLARATION_TERNARY_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + }, { code: DECLARATION_TERNARY_NO_PAREN, options: [{ declaration: false }], @@ -687,6 +718,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { { code: ASSIGNMENT_TERNARY_SINGLE_LINE, }, + { + code: ASSIGNMENT_TERNARY_SINGLE_LINE, + options: [OPTIONS_ALL_NEVER], + }, { code: ASSIGNMENT_TERNARY_PAREN, }, @@ -702,6 +737,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { code: ASSIGNMENT_TERNARY_NO_PAREN, options: [{ assignment: 'ignore' }], }, + { + code: ASSIGNMENT_TERNARY_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + }, { code: ASSIGNMENT_TERNARY_NO_PAREN, options: [{ assignment: false }], @@ -709,6 +748,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { { code: DECLARATION_SINGLE_LINE, }, + { + code: DECLARATION_SINGLE_LINE, + options: [OPTIONS_ALL_NEVER], + }, { code: DECLARATION_PAREN, }, @@ -728,11 +771,20 @@ ruleTester.run('jsx-wrap-multilines', rule, { code: DECLARATION_NO_PAREN, options: [{ declaration: 'ignore' }], }, + { + code: DECLARATION_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + }, { code: DECLARATION_NO_PAREN_FRAGMENT, features: ['fragment'], options: [{ declaration: 'ignore' }], }, + { + code: DECLARATION_NO_PAREN_FRAGMENT, + features: ['fragment'], + options: [OPTIONS_ALL_NEVER], + }, { code: DECLARATION_NO_PAREN, options: [{ declaration: false }], @@ -741,6 +793,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { code: ASSIGNMENT_SINGLE_LINE, options: [{ declaration: 'ignore' }], }, + { + code: ASSIGNMENT_SINGLE_LINE, + options: [OPTIONS_ALL_NEVER], + }, { code: ASSIGNMENT_SINGLE_LINE, options: [{ declaration: false }], @@ -760,11 +816,20 @@ ruleTester.run('jsx-wrap-multilines', rule, { code: ASSIGNMENT_NO_PAREN, options: [{ assignment: 'ignore' }], }, + { + code: ASSIGNMENT_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + }, { code: ASSIGNMENT_NO_PAREN_FRAGMENT, features: ['fragment'], options: [{ assignment: 'ignore' }], }, + { + code: ASSIGNMENT_NO_PAREN_FRAGMENT, + features: ['fragment'], + options: [OPTIONS_ALL_NEVER], + }, { code: ASSIGNMENT_NO_PAREN, options: [{ assignment: false }], @@ -791,11 +856,20 @@ ruleTester.run('jsx-wrap-multilines', rule, { code: ARROW_NO_PAREN, options: [{ arrow: 'ignore' }], }, + { + code: ARROW_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + }, { code: ARROW_NO_PAREN_FRAGMENT, features: ['fragment'], options: [{ arrow: 'ignore' }], }, + { + code: ARROW_NO_PAREN_FRAGMENT, + features: ['fragment'], + options: [OPTIONS_ALL_NEVER], + }, { code: ARROW_NO_PAREN, options: [{ arrow: false }], @@ -803,6 +877,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { { code: CONDITION_SINGLE_LINE, }, + { + code: CONDITION_SINGLE_LINE, + options: [OPTIONS_ALL_NEVER], + }, { code: CONDITION_SINGLE_LINE, options: [{ condition: true }], @@ -810,6 +888,10 @@ ruleTester.run('jsx-wrap-multilines', rule, { { code: CONDITION_NO_PAREN, }, + { + code: CONDITION_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + }, { code: CONDITION_PAREN, options: [{ condition: true }], @@ -822,9 +904,17 @@ ruleTester.run('jsx-wrap-multilines', rule, { { code: LOGICAL_SINGLE_LINE, }, + { + code: LOGICAL_SINGLE_LINE, + options: [OPTIONS_ALL_NEVER], + }, { code: LOGICAL_NO_PAREN, }, + { + code: LOGICAL_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + }, { code: LOGICAL_PAREN, options: [{ logical: true }], @@ -837,8 +927,16 @@ ruleTester.run('jsx-wrap-multilines', rule, { { code: ATTR_SINGLE_LINE, }, + { + code: ATTR_SINGLE_LINE, + options: [OPTIONS_ALL_NEVER], + }, + { + code: ATTR_NO_PAREN, + }, { code: ATTR_NO_PAREN, + options: [OPTIONS_ALL_NEVER], }, { code: ATTR_PAREN, @@ -893,6 +991,12 @@ ruleTester.run('jsx-wrap-multilines', rule, { ]), invalid: parsers.all([ + { + code: RETURN_PAREN, + output: RETURN_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: RETURN_NO_PAREN, output: RETURN_PAREN, @@ -904,6 +1008,13 @@ ruleTester.run('jsx-wrap-multilines', rule, { output: RETURN_PAREN_FRAGMENT, errors: [{ messageId: 'missingParens' }], }, + { + code: RETURN_PAREN_FRAGMENT, + features: ['fragment'], + output: RETURN_NO_PAREN_FRAGMENT, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: RETURN_NO_PAREN, output: RETURN_PAREN, @@ -925,6 +1036,15 @@ ruleTester.run('jsx-wrap-multilines', rule, { { messageId: 'missingParens' }, ], }, + { + code: DECLARATION_TERNARY_PAREN, + output: DECLARATION_TERNARY_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + errors: [ + { messageId: 'extraParens' }, + { messageId: 'extraParens' }, + ], + }, { code: DECLARATION_TERNARY_NO_PAREN_FRAGMENT, features: ['fragment'], @@ -934,6 +1054,16 @@ ruleTester.run('jsx-wrap-multilines', rule, { { messageId: 'missingParens' }, ], }, + { + code: DECLARATION_TERNARY_PAREN_FRAGMENT, + features: ['fragment'], + output: DECLARATION_TERNARY_NO_PAREN_FRAGMENT, + options: [OPTIONS_ALL_NEVER], + errors: [ + { messageId: 'extraParens' }, + { messageId: 'extraParens' }, + ], + }, { code: DECLARATION_TERNARY_NO_PAREN, output: DECLARATION_TERNARY_PAREN, @@ -961,6 +1091,15 @@ ruleTester.run('jsx-wrap-multilines', rule, { { messageId: 'missingParens' }, ], }, + { + code: ASSIGNMENT_TERNARY_PAREN, + output: ASSIGNMENT_TERNARY_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + errors: [ + { messageId: 'extraParens' }, + { messageId: 'extraParens' }, + ], + }, { code: ASSIGNMENT_TERNARY_NO_PAREN_FRAGMENT, features: ['fragment'], @@ -970,6 +1109,16 @@ ruleTester.run('jsx-wrap-multilines', rule, { { messageId: 'missingParens' }, ], }, + { + code: ASSIGNMENT_TERNARY_PAREN_FRAGMENT, + features: ['fragment'], + output: ASSIGNMENT_TERNARY_NO_PAREN_FRAGMENT, + options: [OPTIONS_ALL_NEVER], + errors: [ + { messageId: 'extraParens' }, + { messageId: 'extraParens' }, + ], + }, { code: ASSIGNMENT_TERNARY_NO_PAREN, output: ASSIGNMENT_TERNARY_PAREN, @@ -994,12 +1143,25 @@ ruleTester.run('jsx-wrap-multilines', rule, { output: DECLARATION_PAREN, errors: [{ messageId: 'missingParens' }], }, + { + code: DECLARATION_PAREN, + output: DECLARATION_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: DECLARATION_NO_PAREN_FRAGMENT, features: ['fragment'], output: DECLARATION_PAREN_FRAGMENT, errors: [{ messageId: 'missingParens' }], }, + { + code: DECLARATION_PAREN_FRAGMENT, + features: ['fragment'], + output: DECLARATION_NO_PAREN_FRAGMENT, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: DECLARATION_NO_PAREN, output: DECLARATION_PAREN, @@ -1023,17 +1185,36 @@ ruleTester.run('jsx-wrap-multilines', rule, { options: [{ assignment: true }], errors: [{ messageId: 'missingParens' }], }, + { + code: ASSIGNMENT_PAREN, + output: ASSIGNMENT_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: ARROW_NO_PAREN, output: ARROW_PAREN, errors: [{ messageId: 'missingParens' }], }, + { + code: ARROW_PAREN, + output: ARROW_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: ARROW_NO_PAREN_FRAGMENT, features: ['fragment'], output: ARROW_PAREN_FRAGMENT, errors: [{ messageId: 'missingParens' }], }, + { + code: ARROW_PAREN_FRAGMENT, + features: ['fragment'], + output: ARROW_NO_PAREN_FRAGMENT, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: ARROW_NO_PAREN, output: ARROW_PAREN, @@ -1046,6 +1227,12 @@ ruleTester.run('jsx-wrap-multilines', rule, { options: [{ condition: 'parens' }], errors: [{ messageId: 'missingParens' }], }, + { + code: CONDITION_PAREN, + output: CONDITION_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: CONDITION_NO_PAREN_FRAGMENT, features: ['fragment'], @@ -1053,6 +1240,13 @@ ruleTester.run('jsx-wrap-multilines', rule, { options: [{ condition: 'parens' }], errors: [{ messageId: 'missingParens' }], }, + { + code: CONDITION_PAREN_FRAGMENT, + features: ['fragment'], + output: CONDITION_NO_PAREN_FRAGMENT, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: CONDITION_NO_PAREN, output: CONDITION_PAREN, @@ -1065,6 +1259,12 @@ ruleTester.run('jsx-wrap-multilines', rule, { options: [{ logical: 'parens' }], errors: [{ messageId: 'missingParens' }], }, + { + code: LOGICAL_PAREN, + output: LOGICAL_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: LOGICAL_NO_PAREN_FRAGMENT, features: ['fragment'], @@ -1072,6 +1272,13 @@ ruleTester.run('jsx-wrap-multilines', rule, { options: [{ logical: 'parens' }], errors: [{ messageId: 'missingParens' }], }, + { + code: LOGICAL_PAREN_FRAGMENT, + features: ['fragment'], + output: LOGICAL_NO_PAREN_FRAGMENT, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: LOGICAL_NO_PAREN, output: LOGICAL_PAREN, @@ -1084,6 +1291,12 @@ ruleTester.run('jsx-wrap-multilines', rule, { options: [{ prop: 'parens' }], errors: [{ messageId: 'missingParens' }], }, + { + code: ATTR_PAREN, + output: ATTR_NO_PAREN, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: ATTR_NO_PAREN_FRAGMENT, features: ['fragment'], @@ -1091,6 +1304,13 @@ ruleTester.run('jsx-wrap-multilines', rule, { options: [{ prop: 'parens' }], errors: [{ messageId: 'missingParens' }], }, + { + code: ATTR_PAREN_FRAGMENT, + features: ['fragment'], + output: ATTR_NO_PAREN_FRAGMENT, + options: [OPTIONS_ALL_NEVER], + errors: [{ messageId: 'extraParens' }], + }, { code: ATTR_NO_PAREN, output: ATTR_PAREN,