code:
/**
 * @fileoverview Disallow undefined properties.
 * @author Yosuke Ota
 */
'use strict'

const utils = require('../utils')
const reserved = require('../utils/vue-reserved.json')
const { toRegExp } = require('../utils/regexp')
const { getStyleVariablesContext } = require('../utils/style-variables')
const {
  definePropertyReferenceExtractor
} = require('../utils/property-references')

/**
 * @typedef {import('../utils').VueObjectData} VueObjectData
 * @typedef {import('../utils/property-references').IPropertyReferences} IPropertyReferences
 */
/**
 * @typedef {object} PropertyData
 * @property {boolean} [hasNestProperty]
 * @property { (name: string) => PropertyData | null } [get]
 * @property {boolean} [isProps]
 */

const GROUP_PROPERTY = 'props'
const GROUP_ASYNC_DATA = 'asyncData' // Nuxt.js
const GROUP_DATA = 'data'
const GROUP_COMPUTED_PROPERTY = 'computed'
const GROUP_METHODS = 'methods'
const GROUP_SETUP = 'setup'
const GROUP_WATCHER = 'watch'
const GROUP_EXPOSE = 'expose'
const GROUP_INJECT = 'inject'

/**
 * @param {ObjectExpression} object
 * @returns {Map<string, Property> | null}
 */
function getObjectPropertyMap(object) {
  /** @type {Map<string, Property>} */
  const props = new Map()
  for (const p of object.properties) {
    if (p.type !== 'Property') {
      return null
    }
    const name = utils.getStaticPropertyName(p)
    if (name == null) {
      return null
    }
    props.set(name, p)
  }
  return props
}

/**
 * @param {Property | undefined} property
 * @returns {PropertyData | null}
 */
function getPropertyDataFromObjectProperty(property) {
  if (property == null) {
    return null
  }
  const propertyMap =
    property.value.type === 'ObjectExpression'
      ? getObjectPropertyMap(property.value)
      : null
  return {
    hasNestProperty: Boolean(propertyMap),
    get(name) {
      if (!propertyMap) {
        return null
      }
      return getPropertyDataFromObjectProperty(propertyMap.get(name))
    }
  }
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow undefined properties',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-undef-properties.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          ignores: {
            type: 'array',
            items: { type: 'string' },
            uniqueItems: true
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      undef: "'{{name}}' is not defined.",
      undefProps: "'{{name}}' is not defined in props."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || {}
    const ignores = /** @type {string[]} */ (
      options.ignores || [String.raw`/^\$/`]
    ).map(toRegExp)
    const propertyReferenceExtractor = definePropertyReferenceExtractor(context)
    const programNode = context.getSourceCode().ast
    /**
     * Property names identified as defined via a Vuex or Pinia helpers
     * @type {Set<string>}
     */
    const propertiesDefinedByStoreHelpers = new Set()

    /**
     * @param {ASTNode} node
     */
    function isScriptSetupProgram(node) {
      return node === programNode
    }

    /** Vue component context */
    class VueComponentContext {
      constructor() {
        /** @type { Map<string, PropertyData> } */
        this.defineProperties = new Map()

        /** @type { Set<string | ASTNode> } */
        this.reported = new Set()

        this.hasUnknownProperty = false
      }
      /**
       * Report
       * @param {IPropertyReferences} references
       * @param {object} [options]
       * @param {boolean} [options.props]
       */
      verifyReferences(references, options) {
        if (this.hasUnknownProperty) return
        const report = this.report.bind(this)
        verifyUndefProperties(this.defineProperties, references, null)

        /**
         * @param { { get?: (name: string) => PropertyData | null | undefined } } defineProperties
         * @param {IPropertyReferences|null} references
         * @param {string|null} pathName
         */
        function verifyUndefProperties(defineProperties, references, pathName) {
          if (!references) {
            return
          }
          for (const [refName, { nodes }] of references.allProperties()) {
            const referencePathName = pathName
              ? `${pathName}.${refName}`
              : refName

            const prop = defineProperties.get && defineProperties.get(refName)
            if (prop) {
              if (options && options.props && !prop.isProps) {
                report(nodes[0], referencePathName, 'undefProps')
                continue
              }
            } else {
              report(nodes[0], referencePathName, 'undef')
              continue
            }

            if (prop.hasNestProperty) {
              verifyUndefProperties(
                prop,
                references.getNest(refName),
                referencePathName
              )
            }
          }
        }
      }
      /**
       * Report
       * @param {ASTNode} node
       * @param {string} name
       * @param {'undef' | 'undefProps'} messageId
       */
      report(node, name, messageId = 'undef') {
        if (
          reserved.includes(name) ||
          ignores.some((ignore) => ignore.test(name)) ||
          propertiesDefinedByStoreHelpers.has(name)
        ) {
          return
        }
        if (
          // Prevents reporting to the same node.
          this.reported.has(node) ||
          // Prevents reports with the same name.
          // This is so that intentional undefined properties can be resolved with
          // a single warning suppression comment (`// eslint-disable-line`).
          this.reported.has(name)
        ) {
          return
        }
        this.reported.add(node)
        this.reported.add(name)
        context.report({
          node,
          messageId,
          data: {
            name
          }
        })
      }

      markAsHasUnknownProperty() {
        this.hasUnknownProperty = true
      }
    }

    /** @type {Map<ASTNode, VueComponentContext>} */
    const vueComponentContextMap = new Map()

    /**
     * @param {ASTNode} node
     * @returns {VueComponentContext}
     */
    function getVueComponentContext(node) {
      let ctx = vueComponentContextMap.get(node)
      if (!ctx) {
        ctx = new VueComponentContext()
        vueComponentContextMap.set(node, ctx)
      }
      return ctx
    }
    /**
     * @returns {VueComponentContext|void}
     */
    function getVueComponentContextForTemplate() {
      const keys = [...vueComponentContextMap.keys()]
      const exported =
        keys.find(isScriptSetupProgram) || keys.find(utils.isInExportDefault)
      return exported && vueComponentContextMap.get(exported)
    }

    /**
     * @param {Expression} node
     * @returns {Property|null}
     */
    function getParentProperty(node) {
      if (
        !node.parent ||
        node.parent.type !== 'Property' ||
        node.parent.value !== node
      ) {
        return null
      }
      const property = node.parent
      if (!utils.isProperty(property)) {
        return null
      }
      return property
    }

    const scriptVisitor = utils.compositingVisitors(
      {
        Program() {
          if (!utils.isScriptSetup(context)) {
            return
          }

          const ctx = getVueComponentContext(programNode)
          const globalScope = context.getSourceCode().scopeManager.globalScope
          if (globalScope) {
            for (const variable of globalScope.variables) {
              ctx.defineProperties.set(variable.name, {})
            }
            const moduleScope = globalScope.childScopes.find(
              (scope) => scope.type === 'module'
            )
            for (const variable of (moduleScope && moduleScope.variables) ||
              []) {
              ctx.defineProperties.set(variable.name, {})
            }
          }
        }
      },
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(node, props) {
          const ctx = getVueComponentContext(programNode)

          for (const prop of props) {
            if (prop.type === 'unknown') {
              ctx.markAsHasUnknownProperty()
              return
            }
            if (!prop.propName) {
              continue
            }
            ctx.defineProperties.set(prop.propName, {
              isProps: true
            })
          }
          let target = node
          if (
            target.parent &&
            target.parent.type === 'CallExpression' &&
            target.parent.arguments[0] === target &&
            target.parent.callee.type === 'Identifier' &&
            target.parent.callee.name === 'withDefaults'
          ) {
            target = target.parent
          }

          if (
            !target.parent ||
            target.parent.type !== 'VariableDeclarator' ||
            target.parent.init !== target
          ) {
            return
          }

          const pattern = target.parent.id
          const propertyReferences =
            propertyReferenceExtractor.extractFromPattern(pattern)
          ctx.verifyReferences(propertyReferences)
        },
        onDefineModelEnter(_node, model) {
          const ctx = getVueComponentContext(programNode)

          ctx.defineProperties.set(model.name.modelName, {
            isProps: true
          })
        }
      }),
      utils.defineVueVisitor(context, {
        /**
         * @param {CallExpression} node
         */
        CallExpression(node) {
          if (node.callee.type !== 'Identifier') return
          /** @type {'methods'|'computed'|null} */
          let groupName = null
          if (/^mapMutations|mapActions$/u.test(node.callee.name)) {
            groupName = GROUP_METHODS
          } else if (
            /^mapState|mapGetters|mapWritableState$/u.test(node.callee.name)
          ) {
            groupName = GROUP_COMPUTED_PROPERTY
          }

          if (!groupName || node.arguments.length === 0) return
          // On Pinia the store is always the first argument
          const arg =
            node.arguments.length === 2 ? node.arguments[1] : node.arguments[0]
          if (arg.type === 'ObjectExpression') {
            // e.g.
            // `mapMutations({ add: 'increment' })`
            // `mapState({ count: state => state.todosCount })`
            for (const prop of arg.properties) {
              const name =
                prop.type === 'SpreadElement'
                  ? null
                  : utils.getStaticPropertyName(prop)
              if (name) {
                propertiesDefinedByStoreHelpers.add(name)
              }
            }
          } else if (arg.type === 'ArrayExpression') {
            // e.g. `mapMutations(['add'])`
            for (const element of arg.elements) {
              if (!element || !utils.isStringLiteral(element)) {
                continue
              }
              const name = utils.getStringLiteralValue(element)
              if (name) {
                propertiesDefinedByStoreHelpers.add(name)
              }
            }
          }
        },
        onVueObjectEnter(node) {
          const ctx = getVueComponentContext(node)

          for (const prop of utils.iterateProperties(
            node,
            new Set([
              GROUP_PROPERTY,
              GROUP_ASYNC_DATA,
              GROUP_DATA,
              GROUP_COMPUTED_PROPERTY,
              GROUP_SETUP,
              GROUP_METHODS,
              GROUP_INJECT
            ])
          )) {
            const propertyMap =
              (prop.groupName === GROUP_DATA ||
                prop.groupName === GROUP_ASYNC_DATA) &&
              prop.type === 'object' &&
              prop.property.value.type === 'ObjectExpression'
                ? getObjectPropertyMap(prop.property.value)
                : null
            ctx.defineProperties.set(prop.name, {
              hasNestProperty: Boolean(propertyMap),
              isProps: prop.groupName === GROUP_PROPERTY,
              get(name) {
                if (!propertyMap) {
                  return null
                }
                return getPropertyDataFromObjectProperty(propertyMap.get(name))
              }
            })
          }

          for (const watcherOrExpose of utils.iterateProperties(
            node,
            new Set([GROUP_WATCHER, GROUP_EXPOSE])
          )) {
            if (watcherOrExpose.groupName === GROUP_WATCHER) {
              const watcher = watcherOrExpose
              // Process `watch: { foo /* <- this */ () {} }`
              ctx.verifyReferences(
                propertyReferenceExtractor.extractFromPath(
                  watcher.name,
                  watcher.node
                )
              )
              // Process `watch: { x: 'foo' /* <- this */  }`
              if (watcher.type === 'object') {
                const property = watcher.property
                if (property.kind === 'init') {
                  for (const handlerValueNode of utils.iterateWatchHandlerValues(
                    property
                  )) {
                    ctx.verifyReferences(
                      propertyReferenceExtractor.extractFromNameLiteral(
                        handlerValueNode
                      )
                    )
                  }
                }
              }
            } else if (watcherOrExpose.groupName === GROUP_EXPOSE) {
              const expose = watcherOrExpose
              ctx.verifyReferences(
                propertyReferenceExtractor.extractFromName(
                  expose.name,
                  expose.node
                )
              )
            }
          }
        },
        /** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property }} node */
        'ObjectExpression > Property > :function[params.length>0]'(
          node,
          vueData
        ) {
          let props = false
          const property = getParentProperty(node)
          if (!property) {
            return
          }
          if (property.parent === vueData.node) {
            if (utils.getStaticPropertyName(property) !== 'data') {
              return
            }
            // check { data: (vm) => vm.prop }
            props = true
          } else {
            const parentProperty = getParentProperty(property.parent)
            if (!parentProperty) {
              return
            }
            if (parentProperty.parent === vueData.node) {
              if (utils.getStaticPropertyName(parentProperty) !== 'computed') {
                return
              }
              // check { computed: { foo: (vm) => vm.prop } }
            } else {
              const parentParentProperty = getParentProperty(
                parentProperty.parent
              )
              if (!parentParentProperty) {
                return
              }
              if (parentParentProperty.parent === vueData.node) {
                if (
                  utils.getStaticPropertyName(parentParentProperty) !==
                    'computed' ||
                  utils.getStaticPropertyName(property) !== 'get'
                ) {
                  return
                }
                // check { computed: { foo: { get: (vm) => vm.prop } } }
              } else {
                return
              }
            }
          }

          const propertyReferences =
            propertyReferenceExtractor.extractFromFunctionParam(node, 0)
          const ctx = getVueComponentContext(vueData.node)
          ctx.verifyReferences(propertyReferences, { props })
        },
        onSetupFunctionEnter(node, vueData) {
          const propertyReferences =
            propertyReferenceExtractor.extractFromFunctionParam(node, 0)
          const ctx = getVueComponentContext(vueData.node)
          ctx.verifyReferences(propertyReferences, {
            props: true
          })
        },
        onRenderFunctionEnter(node, vueData) {
          const ctx = getVueComponentContext(vueData.node)

          // Check for Vue 3.x render
          const propertyReferences =
            propertyReferenceExtractor.extractFromFunctionParam(node, 0)
          ctx.verifyReferences(propertyReferences)

          if (vueData.functional) {
            // Check for Vue 2.x render & functional
            const propertyReferencesForV2 =
              propertyReferenceExtractor.extractFromFunctionParam(node, 1)

            ctx.verifyReferences(propertyReferencesForV2.getNest('props'), {
              props: true
            })
          }
        },
        /**
         * @param {ThisExpression | Identifier} node
         * @param {VueObjectData} vueData
         */
        'ThisExpression, Identifier'(node, vueData) {
          if (!utils.isThis(node, context)) {
            return
          }
          const ctx = getVueComponentContext(vueData.node)
          const propertyReferences =
            propertyReferenceExtractor.extractFromExpression(node, false)
          ctx.verifyReferences(propertyReferences)
        }
      }),
      {
        'Program:exit'() {
          const ctx = getVueComponentContextForTemplate()
          if (!ctx) {
            return
          }
          const styleVars = getStyleVariablesContext(context)
          if (styleVars) {
            ctx.verifyReferences(
              propertyReferenceExtractor.extractFromStyleVariablesContext(
                styleVars
              )
            )
          }
        }
      }
    )

    const templateVisitor = {
      /**
       * @param {VExpressionContainer} node
       */
      VExpressionContainer(node) {
        const ctx = getVueComponentContextForTemplate()
        if (!ctx) {
          return
        }
        ctx.verifyReferences(
          propertyReferenceExtractor.extractFromVExpressionContainer(node, {
            ignoreGlobals: true
          })
        )
      }
    }

    return utils.defineTemplateBodyVisitor(
      context,
      templateVisitor,
      scriptVisitor
    )
  }
}


