html-closing-bracket-spacing.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. /**
  2. * @author Toru Nagashima <https://github.com/mysticatea>
  3. */
  4. 'use strict'
  5. // -----------------------------------------------------------------------------
  6. // Requirements
  7. // -----------------------------------------------------------------------------
  8. const utils = require('../utils')
  9. // -----------------------------------------------------------------------------
  10. // Helpers
  11. // -----------------------------------------------------------------------------
  12. /**
  13. * Normalize options.
  14. * @param {{startTag?:"always"|"never",endTag?:"always"|"never",selfClosingTag?:"always"|"never"}} options The options user configured.
  15. * @param {TokenStore} tokens The token store of template body.
  16. * @returns {{startTag:"always"|"never",endTag:"always"|"never",selfClosingTag:"always"|"never"}} The normalized options.
  17. */
  18. function parseOptions (options, tokens) {
  19. return Object.assign({
  20. startTag: 'never',
  21. endTag: 'never',
  22. selfClosingTag: 'always',
  23. detectType (node) {
  24. const openType = tokens.getFirstToken(node).type
  25. const closeType = tokens.getLastToken(node).type
  26. if (openType === 'HTMLEndTagOpen' && closeType === 'HTMLTagClose') {
  27. return this.endTag
  28. }
  29. if (openType === 'HTMLTagOpen' && closeType === 'HTMLTagClose') {
  30. return this.startTag
  31. }
  32. if (openType === 'HTMLTagOpen' && closeType === 'HTMLSelfClosingTagClose') {
  33. return this.selfClosingTag
  34. }
  35. return null
  36. }
  37. }, options)
  38. }
  39. // -----------------------------------------------------------------------------
  40. // Rule Definition
  41. // -----------------------------------------------------------------------------
  42. module.exports = {
  43. meta: {
  44. type: 'layout',
  45. docs: {
  46. description: 'require or disallow a space before tag\'s closing brackets',
  47. category: 'strongly-recommended',
  48. url: 'https://eslint.vuejs.org/rules/html-closing-bracket-spacing.html'
  49. },
  50. schema: [{
  51. type: 'object',
  52. properties: {
  53. startTag: { enum: ['always', 'never'] },
  54. endTag: { enum: ['always', 'never'] },
  55. selfClosingTag: { enum: ['always', 'never'] }
  56. },
  57. additionalProperties: false
  58. }],
  59. fixable: 'whitespace'
  60. },
  61. create (context) {
  62. const sourceCode = context.getSourceCode()
  63. const tokens =
  64. context.parserServices.getTemplateBodyTokenStore &&
  65. context.parserServices.getTemplateBodyTokenStore()
  66. const options = parseOptions(context.options[0], tokens)
  67. return utils.defineTemplateBodyVisitor(context, {
  68. 'VStartTag, VEndTag' (node) {
  69. const type = options.detectType(node)
  70. const lastToken = tokens.getLastToken(node)
  71. const prevToken = tokens.getLastToken(node, 1)
  72. // Skip if EOF exists in the tag or linebreak exists before `>`.
  73. if (type == null || prevToken == null || prevToken.loc.end.line !== lastToken.loc.start.line) {
  74. return
  75. }
  76. // Check and report.
  77. const hasSpace = (prevToken.range[1] !== lastToken.range[0])
  78. if (type === 'always' && !hasSpace) {
  79. context.report({
  80. node,
  81. loc: lastToken.loc,
  82. message: "Expected a space before '{{bracket}}', but not found.",
  83. data: { bracket: sourceCode.getText(lastToken) },
  84. fix: (fixer) => fixer.insertTextBefore(lastToken, ' ')
  85. })
  86. } else if (type === 'never' && hasSpace) {
  87. context.report({
  88. node,
  89. loc: {
  90. start: prevToken.loc.end,
  91. end: lastToken.loc.end
  92. },
  93. message: "Expected no space before '{{bracket}}', but found.",
  94. data: { bracket: sourceCode.getText(lastToken) },
  95. fix: (fixer) => fixer.removeRange([prevToken.range[1], lastToken.range[0]])
  96. })
  97. }
  98. }
  99. })
  100. }
  101. }