blob: f7d907465628c8253ef3ec0c05119a68975d193b [file] [log] [blame]
/**
* @fileoverview Prevent definitions of unused prop types
* @author Evgueni Naverniouk
*/
'use strict';
// As for exceptions for props.children or props.className (and alike) look at
// https://github.com/yannickcr/eslint-plugin-react/issues/7
const Components = require('../util/Components');
const docsUrl = require('../util/docsUrl');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Prevent definitions of unused prop types',
category: 'Best Practices',
recommended: false,
url: docsUrl('no-unused-prop-types')
},
schema: [{
type: 'object',
properties: {
customValidators: {
type: 'array',
items: {
type: 'string'
}
},
skipShapeProps: {
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: Components.detect((context, components) => {
const defaults = {skipShapeProps: true, customValidators: []};
const configuration = Object.assign({}, defaults, context.options[0] || {});
const UNUSED_MESSAGE = '\'{{name}}\' PropType is defined but prop is never used';
/**
* Checks if the component must be validated
* @param {Object} component The component to process
* @returns {Boolean} True if the component must be validated, false if not.
*/
function mustBeValidated(component) {
return Boolean(
component &&
!component.ignoreUnusedPropTypesValidation
);
}
/**
* Checks if a prop is used
* @param {ASTNode} node The AST node being checked.
* @param {Object} prop Declared prop object
* @returns {Boolean} True if the prop is used, false if not.
*/
function isPropUsed(node, prop) {
const usedPropTypes = node.usedPropTypes || [];
for (let i = 0, l = usedPropTypes.length; i < l; i++) {
const usedProp = usedPropTypes[i];
if (
prop.type === 'shape' ||
prop.name === '__ANY_KEY__' ||
usedProp.name === prop.name
) {
return true;
}
}
return false;
}
/**
* Used to recursively loop through each declared prop type
* @param {Object} component The component to process
* @param {ASTNode[]|true} props List of props to validate
*/
function reportUnusedPropType(component, props) {
// Skip props that check instances
if (props === true) {
return;
}
Object.keys(props || {}).forEach((key) => {
const prop = props[key];
// Skip props that check instances
if (prop === true) {
return;
}
if (prop.type === 'shape' && configuration.skipShapeProps) {
return;
}
if (prop.node && !isPropUsed(component, prop)) {
context.report({
node: prop.node.value || prop.node,
message: UNUSED_MESSAGE,
data: {
name: prop.fullName
}
});
}
if (prop.children) {
reportUnusedPropType(component, prop.children);
}
});
}
/**
* Reports unused proptypes for a given component
* @param {Object} component The component to process
*/
function reportUnusedPropTypes(component) {
reportUnusedPropType(component, component.declaredPropTypes);
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
'Program:exit': function () {
const list = components.list();
// Report undeclared proptypes for all classes
Object.keys(list).filter(component => mustBeValidated(list[component])).forEach((component) => {
if (!mustBeValidated(list[component])) {
return;
}
reportUnusedPropTypes(list[component]);
});
}
};
})
};