• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Rule to enforce spacing around embedded expressions of template strings
3 * @author Toru Nagashima
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const astUtils = require("./utils/ast-utils");
13
14//------------------------------------------------------------------------------
15// Rule Definition
16//------------------------------------------------------------------------------
17
18module.exports = {
19    meta: {
20        type: "layout",
21
22        docs: {
23            description: "require or disallow spacing around embedded expressions of template strings",
24            category: "ECMAScript 6",
25            recommended: false,
26            url: "https://eslint.org/docs/rules/template-curly-spacing"
27        },
28
29        fixable: "whitespace",
30
31        schema: [
32            { enum: ["always", "never"] }
33        ],
34        messages: {
35            expectedBefore: "Expected space(s) before '}'.",
36            expectedAfter: "Expected space(s) after '${'.",
37            unexpectedBefore: "Unexpected space(s) before '}'.",
38            unexpectedAfter: "Unexpected space(s) after '${'."
39        }
40    },
41
42    create(context) {
43        const sourceCode = context.getSourceCode();
44        const always = context.options[0] === "always";
45
46        /**
47         * Checks spacing before `}` of a given token.
48         * @param {Token} token A token to check. This is a Template token.
49         * @returns {void}
50         */
51        function checkSpacingBefore(token) {
52            if (!token.value.startsWith("}")) {
53                return; // starts with a backtick, this is the first template element in the template literal
54            }
55
56            const prevToken = sourceCode.getTokenBefore(token, { includeComments: true }),
57                hasSpace = sourceCode.isSpaceBetween(prevToken, token);
58
59            if (!astUtils.isTokenOnSameLine(prevToken, token)) {
60                return;
61            }
62
63            if (always && !hasSpace) {
64                context.report({
65                    loc: {
66                        start: token.loc.start,
67                        end: {
68                            line: token.loc.start.line,
69                            column: token.loc.start.column + 1
70                        }
71                    },
72                    messageId: "expectedBefore",
73                    fix: fixer => fixer.insertTextBefore(token, " ")
74                });
75            }
76
77            if (!always && hasSpace) {
78                context.report({
79                    loc: {
80                        start: prevToken.loc.end,
81                        end: token.loc.start
82                    },
83                    messageId: "unexpectedBefore",
84                    fix: fixer => fixer.removeRange([prevToken.range[1], token.range[0]])
85                });
86            }
87        }
88
89        /**
90         * Checks spacing after `${` of a given token.
91         * @param {Token} token A token to check. This is a Template token.
92         * @returns {void}
93         */
94        function checkSpacingAfter(token) {
95            if (!token.value.endsWith("${")) {
96                return; // ends with a backtick, this is the last template element in the template literal
97            }
98
99            const nextToken = sourceCode.getTokenAfter(token, { includeComments: true }),
100                hasSpace = sourceCode.isSpaceBetween(token, nextToken);
101
102            if (!astUtils.isTokenOnSameLine(token, nextToken)) {
103                return;
104            }
105
106            if (always && !hasSpace) {
107                context.report({
108                    loc: {
109                        start: {
110                            line: token.loc.end.line,
111                            column: token.loc.end.column - 2
112                        },
113                        end: token.loc.end
114                    },
115                    messageId: "expectedAfter",
116                    fix: fixer => fixer.insertTextAfter(token, " ")
117                });
118            }
119
120            if (!always && hasSpace) {
121                context.report({
122                    loc: {
123                        start: token.loc.end,
124                        end: nextToken.loc.start
125                    },
126                    messageId: "unexpectedAfter",
127                    fix: fixer => fixer.removeRange([token.range[1], nextToken.range[0]])
128                });
129            }
130        }
131
132        return {
133            TemplateElement(node) {
134                const token = sourceCode.getFirstToken(node);
135
136                checkSpacingBefore(token);
137                checkSpacingAfter(token);
138            }
139        };
140    }
141};
142