Skip to content

Commit ecbb05d

Browse files
committed
Add some more test cases and refactor
This adds a couple more test cases that were missed. This also refactors the rule a bit to clean it up. There is also a workaround for keeping track of TypeAlias nodes seen to work around an issue with eslint@3 and babel-eslint preventing the TypeAlias variables being found in the scope.
1 parent eae169b commit ecbb05d

File tree

2 files changed

+150
-29
lines changed

2 files changed

+150
-29
lines changed

lib/rules/prefer-exact-props.js

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ module.exports = {
2828
},
2929

3030
create: Components.detect((context, components, utils) => {
31+
const typeAliases = {};
3132
const exactWrappers = propWrapperUtil.getExactPropWrapperFunctions(context);
33+
const sourceCode = context.getSourceCode();
3234

3335
function getPropTypesErrorMessage() {
3436
const formattedWrappers = propWrapperUtil.formatPropWrapperFunctions(exactWrappers);
@@ -71,23 +73,46 @@ module.exports = {
7173
);
7274
}
7375

76+
function isNonExactPropWrapperFunction(node) {
77+
return (
78+
node &&
79+
node.type === 'CallExpression' &&
80+
!propWrapperUtil.isExactPropWrapperFunction(context, sourceCode.getText(node.callee))
81+
);
82+
}
83+
84+
function reportPropTypesError(node) {
85+
context.report({
86+
node: node,
87+
message: PROP_TYPES_MESSAGE,
88+
data: getPropTypesErrorMessage()
89+
});
90+
}
91+
92+
function reportFlowError(node) {
93+
context.report({
94+
node: node,
95+
message: FLOW_MESSAGE
96+
});
97+
}
98+
7499
return {
100+
TypeAlias: function(node) {
101+
// working around an issue with eslint@3 and babel-eslint not finding the TypeAlias in scope
102+
typeAliases[node.id.name] = node;
103+
},
104+
75105
ClassProperty: function(node) {
76106
if (!propsUtil.isPropTypesDeclaration(node)) {
77107
return;
78108
}
79109

80110
if (hasNonExactObjectTypeAnnotation(node)) {
81-
context.report({
82-
node: node,
83-
message: FLOW_MESSAGE
84-
});
85-
} else if (isNonEmptyObjectExpression(node.value) && exactWrappers.size > 0) {
86-
context.report({
87-
node: node,
88-
message: PROP_TYPES_MESSAGE,
89-
data: getPropTypesErrorMessage()
90-
});
111+
reportFlowError(node);
112+
} else if (exactWrappers.size > 0 && isNonEmptyObjectExpression(node.value)) {
113+
reportPropTypesError(node);
114+
} else if (exactWrappers.size > 0 && isNonExactPropWrapperFunction(node.value)) {
115+
reportPropTypesError(node);
91116
}
92117
},
93118

@@ -97,18 +122,13 @@ module.exports = {
97122
}
98123

99124
if (hasNonExactObjectTypeAnnotation(node)) {
100-
context.report({
101-
node: node,
102-
message: FLOW_MESSAGE
103-
});
125+
reportFlowError(node);
104126
} else if (hasGenericTypeAnnotation(node)) {
105127
const identifier = node.typeAnnotation.typeAnnotation.id.name;
106-
const propsDefinition = variableUtil.findVariableByName(context, identifier);
128+
const typeAlias = typeAliases[identifier];
129+
const propsDefinition = typeAlias ? typeAlias.right : null;
107130
if (isNonExactObjectTypeAnnotation(propsDefinition)) {
108-
context.report({
109-
node: node,
110-
message: FLOW_MESSAGE
111-
});
131+
reportFlowError(node);
112132
}
113133
}
114134
},
@@ -120,20 +140,16 @@ module.exports = {
120140

121141
const right = node.parent.right;
122142
if (isNonEmptyObjectExpression(right)) {
123-
context.report({
124-
node: node,
125-
message: PROP_TYPES_MESSAGE,
126-
data: getPropTypesErrorMessage()
127-
});
143+
reportPropTypesError(node);
144+
} else if (isNonExactPropWrapperFunction(right)) {
145+
reportPropTypesError(node);
128146
} else if (right.type === 'Identifier') {
129147
const identifier = right.name;
130148
const propsDefinition = variableUtil.findVariableByName(context, identifier);
131149
if (isNonEmptyObjectExpression(propsDefinition)) {
132-
context.report({
133-
node: node,
134-
message: PROP_TYPES_MESSAGE,
135-
data: getPropTypesErrorMessage()
136-
});
150+
reportPropTypesError(node);
151+
} else if (isNonExactPropWrapperFunction(propsDefinition)) {
152+
reportPropTypesError(node);
137153
}
138154
}
139155
}

tests/lib/rules/prefer-exact-props.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,39 @@ ruleTester.run('prefer-exact-props', rule, {
186186
bar: PropTypes.string,
187187
};
188188
`
189+
}, {
190+
code: `
191+
import somethingElse from "something-else";
192+
const props = {
193+
foo: PropTypes.string,
194+
bar: PropTypes.shape({
195+
baz: PropTypes.string
196+
})
197+
};
198+
class Component extends React.Component {
199+
render() {
200+
return <div />;
201+
}
202+
}
203+
Component.propTypes = somethingElse(props);
204+
`,
205+
}, {
206+
code: `
207+
import somethingElse from "something-else";
208+
const props =
209+
class Component extends React.Component {
210+
static propTypes = somethingElse({
211+
foo: PropTypes.string,
212+
bar: PropTypes.shape({
213+
baz: PropTypes.string
214+
})
215+
});
216+
render() {
217+
return <div />;
218+
}
219+
}
220+
`,
221+
parser: 'babel-eslint'
189222
}],
190223
invalid: [{
191224
code: `
@@ -291,5 +324,77 @@ ruleTester.run('prefer-exact-props', rule, {
291324
]
292325
},
293326
errors: [{message: 'Component propTypes should be exact by using one of \'exact\', \'forbidExtraProps\'.'}]
327+
}, {
328+
code: `
329+
const props = {
330+
foo: PropTypes.string,
331+
bar: PropTypes.shape({
332+
baz: PropTypes.string
333+
})
334+
};
335+
class Component extends React.Component {
336+
render() {
337+
return <div />;
338+
}
339+
}
340+
Component.propTypes = props;
341+
`,
342+
settings: {
343+
propWrapperFunctions: [
344+
{property: 'exact', exact: true},
345+
{property: 'forbidExtraProps', exact: true}
346+
]
347+
},
348+
errors: [{message: 'Component propTypes should be exact by using one of \'exact\', \'forbidExtraProps\'.'}]
349+
}, {
350+
code: `
351+
import somethingElse from "something-else";
352+
function Component({ foo, bar }) {
353+
return <div>{foo}{bar}</div>;
354+
}
355+
Component.propTypes = somethingElse({
356+
foo: PropTypes.string,
357+
bar: PropTypes.string,
358+
});
359+
`,
360+
settings: settings,
361+
errors: [{message: 'Component propTypes should be exact by using \'exact\'.'}]
362+
}, {
363+
code: `
364+
import somethingElse from "something-else";
365+
const props = {
366+
foo: PropTypes.string,
367+
bar: PropTypes.shape({
368+
baz: PropTypes.string
369+
})
370+
};
371+
class Component extends React.Component {
372+
render() {
373+
return <div />;
374+
}
375+
}
376+
Component.propTypes = somethingElse(props);
377+
`,
378+
settings: settings,
379+
errors: [{message: 'Component propTypes should be exact by using \'exact\'.'}]
380+
}, {
381+
code: `
382+
import somethingElse from "something-else";
383+
const props =
384+
class Component extends React.Component {
385+
static propTypes = somethingElse({
386+
foo: PropTypes.string,
387+
bar: PropTypes.shape({
388+
baz: PropTypes.string
389+
})
390+
});
391+
render() {
392+
return <div />;
393+
}
394+
}
395+
`,
396+
settings: settings,
397+
parser: 'babel-eslint',
398+
errors: [{message: 'Component propTypes should be exact by using \'exact\'.'}],
294399
}]
295400
});

0 commit comments

Comments
 (0)