source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/destructuring-assignment.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 8.8 KB
RevLine 
[d565449]1/**
2 * @fileoverview Enforce consistent usage of destructuring assignment of props, state, and context.
3 */
4
5'use strict';
6
7const Components = require('../util/Components');
8const docsUrl = require('../util/docsUrl');
9const eslintUtil = require('../util/eslint');
10const isAssignmentLHS = require('../util/ast').isAssignmentLHS;
11const report = require('../util/report');
12
13const getScope = eslintUtil.getScope;
14const getText = eslintUtil.getText;
15
16const DEFAULT_OPTION = 'always';
17
18function createSFCParams() {
19 const queue = [];
20
21 return {
22 push(params) {
23 queue.unshift(params);
24 },
25 pop() {
26 queue.shift();
27 },
28 propsName() {
29 const found = queue.find((params) => {
30 const props = params[0];
31 return props && !props.destructuring && props.name;
32 });
33 return found && found[0] && found[0].name;
34 },
35 contextName() {
36 const found = queue.find((params) => {
37 const context = params[1];
38 return context && !context.destructuring && context.name;
39 });
40 return found && found[1] && found[1].name;
41 },
42 };
43}
44
45function evalParams(params) {
46 return params.map((param) => ({
47 destructuring: param.type === 'ObjectPattern',
48 name: param.type === 'Identifier' && param.name,
49 }));
50}
51
52const messages = {
53 noDestructPropsInSFCArg: 'Must never use destructuring props assignment in SFC argument',
54 noDestructContextInSFCArg: 'Must never use destructuring context assignment in SFC argument',
55 noDestructAssignment: 'Must never use destructuring {{type}} assignment',
56 useDestructAssignment: 'Must use destructuring {{type}} assignment',
57 destructureInSignature: 'Must destructure props in the function signature.',
58};
59
60/** @type {import('eslint').Rule.RuleModule} */
61module.exports = {
62 meta: {
63 docs: {
64 description: 'Enforce consistent usage of destructuring assignment of props, state, and context',
65 category: 'Stylistic Issues',
66 recommended: false,
67 url: docsUrl('destructuring-assignment'),
68 },
69 fixable: 'code',
70 messages,
71
72 schema: [{
73 type: 'string',
74 enum: [
75 'always',
76 'never',
77 ],
78 }, {
79 type: 'object',
80 properties: {
81 ignoreClassFields: {
82 type: 'boolean',
83 },
84 destructureInSignature: {
85 type: 'string',
86 enum: [
87 'always',
88 'ignore',
89 ],
90 },
91 },
92 additionalProperties: false,
93 }],
94 },
95
96 create: Components.detect((context, components, utils) => {
97 const configuration = context.options[0] || DEFAULT_OPTION;
98 const ignoreClassFields = (context.options[1] && (context.options[1].ignoreClassFields === true)) || false;
99 const destructureInSignature = (context.options[1] && context.options[1].destructureInSignature) || 'ignore';
100 const sfcParams = createSFCParams();
101
102 /**
103 * @param {ASTNode} node We expect either an ArrowFunctionExpression,
104 * FunctionDeclaration, or FunctionExpression
105 */
106 function handleStatelessComponent(node) {
107 const params = evalParams(node.params);
108
109 const SFCComponent = components.get(getScope(context, node).block);
110 if (!SFCComponent) {
111 return;
112 }
113 sfcParams.push(params);
114
115 if (params[0] && params[0].destructuring && components.get(node) && configuration === 'never') {
116 report(context, messages.noDestructPropsInSFCArg, 'noDestructPropsInSFCArg', {
117 node,
118 });
119 } else if (params[1] && params[1].destructuring && components.get(node) && configuration === 'never') {
120 report(context, messages.noDestructContextInSFCArg, 'noDestructContextInSFCArg', {
121 node,
122 });
123 }
124 }
125
126 function handleStatelessComponentExit(node) {
127 const SFCComponent = components.get(getScope(context, node).block);
128 if (SFCComponent) {
129 sfcParams.pop();
130 }
131 }
132
133 function handleSFCUsage(node) {
134 const propsName = sfcParams.propsName();
135 const contextName = sfcParams.contextName();
136 // props.aProp || context.aProp
137 const isPropUsed = (
138 (propsName && node.object.name === propsName)
139 || (contextName && node.object.name === contextName)
140 )
141 && !isAssignmentLHS(node);
142 if (isPropUsed && configuration === 'always' && !node.optional) {
143 report(context, messages.useDestructAssignment, 'useDestructAssignment', {
144 node,
145 data: {
146 type: node.object.name,
147 },
148 });
149 }
150 }
151
152 function isInClassProperty(node) {
153 let curNode = node.parent;
154 while (curNode) {
155 if (curNode.type === 'ClassProperty' || curNode.type === 'PropertyDefinition') {
156 return true;
157 }
158 curNode = curNode.parent;
159 }
160 return false;
161 }
162
163 function handleClassUsage(node) {
164 // this.props.Aprop || this.context.aProp || this.state.aState
165 const isPropUsed = (
166 node.object.type === 'MemberExpression' && node.object.object.type === 'ThisExpression'
167 && (node.object.property.name === 'props' || node.object.property.name === 'context' || node.object.property.name === 'state')
168 && !isAssignmentLHS(node)
169 );
170
171 if (
172 isPropUsed && configuration === 'always'
173 && !(ignoreClassFields && isInClassProperty(node))
174 ) {
175 report(context, messages.useDestructAssignment, 'useDestructAssignment', {
176 node,
177 data: {
178 type: node.object.property.name,
179 },
180 });
181 }
182 }
183
184 return {
185
186 FunctionDeclaration: handleStatelessComponent,
187
188 ArrowFunctionExpression: handleStatelessComponent,
189
190 FunctionExpression: handleStatelessComponent,
191
192 'FunctionDeclaration:exit': handleStatelessComponentExit,
193
194 'ArrowFunctionExpression:exit': handleStatelessComponentExit,
195
196 'FunctionExpression:exit': handleStatelessComponentExit,
197
198 MemberExpression(node) {
199 let scope = getScope(context, node);
200 let SFCComponent = components.get(scope.block);
201 while (!SFCComponent && scope.upper && scope.upper !== scope) {
202 SFCComponent = components.get(scope.upper.block);
203 scope = scope.upper;
204 }
205 if (SFCComponent) {
206 handleSFCUsage(node);
207 }
208
209 const classComponent = utils.getParentComponent(node);
210 if (classComponent) {
211 handleClassUsage(node);
212 }
213 },
214
215 VariableDeclarator(node) {
216 const classComponent = utils.getParentComponent(node);
217 const SFCComponent = components.get(getScope(context, node).block);
218
219 const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern');
220 // let {foo} = props;
221 const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context');
222 // let {foo} = this.props;
223 const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && (
224 node.init.property.name === 'props' || node.init.property.name === 'context' || node.init.property.name === 'state'
225 );
226
227 if (SFCComponent && destructuringSFC && configuration === 'never') {
228 report(context, messages.noDestructAssignment, 'noDestructAssignment', {
229 node,
230 data: {
231 type: node.init.name,
232 },
233 });
234 }
235
236 if (
237 classComponent && destructuringClass && configuration === 'never'
238 && !(ignoreClassFields && (node.parent.type === 'ClassProperty' || node.parent.type === 'PropertyDefinition'))
239 ) {
240 report(context, messages.noDestructAssignment, 'noDestructAssignment', {
241 node,
242 data: {
243 type: node.init.property.name,
244 },
245 });
246 }
247
248 if (
249 SFCComponent
250 && destructuringSFC
251 && configuration === 'always'
252 && destructureInSignature === 'always'
253 && node.init.name === 'props'
254 ) {
255 const scopeSetProps = getScope(context, node).set.get('props');
256 const propsRefs = scopeSetProps && scopeSetProps.references;
257 if (!propsRefs) {
258 return;
259 }
260 // Skip if props is used elsewhere
261 if (propsRefs.length > 1) {
262 return;
263 }
264 report(context, messages.destructureInSignature, 'destructureInSignature', {
265 node,
266 fix(fixer) {
267 const param = SFCComponent.node.params[0];
268 if (!param) {
269 return;
270 }
271 const replaceRange = [
272 param.range[0],
273 param.typeAnnotation ? param.typeAnnotation.range[0] : param.range[1],
274 ];
275 return [
276 fixer.replaceTextRange(replaceRange, getText(context, node.id)),
277 fixer.remove(node.parent),
278 ];
279 },
280 });
281 }
282 },
283 };
284 }),
285};
Note: See TracBrowser for help on using the repository browser.