dec4d5de5630a004136bebbbc200b17e4641573e6c811e1b565cd641b72ff428a5b83d9e2215ceccb1e3184a6ce1de16145132c4c078eafd4ecef579962469 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. /**
  2. * @author Yosuke Ota
  3. * issue https://github.com/vuejs/eslint-plugin-vue/issues/140
  4. */
  5. 'use strict'
  6. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const utils = require('../utils')
  10. const DEFAULT_ORDER = Object.freeze([['script', 'template'], 'style'])
  11. // ------------------------------------------------------------------------------
  12. // Rule Definition
  13. // ------------------------------------------------------------------------------
  14. module.exports = {
  15. meta: {
  16. type: 'suggestion',
  17. docs: {
  18. description: 'enforce order of component top-level elements',
  19. categories: ['vue3-recommended', 'recommended'],
  20. url: 'https://eslint.vuejs.org/rules/component-tags-order.html'
  21. },
  22. fixable: null,
  23. schema: [
  24. {
  25. type: 'object',
  26. properties: {
  27. order: {
  28. type: 'array',
  29. items: {
  30. anyOf: [
  31. { type: 'string' },
  32. { type: 'array', items: { type: 'string' }, uniqueItems: true }
  33. ]
  34. },
  35. uniqueItems: true,
  36. additionalItems: false
  37. }
  38. }
  39. }
  40. ],
  41. messages: {
  42. unexpected:
  43. 'The <{{name}}> should be above the <{{firstUnorderedName}}> on line {{line}}.'
  44. }
  45. },
  46. /**
  47. * @param {RuleContext} context - The rule context.
  48. * @returns {RuleListener} AST event handlers.
  49. */
  50. create(context) {
  51. /** @type {Map<string, number>} */
  52. const orderMap = new Map()
  53. /** @type {(string|string[])[]} */
  54. const orderOptions =
  55. (context.options[0] && context.options[0].order) || DEFAULT_ORDER
  56. orderOptions.forEach((nameOrNames, index) => {
  57. if (Array.isArray(nameOrNames)) {
  58. for (const name of nameOrNames) {
  59. orderMap.set(name, index)
  60. }
  61. } else {
  62. orderMap.set(nameOrNames, index)
  63. }
  64. })
  65. /**
  66. * @param {string} name
  67. */
  68. function getOrderPosition(name) {
  69. const num = orderMap.get(name)
  70. return num == null ? -1 : num
  71. }
  72. const documentFragment =
  73. context.parserServices.getDocumentFragment &&
  74. context.parserServices.getDocumentFragment()
  75. function getTopLevelHTMLElements() {
  76. if (documentFragment) {
  77. return documentFragment.children.filter(utils.isVElement)
  78. }
  79. return []
  80. }
  81. /**
  82. * @param {VElement} element
  83. * @param {VElement} firstUnorderedElement
  84. */
  85. function report(element, firstUnorderedElement) {
  86. context.report({
  87. node: element,
  88. loc: element.loc,
  89. messageId: 'unexpected',
  90. data: {
  91. name: element.name,
  92. firstUnorderedName: firstUnorderedElement.name,
  93. line: firstUnorderedElement.loc.start.line
  94. }
  95. })
  96. }
  97. return {
  98. Program(node) {
  99. if (utils.hasInvalidEOF(node)) {
  100. return
  101. }
  102. const elements = getTopLevelHTMLElements()
  103. elements.forEach((element, index) => {
  104. const expectedIndex = getOrderPosition(element.name)
  105. if (expectedIndex < 0) {
  106. return
  107. }
  108. const firstUnordered = elements
  109. .slice(0, index)
  110. .filter((e) => expectedIndex < getOrderPosition(e.name))
  111. .sort(
  112. (e1, e2) => getOrderPosition(e1.name) - getOrderPosition(e2.name)
  113. )[0]
  114. if (firstUnordered) {
  115. report(element, firstUnordered)
  116. }
  117. })
  118. }
  119. }
  120. }
  121. }