html-closing-bracket-newline.js 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. /**
  2. * @author Toru Nagashima
  3. * @copyright 2016 Toru Nagashima. All rights reserved.
  4. * See LICENSE file in root directory for full license.
  5. */
  6. 'use strict'
  7. // ------------------------------------------------------------------------------
  8. // Requirements
  9. // ------------------------------------------------------------------------------
  10. const utils = require('../utils')
  11. // ------------------------------------------------------------------------------
  12. // Helpers
  13. // ------------------------------------------------------------------------------
  14. function getPhrase (lineBreaks) {
  15. switch (lineBreaks) {
  16. case 0: return 'no line breaks'
  17. case 1: return '1 line break'
  18. default: return `${lineBreaks} line breaks`
  19. }
  20. }
  21. // ------------------------------------------------------------------------------
  22. // Rule Definition
  23. // ------------------------------------------------------------------------------
  24. module.exports = {
  25. meta: {
  26. type: 'layout',
  27. docs: {
  28. description: "require or disallow a line break before tag's closing brackets",
  29. category: 'strongly-recommended',
  30. url: 'https://eslint.vuejs.org/rules/html-closing-bracket-newline.html'
  31. },
  32. fixable: 'whitespace',
  33. schema: [{
  34. type: 'object',
  35. properties: {
  36. 'singleline': { enum: ['always', 'never'] },
  37. 'multiline': { enum: ['always', 'never'] }
  38. },
  39. additionalProperties: false
  40. }]
  41. },
  42. create (context) {
  43. const options = Object.assign({}, {
  44. singleline: 'never',
  45. multiline: 'always'
  46. }, context.options[0] || {})
  47. const template = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore()
  48. return utils.defineTemplateBodyVisitor(context, {
  49. 'VStartTag, VEndTag' (node) {
  50. const closingBracketToken = template.getLastToken(node)
  51. if (closingBracketToken.type !== 'HTMLSelfClosingTagClose' && closingBracketToken.type !== 'HTMLTagClose') {
  52. return
  53. }
  54. const prevToken = template.getTokenBefore(closingBracketToken)
  55. const type = (node.loc.start.line === prevToken.loc.end.line) ? 'singleline' : 'multiline'
  56. const expectedLineBreaks = (options[type] === 'always') ? 1 : 0
  57. const actualLineBreaks = (closingBracketToken.loc.start.line - prevToken.loc.end.line)
  58. if (actualLineBreaks !== expectedLineBreaks) {
  59. context.report({
  60. node,
  61. loc: {
  62. start: prevToken.loc.end,
  63. end: closingBracketToken.loc.start
  64. },
  65. message: 'Expected {{expected}} before closing bracket, but {{actual}} found.',
  66. data: {
  67. expected: getPhrase(expectedLineBreaks),
  68. actual: getPhrase(actualLineBreaks)
  69. },
  70. fix (fixer) {
  71. const range = [prevToken.range[1], closingBracketToken.range[0]]
  72. const text = '\n'.repeat(expectedLineBreaks)
  73. return fixer.replaceTextRange(range, text)
  74. }
  75. })
  76. }
  77. }
  78. })
  79. }
  80. }