• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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