123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- /**
- * @fileoverview Rule to flag fall-through cases in switch statements.
- * @author Matt DuVall <http://mattduvall.com/>
- */
- "use strict";
- //------------------------------------------------------------------------------
- // Helpers
- //------------------------------------------------------------------------------
- const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
- /**
- * Checks whether or not a given case has a fallthrough comment.
- * @param {ASTNode} caseWhichFallsThrough SwitchCase node which falls through.
- * @param {ASTNode} subsequentCase The case after caseWhichFallsThrough.
- * @param {RuleContext} context A rule context which stores comments.
- * @param {RegExp} fallthroughCommentPattern A pattern to match comment to.
- * @returns {boolean} `true` if the case has a valid fallthrough comment.
- */
- function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) {
- const sourceCode = context.getSourceCode();
- if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") {
- const trailingCloseBrace = sourceCode.getLastToken(caseWhichFallsThrough.consequent[0]);
- const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop();
- if (commentInBlock && fallthroughCommentPattern.test(commentInBlock.value)) {
- return true;
- }
- }
- const comment = sourceCode.getCommentsBefore(subsequentCase).pop();
- return Boolean(comment && fallthroughCommentPattern.test(comment.value));
- }
- /**
- * Checks whether or not a given code path segment is reachable.
- * @param {CodePathSegment} segment A CodePathSegment to check.
- * @returns {boolean} `true` if the segment is reachable.
- */
- function isReachable(segment) {
- return segment.reachable;
- }
- /**
- * Checks whether a node and a token are separated by blank lines
- * @param {ASTNode} node The node to check
- * @param {Token} token The token to compare against
- * @returns {boolean} `true` if there are blank lines between node and token
- */
- function hasBlankLinesBetween(node, token) {
- return token.loc.start.line > node.loc.end.line + 1;
- }
- //------------------------------------------------------------------------------
- // Rule Definition
- //------------------------------------------------------------------------------
- /** @type {import('../shared/types').Rule} */
- module.exports = {
- meta: {
- type: "problem",
- docs: {
- description: "Disallow fallthrough of `case` statements",
- recommended: true,
- url: "https://eslint.org/docs/rules/no-fallthrough"
- },
- schema: [
- {
- type: "object",
- properties: {
- commentPattern: {
- type: "string",
- default: ""
- },
- allowEmptyCase: {
- type: "boolean",
- default: false
- }
- },
- additionalProperties: false
- }
- ],
- messages: {
- case: "Expected a 'break' statement before 'case'.",
- default: "Expected a 'break' statement before 'default'."
- }
- },
- create(context) {
- const options = context.options[0] || {};
- let currentCodePath = null;
- const sourceCode = context.getSourceCode();
- const allowEmptyCase = options.allowEmptyCase || false;
- /*
- * We need to use leading comments of the next SwitchCase node because
- * trailing comments is wrong if semicolons are omitted.
- */
- let fallthroughCase = null;
- let fallthroughCommentPattern = null;
- if (options.commentPattern) {
- fallthroughCommentPattern = new RegExp(options.commentPattern, "u");
- } else {
- fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT;
- }
- return {
- onCodePathStart(codePath) {
- currentCodePath = codePath;
- },
- onCodePathEnd() {
- currentCodePath = currentCodePath.upper;
- },
- SwitchCase(node) {
- /*
- * Checks whether or not there is a fallthrough comment.
- * And reports the previous fallthrough node if that does not exist.
- */
- if (fallthroughCase && (!hasFallthroughComment(fallthroughCase, node, context, fallthroughCommentPattern))) {
- context.report({
- messageId: node.test ? "case" : "default",
- node
- });
- }
- fallthroughCase = null;
- },
- "SwitchCase:exit"(node) {
- const nextToken = sourceCode.getTokenAfter(node);
- /*
- * `reachable` meant fall through because statements preceded by
- * `break`, `return`, or `throw` are unreachable.
- * And allows empty cases and the last case.
- */
- if (currentCodePath.currentSegments.some(isReachable) &&
- (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) &&
- node.parent.cases[node.parent.cases.length - 1] !== node) {
- fallthroughCase = node;
- }
- }
- };
- }
- };
|