code:
/**
 * @author Yosuke Ota
 * See LICENSE file in root directory for full license.
 */
'use strict'

const semver = require('semver')
const utils = require('../utils')

/**
 * @typedef {object} SyntaxRule
 * @property {string} supported
 * @property { (context: RuleContext) => TemplateListener } [createTemplateBodyVisitor]
 * @property { (context: RuleContext) => RuleListener } [createScriptVisitor]
 */

const FEATURES = {
  // Vue.js 2.5.0+
  'slot-scope-attribute': require('./syntaxes/slot-scope-attribute'),
  // Vue.js 2.6.0+
  'dynamic-directive-arguments': require('./syntaxes/dynamic-directive-arguments'),
  'v-slot': require('./syntaxes/v-slot'),
  // Vue.js 2.7.0+
  'script-setup': require('./syntaxes/script-setup'),
  'style-css-vars-injection': require('./syntaxes/style-css-vars-injection'),
  // Vue.js 3.0.0+
  'v-model-argument': require('./syntaxes/v-model-argument'),
  'v-model-custom-modifiers': require('./syntaxes/v-model-custom-modifiers'),
  'v-is': require('./syntaxes/v-is'),
  // Vue.js 3.1.0+
  'is-attribute-with-vue-prefix': require('./syntaxes/is-attribute-with-vue-prefix'),
  // Vue.js 3.2.0+
  'v-memo': require('./syntaxes/v-memo'),
  'v-bind-prop-modifier-shorthand': require('./syntaxes/v-bind-prop-modifier-shorthand'),
  'v-bind-attr-modifier': require('./syntaxes/v-bind-attr-modifier'),
  // Vue.js 3.3.0+
  'define-options': require('./syntaxes/define-options'),
  'define-slots': require('./syntaxes/define-slots'),
  // Vue.js 3.4.0+
  'define-model': require('./syntaxes/define-model'),
  'v-bind-same-name-shorthand': require('./syntaxes/v-bind-same-name-shorthand')
}

const SYNTAX_NAMES = /** @type {(keyof FEATURES)[]} */ (Object.keys(FEATURES))

const cache = new Map()
/**
 * Get the `semver.Range` object of a given range text.
 * @param {string} x The text expression for a semver range.
 * @returns {semver.Range} The range object of a given range text.
 * It's null if the `x` is not a valid range text.
 */
