new-line-between-multi-line-property.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /**
  2. * @fileoverview Enforce new lines between multi-line properties in Vue components.
  3. * @author IWANABETHATGUY
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. /**
  8. * @param {Token} node
  9. */
  10. function isComma(node) {
  11. return node.type === 'Punctuator' && node.value === ','
  12. }
  13. /**
  14. * Check whether the between given nodes has empty line.
  15. * @param {SourceCode} sourceCode
  16. * @param {ASTNode} pre
  17. * @param {ASTNode} cur
  18. */
  19. function* iterateBetweenTokens(sourceCode, pre, cur) {
  20. yield sourceCode.getLastToken(pre)
  21. yield* sourceCode.getTokensBetween(pre, cur, {
  22. includeComments: true
  23. })
  24. yield sourceCode.getFirstToken(cur)
  25. }
  26. /**
  27. * Check whether the between given nodes has empty line.
  28. * @param {SourceCode} sourceCode
  29. * @param {ASTNode} pre
  30. * @param {ASTNode} cur
  31. */
  32. function hasEmptyLine(sourceCode, pre, cur) {
  33. /** @type {Token|null} */
  34. let preToken = null
  35. for (const token of iterateBetweenTokens(sourceCode, pre, cur)) {
  36. if (preToken && token.loc.start.line - preToken.loc.end.line >= 2) {
  37. return true
  38. }
  39. preToken = token
  40. }
  41. return false
  42. }
  43. module.exports = {
  44. meta: {
  45. type: 'layout',
  46. docs: {
  47. description:
  48. 'enforce new lines between multi-line properties in Vue components',
  49. categories: undefined,
  50. url: 'https://eslint.vuejs.org/rules/new-line-between-multi-line-property.html'
  51. },
  52. fixable: 'whitespace',
  53. schema: [
  54. {
  55. type: 'object',
  56. properties: {
  57. // number of line you want to insert after multi-line property
  58. minLineOfMultilineProperty: {
  59. type: 'number',
  60. minimum: 2
  61. }
  62. },
  63. additionalProperties: false
  64. }
  65. ],
  66. messages: {
  67. missingEmptyLine:
  68. 'Enforce new lines between multi-line properties in Vue components.'
  69. }
  70. },
  71. /** @param {RuleContext} context */
  72. create(context) {
  73. let minLineOfMultilineProperty = 2
  74. if (
  75. context.options &&
  76. context.options[0] &&
  77. context.options[0].minLineOfMultilineProperty
  78. ) {
  79. minLineOfMultilineProperty = context.options[0].minLineOfMultilineProperty
  80. }
  81. /** @type {CallExpression[]} */
  82. const callStack = []
  83. const sourceCode = context.getSourceCode()
  84. return Object.assign(
  85. utils.defineVueVisitor(context, {
  86. CallExpression(node) {
  87. callStack.push(node)
  88. },
  89. 'CallExpression:exit'() {
  90. callStack.pop()
  91. },
  92. /**
  93. * @param {ObjectExpression} node
  94. */
  95. ObjectExpression(node) {
  96. if (callStack.length > 0) {
  97. return
  98. }
  99. const properties = node.properties
  100. for (let i = 1; i < properties.length; i++) {
  101. const cur = properties[i]
  102. const pre = properties[i - 1]
  103. const lineCountOfPreProperty =
  104. pre.loc.end.line - pre.loc.start.line + 1
  105. if (lineCountOfPreProperty < minLineOfMultilineProperty) {
  106. continue
  107. }
  108. if (hasEmptyLine(sourceCode, pre, cur)) {
  109. continue
  110. }
  111. context.report({
  112. node: pre,
  113. loc: {
  114. start: pre.loc.end,
  115. end: cur.loc.start
  116. },
  117. messageId: 'missingEmptyLine',
  118. fix(fixer) {
  119. /** @type {Token|null} */
  120. let preToken = null
  121. for (const token of iterateBetweenTokens(
  122. sourceCode,
  123. pre,
  124. cur
  125. )) {
  126. if (
  127. preToken &&
  128. preToken.loc.end.line < token.loc.start.line
  129. ) {
  130. return fixer.insertTextAfter(preToken, '\n')
  131. }
  132. preToken = token
  133. }
  134. const commaToken = sourceCode.getTokenAfter(pre, isComma)
  135. return fixer.insertTextAfter(commaToken || pre, '\n\n')
  136. }
  137. })
  138. }
  139. }
  140. })
  141. )
  142. }
  143. }