Skip to content

Commit ae5ace5

Browse files
vedadeeptaljharb
authored andcommitted
[Fix] boolean-prop-naming: add check for typescript "boolean" type
Fixes #2892.
1 parent e54118b commit ae5ace5

File tree

3 files changed

+76
-10
lines changed

3 files changed

+76
-10
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
1818
* [`jsx-curly-brace-presence`]: ignore containers with comments ([#2900][] @golopot)
1919
* [`destructuring-assignment`]: fix a false positive for local prop named `context` in SFC ([#2929][] @SyMind)
2020
* [`jsx-no-target-blank`]: Allow rel="noreferrer" when `allowReferrer` is true ([#2925][] @edemaine)
21+
* [`boolean-prop-naming`]: add check for typescript "boolean" type ([#2930][] @vedadeepta)
2122

2223
### Changed
2324
* [Docs] [`jsx-no-constructed-context-values`][]: fix invalid example syntax ([#2910][] @kud)
2425
* [readme] Replace lists of rules with tables in readme ([#2908][] @motato1)
2526
* [Docs] added missing curly braces ([#2923][] @Muditxofficial)
2627

28+
[#2930]: https://github.com/yannickcr/eslint-plugin-react/pull/2930
2729
[#2929]: https://github.com/yannickcr/eslint-plugin-react/pull/2929
2830
[#2925]: https://github.com/yannickcr/eslint-plugin-react/pull/2925
2931
[#2923]: https://github.com/yannickcr/eslint-plugin-react/pull/2923

lib/rules/boolean-prop-naming.js

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ module.exports = {
8686
if (node.type === 'ExperimentalSpreadProperty' || node.type === 'SpreadElement') {
8787
return null;
8888
}
89-
if (node.value.property) {
89+
if (node.value && node.value.property) {
9090
const name = node.value.property.name;
9191
if (name === 'isRequired') {
9292
if (node.value.object && node.value.object.property) {
@@ -96,7 +96,7 @@ module.exports = {
9696
}
9797
return name;
9898
}
99-
if (node.value.type === 'Identifier') {
99+
if (node.value && node.value.type === 'Identifier') {
100100
return node.value.name;
101101
}
102102
return null;
@@ -145,6 +145,16 @@ module.exports = {
145145
);
146146
}
147147

148+
function tsCheck(prop) {
149+
if (prop.type !== 'TSPropertySignature') return false;
150+
const typeAnnotation = (prop.typeAnnotation || {}).typeAnnotation;
151+
return (
152+
typeAnnotation
153+
&& typeAnnotation.type === 'TSBooleanKeyword'
154+
&& rule.test(getPropName(prop)) === false
155+
);
156+
}
157+
148158
/**
149159
* Checks if prop is nested
150160
* @param {Object} prop Property object, single prop type declaration
@@ -170,7 +180,7 @@ module.exports = {
170180
runCheck(prop.value.arguments[0].properties, addInvalidProp);
171181
return;
172182
}
173-
if (flowCheck(prop) || regularCheck(prop)) {
183+
if (flowCheck(prop) || regularCheck(prop) || tsCheck(prop)) {
174184
addInvalidProp(prop);
175185
}
176186
});
@@ -289,6 +299,12 @@ module.exports = {
289299
}
290300
},
291301

302+
TSTypeAliasDeclaration(node) {
303+
if (node.typeAnnotation.type === 'TSTypeLiteral') {
304+
objectTypeAnnotations.set(node.id.name, node.typeAnnotation);
305+
}
306+
},
307+
292308
// eslint-disable-next-line object-shorthand
293309
'Program:exit'() {
294310
if (!rule) {
@@ -299,22 +315,30 @@ module.exports = {
299315
Object.keys(list).forEach((component) => {
300316
// If this is a functional component that uses a global type, check it
301317
if (
302-
list[component].node.type === 'FunctionDeclaration'
318+
(
319+
list[component].node.type === 'FunctionDeclaration'
320+
|| list[component].node.type === 'ArrowFunctionExpression'
321+
)
303322
&& list[component].node.params
304323
&& list[component].node.params.length
305324
&& list[component].node.params[0].typeAnnotation
306325
) {
307326
const typeNode = list[component].node.params[0].typeAnnotation;
308327
const annotation = typeNode.typeAnnotation;
309-
310328
let propType;
311329
if (annotation.type === 'GenericTypeAnnotation') {
312330
propType = objectTypeAnnotations.get(annotation.id.name);
313331
} else if (annotation.type === 'ObjectTypeAnnotation') {
314332
propType = annotation;
333+
} else if (annotation.type === 'TSTypeReference') {
334+
propType = objectTypeAnnotations.get(annotation.typeName.name);
315335
}
336+
316337
if (propType) {
317-
validatePropNaming(list[component].node, propType.properties);
338+
validatePropNaming(
339+
list[component].node,
340+
propType.properties || propType.members
341+
);
318342
}
319343
}
320344

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

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const parserOptions = {
2929
const ruleTester = new RuleTester({parserOptions});
3030
ruleTester.run('boolean-prop-naming', rule, {
3131

32-
valid: [{
32+
valid: [].concat({
3333
// Should support both `is` and `has` prefixes by default
3434
code: `
3535
var Hello = createReactClass({
@@ -416,9 +416,21 @@ ruleTester.run('boolean-prop-naming', rule, {
416416
rule: '^is[A-Z]([A-Za-z0-9]?)+',
417417
validateNested: true
418418
}]
419-
}],
419+
}, parsers.TS({
420+
code: `
421+
type TestFNType = {
422+
isEnabled: boolean
423+
}
424+
const HelloNew = (props: TestFNType) => { return <div /> };
425+
`,
426+
options: [{
427+
rule: '^is[A-Z]([A-Za-z0-9]?)+'
428+
}],
429+
parser: parsers['@TYPESCRIPT_ESLINT'],
430+
errors: []
431+
})),
420432

421-
invalid: [{
433+
invalid: [].concat({
422434
// createReactClass components with PropTypes
423435
code: `
424436
var Hello = createReactClass({
@@ -944,5 +956,33 @@ ruleTester.run('boolean-prop-naming', rule, {
944956
messageId: 'patternMismatch',
945957
data: {propName: 'something', pattern: '^is[A-Z]([A-Za-z0-9]?)+'}
946958
}]
947-
}]
959+
}, parsers.TS({
960+
code: `
961+
type TestConstType = {
962+
enabled: boolean
963+
}
964+
const HelloNew = (props: TestConstType) => { return <div /> };
965+
`,
966+
options: [{
967+
rule: '^is[A-Z]([A-Za-z0-9]?)+'
968+
}],
969+
parser: parsers['@TYPESCRIPT_ESLINT'],
970+
errors: [{
971+
message: 'Prop name (enabled) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)'
972+
}]
973+
}, {
974+
code: `
975+
type TestFNType = {
976+
enabled: boolean
977+
}
978+
const HelloNew = (props: TestFNType) => { return <div /> };
979+
`,
980+
options: [{
981+
rule: '^is[A-Z]([A-Za-z0-9]?)+'
982+
}],
983+
parser: parsers['@TYPESCRIPT_ESLINT'],
984+
errors: [{
985+
message: 'Prop name (enabled) doesn\'t match rule (^is[A-Z]([A-Za-z0-9]?)+)'
986+
}]
987+
}))
948988
});

0 commit comments

Comments
 (0)