1 |
|
2 |
|
3 |
|
4 |
|
5 | 'use strict';
|
6 |
|
7 | const variableUtil = require('../util/variable');
|
8 | const propsUtil = require('../util/props');
|
9 | const docsUrl = require('../util/docsUrl');
|
10 | const propWrapperUtil = require('../util/propWrapper');
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | module.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 |
|
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 |
|
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 |
|
91 |
|
92 |
|
93 |
|
94 | function checkSorted(declarations) {
|
95 |
|
96 |
|
97 | if (!declarations) {
|
98 | return;
|
99 | }
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
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 |
|
133 | return curr;
|
134 | }
|
135 | if (!previousIsRequired && currentIsRequired) {
|
136 |
|
137 | context.report({
|
138 | node: curr,
|
139 | message: 'Required prop types must be listed before all other prop types'
|
140 |
|
141 | });
|
142 | return curr;
|
143 | }
|
144 | }
|
145 |
|
146 | if (callbacksLast) {
|
147 | if (!previousIsCallback && currentIsCallback) {
|
148 |
|
149 | return curr;
|
150 | }
|
151 | if (previousIsCallback && !currentIsCallback) {
|
152 |
|
153 | context.report({
|
154 | node: prev,
|
155 | message: 'Callback prop types must be listed after all other prop types'
|
156 |
|
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 |
|
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 | };
|