• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Checks for unreachable code due to return, throws, break, and continue.
3 * @author Joel Feenstra
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Helpers
9//------------------------------------------------------------------------------
10
11/**
12 * Checks whether or not a given variable declarator has the initializer.
13 * @param {ASTNode} node A VariableDeclarator node to check.
14 * @returns {boolean} `true` if the node has the initializer.
15 */
16function isInitialized(node) {
17    return Boolean(node.init);
18}
19
20/**
21 * Checks whether or not a given code path segment is unreachable.
22 * @param {CodePathSegment} segment A CodePathSegment to check.
23 * @returns {boolean} `true` if the segment is unreachable.
24 */
25function isUnreachable(segment) {
26    return !segment.reachable;
27}
28
29/**
30 * The class to distinguish consecutive unreachable statements.
31 */
32class ConsecutiveRange {
33    constructor(sourceCode) {
34        this.sourceCode = sourceCode;
35        this.startNode = null;
36        this.endNode = null;
37    }
38
39    /**
40     * The location object of this range.
41     * @type {Object}
42     */
43    get location() {
44        return {
45            start: this.startNode.loc.start,
46            end: this.endNode.loc.end
47        };
48    }
49
50    /**
51     * `true` if this range is empty.
52     * @type {boolean}
53     */
54    get isEmpty() {
55        return !(this.startNode && this.endNode);
56    }
57
58    /**
59     * Checks whether the given node is inside of this range.
60     * @param {ASTNode|Token} node The node to check.
61     * @returns {boolean} `true` if the node is inside of this range.
62     */
63    contains(node) {
64        return (
65            node.range[0] >= this.startNode.range[0] &&
66            node.range[1] <= this.endNode.range[1]
67        );
68    }
69
70    /**
71     * Checks whether the given node is consecutive to this range.
72     * @param {ASTNode} node The node to check.
73     * @returns {boolean} `true` if the node is consecutive to this range.
74     */
75    isConsecutive(node) {
76        return this.contains(this.sourceCode.getTokenBefore(node));
77    }
78
79    /**
80     * Merges the given node to this range.
81     * @param {ASTNode} node The node to merge.
82     * @returns {void}
83     */
84    merge(node) {
85        this.endNode = node;
86    }
87
88    /**
89     * Resets this range by the given node or null.
90     * @param {ASTNode|null} node The node to reset, or null.
91     * @returns {void}
92     */
93    reset(node) {
94        this.startNode = this.endNode = node;
95    }
96}
97
98//------------------------------------------------------------------------------
99// Rule Definition
100//------------------------------------------------------------------------------
101
102module.exports = {
103    meta: {
104        type: "problem",
105
106        docs: {
107            description: "disallow unreachable code after `return`, `throw`, `continue`, and `break` statements",
108            category: "Possible Errors",
109            recommended: true,
110            url: "https://eslint.org/docs/rules/no-unreachable"
111        },
112
113        schema: [],
114
115        messages: {
116            unreachableCode: "Unreachable code."
117        }
118    },
119
120    create(context) {
121        let currentCodePath = null;
122
123        const range = new ConsecutiveRange(context.getSourceCode());
124
125        /**
126         * Reports a given node if it's unreachable.
127         * @param {ASTNode} node A statement node to report.
128         * @returns {void}
129         */
130        function reportIfUnreachable(node) {
131            let nextNode = null;
132
133            if (node && currentCodePath.currentSegments.every(isUnreachable)) {
134
135                // Store this statement to distinguish consecutive statements.
136                if (range.isEmpty) {
137                    range.reset(node);
138                    return;
139                }
140
141                // Skip if this statement is inside of the current range.
142                if (range.contains(node)) {
143                    return;
144                }
145
146                // Merge if this statement is consecutive to the current range.
147                if (range.isConsecutive(node)) {
148                    range.merge(node);
149                    return;
150                }
151
152                nextNode = node;
153            }
154
155            /*
156             * Report the current range since this statement is reachable or is
157             * not consecutive to the current range.
158             */
159            if (!range.isEmpty) {
160                context.report({
161                    messageId: "unreachableCode",
162                    loc: range.location,
163                    node: range.startNode
164                });
165            }
166
167            // Update the current range.
168            range.reset(nextNode);
169        }
170
171        return {
172
173            // Manages the current code path.
174            onCodePathStart(codePath) {
175                currentCodePath = codePath;
176            },
177
178            onCodePathEnd() {
179                currentCodePath = currentCodePath.upper;
180            },
181
182            // Registers for all statement nodes (excludes FunctionDeclaration).
183            BlockStatement: reportIfUnreachable,
184            BreakStatement: reportIfUnreachable,
185            ClassDeclaration: reportIfUnreachable,
186            ContinueStatement: reportIfUnreachable,
187            DebuggerStatement: reportIfUnreachable,
188            DoWhileStatement: reportIfUnreachable,
189            ExpressionStatement: reportIfUnreachable,
190            ForInStatement: reportIfUnreachable,
191            ForOfStatement: reportIfUnreachable,
192            ForStatement: reportIfUnreachable,
193            IfStatement: reportIfUnreachable,
194            ImportDeclaration: reportIfUnreachable,
195            LabeledStatement: reportIfUnreachable,
196            ReturnStatement: reportIfUnreachable,
197            SwitchStatement: reportIfUnreachable,
198            ThrowStatement: reportIfUnreachable,
199            TryStatement: reportIfUnreachable,
200
201            VariableDeclaration(node) {
202                if (node.kind !== "var" || node.declarations.some(isInitialized)) {
203                    reportIfUnreachable(node);
204                }
205            },
206
207            WhileStatement: reportIfUnreachable,
208            WithStatement: reportIfUnreachable,
209            ExportNamedDeclaration: reportIfUnreachable,
210            ExportDefaultDeclaration: reportIfUnreachable,
211            ExportAllDeclaration: reportIfUnreachable,
212
213            "Program:exit"() {
214                reportIfUnreachable();
215            }
216        };
217    }
218};
219