function getSemverRange(x) {
  const s = String(x)
  let ret = cache.get(s) || null

  if (!ret) {
    try {
      ret = new semver.Range(s)
    } catch (_error) {
      // Ignore parsing error.
    }
    cache.set(s, ret)
  }

  return ret
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'disallow unsupported Vue.js syntax on the specified version',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-unsupported-features.html'
    },
    // eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
    fixable: 'code',
    schema: [
      {
        type: 'object',
        properties: {
          version: {
            type: 'string'
          },
          ignores: {
            type: 'array',
            items: {
              enum: SYNTAX_NAMES
            },
            uniqueItems: true
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      // Vue.js 2.5.0+
      forbiddenSlotScopeAttribute:
        '`slot-scope` are not supported except Vue.js ">=2.5.0 <3.0.0".',
      // Vue.js 2.6.0+
      forbiddenDynamicDirectiveArguments:
        'Dynamic arguments are not supported until Vue.js "2.6.0".',
      forbiddenVSlot: '`v-slot` are not supported until Vue.js "2.6.0".',
      // Vue.js 2.7.0+
      forbiddenScriptSetup:
        '`<script setup>` is not supported until Vue.js "2.7.0".',
      forbiddenStyleCssVarsInjection:
        'SFC CSS variable injection is not supported until Vue.js ">=3.0.3 || >=2.7.0 <3.0.0".',
      // Vue.js 3.0.0+
      forbiddenVModelArgument:
        'Argument on `v-model` is not supported until Vue.js "3.0.0".',
      forbiddenVModelCustomModifiers:
        'Custom modifiers on `v-model` are not supported until Vue.js "3.0.0".',
      forbiddenVIs: '`v-is` are not supported until Vue.js "3.0.0".',
      // Vue.js 3.1.0+
      forbiddenIsAttributeWithVuePrefix:
        '`is="vue:"` are not supported until Vue.js "3.1.0".',
      // Vue.js 3.2.0+
      forbiddenVMemo: '`v-memo` are not supported until Vue.js "3.2.0".',
      forbiddenVBindPropModifierShorthand:
        '`.prop` shorthand are not supported until Vue.js "3.2.0".',
      forbiddenVBindAttrModifier:
        '`.attr` modifiers on `v-bind` are not supported until Vue.js "3.2.0".',
      // Vue.js 3.3.0+
      forbiddenDefineOptions:
        '`defineOptions()` macros are not supported until Vue.js "3.3.0".',
      forbiddenDefineSlots:
        '`defineSlots()` macros are not supported until Vue.js "3.3.0".',
      // Vue.js 3.4.0+
      forbiddenDefineModel:
        '`defineModel()` macros are not supported until Vue.js "3.4.0".',
      forbiddenVBindSameNameShorthand:
        '`v-bind` same-name shorthand is not supported until Vue.js "3.4.0".'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const { version, ignores } = Object.assign(
      {
        version: null,
        ignores: []
      },
      context.options[0] || {}
    )
    if (!version) {
      // version is not set.
      return {}
    }
    const versionRange = getSemverRange(version)

    /**
     * Check whether a given case object is full-supported on the configured node version.
     * @param {SyntaxRule} aCase The case object to check.
     * @returns {boolean} `true` if it's supporting.
     */
    function isNotSupportingVersion(aCase) {
      return !semver.subset(versionRange, getSemverRange(aCase.supported))
    }

    /** @type {TemplateListener} */
    let templateBodyVisitor = {}
    /** @type {RuleListener} */
    let scriptVisitor = {}

    for (const syntaxName of SYNTAX_NAMES) {
      /** @type {SyntaxRule} */
      const syntax = FEATURES[syntaxName]
      if (ignores.includes(syntaxName) || !isNotSupportingVersion(syntax)) {
        continue
      }
      if (syntax.createTemplateBodyVisitor) {
        const visitor = syntax.createTemplateBodyVisitor(context)
        templateBodyVisitor = utils.compositingVisitors(
          templateBodyVisitor,
          visitor
        )
      }
      if (syntax.createScriptVisitor) {
        const visitor = syntax.createScriptVisitor(context)
        scriptVisitor = utils.compositingVisitors(scriptVisitor, visitor)
      }
    }

    return utils.defineTemplateBodyVisitor(
      context,
      templateBodyVisitor,
      scriptVisitor
    )
  }
}

license: mit


code:
/**
 * @fileoverview Report used components
 * @author Michał Sajnóg
 */
'use strict'

const utils = require('../utils')
const casing = require('../utils/casing')

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'disallow registering components that are not used inside templates',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/no-unused-components.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          ignoreWhenBindingPresent: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      unused: 'The "{{name}}" component has been registered but not used.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || {}
    const ignoreWhenBindingPresent =
      options.ignoreWhenBindingPresent === undefined
        ? true
        : options.ignoreWhenBindingPresent
    const usedComponents = new Set()
    /** @type { { node: Property, name: string }[] } */
    let registeredComponents = []
    let ignoreReporting = false
    /** @type {Position} */
    let templateLocation

    return utils.defineTemplateBodyVisitor(
      context,
      {
        /** @param {VElement} node */
        VElement(node) {
          if (
            (!utils.isHtmlElementNode(node) &&
              !utils.isSvgElementNode(node) &&
              !utils.isMathElementNode(node)) ||
            utils.isHtmlWellKnownElementName(node.rawName) ||
            utils.isSvgWellKnownElementName(node.rawName) ||
            utils.isMathWellKnownElementName(node.rawName)
          ) {
            return
          }

          usedComponents.add(node.rawName)
        },
        /** @param {VDirective} node */
        "VAttribute[directive=true][key.name.name='bind'][key.argument.name='is'], VAttribute[directive=true][key.name.name='is']"(
          node
        ) {
          if (
            !node.value || // `<component :is>`
            node.value.type !== 'VExpressionContainer' ||
            !node.value.expression // `<component :is="">`
          )
            return

          if (node.value.expression.type === 'Literal') {
            usedComponents.add(node.value.expression.value)
          } else if (ignoreWhenBindingPresent) {
            ignoreReporting = true
          }
        },
        /** @param {VAttribute} node */
        "VAttribute[directive=false][key.name='is']"(node) {
          if (!node.value) {
            return
          }
          const value = node.value.value.startsWith('vue:') // Usage on native elements 3.1+
            ? node.value.value.slice(4)
            : node.value.value
          usedComponents.add(value)
        },
        /** @param {VElement} node */
        "VElement[name='template']"(node) {
          templateLocation = templateLocation || node.loc.start
        },
        /** @param {VElement} node */
        "VElement[name='template']:exit"(node) {
          if (
            node.loc.start !== templateLocation ||
            ignoreReporting ||
            utils.hasAttribute(node, 'src')
          )
            return

          for (const { node, name } of registeredComponents) {
            // If the component name is PascalCase or camelCase
            // it can be used in various of ways inside template,
            // like "theComponent", "The-component" etc.
            // but except snake_case
            if (casing.isPascalCase(name) || casing.isCamelCase(name)) {
              if (
                [...usedComponents].some(
                  (n) =>
                    !n.includes('_') &&
                    (name === casing.pascalCase(n) ||
                      name === casing.camelCase(n))
                )
              ) {
                continue
              }
            } else {
              // In any other case the used component name must exactly match
              // the registered name
              if (usedComponents.has(name)) {
                continue
              }
            }

            context.report({
              node,
              messageId: 'unused',
              data: {
                name
              }
            })
          }
        }
      },
      utils.executeOnVue(context, (obj) => {
        registeredComponents = utils.getRegisteredComponents(obj)
      })
    )
  }
}


code:
/**
 * @author ItMaga
 * See LICENSE file in root directory for full license.
 */
'use strict'

const { findVariable } = require('@eslint-community/eslint-utils')
const utils = require('../utils')

/**
 * @typedef {import('../utils').ComponentEmit} ComponentEmit
 * @typedef {import('../utils').VueObjectData} VueObjectData
 */

/**
 * @typedef {object} SetupContext
 * @property {Set<Identifier>} contextReferenceIds
 * @property {Set<Identifier>} emitReferenceIds
 */

/**
 * @typedef {object} NameWithLoc
 * @property {string} name
 * @property {SourceLocation} loc
 * @property {Range} range
 */

/**
 * Get the name param node from the given CallExpression
 * @param {CallExpression} node CallExpression
 * @returns {NameWithLoc | null}
 */
function getNameParamNode(node) {
  const nameLiteralNode = node.arguments[0]
  if (nameLiteralNode && utils.isStringLiteral(nameLiteralNode)) {
    const name = utils.getStringLiteralValue(nameLiteralNode)
    if (name != null) {
      return { name, loc: nameLiteralNode.loc, range: nameLiteralNode.range }
    }
  }

  return null
}

/**
 * Check if the given node is a reference of setup context
 * @param {Expression | Super | SpreadElement} value
 * @param {SetupContext} setupContext
 * @returns {boolean}
 * */
function hasReferenceId(value, setupContext) {
  const { contextReferenceIds, emitReferenceIds } = setupContext
  return (
    value.type === 'Identifier' &&
    (emitReferenceIds.has(value) || contextReferenceIds.has(value))
  )
}

/**
 * Check if the given name matches emitReferenceIds variable name
 * @param {string} name
 * @param {Set<Identifier>} emitReferenceIds
 * @returns {boolean}
 */
function isEmitVariableName(name, emitReferenceIds) {
  if (emitReferenceIds.size === 0) return false
  const emitVariable = emitReferenceIds.values().next().value.name
  return emitVariable === name
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow unused emit declarations',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-unused-emit-declarations.html'
    },
    fixable: null,
    schema: [],
    messages: {
      unused: '`{{name}}` is defined as emit but never used.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {Map<string, ComponentEmit>} */
    const emitDeclarations = new Map()
    /** @type {Map<string, Expression>} */
    const emitCalls = new Map()
    /** @type {Map<ObjectExpression | Program, SetupContext>} */
    const setupContexts = new Map()
    const programNode = context.getSourceCode().ast

    /**
     * @param {CallExpression} node
     * */
    function addEmitCall(node) {
      const nameParamNode = getNameParamNode(node)
      if (nameParamNode) {
        emitCalls.set(nameParamNode.name, node)
      }
    }
    function clearEmits() {
      emitCalls.clear()
      emitDeclarations.clear()
    }

    /**
     * @param {Expression | SpreadElement} expression
     * @param {SetupContext} setupContext
     * @returns {boolean}
     * */
    function checkExpressionReference(expression, setupContext) {
      if (expression.type === 'MemberExpression') {
        const memObject = utils.skipChainExpression(expression.object)
        if (hasReferenceId(memObject, setupContext)) {
          clearEmits()
          return true
        }
      }
      if (hasReferenceId(expression, setupContext)) {
        clearEmits()
        return true
      }
      return false
    }

    /**
     *
     * @param {Array<Expression | SpreadElement>} args
     * @param {SetupContext} setupContext
     * @returns {boolean}
     */
    function verifyArgumentsReferences(args, setupContext) {
      for (const argument of args) {
        if (argument.type === 'ObjectExpression') {
          for (const property of argument.properties) {
            if (
              property.type === 'Property' &&
              checkExpressionReference(property.value, setupContext)
            ) {
              return true
            }
          }
        }

        if (argument.type === 'ArrayExpression') {
          for (const element of argument.elements) {
            if (!element) {
              continue
            }
            if (checkExpressionReference(element, setupContext)) {
              return true
            }
          }
        }

        if (checkExpressionReference(argument, setupContext)) {
          return true
        }
      }
      return false
    }

    /**
     * @param {Expression | Super} callee
     * @param {Set<Identifier>} referenceIds
     * @param {CallExpression} node
     * */
    function addEmitCallByReference(callee, referenceIds, node) {
      if (callee.type === 'Identifier' && referenceIds.has(callee)) {
        addEmitCall(node)
      }
    }

    const callVisitor = {
      /**
       * @param {CallExpression} node
       * @param {VueObjectData} [info]
       */
      CallExpression(node, info) {
        const callee = utils.skipChainExpression(node.callee)

        let emit = null
        if (callee.type === 'MemberExpression') {
          const name = utils.getStaticPropertyName(callee)
          if (name === 'emit' || name === '$emit') {
            emit = { name, member: callee }
          }
        }

        const vueDefineNode = info ? info.node : programNode
        const setupContext = setupContexts.get(vueDefineNode)
        if (setupContext) {
          if (
            callee.parent.type === 'CallExpression' &&
            callee.parent.arguments &&
            verifyArgumentsReferences(callee.parent.arguments, setupContext)
          ) {
            // skip if the emit passed as argument
            return
          }

          const { contextReferenceIds, emitReferenceIds } = setupContext

          // verify defineEmits variable in template
          if (
            callee.type === 'Identifier' &&
            isEmitVariableName(callee.name, emitReferenceIds)
          ) {
            addEmitCall(node)
          }

          // verify setup(props,{emit}) {emit()}
          addEmitCallByReference(callee, emitReferenceIds, node)
          if (emit && emit.name === 'emit') {
            const memObject = utils.skipChainExpression(emit.member.object)
            // verify setup(props,context) {context.emit()}
            addEmitCallByReference(memObject, contextReferenceIds, node)
          }
        }

        if (emit && emit.name === '$emit') {
          const memObject = utils.skipChainExpression(emit.member.object)
          // verify this.$emit()
          if (utils.isThis(memObject, context)) {
            addEmitCall(node)
          }
        }

        // verify $emit() in template
        if (callee.type === 'Identifier' && callee.name === '$emit') {
          addEmitCall(node)
        }
      }
    }

    return utils.compositingVisitors(
      utils.defineTemplateBodyVisitor(context, callVisitor),
      utils.defineVueVisitor(context, {
        ...callVisitor,
        onVueObjectEnter(node) {
          for (const emit of utils.getComponentEmitsFromOptions(node)) {
            if (emit.emitName) {
              emitDeclarations.set(emit.emitName, emit)
            }
          }
        },
        onSetupFunctionEnter(node, { node: vueNode }) {
          const contextParam = node.params[1]
          if (
            !contextParam ||
            contextParam.type === 'RestElement' ||
            contextParam.type === 'ArrayPattern'
          ) {
            // no arguments or cannot check
            return
          }

          /** @type {Set<Identifier>} */
          const contextReferenceIds = new Set()
          /** @type {Set<Identifier>} */
          const emitReferenceIds = new Set()
          if (contextParam.type === 'ObjectPattern') {
            const emitProperty = utils.findAssignmentProperty(
              contextParam,
              'emit'
            )
            if (!emitProperty) {
              return
            }
            const emitParam = emitProperty.value
            // `setup(props, {emit})`
            const variable =
              emitParam.type === 'Identifier'
                ? findVariable(utils.getScope(context, emitParam), emitParam)
                : null
            if (!variable) {
              return
            }
            for (const reference of variable.references) {
              emitReferenceIds.add(reference.identifier)
            }
          } else if (contextParam.type === 'Identifier') {
            // `setup(props, context)`
            const variable = findVariable(
              utils.getScope(context, contextParam),
              contextParam
            )
            for (const reference of variable.references) {
              contextReferenceIds.add(reference.identifier)
            }
          }

          setupContexts.set(vueNode, {
            contextReferenceIds,
            emitReferenceIds
          })
        }
      }),
      utils.defineScriptSetupVisitor(context, {
        onDefineEmitsEnter(node, emits) {
          for (const emit of emits) {
            if (emit.emitName) {
              emitDeclarations.set(emit.emitName, emit)
            }
          }

          if (
            !node.parent ||
            node.parent.type !== 'VariableDeclarator' ||
            node.parent.init !== node
          ) {
            return
          }

          const emitParam = node.parent.id
          const variable =
            emitParam.type === 'Identifier'
              ? findVariable(utils.getScope(context, emitParam), emitParam)
              : null
          if (!variable) {
            return
          }
          /** @type {Set<Identifier>} */
          const emitReferenceIds = new Set()
          for (const reference of variable.references) {
            emitReferenceIds.add(reference.identifier)
          }
          setupContexts.set(programNode, {
            contextReferenceIds: new Set(),
            emitReferenceIds
          })
        },
        onDefineModelEnter(node, model) {
          if (
            node.parent &&
            node.parent.type === 'VariableDeclarator' &&
            node.parent.init === node
          ) {
            // If the return value of defineModel() is stored in a variable, we can mark the 'update:modelName' event as used if that that variable is used.
            // If that variable is unused, it will already be reported by `no-unused-var` rule.
            emitCalls.set(`update:${model.name.modelName}`, node)
          }
        },
        ...callVisitor
      }),
      {
        'Program:exit'() {
          for (const [name, emit] of emitDeclarations) {
            if (!emitCalls.has(name) && emit.node) {
              context.report({
                node: emit.node,
                loc: emit.node.loc,
                messageId: 'unused',
                data: { name }
              })
            }
          }
        }
      }
    )
  }
}


