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 6 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
* [react/jsx-space-before-closing](docs/rules/jsx-space-before-closing.md): Validate spacing before closing bracket in JSX (fixable)
Expand Down
63 changes: 63 additions & 0 deletions docs/rules/jsx-curly-brace-presence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 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 but only for fixing unnecessary curly braces.

## 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/forbid-elements": [<enabled>, { "props": <string>, "children": <string> }]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might be the wrong rule name

...
```

### `props`, `children`

For both, the valid values are `always`, `never` and `ignore`.

* `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

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>;
```

The following patterns won't.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should both of these examples use the same quote style?

```

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

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

If passed in the option to fix, they will be corrected to

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

## 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
132 changes: 132 additions & 0 deletions lib/rules/jsx-curly-brace-presence.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* @fileoverview Enforce curly braces or disallow unnecessary curly brace in JSX
* @author Jacky Ho
*/
'use strict';

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

const optionAlways = 'always';
const optionNever = 'never';
const optionIgnore = 'ignore';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these constants not be all upper case and use _ as delimiter between words, to be more consistent with the rest of the code base? See no-danger, no-typos, no-escaped-entities, and others...

So: OPTION_ALWAYS, OPTION_NEVER and OPTION_IGNORE

const optionValues = [optionAlways, optionNever, optionIgnore];
const defaultConfig = {props: optionNever, children: optionNever};

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

schema: [
{
type: 'object',
properties: {
props: {enum: optionValues},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably still specify the default?

children: {enum: optionValues}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here?

},
additionalProperties: false
}
]
},

create: function(context) {
const userConfig = Object.assign(
{},
defaultConfig,
context.options[0]
);

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

function reportUnnecessaryCurly(node) {
context.report({
node: node,
message: 'Curly braces are unnecessary here.',
fix: function(fixer) {
let {expression: {value}} = node;

if (node.parent.type === 'JSXAttribute') {
value = `"${value}"`;
}

return fixer.replaceText(node, value);
}
});
}

function reportMissingCurly(node) {
context.report({
node: node,
message: 'Need to wrap this literal in a JSX expression.'
});
}

function lintUnnecessaryCurly(node) {
const {expression} = node;

if (
typeof expression.value === 'string' &&
!containsBackslashForEscaping(expression.raw)
) {
reportUnnecessaryCurly(node);
}
}

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

function shouldCheckForUnnecessaryCurly(expressionType, parentType, config) {
if (expressionType !== 'Literal') {
return false;
}

return areRuleConditionsSatisfied({
parentType, config, ruleCondition: optionNever
});
}

function shouldCheckForMissingCurly(parentType, config) {
return areRuleConditionsSatisfied({
parentType, config, ruleCondition: optionAlways
});
}

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

return {
JSXExpressionContainer: function(node) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any particular reason to change these from arrows?

const {
expression: {type},
parent: {type: parentType}
} = node;

if (shouldCheckForUnnecessaryCurly(type, parentType, userConfig)) {
lintUnnecessaryCurly(node);
}
},

Literal: function(node) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here?

const {parent: {type: parentType}} = node;

if (shouldCheckForMissingCurly(parentType, userConfig)) {
reportMissingCurly(node);
}
}
};
}
};
130 changes: 130 additions & 0 deletions tests/lib/rules/jsx-curly-brace-presence.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* @fileoverview Enforce curly braces or disallow unnecessary curly braces in JSX
* @author Jacky Ho
*/
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const rule = require('../../../lib/rules/jsx-curly-brace-presence');
const RuleTester = require('eslint').RuleTester;
const parserOptions = {
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
};

const missingCurlyMessage = 'Need to wrap this literal in a JSX expression.';
const unnecessaryCurlyMessage = 'Curly braces are unnecessary here.';

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

const ruleTester = new RuleTester({parserOptions});
ruleTester.run('jsx-curly-brace-presence', rule, {
valid: [
{
code: '<App>{<myApp></myApp>}</App>'
},
{
code: '<App>{[]}</App>'
},
{
code: '<App>foo</App>'
},
{
code: '<App prop=\'bar\'>foo</App>'
},
{
code: '<App prop={true}>foo</App>'
},
{
code: '<App prop>foo</App>'
},
{
code: '<App prop=\'bar\'>{\'foo \\n bar\'}</App>'
},
{
code: '<App prop={\'foo \\u00b7 bar\'}>foo</App>'
},
{
code: '<MyComponent prop=\'bar\'>foo</MyComponent>',
options: [{props: 'never'}]
},
{
code: '<MyComponent>foo</MyComponent>',
options: [{children: 'never'}]
},
{
code: '<MyComponent prop={\'bar\'}>foo</MyComponent>',
options: [{props: 'always'}]
},
{
code: '<MyComponent>{\'foo\'}</MyComponent>',
options: [{children: 'always'}]
},
{
code: '<MyComponent>{\'foo\'}</MyComponent>',
options: [{children: 'ignore'}]
},
{
code: '<MyComponent prop={\'bar\'}>foo</MyComponent>',
options: [{props: 'ignore'}]
},
{
code: '<MyComponent prop=\'bar\'>foo</MyComponent>',
options: [{props: 'ignore'}]
},
{
code: '<MyComponent>foo</MyComponent>',
options: [{children: 'ignore'}]
},
{
code: '<MyComponent prop=\'bar\'>{\'foo\'}</MyComponent>',
options: [{children: 'always', props: 'never'}]
},
{
code: '<MyComponent prop={\'bar\'}>foo</MyComponent>',
options: [{children: 'never', props: 'always'}]
}
],

invalid: [
{
code: '<MyComponent>{\'foo\'}</MyComponent>',
output: '<MyComponent>foo</MyComponent>',
errors: [{message: unnecessaryCurlyMessage}]
},
{
code: '<MyComponent prop={\'bar\'}>foo</MyComponent>',
output: '<MyComponent prop="bar">foo</MyComponent>',
errors: [{message: unnecessaryCurlyMessage}]
},
{
code: '<MyComponent>{\'foo\'}</MyComponent>',
output: '<MyComponent>foo</MyComponent>',
options: [{children: 'never'}],
errors: [{message: unnecessaryCurlyMessage}]
},
{
code: '<MyComponent prop={\'bar\'}>foo</MyComponent>',
output: '<MyComponent prop="bar">foo</MyComponent>',
options: [{props: 'never'}],
errors: [{message: unnecessaryCurlyMessage}]
},
{
code: '<MyComponent prop=\'bar\'>foo</MyComponent>',
options: [{props: 'always'}],
errors: [{message: missingCurlyMessage}]
},
{
code: '<MyComponent>foo</MyComponent>',
options: [{children: 'always'}],
errors: [{message: missingCurlyMessage}]
}
]
});