diff --git a/README.md b/README.md index 46a9d595fb..3c7e742ec3 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](# # List of supported rules +* [react/dom-elements-style-is-object](docs/rules/dom-elements-style-is-object.md): Enforce style prop value being an object * [react/display-name](docs/rules/display-name.md): Prevent missing `displayName` in a React component definition * [react/forbid-component-props](docs/rules/forbid-component-props.md): Forbid certain props on Components * [react/forbid-prop-types](docs/rules/forbid-prop-types.md): Forbid certain propTypes diff --git a/docs/rules/dom-elements-style-is-object.md b/docs/rules/dom-elements-style-is-object.md new file mode 100644 index 0000000000..54261a1304 --- /dev/null +++ b/docs/rules/dom-elements-style-is-object.md @@ -0,0 +1,51 @@ +# Enforce style prop value being an object (dom-elements-style-is-object) + +Require that the value of the prop `style` be an object or a variable that is +an object. + +## Rule Details + +The following patterns are considered warnings: + +```jsx +
+ +
+ + + +const styles = true; +
+``` + +```js +React.createElement("div", { style: "color: 'red'" }); + +React.createElement("div", { style: true }); + +React.createElement("Hello", { style: true }); + +const styles = true; +React.createElement("div", { style: styles }); +``` + + +The following patterns are not considered warnings: + +```jsx +
+ + + +const styles = { color: "red" }; +
+``` + +```js +React.createElement("div", { style: { color: 'red' }}); + +React.createElement("Hello", { style: { color: 'red' }}); + +const styles = { height: '100px' }; +React.createElement("div", { style: styles }); +``` diff --git a/index.js b/index.js index 0a25847325..a58e21d1e3 100644 --- a/index.js +++ b/index.js @@ -53,7 +53,8 @@ var rules = { 'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'), 'jsx-filename-extension': require('./lib/rules/jsx-filename-extension'), 'require-optimization': require('./lib/rules/require-optimization'), - 'no-find-dom-node': require('./lib/rules/no-find-dom-node') + 'no-find-dom-node': require('./lib/rules/no-find-dom-node'), + 'dom-elements-style-is-object': require('./lib/rules/dom-elements-style-is-object') }; var ruleNames = Object.keys(rules); diff --git a/lib/rules/dom-elements-style-is-object.js b/lib/rules/dom-elements-style-is-object.js new file mode 100644 index 0000000000..11c9fb1e15 --- /dev/null +++ b/lib/rules/dom-elements-style-is-object.js @@ -0,0 +1,80 @@ +/** + * @fileoverview Enforce style prop value is an object + * @author David Petersen + */ +'use strict'; + +var variableUtil = require('../util/variable'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'Enforce style prop value is an object', + category: '', + recommended: false + }, + schema: [] + }, + + create: function(context) { + /** + * @param {object} node A Identifier node + */ + function checkIdentifiers(node) { + var variable = variableUtil.variablesInScope(context).find(function (item) { + return item.name === node.name; + }); + + if (!variable || !variable.defs[0].node.init) { + return; + } + + if (variable.defs[0].node.init.type === 'Literal') { + context.report(node, 'Style prop value must be an object'); + } + } + + return { + CallExpression: function(node) { + if ( + node.callee + && node.callee.type === 'MemberExpression' + && node.callee.property.name === 'createElement' + && node.arguments.length > 1 + ) { + if (node.arguments[1].type === 'ObjectExpression') { + var style = node.arguments[1].properties.find(function(property) { + return property.key.name === 'style'; + }); + if (style) { + if (style.value.type === 'Identifier') { + checkIdentifiers(style.value); + } else if (style.value.type === 'Literal') { + context.report(style.value, 'Style prop value must be an object'); + } + } + } + } + }, + + JSXAttribute: function(node) { + if (node.name.name !== 'style') { + return; + } + + if ( + node.value.type !== 'JSXExpressionContainer' + || node.value.expression.type === 'Literal' + ) { + context.report(node, 'Style prop value must be an object'); + } else if (node.value.expression.type === 'Identifier') { + checkIdentifiers(node.value.expression); + } + } + }; + } +}; diff --git a/tests/lib/rules/dom-elements-style-is-object.js b/tests/lib/rules/dom-elements-style-is-object.js new file mode 100644 index 0000000000..872dfda7eb --- /dev/null +++ b/tests/lib/rules/dom-elements-style-is-object.js @@ -0,0 +1,175 @@ +/** + * @fileoverview Enforce style prop value is an object + * @author David Petersen + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../../../lib/rules/dom-elements-style-is-object'); +var RuleTester = require('eslint').RuleTester; + +var parserOptions = { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true + } +}; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester(); +ruleTester.run('dom-elements-style-is-object', rule, { + valid: [ + { + code: '
', + parserOptions: parserOptions + }, + { + code: '', + parserOptions: parserOptions + }, + { + code: [ + 'function redDiv() {', + ' const styles = { color: "red" };', + ' return
;', + '}' + ].join('\n'), + parserOptions: parserOptions + }, + { + code: [ + 'function redDiv() {', + ' const styles = { color: "red" };', + ' return ;', + '}' + ].join('\n'), + parserOptions: parserOptions + }, + { + code: [ + 'const styles = { color: "red" };', + 'function redDiv() {', + ' return
;', + '}' + ].join('\n'), + parserOptions: parserOptions + }, + { + code: [ + 'function redDiv(props) {', + ' return
;', + '}' + ].join('\n'), + parserOptions: parserOptions + }, + { + code: [ + 'import styles from \'./styles\';', + 'function redDiv() {', + ' return
;', + '}' + ].join('\n'), + parserOptions: Object.assign({sourceType: 'module'}, parserOptions) + }, + { + code: [ + 'import mystyles from \'./styles\';', + 'const styles = Object.assign({ color: \'red\' }, mystyles);', + 'function redDiv() {', + ' return
;', + '}' + ].join('\n'), + parserOptions: Object.assign({sourceType: 'module'}, parserOptions) + }, + { + code: [ + 'const styles = Object.assign({ color: \'red\' }, mystyles);', + 'React.createElement("div", { style: styles });' + ].join('\n'), + parserOptions: Object.assign({sourceType: 'module'}, parserOptions) + } + ], + invalid: [ + { + code: '
', + errors: [{ + message: 'Style prop value must be an object', + line: 1, + column: 6, + type: 'JSXAttribute' + }], + parserOptions: parserOptions + }, + { + code: '', + errors: [{ + message: 'Style prop value must be an object', + line: 1, + column: 8, + type: 'JSXAttribute' + }], + parserOptions: parserOptions + }, + { + code: '
', + errors: [{ + message: 'Style prop value must be an object', + line: 1, + column: 6, + type: 'JSXAttribute' + }], + parserOptions: parserOptions + }, + { + code: [ + 'const styles = \'color: "red"\';', + 'function redDiv2() {', + ' return
;', + '}' + ].join('\n'), + errors: [{ + message: 'Style prop value must be an object', + line: 3, + column: 22, + type: 'Identifier' + }], + parserOptions: parserOptions + }, + { + code: [ + 'const styles = \'color: "red"\';', + 'function redDiv2() {', + ' return ;', + '}' + ].join('\n'), + errors: [{ + message: 'Style prop value must be an object', + line: 3, + column: 24, + type: 'Identifier' + }], + parserOptions: parserOptions + }, + { + code: [ + 'const styles = true;', + 'function redDiv() {', + ' return
;', + '}' + ].join('\n'), + errors: [{ + message: 'Style prop value must be an object', + line: 3, + column: 22, + type: 'Identifier' + }], + parserOptions: parserOptions + } + ] +});