code:
/**
 * @fileoverview Disallow unused properties, data and computed properties.
 * @author Learning Equality
 */
'use strict'

const utils = require('../utils')
const eslintUtils = require('@eslint-community/eslint-utils')
const { isJSDocComment } = require('../utils/comments.js')
const { getStyleVariablesContext } = require('../utils/style-variables')
const {
  definePropertyReferenceExtractor,
  mergePropertyReferences
} = require('../utils/property-references')

/**
 * @typedef {import('../utils').GroupName} GroupName
 * @typedef {import('../utils').VueObjectData} VueObjectData
 * @typedef {import('../utils/property-references').IPropertyReferences} IPropertyReferences
 */

/**
 * @typedef {object} ComponentObjectPropertyData
 * @property {string} name
 * @property {GroupName} groupName
 * @property {'object'} type
 * @property {ASTNode} node
 * @property {Property} property
 *
 * @typedef {object} ComponentNonObjectPropertyData
 * @property {string} name
 * @property {GroupName} groupName
 * @property {'array' | 'type' | 'infer-type' | 'model' } type
 * @property {ASTNode} node
 *
 * @typedef { ComponentNonObjectPropertyData | ComponentObjectPropertyData } ComponentPropertyData
 */

/**
 * @typedef {object} TemplatePropertiesContainer
 * @property {IPropertyReferences[]} propertyReferences
 * @property {Set<string>} refNames
 * @typedef {object} VueComponentPropertiesContainer
 * @property {ComponentPropertyData[]} properties
 * @property {IPropertyReferences[]} propertyReferences
 * @property {IPropertyReferences[]} propertyReferencesForProps
 */

const GROUP_PROPERTY = 'props'
const GROUP_DATA = 'data'
const GROUP_ASYNC_DATA = 'asyncData'
const GROUP_COMPUTED_PROPERTY = 'computed'
const GROUP_METHODS = 'methods'
const GROUP_SETUP = 'setup'
const GROUP_WATCHER = 'watch'
const GROUP_EXPOSE = 'expose'

const UNREFERENCED_UNKNOWN_MEMBER = 'unknownMemberAsUnreferenced'
const UNREFERENCED_RETURN = 'returnAsUnreferenced'

const PROPERTY_LABEL = {
  props: 'property',
  data: 'data',
  asyncData: 'async data',
  computed: 'computed property',
  methods: 'method',
  setup: 'property returned from `setup()`',

  // not use
  watch: 'watch',
  provide: 'provide',
  inject: 'inject',
  expose: 'expose'
}

/**
 * @param {RuleContext} context
 * @param {Identifier} id
 * @returns {Expression}
 */
function findExpression(context, id) {
  const variable = utils.findVariableByIdentifier(context, id)
  if (!variable) {
    return id
  }
  if (variable.defs.length === 1) {
    const def = variable.defs[0]
    if (
      def.type === 'Variable' &&
      def.parent.kind === 'const' &&
      def.node.init
    ) {
      if (def.node.init.type === 'Identifier') {
        return findExpression(context, def.node.init)
      }
      return def.node.init
    }
  }
  return id
}

/**
 * Check if the given component property is marked as `@public` in JSDoc comments.
 * @param {ComponentPropertyData} property
 * @param {SourceCode} sourceCode
 */
function isPublicMember(property, sourceCode) {
  if (
    property.type === 'object' &&
    // Props do not support @public.
    property.groupName !== 'props'
  ) {
    return isPublicProperty(property.property, sourceCode)
  }
  return false
}

/**
 * Check if the given property node is marked as `@public` in JSDoc comments.
 * @param {Property} node
 * @param {SourceCode} sourceCode
 */
function isPublicProperty(node, sourceCode) {
  const jsdoc = getJSDocFromProperty(node, sourceCode)
  if (jsdoc) {
    return /(?:^|\s|\*)@public\b/u.test(jsdoc.value)
  }
  return false
}

/**
 * Get the JSDoc comment for a given property node.
 * @param {Property} node
 * @param {SourceCode} sourceCode
 */
function getJSDocFromProperty(node, sourceCode) {
  const jsdoc = findJSDocComment(node, sourceCode)
  if (jsdoc) {
    return jsdoc
  }
  if (
    node.value.type === 'FunctionExpression' ||
    node.value.type === 'ArrowFunctionExpression'
  ) {
    return findJSDocComment(node.value, sourceCode)
  }

  return null
}

/**
 * Finds a JSDoc comment for the given node.
 * @param {ASTNode} node
 * @param {SourceCode} sourceCode
 * @returns {Comment | null}
 */
