source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/jsx-no-leaked-render.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.4 KB
RevLine 
[d565449]1/**
2 * @fileoverview Prevent problematic leaked values from being rendered
3 * @author Mario Beltrán
4 */
5
6'use strict';
7
8const find = require('es-iterator-helpers/Iterator.prototype.find');
9const from = require('es-iterator-helpers/Iterator.from');
10
11const getText = require('../util/eslint').getText;
12const docsUrl = require('../util/docsUrl');
13const report = require('../util/report');
14const variableUtil = require('../util/variable');
15const testReactVersion = require('../util/version').testReactVersion;
16const isParenthesized = require('../util/ast').isParenthesized;
17
18//------------------------------------------------------------------------------
19// Rule Definition
20//------------------------------------------------------------------------------
21
22const messages = {
23 noPotentialLeakedRender: 'Potential leaked value that might cause unintentionally rendered values or rendering crashes',
24};
25
26const COERCE_STRATEGY = 'coerce';
27const TERNARY_STRATEGY = 'ternary';
28const DEFAULT_VALID_STRATEGIES = [TERNARY_STRATEGY, COERCE_STRATEGY];
29const COERCE_VALID_LEFT_SIDE_EXPRESSIONS = ['UnaryExpression', 'BinaryExpression', 'CallExpression'];
30const TERNARY_INVALID_ALTERNATE_VALUES = [undefined, null, false];
31
32function trimLeftNode(node) {
33 // Remove double unary expression (boolean coercion), so we avoid trimming valid negations
34 if (node.type === 'UnaryExpression' && node.argument.type === 'UnaryExpression') {
35 return trimLeftNode(node.argument.argument);
36 }
37
38 return node;
39}
40
41function getIsCoerceValidNestedLogicalExpression(node) {
42 if (node.type === 'LogicalExpression') {
43 return getIsCoerceValidNestedLogicalExpression(node.left) && getIsCoerceValidNestedLogicalExpression(node.right);
44 }
45
46 return COERCE_VALID_LEFT_SIDE_EXPRESSIONS.some((validExpression) => validExpression === node.type);
47}
48
49function extractExpressionBetweenLogicalAnds(node) {
50 if (node.type !== 'LogicalExpression') return [node];
51 if (node.operator !== '&&') return [node];
52 return [].concat(
53 extractExpressionBetweenLogicalAnds(node.left),
54 extractExpressionBetweenLogicalAnds(node.right)
55 );
56}
57
58function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNode) {
59 const rightSideText = getText(context, rightNode);
60
61 if (fixStrategy === COERCE_STRATEGY) {
62 const expressions = extractExpressionBetweenLogicalAnds(leftNode);
63 const newText = expressions.map((node) => {
64 let nodeText = getText(context, node);
65 if (isParenthesized(context, node)) {
66 nodeText = `(${nodeText})`;
67 }
68 if (node.parent && node.parent.type === 'ConditionalExpression' && node.parent.consequent.value === false) {
69 return `${getIsCoerceValidNestedLogicalExpression(node) ? '' : '!'}${nodeText}`;
70 }
71 return `${getIsCoerceValidNestedLogicalExpression(node) ? '' : '!!'}${nodeText}`;
72 }).join(' && ');
73
74 if (rightNode.parent && rightNode.parent.type === 'ConditionalExpression' && rightNode.parent.consequent.value === false) {
75 const consequentVal = rightNode.parent.consequent.raw || rightNode.parent.consequent.name;
76 const alternateVal = rightNode.parent.alternate.raw || rightNode.parent.alternate.name;
77 if (rightNode.parent.test && rightNode.parent.test.type === 'LogicalExpression') {
78 return fixer.replaceText(reportedNode, `${newText} ? ${consequentVal} : ${alternateVal}`);
79 }
80 return fixer.replaceText(reportedNode, `${newText} && ${alternateVal}`);
81 }
82
83 if (rightNode.type === 'ConditionalExpression' || rightNode.type === 'LogicalExpression') {
84 return fixer.replaceText(reportedNode, `${newText} && (${rightSideText})`);
85 }
86 if (rightNode.type === 'JSXElement') {
87 const rightSideTextLines = rightSideText.split('\n');
88 if (rightSideTextLines.length > 1) {
89 const rightSideTextLastLine = rightSideTextLines[rightSideTextLines.length - 1];
90 const indentSpacesStart = ' '.repeat(rightSideTextLastLine.search(/\S/));
91 const indentSpacesClose = ' '.repeat(rightSideTextLastLine.search(/\S/) - 2);
92 return fixer.replaceText(reportedNode, `${newText} && (\n${indentSpacesStart}${rightSideText}\n${indentSpacesClose})`);
93 }
94 }
95 if (rightNode.type === 'Literal') {
96 return null;
97 }
98 return fixer.replaceText(reportedNode, `${newText} && ${rightSideText}`);
99 }
100
101 if (fixStrategy === TERNARY_STRATEGY) {
102 let leftSideText = getText(context, trimLeftNode(leftNode));
103 if (isParenthesized(context, leftNode)) {
104 leftSideText = `(${leftSideText})`;
105 }
106 return fixer.replaceText(reportedNode, `${leftSideText} ? ${rightSideText} : null`);
107 }
108
109 throw new TypeError('Invalid value for "validStrategies" option');
110}
111
112/** @type {import('eslint').Rule.RuleModule} */
113module.exports = {
114 meta: {
115 docs: {
116 description: 'Disallow problematic leaked values from being rendered',
117 category: 'Possible Errors',
118 recommended: false,
119 url: docsUrl('jsx-no-leaked-render'),
120 },
121
122 messages,
123
124 fixable: 'code',
125 schema: [
126 {
127 type: 'object',
128 properties: {
129 validStrategies: {
130 type: 'array',
131 items: {
132 enum: [
133 TERNARY_STRATEGY,
134 COERCE_STRATEGY,
135 ],
136 },
137 uniqueItems: true,
138 default: DEFAULT_VALID_STRATEGIES,
139 },
140 },
141 additionalProperties: false,
142 },
143 ],
144 },
145
146 create(context) {
147 const config = context.options[0] || {};
148 const validStrategies = new Set(config.validStrategies || DEFAULT_VALID_STRATEGIES);
149 const fixStrategy = find(from(validStrategies), () => true);
150
151 return {
152 'JSXExpressionContainer > LogicalExpression[operator="&&"]'(node) {
153 const leftSide = node.left;
154
155 const isCoerceValidLeftSide = COERCE_VALID_LEFT_SIDE_EXPRESSIONS
156 .some((validExpression) => validExpression === leftSide.type);
157 if (validStrategies.has(COERCE_STRATEGY)) {
158 if (isCoerceValidLeftSide || getIsCoerceValidNestedLogicalExpression(leftSide)) {
159 return;
160 }
161 const leftSideVar = variableUtil.getVariableFromContext(context, node, leftSide.name);
162 if (leftSideVar) {
163 const leftSideValue = leftSideVar.defs
164 && leftSideVar.defs.length
165 && leftSideVar.defs[0].node.init
166 && leftSideVar.defs[0].node.init.value;
167 if (typeof leftSideValue === 'boolean') {
168 return;
169 }
170 }
171 }
172
173 if (testReactVersion(context, '>= 18') && leftSide.type === 'Literal' && leftSide.value === '') {
174 return;
175 }
176 report(context, messages.noPotentialLeakedRender, 'noPotentialLeakedRender', {
177 node,
178 fix(fixer) {
179 return ruleFixer(context, fixStrategy, fixer, node, leftSide, node.right);
180 },
181 });
182 },
183
184 'JSXExpressionContainer > ConditionalExpression'(node) {
185 if (validStrategies.has(TERNARY_STRATEGY)) {
186 return;
187 }
188
189 const isValidTernaryAlternate = TERNARY_INVALID_ALTERNATE_VALUES.indexOf(node.alternate.value) === -1;
190 const isJSXElementAlternate = node.alternate.type === 'JSXElement';
191 if (isValidTernaryAlternate || isJSXElementAlternate) {
192 return;
193 }
194
195 report(context, messages.noPotentialLeakedRender, 'noPotentialLeakedRender', {
196 node,
197 fix(fixer) {
198 return ruleFixer(context, fixStrategy, fixer, node, node.test, node.consequent);
199 },
200 });
201 },
202 };
203 },
204};
Note: See TracBrowser for help on using the repository browser.