2 * @fileoverview Prevent missing props validation in a React component definition
3 * @author Yannick Croissant
7 // As for exceptions for props.children or props.className (and alike) look at
8 // https://github.com/yannickcr/eslint-plugin-react/issues/7
10 var componentUtil = require('../util/component');
11 var ComponentList = componentUtil.List;
13 // ------------------------------------------------------------------------------
15 // ------------------------------------------------------------------------------
17 module.exports = function(context) {
19 var configuration = context.options[0] || {};
20 var ignored = configuration.ignore || [];
22 var componentList = new ComponentList();
24 var MISSING_MESSAGE = '\'{{name}}\' is missing in props validation';
25 var MISSING_MESSAGE_NAMED_COMP = '\'{{name}}\' is missing in props validation for {{component}}';
28 * Checks if we are using a prop
29 * @param {ASTNode} node The AST node being checked.
30 * @returns {Boolean} True if we are using a prop, false if not.
32 function isPropTypesUsage(node) {
34 node.object.type === 'ThisExpression' &&
35 node.property.name === 'props'
40 * Checks if we are declaring a prop
41 * @param {ASTNode} node The AST node being checked.
42 * @returns {Boolean} True if we are declaring a prop, false if not.
44 function isPropTypesDeclaration(node) {
46 // Special case for class properties
47 // (babel-eslint does not expose property name so we have to rely on tokens)
48 if (node.type === 'ClassProperty') {
49 var tokens = context.getFirstTokens(node, 2);
50 if (tokens[0].value === 'propTypes' || tokens[1].value === 'propTypes') {
58 node.name === 'propTypes'
64 * Checks if the prop is ignored
65 * @param {String} name Name of the prop to check.
66 * @returns {Boolean} True if the prop is ignored, false if not.
68 function isIgnored(name) {
69 return ignored.indexOf(name) !== -1;
73 * Checks if the component must be validated
74 * @param {Object} component The component to process
75 * @returns {Boolean} True if the component must be validated, false if not.
77 function mustBeValidated(component) {
80 component.isReactComponent &&
81 component.usedPropTypes &&
82 !component.ignorePropsValidation
87 * Checks if the prop is declared
88 * @param {String} name Name of the prop to check.
89 * @param {Object} component The component to process
90 * @returns {Boolean} True if the prop is declared, false if not.
92 function isDeclaredInComponent(component, name) {
94 component.declaredPropTypes &&
95 component.declaredPropTypes.indexOf(name) !== -1
100 * Checks if the prop has spread operator.
101 * @param {ASTNode} node The AST node being marked.
102 * @returns {Boolean} True if the prop has spread operator, false if not.
104 function hasSpreadOperator(node) {
105 var tokens = context.getTokens(node);
106 return tokens.length && tokens[0].value === '...';
110 * Mark a prop type as used
111 * @param {ASTNode} node The AST node being marked.
113 function markPropTypesAsUsed(node) {
114 var component = componentList.getByNode(context, node);
115 var usedPropTypes = component && component.usedPropTypes || [];
117 if (node.parent.property && node.parent.property.name && !node.parent.computed) {
120 node.parent.parent.declarations &&
121 node.parent.parent.declarations[0].id.properties &&
122 node.parent.parent.declarations[0].id.properties[0].key.name
124 type = 'destructuring';
130 name: node.parent.property.name,
134 case 'destructuring':
135 var properties = node.parent.parent.declarations[0].id.properties;
136 for (var i = 0, j = properties.length; i < j; i++) {
137 if (hasSpreadOperator(properties[i])) {
141 name: properties[i].key.name,
150 componentList.set(context, node, {
151 usedPropTypes: usedPropTypes
156 * Mark a prop type as declared
157 * @param {ASTNode} node The AST node being checked.
158 * @param {propTypes} node The AST node containing the proptypes
160 function markPropTypesAsDeclared(node, propTypes) {
161 var component = componentList.getByNode(context, node);
162 var declaredPropTypes = component && component.declaredPropTypes || [];
163 var ignorePropsValidation = false;
165 switch (propTypes.type) {
166 case 'ObjectExpression':
167 for (var i = 0, j = propTypes.properties.length; i < j; i++) {
168 declaredPropTypes.push(propTypes.properties[i].key.name);
171 case 'MemberExpression':
172 declaredPropTypes.push(propTypes.property.name);
175 ignorePropsValidation = true;
179 componentList.set(context, node, {
180 declaredPropTypes: declaredPropTypes,
181 ignorePropsValidation: ignorePropsValidation
187 * Reports undeclared proptypes for a given component
188 * @param {Object} component The component to process
190 function reportUndeclaredPropTypes(component) {
192 for (var i = 0, j = component.usedPropTypes.length; i < j; i++) {
193 name = component.usedPropTypes[i].name;
194 if (isDeclaredInComponent(component, name) || isIgnored(name)) {
198 component.usedPropTypes[i].node,
199 component.name === componentUtil.DEFAULT_COMPONENT_NAME ? MISSING_MESSAGE : MISSING_MESSAGE_NAMED_COMP, {
201 component: component.name
207 // --------------------------------------------------------------------------
209 // --------------------------------------------------------------------------
213 ClassProperty: function(node) {
214 if (!isPropTypesDeclaration(node)) {
218 markPropTypesAsDeclared(node, node.value);
221 MemberExpression: function(node) {
223 if (isPropTypesUsage(node)) {
225 } else if (isPropTypesDeclaration(node.property)) {
226 type = 'declaration';
231 markPropTypesAsUsed(node);
234 var component = componentList.getByName(node.object.name);
238 markPropTypesAsDeclared(component.node, node.parent.right || node.parent);
245 MethodDefinition: function(node) {
246 if (!isPropTypesDeclaration(node.key)) {
250 var i = node.value.body.body.length - 1;
251 for (; i >= 0; i--) {
252 if (node.value.body.body[i].type === 'ReturnStatement') {
257 markPropTypesAsDeclared(node, node.value.body.body[i].argument);
260 ObjectExpression: function(node) {
261 // Search for the displayName declaration
262 node.properties.forEach(function(property) {
263 if (!isPropTypesDeclaration(property.key)) {
266 markPropTypesAsDeclared(node, property.value);
270 'Program:exit': function() {
271 var list = componentList.getList();
272 // Report undeclared proptypes for all classes
273 for (var component in list) {
274 if (!list.hasOwnProperty(component) || !mustBeValidated(list[component])) {
277 reportUndeclaredPropTypes(list[component]);
281 ReturnStatement: function(node) {
282 if (!componentUtil.isReactComponent(context, node)) {
285 componentList.set(context, node, {
286 isReactComponent: true