function findJSDocComment(node, sourceCode) {
  /** @type {ASTNode | Token} */
  let currentNode = node
  let tokenBefore = null

  while (currentNode) {
    tokenBefore = sourceCode.getTokenBefore(currentNode, {
      includeComments: true
    })
    if (!tokenBefore || !eslintUtils.isCommentToken(tokenBefore)) {
      return null
    }
    if (tokenBefore.type === 'Line') {
      currentNode = tokenBefore
      continue
    }
    break
  }

  if (tokenBefore && isJSDocComment(tokenBefore)) {
    return tokenBefore
  }

  return null
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow unused properties',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-unused-properties.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          groups: {
            type: 'array',
            items: {
              enum: [
                GROUP_PROPERTY,
                GROUP_DATA,
                GROUP_ASYNC_DATA,
                GROUP_COMPUTED_PROPERTY,
                GROUP_METHODS,
                GROUP_SETUP
              ]
            },
            additionalItems: false,
            uniqueItems: true
          },
          deepData: { type: 'boolean' },
          ignorePublicMembers: { type: 'boolean' },
          unreferencedOptions: {
            type: 'array',
            items: {
              enum: [UNREFERENCED_UNKNOWN_MEMBER, UNREFERENCED_RETURN]
            },
            additionalItems: false,
            uniqueItems: true
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      unused: "'{{name}}' of {{group}} found, but never used."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || {}
    const groups = new Set(options.groups || [GROUP_PROPERTY])
    const deepData = Boolean(options.deepData)
    const ignorePublicMembers = Boolean(options.ignorePublicMembers)
    const unreferencedOptions = new Set(options.unreferencedOptions || [])
    /** @type {null | Pattern} */
    let propsReferencePattern = null

    const propertyReferenceExtractor = definePropertyReferenceExtractor(
      context,
      {
        unknownMemberAsUnreferenced: unreferencedOptions.has(
          UNREFERENCED_UNKNOWN_MEMBER
        ),
        returnAsUnreferenced: unreferencedOptions.has(UNREFERENCED_RETURN)
      }
    )

    /** @type {TemplatePropertiesContainer} */
    const templatePropertiesContainer = {
      propertyReferences: [],
      refNames: new Set()
    }
    /** @type {Map<ASTNode, VueComponentPropertiesContainer>} */
    const vueComponentPropertiesContainerMap = new Map()

    /**
     * @param {ASTNode} node
     * @returns {VueComponentPropertiesContainer}
     */
    function getVueComponentPropertiesContainer(node) {
      let container = vueComponentPropertiesContainerMap.get(node)
      if (!container) {
        container = {
          properties: [],
          propertyReferences: [],
          propertyReferencesForProps: []
        }
        vueComponentPropertiesContainerMap.set(node, container)
      }
      return container
    }

    /**
     * @param {string[]} segments
     * @param {Expression} propertyValue
     * @param {IPropertyReferences} propertyReferences
     */
    function verifyDataOptionDeepProperties(
      segments,
      propertyValue,
      propertyReferences
    ) {
      let targetExpr = propertyValue
      if (targetExpr.type === 'Identifier') {
        targetExpr = findExpression(context, targetExpr)
      }
      if (targetExpr.type === 'ObjectExpression') {
        for (const prop of targetExpr.properties) {
          if (prop.type !== 'Property') {
            continue
          }
          const name = utils.getStaticPropertyName(prop)
          if (name == null) {
            continue
          }
          if (
            !propertyReferences.hasProperty(name, { unknownCallAsAny: true })
          ) {
            // report
            context.report({
              node: prop.key,
              messageId: 'unused',
              data: {
                group: PROPERTY_LABEL.data,
                name: [...segments, name].join('.')
              }
            })
            continue
          }
          // next
          verifyDataOptionDeepProperties(
            [...segments, name],
            prop.value,
            propertyReferences.getNest(name)
          )
        }
      }
    }

    /**
     * Report all unused properties.
     */
    function reportUnusedProperties() {
      for (const container of vueComponentPropertiesContainerMap.values()) {
        const propertyReferences = mergePropertyReferences([
          ...container.propertyReferences,
          ...templatePropertiesContainer.propertyReferences
        ])

        const propertyReferencesForProps = mergePropertyReferences(
          container.propertyReferencesForProps
        )

        for (const property of container.properties) {
          if (
            (property.groupName === 'props' &&
              propertyReferencesForProps.hasProperty(property.name)) ||
            propertyReferences.hasProperty('$props')
          ) {
            // used props
            continue
          }
          if (
            property.groupName === 'setup' &&
            templatePropertiesContainer.refNames.has(property.name)
          ) {
            // used template refs
            continue
          }
          if (
            ignorePublicMembers &&
            isPublicMember(property, context.getSourceCode())
          ) {
            continue
          }

          if (propertyReferences.hasProperty(property.name)) {
            // used
            if (
              deepData &&
              (property.groupName === 'data' ||
                property.groupName === 'asyncData') &&
              property.type === 'object'
            ) {
              // Check the deep properties of the data option.
              verifyDataOptionDeepProperties(
                [property.name],
                property.property.value,
                propertyReferences.getNest(property.name)
              )
            }
            continue
          }

          context.report({
            node: property.node,
            messageId: 'unused',
            data: {
              group: PROPERTY_LABEL[property.groupName],
              name: property.name
            }
          })
        }
      }
    }

    /**
     * @param {Expression} node
     * @returns {Property|null}
     */
    function getParentProperty(node) {
      if (
        !node.parent ||
        node.parent.type !== 'Property' ||
        node.parent.value !== node
      ) {
        return null
      }
      const property = node.parent
      if (!utils.isProperty(property)) {
        return null
      }
      return property
    }

    const scriptVisitor = utils.compositingVisitors(
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(node, props) {
          if (!groups.has('props')) {
            return
          }
          const container = getVueComponentPropertiesContainer(
            context.getSourceCode().ast
          )

          for (const prop of props) {
            if (!prop.propName) {
              continue
            }
            if (prop.type === 'object') {
              container.properties.push({
                type: prop.type,
                name: prop.propName,
                groupName: 'props',
                node: prop.key,
                property: prop.node
              })
            } else {
              container.properties.push({
                type: prop.type,
                name: prop.propName,
                groupName: 'props',
                node: prop.type === 'infer-type' ? prop.node : prop.key
              })
            }
          }
          let target = node
          if (
            target.parent &&
            target.parent.type === 'CallExpression' &&
            target.parent.arguments[0] === target &&
            target.parent.callee.type === 'Identifier' &&
            target.parent.callee.name === 'withDefaults'
          ) {
            target = target.parent
          }

          if (
            !target.parent ||
            target.parent.type !== 'VariableDeclarator' ||
            target.parent.init !== target
          ) {
            return
          }

          propsReferencePattern = target.parent.id
          const propertyReferences =
            propertyReferenceExtractor.extractFromPattern(propsReferencePattern)
          container.propertyReferencesForProps.push(propertyReferences)
        },
        onDefineModelEnter(node, model) {
          if (!groups.has('props')) {
            return
          }
          const container = getVueComponentPropertiesContainer(
            context.getSourceCode().ast
          )
          if (
            node.parent &&
            node.parent.type === 'VariableDeclarator' &&
            node.parent.init === node
          ) {
            // If the return value of defineModel() is stored in a variable, we can mark the model prop as used if that that variable is used.
            // If that variable is unused, it will already be reported by `no-unused-var` rule.
            container.propertyReferences.push(
              propertyReferenceExtractor.extractFromName(
                model.name.modelName,
                model.name.node || node
              )
            )
            return
          }
          container.properties.push({
            type: 'model',
            name: model.name.modelName,
            groupName: 'props',
            node: model.name.node || node
          })
        }
      }),
      utils.defineVueVisitor(context, {
        /**
         * @param {CallExpression} node
         * @param {VueObjectData} vueData
         */
        CallExpression(node, vueData) {
          if (node.callee.type !== 'Identifier') return
          /** @type {'methods'|'computed'|null} */
          let groupName = null
          if (/^mapMutations|mapActions$/u.test(node.callee.name)) {
            groupName = 'methods'
          } else if (
            /^mapState|mapGetters|mapWritableState$/u.test(node.callee.name)
          ) {
            groupName = 'computed'
          }

          if (!groupName || node.arguments.length === 0) return
          // On Pinia the store is always the first argument
          const arg =
            node.arguments.length === 2 ? node.arguments[1] : node.arguments[0]
          if (arg.type === 'ObjectExpression') {
            // e.g.
            // `mapMutations({ add: 'increment' })`
            // `mapState({ count: state => state.todosCount })`
            const container = getVueComponentPropertiesContainer(vueData.node)
            for (const prop of arg.properties) {
              const name =
                prop.type === 'SpreadElement'
                  ? null
                  : utils.getStaticPropertyName(prop)
              if (name) {
                container.properties.push({
                  type: 'array',
                  name,
                  groupName,
                  node: prop
                })
              }
            }
          } else if (arg.type === 'ArrayExpression') {
            // e.g. `mapMutations(['add'])`
            const container = getVueComponentPropertiesContainer(vueData.node)
            for (const element of arg.elements) {
              if (
                !element ||
                (element.type !== 'Literal' &&
                  element.type !== 'TemplateLiteral')
              ) {
                continue
              }
              const name = utils.getStringLiteralValue(element)
              if (name) {
                container.properties.push({
                  type: 'array',
                  name,
                  groupName,
                  node: element
                })
              }
            }
          }
        },

        onVueObjectEnter(node, vueNode) {
          const container = getVueComponentPropertiesContainer(vueNode.node)

          for (const watcherOrExpose of utils.iterateProperties(
            node,
            new Set([GROUP_WATCHER, GROUP_EXPOSE])
          )) {
            if (watcherOrExpose.groupName === GROUP_WATCHER) {
              const watcher = watcherOrExpose
              // Process `watch: { foo /* <- this */ () {} }`
              container.propertyReferences.push(
                propertyReferenceExtractor.extractFromPath(
                  watcher.name,
                  watcher.node
                )
              )

              // Process `watch: { x: 'foo' /* <- this */  }`
              if (watcher.type === 'object') {
                const property = watcher.property
                if (property.kind === 'init') {
                  for (const handlerValueNode of utils.iterateWatchHandlerValues(
                    property
                  )) {
                    container.propertyReferences.push(
                      propertyReferenceExtractor.extractFromNameLiteral(
                        handlerValueNode
                      )
                    )
                  }
                }
              }
            } else if (watcherOrExpose.groupName === GROUP_EXPOSE) {
              const expose = watcherOrExpose
              container.propertyReferences.push(
                propertyReferenceExtractor.extractFromName(
                  expose.name,
                  expose.node
                )
              )
            }
          }

          container.properties.push(...utils.iterateProperties(node, groups))
        },

        /** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property }} node */
        'ObjectExpression > Property > :function[params.length>0]'(
          node,
          vueData
        ) {
          const property = getParentProperty(node)
          if (!property) {
            return
          }
          if (property.parent === vueData.node) {
            if (utils.getStaticPropertyName(property) !== 'data') {
              return
            }
            // check { data: (vm) => vm.prop }
          } else {
            const parentProperty = getParentProperty(property.parent)
            if (!parentProperty) {
              return
            }
            if (parentProperty.parent === vueData.node) {
              if (utils.getStaticPropertyName(parentProperty) !== 'computed') {
                return
              }
              // check { computed: { foo: (vm) => vm.prop } }
            } else {
              const parentParentProperty = getParentProperty(
                parentProperty.parent
              )
              if (!parentParentProperty) {
                return
              }
              if (parentParentProperty.parent === vueData.node) {
                if (
                  utils.getStaticPropertyName(parentParentProperty) !==
                    'computed' ||
                  utils.getStaticPropertyName(property) !== 'get'
                ) {
                  return
                }
                // check { computed: { foo: { get: (vm) => vm.prop } } }
              } else {
                return
              }
            }
          }

          const propertyReferences =
            propertyReferenceExtractor.extractFromFunctionParam(node, 0)
          const container = getVueComponentPropertiesContainer(vueData.node)
          container.propertyReferences.push(propertyReferences)
        },
        onSetupFunctionEnter(node, vueData) {
          const container = getVueComponentPropertiesContainer(vueData.node)
          const propertyReferences =
            propertyReferenceExtractor.extractFromFunctionParam(node, 0)
          container.propertyReferencesForProps.push(propertyReferences)
        },
        onRenderFunctionEnter(node, vueData) {
          const container = getVueComponentPropertiesContainer(vueData.node)

          // Check for Vue 3.x render
          const propertyReferences =
            propertyReferenceExtractor.extractFromFunctionParam(node, 0)
          container.propertyReferencesForProps.push(propertyReferences)

          if (vueData.functional) {
            // Check for Vue 2.x render & functional
            const propertyReferencesForV2 =
              propertyReferenceExtractor.extractFromFunctionParam(node, 1)

            container.propertyReferencesForProps.push(
              propertyReferencesForV2.getNest('props')
            )
          }
        },
        /**
         * @param {ThisExpression | Identifier} node
         * @param {VueObjectData} vueData
         */
        'ThisExpression, Identifier'(node, vueData) {
          if (!utils.isThis(node, context)) {
            return
          }
          const container = getVueComponentPropertiesContainer(vueData.node)
          const propertyReferences =
            propertyReferenceExtractor.extractFromExpression(node, false)
          container.propertyReferences.push(propertyReferences)
        }
      }),
      {
        Program() {
          const styleVars = getStyleVariablesContext(context)
          if (styleVars) {
            templatePropertiesContainer.propertyReferences.push(
              propertyReferenceExtractor.extractFromStyleVariablesContext(
                styleVars
              )
            )
          }
        },
        /** @param {Program} node */
        'Program:exit'(node) {
          if (!node.templateBody) {
            reportUnusedProperties()
          }
        }
      }
    )

    const templateVisitor = {
      /**
       * @param {VExpressionContainer} node
       */
      VExpressionContainer(node) {
        const property =
          propertyReferenceExtractor.extractFromVExpressionContainer(node)

        templatePropertiesContainer.propertyReferences.push(property)

        if (!propsReferencePattern) {
          return
        }

        // props.prop in template
        for (const key of property.allProperties().keys()) {
          if (
            propsReferencePattern.type === 'Identifier' &&
            propsReferencePattern.name === key
          ) {
            templatePropertiesContainer.propertyReferences.push(
              property.getNest(key)
            )
          }
        }
      },
      /**
       * @param {VAttribute} node
       */
      'VAttribute[directive=false]'(node) {
        if (node.key.name === 'ref' && node.value != null) {
          templatePropertiesContainer.refNames.add(node.value.value)
        }
      },
      "VElement[parent.type!='VElement']:exit"() {
        reportUnusedProperties()
      }
    }

    return utils.defineTemplateBodyVisitor(
      context,
      templateVisitor,
      scriptVisitor
    )
  }
}


