padding-line-between-blocks.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /**
  2. * @fileoverview Require or disallow padding lines between blocks
  3. * @author Yosuke Ota
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. /**
  8. * Split the source code into multiple lines based on the line delimiters.
  9. * @param {string} text Source code as a string.
  10. * @returns {string[]} Array of source code lines.
  11. */
  12. function splitLines (text) {
  13. return text.split(/\r\n|[\r\n\u2028\u2029]/gu)
  14. }
  15. /**
  16. * Check and report blocks for `never` configuration.
  17. * This autofix removes blank lines between the given 2 blocks.
  18. * @param {RuleContext} context The rule context to report.
  19. * @param {VElement} prevBlock The previous block to check.
  20. * @param {VElement} nextBlock The next block to check.
  21. * @param {Token[]} betweenTokens The array of tokens between blocks.
  22. * @returns {void}
  23. * @private
  24. */
  25. function verifyForNever (context, prevBlock, nextBlock, betweenTokens) {
  26. if (prevBlock.loc.end.line === nextBlock.loc.start.line) {
  27. // same line
  28. return
  29. }
  30. const tokenOrNodes = [...betweenTokens, nextBlock]
  31. let prev = prevBlock
  32. const paddingLines = []
  33. for (const tokenOrNode of tokenOrNodes) {
  34. const numOfLineBreaks = tokenOrNode.loc.start.line - prev.loc.end.line
  35. if (numOfLineBreaks > 1) {
  36. paddingLines.push([prev, tokenOrNode])
  37. }
  38. prev = tokenOrNode
  39. }
  40. if (!paddingLines.length) {
  41. return
  42. }
  43. context.report({
  44. node: nextBlock,
  45. messageId: 'never',
  46. fix (fixer) {
  47. return paddingLines.map(([prevToken, nextToken]) => {
  48. const start = prevToken.range[1]
  49. const end = nextToken.range[0]
  50. const paddingText = context.getSourceCode().text
  51. .slice(start, end)
  52. const lastSpaces = splitLines(paddingText).pop()
  53. return fixer.replaceTextRange([start, end], '\n' + lastSpaces)
  54. })
  55. }
  56. })
  57. }
  58. /**
  59. * Check and report blocks for `always` configuration.
  60. * This autofix inserts a blank line between the given 2 blocks.
  61. * @param {RuleContext} context The rule context to report.
  62. * @param {VElement} prevBlock The previous block to check.
  63. * @param {VElement} nextBlock The next block to check.
  64. * @param {Token[]} betweenTokens The array of tokens between blocks.
  65. * @returns {void}
  66. * @private
  67. */
  68. function verifyForAlways (context, prevBlock, nextBlock, betweenTokens) {
  69. const tokenOrNodes = [...betweenTokens, nextBlock]
  70. let prev = prevBlock
  71. let linebreak
  72. for (const tokenOrNode of tokenOrNodes) {
  73. const numOfLineBreaks = tokenOrNode.loc.start.line - prev.loc.end.line
  74. if (numOfLineBreaks > 1) {
  75. // Already padded.
  76. return
  77. }
  78. if (!linebreak && numOfLineBreaks > 0) {
  79. linebreak = prev
  80. }
  81. prev = tokenOrNode
  82. }
  83. context.report({
  84. node: nextBlock,
  85. messageId: 'always',
  86. fix (fixer) {
  87. if (linebreak) {
  88. return fixer.insertTextAfter(linebreak, '\n')
  89. }
  90. return fixer.insertTextAfter(prevBlock, '\n\n')
  91. }
  92. })
  93. }
  94. /**
  95. * Types of blank lines.
  96. * `never` and `always` are defined.
  97. * Those have `verify` method to check and report statements.
  98. * @private
  99. */
  100. const PaddingTypes = {
  101. never: { verify: verifyForNever },
  102. always: { verify: verifyForAlways }
  103. }
  104. // ------------------------------------------------------------------------------
  105. // Rule Definition
  106. // ------------------------------------------------------------------------------
  107. module.exports = {
  108. meta: {
  109. type: 'layout',
  110. docs: {
  111. description: 'require or disallow padding lines between blocks',
  112. category: undefined,
  113. url: 'https://eslint.vuejs.org/rules/padding-line-between-blocks.html'
  114. },
  115. fixable: 'whitespace',
  116. schema: [
  117. {
  118. enum: Object.keys(PaddingTypes)
  119. }
  120. ],
  121. messages: {
  122. never: 'Unexpected blank line before this block.',
  123. always: 'Expected blank line before this block.'
  124. }
  125. },
  126. create (context) {
  127. const paddingType = PaddingTypes[context.options[0] || 'always']
  128. const documentFragment = context.parserServices.getDocumentFragment && context.parserServices.getDocumentFragment()
  129. let tokens
  130. function getTopLevelHTMLElements () {
  131. if (documentFragment) {
  132. return documentFragment.children.filter(e => e.type === 'VElement')
  133. }
  134. return []
  135. }
  136. function getTokenAndCommentsBetween (prev, next) {
  137. // When there is no <template>, tokenStore.getTokensBetween cannot be used.
  138. if (!tokens) {
  139. tokens = [
  140. ...documentFragment.tokens
  141. .filter(token => token.type !== 'HTMLWhitespace'),
  142. ...documentFragment.comments
  143. ].sort((a, b) => a.range[0] > b.range[0] ? 1 : a.range[0] < b.range[0] ? -1 : 0)
  144. }
  145. let token = tokens.shift()
  146. const results = []
  147. while (token) {
  148. if (prev.range[1] <= token.range[0]) {
  149. if (next.range[0] <= token.range[0]) {
  150. tokens.unshift(token)
  151. break
  152. } else {
  153. results.push(token)
  154. }
  155. }
  156. token = tokens.shift()
  157. }
  158. return results
  159. }
  160. return utils.defineTemplateBodyVisitor(
  161. context,
  162. {},
  163. {
  164. Program (node) {
  165. if (utils.hasInvalidEOF(node)) {
  166. return
  167. }
  168. const elements = [...getTopLevelHTMLElements()]
  169. let prev = elements.shift()
  170. for (const element of elements) {
  171. const betweenTokens = getTokenAndCommentsBetween(prev, element)
  172. paddingType.verify(context, prev, element, betweenTokens)
  173. prev = element
  174. }
  175. }
  176. }
  177. )
  178. }
  179. }