source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/hook-use-state.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: 7.1 KB
Line 
1/**
2 * @fileoverview Ensure symmetric naming of useState hook value and setter variables
3 * @author Duncan Beevers
4 */
5
6'use strict';
7
8const Components = require('../util/Components');
9const docsUrl = require('../util/docsUrl');
10const report = require('../util/report');
11const getMessageData = require('../util/message');
12const getText = require('../util/eslint').getText;
13
14// ------------------------------------------------------------------------------
15// Rule Definition
16// ------------------------------------------------------------------------------
17
18function isNodeDestructuring(node) {
19 return node && (node.type === 'ArrayPattern' || node.type === 'ObjectPattern');
20}
21
22const messages = {
23 useStateErrorMessage: 'useState call is not destructured into value + setter pair',
24 useStateErrorMessageOrAddOption: 'useState call is not destructured into value + setter pair (you can allow destructuring by enabling "allowDestructuredState" option)',
25 suggestPair: 'Destructure useState call into value + setter pair',
26 suggestMemo: 'Replace useState call with useMemo',
27};
28
29module.exports = {
30 meta: {
31 docs: {
32 description: 'Ensure destructuring and symmetric naming of useState hook value and setter variables',
33 category: 'Best Practices',
34 recommended: false,
35 url: docsUrl('hook-use-state'),
36 },
37 messages,
38 schema: [{
39 type: 'object',
40 properties: {
41 allowDestructuredState: {
42 default: false,
43 type: 'boolean',
44 },
45 },
46 additionalProperties: false,
47 }],
48 type: 'suggestion',
49 hasSuggestions: true,
50 },
51
52 create: Components.detect((context, components, util) => {
53 const configuration = context.options[0] || {};
54 const allowDestructuredState = configuration.allowDestructuredState || false;
55
56 return {
57 CallExpression(node) {
58 const isImmediateReturn = node.parent
59 && node.parent.type === 'ReturnStatement';
60
61 if (isImmediateReturn || !util.isReactHookCall(node, ['useState'])) {
62 return;
63 }
64
65 const isDestructuringDeclarator = node.parent
66 && node.parent.type === 'VariableDeclarator'
67 && node.parent.id.type === 'ArrayPattern';
68
69 if (!isDestructuringDeclarator) {
70 report(
71 context,
72 messages.useStateErrorMessage,
73 'useStateErrorMessage',
74 {
75 node,
76 suggest: false,
77 }
78 );
79 return;
80 }
81
82 const variableNodes = node.parent.id.elements;
83 const valueVariable = variableNodes[0];
84 const setterVariable = variableNodes[1];
85 const isOnlyValueDestructuring = isNodeDestructuring(valueVariable) && !isNodeDestructuring(setterVariable);
86
87 if (allowDestructuredState && isOnlyValueDestructuring) {
88 return;
89 }
90
91 const valueVariableName = valueVariable
92 ? valueVariable.name
93 : undefined;
94
95 const setterVariableName = setterVariable
96 ? setterVariable.name
97 : undefined;
98
99 const caseCandidateMatch = valueVariableName ? valueVariableName.match(/(^[a-z]+)(.*)/) : undefined;
100 const upperCaseCandidatePrefix = caseCandidateMatch ? caseCandidateMatch[1] : undefined;
101 const caseCandidateSuffix = caseCandidateMatch ? caseCandidateMatch[2] : undefined;
102 const expectedSetterVariableNames = upperCaseCandidatePrefix ? [
103 `set${upperCaseCandidatePrefix.charAt(0).toUpperCase()}${upperCaseCandidatePrefix.slice(1)}${caseCandidateSuffix}`,
104 `set${upperCaseCandidatePrefix.toUpperCase()}${caseCandidateSuffix}`,
105 ] : [];
106
107 const isSymmetricGetterSetterPair = valueVariable
108 && setterVariable
109 && expectedSetterVariableNames.indexOf(setterVariableName) !== -1
110 && variableNodes.length === 2;
111
112 if (!isSymmetricGetterSetterPair) {
113 const suggestions = [
114 Object.assign(
115 getMessageData('suggestPair', messages.suggestPair),
116 {
117 fix(fixer) {
118 if (expectedSetterVariableNames.length > 0) {
119 return fixer.replaceTextRange(
120 node.parent.id.range,
121 `[${valueVariableName}, ${expectedSetterVariableNames[0]}]`
122 );
123 }
124 },
125 }
126 ),
127 ];
128
129 const defaultReactImports = components.getDefaultReactImports();
130 const defaultReactImportSpecifier = defaultReactImports
131 ? defaultReactImports[0]
132 : undefined;
133
134 const defaultReactImportName = defaultReactImportSpecifier
135 ? defaultReactImportSpecifier.local.name
136 : undefined;
137
138 const namedReactImports = components.getNamedReactImports();
139 const useStateReactImportSpecifier = namedReactImports
140 ? namedReactImports.find((specifier) => specifier.imported.name === 'useState')
141 : undefined;
142
143 const isSingleGetter = valueVariable && variableNodes.length === 1;
144 const isUseStateCalledWithSingleArgument = node.arguments.length === 1;
145 if (isSingleGetter && isUseStateCalledWithSingleArgument) {
146 const useMemoReactImportSpecifier = namedReactImports
147 && namedReactImports.find((specifier) => specifier.imported.name === 'useMemo');
148
149 let useMemoCode;
150 if (useMemoReactImportSpecifier) {
151 useMemoCode = useMemoReactImportSpecifier.local.name;
152 } else if (defaultReactImportName) {
153 useMemoCode = `${defaultReactImportName}.useMemo`;
154 } else {
155 useMemoCode = 'useMemo';
156 }
157
158 suggestions.unshift(Object.assign(
159 getMessageData('suggestMemo', messages.suggestMemo),
160 {
161 fix: (fixer) => [
162 // Add useMemo import, if necessary
163 useStateReactImportSpecifier
164 && (!useMemoReactImportSpecifier || defaultReactImportName)
165 && fixer.insertTextAfter(useStateReactImportSpecifier, ', useMemo'),
166 // Convert single-value destructure to simple assignment
167 fixer.replaceTextRange(node.parent.id.range, valueVariableName),
168 // Convert useState call to useMemo + arrow function + dependency array
169 fixer.replaceTextRange(
170 node.range,
171 `${useMemoCode}(() => ${getText(context, node.arguments[0])}, [])`
172 ),
173 ].filter(Boolean),
174 }
175 ));
176 }
177
178 if (isOnlyValueDestructuring) {
179 report(
180 context,
181 messages.useStateErrorMessageOrAddOption,
182 'useStateErrorMessageOrAddOption',
183 {
184 node: node.parent.id,
185 suggest: false,
186 }
187 );
188 return;
189 }
190
191 report(
192 context,
193 messages.useStateErrorMessage,
194 'useStateErrorMessage',
195 {
196 node: node.parent.id,
197 suggest: suggestions,
198 }
199 );
200 }
201 },
202 };
203 }),
204};
Note: See TracBrowser for help on using the repository browser.