valid-define-options.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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', '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. if (node.typeParameters) {
  71. context.report({
  72. node: node.typeParameters,
  73. messageId: 'typeArgs'
  74. })
  75. }
  76. },
  77. Identifier(node) {
  78. for (const defineOptions of optionsDefExpressions) {
  79. if (utils.inRange(defineOptions.range, node)) {
  80. const variable = findVariable(context.getScope(), node)
  81. if (
  82. variable &&
  83. variable.references.some((ref) => ref.identifier === node) &&
  84. variable.defs.length > 0 &&
  85. variable.defs.every(
  86. (def) =>
  87. def.type !== 'ImportBinding' &&
  88. utils.inRange(scriptSetup.range, def.name) &&
  89. !utils.inRange(defineOptions.range, def.name)
  90. )
  91. ) {
  92. if (utils.withinTypeNode(node)) {
  93. continue
  94. }
  95. //`defineOptions` is referencing locally declared variables.
  96. context.report({
  97. node,
  98. messageId: 'referencingLocally'
  99. })
  100. }
  101. }
  102. }
  103. }
  104. }),
  105. {
  106. 'Program:exit'() {
  107. if (defineOptionsNodes.length > 1) {
  108. // `defineOptions` has been called multiple times.
  109. for (const node of defineOptionsNodes) {
  110. context.report({
  111. node,
  112. messageId: 'multiple'
  113. })
  114. }
  115. }
  116. }
  117. }
  118. )
  119. }
  120. }