source: imaps-frontend/node_modules/eslint/lib/rules/curly.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: 18.9 KB
Line 
1/**
2 * @fileoverview Rule to flag statements without curly braces
3 * @author Nicholas C. Zakas
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10
11const astUtils = require("./utils/ast-utils");
12
13//------------------------------------------------------------------------------
14// Rule Definition
15//------------------------------------------------------------------------------
16
17/** @type {import('../shared/types').Rule} */
18module.exports = {
19 meta: {
20 type: "suggestion",
21
22 docs: {
23 description: "Enforce consistent brace style for all control statements",
24 recommended: false,
25 url: "https://eslint.org/docs/latest/rules/curly"
26 },
27
28 schema: {
29 anyOf: [
30 {
31 type: "array",
32 items: [
33 {
34 enum: ["all"]
35 }
36 ],
37 minItems: 0,
38 maxItems: 1
39 },
40 {
41 type: "array",
42 items: [
43 {
44 enum: ["multi", "multi-line", "multi-or-nest"]
45 },
46 {
47 enum: ["consistent"]
48 }
49 ],
50 minItems: 0,
51 maxItems: 2
52 }
53 ]
54 },
55
56 fixable: "code",
57
58 messages: {
59 missingCurlyAfter: "Expected { after '{{name}}'.",
60 missingCurlyAfterCondition: "Expected { after '{{name}}' condition.",
61 unexpectedCurlyAfter: "Unnecessary { after '{{name}}'.",
62 unexpectedCurlyAfterCondition: "Unnecessary { after '{{name}}' condition."
63 }
64 },
65
66 create(context) {
67
68 const multiOnly = (context.options[0] === "multi");
69 const multiLine = (context.options[0] === "multi-line");
70 const multiOrNest = (context.options[0] === "multi-or-nest");
71 const consistent = (context.options[1] === "consistent");
72
73 const sourceCode = context.sourceCode;
74
75 //--------------------------------------------------------------------------
76 // Helpers
77 //--------------------------------------------------------------------------
78
79 /**
80 * Determines if a given node is a one-liner that's on the same line as it's preceding code.
81 * @param {ASTNode} node The node to check.
82 * @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code.
83 * @private
84 */
85 function isCollapsedOneLiner(node) {
86 const before = sourceCode.getTokenBefore(node);
87 const last = sourceCode.getLastToken(node);
88 const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last;
89
90 return before.loc.start.line === lastExcludingSemicolon.loc.end.line;
91 }
92
93 /**
94 * Determines if a given node is a one-liner.
95 * @param {ASTNode} node The node to check.
96 * @returns {boolean} True if the node is a one-liner.
97 * @private
98 */
99 function isOneLiner(node) {
100 if (node.type === "EmptyStatement") {
101 return true;
102 }
103
104 const first = sourceCode.getFirstToken(node);
105 const last = sourceCode.getLastToken(node);
106 const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last;
107
108 return first.loc.start.line === lastExcludingSemicolon.loc.end.line;
109 }
110
111 /**
112 * Determines if the given node is a lexical declaration (let, const, function, or class)
113 * @param {ASTNode} node The node to check
114 * @returns {boolean} True if the node is a lexical declaration
115 * @private
116 */
117 function isLexicalDeclaration(node) {
118 if (node.type === "VariableDeclaration") {
119 return node.kind === "const" || node.kind === "let";
120 }
121
122 return node.type === "FunctionDeclaration" || node.type === "ClassDeclaration";
123 }
124
125 /**
126 * Checks if the given token is an `else` token or not.
127 * @param {Token} token The token to check.
128 * @returns {boolean} `true` if the token is an `else` token.
129 */
130 function isElseKeywordToken(token) {
131 return token.value === "else" && token.type === "Keyword";
132 }
133
134 /**
135 * Determines whether the given node has an `else` keyword token as the first token after.
136 * @param {ASTNode} node The node to check.
137 * @returns {boolean} `true` if the node is followed by an `else` keyword token.
138 */
139 function isFollowedByElseKeyword(node) {
140 const nextToken = sourceCode.getTokenAfter(node);
141
142 return Boolean(nextToken) && isElseKeywordToken(nextToken);
143 }
144
145 /**
146 * Determines if a semicolon needs to be inserted after removing a set of curly brackets, in order to avoid a SyntaxError.
147 * @param {Token} closingBracket The } token
148 * @returns {boolean} `true` if a semicolon needs to be inserted after the last statement in the block.
149 */
150 function needsSemicolon(closingBracket) {
151 const tokenBefore = sourceCode.getTokenBefore(closingBracket);
152 const tokenAfter = sourceCode.getTokenAfter(closingBracket);
153 const lastBlockNode = sourceCode.getNodeByRangeIndex(tokenBefore.range[0]);
154
155 if (astUtils.isSemicolonToken(tokenBefore)) {
156
157 // If the last statement already has a semicolon, don't add another one.
158 return false;
159 }
160
161 if (!tokenAfter) {
162
163 // If there are no statements after this block, there is no need to add a semicolon.
164 return false;
165 }
166
167 if (lastBlockNode.type === "BlockStatement" && lastBlockNode.parent.type !== "FunctionExpression" && lastBlockNode.parent.type !== "ArrowFunctionExpression") {
168
169 /*
170 * If the last node surrounded by curly brackets is a BlockStatement (other than a FunctionExpression or an ArrowFunctionExpression),
171 * don't insert a semicolon. Otherwise, the semicolon would be parsed as a separate statement, which would cause
172 * a SyntaxError if it was followed by `else`.
173 */
174 return false;
175 }
176
177 if (tokenBefore.loc.end.line === tokenAfter.loc.start.line) {
178
179 // If the next token is on the same line, insert a semicolon.
180 return true;
181 }
182
183 if (/^[([/`+-]/u.test(tokenAfter.value)) {
184
185 // If the next token starts with a character that would disrupt ASI, insert a semicolon.
186 return true;
187 }
188
189 if (tokenBefore.type === "Punctuator" && (tokenBefore.value === "++" || tokenBefore.value === "--")) {
190
191 // If the last token is ++ or --, insert a semicolon to avoid disrupting ASI.
192 return true;
193 }
194
195 // Otherwise, do not insert a semicolon.
196 return false;
197 }
198
199 /**
200 * Determines whether the code represented by the given node contains an `if` statement
201 * that would become associated with an `else` keyword directly appended to that code.
202 *
203 * Examples where it returns `true`:
204 *
205 * if (a)
206 * foo();
207 *
208 * if (a) {
209 * foo();
210 * }
211 *
212 * if (a)
213 * foo();
214 * else if (b)
215 * bar();
216 *
217 * while (a)
218 * if (b)
219 * if(c)
220 * foo();
221 * else
222 * bar();
223 *
224 * Examples where it returns `false`:
225 *
226 * if (a)
227 * foo();
228 * else
229 * bar();
230 *
231 * while (a) {
232 * if (b)
233 * if(c)
234 * foo();
235 * else
236 * bar();
237 * }
238 *
239 * while (a)
240 * if (b) {
241 * if(c)
242 * foo();
243 * }
244 * else
245 * bar();
246 * @param {ASTNode} node Node representing the code to check.
247 * @returns {boolean} `true` if an `if` statement within the code would become associated with an `else` appended to that code.
248 */
249 function hasUnsafeIf(node) {
250 switch (node.type) {
251 case "IfStatement":
252 if (!node.alternate) {
253 return true;
254 }
255 return hasUnsafeIf(node.alternate);
256 case "ForStatement":
257 case "ForInStatement":
258 case "ForOfStatement":
259 case "LabeledStatement":
260 case "WithStatement":
261 case "WhileStatement":
262 return hasUnsafeIf(node.body);
263 default:
264 return false;
265 }
266 }
267
268 /**
269 * Determines whether the existing curly braces around the single statement are necessary to preserve the semantics of the code.
270 * The braces, which make the given block body, are necessary in either of the following situations:
271 *
272 * 1. The statement is a lexical declaration.
273 * 2. Without the braces, an `if` within the statement would become associated with an `else` after the closing brace:
274 *
275 * if (a) {
276 * if (b)
277 * foo();
278 * }
279 * else
280 * bar();
281 *
282 * if (a)
283 * while (b)
284 * while (c) {
285 * while (d)
286 * if (e)
287 * while(f)
288 * foo();
289 * }
290 * else
291 * bar();
292 * @param {ASTNode} node `BlockStatement` body with exactly one statement directly inside. The statement can have its own nested statements.
293 * @returns {boolean} `true` if the braces are necessary - removing them (replacing the given `BlockStatement` body with its single statement content)
294 * would change the semantics of the code or produce a syntax error.
295 */
296 function areBracesNecessary(node) {
297 const statement = node.body[0];
298
299 return isLexicalDeclaration(statement) ||
300 hasUnsafeIf(statement) && isFollowedByElseKeyword(node);
301 }
302
303 /**
304 * Prepares to check the body of a node to see if it's a block statement.
305 * @param {ASTNode} node The node to report if there's a problem.
306 * @param {ASTNode} body The body node to check for blocks.
307 * @param {string} name The name to report if there's a problem.
308 * @param {{ condition: boolean }} opts Options to pass to the report functions
309 * @returns {Object} a prepared check object, with "actual", "expected", "check" properties.
310 * "actual" will be `true` or `false` whether the body is already a block statement.
311 * "expected" will be `true` or `false` if the body should be a block statement or not, or
312 * `null` if it doesn't matter, depending on the rule options. It can be modified to change
313 * the final behavior of "check".
314 * "check" will be a function reporting appropriate problems depending on the other
315 * properties.
316 */
317 function prepareCheck(node, body, name, opts) {
318 const hasBlock = (body.type === "BlockStatement");
319 let expected = null;
320
321 if (hasBlock && (body.body.length !== 1 || areBracesNecessary(body))) {
322 expected = true;
323 } else if (multiOnly) {
324 expected = false;
325 } else if (multiLine) {
326 if (!isCollapsedOneLiner(body)) {
327 expected = true;
328 }
329
330 // otherwise, the body is allowed to have braces or not to have braces
331
332 } else if (multiOrNest) {
333 if (hasBlock) {
334 const statement = body.body[0];
335 const leadingCommentsInBlock = sourceCode.getCommentsBefore(statement);
336
337 expected = !isOneLiner(statement) || leadingCommentsInBlock.length > 0;
338 } else {
339 expected = !isOneLiner(body);
340 }
341 } else {
342
343 // default "all"
344 expected = true;
345 }
346
347 return {
348 actual: hasBlock,
349 expected,
350 check() {
351 if (this.expected !== null && this.expected !== this.actual) {
352 if (this.expected) {
353 context.report({
354 node,
355 loc: body.loc,
356 messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter",
357 data: {
358 name
359 },
360 fix: fixer => fixer.replaceText(body, `{${sourceCode.getText(body)}}`)
361 });
362 } else {
363 context.report({
364 node,
365 loc: body.loc,
366 messageId: opts && opts.condition ? "unexpectedCurlyAfterCondition" : "unexpectedCurlyAfter",
367 data: {
368 name
369 },
370 fix(fixer) {
371
372 /*
373 * `do while` expressions sometimes need a space to be inserted after `do`.
374 * e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)`
375 */
376 const needsPrecedingSpace = node.type === "DoWhileStatement" &&
377 sourceCode.getTokenBefore(body).range[1] === body.range[0] &&
378 !astUtils.canTokensBeAdjacent("do", sourceCode.getFirstToken(body, { skip: 1 }));
379
380 const openingBracket = sourceCode.getFirstToken(body);
381 const closingBracket = sourceCode.getLastToken(body);
382 const lastTokenInBlock = sourceCode.getTokenBefore(closingBracket);
383
384 if (needsSemicolon(closingBracket)) {
385
386 /*
387 * If removing braces would cause a SyntaxError due to multiple statements on the same line (or
388 * change the semantics of the code due to ASI), don't perform a fix.
389 */
390 return null;
391 }
392
393 const resultingBodyText = sourceCode.getText().slice(openingBracket.range[1], lastTokenInBlock.range[0]) +
394 sourceCode.getText(lastTokenInBlock) +
395 sourceCode.getText().slice(lastTokenInBlock.range[1], closingBracket.range[0]);
396
397 return fixer.replaceText(body, (needsPrecedingSpace ? " " : "") + resultingBodyText);
398 }
399 });
400 }
401 }
402 }
403 };
404 }
405
406 /**
407 * Prepares to check the bodies of a "if", "else if" and "else" chain.
408 * @param {ASTNode} node The first IfStatement node of the chain.
409 * @returns {Object[]} prepared checks for each body of the chain. See `prepareCheck` for more
410 * information.
411 */
412 function prepareIfChecks(node) {
413 const preparedChecks = [];
414
415 for (let currentNode = node; currentNode; currentNode = currentNode.alternate) {
416 preparedChecks.push(prepareCheck(currentNode, currentNode.consequent, "if", { condition: true }));
417 if (currentNode.alternate && currentNode.alternate.type !== "IfStatement") {
418 preparedChecks.push(prepareCheck(currentNode, currentNode.alternate, "else"));
419 break;
420 }
421 }
422
423 if (consistent) {
424
425 /*
426 * If any node should have or already have braces, make sure they
427 * all have braces.
428 * If all nodes shouldn't have braces, make sure they don't.
429 */
430 const expected = preparedChecks.some(preparedCheck => {
431 if (preparedCheck.expected !== null) {
432 return preparedCheck.expected;
433 }
434 return preparedCheck.actual;
435 });
436
437 preparedChecks.forEach(preparedCheck => {
438 preparedCheck.expected = expected;
439 });
440 }
441
442 return preparedChecks;
443 }
444
445 //--------------------------------------------------------------------------
446 // Public
447 //--------------------------------------------------------------------------
448
449 return {
450 IfStatement(node) {
451 const parent = node.parent;
452 const isElseIf = parent.type === "IfStatement" && parent.alternate === node;
453
454 if (!isElseIf) {
455
456 // This is a top `if`, check the whole `if-else-if` chain
457 prepareIfChecks(node).forEach(preparedCheck => {
458 preparedCheck.check();
459 });
460 }
461
462 // Skip `else if`, it's already checked (when the top `if` was visited)
463 },
464
465 WhileStatement(node) {
466 prepareCheck(node, node.body, "while", { condition: true }).check();
467 },
468
469 DoWhileStatement(node) {
470 prepareCheck(node, node.body, "do").check();
471 },
472
473 ForStatement(node) {
474 prepareCheck(node, node.body, "for", { condition: true }).check();
475 },
476
477 ForInStatement(node) {
478 prepareCheck(node, node.body, "for-in").check();
479 },
480
481 ForOfStatement(node) {
482 prepareCheck(node, node.body, "for-of").check();
483 }
484 };
485 }
486};
Note: See TracBrowser for help on using the repository browser.