code:
/**
 * @fileoverview Disallow unused refs.
 * @author Yosuke Ota
 */
'use strict'

const utils = require('../utils')

/**
 * Extract names from references objects.
 * @param {VReference[]} references
 */
function getReferences(references) {
  return references.filter((ref) => ref.variable == null).map((ref) => ref.id)
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow unused refs',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-unused-refs.html'
    },
    fixable: null,
    schema: [],
    messages: {
      unused: "'{{name}}' is defined as ref, but never used."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {Set<string>} */
    const usedRefs = new Set()

    /** @type {VLiteral[]} */
    const defineRefs = []
    let hasUnknown = false

    /**
     * Report all unused refs.
     */
    function reportUnusedRefs() {
      for (const defineRef of defineRefs) {
        if (usedRefs.has(defineRef.value)) {
          continue
        }

        context.report({
          node: defineRef,
          messageId: 'unused',
          data: {
            name: defineRef.value
          }
        })
      }
    }

    /**
     * Extract the use ref names for ObjectPattern.
     * @param {ObjectPattern} node
     * @returns {void}
     */
    function extractUsedForObjectPattern(node) {
      for (const prop of node.properties) {
        if (prop.type === 'Property') {
          const name = utils.getStaticPropertyName(prop)
          if (name) {
            usedRefs.add(name)
          } else {
            hasUnknown = true
            return
          }
        } else {
          hasUnknown = true
          return
        }
      }
    }
    /**
     * Extract the use ref names.
     * @param {Identifier | MemberExpression} refsNode
     * @returns {void}
     */
    function extractUsedForPattern(refsNode) {
      /** @type {Identifier | MemberExpression | ChainExpression} */
      let node = refsNode
      while (node.parent.type === 'ChainExpression') {
        node = node.parent
      }
      const parent = node.parent
      switch (parent.type) {
        case 'AssignmentExpression': {
          if (parent.right === node) {
            if (parent.left.type === 'ObjectPattern') {
              // `({foo} = $refs)`
              extractUsedForObjectPattern(parent.left)
            } else if (parent.left.type === 'Identifier') {
              // `foo = $refs`
              hasUnknown = true
            }
          }
          break
        }
        case 'VariableDeclarator': {
          if (parent.init === node) {
            if (parent.id.type === 'ObjectPattern') {
              // `const {foo} = $refs`
              extractUsedForObjectPattern(parent.id)
            } else if (parent.id.type === 'Identifier') {
              // `const foo = $refs`
              hasUnknown = true
            }
          }
          break
        }
        case 'MemberExpression': {
          if (parent.object === node) {
            // `$refs.foo`
            const name = utils.getStaticPropertyName(parent)
            if (name) {
              usedRefs.add(name)
            } else {
              hasUnknown = true
            }
          }
          break
        }
        case 'CallExpression': {
          const argIndex = parent.arguments.indexOf(node)
          if (argIndex > -1) {
            // `foo($refs)`
            hasUnknown = true
          }
          break
        }
        case 'ForInStatement':
        case 'ReturnStatement': {
          hasUnknown = true
          break
        }
      }
    }

    return utils.defineTemplateBodyVisitor(
      context,
      {
        /**
         * @param {VExpressionContainer} node
         */
        VExpressionContainer(node) {
          if (hasUnknown) {
            return
          }
          for (const id of getReferences(node.references)) {
            if (id.name !== '$refs') {
              continue
            }
            extractUsedForPattern(id)
          }
        },
        /**
         * @param {VAttribute} node
         */
        'VAttribute[directive=false]'(node) {
          if (hasUnknown) {
            return
          }
          if (node.key.name === 'ref' && node.value != null) {
            defineRefs.push(node.value)
          }
        },
        "VElement[parent.type!='VElement']:exit"() {
          if (hasUnknown) {
            return
          }
          reportUnusedRefs()
        }
      },
      utils.compositingVisitors(
        utils.isScriptSetup(context)
          ? {
              Program() {
                const globalScope =
                  context.getSourceCode().scopeManager.globalScope
                if (!globalScope) {
                  return
                }
                for (const variable of globalScope.variables) {
                  if (variable.defs.length > 0) {
                    usedRefs.add(variable.name)
                  }
                }
                const moduleScope = globalScope.childScopes.find(
                  (scope) => scope.type === 'module'
                )
                if (!moduleScope) {
                  return
                }
                for (const variable of moduleScope.variables) {
                  if (variable.defs.length > 0) {
                    usedRefs.add(variable.name)
                  }
                }
              }
            }
          : {},
        utils.defineVueVisitor(context, {
          onVueObjectEnter(node) {
            for (const prop of utils.iterateProperties(
              node,
              new Set(['setup'])
            )) {
              usedRefs.add(prop.name)
            }
          }
        }),
        {
          Identifier(id) {
            if (hasUnknown) {
              return
            }
            if (id.name !== '$refs') {
              return
            }
            /** @type {Identifier | MemberExpression} */
            let refsNode = id
            if (
              id.parent.type === 'MemberExpression' &&
              id.parent.property === id
            ) {
              // `this.$refs.foo`
              refsNode = id.parent
            }
            extractUsedForPattern(refsNode)
          },
          CallExpression(callExpression) {
            const firstArgument = callExpression.arguments[0]
            if (
              callExpression.callee.name !== 'useTemplateRef' ||
              !firstArgument
            ) {
              return
            }
            const name = utils.getStringLiteralValue(firstArgument)
            if (name !== null) {
              usedRefs.add(name)
            }
          }
        }
      )
    )
  }
}


code:
/**
 * @fileoverview disallow unused variable definitions of v-for directives or scope attributes.
 * @author 薛定谔的猫<hh_2013@foxmail.com>
 */
'use strict'

const utils = require('../utils')

/**
 * @typedef {VVariable['kind']} VariableKind
 */

/**
 * Groups variables by directive kind.
 * @param {VElement} node The element node
 * @returns { { [kind in VariableKind]?: VVariable[] } } The variables of grouped by directive kind.
 */
function groupingVariables(node) {
  /** @type { { [kind in VariableKind]?: VVariable[] } } */
  const result = {}
  for (const variable of node.variables) {
    const vars = result[variable.kind] || (result[variable.kind] = [])
    vars.push(variable)
  }
  return result
}

/**
 * Checks if the given variable was defined by destructuring.
 * @param {VVariable} variable the given variable to check
 * @returns {boolean} `true` if the given variable was defined by destructuring.
 */
