Skip to content

Commit

Permalink
Merge branch 'master' into jsx-key-bug
Browse files Browse the repository at this point in the history
  • Loading branch information
TildaDares committed Jul 4, 2022
2 parents c0aa40e + 7a7bb99 commit 757f0b5
Show file tree
Hide file tree
Showing 8 changed files with 310 additions and 31 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,21 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange

## Unreleased

### Added
* [`jsx-newline`]: add `allowMultiline` option when prevent option is true ([#3311][] @TildaDares)

### Fixed
* [`jsx-no-literals`]: properly error on children with noAttributeStrings: true ([#3317][] @TildaDares)

### Changed
* [Refactor] [`jsx-indent-props`]: improved readability of the checkNodesIndent function ([#3315][] @caroline223)

[#3317]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3317
[#3315]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3315
[#3311]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3311

## [7.30.1] - 2022.06.23

### Fixed
* [`display-name`]: fix false positive for HOF returning only nulls ([#3291][] @golopot)
* [`jsx-no-leaked-render`]: avoid unnecessary negation operators and ternary branches deletion ([#3299][] @Belco90)
Expand All @@ -14,6 +29,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [Docs] [`jsx-tag-spacing`]: rename option from [#3264][] ([#3294[] @ljharb)
* [Docs] [`jsx-key`]: split the examples ([#3293][] @ioggstream)

[7.30.1]: https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.30.0...v7.30.1
[#3304]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3304
[#3299]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3299
[#3294]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3294
Expand All @@ -29,6 +45,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`function-component-definition`]: replace `var` by `const` in certain situations ([#3248][] @JohnBerd @SimeonC)
* add [`jsx-no-leaked-render`] ([#3203][] @Belco90)
* [`require-default-props`]: add option `functions` ([#3249][] @nix6839)
* [`jsx-newline`]: Add `allowMultilines` option ([#3311][] @TildaDares)

### Fixed
* [`hook-use-state`]: Allow UPPERCASE setState setter prefixes ([#3244][] @duncanbeevers)
Expand Down
34 changes: 33 additions & 1 deletion docs/rules/jsx-newline.md
Expand Up @@ -9,12 +9,13 @@ This is a stylistic rule intended to make JSX code more readable by requiring or
## Rule Options
```json
...
"react/jsx-newline": [<enabled>, { "prevent": <boolean> }]
"react/jsx-newline": [<enabled>, { "prevent": <boolean>, "allowMultilines": <boolean> }]
...
```

* enabled: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
* prevent: optional boolean. If `true` prevents empty lines between adjacent JSX elements and expressions. Defaults to `false`.
* allowMultilines: optional boolean. If `true` and `prevent` is also equal to `true`, it allows newlines after multiline JSX elements and expressions. Defaults to `false`.

## Examples

Expand Down Expand Up @@ -127,6 +128,37 @@ Examples of **correct** code for this rule, when configured with `{ "prevent": t
</div>
```

Examples of **incorrect** code for this rule, when configured with `{ "prevent": true, "allowMultilines": true }`:

```jsx
<div>
{showSomething === true && <Something />}

<Button>Button 3</Button>
{showSomethingElse === true ? (
<SomethingElse />
) : (
<ErrorMessage />
)}
</div>
```

Examples of **correct** code for this rule, when configured with `{ "prevent": true, "allowMultilines": true }`:

```jsx
<div>
{showSomething === true && <Something />}

<Button>Button 3</Button>

{showSomethingElse === true ? (
<SomethingElse />
) : (
<ErrorMessage />
)}
</div>
```

## When Not To Use It

You can turn this rule off if you are not concerned with spacing between your JSX elements and expressions.
14 changes: 10 additions & 4 deletions lib/rules/jsx-indent-props.js
Expand Up @@ -172,17 +172,23 @@ module.exports = {
* @param {Number} indent needed indent
*/
function checkNodesIndent(nodes, indent) {
let nestedIndent = indent;
nodes.forEach((node) => {
const nodeIndent = getNodeIndent(node);
if (line.isUsingOperator && !line.currentOperator && indentSize !== 'first' && !ignoreTernaryOperator) {
indent += indentSize;
if (
line.isUsingOperator
&& !line.currentOperator
&& indentSize !== 'first'
&& !ignoreTernaryOperator
) {
nestedIndent += indentSize;
line.isUsingOperator = false;
}
if (
node.type !== 'ArrayExpression' && node.type !== 'ObjectExpression'
&& nodeIndent !== indent && astUtil.isNodeFirstInLine(context, node)
&& nodeIndent !== nestedIndent && astUtil.isNodeFirstInLine(context, node)
) {
report(node, indent, nodeIndent);
report(node, nestedIndent, nodeIndent);
}
});
}
Expand Down
53 changes: 51 additions & 2 deletions lib/rules/jsx-newline.js
Expand Up @@ -16,8 +16,13 @@ const report = require('../util/report');
const messages = {
require: 'JSX element should start in a new line',
prevent: 'JSX element should not start in a new line',
allowMultilines: 'Multiline JSX elements should start in a new line',
};

function isMultilined(node) {
return node.loc.start.line !== node.loc.end.line;
}

module.exports = {
meta: {
docs: {
Expand All @@ -37,19 +42,45 @@ module.exports = {
default: false,
type: 'boolean',
},
allowMultilines: {
default: false,
type: 'boolean',
},
},
additionalProperties: false,
if: {
properties: {
allowMultilines: {
const: true,
},
},
},
then: {
properties: {
prevent: {
const: true,
},
},
required: [
'prevent',
],
},
},
],
},
create(context) {
const jsxElementParents = new Set();
const sourceCode = context.getSourceCode();

return {
'Program:exit'() {
jsxElementParents.forEach((parent) => {
parent.children.forEach((element, index, elements) => {
if (element.type === 'JSXElement' || element.type === 'JSXExpressionContainer') {
const configuration = context.options[0] || {};
const prevent = configuration.prevent || false;
const allowMultilines = configuration.allowMultilines || false;

const firstAdjacentSibling = elements[index + 1];
const secondAdjacentSibling = elements[index + 2];

Expand All @@ -62,10 +93,28 @@ module.exports = {
// Check adjacent sibling has the proper amount of newlines
const isWithoutNewLine = !/\n\s*\n/.test(firstAdjacentSibling.value);

const prevent = !!(context.options[0] || {}).prevent;
if (allowMultilines && (isMultilined(element) || isMultilined(secondAdjacentSibling))) {
if (!isWithoutNewLine) return;

if (isWithoutNewLine === prevent) return;
const regex = /(\n)(?!.*\1)/g;
const replacement = '\n\n';
const messageId = 'allowMultilines';

report(context, messages[messageId], messageId, {
node: secondAdjacentSibling,
fix(fixer) {
return fixer.replaceText(
firstAdjacentSibling,
sourceCode.getText(firstAdjacentSibling)
.replace(regex, replacement)
);
},
});

return;
}

if (isWithoutNewLine === prevent) return;
const messageId = prevent
? 'prevent'
: 'require';
Expand Down
28 changes: 15 additions & 13 deletions lib/rules/jsx-no-literals.js
Expand Up @@ -70,7 +70,8 @@ module.exports = {
config.allowedStrings = new Set(config.allowedStrings.map(trimIfString));

function defaultMessageId() {
if (config.noAttributeStrings) {
const ancestorIsJSXElement = arguments.length >= 1 && arguments[0];
if (config.noAttributeStrings && !ancestorIsJSXElement) {
return 'noStringsInAttributes';
}
if (config.noStrings) {
Expand All @@ -79,17 +80,6 @@ module.exports = {
return 'literalNotInJSXExpression';
}

function reportLiteralNode(node, messageId) {
messageId = messageId || defaultMessageId();

report(context, messages[messageId], messageId, {
node,
data: {
text: context.getSourceCode().getText(node).trim(),
},
});
}

function getParentIgnoringBinaryExpressions(node) {
let current = node;
while (current.parent.type === 'BinaryExpression') {
Expand All @@ -107,7 +97,7 @@ module.exports = {
function isParentNodeStandard() {
if (!/^[\s]+$/.test(node.value) && typeof node.value === 'string' && parent.type.includes('JSX')) {
if (config.noAttributeStrings) {
return parent.type === 'JSXAttribute';
return parent.type === 'JSXAttribute' || parent.type === 'JSXElement';
}
if (!config.noAttributeStrings) {
return parent.type !== 'JSXAttribute';
Expand Down Expand Up @@ -146,6 +136,18 @@ module.exports = {
return parentType === 'JSXFragment' || parentType === 'JSXElement' || grandParentType === 'JSXElement';
}

function reportLiteralNode(node, messageId) {
const ancestorIsJSXElement = hasJSXElementParentOrGrandParent(node);
messageId = messageId || defaultMessageId(ancestorIsJSXElement);

report(context, messages[messageId], messageId, {
node,
data: {
text: context.getSourceCode().getText(node).trim(),
},
});
}

// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
Expand Down
23 changes: 12 additions & 11 deletions package.json
@@ -1,6 +1,6 @@
{
"name": "eslint-plugin-react",
"version": "7.30.0",
"version": "7.30.1",
"author": "Yannick Croissant <yannick.croissant+npm@gmail.com>",
"description": "React specific linting rules for ESLint",
"main": "index.js",
Expand Down Expand Up @@ -39,14 +39,15 @@
"string.prototype.matchall": "^4.0.7"
},
"devDependencies": {
"@babel/core": "^7.17.12",
"@babel/eslint-parser": "^7.17.0",
"@babel/plugin-syntax-decorators": "^7.17.12",
"@babel/plugin-syntax-do-expressions": "^7.16.7",
"@babel/plugin-syntax-function-bind": "^7.16.7",
"@babel/preset-react": "^7.17.12",
"@babel/core": "^7.18.6",
"@babel/eslint-parser": "^7.18.2",
"@babel/plugin-syntax-decorators": "^7.18.6",
"@babel/plugin-syntax-do-expressions": "^7.18.6",
"@babel/plugin-syntax-function-bind": "^7.18.6",
"@babel/preset-react": "^7.18.6",
"@technote-space/doctoc": "~2.4",
"@types/eslint": "=7.2.10",
"@types/estree": "0.0.51",
"@types/estree": "0.0.52",
"@types/node": "^16.11.35",
"@typescript-eslint/parser": "^2.34.0 || ^3.10.1 || ^4.0.0 || ^5.0.0",
"aud": "^2.0.0",
Expand All @@ -55,12 +56,12 @@
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-eslint-plugin": "^2.3.0 || ^3.5.3 || ^4.0.1",
"eslint-plugin-import": "^2.26.0",
"eslint-remote-tester": "^2.1.4",
"eslint-remote-tester-repositories": "^0.0.5",
"eslint-remote-tester": "^3.0.0",
"eslint-remote-tester-repositories": "^0.0.6",
"eslint-scope": "^3.7.3",
"espree": "^3.5.4",
"istanbul": "^0.4.5",
"ls-engines": "^0.6.6",
"ls-engines": "^0.7.0",
"markdown-magic": "^2.6.0",
"mocha": "^5.2.0",
"npmignore": "^0.3.0",
Expand Down

0 comments on commit 757f0b5

Please sign in to comment.