Skip to content

Commit 8a1eb1e

Browse files
iiisonljharb
authored andcommitted
[New]: jsx-no-literals: add validateProps option to ignore props validation
1 parent 6e4f43e commit 8a1eb1e

File tree

3 files changed

+128
-20
lines changed

3 files changed

+128
-20
lines changed

docs/rules/jsx-no-literals.md

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,18 @@ var Hello = <div>{'test'}</div>;
2121

2222
### Options
2323

24-
There is only one option:
24+
There are two options:
2525

26-
* `noStrings` - Enforces no string literals used as children, wrapped or unwrapped.
26+
* `noStrings`(`false` default) - Enforces no string literals used as children, wrapped or unwrapped.
27+
* `validateProps`(`false` default) - Enforces no literals used in props, wrapped or unwrapped.
2728

2829
To use, you can specify like the following:
2930

3031
```js
31-
"react/jsx-no-literals": [<enabled>, {"noStrings": true}]
32+
"react/jsx-no-literals": [<enabled>, {"noStrings": true, "validateProps": true}]
3233
```
3334

34-
In this configuration, the following are considered warnings:
35+
With `noStrings` set to `true`, the following are considered warnings:
3536

3637
```jsx
3738
var Hello = <div>test</div>;
@@ -53,6 +54,51 @@ var Hello = <div><Text {...message} /></div>
5354
var Hello = <div>{translate('my.translation.key')}</div>
5455
```
5556

57+
With `validateProps` set to `true`, the following are considered warnings:
58+
59+
```jsx
60+
var Hello = <div class='xx' />;
61+
```
62+
63+
```jsx
64+
var Hello = <div class={'xx'} />;
65+
```
66+
67+
```jsx
68+
var Hello = <div class={`xx`} />;
69+
```
70+
71+
The following are **not** considered warnings:
72+
73+
```jsx
74+
// spread props object
75+
var Hello = <Text {...props} />
76+
```
77+
78+
```jsx
79+
// use variable for prop values
80+
var Hello = <div class={xx} />
81+
```
82+
83+
```jsx
84+
// cache
85+
class Comp1 extends Component {
86+
asdf() {}
87+
88+
render() {
89+
return (
90+
<div onClick={this.asdf}>
91+
{'asdjfl'}
92+
test
93+
{'foo'}
94+
</div>
95+
);
96+
}
97+
}
98+
99+
```
100+
56101
## When Not To Use It
57102

58103
If you do not want to enforce any style JSX literals, then you can disable this rule.
104+

lib/rules/jsx-no-literals.js

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,36 @@ module.exports = {
2525
properties: {
2626
noStrings: {
2727
type: 'boolean'
28+
},
29+
validateProps: {
30+
type: 'boolean'
2831
}
2932
},
3033
additionalProperties: false
3134
}]
3235
},
3336

