Skip to content

Commit ee66c3b

Browse files
author
Gabriel Peal
committed
Register prop-types when set through SuperTypeParameters
Signed-off-by: Gabriel Peal <[email protected]>
1 parent 2748421 commit ee66c3b

File tree

2 files changed

+228
-0
lines changed

2 files changed

+228
-0
lines changed

lib/rules/prop-types.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,21 @@ module.exports = {
151151
return false;
152152
}
153153

154+
/**
155+
* Checks if we are declaring a props as a generic type in a flow-annotated class.
156+
*
157+
* @param {ASTNode} node the AST node being checked.
158+
* @returns {Boolean} True if the node is a class with generic prop types, false if not.
159+
*/
160+
function isSuperTypeParameterPropsDeclaration(node) {
161+
if (node && node.type === 'ClassDeclaration') {
162+
if (node.superTypeParameters && node.superTypeParameters.params.length >= 2) {
163+
return true;
164+
}
165+
}
166+
return false;
167+
}
168+
154169
/**
155170
* Checks if we are declaring a prop
156171
* @param {ASTNode} node The AST node being checked.
@@ -803,6 +818,23 @@ module.exports = {
803818
return annotation;
804819
}
805820

821+
/**
822+
* Resolve the type annotation for a given class declaration node with superTypeParameters.
823+
*
824+
* @param {ASTNode} node The annotation or a node containing the type annotation.
825+
* @returns {ASTNode} The resolved type annotation for the node.
826+
*/
827+
function resolveSuperParameterPropsType(node) {
828+
var annotation = node.superTypeParameters.params[1];
829+
while (annotation && (annotation.type === 'TypeAnnotation' || annotation.type === 'NullableTypeAnnotation')) {
830+
annotation = annotation.typeAnnotation;
831+
}
832+
if (annotation.type === 'GenericTypeAnnotation' && typeScope(annotation.id.name)) {
833+
return typeScope(annotation.id.name);
834+
}
835+
return annotation;
836+
}
837+
806838
/**
807839
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
808840
* FunctionDeclaration, or FunctionExpression
@@ -839,6 +871,12 @@ module.exports = {
839871
// --------------------------------------------------------------------------
840872

841873
return {
874+
ClassDeclaration: function(node) {
875+
if (isSuperTypeParameterPropsDeclaration(node)) {
876+
markPropTypesAsDeclared(node, resolveSuperParameterPropsType(node));
877+
}
878+
},
879+
842880
ClassProperty: function(node) {
843881
if (isAnnotatedClassPropsDeclaration(node)) {
844882
markPropTypesAsDeclared(node, resolveTypeAnnotation(node));

tests/lib/rules/prop-types.js

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1414,6 +1414,88 @@ ruleTester.run('prop-types', rule, {
14141414
'}'
14151415
].join('\n'),
14161416
parser: 'babel-eslint'
1417+
}, {
1418+
code: [
1419+
'type Person = {',
1420+
' firstname: string',
1421+
'};',
1422+
'class Hello extends React.Component<void, Person, void> {',
1423+
' render () {',
1424+
' return <div>Hello {this.props.firstname}</div>;',
1425+
' }',
1426+
'}'
1427+
].join('\n'),
1428+
parser: 'babel-eslint'
1429+
}, {
1430+
code: [
1431+
'type Person = {',
1432+
' firstname: string',
1433+
'};',
1434+
'class Hello extends React.Component<void, Person, void> {',
1435+
' render () {',
1436+
' const { firstname } = this.props;',
1437+
' return <div>Hello {firstname}</div>;',
1438+
' }',
1439+
'}'
1440+
].join('\n'),
1441+
parser: 'babel-eslint'
1442+
}, {
1443+
code: [
1444+
'type Person = {',
1445+
' firstname: string',
1446+
'};',
1447+
'class Hello extends React.Component<void, Person, void> {',
1448+
' render () {',
1449+
' const {',
1450+
' firstname,',
1451+
' } = this.props;',
1452+
' return <div>Hello {firstname}</div>;',
1453+
' }',
1454+
'}'
1455+
].join('\n'),
1456+
parser: 'babel-eslint'
1457+
}, {
1458+
code: [
1459+
'type Props = {name: {firstname: string;};};',
1460+
'class Hello extends React.Component<void, Props, void> {',
1461+
' render () {',
1462+
' return <div>Hello {this.props.name.firstname}</div>;',
1463+
' }',
1464+
'}'
1465+
].join('\n'),
1466+
parser: 'babel-eslint'
1467+
}, {
1468+
code: [
1469+
'import type Props from "fake";',
1470+
'class Hello extends React.Component<void, Props, void> {',
1471+
' render () {',
1472+
' return <div>Hello {this.props.firstname}</div>;',
1473+
' }',
1474+
'}'
1475+
].join('\n'),
1476+
parser: 'babel-eslint'
1477+
}, {
1478+
code: [
1479+
'type Person = {',
1480+
' firstname: string',
1481+
'};',
1482+
'class Hello extends React.Component<void, { person: Person }, void> {',
1483+
' render () {',
1484+
' return <div>Hello {this.props.person.firstname}</div>;',
1485+
' }',
1486+
'}'
1487+
].join('\n'),
1488+
parser: 'babel-eslint'
1489+
}, {
1490+
code: [
1491+
'type Props = {result?: {ok?: ?string | boolean;}|{ok?: ?number | Array}};',
1492+
'class Hello extends React.Component<void, Props, void> {',
1493+
' render () {',
1494+
' return <div>Hello {this.props.result.ok}</div>;',
1495+
' }',
1496+
'}'
1497+
].join('\n'),
1498+
parser: 'babel-eslint'
14171499
}
14181500
],
14191501

@@ -2603,6 +2685,114 @@ ruleTester.run('prop-types', rule, {
26032685
errors: [
26042686
{message: '\'name\' is missing in props validation'}
26052687
]
2688+
}, {
2689+
code: [
2690+
'type Person = {',
2691+
' firstname: string',
2692+
'};',
2693+
'class Hello extends React.Component<void, Person, void> {',
2694+
' render () {',
2695+
' return <div>Hello {this.props.lastname}</div>;',
2696+
' }',
2697+
'}'
2698+
].join('\n'),
2699+
errors: [{
2700+
message: '\'lastname\' is missing in props validation',
2701+
line: 6,
2702+
column: 35,
2703+
type: 'Identifier'
2704+
}],
2705+
parser: 'babel-eslint'
2706+
}, {
2707+
code: [
2708+
'type Person = {',
2709+
' firstname: string',
2710+
'};',
2711+
'class Hello extends React.Component<void, Person, void> {',
2712+
' render () {',
2713+
' const { lastname } = this.props;',
2714+
' return <div>Hello {lastname}</div>;',
2715+
' }',
2716+
'}'
2717+
].join('\n'),
2718+
errors: [{
2719+
message: '\'lastname\' is missing in props validation',
2720+
line: 6,
2721+
column: 13,
2722+
type: 'Property'
2723+
}],
2724+
parser: 'babel-eslint'
2725+
}, {
2726+
code: [
2727+
'type Person = {',
2728+
' firstname: string',
2729+
'};',
2730+
'class Hello extends React.Component<void, Person, void> {',
2731+
' render () {',
2732+
' const {',
2733+
' lastname,',
2734+
' } = this.props;',
2735+
' return <div>Hello {lastname}</div>;',
2736+
' }',
2737+
'}'
2738+
].join('\n'),
2739+
errors: [{
2740+
message: '\'lastname\' is missing in props validation',
2741+
line: 7,
2742+
column: 7,
2743+
type: 'Property'
2744+
}],
2745+
parser: 'babel-eslint'
2746+
}, {
2747+
code: [
2748+
'type Props = {name: {firstname: string;};};',
2749+
'class Hello extends React.Component<void, Props, void> {',
2750+
' render () {',
2751+
' return <div>Hello {this.props.name.lastname}</div>;',
2752+
' }',
2753+
'}'
2754+
].join('\n'),
2755+
errors: [{
2756+
message: '\'name.lastname\' is missing in props validation',
2757+
line: 4,
2758+
column: 40,
2759+
type: 'Identifier'
2760+
}],
2761+
parser: 'babel-eslint'
2762+
}, {
2763+
code: [
2764+
'type Props = {result?: {ok: string | boolean;}|{ok: number | Array}};',
2765+
'class Hello extends React.Component<void, Props, void> {',
2766+
' render () {',
2767+
' return <div>Hello {this.props.result.notok}</div>;',
2768+
' }',
2769+
'}'
2770+
].join('\n'),
2771+
errors: [{
2772+
message: '\'result.notok\' is missing in props validation',
2773+
line: 4,
2774+
column: 42,
2775+
type: 'Identifier'
2776+
}],
2777+
parser: 'babel-eslint'
2778+
}, {
2779+
code: [
2780+
'type Person = {',
2781+
' firstname: string',
2782+
'};',
2783+
'class Hello extends React.Component<void, { person: Person }, void> {',
2784+
' render () {',
2785+
' return <div>Hello {this.props.person.lastname}</div>;',
2786+
' }',
2787+
'}'
2788+
].join('\n'),
2789+
errors: [{
2790+
message: '\'person.lastname\' is missing in props validation',
2791+
line: 6,
2792+
column: 42,
2793+
type: 'Identifier'
2794+
}],
2795+
parser: 'babel-eslint'
26062796
}
26072797
]
26082798
});

0 commit comments

Comments
 (0)