1/** 2 * @fileoverview Rule to flag fall-through cases in switch statements. 3 * @author Matt DuVall <http://mattduvall.com/> 4 */ 5"use strict"; 6 7//------------------------------------------------------------------------------ 8// Requirements 9//------------------------------------------------------------------------------ 10 11const lodash = require("lodash"); 12 13//------------------------------------------------------------------------------ 14// Helpers 15//------------------------------------------------------------------------------ 16 17const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu; 18 19/** 20 * Checks whether or not a given node has a fallthrough comment. 21 * @param {ASTNode} node A SwitchCase node to get comments. 22 * @param {RuleContext} context A rule context which stores comments. 23 * @param {RegExp} fallthroughCommentPattern A pattern to match comment to. 24 * @returns {boolean} `true` if the node has a valid fallthrough comment. 25 */ 26function hasFallthroughComment(node, context, fallthroughCommentPattern) { 27 const sourceCode = context.getSourceCode(); 28 const comment = lodash.last(sourceCode.getCommentsBefore(node)); 29 30 return Boolean(comment && fallthroughCommentPattern.test(comment.value)); 31} 32 33/** 34 * Checks whether or not a given code path segment is reachable. 35 * @param {CodePathSegment} segment A CodePathSegment to check. 36 * @returns {boolean} `true` if the segment is reachable. 37 */ 38function isReachable(segment) { 39 return segment.reachable; 40} 41 42/** 43 * Checks whether a node and a token are separated by blank lines 44 * @param {ASTNode} node The node to check 45 * @param {Token} token The token to compare against 46 * @returns {boolean} `true` if there are blank lines between node and token 47 */ 48function hasBlankLinesBetween(node, token) { 49 return token.loc.start.line > node.loc.end.line + 1; 50} 51 52//------------------------------------------------------------------------------ 53// Rule Definition 54//------------------------------------------------------------------------------ 55 56module.exports = { 57 meta: { 58 type: "problem", 59 60 docs: { 61 description: "disallow fallthrough of `case` statements", 62 category: "Best Practices", 63 recommended: true, 64 url: "https://eslint.org/docs/rules/no-fallthrough" 65 }, 66 67 schema: [ 68 { 69 type: "object", 70 properties: { 71 commentPattern: { 72 type: "string", 73 default: "" 74 } 75 }, 76 additionalProperties: false 77 } 78 ], 79 messages: { 80 case: "Expected a 'break' statement before 'case'.", 81 default: "Expected a 'break' statement before 'default'." 82 } 83 }, 84 85 create(context) { 86 const options = context.options[0] || {}; 87 let currentCodePath = null; 88 const sourceCode = context.getSourceCode(); 89 90 /* 91 * We need to use leading comments of the next SwitchCase node because 92 * trailing comments is wrong if semicolons are omitted. 93 */ 94 let fallthroughCase = null; 95 let fallthroughCommentPattern = null; 96 97 if (options.commentPattern) { 98 fallthroughCommentPattern = new RegExp(options.commentPattern, "u"); 99 } else { 100 fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT; 101 } 102 103 return { 104 onCodePathStart(codePath) { 105 currentCodePath = codePath; 106 }, 107 onCodePathEnd() { 108 currentCodePath = currentCodePath.upper; 109 }, 110 111 SwitchCase(node) { 112 113 /* 114 * Checks whether or not there is a fallthrough comment. 115 * And reports the previous fallthrough node if that does not exist. 116 */ 117 if (fallthroughCase && !hasFallthroughComment(node, context, fallthroughCommentPattern)) { 118 context.report({ 119 messageId: node.test ? "case" : "default", 120 node 121 }); 122 } 123 fallthroughCase = null; 124 }, 125 126 "SwitchCase:exit"(node) { 127 const nextToken = sourceCode.getTokenAfter(node); 128 129 /* 130 * `reachable` meant fall through because statements preceded by 131 * `break`, `return`, or `throw` are unreachable. 132 * And allows empty cases and the last case. 133 */ 134 if (currentCodePath.currentSegments.some(isReachable) && 135 (node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) && 136 lodash.last(node.parent.cases) !== node) { 137 fallthroughCase = node; 138 } 139 } 140 }; 141 } 142}; 143