UNPKG

5.25 kBJavaScriptView Raw
1/**
2 * @fileoverview Enforce default props alphabetical sorting
3 * @author Vladimir Kattsov
4 */
5
6'use strict';
7
8const variableUtil = require('../util/variable');
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 default props alphabetical sorting',
21 category: 'Stylistic Issues',
22 recommended: false,
23 url: docsUrl('jsx-sort-default-props')
24 },
25
26 // fixable: 'code',
27
28 schema: [{
29 type: 'object',
30 properties: {
31 ignoreCase: {
32 type: 'boolean'
33 }
34 },
35 additionalProperties: false
36 }]
37 },
38
39 create(context) {
40 const configuration = context.options[0] || {};
41 const ignoreCase = configuration.ignoreCase || false;
42
43 /**
44 * Get properties name
45 * @param {Object} node - Property.
46 * @returns {String} Property name.
47 */
48 function getPropertyName(node) {
49 if (node.key || ['MethodDefinition', 'Property'].indexOf(node.type) !== -1) {
50 return node.key.name;
51 }
52 if (node.type === 'MemberExpression') {
53 return node.property.name;
54 // Special case for class properties
55 // (babel-eslint@5 does not expose property name so we have to rely on tokens)
56 }
57 if (node.type === 'ClassProperty') {
58 const tokens = context.getFirstTokens(node, 2);
59 return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
60 }
61 return '';
62 }
63
64 /**
65 * Checks if the Identifier node passed in looks like a defaultProps declaration.
66 * @param {ASTNode} node The node to check. Must be an Identifier node.
67 * @returns {Boolean} `true` if the node is a defaultProps declaration, `false` if not
68 */
69 function isDefaultPropsDeclaration(node) {
70 const propName = getPropertyName(node);
71 return (propName === 'defaultProps' || propName === 'getDefaultProps');
72 }
73
74 function getKey(node) {
75 return context.getSourceCode().getText(node.key || node.argument);
76 }
77
78 /**
79 * Find a variable by name in the current scope.
80 * @param {string} name Name of the variable to look for.
81 * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise.
82 */
83 function findVariableByName(name) {
84 const variable = variableUtil.variablesInScope(context).find((item) => item.name === name);
85
86 if (!variable || !variable.defs[0] || !variable.defs[0].node) {
87 return null;
88 }
89
90 if (variable.defs[0].node.type === 'TypeAlias') {
91 return variable.defs[0].node.right;
92 }
93
94 return variable.defs[0].node.init;
95 }
96
97 /**
98 * Checks if defaultProps declarations are sorted
99 * @param {Array} declarations The array of AST nodes being checked.
100 * @returns {void}
101 */
102 function checkSorted(declarations) {
103 // function fix(fixer) {
104 // return propTypesSortUtil.fixPropTypesSort(fixer, context, declarations, ignoreCase);
105 // }
106
107 declarations.reduce((prev, curr, idx, decls) => {
108 if (/Spread(?:Property|Element)$/.test(curr.type)) {
109 return decls[idx + 1];
110 }
111
112 let prevPropName = getKey(prev);
113 let currentPropName = getKey(curr);
114
115 if (ignoreCase) {
116 prevPropName = prevPropName.toLowerCase();
117 currentPropName = currentPropName.toLowerCase();
118 }
119
120 if (currentPropName < prevPropName) {
121 context.report({
122 node: curr,
123 message: 'Default prop types declarations should be sorted alphabetically'
124 // fix
125 });
126
127 return prev;
128 }
129
130 return curr;
131 }, declarations[0]);
132 }
133
134 function checkNode(node) {
135 switch (node && node.type) {
136 case 'ObjectExpression':
137 checkSorted(node.properties);
138 break;
139 case 'Identifier': {
140 const propTypesObject = findVariableByName(node.name);
141 if (propTypesObject && propTypesObject.properties) {
142 checkSorted(propTypesObject.properties);
143 }
144 break;
145 }
146 case 'CallExpression': {
147 const innerNode = node.arguments && node.arguments[0];
148 if (propWrapperUtil.isPropWrapperFunction(context, node.callee.name) && innerNode) {
149 checkNode(innerNode);
150 }
151 break;
152 }
153 default:
154 break;
155 }
156 }
157
158 // --------------------------------------------------------------------------
159 // Public API
160 // --------------------------------------------------------------------------
161
162 return {
163 ClassProperty(node) {
164 if (!isDefaultPropsDeclaration(node)) {
165 return;
166 }
167
168 checkNode(node.value);
169 },
170
171 MemberExpression(node) {
172 if (!isDefaultPropsDeclaration(node)) {
173 return;
174 }
175
176 checkNode(node.parent.right);
177 }
178 };
179 }
180};