From 5605a367f254a02e2ad134dc4e8aec98a3b39e1e Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 7 Aug 2017 13:16:52 -0700 Subject: [PATCH 01/36] Add optional rule to disallow unnecessary JSX expressions --- README.md | 1 + docs/rules/jsx-no-unnecessary-curly-brace.md | 47 ++++++++ index.js | 1 + lib/rules/jsx-no-unnecessary-curly-brace.js | 103 ++++++++++++++++++ package.json | 2 +- .../rules/jsx-no-unnecessary-curly-brace.js | 56 ++++++++++ 6 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 docs/rules/jsx-no-unnecessary-curly-brace.md create mode 100644 lib/rules/jsx-no-unnecessary-curly-brace.js create mode 100644 tests/lib/rules/jsx-no-unnecessary-curly-brace.js diff --git a/README.md b/README.md index 2dd2cddb5a..f576d9c81f 100644 --- a/README.md +++ b/README.md @@ -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-no-unnecessary-curly-brace](docs/rules/jsx-no-unnecessary-curly-brace.md): Disallow unnecessary JSX expressions * [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) diff --git a/docs/rules/jsx-no-unnecessary-curly-brace.md b/docs/rules/jsx-no-unnecessary-curly-brace.md new file mode 100644 index 0000000000..b9e463ac66 --- /dev/null +++ b/docs/rules/jsx-no-unnecessary-curly-brace.md @@ -0,0 +1,47 @@ +Work in progress + +```jsx + +``` + +should be + +```jsx + +``` + +and + +```jsx + +``` + +should be + +```jsx + +``` + +and + +```jsx + {'Foo'} +``` + +should be + +```jsx + Foo +``` + +but + +```jsx + {'First \u00b7 Second'} +``` + +is ok because it contains escaped characters. + +References: +https://facebook.github.io/react/docs/jsx-in-depth.html +https://github.com/facebook/react/blob/v15.4.0-rc.3/docs/docs/02.3-jsx-gotchas.md#html-entities diff --git a/index.js b/index.js index 4a1e118c9b..f1b4709915 100644 --- a/index.js +++ b/index.js @@ -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-no-unnecessary-curly-brace': require('./lib/rules/jsx-no-unnecessary-curly-brace'), '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'), diff --git a/lib/rules/jsx-no-unnecessary-curly-brace.js b/lib/rules/jsx-no-unnecessary-curly-brace.js new file mode 100644 index 0000000000..67d607dbbe --- /dev/null +++ b/lib/rules/jsx-no-unnecessary-curly-brace.js @@ -0,0 +1,103 @@ +/** + * @fileoverview Prevent using string literals in React component definition + * @author Caleb Morris + * @author David Buchan-Swanson + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +const options = { + always: 'always', + never: 'never', + ignore: 'ignore' +} +const optionValues = ['always', 'never', 'ignore'] + +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 }, + children: { enum: optionValues } + }, + additionalProperties: false + } + ] + }, + + create: function(context) { + const config = context.options[0] || {}; + + function containsBackslashForEscaping(rawStringValue) { + return JSON.stringify(rawStringValue).includes('\\'); + } + + function reportStyleViolation(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 isRuleViolated(node) { + const { expression } = node; + + if ( + typeof expression.value === 'string' && + !containsBackslashForEscaping(expression.raw) + ) { + reportStyleViolation(node) + } + } + + function shouldCheckForRule(expressionType, parentType, config) { + return ( + expressionType === 'Literal' && ( + parentType === 'JSXAttribute' && + config.props !== 'never' + ) || ( + parentType === 'JSXElement' && + config.children !== 'never' + ) + ) + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + JSXExpressionContainer: (node) => { + const { + expression: { type }, + parent: { type: parentType } + } = node + + if (shouldCheckForRule(type, parentType, config)) { + isRuleViolated(node) + } + } + } + } +}; diff --git a/package.json b/package.json index c1a52caa51..ff15111ee0 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "coveralls": "cat ./reports/coverage/lcov.info | coveralls", "lint": "eslint ./", "test": "npm run lint && npm run unit-test", - "unit-test": "istanbul cover --dir reports/coverage node_modules/mocha/bin/_mocha tests/**/*.js -- --reporter dot" + "unit-test": "istanbul cover --dir reports/coverage node_modules/mocha/bin/_mocha tests/lib/rules/jsx-no-unnecessary-curly-brace.js -- --reporter dot" }, "files": [ "LICENSE", diff --git a/tests/lib/rules/jsx-no-unnecessary-curly-brace.js b/tests/lib/rules/jsx-no-unnecessary-curly-brace.js new file mode 100644 index 0000000000..90bea66879 --- /dev/null +++ b/tests/lib/rules/jsx-no-unnecessary-curly-brace.js @@ -0,0 +1,56 @@ +/** + * @fileoverview Enforce or disallow spaces inside of curly braces in JSX attributes. + * @author Yannick Croissant + * @author Erik Wendel + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/jsx-no-unnecessary-curly-brace'); +const RuleTester = require('eslint').RuleTester; +const parserOptions = { + ecmaVersion: 8, + sourceType: 'module', + ecmaFeatures: { + experimentalObjectRestSpread: true, + jsx: true + } +}; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({parserOptions}); +ruleTester.run('jsx-curly-spacing', rule, { + valid: [ + { code: "foo" }, + { code: "foo" }, + { + code: "{'foo'}", + options: [{ children: 'never' }] + }, + { + code: "foo", + options: [{ props: 'never' }] + }, + { code: "{'foo \\n bar'}" }, + { code: "foo" } + ], + + invalid: [ + { + code: "{'foo'}", + output: "foo", + errors: [{ message: 'Curly braces are unnecessary here.' }] + }, + { + code: "foo", + output: 'foo', + errors: [{ message: 'Curly braces are unnecessary here.' }] + }, + ] +}) From d5e5a2e716bf6a3b0105273469534cc8fa01cee9 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 7 Aug 2017 16:46:19 -0700 Subject: [PATCH 02/36] Support adding ability to enfore curly brace presence --- README.md | 2 +- index.js | 2 +- ...y-brace.js => jsx-curly-brace-presence.js} | 76 ++++++++---- package.json | 2 +- tests/lib/rules/jsx-curly-brace-presence.js | 110 ++++++++++++++++++ .../rules/jsx-no-unnecessary-curly-brace.js | 56 --------- 6 files changed, 166 insertions(+), 82 deletions(-) rename lib/rules/{jsx-no-unnecessary-curly-brace.js => jsx-curly-brace-presence.js} (50%) create mode 100644 tests/lib/rules/jsx-curly-brace-presence.js delete mode 100644 tests/lib/rules/jsx-no-unnecessary-curly-brace.js diff --git a/README.md b/README.md index f576d9c81f..62f7774bec 100644 --- a/README.md +++ b/README.md @@ -141,7 +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-no-unnecessary-curly-brace](docs/rules/jsx-no-unnecessary-curly-brace.md): Disallow unnecessary JSX expressions +* [react/jsx-curly-brace-presence](docs/rules/jsx-curly-brace-presence.md): Disallow unnecessary JSX expressions * [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) diff --git a/index.js b/index.js index f1b4709915..13b72fdd4d 100644 --- a/index.js +++ b/index.js @@ -28,7 +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-no-unnecessary-curly-brace': require('./lib/rules/jsx-no-unnecessary-curly-brace'), + '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'), diff --git a/lib/rules/jsx-no-unnecessary-curly-brace.js b/lib/rules/jsx-curly-brace-presence.js similarity index 50% rename from lib/rules/jsx-no-unnecessary-curly-brace.js rename to lib/rules/jsx-curly-brace-presence.js index 67d607dbbe..aed4dc0e6c 100644 --- a/lib/rules/jsx-no-unnecessary-curly-brace.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -1,7 +1,6 @@ /** - * @fileoverview Prevent using string literals in React component definition - * @author Caleb Morris - * @author David Buchan-Swanson + * @fileoverview Enfore curly brace presence or disallow unnecessary curly brace in JSX + * @author Jacky Ho */ 'use strict'; @@ -9,12 +8,12 @@ // Rule Definition // ------------------------------------------------------------------------------ -const options = { - always: 'always', - never: 'never', - ignore: 'ignore' -} -const optionValues = ['always', 'never', 'ignore'] + +const optionAlways = 'always'; +const optionNever = 'never'; +const optionIgnore = 'ignore'; +const optionValues = [optionAlways, optionNever, optionIgnore] +const defaultConfig = { props: optionNever, children: optionNever } module.exports = { meta: { @@ -38,13 +37,17 @@ module.exports = { }, create: function(context) { - const config = context.options[0] || {}; + const config = Object.assign( + {}, + defaultConfig, + context.options[0] + ); function containsBackslashForEscaping(rawStringValue) { return JSON.stringify(rawStringValue).includes('\\'); } - function reportStyleViolation(node) { + function reportUnecessaryCurly(node) { context.report({ node: node, message: 'Curly braces are unnecessary here.', @@ -60,29 +63,48 @@ module.exports = { }); } - function isRuleViolated(node) { + 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) ) { - reportStyleViolation(node) + reportUnecessaryCurly(node) } } - function shouldCheckForRule(expressionType, parentType, config) { + function areRuleConditionsSatisfied({ parentType, config, ruleCondition }) { return ( - expressionType === 'Literal' && ( - parentType === 'JSXAttribute' && - config.props !== 'never' - ) || ( - parentType === 'JSXElement' && - config.children !== 'never' - ) + 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 // -------------------------------------------------------------------------- @@ -94,8 +116,16 @@ module.exports = { parent: { type: parentType } } = node - if (shouldCheckForRule(type, parentType, config)) { - isRuleViolated(node) + if (shouldCheckForUnnecessaryCurly(type, parentType, config)) { + lintUnnecessaryCurly(node) + } + }, + + Literal: (node) => { + const { parent: { type: parentType }} = node; + + if (shouldCheckForMissingCurly(parentType, config)) { + reportMissingCurly(node) } } } diff --git a/package.json b/package.json index ff15111ee0..166b0a544c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "coveralls": "cat ./reports/coverage/lcov.info | coveralls", "lint": "eslint ./", "test": "npm run lint && npm run unit-test", - "unit-test": "istanbul cover --dir reports/coverage node_modules/mocha/bin/_mocha tests/lib/rules/jsx-no-unnecessary-curly-brace.js -- --reporter dot" + "unit-test": "istanbul cover --dir reports/coverage node_modules/mocha/bin/_mocha tests/lib/rules/jsx-curly-brace-presence.js -- --reporter dot" }, "files": [ "LICENSE", diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js new file mode 100644 index 0000000000..713bca1eb4 --- /dev/null +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -0,0 +1,110 @@ +/** + * @fileoverview Enfore curly brace presence or disallow unnecessary curly brace in JSX + * @author Jacky Ho + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/jsx-curly-brace-presence'); +const RuleTester = require('eslint').RuleTester; +const parserOptions = { + ecmaVersion: 8, + sourceType: 'module', + ecmaFeatures: { + experimentalObjectRestSpread: true, + 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-spacing', rule, { + valid: [ + { + code: "{}" + }, + { + code: "{[]}" + }, + { + code: "foo" + }, + { + code: "foo" + }, + { + code: "{'foo \\n bar'}" + }, + { + code: "foo" + }, + { + code: "foo", + options: [{ props: 'always' }], + }, + { + code: "{'foo'}", + options: [{ children: 'always' }], + }, + { + code: "{'foo'}", + options: [{ children: 'ignore' }], + }, + { + code: "foo", + options: [{ props: 'ignore' }], + }, + { + code: "foo", + options: [{ props: 'ignore' }], + }, + { + code: "foo", + options: [{ children: 'ignore' }], + }, + ], + + invalid: [ + { + code: "{'foo'}", + output: "foo", + errors: [{ message: unnecessaryCurlyMessage }] + }, + { + code: "foo", + output: 'foo', + errors: [{ message: unnecessaryCurlyMessage }] + }, + { + code: "{'foo'}", + output: "foo", + options: [{ children: 'never' }], + errors: [{ message: unnecessaryCurlyMessage }] + }, + { + code: "foo", + output: 'foo', + options: [{ props: 'never' }], + errors: [{ message: unnecessaryCurlyMessage }] + }, + { + code: "foo", + options: [{ props: 'always' }], + errors: [{ message: missingCurlyMessage }] + }, + { + code: "foo", + options: [{ children: 'always' }], + errors: [{ message: missingCurlyMessage }] + }, + ] +}) diff --git a/tests/lib/rules/jsx-no-unnecessary-curly-brace.js b/tests/lib/rules/jsx-no-unnecessary-curly-brace.js deleted file mode 100644 index 90bea66879..0000000000 --- a/tests/lib/rules/jsx-no-unnecessary-curly-brace.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @fileoverview Enforce or disallow spaces inside of curly braces in JSX attributes. - * @author Yannick Croissant - * @author Erik Wendel - */ -'use strict'; - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const rule = require('../../../lib/rules/jsx-no-unnecessary-curly-brace'); -const RuleTester = require('eslint').RuleTester; -const parserOptions = { - ecmaVersion: 8, - sourceType: 'module', - ecmaFeatures: { - experimentalObjectRestSpread: true, - jsx: true - } -}; - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - -const ruleTester = new RuleTester({parserOptions}); -ruleTester.run('jsx-curly-spacing', rule, { - valid: [ - { code: "foo" }, - { code: "foo" }, - { - code: "{'foo'}", - options: [{ children: 'never' }] - }, - { - code: "foo", - options: [{ props: 'never' }] - }, - { code: "{'foo \\n bar'}" }, - { code: "foo" } - ], - - invalid: [ - { - code: "{'foo'}", - output: "foo", - errors: [{ message: 'Curly braces are unnecessary here.' }] - }, - { - code: "foo", - output: 'foo', - errors: [{ message: 'Curly braces are unnecessary here.' }] - }, - ] -}) From 0b5e95d2c299b75fb94bdbef45643f5a7e35c3d6 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 7 Aug 2017 20:00:47 -0700 Subject: [PATCH 03/36] Add docs --- README.md | 2 +- docs/rules/jsx-no-unnecessary-curly-brace.md | 62 +++++++----- lib/rules/jsx-curly-brace-presence.js | 50 +++++----- package.json | 2 +- tests/lib/rules/jsx-curly-brace-presence.js | 100 +++++++++++-------- 5 files changed, 127 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 62f7774bec..b738463044 100644 --- a/README.md +++ b/README.md @@ -141,7 +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): Disallow unnecessary JSX expressions +* [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) diff --git a/docs/rules/jsx-no-unnecessary-curly-brace.md b/docs/rules/jsx-no-unnecessary-curly-brace.md index b9e463ac66..f8981adc5d 100644 --- a/docs/rules/jsx-no-unnecessary-curly-brace.md +++ b/docs/rules/jsx-no-unnecessary-curly-brace.md @@ -1,47 +1,63 @@ -Work in progress +# Enforce curly braces or disallow unnecessary curly braces in JSX props and/or children. (react/jsx-curly-brace-presence) -```jsx - -``` +This rule allows you to enforce curly braces or disallow unnecessary curly braces in JSX props and/or children. -should be +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). -```jsx - -``` +**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line but only for fixing unnecessary curly braces. -and +## Rule Details -```jsx - +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": [, { "props": , "children": }] +... ``` -should be +### `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 props or/and children +* `ignore`: ignore the rule for props or/and children + +For examples: + +When `{ props: "always", children: "always" }` is set, the following patterns will be given warnings. ```jsx - +Hello world; +{'Hello world'}; ``` -and +The following patterns won't. ```jsx - {'Foo'} +{'Hello world'}; +{'Hello world'}; ``` -should be +When `{ props: "never", children: "never" }` is set, the following patterns will be given warnings. ```jsx - Foo +{'Hello world'}; +; ``` -but +If passed in the option to fix, they will be corrected to ```jsx - {'First \u00b7 Second'} +Hello world; +; ``` -is ok because it contains escaped characters. +## When Not To Use It -References: -https://facebook.github.io/react/docs/jsx-in-depth.html -https://github.com/facebook/react/blob/v15.4.0-rc.3/docs/docs/02.3-jsx-gotchas.md#html-entities +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. diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index aed4dc0e6c..bcaaf75683 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -1,5 +1,5 @@ /** - * @fileoverview Enfore curly brace presence or disallow unnecessary curly brace in JSX + * @fileoverview Enforce curly braces or disallow unnecessary curly brace in JSX * @author Jacky Ho */ 'use strict'; @@ -12,8 +12,8 @@ const optionAlways = 'always'; const optionNever = 'never'; const optionIgnore = 'ignore'; -const optionValues = [optionAlways, optionNever, optionIgnore] -const defaultConfig = { props: optionNever, children: optionNever } +const optionValues = [optionAlways, optionNever, optionIgnore]; +const defaultConfig = {props: optionNever, children: optionNever}; module.exports = { meta: { @@ -28,8 +28,8 @@ module.exports = { { type: 'object', properties: { - props: { enum: optionValues }, - children: { enum: optionValues } + props: {enum: optionValues}, + children: {enum: optionValues} }, additionalProperties: false } @@ -37,7 +37,7 @@ module.exports = { }, create: function(context) { - const config = Object.assign( + const userConfig = Object.assign( {}, defaultConfig, context.options[0] @@ -52,10 +52,10 @@ module.exports = { node: node, message: 'Curly braces are unnecessary here.', fix: function(fixer) { - let { expression: { value }} = node; + let {expression: {value}} = node; if (node.parent.type === 'JSXAttribute') { - value = `"${value}"` + value = `"${value}"`; } return fixer.replaceText(node, value); @@ -71,22 +71,22 @@ module.exports = { } function lintUnnecessaryCurly(node) { - const { expression } = node; + const {expression} = node; if ( typeof expression.value === 'string' && !containsBackslashForEscaping(expression.raw) ) { - reportUnecessaryCurly(node) + reportUnecessaryCurly(node); } } - function areRuleConditionsSatisfied({ parentType, config, ruleCondition }) { + function areRuleConditionsSatisfied({parentType, config, ruleCondition}) { return ( parentType === 'JSXAttribute' && config.props === ruleCondition ) || ( parentType === 'JSXElement' && config.children === ruleCondition - ) + ); } function shouldCheckForUnnecessaryCurly(expressionType, parentType, config) { @@ -96,13 +96,13 @@ module.exports = { return areRuleConditionsSatisfied({ parentType, config, ruleCondition: optionNever - }) + }); } function shouldCheckForMissingCurly(parentType, config) { return areRuleConditionsSatisfied({ parentType, config, ruleCondition: optionAlways - }) + }); } // -------------------------------------------------------------------------- @@ -110,24 +110,24 @@ module.exports = { // -------------------------------------------------------------------------- return { - JSXExpressionContainer: (node) => { + JSXExpressionContainer: node => { const { - expression: { type }, - parent: { type: parentType } - } = node + expression: {type}, + parent: {type: parentType} + } = node; - if (shouldCheckForUnnecessaryCurly(type, parentType, config)) { - lintUnnecessaryCurly(node) + if (shouldCheckForUnnecessaryCurly(type, parentType, userConfig)) { + lintUnnecessaryCurly(node); } }, - Literal: (node) => { - const { parent: { type: parentType }} = node; + Literal: node => { + const {parent: {type: parentType}} = node; - if (shouldCheckForMissingCurly(parentType, config)) { - reportMissingCurly(node) + if (shouldCheckForMissingCurly(parentType, userConfig)) { + reportMissingCurly(node); } } - } + }; } }; diff --git a/package.json b/package.json index 166b0a544c..b1dcdcdc1d 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "main": "index.js", "scripts": { "coveralls": "cat ./reports/coverage/lcov.info | coveralls", - "lint": "eslint ./", + "lint": "eslint --fix ./", "test": "npm run lint && npm run unit-test", "unit-test": "istanbul cover --dir reports/coverage node_modules/mocha/bin/_mocha tests/lib/rules/jsx-curly-brace-presence.js -- --reporter dot" }, diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 713bca1eb4..6863c62800 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -1,5 +1,5 @@ /** - * @fileoverview Enfore curly brace presence or disallow unnecessary curly brace in JSX + * @fileoverview Enforce curly braces or disallow unnecessary curly braces in JSX * @author Jacky Ho */ 'use strict'; @@ -30,81 +30,103 @@ const ruleTester = new RuleTester({parserOptions}); ruleTester.run('jsx-curly-spacing', rule, { valid: [ { - code: "{}" + code: '{}' }, { - code: "{[]}" + code: '{[]}' }, { - code: "foo" + code: 'foo' }, { - code: "foo" + code: 'foo' }, { - code: "{'foo \\n bar'}" + code: 'foo' }, { - code: "foo" + code: 'foo' }, { - code: "foo", - options: [{ props: 'always' }], + code: '{\'foo \\n bar\'}' }, { - code: "{'foo'}", - options: [{ children: 'always' }], + code: 'foo' }, { - code: "{'foo'}", - options: [{ children: 'ignore' }], + code: 'foo', + options: [{props: 'never'}] }, { - code: "foo", - options: [{ props: 'ignore' }], + code: 'foo', + options: [{children: 'never'}] }, { - code: "foo", - options: [{ props: 'ignore' }], + code: 'foo', + options: [{props: 'always'}] }, { - code: "foo", - options: [{ children: 'ignore' }], + code: '{\'foo\'}', + options: [{children: 'always'}] }, + { + code: '{\'foo\'}', + options: [{children: 'ignore'}] + }, + { + code: 'foo', + options: [{props: 'ignore'}] + }, + { + code: 'foo', + options: [{props: 'ignore'}] + }, + { + code: 'foo', + options: [{children: 'ignore'}] + }, + { + code: '{\'foo\'}', + options: [{children: 'always', props: 'never'}] + }, + { + code: 'foo', + options: [{children: 'never', props: 'always'}] + } ], invalid: [ { - code: "{'foo'}", - output: "foo", - errors: [{ message: unnecessaryCurlyMessage }] + code: '{\'foo\'}', + output: 'foo', + errors: [{message: unnecessaryCurlyMessage}] }, { - code: "foo", + code: 'foo', output: 'foo', - errors: [{ message: unnecessaryCurlyMessage }] + errors: [{message: unnecessaryCurlyMessage}] }, { - code: "{'foo'}", - output: "foo", - options: [{ children: 'never' }], - errors: [{ message: unnecessaryCurlyMessage }] + code: '{\'foo\'}', + output: 'foo', + options: [{children: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] }, { - code: "foo", + code: 'foo', output: 'foo', - options: [{ props: 'never' }], - errors: [{ message: unnecessaryCurlyMessage }] + options: [{props: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] }, { - code: "foo", - options: [{ props: 'always' }], - errors: [{ message: missingCurlyMessage }] + code: 'foo', + options: [{props: 'always'}], + errors: [{message: missingCurlyMessage}] }, { - code: "foo", - options: [{ children: 'always' }], - errors: [{ message: missingCurlyMessage }] - }, + code: 'foo', + options: [{children: 'always'}], + errors: [{message: missingCurlyMessage}] + } ] -}) +}); From 5bd5a7ff3ab3fde188b09078f7a0b09848c9b738 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 7 Aug 2017 20:10:43 -0700 Subject: [PATCH 04/36] Change doc name --- ...unnecessary-curly-brace.md => jsx-curly-brace-presence.md} | 4 ++-- lib/rules/jsx-curly-brace-presence.js | 4 ++-- package.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename docs/rules/{jsx-no-unnecessary-curly-brace.md => jsx-curly-brace-presence.md} (93%) diff --git a/docs/rules/jsx-no-unnecessary-curly-brace.md b/docs/rules/jsx-curly-brace-presence.md similarity index 93% rename from docs/rules/jsx-no-unnecessary-curly-brace.md rename to docs/rules/jsx-curly-brace-presence.md index f8981adc5d..ea3363da42 100644 --- a/docs/rules/jsx-no-unnecessary-curly-brace.md +++ b/docs/rules/jsx-curly-brace-presence.md @@ -25,8 +25,8 @@ You can pass in options to enforce the presence of curly braces on JSX props or 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 props or/and children -* `ignore`: ignore the rule for 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: diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index bcaaf75683..9aa560476a 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -47,7 +47,7 @@ module.exports = { return JSON.stringify(rawStringValue).includes('\\'); } - function reportUnecessaryCurly(node) { + function reportUnnecessaryCurly(node) { context.report({ node: node, message: 'Curly braces are unnecessary here.', @@ -77,7 +77,7 @@ module.exports = { typeof expression.value === 'string' && !containsBackslashForEscaping(expression.raw) ) { - reportUnecessaryCurly(node); + reportUnnecessaryCurly(node); } } diff --git a/package.json b/package.json index b1dcdcdc1d..c1a52caa51 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,9 @@ "main": "index.js", "scripts": { "coveralls": "cat ./reports/coverage/lcov.info | coveralls", - "lint": "eslint --fix ./", + "lint": "eslint ./", "test": "npm run lint && npm run unit-test", - "unit-test": "istanbul cover --dir reports/coverage node_modules/mocha/bin/_mocha tests/lib/rules/jsx-curly-brace-presence.js -- --reporter dot" + "unit-test": "istanbul cover --dir reports/coverage node_modules/mocha/bin/_mocha tests/**/*.js -- --reporter dot" }, "files": [ "LICENSE", From 52aaa54775e7c03700424a5001d01b3ed298bb68 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 7 Aug 2017 20:38:14 -0700 Subject: [PATCH 05/36] Fix tests --- lib/rules/jsx-curly-brace-presence.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 9aa560476a..3afd47b217 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -8,12 +8,8 @@ // Rule Definition // ------------------------------------------------------------------------------ - const optionAlways = 'always'; const optionNever = 'never'; -const optionIgnore = 'ignore'; -const optionValues = [optionAlways, optionNever, optionIgnore]; -const defaultConfig = {props: optionNever, children: optionNever}; module.exports = { meta: { @@ -28,20 +24,22 @@ module.exports = { { type: 'object', properties: { - props: {enum: optionValues}, - children: {enum: optionValues} - }, - additionalProperties: false + props: { + type: 'string', + default: optionNever + }, + children: { + type: 'string', + default: optionNever + }, + additionalProperties: false + } } ] }, create: function(context) { - const userConfig = Object.assign( - {}, - defaultConfig, - context.options[0] - ); + const userConfig = context.options[0]; function containsBackslashForEscaping(rawStringValue) { return JSON.stringify(rawStringValue).includes('\\'); From 1a0cac1359919768256e707ec30cd748ab805359 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 7 Aug 2017 21:23:19 -0700 Subject: [PATCH 06/36] Fix tests --- lib/rules/jsx-curly-brace-presence.js | 27 +++++++++++---------- tests/lib/rules/jsx-curly-brace-presence.js | 4 +-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 3afd47b217..c2039eafe8 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -10,6 +10,9 @@ const optionAlways = 'always'; const optionNever = 'never'; +const optionIgnore = 'ignore'; +const optionValues = [optionAlways, optionNever, optionIgnore]; +const defaultConfig = {props: optionNever, children: optionNever}; module.exports = { meta: { @@ -24,22 +27,20 @@ module.exports = { { type: 'object', properties: { - props: { - type: 'string', - default: optionNever - }, - children: { - type: 'string', - default: optionNever - }, - additionalProperties: false - } + props: {enum: optionValues}, + children: {enum: optionValues} + }, + additionalProperties: false } ] }, create: function(context) { - const userConfig = context.options[0]; + const userConfig = Object.assign( + {}, + defaultConfig, + context.options[0] + ); function containsBackslashForEscaping(rawStringValue) { return JSON.stringify(rawStringValue).includes('\\'); @@ -108,7 +109,7 @@ module.exports = { // -------------------------------------------------------------------------- return { - JSXExpressionContainer: node => { + JSXExpressionContainer: function(node) { const { expression: {type}, parent: {type: parentType} @@ -119,7 +120,7 @@ module.exports = { } }, - Literal: node => { + Literal: function(node) { const {parent: {type: parentType}} = node; if (shouldCheckForMissingCurly(parentType, userConfig)) { diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 6863c62800..5da1a038f1 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -11,10 +11,8 @@ const rule = require('../../../lib/rules/jsx-curly-brace-presence'); const RuleTester = require('eslint').RuleTester; const parserOptions = { - ecmaVersion: 8, sourceType: 'module', ecmaFeatures: { - experimentalObjectRestSpread: true, jsx: true } }; @@ -27,7 +25,7 @@ const unnecessaryCurlyMessage = 'Curly braces are unnecessary here.'; // ------------------------------------------------------------------------------ const ruleTester = new RuleTester({parserOptions}); -ruleTester.run('jsx-curly-spacing', rule, { +ruleTester.run('jsx-curly-brace-presence', rule, { valid: [ { code: '{}' From 6ea4972598ae074c2aedb2fab2cc1b30425541c2 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 7 Aug 2017 21:48:50 -0700 Subject: [PATCH 07/36] Account for when there are more than one child --- lib/rules/jsx-curly-brace-presence.js | 18 +++++++++++------- tests/lib/rules/jsx-curly-brace-presence.js | 7 +++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index c2039eafe8..0dec74c825 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -88,8 +88,15 @@ module.exports = { ); } - function shouldCheckForUnnecessaryCurly(expressionType, parentType, config) { - if (expressionType !== 'Literal') { + function shouldCheckForUnnecessaryCurly(expressionType, parent, config) { + const {type: parentType} = parent; + + if ( + expressionType !== 'Literal' || ( + parentType === 'JSXElement' && + parent.children.length !== 1 + ) + ) { return false; } @@ -110,12 +117,9 @@ module.exports = { return { JSXExpressionContainer: function(node) { - const { - expression: {type}, - parent: {type: parentType} - } = node; + const {expression: {type}, parent} = node; - if (shouldCheckForUnnecessaryCurly(type, parentType, userConfig)) { + if (shouldCheckForUnnecessaryCurly(type, parent, userConfig)) { lintUnnecessaryCurly(node); } }, diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 5da1a038f1..f090661f38 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -36,6 +36,9 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo' }, + { + code: '{"foo"}{bar}' + }, { code: 'foo' }, @@ -59,6 +62,10 @@ ruleTester.run('jsx-curly-brace-presence', rule, { code: 'foo', options: [{children: 'never'}] }, + { + code: '{}{"123"}', + options: [{children: 'never'}] + }, { code: 'foo', options: [{props: 'always'}] From 2d695c4b1cd75df62c1c5652efb3fa1c75eb95f4 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 7 Aug 2017 22:00:57 -0700 Subject: [PATCH 08/36] Refactor according to feedback --- lib/rules/jsx-curly-brace-presence.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 0dec74c825..ec545ddc35 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -27,8 +27,8 @@ module.exports = { { type: 'object', properties: { - props: {enum: optionValues}, - children: {enum: optionValues} + props: {enum: optionValues, default: optionNever}, + children: {enum: optionValues, default: optionNever} }, additionalProperties: false } @@ -116,7 +116,7 @@ module.exports = { // -------------------------------------------------------------------------- return { - JSXExpressionContainer: function(node) { + JSXExpressionContainer: node => { const {expression: {type}, parent} = node; if (shouldCheckForUnnecessaryCurly(type, parent, userConfig)) { @@ -124,7 +124,7 @@ module.exports = { } }, - Literal: function(node) { + Literal: node => { const {parent: {type: parentType}} = node; if (shouldCheckForMissingCurly(parentType, userConfig)) { From ba3dc831e9cdbfd199af299f54b7a1007a9e990f Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Tue, 8 Aug 2017 10:22:28 -0700 Subject: [PATCH 09/36] Account for template literal and a single string option to set default --- lib/rules/jsx-curly-brace-presence.js | 76 +++++++++++---------- tests/lib/rules/jsx-curly-brace-presence.js | 66 ++++++++++++++++++ 2 files changed, 107 insertions(+), 35 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index ec545ddc35..6223ecf19e 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -23,41 +23,49 @@ module.exports = { }, fixable: 'code', - schema: [ - { - type: 'object', - properties: { - props: {enum: optionValues, default: optionNever}, - children: {enum: optionValues, default: optionNever} + schema: [{ + oneOf: [ + { + type: 'object', + properties: { + props: {enum: optionValues, default: optionNever}, + children: {enum: optionValues, default: optionNever} + }, + additionalProperties: false }, - additionalProperties: false - } - ] + { + enum: optionValues + } + ] + }] }, create: function(context) { - const userConfig = Object.assign( - {}, - defaultConfig, - context.options[0] - ); + const ruleOptions = context.options[0]; + const userConfig = typeof ruleOptions === 'string' ? + {props: ruleOptions, children: ruleOptions} : + Object.assign({}, defaultConfig, ruleOptions); function containsBackslashForEscaping(rawStringValue) { return JSON.stringify(rawStringValue).includes('\\'); } - function reportUnnecessaryCurly(node) { + /** + * Report 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(node, text) { context.report({ node: node, message: 'Curly braces are unnecessary here.', fix: function(fixer) { - let {expression: {value}} = node; - + let textToReplace = text; if (node.parent.type === 'JSXAttribute') { - value = `"${value}"`; + textToReplace = `"${text}"`; } - return fixer.replaceText(node, value); + return fixer.replaceText(node, textToReplace); } }); } @@ -70,17 +78,24 @@ module.exports = { } function lintUnnecessaryCurly(node) { - const {expression} = node; + const {expression, expression: {type}} = node; if ( - typeof expression.value === 'string' && + type === 'Literal' && + typeof expression.value === 'string' && !containsBackslashForEscaping(expression.raw) ) { - reportUnnecessaryCurly(node); + reportUnnecessaryCurly(node, expression.value); + } else if ( + type === 'TemplateLiteral' && + expression.expressions.length === 0 && + !containsBackslashForEscaping(expression.quasis[0].value.raw) + ) { + reportUnnecessaryCurly(node, expression.quasis[0].value.cooked); } } - function areRuleConditionsSatisfied({parentType, config, ruleCondition}) { + function areRuleConditionsSatisfied(parentType, config, ruleCondition) { return ( parentType === 'JSXAttribute' && config.props === ruleCondition ) || ( @@ -91,24 +106,15 @@ module.exports = { function shouldCheckForUnnecessaryCurly(expressionType, parent, config) { const {type: parentType} = parent; - if ( - expressionType !== 'Literal' || ( - parentType === 'JSXElement' && - parent.children.length !== 1 - ) - ) { + if (parentType === 'JSXElement' && parent.children.length !== 1) { return false; } - return areRuleConditionsSatisfied({ - parentType, config, ruleCondition: optionNever - }); + return areRuleConditionsSatisfied(parentType, config, optionNever); } function shouldCheckForMissingCurly(parentType, config) { - return areRuleConditionsSatisfied({ - parentType, config, ruleCondition: optionAlways - }); + return areRuleConditionsSatisfied(parentType, config, optionAlways); } // -------------------------------------------------------------------------- diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index f090661f38..b809e165f9 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -27,6 +27,37 @@ const unnecessaryCurlyMessage = 'Curly braces are unnecessary here.'; const ruleTester = new RuleTester({parserOptions}); ruleTester.run('jsx-curly-brace-presence', rule, { valid: [ + { + code: 'foo' + }, + { + code: 'foo', + options: [{props: 'never'}] + }, + { + code: 'foo', + options: [{props: 'always'}] + }, + { + code: '{`Hello ${word} World`}', + options: [{children: 'never'}] + }, + { + code: '{`Hello \\n World`}', + options: [{children: 'never'}] + }, + { + code: '{`Hello ${word} World`}{`foo`}', + options: [{children: 'never'}] + }, + { + code: 'foo', + options: [{props: 'never'}] + }, + { + code: 'foo', + options: [{props: 'never'}] + }, { code: '{}' }, @@ -97,10 +128,30 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo', options: [{children: 'never', props: 'always'}] + }, + { + code: '{\'foo\'}', + options: ['always'] + }, + { + code: '\'foo\'', + options: ['never'] } ], invalid: [ + { + code: 'foo', + output: 'foo', + options: [{props: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, + { + code: '{`foo`}', + output: 'foo', + options: [{children: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, { code: '{\'foo\'}', output: 'foo', @@ -132,6 +183,21 @@ ruleTester.run('jsx-curly-brace-presence', rule, { code: 'foo', options: [{children: 'always'}], errors: [{message: missingCurlyMessage}] + }, + { + code: '{\'foo\'}', + output: 'foo', + options: ['never'], + errors: [ + {message: unnecessaryCurlyMessage}, {message: unnecessaryCurlyMessage} + ] + }, + { + code: '\'foo\'', + options: ['always'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] } ] }); From b8c6698bc3334acdedd48845e1ca3a49f14c0df9 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Tue, 8 Aug 2017 17:05:38 -0700 Subject: [PATCH 10/36] Update docs --- docs/rules/jsx-curly-brace-presence.md | 34 ++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/docs/rules/jsx-curly-brace-presence.md b/docs/rules/jsx-curly-brace-presence.md index ea3363da42..6225c2268b 100644 --- a/docs/rules/jsx-curly-brace-presence.md +++ b/docs/rules/jsx-curly-brace-presence.md @@ -20,9 +20,17 @@ You can pass in options to enforce the presence of curly braces on JSX props or ... ``` -### `props`, `children` +or alternatively -For both, the valid values are `always`, `never` and `ignore`. +```js +... +"react/forbid-elements": [, ] +... +``` + +### Valid options for + +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 @@ -58,6 +66,28 @@ If passed in the option to fix, they will be corrected to ; ``` +### Alternative syntax + +The options are also `always`, `ignore` and `ignore` for the same meanings. + +If only a string is provided, 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 +Hello world; +Hello world; +``` + +And the following will pass. + +```jsx +{'Hello world'}; +{'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. From b647ac8dbaeee70c793a77335e8ce62ec379a980 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Tue, 8 Aug 2017 17:22:37 -0700 Subject: [PATCH 11/36] Add a few more tests --- tests/lib/rules/jsx-curly-brace-presence.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index b809e165f9..72d753240b 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -134,7 +134,11 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: ['always'] }, { - code: '\'foo\'', + code: 'foo', + options: ['never'] + }, + { + code: '{`foo ${word}`}', options: ['never'] } ], From 2397a16b46d451540890dda170e9b2a2214eac05 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Wed, 9 Aug 2017 22:09:16 -0700 Subject: [PATCH 12/36] Do a bit of refactoring --- lib/rules/jsx-curly-brace-presence.js | 31 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 6223ecf19e..fc1e09b5fc 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -55,43 +55,46 @@ module.exports = { * @param {ASTNode} node - The AST node with an unnecessary JSX expression * @param {String} text - The text to replace the unnecessary JSX expression */ - function reportUnnecessaryCurly(node, text) { + function reportUnnecessaryCurly(JSXExpressionNode, text) { context.report({ - node: node, + node: JSXExpressionNode, message: 'Curly braces are unnecessary here.', fix: function(fixer) { let textToReplace = text; - if (node.parent.type === 'JSXAttribute') { + if (JSXExpressionNode.parent.type === 'JSXAttribute') { textToReplace = `"${text}"`; } - return fixer.replaceText(node, textToReplace); + return fixer.replaceText(JSXExpressionNode, textToReplace); } }); } - function reportMissingCurly(node) { + function reportMissingCurly(literalNode) { context.report({ - node: node, + node: literalNode, message: 'Need to wrap this literal in a JSX expression.' }); } - function lintUnnecessaryCurly(node) { - const {expression, expression: {type}} = node; + function lintUnnecessaryCurly(JSXExpressionNode) { + const {expression, expression: {type}} = JSXExpressionNode; if ( type === 'Literal' && typeof expression.value === 'string' && !containsBackslashForEscaping(expression.raw) ) { - reportUnnecessaryCurly(node, expression.value); + reportUnnecessaryCurly(JSXExpressionNode, expression.value); } else if ( type === 'TemplateLiteral' && expression.expressions.length === 0 && !containsBackslashForEscaping(expression.quasis[0].value.raw) ) { - reportUnnecessaryCurly(node, expression.quasis[0].value.cooked); + reportUnnecessaryCurly( + JSXExpressionNode, + expression.quasis[0].value.cooked + ); } } @@ -103,9 +106,11 @@ module.exports = { ); } - function shouldCheckForUnnecessaryCurly(expressionType, parent, config) { + function shouldCheckForUnnecessaryCurly(parent, config) { const {type: parentType} = parent; + // 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; } @@ -123,9 +128,9 @@ module.exports = { return { JSXExpressionContainer: node => { - const {expression: {type}, parent} = node; + const {parent} = node; - if (shouldCheckForUnnecessaryCurly(type, parent, userConfig)) { + if (shouldCheckForUnnecessaryCurly(parent, userConfig)) { lintUnnecessaryCurly(node); } }, From c237913a0773ff828b384a026c1803236f2c43e6 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Wed, 9 Aug 2017 22:22:45 -0700 Subject: [PATCH 13/36] Change constant variable names to maintain consistency --- lib/rules/jsx-curly-brace-presence.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index fc1e09b5fc..f8853407f6 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -8,11 +8,11 @@ // Rule Definition // ------------------------------------------------------------------------------ -const optionAlways = 'always'; -const optionNever = 'never'; -const optionIgnore = 'ignore'; -const optionValues = [optionAlways, optionNever, optionIgnore]; -const defaultConfig = {props: optionNever, children: optionNever}; +const OPTION_ALWAYS = 'always'; +const OPTION_NEVER = 'never'; +const OPTION_IGNORE = 'ignore'; +const optionValues = [OPTION_ALWAYS, OPTION_NEVER, OPTION_IGNORE]; +const defaultConfig = {props: OPTION_NEVER, children: OPTION_NEVER}; module.exports = { meta: { @@ -28,8 +28,8 @@ module.exports = { { type: 'object', properties: { - props: {enum: optionValues, default: optionNever}, - children: {enum: optionValues, default: optionNever} + props: {enum: optionValues, default: OPTION_NEVER}, + children: {enum: optionValues, default: OPTION_NEVER} }, additionalProperties: false }, @@ -115,11 +115,11 @@ module.exports = { return false; } - return areRuleConditionsSatisfied(parentType, config, optionNever); + return areRuleConditionsSatisfied(parentType, config, OPTION_NEVER); } function shouldCheckForMissingCurly(parentType, config) { - return areRuleConditionsSatisfied(parentType, config, optionAlways); + return areRuleConditionsSatisfied(parentType, config, OPTION_ALWAYS); } // -------------------------------------------------------------------------- From 3a03eead9fb790c3e8a8e5fc9b599532a1d9375e Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Wed, 9 Aug 2017 23:42:13 -0700 Subject: [PATCH 14/36] Add ability to fix for missing curly and option for quotes --- docs/rules/jsx-curly-brace-presence.md | 2 +- lib/rules/jsx-curly-brace-presence.js | 84 ++++++++++++++++----- tests/lib/rules/jsx-curly-brace-presence.js | 19 ++++- 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/docs/rules/jsx-curly-brace-presence.md b/docs/rules/jsx-curly-brace-presence.md index 6225c2268b..18d0425073 100644 --- a/docs/rules/jsx-curly-brace-presence.md +++ b/docs/rules/jsx-curly-brace-presence.md @@ -4,7 +4,7 @@ This rule allows you to enforce curly braces or disallow unnecessary curly brace 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. +**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line ## Rule Details diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index f8853407f6..8b0d92bdad 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -11,7 +11,10 @@ const OPTION_ALWAYS = 'always'; const OPTION_NEVER = 'never'; const OPTION_IGNORE = 'ignore'; +const OPTION_SINGLE_QUOTE = 'single'; +const OPTION_DOUBLE_QUOTE = 'double'; const optionValues = [OPTION_ALWAYS, OPTION_NEVER, OPTION_IGNORE]; +const quoteOptionsValues = [OPTION_SINGLE_QUOTE, OPTION_DOUBLE_QUOTE]; const defaultConfig = {props: OPTION_NEVER, children: OPTION_NEVER}; module.exports = { @@ -23,28 +26,38 @@ module.exports = { }, fixable: 'code', - schema: [{ - oneOf: [ - { - type: 'object', - properties: { - props: {enum: optionValues, default: OPTION_NEVER}, - children: {enum: optionValues, default: OPTION_NEVER} + schema: [ + { + oneOf: [ + { + type: 'object', + properties: { + props: {enum: optionValues, default: OPTION_NEVER}, + children: {enum: optionValues, default: OPTION_NEVER} + }, + additionalProperties: false }, - additionalProperties: false - }, - { - enum: optionValues - } - ] - }] + { + enum: optionValues + }, + { + enum: quoteOptionsValues + } + ] + }, + { + enum: quoteOptionsValues + } + ] }, create: function(context) { const ruleOptions = context.options[0]; - const userConfig = typeof ruleOptions === 'string' ? - {props: ruleOptions, children: ruleOptions} : - Object.assign({}, defaultConfig, ruleOptions); + const userConfig = ( + typeof ruleOptions === 'string' && optionValues.includes(ruleOptions) ? + {props: ruleOptions, children: ruleOptions} : + Object.assign({}, defaultConfig, ruleOptions) + ); function containsBackslashForEscaping(rawStringValue) { return JSON.stringify(rawStringValue).includes('\\'); @@ -70,10 +83,45 @@ module.exports = { }); } + function translateQuoteOptionToStringLiteral(option) { + return option === OPTION_SINGLE_QUOTE ? '\'' : '"'; + } + + function wrapLiteralNodeInJSXExpression(fixer, literalNode) { + let quoteStyle = translateQuoteOptionToStringLiteral(OPTION_DOUBLE_QUOTE); + const ruleOptionSlotOne = context.options[0]; + const ruleOptionSlotTwo = context.options[1]; + + if (ruleOptionSlotTwo) { + quoteStyle = translateQuoteOptionToStringLiteral(ruleOptionSlotTwo); + } else if ( + typeof ruleOptionSlotOne === 'string' && + quoteOptionsValues.includes(ruleOptionSlotOne) + ) { + quoteStyle = translateQuoteOptionToStringLiteral(ruleOptionSlotOne); + } + + // The only possible case here is a string literal as a JSX child + return fixer.replaceText( + literalNode, + `{${quoteStyle}${literalNode.raw}${quoteStyle}}` + ); + } + function reportMissingCurly(literalNode) { context.report({ node: literalNode, - message: 'Need to wrap this literal in a JSX expression.' + message: 'Need to wrap this literal in a JSX expression.', + fix: function(fixer) { + if (literalNode.parent.type === 'JSXAttribute') { + return [ + fixer.insertTextBefore(literalNode, '{'), + fixer.insertTextAfter(literalNode, '}') + ]; + } + + return wrapLiteralNodeInJSXExpression(fixer, literalNode); + } }); } diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 72d753240b..b80d6cbc56 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -180,11 +180,25 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', + output: 'foo', options: [{props: 'always'}], errors: [{message: missingCurlyMessage}] }, { - code: 'foo', + code: 'foo bar ', + output: '{\"foo bar \"}', + options: [{children: 'always'}], + errors: [{message: missingCurlyMessage}] + }, + { + code: 'foo bar ', + output: '{\"foo bar \"}', + options: [{children: 'always'}], + errors: [{message: missingCurlyMessage}] + }, + { + code: 'foo\\nbar', + output: '{\"foo\\nbar\"}', options: [{children: 'always'}], errors: [{message: missingCurlyMessage}] }, @@ -197,7 +211,8 @@ ruleTester.run('jsx-curly-brace-presence', rule, { ] }, { - code: '\'foo\'', + code: 'foo', + output: '{\"foo\"}', options: ['always'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} From 78e2ee2281991298fccf48ae126ca640a05c3f7d Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Thu, 10 Aug 2017 13:36:43 -0700 Subject: [PATCH 15/36] Accounting for quotes --- lib/rules/jsx-curly-brace-presence.js | 85 +++++++----- tests/lib/rules/jsx-curly-brace-presence.js | 136 +++++++++++++++++++- 2 files changed, 186 insertions(+), 35 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 8b0d92bdad..9e03ab57c8 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -13,8 +13,20 @@ const OPTION_NEVER = 'never'; const OPTION_IGNORE = 'ignore'; const OPTION_SINGLE_QUOTE = 'single'; const OPTION_DOUBLE_QUOTE = 'double'; -const optionValues = [OPTION_ALWAYS, OPTION_NEVER, OPTION_IGNORE]; +const OPTION_ORIGINAL_QUOTE = 'original'; +const optionValues = [ + OPTION_ALWAYS, + `${OPTION_ALWAYS},${OPTION_SINGLE_QUOTE}`, + `${OPTION_ALWAYS},${OPTION_DOUBLE_QUOTE}`, + `${OPTION_ALWAYS},${OPTION_ORIGINAL_QUOTE}`, + OPTION_NEVER, + OPTION_IGNORE +]; const quoteOptionsValues = [OPTION_SINGLE_QUOTE, OPTION_DOUBLE_QUOTE]; +const quoteOptionValueStore = { + [OPTION_SINGLE_QUOTE]: '\'', + [OPTION_DOUBLE_QUOTE]: '"' +}; const defaultConfig = {props: OPTION_NEVER, children: OPTION_NEVER}; module.exports = { @@ -39,9 +51,6 @@ module.exports = { }, { enum: optionValues - }, - { - enum: quoteOptionsValues } ] }, @@ -53,16 +62,18 @@ module.exports = { create: function(context) { const ruleOptions = context.options[0]; - const userConfig = ( - typeof ruleOptions === 'string' && optionValues.includes(ruleOptions) ? - {props: ruleOptions, children: ruleOptions} : - Object.assign({}, defaultConfig, ruleOptions) - ); + const userConfig = typeof ruleOptions === 'string' ? + {props: ruleOptions, children: ruleOptions} : + Object.assign({}, defaultConfig, ruleOptions); function containsBackslashForEscaping(rawStringValue) { return JSON.stringify(rawStringValue).includes('\\'); } + function containsQuote(string) { + return string.match(/'|"/g); + } + /** * Report an unnecessary curly brace violation on a node * @param {ASTNode} node - The AST node with an unnecessary JSX expression @@ -83,28 +94,39 @@ module.exports = { }); } - function translateQuoteOptionToStringLiteral(option) { - return option === OPTION_SINGLE_QUOTE ? '\'' : '"'; - } - function wrapLiteralNodeInJSXExpression(fixer, literalNode) { - let quoteStyle = translateQuoteOptionToStringLiteral(OPTION_DOUBLE_QUOTE); - const ruleOptionSlotOne = context.options[0]; - const ruleOptionSlotTwo = context.options[1]; + const newDefault = context.options[1]; + const defaultQuoteOption = newDefault || OPTION_DOUBLE_QUOTE; + const {parent: {type: parentType}} = literalNode; + const {raw, value} = literalNode; + const valueContainsQuote = containsQuote(value); + let text = raw; + let userSetQuoteOption; + + if (parentType === 'JSXAttribute') { + userSetQuoteOption = userConfig.props.split(',')[1]; + text = raw.substring(1, raw.length - 1); + } else if (parentType === 'JSXElement') { + userSetQuoteOption = userConfig.children.split(',')[1]; + } - if (ruleOptionSlotTwo) { - quoteStyle = translateQuoteOptionToStringLiteral(ruleOptionSlotTwo); - } else if ( - typeof ruleOptionSlotOne === 'string' && - quoteOptionsValues.includes(ruleOptionSlotOne) + if ( + userSetQuoteOption === OPTION_ORIGINAL_QUOTE || + (parentType === 'JSXAttribute' && valueContainsQuote) ) { - quoteStyle = translateQuoteOptionToStringLiteral(ruleOptionSlotOne); + return fixer.replaceText(literalNode, `{${raw}}`); } - // The only possible case here is a string literal as a JSX child + if (parentType === 'JSXElement' && valueContainsQuote) { + return fixer.replaceText(literalNode, `{${JSON.stringify(value)}}`); + } + + const quoteStyle = quoteOptionValueStore[ + userSetQuoteOption || defaultQuoteOption + ]; return fixer.replaceText( literalNode, - `{${quoteStyle}${literalNode.raw}${quoteStyle}}` + `{${quoteStyle}${text}${quoteStyle}}` ); } @@ -113,13 +135,6 @@ module.exports = { node: literalNode, message: 'Need to wrap this literal in a JSX expression.', fix: function(fixer) { - if (literalNode.parent.type === 'JSXAttribute') { - return [ - fixer.insertTextBefore(literalNode, '{'), - fixer.insertTextAfter(literalNode, '}') - ]; - } - return wrapLiteralNodeInJSXExpression(fixer, literalNode); } }); @@ -148,9 +163,13 @@ module.exports = { function areRuleConditionsSatisfied(parentType, config, ruleCondition) { return ( - parentType === 'JSXAttribute' && config.props === ruleCondition + parentType === 'JSXAttribute' && + typeof config.props === 'string' && + config.props.split(',')[0] === ruleCondition ) || ( - parentType === 'JSXElement' && config.children === ruleCondition + parentType === 'JSXElement' && + typeof config.children === 'string' && + config.children.split(',')[0] === ruleCondition ); } diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index b80d6cbc56..5cc471c1c3 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -27,6 +27,22 @@ const unnecessaryCurlyMessage = 'Curly braces are unnecessary here.'; const ruleTester = new RuleTester({parserOptions}); ruleTester.run('jsx-curly-brace-presence', rule, { valid: [ + { + code: '{\'foo "bar"\'}', + options: [{children: 'never'}] + }, + { + code: '{\"foo \'bar\'\"}', + options: [{children: 'never'}] + }, + { + code: 'foo', + options: [{props: 'never'}] + }, + { + code: 'foo', + options: [{props: 'never'}] + }, { code: 'foo' }, @@ -180,7 +196,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', - output: 'foo', + output: 'foo', options: [{props: 'always'}], errors: [{message: missingCurlyMessage}] }, @@ -212,11 +228,127 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', - output: '{\"foo\"}', + output: '{\"foo\"}', options: ['always'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] + }, + { + code: 'foo', + output: '{\'foo\'}', + options: ['always', 'single'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo', + output: '{\"foo\"}', + options: ['always', 'double'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo', + output: '{\'foo\'}', + options: [{props: 'always,double', children: 'always,single'}], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo', + output: '{\"foo\"}', + options: [{props: 'always,single', children: 'always,double'}], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo', + output: '{\"foo\"}', + options: [{props: 'always', children: 'always,double'}, 'single'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo', + output: '{\'foo\'}', + options: [{props: 'always', children: 'always'}, 'single'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo', + output: '{\"foo\"}', + options: [{props: 'always,double', children: 'always,double'}, 'single'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo', + output: '{\"foo\"}', + options: ['always', 'double'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo', + output: '{\"foo\"}', + options: ['always', 'double'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo', + output: 'foo', + options: [{props: 'always,original'}], + errors: [{message: missingCurlyMessage}] + }, + { + code: 'foo', + output: 'foo', + options: [{props: 'always,original'}], + errors: [{message: missingCurlyMessage}] + }, + { + code: 'foo"bar"', + output: '{\"foo\\"bar\\"\"}', + options: ['always', 'single'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo"bar"', + output: '{\"foo\\"bar\\"\"}', + options: ['always', 'double'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo\'bar\'', + output: '{"foo\'bar\'"}', + options: ['always', 'single'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] + }, + { + code: 'foo\'bar\'', + output: '{"foo\'bar\'"}', + options: ['always', 'double'], + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ] } ] }); From d87c53f6128f73af3408eb9063e35e1768e9d17f Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Fri, 11 Aug 2017 18:58:24 -0700 Subject: [PATCH 16/36] Account for edge cases --- lib/rules/jsx-curly-brace-presence.js | 49 +++++++++++++-------- tests/lib/rules/jsx-curly-brace-presence.js | 40 ++++++++++------- 2 files changed, 55 insertions(+), 34 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 9e03ab57c8..e12e490c08 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -67,7 +67,7 @@ module.exports = { Object.assign({}, defaultConfig, ruleOptions); function containsBackslashForEscaping(rawStringValue) { - return JSON.stringify(rawStringValue).includes('\\'); + return rawStringValue.includes('\\'); } function containsQuote(string) { @@ -75,18 +75,28 @@ module.exports = { } /** - * Report an unnecessary curly brace violation on a node + * 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, text) { + function reportUnnecessaryCurly(JSXExpressionNode) { context.report({ node: JSXExpressionNode, message: 'Curly braces are unnecessary here.', fix: function(fixer) { - let textToReplace = text; - if (JSXExpressionNode.parent.type === 'JSXAttribute') { - textToReplace = `"${text}"`; + const { + expression, + expression: {type: expressionType}, + parent: {type: parentType} + } = JSXExpressionNode; + + 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); @@ -141,23 +151,26 @@ module.exports = { } function lintUnnecessaryCurly(JSXExpressionNode) { - const {expression, expression: {type}} = JSXExpressionNode; + const { + expression, + expression: {type: expressionType}, + parent: {type: parentType} + } = JSXExpressionNode; if ( - type === 'Literal' && - typeof expression.value === 'string' && - !containsBackslashForEscaping(expression.raw) + expressionType === 'Literal' && + typeof expression.value === 'string' && ( + !containsBackslashForEscaping(expression.raw) || + parentType === 'JSXAttribute') ) { - reportUnnecessaryCurly(JSXExpressionNode, expression.value); + reportUnnecessaryCurly(JSXExpressionNode); } else if ( - type === 'TemplateLiteral' && - expression.expressions.length === 0 && - !containsBackslashForEscaping(expression.quasis[0].value.raw) + expressionType === 'TemplateLiteral' && + expression.expressions.length === 0 && ( + !containsBackslashForEscaping(expression.quasis[0].value.raw) || + parentType === 'JSXAttribute') ) { - reportUnnecessaryCurly( - JSXExpressionNode, - expression.quasis[0].value.cooked - ); + reportUnnecessaryCurly(JSXExpressionNode); } } diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 5cc471c1c3..228034c59c 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -27,22 +27,6 @@ const unnecessaryCurlyMessage = 'Curly braces are unnecessary here.'; const ruleTester = new RuleTester({parserOptions}); ruleTester.run('jsx-curly-brace-presence', rule, { valid: [ - { - code: '{\'foo "bar"\'}', - options: [{children: 'never'}] - }, - { - code: '{\"foo \'bar\'\"}', - options: [{children: 'never'}] - }, - { - code: 'foo', - options: [{props: 'never'}] - }, - { - code: 'foo', - options: [{props: 'never'}] - }, { code: 'foo' }, @@ -349,6 +333,30 @@ ruleTester.run('jsx-curly-brace-presence', rule, { errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] + }, + { + code: '{\'foo "bar"\'}', + output: 'foo \"bar\"', + options: [{children: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, + { + code: '{\"foo \'bar\'\"}', + output: 'foo \'bar\'', + errors: [{message: unnecessaryCurlyMessage}], + options: [{children: 'never'}] + }, + { + code: 'foo', + output: 'foo', + errors: [{message: unnecessaryCurlyMessage}], + options: [{props: 'never'}] + }, + { + code: 'foo', + output: 'foo', + errors: [{message: unnecessaryCurlyMessage}], + options: [{props: 'never'}] } ] }); From 0f0e292563827eb3473eade3c96815f5e3879b05 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Sat, 12 Aug 2017 00:07:08 -0700 Subject: [PATCH 17/36] Add more tests --- lib/rules/jsx-curly-brace-presence.js | 9 +- tests/lib/rules/jsx-curly-brace-presence.js | 97 +++++++++++++++++---- 2 files changed, 83 insertions(+), 23 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index e12e490c08..f260975166 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -32,7 +32,9 @@ const defaultConfig = {props: OPTION_NEVER, children: OPTION_NEVER}; module.exports = { meta: { docs: { - description: 'Disallow unnecessary JSX expressions when literals alone are sufficient', + 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 }, @@ -93,7 +95,7 @@ module.exports = { let textToReplace; if (parentType === 'JSXAttribute') { textToReplace = expressionType === 'TemplateLiteral' ? - `\"${expression.quasis[0].value.raw}\"` : expression.raw; + `"${expression.quasis[0].value.raw}"` : expression.raw; } else { textToReplace = expressionType === 'TemplateLiteral' ? expression.quasis[0].value.cooked : expression.value; @@ -105,8 +107,7 @@ module.exports = { } function wrapLiteralNodeInJSXExpression(fixer, literalNode) { - const newDefault = context.options[1]; - const defaultQuoteOption = newDefault || OPTION_DOUBLE_QUOTE; + const defaultQuoteOption = context.options[1] || OPTION_DOUBLE_QUOTE; const {parent: {type: parentType}} = literalNode; const {raw, value} = literalNode; const valueContainsQuote = containsQuote(value); diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 228034c59c..cbc36eb093 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -39,25 +39,21 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: [{props: 'always'}] }, { - code: '{`Hello ${word} World`}', + code: '{`Hello ${word} World`}', options: [{children: 'never'}] }, { - code: '{`Hello \\n World`}', + code: '{`Hello \\n World`}', options: [{children: 'never'}] }, { - code: '{`Hello ${word} World`}{`foo`}', + code: '{`Hello ${word} World`}{`foo`}', options: [{children: 'never'}] }, { code: 'foo', options: [{props: 'never'}] }, - { - code: 'foo', - options: [{props: 'never'}] - }, { code: '{}' }, @@ -83,10 +79,11 @@ ruleTester.run('jsx-curly-brace-presence', rule, { code: '{\'foo \\n bar\'}' }, { - code: 'foo' + code: 'foo', + options: [{props: 'never'}] }, { - code: 'foo', + code: 'foo', options: [{props: 'never'}] }, { @@ -113,13 +110,17 @@ ruleTester.run('jsx-curly-brace-presence', rule, { code: 'foo', options: [{props: 'ignore'}] }, + { + code: 'foo', + options: [{children: 'ignore'}] + }, { code: 'foo', options: [{props: 'ignore'}] }, { - code: 'foo', - options: [{children: 'ignore'}] + code: 'foo', + options: [{props: 'ignore'}] }, { code: '{\'foo\'}', @@ -133,17 +134,51 @@ ruleTester.run('jsx-curly-brace-presence', rule, { code: '{\'foo\'}', options: ['always'] }, + { + code: '{\"foo\"}', + options: ['always'] + }, { code: 'foo', options: ['never'] }, { - code: '{`foo ${word}`}', + code: '{`foo ${word}`}', options: ['never'] } ], invalid: [ + { + code: 'foo', + output: 'foo', + options: [{props: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, + { + code: 'foo', + output: 'foo', + options: [{props: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, + { + code: 'foo', + output: 'foo', + options: [{props: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, + { + code: 'foo', + output: 'foo', + options: [{props: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, + { + code: 'foo', + output: 'foo', + options: [{props: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, { code: 'foo', output: 'foo', @@ -163,7 +198,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', - output: 'foo', + output: 'foo', errors: [{message: unnecessaryCurlyMessage}] }, { @@ -174,7 +209,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', - output: 'foo', + output: 'foo', options: [{props: 'never'}], errors: [{message: unnecessaryCurlyMessage}] }, @@ -190,6 +225,18 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: [{children: 'always'}], errors: [{message: missingCurlyMessage}] }, + { + code: 'foo', + output: 'foo', + options: [{props: 'always,single'}], + errors: [{message: missingCurlyMessage}] + }, + { + code: 'foo bar ', + output: '{\'foo bar \'}', + options: [{children: 'always,single'}], + errors: [{message: missingCurlyMessage}] + }, { code: 'foo bar ', output: '{\"foo bar \"}', @@ -204,7 +251,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: '{\'foo\'}', - output: 'foo', + output: 'foo', options: ['never'], errors: [ {message: unnecessaryCurlyMessage}, {message: unnecessaryCurlyMessage} @@ -302,9 +349,21 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: [{props: 'always,original'}], errors: [{message: missingCurlyMessage}] }, + { + code: 'foo', + output: 'foo', + options: [{props: 'always,original'}], + errors: [{message: missingCurlyMessage}] + }, + { + code: 'foo', + output: 'foo', + options: [{props: 'always,original'}], + errors: [{message: missingCurlyMessage}] + }, { code: 'foo"bar"', - output: '{\"foo\\"bar\\"\"}', + output: '{\"foo\\"bar\\"\"}', options: ['always', 'single'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} @@ -312,7 +371,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo"bar"', - output: '{\"foo\\"bar\\"\"}', + output: '{\"foo\\"bar\\"\"}', options: ['always', 'double'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} @@ -347,8 +406,8 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: [{children: 'never'}] }, { - code: 'foo', - output: 'foo', + code: 'foo', + output: 'foo', errors: [{message: unnecessaryCurlyMessage}], options: [{props: 'never'}] }, From 82fe18eac9612d62fa08735ee7afc57214649eb5 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Sat, 12 Aug 2017 12:23:40 -0700 Subject: [PATCH 18/36] Update docs --- docs/rules/jsx-curly-brace-presence.md | 60 +++++++++++++++++---- lib/rules/jsx-curly-brace-presence.js | 30 +++++------ tests/lib/rules/jsx-curly-brace-presence.js | 34 ++++++++---- 3 files changed, 88 insertions(+), 36 deletions(-) diff --git a/docs/rules/jsx-curly-brace-presence.md b/docs/rules/jsx-curly-brace-presence.md index 18d0425073..405f491d71 100644 --- a/docs/rules/jsx-curly-brace-presence.md +++ b/docs/rules/jsx-curly-brace-presence.md @@ -30,9 +30,12 @@ or alternatively ### Valid options for -They are `always`, `never` and `ignore` for checking on JSX props and children. +They are `always`, `always,single`, `always,double`, `always,orignal`, `never` and `ignore` for checking on JSX props and children. -* `always`: always enforce curly braces inside JSX props or/and children +* `always`: always enforce curly braces inside JSX props or/and children and fix with double quotes inside the generated JSX expressions +* `always,single`: always enforce curly braces and fix with single quotes +* `always,double`: always enforce curly braces and fix with double quotes +* `always,original`: always enforce curly braces and fix with original quotes(default to double quotes for JSX children) * `never`: never allow unnecessary curly braces inside JSX props or/and children * `ignore`: ignore the rule for JSX props or/and children @@ -45,13 +48,15 @@ When `{ props: "always", children: "always" }` is set, the following patterns wi {'Hello world'}; ``` -The following patterns won't. +They can be fixed to: ```jsx -{'Hello world'}; -{'Hello world'}; +{"Hello world"}; +{"Hello world"}; ``` +They will fixed with single, double or original quotes based on the option you passed in. The default is double. + When `{ props: "never", children: "never" }` is set, the following patterns will be given warnings. ```jsx @@ -59,7 +64,7 @@ When `{ props: "never", children: "never" }` is set, the following patterns will ; ``` -If passed in the option to fix, they will be corrected to +They can be fixed to: ```jsx Hello world; @@ -68,9 +73,9 @@ If passed in the option to fix, they will be corrected to ### Alternative syntax -The options are also `always`, `ignore` and `ignore` for the same meanings. +The options are also `always`, `always,single`, `always,double`, `always,orignal`, `never` and `ignore` for the same meanings. -If only a string is provided, the default will be set to that option for checking on both JSX props and children. +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: @@ -81,13 +86,50 @@ When `'always'` is set, the following patterns will be given warnings. Hello world; ``` -And the following will pass. +They can be fixed to: +```jsx +{"Hello world"}; +{"Hello world"}; +``` + +If `'always,single'` is passed, they can be fixed to: ```jsx {'Hello world'}; {'Hello world'}; ``` +## 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 +Hello world; +``` + +will warned and fixed to: + +```jsx +Hello world; +``` + +* If the rule is set to enforce curly braces and the strings have quotes, it will be fixed with original quotes for JSX attributes and double for JSX children. For example: + + +```jsx +Hello 'foo' "bar" world; +``` + +will warned and fixed to: + +```jsx +{"Hello 'foo' \"bar\" world"}; +``` + +* If the rule is set to get rid of unnecessary curly braces and the strings will have escaped characters, it will not warn or fix for JSX children because JSX expressions are necessary in this case. + ## 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. diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index f260975166..fdfe91118f 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -13,8 +13,10 @@ const OPTION_NEVER = 'never'; const OPTION_IGNORE = 'ignore'; const OPTION_SINGLE_QUOTE = 'single'; const OPTION_DOUBLE_QUOTE = 'double'; +const DEAULT_QUOTE_OPTION = OPTION_DOUBLE_QUOTE; const OPTION_ORIGINAL_QUOTE = 'original'; -const optionValues = [ + +const OPTION_VALUES = [ OPTION_ALWAYS, `${OPTION_ALWAYS},${OPTION_SINGLE_QUOTE}`, `${OPTION_ALWAYS},${OPTION_DOUBLE_QUOTE}`, @@ -22,12 +24,12 @@ const optionValues = [ OPTION_NEVER, OPTION_IGNORE ]; -const quoteOptionsValues = [OPTION_SINGLE_QUOTE, OPTION_DOUBLE_QUOTE]; -const quoteOptionValueStore = { +const QUOTE_OPTON_VALUE_STORE = { [OPTION_SINGLE_QUOTE]: '\'', - [OPTION_DOUBLE_QUOTE]: '"' + [OPTION_DOUBLE_QUOTE]: '"', + [OPTION_ORIGINAL_QUOTE]: '"' // Defaults to double if no orignal quote }; -const defaultConfig = {props: OPTION_NEVER, children: OPTION_NEVER}; +const DEFAULT_CONFIG = {props: OPTION_NEVER, children: OPTION_NEVER}; module.exports = { meta: { @@ -46,8 +48,8 @@ module.exports = { { type: 'object', properties: { - props: {enum: optionValues, default: OPTION_NEVER}, - children: {enum: optionValues, default: OPTION_NEVER} + props: {enum: OPTION_VALUES, default: OPTION_NEVER}, + children: {enum: OPTION_VALUES, default: OPTION_NEVER} }, additionalProperties: false }, @@ -55,9 +57,6 @@ module.exports = { enum: optionValues } ] - }, - { - enum: quoteOptionsValues } ] }, @@ -66,7 +65,7 @@ module.exports = { const ruleOptions = context.options[0]; const userConfig = typeof ruleOptions === 'string' ? {props: ruleOptions, children: ruleOptions} : - Object.assign({}, defaultConfig, ruleOptions); + Object.assign({}, DEFAULT_CONFIG, ruleOptions); function containsBackslashForEscaping(rawStringValue) { return rawStringValue.includes('\\'); @@ -107,7 +106,6 @@ module.exports = { } function wrapLiteralNodeInJSXExpression(fixer, literalNode) { - const defaultQuoteOption = context.options[1] || OPTION_DOUBLE_QUOTE; const {parent: {type: parentType}} = literalNode; const {raw, value} = literalNode; const valueContainsQuote = containsQuote(value); @@ -122,8 +120,8 @@ module.exports = { } if ( - userSetQuoteOption === OPTION_ORIGINAL_QUOTE || - (parentType === 'JSXAttribute' && valueContainsQuote) + parentType === 'JSXAttribute' && + (userSetQuoteOption === OPTION_ORIGINAL_QUOTE || valueContainsQuote) ) { return fixer.replaceText(literalNode, `{${raw}}`); } @@ -132,8 +130,8 @@ module.exports = { return fixer.replaceText(literalNode, `{${JSON.stringify(value)}}`); } - const quoteStyle = quoteOptionValueStore[ - userSetQuoteOption || defaultQuoteOption + const quoteStyle = QUOTE_OPTON_VALUE_STORE[ + userSetQuoteOption || DEAULT_QUOTE_OPTION ]; return fixer.replaceText( literalNode, diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index cbc36eb093..704958dff9 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -268,7 +268,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo', output: '{\'foo\'}', - options: ['always', 'single'], + options: ['always,single'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] @@ -276,7 +276,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo', output: '{\"foo\"}', - options: ['always', 'double'], + options: ['always,double'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] @@ -300,7 +300,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo', output: '{\"foo\"}', - options: [{props: 'always', children: 'always,double'}, 'single'], + options: [{props: 'always,single', children: 'always,double'}], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] @@ -308,7 +308,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo', output: '{\'foo\'}', - options: [{props: 'always', children: 'always'}, 'single'], + options: [{props: 'always,single', children: 'always,single'}], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] @@ -316,7 +316,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo', output: '{\"foo\"}', - options: [{props: 'always,double', children: 'always,double'}, 'single'], + options: [{props: 'always,double', children: 'always,double'}], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] @@ -324,7 +324,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo', output: '{\"foo\"}', - options: ['always', 'double'], + options: ['always,double'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] @@ -332,7 +332,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo', output: '{\"foo\"}', - options: ['always', 'double'], + options: ['always,double'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] @@ -361,10 +361,22 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: [{props: 'always,original'}], errors: [{message: missingCurlyMessage}] }, + { + code: 'foo', + output: '{"foo"}', + options: ['always,original'], + errors: [{message: missingCurlyMessage}, {message: missingCurlyMessage}] + }, + { + code: 'foo \\n \'bar\' "foo"', + output: '{"foo \\\\n \'bar\' \\"foo\\""}', + options: ['always,original'], + errors: [{message: missingCurlyMessage}, {message: missingCurlyMessage}] + }, { code: 'foo"bar"', output: '{\"foo\\"bar\\"\"}', - options: ['always', 'single'], + options: ['always,single'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] @@ -372,7 +384,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo"bar"', output: '{\"foo\\"bar\\"\"}', - options: ['always', 'double'], + options: ['always,double'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] @@ -380,7 +392,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo\'bar\'', output: '{"foo\'bar\'"}', - options: ['always', 'single'], + options: ['always,single'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] @@ -388,7 +400,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { { code: 'foo\'bar\'', output: '{"foo\'bar\'"}', - options: ['always', 'double'], + options: ['always,double'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] From 03cf18b1ce1071f8f778d684623308e765b99308 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Sat, 12 Aug 2017 12:32:34 -0700 Subject: [PATCH 19/36] Improve docs --- docs/rules/jsx-curly-brace-presence.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/rules/jsx-curly-brace-presence.md b/docs/rules/jsx-curly-brace-presence.md index 405f491d71..9ac4dc60f2 100644 --- a/docs/rules/jsx-curly-brace-presence.md +++ b/docs/rules/jsx-curly-brace-presence.md @@ -55,7 +55,7 @@ They can be fixed to: {"Hello world"}; ``` -They will fixed with single, double or original quotes based on the option you passed in. The default is double. +They will be fixed with single, double or original quotes based on the option you passed in. The default is double. When `{ props: "never", children: "never" }` is set, the following patterns will be given warnings. @@ -99,6 +99,18 @@ If `'always,single'` is passed, they can be fixed to: {'Hello world'}; ``` +When `'never'` is set, the following pattern will be given warnings. + +```jsx +{'Hello world'}; +``` + +It can fixed to: + +```jsx +Hello world; +``` + ## Edge cases The fix also deals with template literals, strings with quotes and strings with escapes characters. @@ -109,7 +121,7 @@ The fix also deals with template literals, strings with quotes and strings with Hello world; ``` -will warned and fixed to: +will be warned and fixed to: ```jsx Hello world; @@ -128,7 +140,13 @@ will warned and fixed to: {"Hello 'foo' \"bar\" world"}; ``` -* If the rule is set to get rid of unnecessary curly braces and the strings will have escaped characters, it will not warn or fix for JSX children because JSX expressions are necessary in this case. +* 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 +{"Hello \u00b7 world"}; +``` ## When Not To Use It From dfddc4d903d1d747ba7e916973cec3b09871022b Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Sat, 12 Aug 2017 12:41:08 -0700 Subject: [PATCH 20/36] Fix a lint error --- lib/rules/jsx-curly-brace-presence.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index fdfe91118f..4bd0251151 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -54,7 +54,7 @@ module.exports = { additionalProperties: false }, { - enum: optionValues + enum: OPTION_VALUES } ] } From 50b3ae8389bb7c603b0cf9ac7d6bf822d92bae6c Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Sun, 13 Aug 2017 17:08:48 -0700 Subject: [PATCH 21/36] Add comments --- lib/rules/jsx-curly-brace-presence.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 4bd0251151..5f8131ba5b 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -5,7 +5,7 @@ 'use strict'; // ------------------------------------------------------------------------------ -// Rule Definition +// Constants // ------------------------------------------------------------------------------ const OPTION_ALWAYS = 'always'; @@ -31,6 +31,10 @@ const QUOTE_OPTON_VALUE_STORE = { }; const DEFAULT_CONFIG = {props: OPTION_NEVER, children: OPTION_NEVER}; +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + module.exports = { meta: { docs: { From fe850fbd5e578876ff1e7682753f0a0062bf3d9b Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Sun, 13 Aug 2017 18:25:38 -0700 Subject: [PATCH 22/36] Get rid of deconstruction for older node version compatibility --- lib/rules/jsx-curly-brace-presence.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 5f8131ba5b..5e4c0893ec 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -89,11 +89,9 @@ module.exports = { node: JSXExpressionNode, message: 'Curly braces are unnecessary here.', fix: function(fixer) { - const { - expression, - expression: {type: expressionType}, - parent: {type: parentType} - } = JSXExpressionNode; + const expression = JSXExpressionNode.expression; + const expressionType = expression.type; + const parentType = JSXExpressionNode.parent.type; let textToReplace; if (parentType === 'JSXAttribute') { @@ -110,8 +108,9 @@ module.exports = { } function wrapLiteralNodeInJSXExpression(fixer, literalNode) { - const {parent: {type: parentType}} = literalNode; - const {raw, value} = literalNode; + const parentType = literalNode.parent.type; + const raw = literalNode.raw; + const value = literalNode.value; const valueContainsQuote = containsQuote(value); let text = raw; let userSetQuoteOption; @@ -154,11 +153,9 @@ module.exports = { } function lintUnnecessaryCurly(JSXExpressionNode) { - const { - expression, - expression: {type: expressionType}, - parent: {type: parentType} - } = JSXExpressionNode; + const expression = JSXExpressionNode.expression; + const expressionType = expression.type; + const parentType = JSXExpressionNode.parent.type; if ( expressionType === 'Literal' && @@ -190,7 +187,7 @@ module.exports = { } function shouldCheckForUnnecessaryCurly(parent, config) { - const {type: parentType} = parent; + const parentType = parent.type; // If there are more than one JSX child, there is no need to check for // unnecessary curly braces. @@ -211,7 +208,7 @@ module.exports = { return { JSXExpressionContainer: node => { - const {parent} = node; + const parent = node.parent; if (shouldCheckForUnnecessaryCurly(parent, userConfig)) { lintUnnecessaryCurly(node); @@ -219,7 +216,7 @@ module.exports = { }, Literal: node => { - const {parent: {type: parentType}} = node; + const parentType = node.parent.type; if (shouldCheckForMissingCurly(parentType, userConfig)) { reportMissingCurly(node); From 35cb9613000ad4a61e090da4655a7dcc28c2d2b2 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Sun, 13 Aug 2017 19:37:53 -0700 Subject: [PATCH 23/36] Add a comment --- lib/rules/jsx-curly-brace-presence.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 5e4c0893ec..b5dd251e2a 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -130,6 +130,10 @@ module.exports = { } if (parentType === 'JSXElement' && valueContainsQuote) { + // JSON.stringify is used because the value could contain both single + // and double quoted text and the method will account for both + // situations by wrapping the whole text in double qutoes and escaping + // any double quote. return fixer.replaceText(literalNode, `{${JSON.stringify(value)}}`); } From c299551b1236387bab318d24c68f30f3f782784c Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Tue, 15 Aug 2017 23:12:11 -0700 Subject: [PATCH 24/36] Add two more test cases --- tests/lib/rules/jsx-curly-brace-presence.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 704958dff9..e0af242af9 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -54,6 +54,10 @@ ruleTester.run('jsx-curly-brace-presence', rule, { code: 'foo', options: [{props: 'never'}] }, + { + code: '', + options: [{props: 'never'}] + }, { code: '{}' }, @@ -149,6 +153,12 @@ ruleTester.run('jsx-curly-brace-presence', rule, { ], invalid: [ + { + code: '', + output: '', + options: [{props: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, { code: 'foo', output: 'foo', From b1809192ba9eddb5a774fe416dff8c622fc56949 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Wed, 16 Aug 2017 00:32:16 -0700 Subject: [PATCH 25/36] Remove ability to handle quotes --- lib/rules/jsx-curly-brace-presence.js | 33 ++------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index b5dd251e2a..a81be4f05e 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -11,24 +11,12 @@ const OPTION_ALWAYS = 'always'; const OPTION_NEVER = 'never'; const OPTION_IGNORE = 'ignore'; -const OPTION_SINGLE_QUOTE = 'single'; -const OPTION_DOUBLE_QUOTE = 'double'; -const DEAULT_QUOTE_OPTION = OPTION_DOUBLE_QUOTE; -const OPTION_ORIGINAL_QUOTE = 'original'; const OPTION_VALUES = [ OPTION_ALWAYS, - `${OPTION_ALWAYS},${OPTION_SINGLE_QUOTE}`, - `${OPTION_ALWAYS},${OPTION_DOUBLE_QUOTE}`, - `${OPTION_ALWAYS},${OPTION_ORIGINAL_QUOTE}`, OPTION_NEVER, OPTION_IGNORE ]; -const QUOTE_OPTON_VALUE_STORE = { - [OPTION_SINGLE_QUOTE]: '\'', - [OPTION_DOUBLE_QUOTE]: '"', - [OPTION_ORIGINAL_QUOTE]: '"' // Defaults to double if no orignal quote -}; const DEFAULT_CONFIG = {props: OPTION_NEVER, children: OPTION_NEVER}; // ------------------------------------------------------------------------------ @@ -113,19 +101,8 @@ module.exports = { const value = literalNode.value; const valueContainsQuote = containsQuote(value); let text = raw; - let userSetQuoteOption; - - if (parentType === 'JSXAttribute') { - userSetQuoteOption = userConfig.props.split(',')[1]; - text = raw.substring(1, raw.length - 1); - } else if (parentType === 'JSXElement') { - userSetQuoteOption = userConfig.children.split(',')[1]; - } - if ( - parentType === 'JSXAttribute' && - (userSetQuoteOption === OPTION_ORIGINAL_QUOTE || valueContainsQuote) - ) { + if (parentType === 'JSXAttribute' && valueContainsQuote) { return fixer.replaceText(literalNode, `{${raw}}`); } @@ -137,13 +114,7 @@ module.exports = { return fixer.replaceText(literalNode, `{${JSON.stringify(value)}}`); } - const quoteStyle = QUOTE_OPTON_VALUE_STORE[ - userSetQuoteOption || DEAULT_QUOTE_OPTION - ]; - return fixer.replaceText( - literalNode, - `{${quoteStyle}${text}${quoteStyle}}` - ); + return fixer.replaceText(literalNode, `{"${text}"}`); } function reportMissingCurly(literalNode) { From c66def5455dae1ac97d394baa3cbf3243060a5d8 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Wed, 16 Aug 2017 10:36:03 -0700 Subject: [PATCH 26/36] Change tests --- lib/rules/jsx-curly-brace-presence.js | 35 +---- tests/lib/rules/jsx-curly-brace-presence.js | 166 ++------------------ 2 files changed, 17 insertions(+), 184 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index a81be4f05e..b6e3298751 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -63,10 +63,6 @@ module.exports = { return rawStringValue.includes('\\'); } - function containsQuote(string) { - return string.match(/'|"/g); - } - /** * Report and fix an unnecessary curly brace violation on a node * @param {ASTNode} node - The AST node with an unnecessary JSX expression @@ -95,34 +91,15 @@ module.exports = { }); } - function wrapLiteralNodeInJSXExpression(fixer, literalNode) { - const parentType = literalNode.parent.type; - const raw = literalNode.raw; - const value = literalNode.value; - const valueContainsQuote = containsQuote(value); - let text = raw; - - if (parentType === 'JSXAttribute' && valueContainsQuote) { - return fixer.replaceText(literalNode, `{${raw}}`); - } - - if (parentType === 'JSXElement' && valueContainsQuote) { - // JSON.stringify is used because the value could contain both single - // and double quoted text and the method will account for both - // situations by wrapping the whole text in double qutoes and escaping - // any double quote. - return fixer.replaceText(literalNode, `{${JSON.stringify(value)}}`); - } - - return fixer.replaceText(literalNode, `{"${text}"}`); - } - function reportMissingCurly(literalNode) { context.report({ node: literalNode, message: 'Need to wrap this literal in a JSX expression.', fix: function(fixer) { - return wrapLiteralNodeInJSXExpression(fixer, literalNode); + const expression = literalNode.parent.type === 'JSXAttribute' ? + `{${literalNode.raw}}` : `{${JSON.stringify(literalNode.value)}}`; + + return fixer.replaceText(literalNode, expression); } }); } @@ -153,11 +130,11 @@ module.exports = { return ( parentType === 'JSXAttribute' && typeof config.props === 'string' && - config.props.split(',')[0] === ruleCondition + config.props === ruleCondition ) || ( parentType === 'JSXElement' && typeof config.children === 'string' && - config.children.split(',')[0] === ruleCondition + config.children === ruleCondition ); } diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index e0af242af9..1fb5e7ee6b 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -106,6 +106,14 @@ ruleTester.run('jsx-curly-brace-presence', rule, { code: '{\'foo\'}', options: [{children: 'always'}] }, + { + code: 'foo', + options: [{props: 'always'}] + }, + { + code: '{\"foo\"}', + options: [{children: 'always'}] + }, { code: '{\'foo\'}', options: [{children: 'ignore'}] @@ -225,7 +233,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', - output: 'foo', + output: 'foo', options: [{props: 'always'}], errors: [{message: missingCurlyMessage}] }, @@ -235,18 +243,6 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: [{children: 'always'}], errors: [{message: missingCurlyMessage}] }, - { - code: 'foo', - output: 'foo', - options: [{props: 'always,single'}], - errors: [{message: missingCurlyMessage}] - }, - { - code: 'foo bar ', - output: '{\'foo bar \'}', - options: [{children: 'always,single'}], - errors: [{message: missingCurlyMessage}] - }, { code: 'foo bar ', output: '{\"foo bar \"}', @@ -255,7 +251,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo\\nbar', - output: '{\"foo\\nbar\"}', + output: '{\"foo\\\\nbar\"}', options: [{children: 'always'}], errors: [{message: missingCurlyMessage}] }, @@ -269,148 +265,8 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', - output: '{\"foo\"}', - options: ['always'], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo', - output: '{\'foo\'}', - options: ['always,single'], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo', - output: '{\"foo\"}', - options: ['always,double'], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo', - output: '{\'foo\'}', - options: [{props: 'always,double', children: 'always,single'}], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo', output: '{\"foo\"}', - options: [{props: 'always,single', children: 'always,double'}], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo', - output: '{\"foo\"}', - options: [{props: 'always,single', children: 'always,double'}], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo', - output: '{\'foo\'}', - options: [{props: 'always,single', children: 'always,single'}], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo', - output: '{\"foo\"}', - options: [{props: 'always,double', children: 'always,double'}], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo', - output: '{\"foo\"}', - options: ['always,double'], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo', - output: '{\"foo\"}', - options: ['always,double'], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo', - output: 'foo', - options: [{props: 'always,original'}], - errors: [{message: missingCurlyMessage}] - }, - { - code: 'foo', - output: 'foo', - options: [{props: 'always,original'}], - errors: [{message: missingCurlyMessage}] - }, - { - code: 'foo', - output: 'foo', - options: [{props: 'always,original'}], - errors: [{message: missingCurlyMessage}] - }, - { - code: 'foo', - output: 'foo', - options: [{props: 'always,original'}], - errors: [{message: missingCurlyMessage}] - }, - { - code: 'foo', - output: '{"foo"}', - options: ['always,original'], - errors: [{message: missingCurlyMessage}, {message: missingCurlyMessage}] - }, - { - code: 'foo \\n \'bar\' "foo"', - output: '{"foo \\\\n \'bar\' \\"foo\\""}', - options: ['always,original'], - errors: [{message: missingCurlyMessage}, {message: missingCurlyMessage}] - }, - { - code: 'foo"bar"', - output: '{\"foo\\"bar\\"\"}', - options: ['always,single'], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo"bar"', - output: '{\"foo\\"bar\\"\"}', - options: ['always,double'], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo\'bar\'', - output: '{"foo\'bar\'"}', - options: ['always,single'], - errors: [ - {message: missingCurlyMessage}, {message: missingCurlyMessage} - ] - }, - { - code: 'foo\'bar\'', - output: '{"foo\'bar\'"}', - options: ['always,double'], + options: ['always'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ] From 0554b49eb7e3503eabfaa00bdd235bb93d81415e Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Wed, 16 Aug 2017 10:41:22 -0700 Subject: [PATCH 27/36] Fix docs --- docs/rules/jsx-curly-brace-presence.md | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/docs/rules/jsx-curly-brace-presence.md b/docs/rules/jsx-curly-brace-presence.md index 9ac4dc60f2..aecce18c11 100644 --- a/docs/rules/jsx-curly-brace-presence.md +++ b/docs/rules/jsx-curly-brace-presence.md @@ -30,12 +30,9 @@ or alternatively ### Valid options for -They are `always`, `always,single`, `always,double`, `always,orignal`, `never` and `ignore` for checking on JSX props and children. +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 inside the generated JSX expressions -* `always,single`: always enforce curly braces and fix with single quotes -* `always,double`: always enforce curly braces and fix with double quotes -* `always,original`: always enforce curly braces and fix with original quotes(default to double quotes for JSX 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 @@ -55,8 +52,6 @@ They can be fixed to: {"Hello world"}; ``` -They will be fixed with single, double or original quotes based on the option you passed in. The default is double. - When `{ props: "never", children: "never" }` is set, the following patterns will be given warnings. ```jsx @@ -73,7 +68,7 @@ They can be fixed to: ### Alternative syntax -The options are also `always`, `always,single`, `always,double`, `always,orignal`, `never` and `ignore` for the same meanings. +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. @@ -92,13 +87,6 @@ They can be fixed to: {"Hello world"}; ``` -If `'always,single'` is passed, they can be fixed to: - -```jsx -{'Hello world'}; -{'Hello world'}; -``` - When `'never'` is set, the following pattern will be given warnings. ```jsx @@ -118,7 +106,7 @@ The fix also deals with template literals, strings with quotes and strings with * 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 -Hello world; +{`Hello world`}; ``` will be warned and fixed to: @@ -127,7 +115,9 @@ will be warned and fixed to: Hello world; ``` -* If the rule is set to enforce curly braces and the strings have quotes, it will be fixed with original quotes for JSX attributes and double for JSX children. For example: +* 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 From cb93f6b1b655c80a1bb0dd936830b79aceecced1 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Wed, 16 Aug 2017 10:45:23 -0700 Subject: [PATCH 28/36] Fix an error in the doc --- docs/rules/jsx-curly-brace-presence.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/jsx-curly-brace-presence.md b/docs/rules/jsx-curly-brace-presence.md index aecce18c11..0a98748cc6 100644 --- a/docs/rules/jsx-curly-brace-presence.md +++ b/docs/rules/jsx-curly-brace-presence.md @@ -49,7 +49,7 @@ They can be fixed to: ```jsx {"Hello world"}; -{"Hello world"}; +{'Hello world'}; ``` When `{ props: "never", children: "never" }` is set, the following patterns will be given warnings. From 8adfb8c4a8d6e1d59b8f72dfc3a0c49124593d99 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Wed, 16 Aug 2017 15:20:43 -0700 Subject: [PATCH 29/36] Improve jsx-curly-brace-presence docs --- docs/rules/jsx-curly-brace-presence.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/rules/jsx-curly-brace-presence.md b/docs/rules/jsx-curly-brace-presence.md index 0a98748cc6..4e95e04620 100644 --- a/docs/rules/jsx-curly-brace-presence.md +++ b/docs/rules/jsx-curly-brace-presence.md @@ -16,7 +16,7 @@ You can pass in options to enforce the presence of curly braces on JSX props or ```js ... -"react/forbid-elements": [, { "props": , "children": }] +"react/jsx-curly-brace-presence": [, { "props": , "children": }] ... ``` @@ -24,7 +24,7 @@ or alternatively ```js ... -"react/forbid-elements": [, ] +"react/jsx-curly-brace-presence": [, ] ... ``` @@ -49,21 +49,21 @@ They can be fixed to: ```jsx {"Hello world"}; -{'Hello world'}; +{'Hello world'}; ``` When `{ props: "never", children: "never" }` is set, the following patterns will be given warnings. ```jsx {'Hello world'}; -; +; ``` They can be fixed to: ```jsx Hello world; -; +; ``` ### Alternative syntax @@ -78,25 +78,25 @@ When `'always'` is set, the following patterns will be given warnings. ```jsx Hello world; -Hello world; +Hello world; ``` They can be fixed to: ```jsx {"Hello world"}; -{"Hello world"}; +{"Hello world"}; ``` When `'never'` is set, the following pattern will be given warnings. ```jsx -{'Hello world'}; +{'Hello world'}; ``` It can fixed to: ```jsx -Hello world; +Hello world; ``` ## Edge cases From 0203b708c0a5d7a2846bcc83ab698e97ff33843d Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Wed, 16 Aug 2017 16:43:22 -0700 Subject: [PATCH 30/36] Add more tests for more than one prop --- tests/lib/rules/jsx-curly-brace-presence.js | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 1fb5e7ee6b..31bea75e96 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -150,6 +150,14 @@ ruleTester.run('jsx-curly-brace-presence', rule, { code: '{\"foo\"}', options: ['always'] }, + { + code: '', + options: ['always'] + }, + { + code: '', + options: ['never'] + }, { code: 'foo', options: ['never'] @@ -294,6 +302,34 @@ ruleTester.run('jsx-curly-brace-presence', rule, { output: 'foo', errors: [{message: unnecessaryCurlyMessage}], options: [{props: 'never'}] + }, + { + code: '', + output: '', + errors: [ + {message: unnecessaryCurlyMessage}, {message: unnecessaryCurlyMessage} + ], + options: [{props: 'never'}] + }, + { + code: '', + output: '', + errors: [ + {message: missingCurlyMessage}, {message: missingCurlyMessage} + ], + options: [{props: 'always'}] + }, + { + code: '', + output: '', + errors: [{message: missingCurlyMessage}], + options: [{props: 'always'}] + }, + { + code: '', + output: '', + errors: [{message: missingCurlyMessage}], + options: [{props: 'always'}] } ] }); From d2b2aac7e5b60acef950b8b107ea268ef7302d74 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Thu, 17 Aug 2017 00:27:49 -0700 Subject: [PATCH 31/36] Further improve docs about jsx-curly-brace-presence rule fixing --- docs/rules/jsx-curly-brace-presence.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/rules/jsx-curly-brace-presence.md b/docs/rules/jsx-curly-brace-presence.md index 4e95e04620..d97eb71257 100644 --- a/docs/rules/jsx-curly-brace-presence.md +++ b/docs/rules/jsx-curly-brace-presence.md @@ -32,10 +32,15 @@ or alternatively 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 +* `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 + For examples: When `{ props: "always", children: "always" }` is set, the following patterns will be given warnings. From 401e341f407260ec7db793189ab3c8dcb9152a9e Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 21 Aug 2017 17:22:59 -0700 Subject: [PATCH 32/36] Account for feedback and add more tests --- docs/rules/jsx-curly-brace-presence.md | 12 +++--- lib/rules/jsx-curly-brace-presence.js | 14 ++++++- tests/lib/rules/jsx-curly-brace-presence.js | 46 ++++++++++++++++----- 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/docs/rules/jsx-curly-brace-presence.md b/docs/rules/jsx-curly-brace-presence.md index d97eb71257..7012118228 100644 --- a/docs/rules/jsx-curly-brace-presence.md +++ b/docs/rules/jsx-curly-brace-presence.md @@ -41,6 +41,8 @@ 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. @@ -54,7 +56,7 @@ They can be fixed to: ```jsx {"Hello world"}; -{'Hello world'}; +{'Hello world'}; ``` When `{ props: "never", children: "never" }` is set, the following patterns will be given warnings. @@ -68,7 +70,7 @@ They can be fixed to: ```jsx Hello world; -; +; ``` ### Alternative syntax @@ -89,7 +91,7 @@ When `'always'` is set, the following patterns will be given warnings. They can be fixed to: ```jsx {"Hello world"}; -{"Hello world"}; +{"Hello world"}; ``` When `'never'` is set, the following pattern will be given warnings. @@ -101,7 +103,7 @@ When `'never'` is set, the following pattern will be given warnings. It can fixed to: ```jsx -Hello world; +Hello world; ``` ## Edge cases @@ -132,7 +134,7 @@ For example: will warned and fixed to: ```jsx -{"Hello 'foo' \"bar\" world"}; +{"Hello 'foo' \"bar\" world"}; ``` * 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: diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index b6e3298751..73103e349b 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -63,6 +63,10 @@ module.exports = { return rawStringValue.includes('\\'); } + function escapeDoubleQuoteInString(value) { + return value.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 @@ -80,7 +84,10 @@ module.exports = { let textToReplace; if (parentType === 'JSXAttribute') { textToReplace = expressionType === 'TemplateLiteral' ? - `"${expression.quasis[0].value.raw}"` : expression.raw; + `"${expression.quasis[0].value.raw}"` : + `"${escapeDoubleQuoteInString( + expression.raw.substring(1, expression.raw.length - 1) + )}"`; } else { textToReplace = expressionType === 'TemplateLiteral' ? expression.quasis[0].value.cooked : expression.value; @@ -97,7 +104,10 @@ module.exports = { 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)}}`; + `{"${escapeDoubleQuoteInString( + literalNode.raw.substring(1, literalNode.raw.length - 1) + )}"}` : + `{${JSON.stringify(literalNode.value)}}`; return fixer.replaceText(literalNode, expression); } diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 31bea75e96..07d04960f7 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -177,7 +177,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', - output: 'foo', + output: 'foo', options: [{props: 'never'}], errors: [{message: unnecessaryCurlyMessage}] }, @@ -224,7 +224,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', - output: 'foo', + output: 'foo', errors: [{message: unnecessaryCurlyMessage}] }, { @@ -235,13 +235,25 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', - output: 'foo', + output: 'foo', + options: [{props: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, + { + code: 'foo', + output: 'foo', + options: [{props: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, + { + code: 'foo', + output: 'foo', options: [{props: 'never'}], errors: [{message: unnecessaryCurlyMessage}] }, { code: 'foo', - output: 'foo', + output: 'foo', options: [{props: 'always'}], errors: [{message: missingCurlyMessage}] }, @@ -251,6 +263,18 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: [{children: 'always'}], errors: [{message: missingCurlyMessage}] }, + { + code: 'foo bar \'foo\'', + output: '{\"foo bar \'foo\'\"}', + options: [{children: 'always'}], + errors: [{message: missingCurlyMessage}] + }, + { + code: 'foo bar \"foo\"', + output: '{\"foo bar \\\"foo\\\"\"}', + options: [{children: 'always'}], + errors: [{message: missingCurlyMessage}] + }, { code: 'foo bar ', output: '{\"foo bar \"}', @@ -265,7 +289,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: '{\'foo\'}', - output: 'foo', + output: 'foo', options: ['never'], errors: [ {message: unnecessaryCurlyMessage}, {message: unnecessaryCurlyMessage} @@ -273,7 +297,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', - output: '{\"foo\"}', + output: '{\"foo\"}', options: ['always'], errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} @@ -299,13 +323,13 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: 'foo', - output: 'foo', + output: 'foo', errors: [{message: unnecessaryCurlyMessage}], options: [{props: 'never'}] }, { code: '', - output: '', + output: '', errors: [ {message: unnecessaryCurlyMessage}, {message: unnecessaryCurlyMessage} ], @@ -313,7 +337,7 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: '', - output: '', + output: '', errors: [ {message: missingCurlyMessage}, {message: missingCurlyMessage} ], @@ -321,13 +345,13 @@ ruleTester.run('jsx-curly-brace-presence', rule, { }, { code: '', - output: '', + output: '', errors: [{message: missingCurlyMessage}], options: [{props: 'always'}] }, { code: '', - output: '', + output: '', errors: [{message: missingCurlyMessage}], options: [{props: 'always'}] } From f2a4d031ece7e43dbe2168fee3b0c781fc4a5106 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 21 Aug 2017 17:25:14 -0700 Subject: [PATCH 33/36] Improve docs --- docs/rules/jsx-curly-brace-presence.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/jsx-curly-brace-presence.md b/docs/rules/jsx-curly-brace-presence.md index 7012118228..b4e27a2b78 100644 --- a/docs/rules/jsx-curly-brace-presence.md +++ b/docs/rules/jsx-curly-brace-presence.md @@ -108,7 +108,7 @@ It can fixed to: ## Edge cases -The fix also deals with template literals, strings with quotes and strings with escapes characters. +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: From 2439068ec2d88234a533636f98e0efd903e18223 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 21 Aug 2017 17:43:47 -0700 Subject: [PATCH 34/36] Change a variable name --- lib/rules/jsx-curly-brace-presence.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 73103e349b..11418fe6e8 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -63,8 +63,8 @@ module.exports = { return rawStringValue.includes('\\'); } - function escapeDoubleQuoteInString(value) { - return value.replace(/\\"/g, '"').replace(/"/g, '\\"'); + function escapeDoubleQuoteInString(rawStringValue) { + return rawStringValue.replace(/\\"/g, '"').replace(/"/g, '\\"'); } /** From d3df38b69d7364d270bfaacf184699d8ca74bd9c Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Mon, 21 Aug 2017 17:48:44 -0700 Subject: [PATCH 35/36] Change a function name --- lib/rules/jsx-curly-brace-presence.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index 11418fe6e8..ddd1cf2765 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -63,7 +63,7 @@ module.exports = { return rawStringValue.includes('\\'); } - function escapeDoubleQuoteInString(rawStringValue) { + function escapeDoubleQuotes(rawStringValue) { return rawStringValue.replace(/\\"/g, '"').replace(/"/g, '\\"'); } @@ -85,7 +85,7 @@ module.exports = { if (parentType === 'JSXAttribute') { textToReplace = expressionType === 'TemplateLiteral' ? `"${expression.quasis[0].value.raw}"` : - `"${escapeDoubleQuoteInString( + `"${escapeDoubleQuotes( expression.raw.substring(1, expression.raw.length - 1) )}"`; } else { @@ -104,7 +104,7 @@ module.exports = { message: 'Need to wrap this literal in a JSX expression.', fix: function(fixer) { const expression = literalNode.parent.type === 'JSXAttribute' ? - `{"${escapeDoubleQuoteInString( + `{"${escapeDoubleQuotes( literalNode.raw.substring(1, literalNode.raw.length - 1) )}"}` : `{${JSON.stringify(literalNode.value)}}`; From 990dacad282d28c149206eb1ab0bab5e29139e76 Mon Sep 17 00:00:00 2001 From: jackyho112 Date: Tue, 29 Aug 2017 13:48:43 -0700 Subject: [PATCH 36/36] Fix an edge case --- lib/rules/jsx-curly-brace-presence.js | 16 +++++----- tests/lib/rules/jsx-curly-brace-presence.js | 34 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/lib/rules/jsx-curly-brace-presence.js b/lib/rules/jsx-curly-brace-presence.js index ddd1cf2765..51ef422b76 100644 --- a/lib/rules/jsx-curly-brace-presence.js +++ b/lib/rules/jsx-curly-brace-presence.js @@ -83,11 +83,11 @@ module.exports = { let textToReplace; if (parentType === 'JSXAttribute') { - textToReplace = expressionType === 'TemplateLiteral' ? - `"${expression.quasis[0].value.raw}"` : - `"${escapeDoubleQuotes( + 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; @@ -122,15 +122,15 @@ module.exports = { if ( expressionType === 'Literal' && typeof expression.value === 'string' && ( - !containsBackslashForEscaping(expression.raw) || - parentType === 'JSXAttribute') + parentType === 'JSXAttribute' || + !containsBackslashForEscaping(expression.raw)) ) { reportUnnecessaryCurly(JSXExpressionNode); } else if ( expressionType === 'TemplateLiteral' && expression.expressions.length === 0 && ( - !containsBackslashForEscaping(expression.quasis[0].value.raw) || - parentType === 'JSXAttribute') + parentType === 'JSXAttribute' || + !containsBackslashForEscaping(expression.quasis[0].value.raw)) ) { reportUnnecessaryCurly(JSXExpressionNode); } diff --git a/tests/lib/rules/jsx-curly-brace-presence.js b/tests/lib/rules/jsx-curly-brace-presence.js index 07d04960f7..1ae02da4c7 100644 --- a/tests/lib/rules/jsx-curly-brace-presence.js +++ b/tests/lib/rules/jsx-curly-brace-presence.js @@ -98,6 +98,10 @@ ruleTester.run('jsx-curly-brace-presence', rule, { code: '{}{"123"}', options: [{children: 'never'}] }, + { + code: '{\"foo \'bar\' \\\"foo\\\" bar\"}', + options: [{children: 'never'}] + }, { code: 'foo', options: [{props: 'always'}] @@ -205,6 +209,12 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: [{props: 'never'}], errors: [{message: unnecessaryCurlyMessage}] }, + { + code: 'foo', + output: 'foo', + options: [{props: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, { code: 'foo', output: 'foo', @@ -217,6 +227,12 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: [{children: 'never'}], errors: [{message: unnecessaryCurlyMessage}] }, + { + code: '{`foo "foo" bar`}', + output: 'foo "foo" bar', + options: [{children: 'never'}], + errors: [{message: unnecessaryCurlyMessage}] + }, { code: '{\'foo\'}', output: 'foo', @@ -257,6 +273,24 @@ ruleTester.run('jsx-curly-brace-presence', rule, { options: [{props: 'always'}], errors: [{message: missingCurlyMessage}] }, + { + code: 'foo', + output: 'foo', + options: [{props: 'always'}], + errors: [{message: missingCurlyMessage}] + }, + { + code: 'foo', + output: 'foo', + options: [{props: 'always'}], + errors: [{message: missingCurlyMessage}] + }, + { + code: 'foo', + output: 'foo', + options: [{props: 'always'}], + errors: [{message: missingCurlyMessage}] + }, { code: 'foo bar ', output: '{\"foo bar \"}',