--- /dev/null
+/**
+ * @fileoverview Rule to enforce spacing around embedded expressions of template strings
+ * @author Toru Nagashima
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const OPEN_PAREN = /\$\{$/u;
+const CLOSE_PAREN = /^\}/u;
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "layout",
+
+ docs: {
+ description: "require or disallow spacing around embedded expressions of template strings",
+ category: "ECMAScript 6",
+ recommended: false,
+ url: "https://eslint.org/docs/rules/template-curly-spacing"
+ },
+
+ fixable: "whitespace",
+
+ schema: [
+ { enum: ["always", "never"] }
+ ],
+ messages: {
+ expectedBefore: "Expected space(s) before '}'.",
+ expectedAfter: "Expected space(s) after '${'.",
+ unexpectedBefore: "Unexpected space(s) before '}'.",
+ unexpectedAfter: "Unexpected space(s) after '${'."
+ }
+ },
+
+ create(context) {
+ const sourceCode = context.getSourceCode();
+ const always = context.options[0] === "always";
+ const prefix = always ? "expected" : "unexpected";
+
+ /**
+ * Checks spacing before `}` of a given token.
+ * @param {Token} token A token to check. This is a Template token.
+ * @returns {void}
+ */
+ function checkSpacingBefore(token) {
+ const prevToken = sourceCode.getTokenBefore(token);
+
+ if (prevToken &&
+ CLOSE_PAREN.test(token.value) &&
+ astUtils.isTokenOnSameLine(prevToken, token) &&
+ sourceCode.isSpaceBetweenTokens(prevToken, token) !== always
+ ) {
+ context.report({
+ loc: token.loc.start,
+ messageId: `${prefix}Before`,
+ fix(fixer) {
+ if (always) {
+ return fixer.insertTextBefore(token, " ");
+ }
+ return fixer.removeRange([
+ prevToken.range[1],
+ token.range[0]
+ ]);
+ }
+ });
+ }
+ }
+
+ /**
+ * Checks spacing after `${` of a given token.
+ * @param {Token} token A token to check. This is a Template token.
+ * @returns {void}
+ */
+ function checkSpacingAfter(token) {
+ const nextToken = sourceCode.getTokenAfter(token);
+
+ if (nextToken &&
+ OPEN_PAREN.test(token.value) &&
+ astUtils.isTokenOnSameLine(token, nextToken) &&
+ sourceCode.isSpaceBetweenTokens(token, nextToken) !== always
+ ) {
+ context.report({
+ loc: {
+ line: token.loc.end.line,
+ column: token.loc.end.column - 2
+ },
+ messageId: `${prefix}After`,
+ fix(fixer) {
+ if (always) {
+ return fixer.insertTextAfter(token, " ");
+ }
+ return fixer.removeRange([
+ token.range[1],
+ nextToken.range[0]
+ ]);
+ }
+ });
+ }
+ }
+
+ return {
+ TemplateElement(node) {
+ const token = sourceCode.getFirstToken(node);
+
+ checkSpacingBefore(token);
+ checkSpacingAfter(token);
+ }
+ };
+ }
+};