UNPKG

7.21 kBJavaScriptView Raw
1/**
2 * @fileoverview Enforce propTypes declarations alphabetical sorting
3 */
4
5'use strict';
6
7const variableUtil = require('../util/variable');
8const propsUtil = require('../util/props');
9const docsUrl = require('../util/docsUrl');
10const propWrapperUtil = require('../util/propWrapper');
11// const propTypesSortUtil = require('../util/propTypesSort');
12
13// ------------------------------------------------------------------------------
14// Rule Definition
15// ------------------------------------------------------------------------------
16
17module.exports = {
18 meta: {
19 docs: {
20 description: 'Enforce propTypes declarations alphabetical sorting',
21 category: 'Stylistic Issues',
22 recommended: false,
23 url: docsUrl('sort-prop-types')
24 },
25
26 // fixable: 'code',
27
28 schema: [{
29 type: 'object',
30 properties: {
31 requiredFirst: {
32 type: 'boolean'
33 },
34 callbacksLast: {
35 type: 'boolean'
36 },
37 ignoreCase: {
38 type: 'boolean'
39 },
40 // Whether alphabetical sorting should be enforced
41 noSortAlphabetically: {
42 type: 'boolean'
43 },
44 sortShapeProp: {
45 type: 'boolean'
46 }
47 },
48 additionalProperties: false
49 }]
50 },
51
52 create(context) {
53 const configuration = context.options[0] || {};
54 const requiredFirst = configuration.requiredFirst || false;
55 const callbacksLast = configuration.callbacksLast || false;
56 const ignoreCase = configuration.ignoreCase || false;
57 const noSortAlphabetically = configuration.noSortAlphabetically || false;
58 const sortShapeProp = configuration.sortShapeProp || false;
59
60 function getKey(node) {
61 if (node.key && node.key.value) {
62 return node.key.value;
63 }
64 return context.getSourceCode().getText(node.key || node.argument);
65 }
66
67 function getValueName(node) {
68 return node.type === 'Property' && node.value.property && node.value.property.name;
69 }
70
71 function isCallbackPropName(propName) {
72 return /^on[A-Z]/.test(propName);
73 }
74
75 function isRequiredProp(node) {
76 return getValueName(node) === 'isRequired';
77 }
78
79 function isShapeProp(node) {
80 return Boolean(
81 node && node.callee && node.callee.property && node.callee.property.name === 'shape'
82 );
83 }
84
85 function toLowerCase(item) {
86 return String(item).toLowerCase();
87 }
88
89 /**
90 * Checks if propTypes declarations are sorted
91 * @param {Array} declarations The array of AST nodes being checked.
92 * @returns {void}
93 */
94 function checkSorted(declarations) {
95 // Declarations will be `undefined` if the `shape` is not a literal. For
96 // example, if it is a propType imported from another file.
97 if (!declarations) {
98 return;
99 }
100
101 // function fix(fixer) {
102 // return propTypesSortUtil.fixPropTypesSort(
103 // fixer,
104 // context,
105 // declarations,
106 // ignoreCase,
107 // requiredFirst,
108 // callbacksLast,
109 // sortShapeProp
110 // );
111 // }
112
113 declarations.reduce((prev, curr, idx, decls) => {
114 if (curr.type === 'ExperimentalSpreadProperty' || curr.type === 'SpreadElement') {
115 return decls[idx + 1];
116 }
117
118 let prevPropName = getKey(prev);
119 let currentPropName = getKey(curr);
120 const previousIsRequired = isRequiredProp(prev);
121 const currentIsRequired = isRequiredProp(curr);
122 const previousIsCallback = isCallbackPropName(prevPropName);
123 const currentIsCallback = isCallbackPropName(currentPropName);
124
125 if (ignoreCase) {
126 prevPropName = toLowerCase(prevPropName);
127 currentPropName = toLowerCase(currentPropName);
128 }
129
130 if (requiredFirst) {
131 if (previousIsRequired && !currentIsRequired) {
132 // Transition between required and non-required. Don't compare for alphabetical.
133 return curr;
134 }
135 if (!previousIsRequired && currentIsRequired) {
136 // Encountered a non-required prop after a required prop
137 context.report({
138 node: curr,
139 message: 'Required prop types must be listed before all other prop types'
140 // fix
141 });
142 return curr;
143 }
144 }
145
146 if (callbacksLast) {
147 if (!previousIsCallback && currentIsCallback) {
148 // Entering the callback prop section
149 return curr;
150 }
151 if (previousIsCallback && !currentIsCallback) {
152 // Encountered a non-callback prop after a callback prop
153 context.report({
154 node: prev,
155 message: 'Callback prop types must be listed after all other prop types'
156 // fix
157 });
158 return prev;
159 }
160 }
161
162 if (!noSortAlphabetically && currentPropName < prevPropName) {
163 context.report({
164 node: curr,
165 message: 'Prop types declarations should be sorted alphabetically'
166 // fix
167 });
168 return prev;
169 }
170
171 return curr;
172 }, declarations[0]);
173 }
174
175 function checkNode(node) {
176 switch (node && node.type) {
177 case 'ObjectExpression':
178 checkSorted(node.properties);
179 break;
180 case 'Identifier': {
181 const propTypesObject = variableUtil.findVariableByName(context, node.name);
182 if (propTypesObject && propTypesObject.properties) {
183 checkSorted(propTypesObject.properties);
184 }
185 break;
186 }
187 case 'CallExpression': {
188 const innerNode = node.arguments && node.arguments[0];
189 if (propWrapperUtil.isPropWrapperFunction(context, node.callee.name) && innerNode) {
190 checkNode(innerNode);
191 }
192 break;
193 }
194 default:
195 break;
196 }
197 }
198
199 return {
200 CallExpression(node) {
201 if (!sortShapeProp || !isShapeProp(node) || !(node.arguments && node.arguments[0])) {
202 return;
203 }
204
205 const firstArg = node.arguments[0];
206 if (firstArg.properties) {
207 checkSorted(firstArg.properties);
208 } else if (firstArg.type === 'Identifier') {
209 const variable = variableUtil.findVariableByName(context, firstArg.name);
210 if (variable && variable.properties) {
211 checkSorted(variable.properties);
212 }
213 }
214 },
215
216 ClassProperty(node) {
217 if (!propsUtil.isPropTypesDeclaration(node)) {
218 return;
219 }
220 checkNode(node.value);
221 },
222
223 MemberExpression(node) {
224 if (!propsUtil.isPropTypesDeclaration(node)) {
225 return;
226 }
227
228 checkNode(node.parent.right);
229 },
230
231 ObjectExpression(node) {
232 node.properties.forEach((property) => {
233 if (!property.key) {
234 return;
235 }
236
237 if (!propsUtil.isPropTypesDeclaration(property)) {
238 return;
239 }
240 if (property.value.type === 'ObjectExpression') {
241 checkSorted(property.value.properties);
242 }
243 });
244 }
245
246 };
247 }
248};