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