prefer-define-options.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. /**
  2. * @author Yosuke Ota <https://github.com/ota-meshi>
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. module.exports = {
  8. meta: {
  9. type: 'suggestion',
  10. docs: {
  11. description: 'enforce use of `defineOptions` instead of default export',
  12. categories: undefined,
  13. url: 'https://eslint.vuejs.org/rules/prefer-define-options.html'
  14. },
  15. fixable: 'code',
  16. schema: [],
  17. messages: {
  18. preferDefineOptions: 'Use `defineOptions` instead of default export.'
  19. }
  20. },
  21. /**
  22. * @param {RuleContext} context
  23. * @returns {RuleListener}
  24. */
  25. create(context) {
  26. const scriptSetup = utils.getScriptSetupElement(context)
  27. if (!scriptSetup) {
  28. return {}
  29. }
  30. /** @type {CallExpression | null} */
  31. let defineOptionsNode = null
  32. /** @type {ExportDefaultDeclaration | null} */
  33. let exportDefaultDeclaration = null
  34. /** @type {ImportDeclaration|null} */
  35. let lastImportDeclaration = null
  36. return utils.compositingVisitors(
  37. utils.defineScriptSetupVisitor(context, {
  38. ImportDeclaration(node) {
  39. lastImportDeclaration = node
  40. },
  41. onDefineOptionsEnter(node) {
  42. defineOptionsNode = node
  43. }
  44. }),
  45. {
  46. ExportDefaultDeclaration(node) {
  47. exportDefaultDeclaration = node
  48. },
  49. 'Program:exit'() {
  50. if (!exportDefaultDeclaration) {
  51. return
  52. }
  53. context.report({
  54. node: exportDefaultDeclaration,
  55. messageId: 'preferDefineOptions',
  56. fix: defineOptionsNode
  57. ? null
  58. : buildFix(exportDefaultDeclaration, scriptSetup)
  59. })
  60. }
  61. }
  62. )
  63. /**
  64. * @param {ExportDefaultDeclaration} node
  65. * @param {VElement} scriptSetup
  66. * @returns {(fixer: RuleFixer) => Fix[]}
  67. */
  68. function buildFix(node, scriptSetup) {
  69. return (fixer) => {
  70. const sourceCode = context.getSourceCode()
  71. // Calc remove range
  72. /** @type {Range} */
  73. let removeRange = [...node.range]
  74. const script = scriptSetup.parent.children
  75. .filter(utils.isVElement)
  76. .find(
  77. (node) =>
  78. node.name === 'script' && !utils.hasAttribute(node, 'setup')
  79. )
  80. if (
  81. script &&
  82. script.endTag &&
  83. sourceCode
  84. .getTokensBetween(script.startTag, script.endTag, {
  85. includeComments: true
  86. })
  87. .every(
  88. (token) =>
  89. removeRange[0] <= token.range[0] &&
  90. token.range[1] <= removeRange[1]
  91. )
  92. ) {
  93. removeRange = [...script.range]
  94. }
  95. const removeStartLoc = sourceCode.getLocFromIndex(removeRange[0])
  96. if (
  97. sourceCode.lines[removeStartLoc.line - 1]
  98. .slice(0, removeStartLoc.column)
  99. .trim() === ''
  100. ) {
  101. removeRange[0] =
  102. removeStartLoc.line === 1
  103. ? 0
  104. : sourceCode.getIndexFromLoc({
  105. line: removeStartLoc.line - 1,
  106. column: sourceCode.lines[removeStartLoc.line - 2].length
  107. })
  108. }
  109. /** @type {VStartTag | ImportDeclaration} */
  110. const insertAfterTag = lastImportDeclaration || scriptSetup.startTag
  111. return [
  112. fixer.removeRange(removeRange),
  113. fixer.insertTextAfter(
  114. insertAfterTag,
  115. `\ndefineOptions(${sourceCode.getText(node.declaration)})\n`
  116. )
  117. ]
  118. }
  119. }
  120. }
  121. }