-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
[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
Changes from 6 commits
5605a36
d5e5a2e
0b5e95d
5bd5a7f
52aaa54
1a0cac1
6ea4972
2d695c4
ba3dc83
b8c6698
b647ac8
2397a16
50cc416
c237913
3a03eea
78e2ee2
d87c53f
0f0e292
82fe18e
03cf18b
dfddc4d
50b3ae8
fe850fb
35cb961
c299551
b180919
c66def5
0554b49
cb93f6b
8adfb8c
0203b70
d2b2aac
401e341
f2a4d03
2439068
d3df38b
990daca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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> }] | ||
... | ||
``` | ||
|
||
### `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>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should these constants not be all upper case and use So: |
||
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}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should probably still specify the default? |
||
children: {enum: optionValues} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} | ||
}; | ||
} | ||
}; |
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}] | ||
} | ||
] | ||
}); |
There was a problem hiding this comment.
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