Skip to content

Commit 157332f

Browse files
Paweł Nowakljharb
authored andcommitted
[new] boolean-prop-naming: add option to validate shape prop names
1 parent 60b4b31 commit 157332f

File tree

3 files changed

+168
-17
lines changed

3 files changed

+168
-17
lines changed

docs/rules/boolean-prop-naming.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ var Hello = createReactClass({
3030

3131
```js
3232
...
33-
"react/boolean-prop-naming": [<enabled>, { "propTypeNames": Array<string>, "rule": <string>, "message": <string> }]
33+
"react/boolean-prop-naming": [<enabled>, {
34+
"propTypeNames": Array<string>,
35+
"rule": <string>,
36+
"message": <string>,
37+
"validateNested": <boolean>
38+
}]
3439
...
3540
```
3641

@@ -86,3 +91,11 @@ And the failure would look like so:
8691
```
8792
It is better if your prop (something) matches this pattern: (^is[A-Z]([A-Za-z0-9]?)+)
8893
```
94+
95+
### `validateNested`
96+
97+
This value is boolean. It tells if nested props should be validated as well. By default this is set to false but you can change it to true, to validate deeper layers of object:
98+
99+
```jsx
100+
"react/boolean-prop-naming": ["error", { "validateNested": true }]
101+
```

lib/rules/boolean-prop-naming.js

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ module.exports = {
4141
message: {
4242
minLength: 1,
4343
type: 'string'
44+
},
45+
validateNested: {
46+
default: false,
47+
type: 'boolean'
4448
}
4549
},
4650
type: 'object'
@@ -104,6 +108,64 @@ module.exports = {
104108
return node.key.name;
105109
}
106110

111+
/**
112+
* Checks if prop is declared in flow way
113+
* @param {Object} prop Property object, single prop type declaration
114+
* @returns {Boolean}
115+
*/
116+
function flowCheck(prop) {
117+
return (
118+
prop.type === 'ObjectTypeProperty' &&
119+
prop.value.type === 'BooleanTypeAnnotation' &&
120+
rule.test(getPropName(prop)) === false
121+
);
122+
}
123+
124+
/**
125+
* Checks if prop is declared in regular way
126+
* @param {Object} prop Property object, single prop type declaration
127+
* @returns {Boolean}
128+
*/
129+
function regularCheck(prop) {
130+
const propKey = getPropKey(prop);
131+
return (
132+
propKey &&
133+
propTypeNames.indexOf(propKey) >= 0 &&
134+
rule.test(getPropName(prop)) === false
135+
);
136+
}
137+
138+
/**
139+
* Checks if prop is nested
140+
* @param {Object} prop Property object, single prop type declaration
141+
* @returns {Boolean}
142+
*/
143+
function nestedPropTypes(prop) {
144+
return (
145+
prop.type === 'Property' &&
146+
prop.value.type === 'CallExpression'
147+
);
148+
}
149+
150+
/**
151+
* Runs recursive check on all proptypes
152+
* @param {Array} proptypes A list of Property object (for each proptype defined)
153+
* @param {Function} addInvalidProp callback to run for each error
154+
*/
155+
function runCheck(proptypes, addInvalidProp) {
156+
proptypes = proptypes || [];
157+
158+
proptypes.forEach(prop => {
159+
if (config.validateNested && nestedPropTypes(prop)) {
160+
runCheck(prop.value.arguments[0].properties, addInvalidProp);
161+
return;
162+
}
163+
if (flowCheck(prop) || regularCheck(prop)) {
164+
addInvalidProp(prop);
165+
}
166+
});
167+
}
168+
107169
/**
108170
* Checks and mark props with invalid naming
109171
* @param {Object} node The component node we're testing
@@ -113,22 +175,8 @@ module.exports = {
113175
const component = components.get(node) || node;
114176
const invalidProps = component.invalidProps || [];
115177

116-
(proptypes || []).forEach(prop => {
117-
const propKey = getPropKey(prop);
118-
const flowCheck = (
119-
prop.type === 'ObjectTypeProperty' &&
120-
prop.value.type === 'BooleanTypeAnnotation' &&
121-
rule.test(getPropName(prop)) === false
122-
);
123-
const regularCheck = (
124-
propKey &&
125-
propTypeNames.indexOf(propKey) >= 0 &&
126-
rule.test(getPropName(prop)) === false
127-
);
128-
129-
if (flowCheck || regularCheck) {
130-
invalidProps.push(prop);
131-
}
178+
runCheck(proptypes, prop => {
179+
invalidProps.push(prop);
132180
});
133181

134182
components.set(node, {

tests/lib/rules/boolean-prop-naming.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,46 @@ ruleTester.run('boolean-prop-naming', rule, {
373373
rule: '^is[A-Z]([A-Za-z0-9]?)+'
374374
}],
375375
parser: 'babel-eslint'
376+
}, {
377+
code: `
378+
class Hello extends React.Component {
379+
render() {
380+
return (
381+
<div />
382+
);
383+
}
384+
}
385+
386+
Hello.propTypes = {
387+
isSomething: PropTypes.bool.isRequired,
388+
nested: PropTypes.shape({
389+
isWorking: PropTypes.bool
390+
})
391+
};
392+
`
393+
}, {
394+
code: `
395+
class Hello extends React.Component {
396+
render() {
397+
return (
398+
<div />
399+
);
400+
}
401+
}
402+
403+
Hello.propTypes = {
404+
isSomething: PropTypes.bool.isRequired,
405+
nested: PropTypes.shape({
406+
nested: PropTypes.shape({
407+
isWorking: PropTypes.bool
408+
})
409+
})
410+
};
411+
`,
412+
options: [{
413+
rule: '^is[A-Z]([A-Za-z0-9]?)+',
414+
validateNested: true
415+
}]
376416
}],
377417

378418
invalid: [{
@@ -807,5 +847,55 @@ ruleTester.run('boolean-prop-naming', rule, {
807847
errors: [{
808848
message: 'Prop name (something) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)'
809849
}]
850+
}, {
851+
code: `
852+
class Hello extends React.Component {
853+
render() {
854+
return (
855+
<div />
856+
);
857+
}
858+
}
859+
860+
Hello.propTypes = {
861+
isSomething: PropTypes.bool.isRequired,
862+
nested: PropTypes.shape({
863+
failingItIs: PropTypes.bool
864+
})
865+
};
866+
`,
867+
options: [{
868+
rule: '^is[A-Z]([A-Za-z0-9]?)+',
869+
validateNested: true
870+
}],
871+
errors: [{
872+
message: 'Prop name (failingItIs) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)'
873+
}]
874+
}, {
875+
code: `
876+
class Hello extends React.Component {
877+
render() {
878+
return (
879+
<div />
880+
);
881+
}
882+
}
883+
884+
Hello.propTypes = {
885+
isSomething: PropTypes.bool.isRequired,
886+
nested: PropTypes.shape({
887+
nested: PropTypes.shape({
888+
failingItIs: PropTypes.bool
889+
})
890+
})
891+
};
892+
`,
893+
options: [{
894+
rule: '^is[A-Z]([A-Za-z0-9]?)+',
895+
validateNested: true
896+
}],
897+
errors: [{
898+
message: 'Prop name (failingItIs) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)'
899+
}]
810900
}]
811901
});

0 commit comments

Comments
 (0)