function isDestructuringVar(variable) {
  const node = variable.id
  /** @type {ASTNode | null} */
  let parent = node.parent
  while (parent) {
    if (
      parent.type === 'VForExpression' ||
      parent.type === 'VSlotScopeExpression'
    ) {
      return false
    }
    if (parent.type === 'Property' || parent.type === 'ArrayPattern') {
      return true
    }
    parent = parent.parent
  }
  return false
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'disallow unused variable definitions of v-for directives or scope attributes',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/no-unused-vars.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: [
      {
        type: 'object',
        properties: {
          ignorePattern: {
            type: 'string'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      unusedVariable: "'{{name}}' is defined but never used.",
      replaceWithUnderscore:
        'Replace `{{name}}` with `_{{name}}` to ignore the unused variable.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const option = context.options[0] || {}
    const ignorePattern = option.ignorePattern
    /** @type {RegExp | null} */
    let ignoreRegEx = null
    if (ignorePattern) {
      ignoreRegEx = new RegExp(ignorePattern, 'u')
    }
    return utils.defineTemplateBodyVisitor(context, {
      /**
       * @param {VElement} node
       */
      VElement(node) {
        const vars = groupingVariables(node)
        for (const variables of Object.values(vars)) {
          if (!variables) {
            continue
          }
          let hasAfterUsed = false

          for (let i = variables.length - 1; i >= 0; i--) {
            const variable = variables[i]

            if (variable.references.length > 0) {
              hasAfterUsed = true
              continue
            }

            if (ignoreRegEx != null && ignoreRegEx.test(variable.id.name)) {
              continue
            }

            if (hasAfterUsed && !isDestructuringVar(variable)) {
              // If a variable after the variable is used, it will be skipped.
              continue
            }

            context.report({
              node: variable.id,
              loc: variable.id.loc,
              messageId: 'unusedVariable',
              data: {
                name: variable.id.name
              },
              suggest:
                ignorePattern === '^_'
                  ? [
                      {
                        messageId: 'replaceWithUnderscore',
                        data: { name: variable.id.name },
                        fix(fixer) {
                          return fixer.replaceText(
                            variable.id,
                            `_${variable.id.name}`
                          )
                        }
                      }
                    ]
                  : []
            })
          }
        }
      }
    })
  }
}


code:
/**
 * @author tyankatsu <https://github.com/tyankatsu0105>
 * See LICENSE file in root directory for full license.
 */
'use strict'

const eslintUtils = require('@eslint-community/eslint-utils')
const utils = require('../utils')

/**
 * @typedef {import('eslint').Scope.Scope} Scope
 * @typedef {import('../utils').ComponentObjectPropertyData} ComponentObjectPropertyData
 * @typedef {import('../utils').GroupName} GroupName
 */

/**
 * @typedef {object} CallMember
 * @property {string} name
 * @property {CallExpression} node
 */

/** @type {Set<GroupName>} */
const GROUPS = new Set(['data', 'props', 'computed', 'methods'])

const NATIVE_NOT_FUNCTION_TYPES = new Set([
  'String',
  'Number',
  'BigInt',
  'Boolean',
  'Object',
  'Array',
  'Symbol'
])

/**
 * @param {RuleContext} context
 * @param {Expression} node
 * @returns {Set<Expression>}
 */
function resolvedExpressions(context, node) {
  /** @type {Map<Expression, Set<Expression>>} */
  const resolvedMap = new Map()

  return resolvedExpressionsInternal(node)

  /**
   * @param {Expression} node
   * @returns {Set<Expression>}
   */
  function resolvedExpressionsInternal(node) {
    let resolvedSet = resolvedMap.get(node)
    if (!resolvedSet) {
      resolvedSet = new Set()
      resolvedMap.set(node, resolvedSet)
      for (const e of extractResolvedExpressions(node)) {
        resolvedSet.add(e)
      }
    }

    if (resolvedSet.size === 0) {
      resolvedSet.add(node)
    }

    return resolvedSet
  }
  /**
   * @param {Expression} node
   * @returns {Iterable<Expression>}
   */
  function* extractResolvedExpressions(node) {
    switch (node.type) {
      case 'Identifier': {
        const variable = utils.findVariableByIdentifier(context, node)
        if (variable) {
          for (const ref of variable.references) {
            const id = ref.identifier
            if (id.parent.type === 'VariableDeclarator') {
              if (id.parent.id === id && id.parent.init) {
                yield* resolvedExpressionsInternal(id.parent.init)
              }
            } else if (
              id.parent.type === 'AssignmentExpression' &&
              id.parent.left === id
            ) {
              yield* resolvedExpressionsInternal(id.parent.right)
            }
          }
        }

        break
      }
      case 'ConditionalExpression': {
        yield* resolvedExpressionsInternal(node.consequent)
        yield* resolvedExpressionsInternal(node.alternate)

        break
      }
      case 'LogicalExpression': {
        yield* resolvedExpressionsInternal(node.left)
        yield* resolvedExpressionsInternal(node.right)

        break
      }
    }
  }
}

/**
 * Get type of props item.
 * Can't consider array props like: props: {propsA: [String, Number, Function]}
 * @param {RuleContext} context
 * @param {ComponentObjectPropertyData} prop
 * @return {string[] | null}
 *
 * @example
 * props: {
 *   propA: String, // => String
 *   propB: {
 *     type: Number // => Number
 *   },
 * }
 */
function getComponentPropsTypes(context, prop) {
  const result = []
  for (const expr of resolvedExpressions(context, prop.property.value)) {
    const types = getComponentPropsTypesFromExpression(expr)
    if (types == null) {
      return null
    }
    result.push(...types)
  }
  return result

  /**
   * @param {Expression} expr
   */
  function getComponentPropsTypesFromExpression(expr) {
    let typeExprs
    /**
     * Check object props `props: { objectProps: {...} }`
     */
    if (expr.type === 'ObjectExpression') {
      const type = utils.findProperty(expr, 'type')
      if (type == null) return null

      typeExprs = resolvedExpressions(context, type.value)
    } else {
      typeExprs = [expr]
    }

    const result = []
    for (const typeExpr of typeExprs) {
      const types = getComponentPropsTypesFromTypeExpression(typeExpr)
      if (types == null) {
        return null
      }
      result.push(...types)
    }
    return result
  }

  /**
   * @param {Expression} typeExpr
   */
  function getComponentPropsTypesFromTypeExpression(typeExpr) {
    if (typeExpr.type === 'Identifier') {
      return [typeExpr.name]
    }
    if (typeExpr.type === 'ArrayExpression') {
      const types = []
      for (const element of typeExpr.elements) {
        if (!element) {
          continue
        }
        if (element.type === 'SpreadElement') {
          return null
        }
        for (const elementExpr of resolvedExpressions(context, element)) {
          if (elementExpr.type !== 'Identifier') {
            return null
          }
          types.push(elementExpr.name)
        }
      }
      return types
    }
    return null
  }
}

/**
 * Check whether given expression may be a function.
 * @param {RuleContext} context
 * @param {Expression} node
 * @returns {boolean}
 */
function maybeFunction(context, node) {
  for (const expr of resolvedExpressions(context, node)) {
    if (
      expr.type === 'ObjectExpression' ||
      expr.type === 'ArrayExpression' ||
      expr.type === 'Literal' ||
      expr.type === 'TemplateLiteral' ||
      expr.type === 'BinaryExpression' ||
      expr.type === 'UnaryExpression' ||
      expr.type === 'UpdateExpression'
    ) {
      continue
    }
    if (
      expr.type === 'ConditionalExpression' &&
      !maybeFunction(context, expr.consequent) &&
      !maybeFunction(context, expr.alternate)
    ) {
      continue
    }
    const evaluated = eslintUtils.getStaticValue(
      expr,
      utils.getScope(context, expr)
    )
    if (!evaluated) {
      // It could be a function because we don't know what it is.
      return true
    }
    if (typeof evaluated.value === 'function') {
      return true
    }
  }
  return false
}

class FunctionData {
  /**
   * @param {string} name
   * @param {'methods' | 'computed'} kind
   * @param {FunctionExpression | ArrowFunctionExpression} node
   * @param {RuleContext} context
   */
  constructor(name, kind, node, context) {
    this.context = context
    this.name = name
    this.kind = kind
    this.node = node
    /** @type {(Expression | null)[]} */
    this.returnValues = []
    /** @type {boolean | null} */
    this.cacheMaybeReturnFunction = null
  }

  /**
   * @param {Expression | null} node
   */
  addReturnValue(node) {
    this.returnValues.push(node)
  }

  /**
   * @param {ComponentStack} component
   */
  maybeReturnFunction(component) {
    if (this.cacheMaybeReturnFunction != null) {
      return this.cacheMaybeReturnFunction
    }
    // Avoid infinite recursion.
    this.cacheMaybeReturnFunction = true

    return (this.cacheMaybeReturnFunction = this.returnValues.some(
      (returnValue) =>
        returnValue && component.maybeFunctionExpression(returnValue)
    ))
  }
}

/** Component information class. */
class ComponentStack {
  /**
   * @param {ObjectExpression} node
   * @param {RuleContext} context
   * @param {ComponentStack | null} upper
   */
  constructor(node, context, upper) {
    this.node = node
    this.context = context
    /** Upper scope component */
    this.upper = upper

    /** @type {Map<string, boolean>} */
    const maybeFunctions = new Map()
    /** @type {FunctionData[]} */
    const functions = []

    // Extract properties
    for (const property of utils.iterateProperties(node, GROUPS)) {
      if (property.type === 'array') {
        continue
      }
      switch (property.groupName) {
        case 'data': {
          maybeFunctions.set(
            property.name,
            maybeFunction(context, property.property.value)
          )
          break
        }
        case 'props': {
          const types = getComponentPropsTypes(context, property)
          maybeFunctions.set(
            property.name,
            !types || types.some((type) => !NATIVE_NOT_FUNCTION_TYPES.has(type))
          )
          break
        }
        case 'computed': {
          let value = property.property.value
          if (value.type === 'ObjectExpression') {
            const getProp = utils.findProperty(value, 'get')
            if (getProp) {
              value = getProp.value
            }
          }
          processFunction(property.name, value, 'computed')
          break
        }
        case 'methods': {
          const value = property.property.value
          processFunction(property.name, value, 'methods')
          maybeFunctions.set(property.name, true)
          break
        }
      }
    }
    this.maybeFunctions = maybeFunctions
    this.functions = functions
    /** @type {CallMember[]} */
    this.callMembers = []
    /** @type {Map<Expression, boolean>} */
    this.cacheMaybeFunctionExpressions = new Map()

    /**
     * @param {string} name
     * @param {Expression} value
     * @param {'methods' | 'computed'} kind
     */
    function processFunction(name, value, kind) {
      if (value.type === 'FunctionExpression') {
        functions.push(new FunctionData(name, kind, value, context))
      } else if (value.type === 'ArrowFunctionExpression') {
        const data = new FunctionData(name, kind, value, context)
        if (value.expression) {
          data.addReturnValue(value.body)
        }
        functions.push(data)
      }
    }
  }

  /**
   * Adds the given return statement to the return value of the function.
   * @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} scopeFunction
   * @param {ReturnStatement} returnNode
   */
  addReturnStatement(scopeFunction, returnNode) {
    for (const data of this.functions) {
      if (data.node === scopeFunction) {
        data.addReturnValue(returnNode.argument)
        break
      }
    }
  }

  verifyComponent() {
    for (const call of this.callMembers) {
      this.verifyCallMember(call)
    }
  }

  /**
   * @param {CallMember} call
   */
  verifyCallMember(call) {
    const fnData = this.functions.find(
      (data) => data.name === call.name && data.kind === 'computed'
    )
    if (!fnData) {
      // It is not computed, or unknown.
      return
    }

    if (!fnData.maybeReturnFunction(this)) {
      const prefix = call.node.callee.type === 'MemberExpression' ? 'this.' : ''
      this.context.report({
        node: call.node,
        messageId: 'unexpected',
        data: {
          likeProperty: `${prefix}${call.name}`,
          likeMethod: `${prefix}${call.name}()`
        }
      })
    }
  }

  /**
   * Check whether given expression may be a function.
   * @param {Expression} node
   * @returns {boolean}
   */
  maybeFunctionExpression(node) {
    const cache = this.cacheMaybeFunctionExpressions.get(node)
    if (cache != null) {
      return cache
    }
    // Avoid infinite recursion.
    this.cacheMaybeFunctionExpressions.set(node, true)

    const result = maybeFunctionExpressionWithoutCache.call(this)
    this.cacheMaybeFunctionExpressions.set(node, result)
    return result

    /**
     * @this {ComponentStack}
     */
    function maybeFunctionExpressionWithoutCache() {
      for (const expr of resolvedExpressions(this.context, node)) {
        if (!maybeFunction(this.context, expr)) {
          continue
        }
        switch (expr.type) {
          case 'MemberExpression': {
            if (utils.isThis(expr.object, this.context)) {
              const name = utils.getStaticPropertyName(expr)
              if (name && !this.maybeFunctionProperty(name)) {
                continue
              }
            }
            break
          }
          case 'CallExpression': {
            if (
              expr.callee.type === 'MemberExpression' &&
              utils.isThis(expr.callee.object, this.context)
            ) {
              const name = utils.getStaticPropertyName(expr.callee)
              const fnData = this.functions.find((data) => data.name === name)
              if (
                fnData &&
                fnData.kind === 'methods' &&
                !fnData.maybeReturnFunction(this)
              ) {
                continue
              }
            }
            break
          }
          case 'ConditionalExpression': {
            if (
              !this.maybeFunctionExpression(expr.consequent) &&
              !this.maybeFunctionExpression(expr.alternate)
            ) {
              continue
            }
            break
          }
        }
        // It could be a function because we don't know what it is.
        return true
      }
      return false
    }
  }

  /**
   * Check whether given property name may be a function.
   * @param {string} name
   * @returns {boolean}
   */
  maybeFunctionProperty(name) {
    const cache = this.maybeFunctions.get(name)
    if (cache != null) {
      return cache
    }
    // Avoid infinite recursion.
    this.maybeFunctions.set(name, true)

    const result = maybeFunctionPropertyWithoutCache.call(this)
    this.maybeFunctions.set(name, result)
    return result

    /**
     * @this {ComponentStack}
     */
    function maybeFunctionPropertyWithoutCache() {
      const fnData = this.functions.find((data) => data.name === name)
      if (fnData && fnData.kind === 'computed') {
        return fnData.maybeReturnFunction(this)
      }
      // It could be a function because we don't know what it is.
      return true
    }
  }
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'disallow use computed property like method',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/no-use-computed-property-like-method.html'
    },
    fixable: null,
    schema: [],
    messages: {
      unexpected: 'Use {{ likeProperty }} instead of {{ likeMethod }}.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /**
     * @typedef {object} ScopeStack
     * @property {ScopeStack | null} upper
     * @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} scopeNode
     */
    /** @type {ScopeStack | null} */
    let scopeStack = null

    /** @type {ComponentStack | null} */
    let componentStack = null
    /** @type {ComponentStack | null} */
    let templateComponent = null

    return utils.compositingVisitors(
      {},
      utils.defineVueVisitor(context, {
        onVueObjectEnter(node) {
          componentStack = new ComponentStack(node, context, componentStack)
          if (!templateComponent && utils.isInExportDefault(node)) {
            templateComponent = componentStack
          }
        },
        onVueObjectExit() {
          if (componentStack) {
            componentStack.verifyComponent()
            componentStack = componentStack.upper
          }
        },
        /**
         * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
         */
        ':function'(node) {
          scopeStack = {
            upper: scopeStack,
            scopeNode: node
          }
        },
        ReturnStatement(node) {
          if (scopeStack && componentStack) {
            componentStack.addReturnStatement(scopeStack.scopeNode, node)
          }
        },
        ':function:exit'() {
          scopeStack = scopeStack && scopeStack.upper
        },

        /**
         * @param {ThisExpression | Identifier} node
         */
        'ThisExpression, Identifier'(node) {
          if (
            !componentStack ||
            node.parent.type !== 'MemberExpression' ||
            node.parent.object !== node ||
            node.parent.parent.type !== 'CallExpression' ||
            node.parent.parent.callee !== node.parent ||
            !utils.isThis(node, context)
          ) {
            return
          }
          const name = utils.getStaticPropertyName(node.parent)
          if (name) {
            componentStack.callMembers.push({
              name,
              node: node.parent.parent
            })
          }
        }
      }),
      utils.defineTemplateBodyVisitor(context, {
        /**
         * @param {VExpressionContainer} node
         */
        VExpressionContainer(node) {
          if (!templateComponent) {
            return
          }
          for (const id of node.references
            .filter((ref) => ref.variable == null)
            .map((ref) => ref.id)) {
            if (
              id.parent.type !== 'CallExpression' ||
              id.parent.callee !== id
            ) {
              continue
            }
            templateComponent.verifyCallMember({
              name: id.name,
              node: id.parent
            })
          }
        }
      })
    )
  }
}


code:
'use strict'

const { defineTemplateBodyVisitor, hasDirective } = require('../utils')

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'disallow using `v-else-if`/`v-else` on the same element as `v-for`',
      categories: null,
      url: 'https://eslint.vuejs.org/rules/no-use-v-else-with-v-for.html'
    },
    fixable: null,
    schema: [],
    messages: {
      unexpectedDirectiveWithVFor:
        'Unexpected `{{ directiveName }}` and `v-for` on the same element. Move `{{ directiveName }}` to a wrapper element instead.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return defineTemplateBodyVisitor(context, {
      /** @param {VDirective} node */
      "VAttribute[directive=true][key.name.name='for']"(node) {
        const element = node.parent.parent

        if (hasDirective(element, 'else-if')) {
          context.report({
            node: element,
            messageId: 'unexpectedDirectiveWithVFor',
            data: { directiveName: 'v-else-if' }
          })
        } else if (hasDirective(element, 'else')) {
          context.report({
            node: element,
            messageId: 'unexpectedDirectiveWithVFor',
            data: { directiveName: 'v-else' }
          })
        }
      }
    })
  }
}


