|
|
/** * @fileoverview Rule to flag fall-through cases in switch statements. * @author Matt DuVall <http://mattduvall.com/>
*/ "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const lodash = require("lodash");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
/** * Checks whether or not a given node has a fallthrough comment. * @param {ASTNode} node A SwitchCase node to get comments. * @param {RuleContext} context A rule context which stores comments. * @param {RegExp} fallthroughCommentPattern A pattern to match comment to. * @returns {boolean} `true` if the node has a valid fallthrough comment. */ function hasFallthroughComment(node, context, fallthroughCommentPattern) { const sourceCode = context.getSourceCode(); const comment = lodash.last(sourceCode.getCommentsBefore(node));
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
//------------------------------------------------------------------------------
module.exports = { meta: { type: "problem",
docs: { description: "disallow fallthrough of `case` statements", category: "Best Practices", recommended: true, url: "https://eslint.org/docs/rules/no-fallthrough" },
schema: [ { type: "object", properties: { commentPattern: { type: "string", default: "" } }, 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();
/* * 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(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 || hasBlankLinesBetween(node, nextToken)) && lodash.last(node.parent.cases) !== node) { fallthroughCase = node; } } }; } };
|