source: imaps-frontend/node_modules/eslint-plugin-react/lib/rules/jsx-wrap-multilines.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: 8.2 KB
Line 
1/**
2 * @fileoverview Prevent missing parentheses around multilines JSX
3 * @author Yannick Croissant
4 */
5
6'use strict';
7
8const has = require('hasown');
9const docsUrl = require('../util/docsUrl');
10const eslintUtil = require('../util/eslint');
11const jsxUtil = require('../util/jsx');
12const reportC = require('../util/report');
13const isParenthesized = require('../util/ast').isParenthesized;
14
15const getSourceCode = eslintUtil.getSourceCode;
16const getText = eslintUtil.getText;
17
18// ------------------------------------------------------------------------------
19// Constants
20// ------------------------------------------------------------------------------
21
22const DEFAULTS = {
23 declaration: 'parens',
24 assignment: 'parens',
25 return: 'parens',
26 arrow: 'parens',
27 condition: 'ignore',
28 logical: 'ignore',
29 prop: 'ignore',
30};
31
32// ------------------------------------------------------------------------------
33// Rule Definition
34// ------------------------------------------------------------------------------
35
36const messages = {
37 missingParens: 'Missing parentheses around multilines JSX',
38 extraParens: 'Expected no parentheses around multilines JSX',
39 parensOnNewLines: 'Parentheses around JSX should be on separate lines',
40};
41
42/** @type {import('eslint').Rule.RuleModule} */
43module.exports = {
44 meta: {
45 docs: {
46 description: 'Disallow missing parentheses around multiline JSX',
47 category: 'Stylistic Issues',
48 recommended: false,
49 url: docsUrl('jsx-wrap-multilines'),
50 },
51 fixable: 'code',
52
53 messages,
54
55 schema: [{
56 type: 'object',
57 // true/false are for backwards compatibility
58 properties: {
59 declaration: {
60 enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
61 },
62 assignment: {
63 enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
64 },
65 return: {
66 enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
67 },
68 arrow: {
69 enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
70 },
71 condition: {
72 enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
73 },
74 logical: {
75 enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
76 },
77 prop: {
78 enum: [true, false, 'ignore', 'parens', 'parens-new-line', 'never'],
79 },
80 },
81 additionalProperties: false,
82 }],
83 },
84
85 create(context) {
86 function getOption(type) {
87 const userOptions = context.options[0] || {};
88 if (has(userOptions, type)) {
89 return userOptions[type];
90 }
91 return DEFAULTS[type];
92 }
93
94 function isEnabled(type) {
95 const option = getOption(type);
96 return option && option !== 'ignore';
97 }
98
99 function needsOpeningNewLine(node) {
100 const previousToken = getSourceCode(context).getTokenBefore(node);
101
102 if (!isParenthesized(context, node)) {
103 return false;
104 }
105
106 if (previousToken.loc.end.line === node.loc.start.line) {
107 return true;
108 }
109
110 return false;
111 }
112
113 function needsClosingNewLine(node) {
114 const nextToken = getSourceCode(context).getTokenAfter(node);
115
116 if (!isParenthesized(context, node)) {
117 return false;
118 }
119
120 if (node.loc.end.line === nextToken.loc.end.line) {
121 return true;
122 }
123
124 return false;
125 }
126
127 function isMultilines(node) {
128 return node.loc.start.line !== node.loc.end.line;
129 }
130
131 function report(node, messageId, fix) {
132 reportC(context, messages[messageId], messageId, {
133 node,
134 fix,
135 });
136 }
137
138 function trimTokenBeforeNewline(node, tokenBefore) {
139 // if the token before the jsx is a bracket or curly brace
140 // we don't want a space between the opening parentheses and the multiline jsx
141 const isBracket = tokenBefore.value === '{' || tokenBefore.value === '[';
142 return `${tokenBefore.value.trim()}${isBracket ? '' : ' '}`;
143 }
144
145 function check(node, type) {
146 if (!node || !jsxUtil.isJSX(node)) {
147 return;
148 }
149
150 const sourceCode = getSourceCode(context);
151 const option = getOption(type);
152
153 if ((option === true || option === 'parens') && !isParenthesized(context, node) && isMultilines(node)) {
154 report(node, 'missingParens', (fixer) => fixer.replaceText(node, `(${getText(context, node)})`));
155 }
156
157 if (option === 'parens-new-line' && isMultilines(node)) {
158 if (!isParenthesized(context, node)) {
159 const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true });
160 const tokenAfter = sourceCode.getTokenAfter(node, { includeComments: true });
161 const start = node.loc.start;
162 if (tokenBefore.loc.end.line < start.line) {
163 // Strip newline after operator if parens newline is specified
164 report(
165 node,
166 'missingParens',
167 (fixer) => fixer.replaceTextRange(
168 [tokenBefore.range[0], tokenAfter && (tokenAfter.value === ';' || tokenAfter.value === '}') ? tokenAfter.range[0] : node.range[1]],
169 `${trimTokenBeforeNewline(node, tokenBefore)}(\n${start.column > 0 ? ' '.repeat(start.column) : ''}${getText(context, node)}\n${start.column > 0 ? ' '.repeat(start.column - 2) : ''})`
170 )
171 );
172 } else {
173 report(node, 'missingParens', (fixer) => fixer.replaceText(node, `(\n${getText(context, node)}\n)`));
174 }
175 } else {
176 const needsOpening = needsOpeningNewLine(node);
177 const needsClosing = needsClosingNewLine(node);
178 if (needsOpening || needsClosing) {
179 report(node, 'parensOnNewLines', (fixer) => {
180 const text = getText(context, node);
181 let fixed = text;
182 if (needsOpening) {
183 fixed = `\n${fixed}`;
184 }
185 if (needsClosing) {
186 fixed = `${fixed}\n`;
187 }
188 return fixer.replaceText(node, fixed);
189 });
190 }
191 }
192 }
193
194 if (option === 'never' && isParenthesized(context, node)) {
195 const tokenBefore = sourceCode.getTokenBefore(node);
196 const tokenAfter = sourceCode.getTokenAfter(node);
197 report(node, 'extraParens', (fixer) => fixer.replaceTextRange(
198 [tokenBefore.range[0], tokenAfter.range[1]],
199 getText(context, node)
200 ));
201 }
202 }
203
204 // --------------------------------------------------------------------------
205 // Public
206 // --------------------------------------------------------------------------
207
208 return {
209
210 VariableDeclarator(node) {
211 const type = 'declaration';
212 if (!isEnabled(type)) {
213 return;
214 }
215 if (!isEnabled('condition') && node.init && node.init.type === 'ConditionalExpression') {
216 check(node.init.consequent, type);
217 check(node.init.alternate, type);
218 return;
219 }
220 check(node.init, type);
221 },
222
223 AssignmentExpression(node) {
224 const type = 'assignment';
225 if (!isEnabled(type)) {
226 return;
227 }
228 if (!isEnabled('condition') && node.right.type === 'ConditionalExpression') {
229 check(node.right.consequent, type);
230 check(node.right.alternate, type);
231 return;
232 }
233 check(node.right, type);
234 },
235
236 ReturnStatement(node) {
237 const type = 'return';
238 if (isEnabled(type)) {
239 check(node.argument, type);
240 }
241 },
242
243 'ArrowFunctionExpression:exit': (node) => {
244 const arrowBody = node.body;
245 const type = 'arrow';
246
247 if (isEnabled(type) && arrowBody.type !== 'BlockStatement') {
248 check(arrowBody, type);
249 }
250 },
251
252 ConditionalExpression(node) {
253 const type = 'condition';
254 if (isEnabled(type)) {
255 check(node.consequent, type);
256 check(node.alternate, type);
257 }
258 },
259
260 LogicalExpression(node) {
261 const type = 'logical';
262 if (isEnabled(type)) {
263 check(node.right, type);
264 }
265 },
266
267 JSXAttribute(node) {
268 const type = 'prop';
269 if (isEnabled(type) && node.value && node.value.type === 'JSXExpressionContainer') {
270 check(node.value.expression, type);
271 }
272 },
273 };
274 },
275};
Note: See TracBrowser for help on using the repository browser.