Skip to content

Commit

Permalink
Add style-prop-object rule (fixes #715)
Browse files Browse the repository at this point in the history
  • Loading branch information
petersendidit authored and yannickcr committed Aug 22, 2016
1 parent 8ea2d0e commit 7ed3b29
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
* [react/self-closing-comp](docs/rules/self-closing-comp.md): Prevent extra closing tags for components without children (fixable)
* [react/sort-comp](docs/rules/sort-comp.md): Enforce component methods order
* [react/sort-prop-types](docs/rules/sort-prop-types.md): Enforce propTypes declarations alphabetical sorting
* [react/style-prop-object](docs/rules/style-prop-object.md): Enforce style prop value being an object

## JSX-specific rules

Expand Down
51 changes: 51 additions & 0 deletions docs/rules/style-prop-object.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Enforce style prop value being an object (style-prop-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
<div style="color: 'red'" />

<div style={true} />

<Hello style={true} />

const styles = true;
<div style={styles} />
```

```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
<div style={{ color: "red" }} />

<Hello style={{ color: "red" }} />

const styles = { color: "red" };
<div style={styles} />
```

```js
React.createElement("div", { style: { color: 'red' }});

React.createElement("Hello", { style: { color: 'red' }});

const styles = { height: '100px' };
React.createElement("div", { style: styles });
```
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ var rules = {
'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-danger-with-children': require('./lib/rules/no-danger-with-children')
'no-danger-with-children': require('./lib/rules/no-danger-with-children'),
'style-prop-object': require('./lib/rules/style-prop-object')
};

var ruleNames = Object.keys(rules);
Expand Down
80 changes: 80 additions & 0 deletions lib/rules/style-prop-object.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
};
}
};
184 changes: 184 additions & 0 deletions tests/lib/rules/style-prop-object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/**
* @fileoverview Enforce style prop value is an object
* @author David Petersen
*/
'use strict';

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

var rule = require('../../../lib/rules/style-prop-object');
var RuleTester = require('eslint').RuleTester;

var parserOptions = {
ecmaVersion: 6,
ecmaFeatures: {
experimentalObjectRestSpread: true,
jsx: true
}
};

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

var ruleTester = new RuleTester();
ruleTester.run('style-prop-object', rule, {
valid: [
{
code: '<div style={{ color: "red" }} />',
parserOptions: parserOptions
},
{
code: '<Hello style={{ color: "red" }} />',
parserOptions: parserOptions
},
{
code: [
'function redDiv() {',
' const styles = { color: "red" };',
' return <div style={styles} />;',
'}'
].join('\n'),
parserOptions: parserOptions
},
{
code: [
'function redDiv() {',
' const styles = { color: "red" };',
' return <Hello style={styles} />;',
'}'
].join('\n'),
parserOptions: parserOptions
},
{
code: [
'const styles = { color: "red" };',
'function redDiv() {',
' return <div style={styles} />;',
'}'
].join('\n'),
parserOptions: parserOptions
},
{
code: [
'function redDiv(props) {',
' return <div style={props.styles} />;',
'}'
].join('\n'),
parserOptions: parserOptions
},
{
code: [
'import styles from \'./styles\';',
'function redDiv() {',
' return <div style={styles} />;',
'}'
].join('\n'),
parserOptions: Object.assign({sourceType: 'module'}, parserOptions)
},
{
code: [
'import mystyles from \'./styles\';',
'const styles = Object.assign({ color: \'red\' }, mystyles);',
'function redDiv() {',
' return <div style={styles} />;',
'}'
].join('\n'),
parserOptions: Object.assign({sourceType: 'module'}, parserOptions)
},
{
code: [
'const otherProps = { style: { color: "red" } };',
'const { a, b, ...props } = otherProps;',
'<div {...props} />'
].join('\n'),
parserOptions: parserOptions
},
{
code: [
'const styles = Object.assign({ color: \'red\' }, mystyles);',
'React.createElement("div", { style: styles });'
].join('\n'),
parserOptions: Object.assign({sourceType: 'module'}, parserOptions)
}
],
invalid: [
{
code: '<div style="color: \'red\'" />',
errors: [{
message: 'Style prop value must be an object',
line: 1,
column: 6,
type: 'JSXAttribute'
}],
parserOptions: parserOptions
},
{
code: '<Hello style="color: \'red\'" />',
errors: [{
message: 'Style prop value must be an object',
line: 1,
column: 8,
type: 'JSXAttribute'
}],
parserOptions: parserOptions
},
{
code: '<div style={true} />',
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 <div style={styles} />;',
'}'
].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 <Hello style={styles} />;',
'}'
].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 <div style={styles} />;',
'}'
].join('\n'),
errors: [{
message: 'Style prop value must be an object',
line: 3,
column: 22,
type: 'Identifier'
}],
parserOptions: parserOptions
}
]
});

0 comments on commit 7ed3b29

Please sign in to comment.