source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-constructed-context-values.js@ d565449

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

Update repo after prototype presentation

  • Property mode set to 100644
File size: 7.4 KB
RevLine 
[d565449]1/**
2 * @fileoverview Prevents jsx context provider values from taking values that
3 * will cause needless rerenders.
4 * @author Dylan Oshima
5 */
6
7'use strict';
8
9const Components = require('../util/Components');
10const docsUrl = require('../util/docsUrl');
11const getScope = require('../util/eslint').getScope;
12const report = require('../util/report');
13
14// ------------------------------------------------------------------------------
15// Helpers
16// ------------------------------------------------------------------------------
17
18// Recursively checks if an element is a construction.
19// A construction is a variable that changes identity every render.
20function isConstruction(node, callScope) {
21 switch (node.type) {
22 case 'Literal':
23 if (node.regex != null) {
24 return { type: 'regular expression', node };
25 }
26 return null;
27 case 'Identifier': {
28 const variableScoping = callScope.set.get(node.name);
29
30 if (variableScoping == null || variableScoping.defs == null) {
31 // If it's not in scope, we don't care.
32 return null; // Handled
33 }
34
35 // Gets the last variable identity
36 const variableDefs = variableScoping.defs;
37 const def = variableDefs[variableDefs.length - 1];
38 if (def != null
39 && def.type !== 'Variable'
40 && def.type !== 'FunctionName'
41 ) {
42 // Parameter or an unusual pattern. Bail out.
43 return null; // Unhandled
44 }
45
46 if (def.node.type === 'FunctionDeclaration') {
47 return { type: 'function declaration', node: def.node, usage: node };
48 }
49
50 const init = def.node.init;
51 if (init == null) {
52 return null;
53 }
54
55 const initConstruction = isConstruction(init, callScope);
56 if (initConstruction == null) {
57 return null;
58 }
59
60 return {
61 type: initConstruction.type,
62 node: initConstruction.node,
63 usage: node,
64 };
65 }
66 case 'ObjectExpression':
67 // Any object initialized inline will create a new identity
68 return { type: 'object', node };
69 case 'ArrayExpression':
70 return { type: 'array', node };
71 case 'ArrowFunctionExpression':
72 case 'FunctionExpression':
73 // Functions that are initialized inline will have a new identity
74 return { type: 'function expression', node };
75 case 'ClassExpression':
76 return { type: 'class expression', node };
77 case 'NewExpression':
78 // `const a = new SomeClass();` is a construction
79 return { type: 'new expression', node };
80 case 'ConditionalExpression':
81 return (isConstruction(node.consequent, callScope)
82 || isConstruction(node.alternate, callScope)
83 );
84 case 'LogicalExpression':
85 return (isConstruction(node.left, callScope)
86 || isConstruction(node.right, callScope)
87 );
88 case 'MemberExpression': {
89 const objConstruction = isConstruction(node.object, callScope);
90 if (objConstruction == null) {
91 return null;
92 }
93 return {
94 type: objConstruction.type,
95 node: objConstruction.node,
96 usage: node.object,
97 };
98 }
99 case 'JSXFragment':
100 return { type: 'JSX fragment', node };
101 case 'JSXElement':
102 return { type: 'JSX element', node };
103 case 'AssignmentExpression': {
104 const construct = isConstruction(node.right, callScope);
105 if (construct != null) {
106 return {
107 type: 'assignment expression',
108 node: construct.node,
109 usage: node,
110 };
111 }
112 return null;
113 }
114 case 'TypeCastExpression':
115 case 'TSAsExpression':
116 return isConstruction(node.expression, callScope);
117 default:
118 return null;
119 }
120}
121
122// ------------------------------------------------------------------------------
123// Rule Definition
124// ------------------------------------------------------------------------------
125
126const messages = {
127 withIdentifierMsg: "The '{{variableName}}' {{type}} (at line {{nodeLine}}) passed as the value prop to the Context provider (at line {{usageLine}}) changes every render. To fix this consider wrapping it in a useMemo hook.",
128 withIdentifierMsgFunc: "The '{{variableName}}' {{type}} (at line {{nodeLine}}) passed as the value prop to the Context provider (at line {{usageLine}}) changes every render. To fix this consider wrapping it in a useCallback hook.",
129 defaultMsg: 'The {{type}} passed as the value prop to the Context provider (at line {{nodeLine}}) changes every render. To fix this consider wrapping it in a useMemo hook.',
130 defaultMsgFunc: 'The {{type}} passed as the value prop to the Context provider (at line {{nodeLine}}) changes every render. To fix this consider wrapping it in a useCallback hook.',
131};
132
133/** @type {import('eslint').Rule.RuleModule} */
134module.exports = {
135 meta: {
136 docs: {
137 description: 'Disallows JSX context provider values from taking values that will cause needless rerenders',
138 category: 'Best Practices',
139 recommended: false,
140 url: docsUrl('jsx-no-constructed-context-values'),
141 },
142 messages,
143 schema: false,
144 },
145
146 // eslint-disable-next-line arrow-body-style
147 create: Components.detect((context, components, utils) => {
148 return {
149 JSXOpeningElement(node) {
150 const openingElementName = node.name;
151 if (openingElementName.type !== 'JSXMemberExpression') {
152 // Has no member
153 return;
154 }
155
156 const isJsxContext = openingElementName.property.name === 'Provider';
157 if (!isJsxContext) {
158 // Member is not Provider
159 return;
160 }
161
162 // Contexts can take in more than just a value prop
163 // so we need to iterate through all of them
164 const jsxValueAttribute = node.attributes.find(
165 (attribute) => attribute.type === 'JSXAttribute' && attribute.name.name === 'value'
166 );
167
168 if (jsxValueAttribute == null) {
169 // No value prop was passed
170 return;
171 }
172
173 const valueNode = jsxValueAttribute.value;
174 if (!valueNode) {
175 // attribute is a boolean shorthand
176 return;
177 }
178 if (valueNode.type !== 'JSXExpressionContainer') {
179 // value could be a literal
180 return;
181 }
182
183 const valueExpression = valueNode.expression;
184 const invocationScope = getScope(context, node);
185
186 // Check if the value prop is a construction
187 const constructInfo = isConstruction(valueExpression, invocationScope);
188 if (constructInfo == null) {
189 return;
190 }
191
192 if (!utils.getParentComponent(node)) {
193 return;
194 }
195
196 // Report found error
197 const constructType = constructInfo.type;
198 const constructNode = constructInfo.node;
199 const constructUsage = constructInfo.usage;
200 const data = {
201 type: constructType, nodeLine: constructNode.loc.start.line,
202 };
203 let messageId = 'defaultMsg';
204
205 // Variable passed to value prop
206 if (constructUsage != null) {
207 messageId = 'withIdentifierMsg';
208 data.usageLine = constructUsage.loc.start.line;
209 data.variableName = constructUsage.name;
210 }
211
212 // Type of expression
213 if (
214 constructType === 'function expression'
215 || constructType === 'function declaration'
216 ) {
217 messageId += 'Func';
218 }
219
220 report(context, messages[messageId], messageId, {
221 node: constructNode,
222 data,
223 });
224 },
225 };
226 }),
227};
Note: See TracBrowser for help on using the repository browser.