-
-
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 29 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,143 @@ | ||
# 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/forbid-elements": [<enabled>, { "props": <string>, "children": <string> }] | ||
... | ||
``` | ||
|
||
or alternatively | ||
|
||
```js | ||
... | ||
"react/forbid-elements": [<enabled>, <string>] | ||
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 |
||
... | ||
``` | ||
|
||
### 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 and fix with double quotes for JSX children inside the generated JSX expressions | ||
* `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>; | ||
``` | ||
|
||
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'} />; | ||
``` | ||
|
||
They can be fixed to: | ||
|
||
```jsx | ||
<App>Hello world</App>; | ||
<App prop='Hello world' />; | ||
``` | ||
|
||
### 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'>Hello world</App>; | ||
``` | ||
|
||
They can be fixed to: | ||
```jsx | ||
<App>{"Hello world"}</App>; | ||
<App prop={"Hello world"}>{"Hello world"}</App>; | ||
``` | ||
|
||
When `'never'` is set, the following pattern will be given warnings. | ||
|
||
```jsx | ||
<App prop={'foo'}>{'Hello world'}</App>; | ||
``` | ||
|
||
It can fixed to: | ||
|
||
```jsx | ||
<App prop='foo'>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. let's use multiple props in this example so we can show that it allows either single or double quotes (one of each) |
||
``` | ||
|
||
## Edge cases | ||
|
||
The fix also deals with template literals, strings with quotes and strings with escapes characters. | ||
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. please use the Oxford comma - in this case, after "quotes" |
||
|
||
* 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>; | ||
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. is it expected that the quote style is unchanged for |
||
``` | ||
|
||
* 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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
/** | ||
* @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('\\'); | ||
} | ||
|
||
/** | ||
* 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 = expressionType === 'TemplateLiteral' ? | ||
`"${expression.quasis[0].value.raw}"` : expression.raw; | ||
} 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' ? | ||
`{${literalNode.raw}}` : `{${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' && ( | ||
!containsBackslashForEscaping(expression.raw) || | ||
parentType === 'JSXAttribute') | ||
) { | ||
reportUnnecessaryCurly(JSXExpressionNode); | ||
} else if ( | ||
expressionType === 'TemplateLiteral' && | ||
expression.expressions.length === 0 && ( | ||
!containsBackslashForEscaping(expression.quasis[0].value.raw) || | ||
parentType === 'JSXAttribute') | ||
) { | ||
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 => { | ||
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. I made the change to maintain consistencies across files, but I guess it doesn't matter anyway. I have changed it back to using the arrow function. |
||
const parent = node.parent; | ||
|
||
if (shouldCheckForUnnecessaryCurly(parent, userConfig)) { | ||
lintUnnecessaryCurly(node); | ||
} | ||
}, | ||
|
||
Literal: node => { | ||
const parentType = node.parent.type; | ||
|
||
if (shouldCheckForMissingCurly(parentType, userConfig)) { | ||
reportMissingCurly(node); | ||
} | ||
} | ||
}; | ||
} | ||
}; |
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