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

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

  • Property mode set to 100644
File size: 9.7 KB
Line 
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 // valid-jsdoc cannot read function types
185 // eslint-disable-next-line valid-jsdoc
186 /**
187 * Find a parent that satisfy the given predicate
188 * @param {ASTNode} node
189 * @param {(node: ASTNode) => boolean} predicate
190 * @returns {ASTNode | undefined}
191 */
192 function findParent(node, predicate) {
193 let n = node;
194 while (n) {
195 if (predicate(n)) {
196 return n;
197 }
198 n = n.parent;
199 }
200 return undefined;
201 }
202
203 return {
204
205 FunctionDeclaration: handleStatelessComponent,
206
207 ArrowFunctionExpression: handleStatelessComponent,
208
209 FunctionExpression: handleStatelessComponent,
210
211 'FunctionDeclaration:exit': handleStatelessComponentExit,
212
213 'ArrowFunctionExpression:exit': handleStatelessComponentExit,
214
215 'FunctionExpression:exit': handleStatelessComponentExit,
216
217 MemberExpression(node) {
218 const SFCComponent = utils.getParentStatelessComponent(node);
219 if (SFCComponent) {
220 handleSFCUsage(node);
221 }
222
223 const classComponent = utils.getParentComponent(node);
224 if (classComponent) {
225 handleClassUsage(node);
226 }
227 },
228
229 TSQualifiedName(node) {
230 if (configuration !== 'always') {
231 return;
232 }
233 // handle `typeof props.a.b`
234 if (node.left.type === 'Identifier'
235 && node.left.name === sfcParams.propsName()
236 && findParent(node, (n) => n.type === 'TSTypeQuery')
237 && utils.getParentStatelessComponent(node)
238 ) {
239 report(context, messages.useDestructAssignment, 'useDestructAssignment', {
240 node,
241 data: {
242 type: 'props',
243 },
244 });
245 }
246 },
247
248 VariableDeclarator(node) {
249 const classComponent = utils.getParentComponent(node);
250 const SFCComponent = components.get(getScope(context, node).block);
251
252 const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern');
253 // let {foo} = props;
254 const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context');
255 // let {foo} = this.props;
256 const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && (
257 node.init.property.name === 'props' || node.init.property.name === 'context' || node.init.property.name === 'state'
258 );
259
260 if (SFCComponent && destructuringSFC && configuration === 'never') {
261 report(context, messages.noDestructAssignment, 'noDestructAssignment', {
262 node,
263 data: {
264 type: node.init.name,
265 },
266 });
267 }
268
269 if (
270 classComponent && destructuringClass && configuration === 'never'
271 && !(ignoreClassFields && (node.parent.type === 'ClassProperty' || node.parent.type === 'PropertyDefinition'))
272 ) {
273 report(context, messages.noDestructAssignment, 'noDestructAssignment', {
274 node,
275 data: {
276 type: node.init.property.name,
277 },
278 });
279 }
280
281 if (
282 SFCComponent
283 && destructuringSFC
284 && configuration === 'always'
285 && destructureInSignature === 'always'
286 && node.init.name === 'props'
287 ) {
288 const scopeSetProps = getScope(context, node).set.get('props');
289 const propsRefs = scopeSetProps && scopeSetProps.references;
290 if (!propsRefs) {
291 return;
292 }
293
294 // Skip if props is used elsewhere
295 if (propsRefs.length > 1) {
296 return;
297 }
298 report(context, messages.destructureInSignature, 'destructureInSignature', {
299 node,
300 fix(fixer) {
301 const param = SFCComponent.node.params[0];
302 if (!param) {
303 return;
304 }
305 const replaceRange = [
306 param.range[0],
307 param.typeAnnotation ? param.typeAnnotation.range[0] : param.range[1],
308 ];
309 return [
310 fixer.replaceTextRange(replaceRange, getText(context, node.id)),
311 fixer.remove(node.parent),
312 ];
313 },
314 });
315 }
316 },
317 };
318 }),
319};
Note: See TracBrowser for help on using the repository browser.