UNPKG

4.16 kBJavaScriptView Raw
1/**
2 * @fileoverview Prevent definitions of unused prop types
3 * @author Evgueni Naverniouk
4 */
5
6'use strict';
7
8// As for exceptions for props.children or props.className (and alike) look at
9// https://github.com/yannickcr/eslint-plugin-react/issues/7
10
11const Components = require('../util/Components');
12const docsUrl = require('../util/docsUrl');
13
14// ------------------------------------------------------------------------------
15// Rule Definition
16// ------------------------------------------------------------------------------
17
18module.exports = {
19 meta: {
20 docs: {
21 description: 'Prevent definitions of unused prop types',
22 category: 'Best Practices',
23 recommended: false,
24 url: docsUrl('no-unused-prop-types')
25 },
26
27 schema: [{
28 type: 'object',
29 properties: {
30 customValidators: {
31 type: 'array',
32 items: {
33 type: 'string'
34 }
35 },
36 skipShapeProps: {
37 type: 'boolean'
38 }
39 },
40 additionalProperties: false
41 }]
42 },
43
44 create: Components.detect((context, components) => {
45 const defaults = {skipShapeProps: true, customValidators: []};
46 const configuration = Object.assign({}, defaults, context.options[0] || {});
47 const UNUSED_MESSAGE = '\'{{name}}\' PropType is defined but prop is never used';
48
49 /**
50 * Checks if the component must be validated
51 * @param {Object} component The component to process
52 * @returns {Boolean} True if the component must be validated, false if not.
53 */
54 function mustBeValidated(component) {
55 return Boolean(
56 component
57 && !component.ignoreUnusedPropTypesValidation
58 );
59 }
60
61 /**
62 * Checks if a prop is used
63 * @param {ASTNode} node The AST node being checked.
64 * @param {Object} prop Declared prop object
65 * @returns {Boolean} True if the prop is used, false if not.
66 */
67 function isPropUsed(node, prop) {
68 const usedPropTypes = node.usedPropTypes || [];
69 for (let i = 0, l = usedPropTypes.length; i < l; i++) {
70 const usedProp = usedPropTypes[i];
71 if (
72 prop.type === 'shape'
73 || prop.name === '__ANY_KEY__'
74 || usedProp.name === prop.name
75 ) {
76 return true;
77 }
78 }
79
80 return false;
81 }
82
83 /**
84 * Used to recursively loop through each declared prop type
85 * @param {Object} component The component to process
86 * @param {ASTNode[]|true} props List of props to validate
87 */
88 function reportUnusedPropType(component, props) {
89 // Skip props that check instances
90 if (props === true) {
91 return;
92 }
93
94 Object.keys(props || {}).forEach((key) => {
95 const prop = props[key];
96 // Skip props that check instances
97 if (prop === true) {
98 return;
99 }
100
101 if (prop.type === 'shape' && configuration.skipShapeProps) {
102 return;
103 }
104
105 if (prop.node && !isPropUsed(component, prop)) {
106 context.report({
107 node: prop.node.key || prop.node,
108 message: UNUSED_MESSAGE,
109 data: {
110 name: prop.fullName
111 }
112 });
113 }
114
115 if (prop.children) {
116 reportUnusedPropType(component, prop.children);
117 }
118 });
119 }
120
121 /**
122 * Reports unused proptypes for a given component
123 * @param {Object} component The component to process
124 */
125 function reportUnusedPropTypes(component) {
126 reportUnusedPropType(component, component.declaredPropTypes);
127 }
128
129 // --------------------------------------------------------------------------
130 // Public
131 // --------------------------------------------------------------------------
132
133 return {
134 'Program:exit'() {
135 const list = components.list();
136 // Report undeclared proptypes for all classes
137 Object.keys(list).filter((component) => mustBeValidated(list[component])).forEach((component) => {
138 if (!mustBeValidated(list[component])) {
139 return;
140 }
141 reportUnusedPropTypes(list[component]);
142 });
143 }
144 };
145 })
146};