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