Skip to content

[new] [jsx-curly-brace-presence] Disallow unnecessary JSX expressions or enforce them #1349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5605a36
Add optional rule to disallow unnecessary JSX expressions
jackyho112 Aug 7, 2017
d5e5a2e
Support adding ability to enfore curly brace presence
jackyho112 Aug 7, 2017
0b5e95d
Add docs
jackyho112 Aug 8, 2017
5bd5a7f
Change doc name
jackyho112 Aug 8, 2017
52aaa54
Fix tests
jackyho112 Aug 8, 2017
1a0cac1
Fix tests
jackyho112 Aug 8, 2017
6ea4972
Account for when there are more than one child
jackyho112 Aug 8, 2017
2d695c4
Refactor according to feedback
jackyho112 Aug 8, 2017
ba3dc83
Account for template literal and a single string option to set default
jackyho112 Aug 8, 2017
b8c6698
Update docs
jackyho112 Aug 9, 2017
b647ac8
Add a few more tests
jackyho112 Aug 9, 2017
2397a16
Do a bit of refactoring
jackyho112 Aug 10, 2017
50cc416
Merge branch 'master' into add-new-rule-no-unnecessary-curly-brace
jackyho112 Aug 10, 2017
c237913
Change constant variable names to maintain consistency
jackyho112 Aug 10, 2017
3a03eea
Add ability to fix for missing curly and option for quotes
jackyho112 Aug 10, 2017
78e2ee2
Accounting for quotes
jackyho112 Aug 10, 2017
d87c53f
Account for edge cases
jackyho112 Aug 12, 2017
0f0e292
Add more tests
jackyho112 Aug 12, 2017
82fe18e
Update docs
jackyho112 Aug 12, 2017
03cf18b
Improve docs
jackyho112 Aug 12, 2017
dfddc4d
Fix a lint error
jackyho112 Aug 12, 2017
50b3ae8
Add comments
jackyho112 Aug 14, 2017
fe850fb
Get rid of deconstruction for older node version compatibility
jackyho112 Aug 14, 2017
35cb961
Add a comment
jackyho112 Aug 14, 2017
c299551
Add two more test cases
jackyho112 Aug 16, 2017
b180919
Remove ability to handle quotes
jackyho112 Aug 16, 2017
c66def5
Change tests
jackyho112 Aug 16, 2017
0554b49
Fix docs
jackyho112 Aug 16, 2017
cb93f6b
Fix an error in the doc
jackyho112 Aug 16, 2017
8adfb8c
Improve jsx-curly-brace-presence docs
jackyho112 Aug 16, 2017
0203b70
Add more tests for more than one prop
jackyho112 Aug 16, 2017
d2b2aac
Further improve docs about jsx-curly-brace-presence rule fixing
jackyho112 Aug 17, 2017
401e341
Account for feedback and add more tests
jackyho112 Aug 22, 2017
f2a4d03
Improve docs
jackyho112 Aug 22, 2017
2439068
Change a variable name
jackyho112 Aug 22, 2017
d3df38b
Change a function name
jackyho112 Aug 22, 2017
990daca
Fix an edge case
jackyho112 Aug 29, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
* [react/jsx-no-literals](docs/rules/jsx-no-literals.md): Prevent usage of unwrapped JSX strings
* [react/jsx-no-target-blank](docs/rules/jsx-no-target-blank.md): Prevent usage of unsafe `target='_blank'`
* [react/jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX
* [react/jsx-curly-brace-presence](docs/rules/jsx-curly-brace-presence.md): Enforce curly braces or disallow unnecessary curly braces in JSX
* [react/jsx-pascal-case](docs/rules/jsx-pascal-case.md): Enforce PascalCase for user-defined JSX components
* [react/jsx-sort-props](docs/rules/jsx-sort-props.md): Enforce props alphabetical sorting (fixable)
* [react/jsx-space-before-closing](docs/rules/jsx-space-before-closing.md): Validate spacing before closing bracket in JSX (fixable)
Expand Down
150 changes: 150 additions & 0 deletions docs/rules/jsx-curly-brace-presence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Enforce curly braces or disallow unnecessary curly braces in JSX props and/or children. (react/jsx-curly-brace-presence)

This rule allows you to enforce curly braces or disallow unnecessary curly braces in JSX props and/or children.

For situations where JSX expressions are unnecessary, please refer to [the React doc](https://facebook.github.io/react/docs/jsx-in-depth.html) and [this page about JSX gotchas](https://github.com/facebook/react/blob/v15.4.0-rc.3/docs/docs/02.3-jsx-gotchas.md#html-entities).

**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line

## Rule Details

By default, this rule will check for and warn about unnecessary curly braces in both JSX props and children.

You can pass in options to enforce the presence of curly braces on JSX props or children or both. The same options are available for not allowing unnecessary curly braces as well as ignoring the check.

## Rule Options

```js
...
"react/jsx-curly-brace-presence": [<enabled>, { "props": <string>, "children": <string> }]
...
```

or alternatively

```js
...
"react/jsx-curly-brace-presence": [<enabled>, <string>]
...
```

### Valid options for <string>

They are `always`, `never` and `ignore` for checking on JSX props and children.

* `always`: always enforce curly braces inside JSX props or/and children
* `never`: never allow unnecessary curly braces inside JSX props or/and children
* `ignore`: ignore the rule for JSX props or/and children

If passed in the option to fix, this is how a style violation will get fixed

* `always`: wrap a JSX attribute in curly braces/JSX expression and/or a JSX child the same way but also with double quotes
* `never`: get rid of curly braces from a JSX attribute and/or a JSX child

- All fixing operations use double quotes.

For examples:

When `{ props: "always", children: "always" }` is set, the following patterns will be given warnings.

```jsx
<App>Hello world</App>;
<App prop='Hello world'>{'Hello world'}</App>;
```

They can be fixed to:

```jsx
<App>{"Hello world"}</App>;
<App prop={"Hello world"}>{'Hello world'}</App>;
```

When `{ props: "never", children: "never" }` is set, the following patterns will be given warnings.

```jsx
<App>{'Hello world'}</App>;
<App prop={'Hello world'} attr={"foo"} />;
```

They can be fixed to:

```jsx
<App>Hello world</App>;
<App prop="Hello world" attr="foo" />;
```

### Alternative syntax

The options are also `always`, `never` and `ignore` for the same meanings.

In this syntax, only a string is provided and the default will be set to that option for checking on both JSX props and children.

For examples:

When `'always'` is set, the following patterns will be given warnings.

```jsx
<App>Hello world</App>;
<App prop='Hello world' attr="foo">Hello world</App>;
```

They can be fixed to:
```jsx
<App>{"Hello world"}</App>;
<App prop={"Hello world"} attr={"foo"}>{"Hello world"}</App>;
```

When `'never'` is set, the following pattern will be given warnings.

```jsx
<App prop={'foo'} attr={"bar"}>{'Hello world'}</App>;
```

It can fixed to:

```jsx
<App prop="foo" attr="bar">Hello world</App>;
```

## Edge cases

The fix also deals with template literals, strings with quotes, and strings with escapes characters.

* If the rule is set to get rid of unnecessary curly braces and the template literal inside a JSX expression has no expression, it will throw a warning and be fixed with double quotes. For example:

```jsx
<App prop={`Hello world`}>{`Hello world`}</App>;
```

will be warned and fixed to:

```jsx
<App prop="Hello world">Hello world</App>;
```

* If the rule is set to enforce curly braces and the strings have quotes, it will be fixed with double quotes for JSX children and the normal way for JSX attributes.

For example:


```jsx
<App prop='Hello "foo" world'>Hello 'foo' "bar" world</App>;
```

will warned and fixed to:

```jsx
<App prop={"Hello \"foo\" world"}>{"Hello 'foo' \"bar\" world"}</App>;
```

* If the rule is set to get rid of unnecessary curly braces and the strings have escaped characters, it will not warn or fix for JSX children because JSX expressions are necessary in this case. For instance:

The following pattern will not be given a warning even if `'never'` is passed.

```jsx
<App>{"Hello \u00b7 world"}</App>;
```

## When Not To Use It

You should turn this rule off if you are not concerned about maintaining consistency regarding the use of curly braces in JSX props and/or children as well as the use of unnecessary JSX expressions.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const allRules = {
'jsx-no-literals': require('./lib/rules/jsx-no-literals'),
'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'),
'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
'jsx-curly-brace-presence': require('./lib/rules/jsx-curly-brace-presence'),
'jsx-pascal-case': require('./lib/rules/jsx-pascal-case'),
'jsx-sort-props': require('./lib/rules/jsx-sort-props'),
'jsx-space-before-closing': require('./lib/rules/jsx-space-before-closing'),
Expand Down
189 changes: 189 additions & 0 deletions lib/rules/jsx-curly-brace-presence.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
* @fileoverview Enforce curly braces or disallow unnecessary curly brace in JSX
* @author Jacky Ho
*/
'use strict';

// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------

const OPTION_ALWAYS = 'always';
const OPTION_NEVER = 'never';
const OPTION_IGNORE = 'ignore';

const OPTION_VALUES = [
OPTION_ALWAYS,
OPTION_NEVER,
OPTION_IGNORE
];
const DEFAULT_CONFIG = {props: OPTION_NEVER, children: OPTION_NEVER};

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description:
'Disallow unnecessary JSX expressions when literals alone are sufficient ' +
'or enfore JSX expressions on literals in JSX children or attributes',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code',

schema: [
{
oneOf: [
{
type: 'object',
properties: {
props: {enum: OPTION_VALUES, default: OPTION_NEVER},
children: {enum: OPTION_VALUES, default: OPTION_NEVER}
},
additionalProperties: false
},
{
enum: OPTION_VALUES
}
]
}
]
},

create: function(context) {
const ruleOptions = context.options[0];
const userConfig = typeof ruleOptions === 'string' ?
{props: ruleOptions, children: ruleOptions} :
Object.assign({}, DEFAULT_CONFIG, ruleOptions);

function containsBackslashForEscaping(rawStringValue) {
return rawStringValue.includes('\\');
}

function escapeDoubleQuotes(rawStringValue) {
return rawStringValue.replace(/\\"/g, '"').replace(/"/g, '\\"');
}

/**
* Report and fix an unnecessary curly brace violation on a node
* @param {ASTNode} node - The AST node with an unnecessary JSX expression
* @param {String} text - The text to replace the unnecessary JSX expression
*/
function reportUnnecessaryCurly(JSXExpressionNode) {
context.report({
node: JSXExpressionNode,
message: 'Curly braces are unnecessary here.',
fix: function(fixer) {
const expression = JSXExpressionNode.expression;
const expressionType = expression.type;
const parentType = JSXExpressionNode.parent.type;

let textToReplace;
if (parentType === 'JSXAttribute') {
textToReplace = `"${escapeDoubleQuotes(
expressionType === 'TemplateLiteral' ?
expression.quasis[0].value.raw :
expression.raw.substring(1, expression.raw.length - 1)
)}"`;
} else {
textToReplace = expressionType === 'TemplateLiteral' ?
expression.quasis[0].value.cooked : expression.value;
}

return fixer.replaceText(JSXExpressionNode, textToReplace);
}
});
}

function reportMissingCurly(literalNode) {
context.report({
node: literalNode,
message: 'Need to wrap this literal in a JSX expression.',
fix: function(fixer) {
const expression = literalNode.parent.type === 'JSXAttribute' ?
`{"${escapeDoubleQuotes(
literalNode.raw.substring(1, literalNode.raw.length - 1)
)}"}` :
`{${JSON.stringify(literalNode.value)}}`;

return fixer.replaceText(literalNode, expression);
}
});
}

function lintUnnecessaryCurly(JSXExpressionNode) {
const expression = JSXExpressionNode.expression;
const expressionType = expression.type;
const parentType = JSXExpressionNode.parent.type;

if (
expressionType === 'Literal' &&
typeof expression.value === 'string' && (
parentType === 'JSXAttribute' ||
!containsBackslashForEscaping(expression.raw))
) {
reportUnnecessaryCurly(JSXExpressionNode);
} else if (
expressionType === 'TemplateLiteral' &&
expression.expressions.length === 0 && (
parentType === 'JSXAttribute' ||
!containsBackslashForEscaping(expression.quasis[0].value.raw))
) {
reportUnnecessaryCurly(JSXExpressionNode);
}
}

function areRuleConditionsSatisfied(parentType, config, ruleCondition) {
return (
parentType === 'JSXAttribute' &&
typeof config.props === 'string' &&
config.props === ruleCondition
) || (
parentType === 'JSXElement' &&
typeof config.children === 'string' &&
config.children === ruleCondition
);
}

function shouldCheckForUnnecessaryCurly(parent, config) {
const parentType = parent.type;

// If there are more than one JSX child, there is no need to check for
// unnecessary curly braces.
if (parentType === 'JSXElement' && parent.children.length !== 1) {
return false;
}

return areRuleConditionsSatisfied(parentType, config, OPTION_NEVER);
}

function shouldCheckForMissingCurly(parentType, config) {
return areRuleConditionsSatisfied(parentType, config, OPTION_ALWAYS);
}

// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------

return {
JSXExpressionContainer: node => {
const parent = node.parent;

if (shouldCheckForUnnecessaryCurly(parent, userConfig)) {
lintUnnecessaryCurly(node);
}
},

Literal: node => {
const parentType = node.parent.type;

if (shouldCheckForMissingCurly(parentType, userConfig)) {
reportMissingCurly(node);
}
}
};
}
};
Loading