UNPKG

4.75 kBJavaScriptView Raw
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/i;
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 docs: {
59 description: "disallow fallthrough of `case` statements",
60 category: "Best Practices",
61 recommended: true,
62 url: "https://eslint.org/docs/rules/no-fallthrough"
63 },
64
65 schema: [
66 {
67 type: "object",
68 properties: {
69 commentPattern: {
70 type: "string"
71 }
72 },
73 additionalProperties: false
74 }
75 ]
76 },
77
78 create(context) {
79 const options = context.options[0] || {};
80 let currentCodePath = null;
81 const sourceCode = context.getSourceCode();
82
83 /*
84 * We need to use leading comments of the next SwitchCase node because
85 * trailing comments is wrong if semicolons are omitted.
86 */
87 let fallthroughCase = null;
88 let fallthroughCommentPattern = null;
89
90 if (options.commentPattern) {
91 fallthroughCommentPattern = new RegExp(options.commentPattern);
92 } else {
93 fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT;
94 }
95
96 return {
97 onCodePathStart(codePath) {
98 currentCodePath = codePath;
99 },
100 onCodePathEnd() {
101 currentCodePath = currentCodePath.upper;
102 },
103
104 SwitchCase(node) {
105
106 /*
107 * Checks whether or not there is a fallthrough comment.
108 * And reports the previous fallthrough node if that does not exist.
109 */
110 if (fallthroughCase && !hasFallthroughComment(node, context, fallthroughCommentPattern)) {
111 context.report({
112 message: "Expected a 'break' statement before '{{type}}'.",
113 data: { type: node.test ? "case" : "default" },
114 node
115 });
116 }
117 fallthroughCase = null;
118 },
119
120 "SwitchCase:exit"(node) {
121 const nextToken = sourceCode.getTokenAfter(node);
122
123 /*
124 * `reachable` meant fall through because statements preceded by
125 * `break`, `return`, or `throw` are unreachable.
126 * And allows empty cases and the last case.
127 */
128 if (currentCodePath.currentSegments.some(isReachable) &&
129 (node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) &&
130 lodash.last(node.parent.cases) !== node) {
131 fallthroughCase = node;
132 }
133 }
134 };
135 }
136};