template-curly-spacing.js 4.7 KB

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