source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/jsx-curly-brace-presence.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 100755
File size: 13.6 KB
RevLine 
[d565449]1/**
2 * @fileoverview Enforce curly braces or disallow unnecessary curly brace in JSX
3 * @author Jacky Ho
4 * @author Simon Lydell
5 */
6
7'use strict';
8
9const arrayIncludes = require('array-includes');
10
11const docsUrl = require('../util/docsUrl');
12const jsxUtil = require('../util/jsx');
13const report = require('../util/report');
14const eslintUtil = require('../util/eslint');
15
16const getSourceCode = eslintUtil.getSourceCode;
17const getText = eslintUtil.getText;
18
19// ------------------------------------------------------------------------------
20// Constants
21// ------------------------------------------------------------------------------
22
23const OPTION_ALWAYS = 'always';
24const OPTION_NEVER = 'never';
25const OPTION_IGNORE = 'ignore';
26
27const OPTION_VALUES = [
28 OPTION_ALWAYS,
29 OPTION_NEVER,
30 OPTION_IGNORE,
31];
32const DEFAULT_CONFIG = { props: OPTION_NEVER, children: OPTION_NEVER, propElementValues: OPTION_IGNORE };
33
[0c6b92a]34const HTML_ENTITY_REGEX = () => /&[A-Za-z\d#]+;/g;
35
36function containsLineTerminators(rawStringValue) {
37 return /[\n\r\u2028\u2029]/.test(rawStringValue);
38}
39
40function containsBackslash(rawStringValue) {
41 return arrayIncludes(rawStringValue, '\\');
42}
43
44function containsHTMLEntity(rawStringValue) {
45 return HTML_ENTITY_REGEX().test(rawStringValue);
46}
47
48function containsOnlyHtmlEntities(rawStringValue) {
49 return rawStringValue.replace(HTML_ENTITY_REGEX(), '').trim() === '';
50}
51
52function containsDisallowedJSXTextChars(rawStringValue) {
53 return /[{<>}]/.test(rawStringValue);
54}
55
56function containsQuoteCharacters(value) {
57 return /['"]/.test(value);
58}
59
60function containsMultilineComment(value) {
61 return /\/\*/.test(value);
62}
63
64function escapeDoubleQuotes(rawStringValue) {
65 return rawStringValue.replace(/\\"/g, '"').replace(/"/g, '\\"');
66}
67
68function escapeBackslashes(rawStringValue) {
69 return rawStringValue.replace(/\\/g, '\\\\');
70}
71
72function needToEscapeCharacterForJSX(raw, node) {
73 return (
74 containsBackslash(raw)
75 || containsHTMLEntity(raw)
76 || (node.parent.type !== 'JSXAttribute' && containsDisallowedJSXTextChars(raw))
77 );
78}
79
80function containsWhitespaceExpression(child) {
81 if (child.type === 'JSXExpressionContainer') {
82 const value = child.expression.value;
83 return value ? jsxUtil.isWhiteSpaces(value) : false;
84 }
85 return false;
86}
87
88function isLineBreak(text) {
89 return containsLineTerminators(text) && text.trim() === '';
90}
91
92function wrapNonHTMLEntities(text) {
93 const HTML_ENTITY = '<HTML_ENTITY>';
94 const withCurlyBraces = text.split(HTML_ENTITY_REGEX()).map((word) => (
95 word === '' ? '' : `{${JSON.stringify(word)}}`
96 )).join(HTML_ENTITY);
97
98 const htmlEntities = text.match(HTML_ENTITY_REGEX());
99 return htmlEntities.reduce((acc, htmlEntity) => (
100 acc.replace(HTML_ENTITY, htmlEntity)
101 ), withCurlyBraces);
102}
103
104function wrapWithCurlyBraces(rawText) {
105 if (!containsLineTerminators(rawText)) {
106 return `{${JSON.stringify(rawText)}}`;
107 }
108
109 return rawText.split('\n').map((line) => {
110 if (line.trim() === '') {
111 return line;
112 }
113 const firstCharIndex = line.search(/[^\s]/);
114 const leftWhitespace = line.slice(0, firstCharIndex);
115 const text = line.slice(firstCharIndex);
116
117 if (containsHTMLEntity(line)) {
118 return `${leftWhitespace}${wrapNonHTMLEntities(text)}`;
119 }
120 return `${leftWhitespace}{${JSON.stringify(text)}}`;
121 }).join('\n');
122}
123
124function isWhiteSpaceLiteral(node) {
125 return node.type && node.type === 'Literal' && node.value && jsxUtil.isWhiteSpaces(node.value);
126}
127
128function isStringWithTrailingWhiteSpaces(value) {
129 return /^\s|\s$/.test(value);
130}
131
132function isLiteralWithTrailingWhiteSpaces(node) {
133 return node.type && node.type === 'Literal' && node.value && isStringWithTrailingWhiteSpaces(node.value);
134}
135
[d565449]136// ------------------------------------------------------------------------------
137// Rule Definition
138// ------------------------------------------------------------------------------
139
140const messages = {
141 unnecessaryCurly: 'Curly braces are unnecessary here.',
142 missingCurly: 'Need to wrap this literal in a JSX expression.',
143};
144
145/** @type {import('eslint').Rule.RuleModule} */
146module.exports = {
147 meta: {
148 docs: {
149 description: 'Disallow unnecessary JSX expressions when literals alone are sufficient or enforce JSX expressions on literals in JSX children or attributes',
150 category: 'Stylistic Issues',
151 recommended: false,
152 url: docsUrl('jsx-curly-brace-presence'),
153 },
154 fixable: 'code',
155
156 messages,
157
158 schema: [
159 {
160 anyOf: [
161 {
162 type: 'object',
163 properties: {
164 props: { enum: OPTION_VALUES },
165 children: { enum: OPTION_VALUES },
166 propElementValues: { enum: OPTION_VALUES },
167 },
168 additionalProperties: false,
169 },
170 {
171 enum: OPTION_VALUES,
172 },
173 ],
174 },
175 ],
176 },
177
178 create(context) {
179 const ruleOptions = context.options[0];
180 const userConfig = typeof ruleOptions === 'string'
181 ? { props: ruleOptions, children: ruleOptions, propElementValues: OPTION_IGNORE }
182 : Object.assign({}, DEFAULT_CONFIG, ruleOptions);
183
184 /**
185 * Report and fix an unnecessary curly brace violation on a node
186 * @param {ASTNode} JSXExpressionNode - The AST node with an unnecessary JSX expression
187 */
188 function reportUnnecessaryCurly(JSXExpressionNode) {
189 report(context, messages.unnecessaryCurly, 'unnecessaryCurly', {
190 node: JSXExpressionNode,
191 fix(fixer) {
192 const expression = JSXExpressionNode.expression;
193
194 let textToReplace;
195 if (jsxUtil.isJSX(expression)) {
196 textToReplace = getText(context, expression);
197 } else {
198 const expressionType = expression && expression.type;
199 const parentType = JSXExpressionNode.parent.type;
200
201 if (parentType === 'JSXAttribute') {
[0c6b92a]202 if (expressionType !== 'TemplateLiteral' && /["]/.test(expression.raw.slice(1, -1))) {
203 textToReplace = expression.raw;
204 } else {
205 textToReplace = `"${expressionType === 'TemplateLiteral'
206 ? expression.quasis[0].value.raw
207 : expression.raw.slice(1, -1)
208 }"`;
209 }
[d565449]210 } else if (jsxUtil.isJSX(expression)) {
211 textToReplace = getText(context, expression);
212 } else {
213 textToReplace = expressionType === 'TemplateLiteral'
214 ? expression.quasis[0].value.cooked : expression.value;
215 }
216 }
217
218 return fixer.replaceText(JSXExpressionNode, textToReplace);
219 },
220 });
221 }
222
223 function reportMissingCurly(literalNode) {
224 report(context, messages.missingCurly, 'missingCurly', {
225 node: literalNode,
226 fix(fixer) {
227 if (jsxUtil.isJSX(literalNode)) {
228 return fixer.replaceText(literalNode, `{${getText(context, literalNode)}}`);
229 }
230
231 // If a HTML entity name is found, bail out because it can be fixed
232 // by either using the real character or the unicode equivalent.
233 // If it contains any line terminator character, bail out as well.
234 if (
235 containsOnlyHtmlEntities(literalNode.raw)
236 || (literalNode.parent.type === 'JSXAttribute' && containsLineTerminators(literalNode.raw))
237 || isLineBreak(literalNode.raw)
238 ) {
239 return null;
240 }
241
242 const expression = literalNode.parent.type === 'JSXAttribute'
243 ? `{"${escapeDoubleQuotes(escapeBackslashes(
244 literalNode.raw.slice(1, -1)
245 ))}"}`
246 : wrapWithCurlyBraces(literalNode.raw);
247
248 return fixer.replaceText(literalNode, expression);
249 },
250 });
251 }
252
253 // Bail out if there is any character that needs to be escaped in JSX
254 // because escaping decreases readability and the original code may be more
255 // readable anyway or intentional for other specific reasons
256 function lintUnnecessaryCurly(JSXExpressionNode) {
257 const expression = JSXExpressionNode.expression;
258 const expressionType = expression.type;
259
260 const sourceCode = getSourceCode(context);
261 // Curly braces containing comments are necessary
262 if (sourceCode.getCommentsInside && sourceCode.getCommentsInside(JSXExpressionNode).length > 0) {
263 return;
264 }
265
266 if (
267 (expressionType === 'Literal' || expressionType === 'JSXText')
268 && typeof expression.value === 'string'
269 && (
270 (JSXExpressionNode.parent.type === 'JSXAttribute' && !isWhiteSpaceLiteral(expression))
271 || !isLiteralWithTrailingWhiteSpaces(expression)
272 )
273 && !containsMultilineComment(expression.value)
274 && !needToEscapeCharacterForJSX(expression.raw, JSXExpressionNode) && (
275 jsxUtil.isJSX(JSXExpressionNode.parent)
[0c6b92a]276 || (!containsQuoteCharacters(expression.value) || typeof expression.value === 'string')
[d565449]277 )
278 ) {
279 reportUnnecessaryCurly(JSXExpressionNode);
280 } else if (
281 expressionType === 'TemplateLiteral'
282 && expression.expressions.length === 0
283 && expression.quasis[0].value.raw.indexOf('\n') === -1
284 && !isStringWithTrailingWhiteSpaces(expression.quasis[0].value.raw)
285 && !needToEscapeCharacterForJSX(expression.quasis[0].value.raw, JSXExpressionNode)
286 && !containsQuoteCharacters(expression.quasis[0].value.cooked)
287 ) {
288 reportUnnecessaryCurly(JSXExpressionNode);
289 } else if (jsxUtil.isJSX(expression)) {
290 reportUnnecessaryCurly(JSXExpressionNode);
291 }
292 }
293
294 function areRuleConditionsSatisfied(parent, config, ruleCondition) {
295 return (
296 parent.type === 'JSXAttribute'
297 && typeof config.props === 'string'
298 && config.props === ruleCondition
299 ) || (
300 jsxUtil.isJSX(parent)
301 && typeof config.children === 'string'
302 && config.children === ruleCondition
303 );
304 }
305
306 function getAdjacentSiblings(node, children) {
307 for (let i = 1; i < children.length - 1; i++) {
308 const child = children[i];
309 if (node === child) {
310 return [children[i - 1], children[i + 1]];
311 }
312 }
313 if (node === children[0] && children[1]) {
314 return [children[1]];
315 }
316 if (node === children[children.length - 1] && children[children.length - 2]) {
317 return [children[children.length - 2]];
318 }
319 return [];
320 }
321
322 function hasAdjacentJsxExpressionContainers(node, children) {
323 if (!children) {
324 return false;
325 }
326 const childrenExcludingWhitespaceLiteral = children.filter((child) => !isWhiteSpaceLiteral(child));
327 const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral);
328
329 return adjSiblings.some((x) => x.type && x.type === 'JSXExpressionContainer');
330 }
331 function hasAdjacentJsx(node, children) {
332 if (!children) {
333 return false;
334 }
335 const childrenExcludingWhitespaceLiteral = children.filter((child) => !isWhiteSpaceLiteral(child));
336 const adjSiblings = getAdjacentSiblings(node, childrenExcludingWhitespaceLiteral);
337
338 return adjSiblings.some((x) => x.type && arrayIncludes(['JSXExpressionContainer', 'JSXElement'], x.type));
339 }
340 function shouldCheckForUnnecessaryCurly(node, config) {
341 const parent = node.parent;
342 // Bail out if the parent is a JSXAttribute & its contents aren't
343 // StringLiteral or TemplateLiteral since e.g
344 // <App prop1={<CustomEl />} prop2={<CustomEl>...</CustomEl>} />
345
346 if (
347 parent.type && parent.type === 'JSXAttribute'
348 && (node.expression && node.expression.type
349 && node.expression.type !== 'Literal'
350 && node.expression.type !== 'StringLiteral'
351 && node.expression.type !== 'TemplateLiteral')
352 ) {
353 return false;
354 }
355
356 // If there are adjacent `JsxExpressionContainer` then there is no need,
357 // to check for unnecessary curly braces.
358 if (jsxUtil.isJSX(parent) && hasAdjacentJsxExpressionContainers(node, parent.children)) {
359 return false;
360 }
361 if (containsWhitespaceExpression(node) && hasAdjacentJsx(node, parent.children)) {
362 return false;
363 }
364 if (
365 parent.children
366 && parent.children.length === 1
367 && containsWhitespaceExpression(node)
368 ) {
369 return false;
370 }
371
372 return areRuleConditionsSatisfied(parent, config, OPTION_NEVER);
373 }
374
375 function shouldCheckForMissingCurly(node, config) {
376 if (jsxUtil.isJSX(node)) {
377 return config.propElementValues !== OPTION_IGNORE;
378 }
379 if (
380 isLineBreak(node.raw)
381 || containsOnlyHtmlEntities(node.raw)
382 ) {
383 return false;
384 }
385 const parent = node.parent;
386 if (
387 parent.children
388 && parent.children.length === 1
389 && containsWhitespaceExpression(parent.children[0])
390 ) {
391 return false;
392 }
393
394 return areRuleConditionsSatisfied(parent, config, OPTION_ALWAYS);
395 }
396
397 // --------------------------------------------------------------------------
398 // Public
399 // --------------------------------------------------------------------------
400
401 return {
402 'JSXAttribute > JSXExpressionContainer > JSXElement'(node) {
403 if (userConfig.propElementValues === OPTION_NEVER) {
404 reportUnnecessaryCurly(node.parent);
405 }
406 },
407
408 JSXExpressionContainer(node) {
409 if (shouldCheckForUnnecessaryCurly(node, userConfig)) {
410 lintUnnecessaryCurly(node);
411 }
412 },
413
414 'JSXAttribute > JSXElement, Literal, JSXText'(node) {
415 if (shouldCheckForMissingCurly(node, userConfig)) {
416 reportMissingCurly(node);
417 }
418 },
419 };
420 },
421};
Note: See TracBrowser for help on using the repository browser.