1 |
|
2 |
|
3 |
|
4 |
|
5 | 'use strict';
|
6 |
|
7 | const Components = require('../util/Components');
|
8 | const docsUrl = require('../util/docsUrl');
|
9 | const isAssignmentLHS = require('../util/ast').isAssignmentLHS;
|
10 |
|
11 | const DEFAULT_OPTION = 'always';
|
12 |
|
13 | module.exports = {
|
14 | meta: {
|
15 | docs: {
|
16 | description: 'Enforce consistent usage of destructuring assignment of props, state, and context',
|
17 | category: 'Stylistic Issues',
|
18 | recommended: false,
|
19 | url: docsUrl('destructuring-assignment')
|
20 | },
|
21 | schema: [{
|
22 | type: 'string',
|
23 | enum: [
|
24 | 'always',
|
25 | 'never'
|
26 | ]
|
27 | }, {
|
28 | type: 'object',
|
29 | properties: {
|
30 | ignoreClassFields: {
|
31 | type: 'boolean'
|
32 | }
|
33 | },
|
34 | additionalProperties: false
|
35 | }]
|
36 | },
|
37 |
|
38 | create: Components.detect((context, components, utils) => {
|
39 | const configuration = context.options[0] || DEFAULT_OPTION;
|
40 | const ignoreClassFields = context.options[1] && context.options[1].ignoreClassFields === true || false;
|
41 |
|
42 | |
43 |
|
44 |
|
45 |
|
46 | function handleStatelessComponent(node) {
|
47 | const destructuringProps = node.params && node.params[0] && node.params[0].type === 'ObjectPattern';
|
48 | const destructuringContext = node.params && node.params[1] && node.params[1].type === 'ObjectPattern';
|
49 |
|
50 | if (destructuringProps && components.get(node) && configuration === 'never') {
|
51 | context.report({
|
52 | node,
|
53 | message: 'Must never use destructuring props assignment in SFC argument'
|
54 | });
|
55 | } else if (destructuringContext && components.get(node) && configuration === 'never') {
|
56 | context.report({
|
57 | node,
|
58 | message: 'Must never use destructuring context assignment in SFC argument'
|
59 | });
|
60 | }
|
61 | }
|
62 |
|
63 | function handleSFCUsage(node) {
|
64 |
|
65 | const isPropUsed = (node.object.name === 'props' || node.object.name === 'context') && !isAssignmentLHS(node);
|
66 | if (isPropUsed && configuration === 'always') {
|
67 | context.report({
|
68 | node,
|
69 | message: `Must use destructuring ${node.object.name} assignment`
|
70 | });
|
71 | }
|
72 | }
|
73 |
|
74 | function isInClassProperty(node) {
|
75 | let curNode = node.parent;
|
76 | while (curNode) {
|
77 | if (curNode.type === 'ClassProperty') {
|
78 | return true;
|
79 | }
|
80 | curNode = curNode.parent;
|
81 | }
|
82 | return false;
|
83 | }
|
84 |
|
85 | function handleClassUsage(node) {
|
86 |
|
87 | const isPropUsed = (
|
88 | node.object.type === 'MemberExpression' && node.object.object.type === 'ThisExpression'
|
89 | && (node.object.property.name === 'props' || node.object.property.name === 'context' || node.object.property.name === 'state')
|
90 | && !isAssignmentLHS(node)
|
91 | );
|
92 |
|
93 | if (
|
94 | isPropUsed && configuration === 'always'
|
95 | && !(ignoreClassFields && isInClassProperty(node))
|
96 | ) {
|
97 | context.report({
|
98 | node,
|
99 | message: `Must use destructuring ${node.object.property.name} assignment`
|
100 | });
|
101 | }
|
102 | }
|
103 |
|
104 | return {
|
105 |
|
106 | FunctionDeclaration: handleStatelessComponent,
|
107 |
|
108 | ArrowFunctionExpression: handleStatelessComponent,
|
109 |
|
110 | FunctionExpression: handleStatelessComponent,
|
111 |
|
112 | MemberExpression(node) {
|
113 | const SFCComponent = components.get(context.getScope(node).block);
|
114 | const classComponent = utils.getParentComponent(node);
|
115 | if (SFCComponent) {
|
116 | handleSFCUsage(node);
|
117 | }
|
118 | if (classComponent) {
|
119 | handleClassUsage(node);
|
120 | }
|
121 | },
|
122 |
|
123 | VariableDeclarator(node) {
|
124 | const classComponent = utils.getParentComponent(node);
|
125 | const SFCComponent = components.get(context.getScope(node).block);
|
126 |
|
127 | const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern');
|
128 |
|
129 | const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context');
|
130 |
|
131 | const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && (
|
132 | node.init.property.name === 'props' || node.init.property.name === 'context' || node.init.property.name === 'state'
|
133 | );
|
134 |
|
135 | if (SFCComponent && destructuringSFC && configuration === 'never') {
|
136 | context.report({
|
137 | node,
|
138 | message: `Must never use destructuring ${node.init.name} assignment`
|
139 | });
|
140 | }
|
141 |
|
142 | if (
|
143 | classComponent && destructuringClass && configuration === 'never'
|
144 | && !(ignoreClassFields && node.parent.type === 'ClassProperty')
|
145 | ) {
|
146 | context.report({
|
147 | node,
|
148 | message: `Must never use destructuring ${node.init.property.name} assignment`
|
149 | });
|
150 | }
|
151 | }
|
152 | };
|
153 | })
|
154 | };
|