html-closing-bracket-newline.js 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  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. docs: {
  27. description: "require or disallow a line break before tag's closing brackets",
  28. category: undefined,
  29. url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.7.1/docs/rules/html-closing-bracket-newline.md'
  30. },
  31. fixable: 'whitespace',
  32. schema: [{
  33. type: 'object',
  34. properties: {
  35. 'singleline': { enum: ['always', 'never'] },
  36. 'multiline': { enum: ['always', 'never'] }
  37. },
  38. additionalProperties: false
  39. }]
  40. },
  41. create (context) {
  42. const options = context.options[0] || {}
  43. const template = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore()
  44. return utils.defineTemplateBodyVisitor(context, {
  45. 'VStartTag, VEndTag' (node) {
  46. const closingBracketToken = template.getLastToken(node)
  47. if (closingBracketToken.type !== 'HTMLSelfClosingTagClose' && closingBracketToken.type !== 'HTMLTagClose') {
  48. return
  49. }
  50. const prevToken = template.getTokenBefore(closingBracketToken)
  51. const type = (node.loc.start.line === prevToken.loc.end.line) ? 'singleline' : 'multiline'
  52. const expectedLineBreaks = (options[type] === 'always') ? 1 : 0
  53. const actualLineBreaks = (closingBracketToken.loc.start.line - prevToken.loc.end.line)
  54. if (actualLineBreaks !== expectedLineBreaks) {
  55. context.report({
  56. node,
  57. loc: {
  58. start: prevToken.loc.end,
  59. end: closingBracketToken.loc.start
  60. },
  61. message: 'Expected {{expected}} before closing bracket, but {{actual}} found.',
  62. data: {
  63. expected: getPhrase(expectedLineBreaks),
  64. actual: getPhrase(actualLineBreaks)
  65. },
  66. fix (fixer) {
  67. const range = [prevToken.range[1], closingBracketToken.range[0]]
  68. const text = '\n'.repeat(expectedLineBreaks)
  69. return fixer.replaceTextRange(range, text)
  70. }
  71. })
  72. }
  73. }
  74. })
  75. }
  76. }