mustache-interpolation-spacing.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. /**
  2. * @fileoverview enforce unified spacing in mustache interpolations.
  3. * @author Armano
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. module.exports = {
  8. meta: {
  9. type: 'layout',
  10. docs: {
  11. description: 'enforce unified spacing in mustache interpolations',
  12. categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
  13. url: 'https://eslint.vuejs.org/rules/mustache-interpolation-spacing.html'
  14. },
  15. fixable: 'whitespace',
  16. schema: [
  17. {
  18. enum: ['always', 'never']
  19. }
  20. ],
  21. messages: {
  22. expectedSpaceAfter: "Expected 1 space after '{{', but not found.",
  23. expectedSpaceBefore: "Expected 1 space before '}}', but not found.",
  24. unexpectedSpaceAfter: "Expected no space after '{{', but found.",
  25. unexpectedSpaceBefore: "Expected no space before '}}', but found."
  26. }
  27. },
  28. /** @param {RuleContext} context */
  29. create(context) {
  30. const options = context.options[0] || 'always'
  31. const sourceCode = context.getSourceCode()
  32. const template =
  33. sourceCode.parserServices.getTemplateBodyTokenStore &&
  34. sourceCode.parserServices.getTemplateBodyTokenStore()
  35. return utils.defineTemplateBodyVisitor(context, {
  36. /** @param {VExpressionContainer} node */
  37. 'VExpressionContainer[expression!=null]'(node) {
  38. const openBrace = template.getFirstToken(node)
  39. const closeBrace = template.getLastToken(node)
  40. if (
  41. !openBrace ||
  42. !closeBrace ||
  43. openBrace.type !== 'VExpressionStart' ||
  44. closeBrace.type !== 'VExpressionEnd'
  45. ) {
  46. return
  47. }
  48. const firstToken = template.getTokenAfter(openBrace, {
  49. includeComments: true
  50. })
  51. const lastToken = template.getTokenBefore(closeBrace, {
  52. includeComments: true
  53. })
  54. if (options === 'always') {
  55. if (openBrace.range[1] === firstToken.range[0]) {
  56. context.report({
  57. node: openBrace,
  58. messageId: 'expectedSpaceAfter',
  59. fix: (fixer) => fixer.insertTextAfter(openBrace, ' ')
  60. })
  61. }
  62. if (closeBrace.range[0] === lastToken.range[1]) {
  63. context.report({
  64. node: closeBrace,
  65. messageId: 'expectedSpaceBefore',
  66. fix: (fixer) => fixer.insertTextBefore(closeBrace, ' ')
  67. })
  68. }
  69. } else {
  70. if (openBrace.range[1] !== firstToken.range[0]) {
  71. context.report({
  72. loc: {
  73. start: openBrace.loc.start,
  74. end: firstToken.loc.start
  75. },
  76. messageId: 'unexpectedSpaceAfter',
  77. fix: (fixer) =>
  78. fixer.removeRange([openBrace.range[1], firstToken.range[0]])
  79. })
  80. }
  81. if (closeBrace.range[0] !== lastToken.range[1]) {
  82. context.report({
  83. loc: {
  84. start: lastToken.loc.end,
  85. end: closeBrace.loc.end
  86. },
  87. messageId: 'unexpectedSpaceBefore',
  88. fix: (fixer) =>
  89. fixer.removeRange([lastToken.range[1], closeBrace.range[0]])
  90. })
  91. }
  92. }
  93. }
  94. })
  95. }
  96. }