code:
/**
 * @author Yosuke Ota
 *
 * Style guide: https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for
 */
'use strict'

const utils = require('../utils')

/**
 * Check whether the given `v-if` node is using the variable which is defined by the `v-for` directive.
 * @param {VDirective} vIf The `v-if` attribute node to check.
 * @returns {boolean} `true` if the `v-if` is using the variable which is defined by the `v-for` directive.
 */
function isUsingIterationVar(vIf) {
  return !!getVForUsingIterationVar(vIf)
}

/** @param {VDirective} vIf */
function getVForUsingIterationVar(vIf) {
  if (!vIf.value) {
    return null
  }
  const element = vIf.parent.parent
  for (const reference of vIf.value.references) {
    const targetVFor = element.variables.find(
      (variable) =>
        variable.id.name === reference.id.name && variable.kind === 'v-for'
    )
    if (targetVFor) {
      return targetVFor
    }
  }
  return null
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow using `v-if` on the same element as `v-for`',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/no-use-v-if-with-v-for.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          allowUsingIterationVar: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      movedToWrapper: "This 'v-if' should be moved to the wrapper element.",
      shouldUseComputed:
        "The '{{iteratorName}}' {{kind}} inside 'v-for' directive should be replaced with a computed property that returns filtered array instead. You should not mix 'v-for' with 'v-if'."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || {}
    const allowUsingIterationVar = options.allowUsingIterationVar === true // default false
    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VDirective} node */
      "VAttribute[directive=true][key.name.name='if']"(node) {
        const element = node.parent.parent

        if (utils.hasDirective(element, 'for')) {
          if (isUsingIterationVar(node)) {
            if (!allowUsingIterationVar) {
              const vForVar = getVForUsingIterationVar(node)
              if (!vForVar) {
                return
              }

              let targetVForExpr = vForVar.id.parent
              while (targetVForExpr.type !== 'VForExpression') {
                targetVForExpr = /** @type {ASTNode} */ (targetVForExpr.parent)
              }
              const iteratorNode = targetVForExpr.right
              context.report({
                node,
                loc: node.loc,
                messageId: 'shouldUseComputed',
                data: {
                  iteratorName:
                    iteratorNode.type === 'Identifier'
                      ? iteratorNode.name
                      : context.getSourceCode().getText(iteratorNode),
                  kind:
                    iteratorNode.type === 'Identifier'
                      ? 'variable'
                      : 'expression'
                }
              })
            }
          } else {
            context.report({
              node,
              loc: node.loc,
              messageId: 'movedToWrapper'
            })
          }
        }
      }
    })
  }
}


code:
/**
 * @author Yosuke Ota
 */
'use strict'

const { wrapCoreRule } = require('../utils')

// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapCoreRule('no-useless-concat', {
  applyDocument: true
})


code:
/**
 * @author Yosuke Ota
 * See LICENSE file in root directory for full license.
 */
'use strict'

const utils = require('../utils')

/**
 * Strip quotes string
 * @param {string} text
 * @returns {string|null}
 */
function stripQuotesForHTML(text) {
  if (
    (text[0] === '"' || text[0] === "'" || text[0] === '`') &&
    text[0] === text[text.length - 1]
  ) {
    return text.slice(1, -1)
  }

  const re =
    /^(?:&(?:quot|apos|#\d+|#x[\da-f]+);|["'`])([\s\S]*)(?:&(?:quot|apos|#\d+|#x[\da-f]+);|["'`])$/u.exec(
      text
    )
  if (!re) {
    return null
  }
  return re[1]
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow unnecessary mustache interpolations',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-useless-mustaches.html'
    },
    fixable: 'code',
    schema: [
      {
        type: 'object',
        properties: {
          ignoreIncludesComment: {
            type: 'boolean'
          },
          ignoreStringEscape: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      unexpected:
        'Unexpected mustache interpolation with a string literal value.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const opts = context.options[0] || {}
    const ignoreIncludesComment = opts.ignoreIncludesComment
    const ignoreStringEscape = opts.ignoreStringEscape
    const sourceCode = context.getSourceCode()

    /**
     * Report if the value expression is string literals
     * @param {VExpressionContainer} node the node to check
     */
    function verify(node) {
      const { expression } = node
      if (!expression) {
        return
      }
      /** @type {string} */
      let strValue
      /** @type {string} */
      let rawValue
      if (expression.type === 'Literal') {
        if (typeof expression.value !== 'string') {
          return
        }
        strValue = expression.value
        rawValue = sourceCode.getText(expression).slice(1, -1)
      } else if (expression.type === 'TemplateLiteral') {
        if (expression.expressions.length > 0) {
          return
        }
        strValue = expression.quasis[0].value.cooked
        rawValue = expression.quasis[0].value.raw
      } else {
        return
      }
      const tokenStore = sourceCode.parserServices.getTemplateBodyTokenStore()
      const hasComment = tokenStore
        .getTokens(node, { includeComments: true })
        .some((t) => t.type === 'Block' || t.type === 'Line')
      if (ignoreIncludesComment && hasComment) {
        return
      }

      let hasEscape = false
      if (rawValue !== strValue) {
        // check escapes
        const chars = [...rawValue]
        let c = chars.shift()
        while (c) {
          if (c === '\\') {
            c = chars.shift()
            if (
              c == null ||
              // ignore "\\", '"', "'", "`" and "$"
              'nrvtbfux'.includes(c)
            ) {
              // has useful escape.
              hasEscape = true
              break
            }
          }
          c = chars.shift()
        }
      }
      if (ignoreStringEscape && hasEscape) {
        return
      }

      context.report({
        node,
        messageId: 'unexpected',
        fix(fixer) {
          if (hasComment || hasEscape) {
            // cannot fix
            return null
          }
          const text = stripQuotesForHTML(sourceCode.getText(expression))
          if (text == null) {
            // unknowns
            return null
          }
          if (text.includes('\n') || /^\s|\s$/u.test(text)) {
            // It doesn't autofix because another rule like indent or eol space might remove spaces.
            return null
          }
          const escaped = text.replace(/</g, '<')

          return fixer.replaceText(node, escaped.replace(/\\([\S\s])/g, '$1'))
        }
      })
    }

    return utils.defineTemplateBodyVisitor(context, {
      'VElement > VExpressionContainer': verify
    })
  }
}