Skip to content

Commit ff6ab04

Browse files
committed
Add new dom-elements-style-is-object rule (Fixes jsx-eslint#715)
1 parent 4be546a commit ff6ab04

File tree

5 files changed

+318
-1
lines changed

5 files changed

+318
-1
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
105105
* [react/self-closing-comp](docs/rules/self-closing-comp.md): Prevent extra closing tags for components without children (fixable)
106106
* [react/sort-comp](docs/rules/sort-comp.md): Enforce component methods order
107107
* [react/sort-prop-types](docs/rules/sort-prop-types.md): Enforce propTypes declarations alphabetical sorting
108+
* [react/style-prop-object](docs/rules/style-prop-object.md): Enforce style prop value being an object
108109

109110
## JSX-specific rules
110111

docs/rules/style-prop-object.md

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Enforce style prop value being an object (style-prop-object)
2+
3+
Require that the value of the prop `style` be an object or a variable that is
4+
an object.
5+
6+
## Rule Details
7+
8+
The following patterns are considered warnings:
9+
10+
```jsx
11+
<div style="color: 'red'" />
12+
13+
<div style={true} />
14+
15+
<Hello style={true} />
16+
17+
const styles = true;
18+
<div style={styles} />
19+
```
20+
21+
```js
22+
React.createElement("div", { style: "color: 'red'" });
23+
24+
React.createElement("div", { style: true });
25+
26+
React.createElement("Hello", { style: true });
27+
28+
const styles = true;
29+
React.createElement("div", { style: styles });
30+
```
31+
32+
33+
The following patterns are not considered warnings:
34+
35+
```jsx
36+
<div style={{ color: "red" }} />
37+
38+
<Hello style={{ color: "red" }} />
39+
40+
const styles = { color: "red" };
41+
<div style={styles} />
42+
```
43+
44+
```js
45+
React.createElement("div", { style: { color: 'red' }});
46+
47+
React.createElement("Hello", { style: { color: 'red' }});
48+
49+
const styles = { height: '100px' };
50+
React.createElement("div", { style: styles });
51+
```

index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ var rules = {
5454
'jsx-filename-extension': require('./lib/rules/jsx-filename-extension'),
5555
'require-optimization': require('./lib/rules/require-optimization'),
5656
'no-find-dom-node': require('./lib/rules/no-find-dom-node'),
57-
'no-danger-with-children': require('./lib/rules/no-danger-with-children')
57+
'no-danger-with-children': require('./lib/rules/no-danger-with-children'),
58+
'style-prop-object': require('./lib/rules/style-prop-object')
5859
};
5960

6061
var ruleNames = Object.keys(rules);

lib/rules/style-prop-object.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @fileoverview Enforce style prop value is an object
3+
* @author David Petersen
4+
*/
5+
'use strict';
6+
7+
var variableUtil = require('../util/variable');
8+
9+
// ------------------------------------------------------------------------------
10+
// Rule Definition
11+
// ------------------------------------------------------------------------------
12+
13+
module.exports = {
14+
meta: {
15+
docs: {
16+
description: 'Enforce style prop value is an object',
17+
category: '',
18+
recommended: false
19+
},
20+
schema: []
21+
},
22+
23+
create: function(context) {
24+
/**
25+
* @param {object} node A Identifier node
26+
*/
27+
function checkIdentifiers(node) {
28+
var variable = variableUtil.variablesInScope(context).find(function (item) {
29+
return item.name === node.name;
30+
});
31+
32+
if (!variable || !variable.defs[0].node.init) {
33+
return;
34+
}
35+
36+
if (variable.defs[0].node.init.type === 'Literal') {
37+
context.report(node, 'Style prop value must be an object');
38+
}
39+
}
40+
41+
return {
42+
CallExpression: function(node) {
43+
if (
44+
node.callee
45+
&& node.callee.type === 'MemberExpression'
46+
&& node.callee.property.name === 'createElement'
47+
&& node.arguments.length > 1
48+
) {
49+
if (node.arguments[1].type === 'ObjectExpression') {
50+
var style = node.arguments[1].properties.find(function(property) {
51+
return property.key.name === 'style';
52+
});
53+
if (style) {
54+
if (style.value.type === 'Identifier') {
55+
checkIdentifiers(style.value);
56+
} else if (style.value.type === 'Literal') {
57+
context.report(style.value, 'Style prop value must be an object');
58+
}
59+
}
60+
}
61+
}
62+
},
63+
64+
JSXAttribute: function(node) {
65+
if (node.name.name !== 'style') {
66+
return;
67+
}
68+
69+
if (
70+
node.value.type !== 'JSXExpressionContainer'
71+
|| node.value.expression.type === 'Literal'
72+
) {
73+
context.report(node, 'Style prop value must be an object');
74+
} else if (node.value.expression.type === 'Identifier') {
75+
checkIdentifiers(node.value.expression);
76+
}
77+
}
78+
};
79+
}
80+
};

tests/lib/rules/style-prop-object.js

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/**
2+
* @fileoverview Enforce style prop value is an object
3+
* @author David Petersen
4+
*/
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
var rule = require('../../../lib/rules/style-prop-object');
12+
var RuleTester = require('eslint').RuleTester;
13+
14+
var parserOptions = {
15+
ecmaVersion: 6,
16+
ecmaFeatures: {
17+
experimentalObjectRestSpread: true,
18+
jsx: true
19+
}
20+
};
21+
22+
// ------------------------------------------------------------------------------
23+
// Tests
24+
// ------------------------------------------------------------------------------
25+
26+
var ruleTester = new RuleTester();
27+
ruleTester.run('style-prop-object', rule, {
28+
valid: [
29+
{
30+
code: '<div style={{ color: "red" }} />',
31+
parserOptions: parserOptions
32+
},
33+
{
34+
code: '<Hello style={{ color: "red" }} />',
35+
parserOptions: parserOptions
36+
},
37+
{
38+
code: [
39+
'function redDiv() {',
40+
' const styles = { color: "red" };',
41+
' return <div style={styles} />;',
42+
'}'
43+
].join('\n'),
44+
parserOptions: parserOptions
45+
},
46+
{
47+
code: [
48+
'function redDiv() {',
49+
' const styles = { color: "red" };',
50+
' return <Hello style={styles} />;',
51+
'}'
52+
].join('\n'),
53+
parserOptions: parserOptions
54+
},
55+
{
56+
code: [
57+
'const styles = { color: "red" };',
58+
'function redDiv() {',
59+
' return <div style={styles} />;',
60+
'}'
61+
].join('\n'),
62+
parserOptions: parserOptions
63+
},
64+
{
65+
code: [
66+
'function redDiv(props) {',
67+
' return <div style={props.styles} />;',
68+
'}'
69+
].join('\n'),
70+
parserOptions: parserOptions
71+
},
72+
{
73+
code: [
74+
'import styles from \'./styles\';',
75+
'function redDiv() {',
76+
' return <div style={styles} />;',
77+
'}'
78+
].join('\n'),
79+
parserOptions: Object.assign({sourceType: 'module'}, parserOptions)
80+
},
81+
{
82+
code: [
83+
'import mystyles from \'./styles\';',
84+
'const styles = Object.assign({ color: \'red\' }, mystyles);',
85+
'function redDiv() {',
86+
' return <div style={styles} />;',
87+
'}'
88+
].join('\n'),
89+
parserOptions: Object.assign({sourceType: 'module'}, parserOptions)
90+
},
91+
{
92+
code: [
93+
'const otherProps = { style: { color: "red" } };',
94+
'const { a, b, ...props } = otherProps;',
95+
'<div {...props} />'
96+
].join('\n'),
97+
parserOptions: parserOptions
98+
},
99+
{
100+
code: [
101+
'const styles = Object.assign({ color: \'red\' }, mystyles);',
102+
'React.createElement("div", { style: styles });'
103+
].join('\n'),
104+
parserOptions: Object.assign({sourceType: 'module'}, parserOptions)
105+
}
106+
],
107+
invalid: [
108+
{
109+
code: '<div style="color: \'red\'" />',
110+
errors: [{
111+
message: 'Style prop value must be an object',
112+
line: 1,
113+
column: 6,
114+
type: 'JSXAttribute'
115+
}],
116+
parserOptions: parserOptions
117+
},
118+
{
119+
code: '<Hello style="color: \'red\'" />',
120+
errors: [{
121+
message: 'Style prop value must be an object',
122+
line: 1,
123+
column: 8,
124+
type: 'JSXAttribute'
125+
}],
126+
parserOptions: parserOptions
127+
},
128+
{
129+
code: '<div style={true} />',
130+
errors: [{
131+
message: 'Style prop value must be an object',
132+
line: 1,
133+
column: 6,
134+
type: 'JSXAttribute'
135+
}],
136+
parserOptions: parserOptions
137+
},
138+
{
139+
code: [
140+
'const styles = \'color: "red"\';',
141+
'function redDiv2() {',
142+
' return <div style={styles} />;',
143+
'}'
144+
].join('\n'),
145+
errors: [{
146+
message: 'Style prop value must be an object',
147+
line: 3,
148+
column: 22,
149+
type: 'Identifier'
150+
}],
151+
parserOptions: parserOptions
152+
},
153+
{
154+
code: [
155+
'const styles = \'color: "red"\';',
156+
'function redDiv2() {',
157+
' return <Hello style={styles} />;',
158+
'}'
159+
].join('\n'),
160+
errors: [{
161+
message: 'Style prop value must be an object',
162+
line: 3,
163+
column: 24,
164+
type: 'Identifier'
165+
}],
166+
parserOptions: parserOptions
167+
},
168+
{
169+
code: [
170+
'const styles = true;',
171+
'function redDiv() {',
172+
' return <div style={styles} />;',
173+
'}'
174+
].join('\n'),
175+
errors: [{
176+
message: 'Style prop value must be an object',
177+
line: 3,
178+
column: 22,
179+
type: 'Identifier'
180+
}],
181+
parserOptions: parserOptions
182+
}
183+
]
184+
});

0 commit comments

Comments
 (0)