3437
create: function(context) {
35-
const isNoStrings = context.options[0] ? context.options[0].noStrings : false;
38+
const options = context.options;
39+
const configs = options[0] || {};
40+
41+
const noStrings = configs.noStrings || false;
42+
const validateProps = configs.validateProps || false;
43+
44+
const isNoStrings = noStrings;
45+
const shouldValidateProps = validateProps;
3646
const sourceCode = context.getSourceCode();
3747

3848
const message = isNoStrings ?
3949
'Strings not allowed in JSX files' :
4050
'Missing JSX expression container around literal string';
4151

42-
function reportLiteralNode(node) {
52+
function reportLiteralNode(node, customMessage) {
53+
const errorMessage = customMessage || message;
54+
4355
context.report({
4456
node: node,
45-
message: `${message}: “${sourceCode.getText(node).trim()}”`
57+
message: `${errorMessage}: “${sourceCode.getText(node).trim()}”`
4658
});
4759
}
4860

@@ -66,31 +78,64 @@ module.exports = {
6678
return standard && parent.type !== 'JSXExpressionContainer';
6779
}
6880

81+
function getParentAndGrandParentType(node) {
82+
const parent = getParentIgnoringBinaryExpressions(node);
83+
const parentType = parent.type;
84+
const grandParentType = parent.parent.type;
85+
86+
return {
87+
parent: parent,
88+
parentType: parentType,
89+
grandParentType: grandParentType,
90+
grandParent: parent.parent
91+
};
92+
}
93+
94+
function hasJSXElementParentOrGrandParent(node) {
95+
const parents = getParentAndGrandParentType(node);
96+
const parentType = parents.parentType;
97+
const grandParentType = parents.grandParentType;
98+
99+
return (parentType === 'JSXFragment') || (parentType === 'JSXElement' || grandParentType === 'JSXElement');
100+
}
101+
69102
// --------------------------------------------------------------------------
70103
// Public
71104
// --------------------------------------------------------------------------
72105

73106
return {
74-
75107
Literal: function(node) {
76-
if (getValidation(node)) {
108+
if (getValidation(node) && (hasJSXElementParentOrGrandParent(node) || shouldValidateProps)) {
77109
reportLiteralNode(node);
78110
}
79111
},
80112

113+
JSXAttribute: function(node) {
114+
const isNodeValueString = node.value && node.value && node.value.type === 'Literal' && typeof node.value.value === 'string';
115+
116+
if (isNoStrings && shouldValidateProps && isNodeValueString) {
117+
const customMessage = 'Invalid attribute value';
118+
reportLiteralNode(node, customMessage);
119+
}
120+
},
121+
81122
JSXText: function(node) {
82123
if (getValidation(node)) {
83124
reportLiteralNode(node);
84125
}
85126
},
86127

87128
TemplateLiteral: function(node) {
88-
const parent = getParentIgnoringBinaryExpressions(node);
89-
if (isNoStrings && parent.type === 'JSXExpressionContainer') {
129+
const parents = getParentAndGrandParentType(node);
130+
const parentType = parents.parentType;
131+
const grandParentType = parents.grandParentType;
132+
const isParentJSXExpressionCont = parentType === 'JSXExpressionContainer';
133+
const isParentJSXElement = parentType === 'JSXElement' || grandParentType === 'JSXElement';
134+
135+
if (isParentJSXExpressionCont && isNoStrings && (isParentJSXElement || shouldValidateProps)) {
90136
reportLiteralNode(node);
91137
}
92138
}
93-
94139
};
95140
}
96141
};

tests/lib/rules/jsx-no-literals.js

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ ruleTester.run('jsx-no-literals', rule, {
182182
class Comp1 extends Component {
183183
asdf() {}
184184
render() {
185-
return <Foo bar={this.asdf} />;
185+
return <Foo bar={this.asdf} class='xx' />;
186186
}
187187
}
188188
`,
@@ -197,8 +197,19 @@ ruleTester.run('jsx-no-literals', rule, {
197197
}
198198
`,
199199
options: [{noStrings: true}]
200-
}
200+
}, {
201+
code: `
202+
class Comp1 extends Component {
203+
asdf() {}
204+
render() {
205+
const xx = 'xx';
201206
207+
return <Foo bar={this.asdf} class={xx} />;
208+
}
209+
} `,
210+
parser: 'babel-eslint',
211+
options: [{noStrings: true, validateProps: true}]
212+
}
202213
],
203214

204215
invalid: [
@@ -353,37 +364,43 @@ ruleTester.run('jsx-no-literals', rule, {
353364
errors: [{message: stringsMessage('`Test`')}]
354365
}, {
355366
code: '<Foo bar={`Test`} />',
356-
options: [{noStrings: true}],
367+
options: [{noStrings: true, validateProps: true}],
357368
errors: [{message: stringsMessage('`Test`')}]
358369
}, {
359370
code: '<Foo bar={`${baz}`} />',
360-
options: [{noStrings: true}],
371+
options: [{noStrings: true, validateProps: true}],
361372
errors: [{message: stringsMessage('`${baz}`')}]
362373
}, {
363374
code: '<Foo bar={`Test ${baz}`} />',
364-
options: [{noStrings: true}],
375+
options: [{noStrings: true, validateProps: true}],
365376
errors: [{message: stringsMessage('`Test ${baz}`')}]
366377
}, {
367378
code: '<Foo bar={`foo` + \'bar\'} />',
368-
options: [{noStrings: true}],
379+
options: [{noStrings: true, validateProps: true}],
369380
errors: [
370381
{message: stringsMessage('`foo`')},
371382
{message: stringsMessage('\'bar\'')}
372383
]
373384
}, {
374385
code: '<Foo bar={`foo` + `bar`} />',
375-
options: [{noStrings: true}],
386+
options: [{noStrings: true, validateProps: true}],
376387
errors: [
377388
{message: stringsMessage('`foo`')},
378389
{message: stringsMessage('`bar`')}
379390
]
380391
}, {
381392
code: '<Foo bar={\'foo\' + `bar`} />',
382-
options: [{noStrings: true}],
393+
options: [{noStrings: true, validateProps: true}],
383394
errors: [
384395
{message: stringsMessage('\'foo\'')},
385396
{message: stringsMessage('`bar`')}
386397
]
398+
}, {
399+
code: '<Foo bar={\'bar\'} />',
400+
options: [{noStrings: true, validateProps: true}],
401+
errors: [
402+
{message: stringsMessage('\'bar\'')}
403+
]
387404
}
388405
]
389406
});

0 commit comments

Comments
 (0)