Permalink
Cannot retrieve contributors at this time
| /** | |
| * @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; | |
| } | |
| } | |
| }; | |
| } | |
| }; |