• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Validates spacing before and after semicolon
3 * @author Mathias Schreck
4 */
5
6"use strict";
7
8const astUtils = require("./utils/ast-utils");
9
10//------------------------------------------------------------------------------
11// Rule Definition
12//------------------------------------------------------------------------------
13
14module.exports = {
15    meta: {
16        type: "layout",
17
18        docs: {
19            description: "enforce consistent spacing before and after semicolons",
20            category: "Stylistic Issues",
21            recommended: false,
22            url: "https://eslint.org/docs/rules/semi-spacing"
23        },
24
25        fixable: "whitespace",
26
27        schema: [
28            {
29                type: "object",
30                properties: {
31                    before: {
32                        type: "boolean",
33                        default: false
34                    },
35                    after: {
36                        type: "boolean",
37                        default: true
38                    }
39                },
40                additionalProperties: false
41            }
42        ],
43
44        messages: {
45            unexpectedWhitespaceBefore: "Unexpected whitespace before semicolon.",
46            unexpectedWhitespaceAfter: "Unexpected whitespace after semicolon.",
47            missingWhitespaceBefore: "Missing whitespace before semicolon.",
48            missingWhitespaceAfter: "Missing whitespace after semicolon."
49        }
50    },
51
52    create(context) {
53
54        const config = context.options[0],
55            sourceCode = context.getSourceCode();
56        let requireSpaceBefore = false,
57            requireSpaceAfter = true;
58
59        if (typeof config === "object") {
60            requireSpaceBefore = config.before;
61            requireSpaceAfter = config.after;
62        }
63
64        /**
65         * Checks if a given token has leading whitespace.
66         * @param {Object} token The token to check.
67         * @returns {boolean} True if the given token has leading space, false if not.
68         */
69        function hasLeadingSpace(token) {
70            const tokenBefore = sourceCode.getTokenBefore(token);
71
72            return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token);
73        }
74
75        /**
76         * Checks if a given token has trailing whitespace.
77         * @param {Object} token The token to check.
78         * @returns {boolean} True if the given token has trailing space, false if not.
79         */
80        function hasTrailingSpace(token) {
81            const tokenAfter = sourceCode.getTokenAfter(token);
82
83            return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter);
84        }
85
86        /**
87         * Checks if the given token is the last token in its line.
88         * @param {Token} token The token to check.
89         * @returns {boolean} Whether or not the token is the last in its line.
90         */
91        function isLastTokenInCurrentLine(token) {
92            const tokenAfter = sourceCode.getTokenAfter(token);
93
94            return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter));
95        }
96
97        /**
98         * Checks if the given token is the first token in its line
99         * @param {Token} token The token to check.
100         * @returns {boolean} Whether or not the token is the first in its line.
101         */
102        function isFirstTokenInCurrentLine(token) {
103            const tokenBefore = sourceCode.getTokenBefore(token);
104
105            return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore));
106        }
107
108        /**
109         * Checks if the next token of a given token is a closing parenthesis.
110         * @param {Token} token The token to check.
111         * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis.
112         */
113        function isBeforeClosingParen(token) {
114            const nextToken = sourceCode.getTokenAfter(token);
115
116            return (nextToken && astUtils.isClosingBraceToken(nextToken) || astUtils.isClosingParenToken(nextToken));
117        }
118
119        /**
120         * Report location example :
121         *
122         * for unexpected space `before`
123         *
124         * var a = 'b'   ;
125         *            ^^^
126         *
127         * for unexpected space `after`
128         *
129         * var a = 'b';  c = 10;
130         *             ^^
131         *
132         * Reports if the given token has invalid spacing.
133         * @param {Token} token The semicolon token to check.
134         * @param {ASTNode} node The corresponding node of the token.
135         * @returns {void}
136         */
137        function checkSemicolonSpacing(token, node) {
138            if (astUtils.isSemicolonToken(token)) {
139                if (hasLeadingSpace(token)) {
140                    if (!requireSpaceBefore) {
141                        const tokenBefore = sourceCode.getTokenBefore(token);
142                        const loc = {
143                            start: tokenBefore.loc.end,
144                            end: token.loc.start
145                        };
146
147                        context.report({
148                            node,
149                            loc,
150                            messageId: "unexpectedWhitespaceBefore",
151                            fix(fixer) {
152
153                                return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
154                            }
155                        });
156                    }
157                } else {
158                    if (requireSpaceBefore) {
159                        const loc = token.loc;
160
161                        context.report({
162                            node,
163                            loc,
164                            messageId: "missingWhitespaceBefore",
165                            fix(fixer) {
166                                return fixer.insertTextBefore(token, " ");
167                            }
168                        });
169                    }
170                }
171
172                if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) {
173                    if (hasTrailingSpace(token)) {
174                        if (!requireSpaceAfter) {
175                            const tokenAfter = sourceCode.getTokenAfter(token);
176                            const loc = {
177                                start: token.loc.end,
178                                end: tokenAfter.loc.start
179                            };
180
181                            context.report({
182                                node,
183                                loc,
184                                messageId: "unexpectedWhitespaceAfter",
185                                fix(fixer) {
186
187                                    return fixer.removeRange([token.range[1], tokenAfter.range[0]]);
188                                }
189                            });
190                        }
191                    } else {
192                        if (requireSpaceAfter) {
193                            const loc = token.loc;
194
195                            context.report({
196                                node,
197                                loc,
198                                messageId: "missingWhitespaceAfter",
199                                fix(fixer) {
200                                    return fixer.insertTextAfter(token, " ");
201                                }
202                            });
203                        }
204                    }
205                }
206            }
207        }
208
209        /**
210         * Checks the spacing of the semicolon with the assumption that the last token is the semicolon.
211         * @param {ASTNode} node The node to check.
212         * @returns {void}
213         */
214        function checkNode(node) {
215            const token = sourceCode.getLastToken(node);
216
217            checkSemicolonSpacing(token, node);
218        }
219
220        return {
221            VariableDeclaration: checkNode,
222            ExpressionStatement: checkNode,
223            BreakStatement: checkNode,
224            ContinueStatement: checkNode,
225            DebuggerStatement: checkNode,
226            ReturnStatement: checkNode,
227            ThrowStatement: checkNode,
228            ImportDeclaration: checkNode,
229            ExportNamedDeclaration: checkNode,
230            ExportAllDeclaration: checkNode,
231            ExportDefaultDeclaration: checkNode,
232            ForStatement(node) {
233                if (node.init) {
234                    checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node);
235                }
236
237                if (node.test) {
238                    checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node);
239                }
240            }
241        };
242    }
243};
244