valid-define-options.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  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 { findVariable } = require('@eslint-community/eslint-utils')
  7. const utils = require('../utils')
  8. module.exports = {
  9. meta: {
  10. type: 'problem',
  11. docs: {
  12. description: 'enforce valid `defineOptions` compiler macro',
  13. // TODO Switch in the next major version
  14. // categories: ['vue3-essential', 'vue2-essential'],
  15. categories: undefined,
  16. url: 'https://eslint.vuejs.org/rules/valid-define-options.html'
  17. },
  18. fixable: null,
  19. schema: [],
  20. messages: {
  21. referencingLocally:
  22. '`defineOptions` is referencing locally declared variables.',
  23. multiple: '`defineOptions` has been called multiple times.',
  24. notDefined: 'Options are not defined.',
  25. disallowProp:
  26. '`defineOptions()` cannot be used to declare `{{propName}}`. Use `{{insteadMacro}}()` instead.',
  27. typeArgs: '`defineOptions()` cannot accept type arguments.'
  28. }
  29. },
  30. /** @param {RuleContext} context */
  31. create(context) {
  32. const scriptSetup = utils.getScriptSetupElement(context)
  33. if (!scriptSetup) {
  34. return {}
  35. }
  36. /** @type {Set<Expression | SpreadElement>} */
  37. const optionsDefExpressions = new Set()
  38. /** @type {CallExpression[]} */
  39. const defineOptionsNodes = []
  40. return utils.compositingVisitors(
  41. utils.defineScriptSetupVisitor(context, {
  42. onDefineOptionsEnter(node) {
  43. defineOptionsNodes.push(node)
  44. if (node.arguments.length > 0) {
  45. const define = node.arguments[0]
  46. if (define.type === 'ObjectExpression') {
  47. for (const [propName, insteadMacro] of [
  48. ['props', 'defineProps'],
  49. ['emits', 'defineEmits'],
  50. ['expose', 'defineExpose'],
  51. ['slots', 'defineSlots']
  52. ]) {
  53. const prop = utils.findProperty(define, propName)
  54. if (prop) {
  55. context.report({
  56. node,
  57. messageId: 'disallowProp',
  58. data: { propName, insteadMacro }
  59. })
  60. }
  61. }
  62. }
  63. optionsDefExpressions.add(node.arguments[0])
  64. } else {
  65. context.report({
  66. node,
  67. messageId: 'notDefined'
  68. })
  69. }
  70. const typeArguments =
  71. 'typeArguments' in node ? node.typeArguments : node.typeParameters
  72. if (typeArguments) {
  73. context.report({
  74. node: typeArguments,
  75. messageId: 'typeArgs'
  76. })
  77. }
  78. },
  79. Identifier(node) {
  80. for (const defineOptions of optionsDefExpressions) {
  81. if (utils.inRange(defineOptions.range, node)) {
  82. const variable = findVariable(utils.getScope(context, node), node)
  83. if (
  84. variable &&
  85. variable.references.some((ref) => ref.identifier === node) &&
  86. variable.defs.length > 0 &&
  87. variable.defs.every(
  88. (def) =>
  89. def.type !== 'ImportBinding' &&
  90. utils.inRange(scriptSetup.range, def.name) &&
  91. !utils.inRange(defineOptions.range, def.name)
  92. )
  93. ) {
  94. if (utils.withinTypeNode(node)) {
  95. continue
  96. }
  97. //`defineOptions` is referencing locally declared variables.
  98. context.report({
  99. node,
  100. messageId: 'referencingLocally'
  101. })
  102. }
  103. }
  104. }
  105. }
  106. }),
  107. {
  108. 'Program:exit'() {
  109. if (defineOptionsNodes.length > 1) {
  110. // `defineOptions` has been called multiple times.
  111. for (const node of defineOptionsNodes) {
  112. context.report({
  113. node,
  114. messageId: 'multiple'
  115. })
  116. }
  117. }
  118. }
  119. }
  120. )
  121. }
  122. }