new-line-between-multi-line-property.js 4.1 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. // ------------------------------------------------------------------------------
  44. // Rule Definition
  45. // ------------------------------------------------------------------------------
  46. module.exports = {
  47. meta: {
  48. type: 'layout',
  49. docs: {
  50. description:
  51. 'enforce new lines between multi-line properties in Vue components',
  52. categories: undefined,
  53. url: 'https://eslint.vuejs.org/rules/new-line-between-multi-line-property.html'
  54. },
  55. fixable: 'whitespace', // or "code" or "whitespace"
  56. schema: [
  57. {
  58. type: 'object',
  59. properties: {
  60. // number of line you want to insert after multi-line property
  61. minLineOfMultilineProperty: {
  62. type: 'number',
  63. minimum: 2
  64. }
  65. },
  66. additionalProperties: false
  67. }
  68. ]
  69. },
  70. /** @param {RuleContext} context */
  71. create(context) {
  72. let minLineOfMultilineProperty = 2
  73. if (
  74. context.options &&
  75. context.options[0] &&
  76. context.options[0].minLineOfMultilineProperty
  77. ) {
  78. minLineOfMultilineProperty = context.options[0].minLineOfMultilineProperty
  79. }
  80. /** @type {CallExpression[]} */
  81. const callStack = []
  82. const sourceCode = context.getSourceCode()
  83. return Object.assign(
  84. utils.defineVueVisitor(context, {
  85. CallExpression(node) {
  86. callStack.push(node)
  87. },
  88. 'CallExpression:exit'() {
  89. callStack.pop()
  90. },
  91. /**
  92. * @param {ObjectExpression} node
  93. */
  94. ObjectExpression(node) {
  95. if (callStack.length) {
  96. return
  97. }
  98. const properties = node.properties
  99. for (let i = 1; i < properties.length; i++) {
  100. const cur = properties[i]
  101. const pre = properties[i - 1]
  102. const lineCountOfPreProperty =
  103. pre.loc.end.line - pre.loc.start.line + 1
  104. if (lineCountOfPreProperty < minLineOfMultilineProperty) {
  105. continue
  106. }
  107. if (hasEmptyLine(sourceCode, pre, cur)) {
  108. continue
  109. }
  110. context.report({
  111. node: pre,
  112. loc: {
  113. start: pre.loc.end,
  114. end: cur.loc.start
  115. },
  116. message:
  117. 'Enforce new lines between multi-line properties in Vue components.',
  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. }