| /** |
| * @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; |
| } |
| } |
| }; |
| } |
| }; |