• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Rule to flag use of unnecessary semicolons
3 * @author Nicholas C. Zakas
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const FixTracker = require("./utils/fix-tracker");
13const astUtils = require("./utils/ast-utils");
14
15//------------------------------------------------------------------------------
16// Rule Definition
17//------------------------------------------------------------------------------
18
19module.exports = {
20    meta: {
21        type: "suggestion",
22
23        docs: {
24            description: "disallow unnecessary semicolons",
25            category: "Possible Errors",
26            recommended: true,
27            url: "https://eslint.org/docs/rules/no-extra-semi"
28        },
29
30        fixable: "code",
31        schema: [],
32
33        messages: {
34            unexpected: "Unnecessary semicolon."
35        }
36    },
37
38    create(context) {
39        const sourceCode = context.getSourceCode();
40
41        /**
42         * Reports an unnecessary semicolon error.
43         * @param {Node|Token} nodeOrToken A node or a token to be reported.
44         * @returns {void}
45         */
46        function report(nodeOrToken) {
47            context.report({
48                node: nodeOrToken,
49                messageId: "unexpected",
50                fix(fixer) {
51
52                    /*
53                     * Expand the replacement range to include the surrounding
54                     * tokens to avoid conflicting with semi.
55                     * https://github.com/eslint/eslint/issues/7928
56                     */
57                    return new FixTracker(fixer, context.getSourceCode())
58                        .retainSurroundingTokens(nodeOrToken)
59                        .remove(nodeOrToken);
60                }
61            });
62        }
63
64        /**
65         * Checks for a part of a class body.
66         * This checks tokens from a specified token to a next MethodDefinition or the end of class body.
67         * @param {Token} firstToken The first token to check.
68         * @returns {void}
69         */
70        function checkForPartOfClassBody(firstToken) {
71            for (let token = firstToken;
72                token.type === "Punctuator" && !astUtils.isClosingBraceToken(token);
73                token = sourceCode.getTokenAfter(token)
74            ) {
75                if (astUtils.isSemicolonToken(token)) {
76                    report(token);
77                }
78            }
79        }
80
81        return {
82
83            /**
84             * Reports this empty statement, except if the parent node is a loop.
85             * @param {Node} node A EmptyStatement node to be reported.
86             * @returns {void}
87             */
88            EmptyStatement(node) {
89                const parent = node.parent,
90                    allowedParentTypes = [
91                        "ForStatement",
92                        "ForInStatement",
93                        "ForOfStatement",
94                        "WhileStatement",
95                        "DoWhileStatement",
96                        "IfStatement",
97                        "LabeledStatement",
98                        "WithStatement"
99                    ];
100
101                if (allowedParentTypes.indexOf(parent.type) === -1) {
102                    report(node);
103                }
104            },
105
106            /**
107             * Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body.
108             * @param {Node} node A ClassBody node to check.
109             * @returns {void}
110             */
111            ClassBody(node) {
112                checkForPartOfClassBody(sourceCode.getFirstToken(node, 1)); // 0 is `{`.
113            },
114
115            /**
116             * Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body.
117             * @param {Node} node A MethodDefinition node of the start point.
118             * @returns {void}
119             */
120            MethodDefinition(node) {
121                checkForPartOfClassBody(sourceCode.getTokenAfter(node));
122            }
123        };
124
125    }
126};
127