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

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

/**
 * @typedef {object} ParsedOption
 * @property {Tester} test
 * @property {string|undefined} [message]
 */
/**
 * @typedef {object} MatchResult
 * @property {Tester | undefined} [next]
 * @property {boolean} [wildcard]
 * @property {string} keyName
 */
/**
 * @typedef { (name: string) => boolean } Matcher
 * @typedef { (node: Property | SpreadElement) => (MatchResult | null) } Tester
 */

/**
 * @param {string} str
 * @returns {Matcher}
 */
function buildMatcher(str) {
  if (regexp.isRegExp(str)) {
    const re = regexp.toRegExp(str)
    return (s) => {
      re.lastIndex = 0
      return re.test(s)
    }
  }
  return (s) => s === str
}

/**
 * @param {string | string[] | { name: string | string[], message?: string } } option
 * @returns {ParsedOption}
 */
function parseOption(option) {
  if (typeof option === 'string' || Array.isArray(option)) {
    return parseOption({
      name: option
    })
  }

  /**
   * @typedef {object} StepForTest
   * @property {Matcher} test
   * @property {undefined} [wildcard]
   * @typedef {object} StepForWildcard
   * @property {undefined} [test]
   * @property {true} wildcard
   * @typedef {StepForTest | StepForWildcard} Step
   */

  /** @type {Step[]} */
  const steps = []
  for (const name of Array.isArray(option.name) ? option.name : [option.name]) {
    if (name === '*') {
      steps.push({ wildcard: true })
    } else {
      steps.push({ test: buildMatcher(name) })
    }
  }
  const message = option.message

  return {
    test: buildTester(0),
    message
  }

  /**
   * @param {number} index
   * @returns {Tester}
   */
  function buildTester(index) {
    const step = steps[index]
    const next = index + 1
    const needNext = steps.length > next
    return (node) => {
      /** @type {string} */
      let keyName
      if (step.wildcard) {
        keyName = '*'
      } else {
        if (node.type !== 'Property') {
          return null
        }
        const name = utils.getStaticPropertyName(node)
        if (!name || !step.test(name)) {
          return null
        }
        keyName = name
      }

      return {
        next: needNext ? buildTester(next) : undefined,
        wildcard: step.wildcard,
        keyName
      }
    }
  }
}

/**
 * @param {string[]} path
 */
function defaultMessage(path) {
  return `Using \`${path.join('.')}\` is not allowed.`
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow specific component option',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-restricted-component-options.html'
    },
    fixable: null,
    schema: {
      type: 'array',
      items: {
        oneOf: [
          { type: 'string' },
          {
            type: 'array',
            items: {
              type: 'string'
            }
          },
          {
            type: 'object',
            properties: {
              name: {
                anyOf: [
                  { type: 'string' },
                  {
                    type: 'array',
                    items: {
                      type: 'string'
                    }
                  }
                ]
              },
              message: { type: 'string', minLength: 1 }
            },
            required: ['name'],
            additionalProperties: false
          }
        ]
      },
      uniqueItems: true,
      minItems: 0
    },

    messages: {
      // eslint-disable-next-line eslint-plugin/report-message-format
      restrictedOption: '{{message}}'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    if (!context.options || context.options.length === 0) {
      return {}
    }
    /** @type {ParsedOption[]} */
    const options = context.options.map(parseOption)

    return utils.compositingVisitors(
      utils.defineVueVisitor(context, {
        onVueObjectEnter(node) {
          for (const option of options) {
            verify(node, option.test, option.message)
          }
        }
      }),
      utils.defineScriptSetupVisitor(context, {
        onDefineOptionsEnter(node) {
          if (node.arguments.length === 0) return
          const define = node.arguments[0]
          if (define.type !== 'ObjectExpression') return
          for (const option of options) {
            verify(define, option.test, option.message)
          }
        }
      })
    )

    /**
     * @param {ObjectExpression} node
     * @param {Tester} test
     * @param {string | undefined} customMessage
     * @param {string[]} path
     */
    function verify(node, test, customMessage, path = []) {
      for (const prop of node.properties) {
        const result = test(prop)
        if (!result) {
          continue
        }
        if (result.next) {
          if (
            prop.type !== 'Property' ||
            prop.value.type !== 'ObjectExpression'
          ) {
            continue
          }
          verify(prop.value, result.next, customMessage, [
            ...path,
            result.keyName
          ])
        } else {
          const message =
            customMessage || defaultMessage([...path, result.keyName])
          context.report({
            node: prop.type === 'Property' ? prop.key : prop,
            messageId: 'restrictedOption',
            data: { message }
          })
        }
      }
    }
  }
}


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

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

/**
 * @typedef {object} ParsedOption
 * @property { (name: string) => boolean } test
 * @property {string|undefined} [message]
 * @property {string|undefined} [suggest]
 */

/**
 * @param {string} str
 * @returns {(str: string) => boolean}
 */
function buildMatcher(str) {
  if (regexp.isRegExp(str)) {
    const re = regexp.toRegExp(str)
    return (s) => {
      re.lastIndex = 0
      return re.test(s)
    }
  }
  return (s) => s === str
}
/**
 * @param {string|{event: string, message?: string, suggest?: string}} option
 * @returns {ParsedOption}
 */
function parseOption(option) {
  if (typeof option === 'string') {
    const matcher = buildMatcher(option)
    return {
      test(name) {
        return matcher(name)
      }
    }
  }
  const parsed = parseOption(option.event)
  parsed.message = option.message
  parsed.suggest = option.suggest
  return parsed
}

/**
 * @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 }
    }
  }

  // cannot check
  return null
}
/**
 * Get the callee member node from the given CallExpression
 * @param {CallExpression} node CallExpression
 */
function getCalleeMemberNode(node) {
  const callee = utils.skipChainExpression(node.callee)

  if (callee.type === 'MemberExpression') {
    const name = utils.getStaticPropertyName(callee)
    if (name) {
      return { name, member: callee }
    }
  }
  return null
}
module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow specific custom event',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-restricted-custom-event.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: {
      type: 'array',
      items: {
        oneOf: [
          { type: ['string'] },
          {
            type: 'object',
            properties: {
              event: { type: 'string' },
              message: { type: 'string', minLength: 1 },
              suggest: { type: 'string' }
            },
            required: ['event'],
            additionalProperties: false
          }
        ]
      },
      uniqueItems: true,
      minItems: 0
    },

    messages: {
      // eslint-disable-next-line eslint-plugin/report-message-format
      restrictedEvent: '{{message}}',
      instead: 'Instead, change to `{{suggest}}`.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {Map<ObjectExpression, {contextReferenceIds:Set<Identifier>,emitReferenceIds:Set<Identifier>}>} */
    const setupContexts = new Map()
    /** @type {ParsedOption[]} */
    const options = context.options.map(parseOption)

    /**
     * @param { NameWithLoc } nameWithLoc
     */
    function verify(nameWithLoc) {
      const name = nameWithLoc.name

      for (const option of options) {
        if (option.test(name)) {
          const message =
            option.message || `Using \`${name}\` event is not allowed.`
          context.report({
            loc: nameWithLoc.loc,
            messageId: 'restrictedEvent',
            data: { message },
            suggest: option.suggest
              ? [
                  {
                    fix(fixer) {
                      const sourceCode = context.getSourceCode()
                      return fixer.replaceTextRange(
                        nameWithLoc.range,
                        `${
                          sourceCode.text[nameWithLoc.range[0]]
                        }${JSON.stringify(option.suggest)
                          .slice(1, -1)
                          .replace(/'/gu, String.raw`\'`)}${
                          sourceCode.text[nameWithLoc.range[1] - 1]
                        }`
                      )
                    },
                    messageId: 'instead',
                    data: { suggest: option.suggest }
                  }
                ]
              : []
          })
          break
        }
      }
    }

    return utils.defineTemplateBodyVisitor(
      context,
      {
        CallExpression(node) {
          const callee = node.callee
          const nameWithLoc = getNameParamNode(node)
          if (!nameWithLoc) {
            // cannot check
            return
          }
          if (callee.type === 'Identifier' && callee.name === '$emit') {
            verify(nameWithLoc)
          }
        }
      },
      utils.compositingVisitors(
        utils.defineVueVisitor(context, {
          onSetupFunctionEnter(node, { node: vueNode }) {
            const contextParam = utils.skipDefaultParamValue(node.params[1])
            if (!contextParam) {
              // no arguments
              return
            }
            if (
              contextParam.type === 'RestElement' ||
              contextParam.type === 'ArrayPattern'
            ) {
              // cannot check
              return
            }
            const contextReferenceIds = new Set()
            const emitReferenceIds = new Set()
            if (contextParam.type === 'ObjectPattern') {
              const emitProperty = utils.findAssignmentProperty(
                contextParam,
                'emit'
              )
              if (!emitProperty || emitProperty.value.type !== 'Identifier') {
                return
              }
              const emitParam = emitProperty.value
              // `setup(props, {emit})`
              const variable = findVariable(
                utils.getScope(context, emitParam),
                emitParam
              )
              if (!variable) {
                return
              }
              for (const reference of variable.references) {
                emitReferenceIds.add(reference.identifier)
              }
            } else {
              // `setup(props, context)`
              const variable = findVariable(
                utils.getScope(context, contextParam),
                contextParam
              )
              if (!variable) {
                return
              }
              for (const reference of variable.references) {
                contextReferenceIds.add(reference.identifier)
              }
            }
            setupContexts.set(vueNode, {
              contextReferenceIds,
              emitReferenceIds
            })
          },
          CallExpression(node, { node: vueNode }) {
            const nameWithLoc = getNameParamNode(node)
            if (!nameWithLoc) {
              // cannot check
              return
            }

            // verify setup context
            const setupContext = setupContexts.get(vueNode)
            if (setupContext) {
              const { contextReferenceIds, emitReferenceIds } = setupContext
              if (
                node.callee.type === 'Identifier' &&
                emitReferenceIds.has(node.callee)
              ) {
                // verify setup(props,{emit}) {emit()}
                verify(nameWithLoc)
              } else {
                const emit = getCalleeMemberNode(node)
                if (
                  emit &&
                  emit.name === 'emit' &&
                  emit.member.object.type === 'Identifier' &&
                  contextReferenceIds.has(emit.member.object)
                ) {
                  // verify setup(props,context) {context.emit()}
                  verify(nameWithLoc)
                }
              }
            }
          },
          onVueObjectExit(node) {
            setupContexts.delete(node)
          }
        }),
        {
          CallExpression(node) {
            const nameWithLoc = getNameParamNode(node)
            if (!nameWithLoc) {
              // cannot check
              return
            }
            const emit = getCalleeMemberNode(node)
            // verify $emit
            if (emit && emit.name === '$emit') {
              // verify this.$emit()
              verify(nameWithLoc)
            }
          }
        }
      )
    )
  }
}

license: mit


code:
/**
 * @author Doug Wade <douglas.b.wade@gmail.com>
 */

'use strict'

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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow specific HTML elements',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-restricted-html-elements.html'
    },
    fixable: null,
    schema: {
      type: 'array',
      items: {
        oneOf: [
          { type: 'string' },
          {
            type: 'object',
            properties: {
              element: { type: 'string' },
              message: { type: 'string', minLength: 1 }
            },
            required: ['element'],
            additionalProperties: false
          }
        ]
      },
      uniqueItems: true,
      minItems: 0
    },
    messages: {
      forbiddenElement: 'Unexpected use of forbidden HTML element {{name}}.',
      // eslint-disable-next-line eslint-plugin/report-message-format
      customMessage: '{{message}}'
    }
  },
  /**
   * @param {RuleContext} context - The rule context.
   * @returns {RuleListener} AST event handlers.
   */
  create(context) {
    return utils.defineTemplateBodyVisitor(context, {
      /**
       * @param {VElement} node
       */
      VElement(node) {
        if (!utils.isHtmlElementNode(node)) {
          return
        }

        for (const option of context.options) {
          const element = option.element || option

          if (element === node.rawName) {
            context.report({
              messageId: option.message ? 'customMessage' : 'forbiddenElement',
              data: {
                name: node.rawName,
                message: option.message
              },
              node: node.startTag
            })
          }
        }
      }
    })
  }
}


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

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

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

/**
 * @typedef {object} ParsedOption
 * @property { (name: string) => boolean } test
 * @property {string|undefined} [message]
 * @property {string|undefined} [suggest]
 */

/**
 * @param {string} str
 * @returns {(str: string) => boolean}
 */
function buildMatcher(str) {
  if (regexp.isRegExp(str)) {
    const re = regexp.toRegExp(str)
    return (s) => {
      re.lastIndex = 0
      return re.test(s)
    }
  }
  return (s) => s === str
}
/**
 * @param {string|{name:string, message?: string, suggest?:string}} option
 * @returns {ParsedOption}
 */
function parseOption(option) {
  if (typeof option === 'string') {
    const matcher = buildMatcher(option)
    return {
      test(name) {
        return matcher(name)
      }
    }
  }
  const parsed = parseOption(option.name)
  parsed.message = option.message
  parsed.suggest = option.suggest
  return parsed
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow specific props',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-restricted-props.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: {
      type: 'array',
      items: {
        oneOf: [
          { type: ['string'] },
          {
            type: 'object',
            properties: {
              name: { type: 'string' },
              message: { type: 'string', minLength: 1 },
              suggest: { type: 'string' }
            },
            required: ['name'],
            additionalProperties: false
          }
        ]
      },
      uniqueItems: true,
      minItems: 0
    },

    messages: {
      // eslint-disable-next-line eslint-plugin/report-message-format
      restrictedProp: '{{message}}',
      instead: 'Instead, change to `{{suggest}}`.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {ParsedOption[]} */
    const options = context.options.map(parseOption)

    /**
     * @param {ComponentProp[]} props
     * @param {(fixer: RuleFixer, propName: string, replaceKeyText: string) => Iterable<Fix>} [fixPropInOtherPlaces]
     */
    function processProps(props, fixPropInOtherPlaces) {
      for (const prop of props) {
        if (!prop.propName) {
          continue
        }

        for (const option of options) {
          if (option.test(prop.propName)) {
            const message =
              option.message ||
              `Using \`${prop.propName}\` props is not allowed.`
            context.report({
              node: prop.type === 'infer-type' ? prop.node : prop.key,
              messageId: 'restrictedProp',
              data: { message },
              suggest:
                prop.type === 'infer-type'
                  ? null
                  : createSuggest(
                      prop.key,
                      option,
                      fixPropInOtherPlaces
                        ? (fixer, replaceKeyText) =>
                            fixPropInOtherPlaces(
                              fixer,
                              prop.propName,
                              replaceKeyText
                            )
                        : undefined
                    )
            })
            break
          }
        }
      }
    }
    return utils.compositingVisitors(
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(node, props) {
          processProps(props, fixPropInOtherPlaces)

          /**
           * @param {RuleFixer} fixer
           * @param {string} propName
           * @param {string} replaceKeyText
           */
          function fixPropInOtherPlaces(fixer, propName, replaceKeyText) {
            /** @type {(Property|AssignmentProperty)[]} */
            const propertyNodes = []
            const withDefault = utils.getWithDefaultsProps(node)[propName]
            if (withDefault) {
              propertyNodes.push(withDefault)
            }
            const propDestructure = utils.getPropsDestructure(node)[propName]
            if (propDestructure) {
              propertyNodes.push(propDestructure)
            }
            return propertyNodes.map((propertyNode) =>
              propertyNode.shorthand
                ? fixer.insertTextBefore(
                    propertyNode.value,
                    `${replaceKeyText}:`
                  )
                : fixer.replaceText(propertyNode.key, replaceKeyText)
            )
          }
        }
      }),
      utils.defineVueVisitor(context, {
        onVueObjectEnter(node) {
          processProps(utils.getComponentPropsFromOptions(node))
        }
      })
    )
  }
}

/**
 * @param {Expression} node
 * @param {ParsedOption} option
 * @param {(fixer: RuleFixer, replaceKeyText: string) => Iterable<Fix>} [fixPropInOtherPlaces]
 * @returns {Rule.SuggestionReportDescriptor[]}
 */
function createSuggest(node, option, fixPropInOtherPlaces) {
  if (!option.suggest) {
    return []
  }

  /** @type {string} */
  let replaceText
  if (node.type === 'Literal' || node.type === 'TemplateLiteral') {
    replaceText = JSON.stringify(option.suggest)
  } else if (node.type === 'Identifier') {
    replaceText = /^[a-z]\w*$/iu.test(option.suggest)
      ? option.suggest
      : JSON.stringify(option.suggest)
  } else {
    return []
  }

  return [
    {
      fix(fixer) {
        const fixes = [fixer.replaceText(node, replaceText)]
        if (fixPropInOtherPlaces) {
          fixes.push(...fixPropInOtherPlaces(fixer, replaceText))
        }
        return fixes.sort((a, b) => a.range[0] - b.range[0])
      },
      messageId: 'instead',
      data: { suggest: option.suggest }
    }
  ]
}


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

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

/**
 * @typedef {object} ParsedOption
 * @property { (key: VAttribute) => boolean } test
 * @property {boolean} [useValue]
 * @property {boolean} [useElement]
 * @property {string} [message]
 */

/**
 * @param {string} str
 * @returns {(str: string) => boolean}
 */
function buildMatcher(str) {
  if (regexp.isRegExp(str)) {
    const re = regexp.toRegExp(str)
    return (s) => {
      re.lastIndex = 0
      return re.test(s)
    }
  }
  return (s) => s === str
}
/**
 * @param {any} option
 * @returns {ParsedOption}
 */
function parseOption(option) {
  if (typeof option === 'string') {
    const matcher = buildMatcher(option)
    return {
      test({ key }) {
        return matcher(key.rawName)
      }
    }
  }
  const parsed = parseOption(option.key)
  if (option.value) {
    const keyTest = parsed.test
    if (option.value === true) {
      parsed.test = (node) => {
        if (!keyTest(node)) {
          return false
        }
        return node.value == null || node.value.value === node.key.rawName
      }
    } else {
      const valueMatcher = buildMatcher(option.value)
      parsed.test = (node) => {
        if (!keyTest(node)) {
          return false
        }
        return node.value != null && valueMatcher(node.value.value)
      }
    }
    parsed.useValue = true
  }
  if (option.element) {
    const argTest = parsed.test
    const tagMatcher = buildMatcher(option.element)
    parsed.test = (node) => {
      if (!argTest(node)) {
        return false
      }
      const element = node.parent.parent
      return tagMatcher(element.rawName)
    }
    parsed.useElement = true
  }
  parsed.message = option.message
  return parsed
}

/**
 * @param {VAttribute} node
 * @param {ParsedOption} option
 */
function defaultMessage(node, option) {
  const key = node.key.rawName
  let value = ''
  if (option.useValue) {
    value = node.value == null ? '` set to `true' : `="${node.value.value}"`
  }

  let on = ''
  if (option.useElement) {
    on = ` on \`<${node.parent.parent.rawName}>\``
  }
  return `Using \`${key + value}\`${on} is not allowed.`
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow specific attribute',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-restricted-static-attribute.html'
    },
    fixable: null,
    schema: {
      type: 'array',
      items: {
        oneOf: [
          { type: 'string' },
          {
            type: 'object',
            properties: {
              key: { type: 'string' },
              value: { anyOf: [{ type: 'string' }, { enum: [true] }] },
              element: { type: 'string' },
              message: { type: 'string', minLength: 1 }
            },
            required: ['key'],
            additionalProperties: false
          }
        ]
      },
      uniqueItems: true,
      minItems: 0
    },

    messages: {
      // eslint-disable-next-line eslint-plugin/report-message-format
      restrictedAttr: '{{message}}'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    if (context.options.length === 0) {
      return {}
    }
    /** @type {ParsedOption[]} */
    const options = context.options.map(parseOption)

    return utils.defineTemplateBodyVisitor(context, {
      /**
       * @param {VAttribute} node
       */
      'VAttribute[directive=false]'(node) {
        for (const option of options) {
          if (option.test(node)) {
            const message = option.message || defaultMessage(node, option)
            context.report({
              node,
              messageId: 'restrictedAttr',
              data: { message }
            })
            return
          }
        }
      }
    })
  }
}


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

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

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


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

const utils = require('../utils')
const regexp = require('../utils/regexp')
/**
 * @typedef {object} ParsedOption
 * @property { (key: VDirectiveKey) => boolean } test
 * @property {string[]} modifiers
 * @property {boolean} [useElement]
 * @property {string} [message]
 */

const DEFAULT_OPTIONS = [
  {
    argument: '/^v-/',
    message:
      'Using `:v-xxx` is not allowed. Instead, remove `:` and use it as directive.'
  }
]

/**
 * @param {string} str
 * @returns {(str: string) => boolean}
 */
function buildMatcher(str) {
  if (regexp.isRegExp(str)) {
    const re = regexp.toRegExp(str)
    return (s) => {
      re.lastIndex = 0
      return re.test(s)
    }
  }
  return (s) => s === str
}
/**
 * @param {any} option
 * @returns {ParsedOption}
 */
function parseOption(option) {
  if (typeof option === 'string') {
    const matcher = buildMatcher(option)
    return {
      test(key) {
        return Boolean(
          key.argument &&
            key.argument.type === 'VIdentifier' &&
            matcher(key.argument.rawName)
        )
      },
      modifiers: []
    }
  }
  if (option === null) {
    return {
      test(key) {
        return key.argument === null
      },
      modifiers: []
    }
  }
  const parsed = parseOption(option.argument)
  if (option.modifiers) {
    const argTest = parsed.test
    parsed.test = (key) => {
      if (!argTest(key)) {
        return false
      }
      return /** @type {string[]} */ (option.modifiers).every((modName) =>
        key.modifiers.some((mid) => mid.name === modName)
      )
    }
    parsed.modifiers = option.modifiers
  }
  if (option.element) {
    const argTest = parsed.test
    const tagMatcher = buildMatcher(option.element)
    parsed.test = (key) => {
      if (!argTest(key)) {
        return false
      }
      const element = key.parent.parent.parent
      return tagMatcher(element.rawName)
    }
    parsed.useElement = true
  }
  parsed.message = option.message
  return parsed
}

/**
 * @param {VDirectiveKey} key
 * @param {ParsedOption} option
 */
function defaultMessage(key, option) {
  const vbind = key.name.rawName === ':' ? '' : 'v-bind'
  const arg =
    key.argument != null && key.argument.type === 'VIdentifier'
      ? `:${key.argument.rawName}`
      : ''
  const mod =
    option.modifiers.length > 0 ? `.${option.modifiers.join('.')}` : ''
  let on = ''
  if (option.useElement) {
    on = ` on \`<${key.parent.parent.parent.rawName}>\``
  }
  return `Using \`${vbind + arg + mod}\`${on} is not allowed.`
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow specific argument in `v-bind`',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-restricted-v-bind.html'
    },
    fixable: null,
    schema: {
      type: 'array',
      items: {
        oneOf: [
          { type: ['string', 'null'] },
          {
            type: 'object',
            properties: {
              argument: { type: ['string', 'null'] },
              modifiers: {
                type: 'array',
                items: {
                  type: 'string',
                  enum: ['prop', 'camel', 'sync', 'attr']
                },
                uniqueItems: true
              },
              element: { type: 'string' },
              message: { type: 'string', minLength: 1 }
            },
            required: ['argument'],
            additionalProperties: false
          }
        ]
      },
      uniqueItems: true,
      minItems: 0
    },

    messages: {
      // eslint-disable-next-line eslint-plugin/report-message-format
      restrictedVBind: '{{message}}'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {ParsedOption[]} */
    const options = (
      context.options.length === 0 ? DEFAULT_OPTIONS : context.options
    ).map(parseOption)

    return utils.defineTemplateBodyVisitor(context, {
      /**
       * @param {VDirectiveKey} node
       */
      "VAttribute[directive=true][key.name.name='bind'] > VDirectiveKey"(node) {
        for (const option of options) {
          if (option.test(node)) {
            const message = option.message || defaultMessage(node, option)
            context.report({
              node,
              messageId: 'restrictedVBind',
              data: { message }
            })
            return
          }
        }
      }
    })
  }
}


code:
/**
 * @author Kamogelo Moalusi <github.com/thesheppard>
 * See LICENSE file in root directory for full license.
 */
'use strict'

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

/**
 * @typedef {object} ParsedOption
 * @property { (key: VDirectiveKey) => boolean } test
 * @property {string[]} [modifiers]
 * @property {boolean} [useElement]
 * @property {string} [message]
 */

/**
 * @param {string} str
 * @returns {(str: string) => boolean}
 */
function buildMatcher(str) {
  if (regexp.isRegExp(str)) {
    const re = regexp.toRegExp(str)
    return (s) => {
      re.lastIndex = 0
      return re.test(s)
    }
  }
  return (s) => s === str
}

/**
 * @param {any} option
 * @returns {ParsedOption}
 */
function parseOption(option) {
  if (typeof option === 'string') {
    const matcher = buildMatcher(option)
    return {
      test(key) {
        return Boolean(
          key.argument &&
            key.argument.type === 'VIdentifier' &&
            matcher(key.argument.rawName)
        )
      }
    }
  }
  if (option === null) {
    return {
      test(key) {
        return key.argument === null
      }
    }
  }
  const parsed = parseOption(option.argument)

  if (option.modifiers) {
    const argTest = parsed.test
    parsed.test = (key) => {
      if (!argTest(key)) {
        return false
      }
      return /** @type {string[]} */ (option.modifiers).every((modName) =>
        key.modifiers.some((mid) => mid.name === modName)
      )
    }
    parsed.modifiers = option.modifiers
  }
  if (option.element) {
    const argTest = parsed.test
    const tagMatcher = buildMatcher(option.element)
    parsed.test = (key) => {
      if (!argTest(key)) {
        return false
      }
      return tagMatcher(key.parent.parent.parent.rawName)
    }
    parsed.useElement = true
  }
  parsed.message = option.message
  return parsed
}

/**
 * @param {VDirectiveKey} key
 * @param {ParsedOption} option
 */
function defaultMessage(key, option) {
  const von = key.name.rawName === '@' ? '' : 'v-on'
  const arg =
    key.argument != null && key.argument.type === 'VIdentifier'
      ? `${key.name.rawName === '@' ? '@' : ':'}${key.argument.rawName}`
      : ''
  const mod =
    option.modifiers != null && option.modifiers.length > 0
      ? `.${option.modifiers.join('.')}`
      : ''
  let element = 'element'
  if (option.useElement) {
    element = `<${key.parent.parent.parent.rawName}>`
  }
  return `Using \`${von + arg + mod}\` is not allowed on this ${element}.`
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow specific argument in `v-on`',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-restricted-v-on.html'
    },
    fixable: null,
    schema: {
      type: 'array',
      items: {
        oneOf: [
          { type: ['string', 'null'] },
          {
            type: 'object',
            properties: {
              argument: { type: ['string', 'null'] },
              element: { type: 'string' },
              message: { type: 'string', minLength: 1 },
              modifiers: {
                type: 'array',
                items: {
                  type: 'string',
                  enum: [
                    'prevent',
                    'stop',
                    'capture',
                    'self',
                    'once',
                    'passive'
                  ]
                },
                uniqueItems: true,
                minItems: 1
              }
            },
            required: ['argument'],
            additionalProperties: false
          }
        ]
      },
      uniqueItems: true
    },
    messages: {
      // eslint-disable-next-line eslint-plugin/report-message-format
      restrictedVOn: '{{message}}'
    }
  },

  /** @param {RuleContext} context */
  create(context) {
    if (context.options.length === 0) {
      return {}
    }
    /** @type {ParsedOption[]} */
    const options = context.options.map(parseOption)

    return utils.defineTemplateBodyVisitor(context, {
      /**
       * @param {VDirectiveKey} node
       */
      "VAttribute[directive=true][key.name.name='on'] > VDirectiveKey"(node) {
        for (const option of options) {
          if (option.test(node)) {
            const message = option.message || defaultMessage(node, option)
            context.report({
              node,
              messageId: 'restrictedVOn',
              data: { message }
            })
            return
          }
        }
      }
    })
  }
}


code:
/**
 * @author Perry Song
 * @copyright 2023 Perry Song. All rights reserved.
 * See LICENSE file in root directory for full license.
 */
'use strict'

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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow `v-if` directives on root element',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-root-v-if.html'
    },
    fixable: null,
    schema: [],
    messages: {
      noRootVIf: '`v-if` should not be used on root element without `v-else`.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return {
      /** @param {Program} program */
      Program(program) {
        const element = program.templateBody
        if (element == null) {
          return
        }

        const rootElements = element.children.filter(utils.isVElement)
        if (
          rootElements.length === 1 &&
          utils.hasDirective(rootElements[0], 'if')
        ) {
          context.report({
            node: element,
            loc: element.loc,
            messageId: 'noRootVIf'
          })
        }
      }
    }
  }
}


code:
/**
 * @author Yosuke Ota
 * See LICENSE file in root directory for full license.
 */
'use strict'
const baseRule = require('./no-setup-props-reactivity-loss')

module.exports = {
  // eslint-disable-next-line eslint-plugin/require-meta-schema, eslint-plugin/prefer-message-ids, internal/no-invalid-meta, eslint-plugin/require-meta-type -- inherit schema from base rule
  meta: {
    ...baseRule.meta,
    // eslint-disable-next-line eslint-plugin/require-meta-docs-description, internal/no-invalid-meta-docs-categories, eslint-plugin/meta-property-ordering
    docs: {
      ...baseRule.meta.docs,
      url: 'https://eslint.vuejs.org/rules/no-setup-props-destructure.html'
    },
    deprecated: true,
    replacedBy: ['no-setup-props-reactivity-loss']
  },
  /** @param {RuleContext} context */
  create(context) {
    return baseRule.create(context)
  }
}


code:
/**
 * @author Yosuke Ota
 * See LICENSE file in root directory for full license.
 */
'use strict'
const { findVariable } = require('@eslint-community/eslint-utils')
const utils = require('../utils')

/**
 * @typedef {'props'|'prop'} PropIdKind
 *    - `'props'`: A node is a container object that has props.
 *    - `'prop'`: A node is a variable with one prop.
 */
/**
 * @typedef {object} PropId
 * @property {Pattern} node
 * @property {PropIdKind} kind
 */
/**
 * Iterates over Prop identifiers by parsing the given pattern
 * in the left operand of defineProps().
 * @param {Pattern} node
 * @returns {IterableIterator<PropId>}
 */
function* iteratePropIds(node) {
  switch (node.type) {
    case 'ObjectPattern': {
      for (const prop of node.properties) {
        yield prop.type === 'Property'
          ? {
              // e.g. `const { prop } = defineProps()`
              node: unwrapAssignment(prop.value),
              kind: 'prop'
            }
          : {
              // RestElement
              // e.g. `const { x, ...prop } = defineProps()`
              node: unwrapAssignment(prop.argument),
              kind: 'props'
            }
      }
      break
    }
    default: {
      // e.g. `const props = defineProps()`
      yield { node: unwrapAssignment(node), kind: 'props' }
    }
  }
}

/**
 * @template {Pattern} T
 * @param {T} node
 * @returns {Pattern}
 */
function unwrapAssignment(node) {
  if (node.type === 'AssignmentPattern') {
    return node.left
  }
  return node
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'disallow usages that lose the reactivity of `props` passed to `setup`',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-setup-props-reactivity-loss.html'
    },
    fixable: null,
    schema: [],
    messages: {
      destructuring:
        'Destructuring the `props` will cause the value to lose reactivity.',
      getProperty:
        'Getting a value from the `props` in root scope of `{{scopeName}}` will cause the value to lose reactivity.'
    }
  },
  /**
   * @param {RuleContext} context
   * @returns {RuleListener}
   **/
  create(context) {
    /**
     * @typedef {object} ScopePropsReferences
     * @property {object} refs
     * @property {Set<Identifier>} refs.props A set of references to container objects with multiple props.
     * @property {Set<Identifier>} refs.prop A set of references a variable with one property.
     * @property {string} scopeName
     */
    /** @type {Map<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program, ScopePropsReferences>} */
    const setupScopePropsReferenceIds = new Map()
    const wrapperExpressionTypes = new Set([
      'ArrayExpression',
      'ObjectExpression'
    ])

    /**
     * @param {ESNode} node
     * @param {string} messageId
     * @param {string} scopeName
     */
    function report(node, messageId, scopeName) {
      context.report({
        node,
        messageId,
        data: {
          scopeName
        }
      })
    }

    /**
     * @param {Pattern} left
     * @param {Expression | null} right
     * @param {ScopePropsReferences} propsReferences
     */
    function verify(left, right, propsReferences) {
      if (!right) {
        return
      }

      const rightNode = utils.skipChainExpression(right)

      if (
        wrapperExpressionTypes.has(rightNode.type) &&
        isPropsMemberAccessed(rightNode, propsReferences)
      ) {
        // e.g. `const foo = { x: props.x }`
        report(rightNode, 'getProperty', propsReferences.scopeName)
        return
      }

      // Get the expression that provides the value.
      /** @type {Expression | Super} */
      let expression = rightNode
      while (expression.type === 'MemberExpression') {
        expression = utils.skipChainExpression(expression.object)
      }
      /** A list of expression nodes to verify */
      const expressions =
        expression.type === 'TemplateLiteral'
          ? expression.expressions
          : expression.type === 'ConditionalExpression'
            ? [expression.test, expression.consequent, expression.alternate]
            : expression.type === 'Identifier'
              ? [expression]
              : []

      if (
        (left.type === 'ArrayPattern' || left.type === 'ObjectPattern') &&
        expressions.some(
          (expr) =>
            expr.type === 'Identifier' && propsReferences.refs.props.has(expr)
        )
      ) {
        // e.g. `const {foo} = props`
        report(left, 'getProperty', propsReferences.scopeName)
        return
      }

      const reportNode = expressions.find((expr) =>
        isPropsMemberAccessed(expr, propsReferences)
      )
      if (reportNode) {
        report(reportNode, 'getProperty', propsReferences.scopeName)
      }
    }

    /**
     * @param {Expression | Super} node
     * @param {ScopePropsReferences} propsReferences
     */
    function isPropsMemberAccessed(node, propsReferences) {
      for (const props of propsReferences.refs.props) {
        const isPropsInExpressionRange = utils.inRange(node.range, props)
        const isPropsMemberExpression =
          props.parent.type === 'MemberExpression' &&
          props.parent.object === props

        if (isPropsInExpressionRange && isPropsMemberExpression) {
          return true
        }
      }

      // Checks for actual member access using prop destructuring.
      for (const prop of propsReferences.refs.prop) {
        const isPropsInExpressionRange = utils.inRange(node.range, prop)
        if (isPropsInExpressionRange) {
          return true
        }
      }

      return false
    }

    /**
     * @typedef {object} ScopeStack
     * @property {ScopeStack | null} upper
     * @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program} scopeNode
     */
    /**
     * @type {ScopeStack | null}
     */
    let scopeStack = null

    /**
     * @param {PropId} propId
     * @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program} scopeNode
     * @param {import('eslint').Scope.Scope} currentScope
     * @param {string} scopeName
     */
    function processPropId({ node, kind }, scopeNode, currentScope, scopeName) {
      if (
        node.type === 'RestElement' ||
        node.type === 'AssignmentPattern' ||
        node.type === 'MemberExpression'
      ) {
        // cannot check
        return
      }
      if (node.type === 'ArrayPattern' || node.type === 'ObjectPattern') {
        report(node, 'destructuring', scopeName)
        return
      }

      const variable = findVariable(currentScope, node)
      if (!variable) {
        return
      }

      let scopePropsReferences = setupScopePropsReferenceIds.get(scopeNode)
      if (!scopePropsReferences) {
        scopePropsReferences = {
          refs: {
            props: new Set(),
            prop: new Set()
          },
          scopeName
        }
        setupScopePropsReferenceIds.set(scopeNode, scopePropsReferences)
      }
      const propsReferenceIds = scopePropsReferences.refs[kind]
      for (const reference of variable.references) {
        // If reference is in another scope, we can't check it.
        if (reference.from !== currentScope) {
          continue
        }

        if (!reference.isRead()) {
          continue
        }

        propsReferenceIds.add(reference.identifier)
      }
    }

    return utils.compositingVisitors(
      {
        /**
         * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | Program} node
         */
        'Program, :function'(node) {
          scopeStack = {
            upper: scopeStack,
            scopeNode: node
          }
        },
        /**
         * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | Program} node
         */
        'Program, :function:exit'(node) {
          scopeStack = scopeStack && scopeStack.upper

          setupScopePropsReferenceIds.delete(node)
        },
        /**
         * @param {CallExpression} node
         */
        CallExpression(node) {
          if (!scopeStack) {
            return
          }

          const propsReferenceIds = setupScopePropsReferenceIds.get(
            scopeStack.scopeNode
          )

          if (!propsReferenceIds) {
            return
          }

          if (isPropsMemberAccessed(node, propsReferenceIds)) {
            report(node, 'getProperty', propsReferenceIds.scopeName)
          }
        },
        /**
         * @param {VariableDeclarator} node
         */
        VariableDeclarator(node) {
          if (!scopeStack) {
            return
          }
          const propsReferenceIds = setupScopePropsReferenceIds.get(
            scopeStack.scopeNode
          )
          if (!propsReferenceIds) {
            return
          }
          verify(node.id, node.init, propsReferenceIds)
        },
        /**
         * @param {AssignmentExpression} node
         */
        AssignmentExpression(node) {
          if (!scopeStack) {
            return
          }
          const propsReferenceIds = setupScopePropsReferenceIds.get(
            scopeStack.scopeNode
          )
          if (!propsReferenceIds) {
            return
          }
          verify(node.left, node.right, propsReferenceIds)
        }
      },
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(node) {
          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) {
            return
          }

          /** @type {Pattern|null} */
          let id = null
          if (target.parent.type === 'VariableDeclarator') {
            id = target.parent.init === target ? target.parent.id : null
          } else if (target.parent.type === 'AssignmentExpression') {
            id = target.parent.right === target ? target.parent.left : null
          }
          if (!id) return
          const currentScope = utils.getScope(context, node)
          for (const propId of iteratePropIds(id)) {
            processPropId(
              propId,
              context.getSourceCode().ast,
              currentScope,
              '<script setup>'
            )
          }
        }
      }),
      utils.defineVueVisitor(context, {
        onSetupFunctionEnter(node) {
          const currentScope = utils.getScope(context, node)
          const propsParam = utils.skipDefaultParamValue(node.params[0])
          if (!propsParam) return
          processPropId(
            { node: propsParam, kind: 'props' },
            node,
            currentScope,
            'setup()'
          )
        }
      })
    )
  }
}


code:
/**
 * @fileoverview Enforces component's data property to be a function.
 * @author Armano
 */
'use strict'

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

/** @param {Token} token  */
function isOpenParen(token) {
  return token.type === 'Punctuator' && token.value === '('
}

/** @param {Token} token  */
function isCloseParen(token) {
  return token.type === 'Punctuator' && token.value === ')'
}

/**
 * @param {Expression} node
 * @param {SourceCode} sourceCode
 */
function getFirstAndLastTokens(node, sourceCode) {
  let first = sourceCode.getFirstToken(node)
  let last = sourceCode.getLastToken(node)

  // If the value enclosed by parentheses, update the 'first' and 'last' by the parentheses.
  while (true) {
    const prev = sourceCode.getTokenBefore(first)
    const next = sourceCode.getTokenAfter(last)
    if (isOpenParen(prev) && isCloseParen(next)) {
      first = prev
      last = next
    } else {
      return { first, last }
    }
  }
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: "enforce component's data property to be a function",
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/no-shared-component-data.html'
    },
    fixable: 'code',
    schema: [],
    messages: {
      dataPropertyMustBeFunction:
        '`data` property in component must be a function.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()

    return utils.executeOnVueComponent(context, (obj) => {
      const invalidData = utils.findProperty(
        obj,
        'data',
        (p) =>
          p.value.type !== 'FunctionExpression' &&
          p.value.type !== 'ArrowFunctionExpression' &&
          p.value.type !== 'Identifier'
      )
      if (invalidData) {
        context.report({
          node: invalidData,
          messageId: 'dataPropertyMustBeFunction',
          fix(fixer) {
            const tokens = getFirstAndLastTokens(invalidData.value, sourceCode)

            return [
              fixer.insertTextBefore(tokens.first, 'function() {\nreturn '),
              fixer.insertTextAfter(tokens.last, ';\n}')
            ]
          }
        })
      }
    })
  }
}


code:
/**
 * @fileoverview Don't introduce side effects in computed properties
 * @author Michał Sajnóg
 */
'use strict'
const {
  ReferenceTracker,
  findVariable
} = require('@eslint-community/eslint-utils')
const utils = require('../utils')

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'disallow side effects in computed properties',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/no-side-effects-in-computed-properties.html'
    },
    fixable: null,
    schema: [],
    messages: {
      unexpectedSideEffectInFunction:
        'Unexpected side effect in computed function.',
      unexpectedSideEffectInProperty:
        'Unexpected side effect in "{{key}}" computed property.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {Map<ObjectExpression, ComponentComputedProperty[]>} */
    const computedPropertiesMap = new Map()
    /** @type {Array<FunctionExpression | ArrowFunctionExpression>} */
    const computedCallNodes = []
    /** @type {[number, number][]} */
    const setupRanges = []

    /**
     * @typedef {object} ScopeStack
     * @property {ScopeStack | null} upper
     * @property {BlockStatement | Expression | null} body
     */
    /**
     * @type {ScopeStack | null}
     */
    let scopeStack = null

    /** @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} node */
    function onFunctionEnter(node) {
      scopeStack = {
        upper: scopeStack,
        body: node.body
      }
    }

    function onFunctionExit() {
      scopeStack = scopeStack && scopeStack.upper
    }

    const nodeVisitor = {
      ':function': onFunctionEnter,
      ':function:exit': onFunctionExit,

      /**
       * @param {(Identifier | ThisExpression) & {parent: MemberExpression}} node
       * @param {VueObjectData|undefined} [info]
       */
      'MemberExpression > :matches(Identifier, ThisExpression)'(node, info) {
        if (!scopeStack) {
          return
        }
        const targetBody = scopeStack.body

        const computedProperty = (
          info ? computedPropertiesMap.get(info.node) || [] : []
        ).find(
          (cp) =>
            cp.value &&
            cp.value.range[0] <= node.range[0] &&
            node.range[1] <= cp.value.range[1] &&
            targetBody === cp.value
        )
        if (computedProperty) {
          const mem = node.parent
          if (mem.object !== node) {
            return
          }

          const isThis = utils.isThis(node, context)
          const isVue = node.type === 'Identifier' && node.name === 'Vue'

          const isVueSet =
            mem.parent.type === 'CallExpression' &&
            mem.property.type === 'Identifier' &&
            ((isThis && mem.property.name === '$set') ||
              (isVue && mem.property.name === 'set'))

          const invalid = isVueSet
            ? { node: mem.property }
            : isThis && utils.findMutating(mem)

          if (invalid) {
            context.report({
              node: invalid.node,
              messageId: 'unexpectedSideEffectInProperty',
              data: { key: computedProperty.key || 'Unknown' }
            })
          }
          return
        }

        // ignore `this` for computed functions
        if (node.type === 'ThisExpression') {
          return
        }

        const computedFunction = computedCallNodes.find(
          (c) =>
            c.range[0] <= node.range[0] &&
            node.range[1] <= c.range[1] &&
            targetBody === c.body
        )
        if (!computedFunction) {
          return
        }

        const mem = node.parent
        if (mem.object !== node) {
          return
        }

        const variable = findVariable(utils.getScope(context, node), node)
        if (!variable || variable.defs.length !== 1) {
          return
        }

        const def = variable.defs[0]
        if (
          def.type === 'ImplicitGlobalVariable' ||
          def.type === 'TDZ' ||
          def.type === 'ImportBinding'
        ) {
          return
        }

        const isDeclaredInsideSetup = setupRanges.some(
          ([start, end]) =>
            start <= def.node.range[0] && def.node.range[1] <= end
        )
        if (!isDeclaredInsideSetup) {
          return
        }

        if (
          computedFunction.range[0] <= def.node.range[0] &&
          def.node.range[1] <= computedFunction.range[1]
        ) {
          // mutating local variables are accepted
          return
        }

        const invalid = utils.findMutating(node)
        if (invalid) {
          context.report({
            node: invalid.node,
            messageId: 'unexpectedSideEffectInFunction'
          })
        }
      }
    }
    const scriptSetupNode = utils.getScriptSetupElement(context)
    if (scriptSetupNode) {
      setupRanges.push(scriptSetupNode.range)
    }
    return utils.compositingVisitors(
      {
        /** @param {Program} program */
        Program(program) {
          const tracker = new ReferenceTracker(utils.getScope(context, program))

          for (const { node } of utils.iterateReferencesTraceMap(tracker, {
            computed: {
              [ReferenceTracker.CALL]: true
            }
          })) {
            if (node.type !== 'CallExpression') {
              continue
            }

            const getterBody = utils.getGetterBodyFromComputedFunction(node)
            if (getterBody) {
              computedCallNodes.push(getterBody)
            }
          }
        }
      },
      scriptSetupNode
        ? utils.defineScriptSetupVisitor(context, nodeVisitor)
        : utils.defineVueVisitor(context, {
            onVueObjectEnter(node) {
              computedPropertiesMap.set(node, utils.getComputedProperties(node))
            },
            onSetupFunctionEnter(node) {
              setupRanges.push(node.body.range)
            },
            ...nodeVisitor
          })
    )
  }
}


code:
/**
 * @author Yosuke Ota
 * issue https://github.com/vuejs/eslint-plugin-vue/issues/460
 */
'use strict'

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

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description: 'disallow spaces around equal signs in attribute',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/no-spaces-around-equal-signs-in-attribute.html'
    },
    fixable: 'whitespace',
    schema: [],
    messages: {
      unexpectedSpaces: 'Unexpected spaces found around equal signs.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()
    return utils.defineTemplateBodyVisitor(context, {
      VAttribute(node) {
        if (!node.value) {
          return
        }
        /** @type {Range} */
        const range = [node.key.range[1], node.value.range[0]]
        const eqText = sourceCode.text.slice(range[0], range[1])
        const expect = eqText.trim()

        if (eqText !== expect) {
          context.report({
            node: node.key,
            loc: {
              start: node.key.loc.end,
              end: node.value.loc.start
            },
            messageId: 'unexpectedSpaces',
            fix: (fixer) => fixer.replaceTextRange(range, expect)
          })
        }
      }
    })
  }
}


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

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

// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapCoreRule('no-sparse-arrays')


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

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

/**
 * Checks whether if the given property node is a static value.
 * @param {Property} prop property node to check
 * @returns {boolean} `true` if the given property node is a static value.
 */
function isStaticValue(prop) {
  return (
    !prop.computed &&
    prop.value.type === 'Literal' &&
    (prop.key.type === 'Identifier' || prop.key.type === 'Literal')
  )
}

/**
 * Gets the static properties of a given expression node.
 * - If `SpreadElement` or computed property exists, it gets only the static properties before it.
 *   `:style="{ color: 'red', display: 'flex', ...spread, width: '16px' }"`
 *              ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^
 * - If non-static object exists, it gets only the static properties up to that object.
 *   `:style="[ { color: 'red' }, { display: 'flex', color, width: '16px' }, { height: '16px' } ]"`
 *                ^^^^^^^^^^^^      ^^^^^^^^^^^^^^^         ^^^^^^^^^^^^^
 * - If all properties are static properties, it returns one root node.
 *   `:style="[ { color: 'red' }, { display: 'flex', width: '16px' } ]"`
 *    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 * @param {VDirective} node `:style` node to check
 * @returns {Property[] | [VDirective]} the static properties.
 */
function getReportNodes(node) {
  const { value } = node
  if (!value) {
    return []
  }
  const { expression } = value
  if (!expression) {
    return []
  }

  let elements
  if (expression.type === 'ObjectExpression') {
    elements = [expression]
  } else if (expression.type === 'ArrayExpression') {
    elements = expression.elements
  } else {
    return []
  }
  const staticProperties = []
  for (const element of elements) {
    if (!element) {
      continue
    }
    if (element.type !== 'ObjectExpression') {
      return staticProperties
    }

    let isAllStatic = true
    for (const prop of element.properties) {
      if (prop.type === 'SpreadElement' || prop.computed) {
        // If `SpreadElement` or computed property exists, it gets only the static properties before it.
        return staticProperties
      }
      if (isStaticValue(prop)) {
        staticProperties.push(prop)
      } else {
        isAllStatic = false
      }
    }
    if (!isAllStatic) {
      // If non-static object exists, it gets only the static properties up to that object.
      return staticProperties
    }
  }
  // If all properties are static properties, it returns one root node.
  return [node]
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow static inline `style` attributes',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-static-inline-styles.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          allowBinding: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      forbiddenStaticInlineStyle: 'Static inline `style` are forbidden.',
      forbiddenStyleAttr: '`style` attributes are forbidden.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /**
     * Reports if the value is static.
     * @param {VDirective} node `:style` node to check
     */
    function verifyVBindStyle(node) {
      for (const n of getReportNodes(node)) {
        context.report({
          node: n,
          messageId: 'forbiddenStaticInlineStyle'
        })
      }
    }

    /** @type {TemplateListener} */
    const visitor = {
      /** @param {VAttribute} node */
      "VAttribute[directive=false][key.name='style']"(node) {
        context.report({
          node,
          messageId: 'forbiddenStyleAttr'
        })
      }
    }
    if (!context.options[0] || !context.options[0].allowBinding) {
      visitor[
        "VAttribute[directive=true][key.name.name='bind'][key.argument.name='style']"
      ] = verifyVBindStyle
    }

    return utils.defineTemplateBodyVisitor(context, visitor)
  }
}


code:
/**
 * @author Toru Nagashima
 * @copyright 2016 Toru Nagashima. All rights reserved.
 * See LICENSE file in root directory for full license.
 */
'use strict'

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'disallow `key` attribute on `<template>`',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/no-template-key.html'
    },
    fixable: null,
    schema: [],
    messages: {
      disallow:
        "'<template>' cannot be keyed. Place the key on real elements instead."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VElement} node */
      "VElement[name='template']"(node) {
        const keyNode =
          utils.getAttribute(node, 'key') ||
          utils.getDirective(node, 'bind', 'key')
        if (keyNode) {
          if (utils.hasDirective(node, 'for')) {
            // It's valid for Vue.js 3.x.
            // <template v-for="item in list" :key="item.id"> ... </template>
            // see https://github.com/vuejs/vue-next/issues/1734
            return
          }
          context.report({
            node: keyNode,
            loc: keyNode.loc,
            messageId: 'disallow'
          })
        }
      }
    })
  }
}


code:
/**
 * @fileoverview Disallow variable declarations from shadowing variables declared in the outer scope.
 * @author Armano
 */
'use strict'

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

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

/** @type {GroupName[]} */
const GROUP_NAMES = [
  'props',
  'computed',
  'data',
  'asyncData',
  'methods',
  'setup'
]

/**
 * @param {RuleContext} context
 * @param {string} variableName
 */
function isAllowedVarName(context, variableName) {
  if (context.options[0] && context.options[0].allow) {
    return context.options[0].allow.includes(variableName)
  }
  return false
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'disallow variable declarations from shadowing variables declared in the outer scope',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/no-template-shadow.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          allow: {
            type: 'array',
            items: {
              type: 'string'
            },
            uniqueItems: true
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      alreadyDeclaredInUpperScope:
        "Variable '{{name}}' is already declared in the upper scope."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {Set<string>} */
    const jsVars = new Set()

    /**
     * @typedef {object} ScopeStack
     * @property {ScopeStack | null} parent
     * @property {Identifier[]} nodes
     */
    /** @type {ScopeStack | null} */
    let scopeStack = null

    return 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) {
                  jsVars.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) {
                  jsVars.add(variable.name)
                }
              }
            }
          }
        : {},
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(_node, props) {
          for (const prop of props) {
            if (prop.propName) {
              jsVars.add(prop.propName)
            }
          }
        }
      }),
      utils.executeOnVue(context, (obj) => {
        const properties = utils.iterateProperties(obj, new Set(GROUP_NAMES))
        for (const node of properties) {
          jsVars.add(node.name)
        }
      }),
      utils.defineTemplateBodyVisitor(context, {
        /** @param {VElement} node */
        VElement(node) {
          scopeStack = {
            parent: scopeStack,
            nodes: scopeStack ? [...scopeStack.nodes] : []
          }
          for (const variable of node.variables) {
            const varNode = variable.id
            const name = varNode.name

            if (isAllowedVarName(context, name)) {
              continue
            }

            if (
              scopeStack.nodes.some((node) => node.name === name) ||
              jsVars.has(name)
            ) {
              context.report({
                node: varNode,
                loc: varNode.loc,
                messageId: 'alreadyDeclaredInUpperScope',
                data: {
                  name
                }
              })
            } else {
              scopeStack.nodes.push(varNode)
            }
          }
        },
        'VElement:exit'() {
          scopeStack = scopeStack && scopeStack.parent
        }
      })
    )
  }
}


code:
/**
 * @fileoverview disallow target="_blank" attribute without rel="noopener noreferrer"
 * @author Sosukesuzuki
 */
'use strict'

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

/** @param {VAttribute} node */
function isTargetBlank(node) {
  return (
    node.key &&
    node.key.name === 'target' &&
    node.value &&
    node.value.value === '_blank'
  )
}
/**
 * @param {VStartTag} node
 * @param {boolean} allowReferrer
 */
function hasSecureRel(node, allowReferrer) {
  return node.attributes.some((attr) => {
    if (attr.key && attr.key.name === 'rel') {
      const tags =
        attr.value &&
        attr.value.type === 'VLiteral' &&
        attr.value.value.toLowerCase().split(' ')
      return (
        tags &&
        tags.includes('noopener') &&
        (allowReferrer || tags.includes('noreferrer'))
      )
    } else {
      return false
    }
  })
}

/**
 * @param {VStartTag} node
 */
function hasExternalLink(node) {
  return node.attributes.some(
    (attr) =>
      attr.key &&
      attr.key.name === 'href' &&
      attr.value &&
      attr.value.type === 'VLiteral' &&
      /^(?:\w+:|\/\/)/.test(attr.value.value)
  )
}

/**
 * @param {VStartTag} node
 */
function hasDynamicLink(node) {
  return node.attributes.some(
    (attr) =>
      attr.key &&
      attr.key.type === 'VDirectiveKey' &&
      attr.key.name &&
      attr.key.name.name === 'bind' &&
      attr.key.argument &&
      attr.key.argument.type === 'VIdentifier' &&
      attr.key.argument.name === 'href'
  )
}

/**
 * @param {VAttribute} node
 * @returns {Rule.SuggestionReportDescriptor}
 */
function getSuggestion(node) {
  const relAttributeNode = node.parent.attributes.find(
    (attribute) => attribute.key.name === 'rel'
  )

  if (relAttributeNode) {
    return {
      desc: 'Change `rel` attribute value to `noopener noreferrer`.',
      fix(fixer) {
        return fixer.replaceText(relAttributeNode, 'rel="noopener noreferrer"')
      }
    }
  }

  return {
    desc: 'Add `rel="noopener noreferrer"`.',
    fix(fixer) {
      return fixer.insertTextAfter(node, ' rel="noopener noreferrer"')
    }
  }
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description:
        'disallow target="_blank" attribute without rel="noopener noreferrer"',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-template-target-blank.html'
    },
    hasSuggestions: true,
    schema: [
      {
        type: 'object',
        properties: {
          allowReferrer: {
            type: 'boolean'
          },
          enforceDynamicLinks: {
            enum: ['always', 'never']
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      missingRel:
        'Using target="_blank" without rel="noopener noreferrer" is a security risk.'
    }
  },

  /**
   * Creates AST event handlers for no-template-target-blank
   *
   * @param {RuleContext} context - The rule context.
   * @returns {Object} AST event handlers.
   */
  create(context) {
    const configuration = context.options[0] || {}
    const allowReferrer = configuration.allowReferrer || false
    const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always'

    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VAttribute} node */
      'VAttribute[directive=false]'(node) {
        if (!isTargetBlank(node) || hasSecureRel(node.parent, allowReferrer)) {
          return
        }

        const hasDangerHref =
          hasExternalLink(node.parent) ||
          (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent))

        if (hasDangerHref) {
          context.report({
            node,
            messageId: 'missingRel',
            suggest: [getSuggestion(node)]
          })
        }
      }
    })
  }
}


code:
/**
 * @author Toru Nagashima
 * @copyright 2017 Toru Nagashima. All rights reserved.
 * See LICENSE file in root directory for full license.
 */
'use strict'

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'disallow mustaches in `<textarea>`',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/no-textarea-mustache.html'
    },
    fixable: null,
    schema: [],
    messages: {
      unexpected: "Unexpected mustache. Use 'v-model' instead."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VExpressionContainer} node */
      "VElement[rawName='textarea'] VExpressionContainer"(node) {
        if (node.parent.type !== 'VElement') {
          return
        }

        context.report({
          node,
          loc: node.loc,
          messageId: 'unexpected'
        })
      }
    })
  }
}


code:
/**
 * @fileoverview Don't use this in a beforeRouteEnter method
 * @author Przemyslaw Jan Beigert
 */
'use strict'

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'disallow `this` usage in a `beforeRouteEnter` method',
      categories: null,
      url: 'https://eslint.vuejs.org/rules/no-this-in-before-route-enter.html'
    },
    fixable: null,
    schema: [],
    messages: {
      disallow:
        "'beforeRouteEnter' does NOT have access to `this` component instance. https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /**
     * @typedef {object} ScopeStack
     * @property {ScopeStack | null} upper
     * @property {FunctionExpression | FunctionDeclaration} node
     * @property {boolean} beforeRouteEnter
     */
    /** @type {Set<FunctionExpression>} */
    const beforeRouteEnterFunctions = new Set()
    /** @type {ScopeStack | null} */
    let scopeStack = null

    /**
     * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
     */
    function onFunctionEnter(node) {
      if (node.type === 'ArrowFunctionExpression') {
        return
      }
      scopeStack = {
        upper: scopeStack,
        node,
        beforeRouteEnter: beforeRouteEnterFunctions.has(
          /** @type {never} */ (node)
        )
      }
    }

    /**
     * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
     */
    function onFunctionExit(node) {
      if (scopeStack && scopeStack.node === node) {
        scopeStack = scopeStack.upper
      }
    }
    return utils.defineVueVisitor(context, {
      onVueObjectEnter(node) {
        const beforeRouteEnter = utils.findProperty(node, 'beforeRouteEnter')
        if (
          beforeRouteEnter &&
          beforeRouteEnter.value.type === 'FunctionExpression'
        ) {
          beforeRouteEnterFunctions.add(beforeRouteEnter.value)
        }
      },
      ':function': onFunctionEnter,
      ':function:exit': onFunctionExit,
      ThisExpression(node) {
        if (scopeStack && scopeStack.beforeRouteEnter) {
          context.report({
            node,
            messageId: 'disallow'
          })
        }
      }
    })
  }
}


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

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

/**
 * `casing.camelCase()` converts the beginning to lowercase,
 * but does not convert the case of the beginning character when converting with Vue3.
 * @see https://github.com/vuejs/core/blob/ae4b0783d78670b6e942ae2a4e3ec6efbbffa158/packages/shared/src/index.ts#L105
 * @param {string} str
 */
function camelize(str) {
  return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
}

class DefinedInSetupComponents {
  constructor() {
    /**
     * Component names
     * @type {Set<string>}
     */
    this.names = new Set()
  }

  /**
   * @param {string[]} names
   */
  addName(...names) {
    for (const name of names) {
      this.names.add(name)
    }
  }

  /**
   * @see https://github.com/vuejs/core/blob/ae4b0783d78670b6e942ae2a4e3ec6efbbffa158/packages/compiler-core/src/transforms/transformElement.ts#L334
   * @param {string} rawName
   */
  isDefinedComponent(rawName) {
    if (this.names.has(rawName)) {
      return true
    }
    const camelName = camelize(rawName)
    if (this.names.has(camelName)) {
      return true
    }
    const pascalName = casing.capitalize(camelName)
    if (this.names.has(pascalName)) {
      return true
    }
    // Check namespace
    // https://github.com/vuejs/core/blob/ae4b0783d78670b6e942ae2a4e3ec6efbbffa158/packages/compiler-core/src/transforms/transformElement.ts#L305
    const dotIndex = rawName.indexOf('.')
    if (dotIndex > 0 && this.isDefinedComponent(rawName.slice(0, dotIndex))) {
      return true
    }
    return false
  }
}

class DefinedInOptionComponents {
  constructor() {
    /**
     * Component names
     * @type {Set<string>}
     */
    this.names = new Set()
    /**
     * Component names, transformed to kebab-case
     * @type {Set<string>}
     */
    this.kebabCaseNames = new Set()
  }

  /**
   * @param {string[]} names
   */
  addName(...names) {
    for (const name of names) {
      this.names.add(name)
      this.kebabCaseNames.add(casing.kebabCase(name))
    }
  }

  /**
   * @param {string} rawName
   */
  isDefinedComponent(rawName) {
    if (this.names.has(rawName)) {
      return true
    }
    const kebabCaseName = casing.kebabCase(rawName)
    if (
      this.kebabCaseNames.has(kebabCaseName) &&
      !casing.isPascalCase(rawName)
    ) {
      // Component registered as `foo-bar` cannot be used as `FooBar`
      return true
    }
    return false
  }
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow use of undefined components in `<template>`',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-undef-components.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          ignorePatterns: {
            type: 'array'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      undef: "The '<{{name}}>' component has been used, but not defined.",
      typeOnly:
        "The '<{{name}}>' component has been used, but '{{name}}' only refers to a type."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || {}
    /** @type {string[]} */
    const ignorePatterns = options.ignorePatterns || []

    /**
     * Check whether the given element name is a verify target or not.
     *
     * @param {string} rawName The element name.
     * @returns {boolean}
     */
    function isVerifyTargetComponent(rawName) {
      const kebabCaseName = casing.kebabCase(rawName)

      if (
        utils.isHtmlWellKnownElementName(rawName) ||
        utils.isSvgWellKnownElementName(rawName) ||
        utils.isMathWellKnownElementName(rawName) ||
        utils.isBuiltInComponentName(kebabCaseName)
      ) {
        return false
      }
      const pascalCaseName = casing.pascalCase(rawName)
      // Check ignored patterns
      if (
        ignorePatterns.some((pattern) => {
          const regExp = new RegExp(pattern)
          return (
            regExp.test(rawName) ||
            regExp.test(kebabCaseName) ||
            regExp.test(pascalCaseName)
          )
        })
      ) {
        return false
      }
      return true
    }

    /** @type { (rawName:string, reportNode: ASTNode) => void } */
    let verifyName
    /** @type {RuleListener} */
    let scriptVisitor = {}
    /** @type {TemplateListener} */
    const templateBodyVisitor = {
      VElement(node) {
        if (
          !utils.isHtmlElementNode(node) &&
          !utils.isSvgElementNode(node) &&
          !utils.isMathElementNode(node)
        ) {
          return
        }
        verifyName(node.rawName, node.startTag)
      },
      /** @param {VAttribute} node */
      "VAttribute[directive=false][key.name='is']"(node) {
        if (
          !node.value // `<component is />`
        )
          return
        const value = node.value.value.startsWith('vue:') // Usage on native elements 3.1+
          ? node.value.value.slice(4)
          : node.value.value
        verifyName(value, node)
      }
    }

    if (utils.isScriptSetup(context)) {
      // For <script setup>
      const definedInSetupComponents = new DefinedInSetupComponents()
      const definedInOptionComponents = new DefinedInOptionComponents()

      /** @type {Set<string>} */
      const scriptTypeOnlyNames = new Set()
      const globalScope = context.getSourceCode().scopeManager.globalScope
      if (globalScope) {
        for (const variable of globalScope.variables) {
          definedInSetupComponents.addName(variable.name)
        }
        const moduleScope = globalScope.childScopes.find(
          (scope) => scope.type === 'module'
        )
        for (const variable of (moduleScope && moduleScope.variables) || []) {
          if (
            // Check for type definitions. e.g. type Foo = {}
            (variable.isTypeVariable && !variable.isValueVariable) ||
            // type-only import seems to have isValueVariable set to true. So we need to check the actual Node.
            (variable.defs.length > 0 &&
              variable.defs.every((def) => {
                if (def.type !== 'ImportBinding') {
                  return false
                }
                if (def.parent.importKind === 'type') {
                  // check for `import type Foo from './xxx'`
                  return true
                }
                if (
                  def.node.type === 'ImportSpecifier' &&
                  def.node.importKind === 'type'
                ) {
                  // check for `import { type Foo } from './xxx'`
                  return true
                }
                return false
              }))
          ) {
            scriptTypeOnlyNames.add(variable.name)
          } else {
            definedInSetupComponents.addName(variable.name)
          }
        }
      }

      // For circular references
      const fileName = context.getFilename()
      const selfComponentName = path.basename(fileName, path.extname(fileName))
      definedInSetupComponents.addName(selfComponentName)
      scriptVisitor = utils.defineVueVisitor(context, {
        onVueObjectEnter(node, { type }) {
          if (type !== 'export') return
          const nameProperty = utils.findProperty(node, 'name')

          if (nameProperty && utils.isStringLiteral(nameProperty.value)) {
            const name = utils.getStringLiteralValue(nameProperty.value)
            if (name) {
              definedInOptionComponents.addName(name)
            }
          }
        }
      })

      verifyName = (rawName, reportNode) => {
        if (!isVerifyTargetComponent(rawName)) {
          return
        }
        if (definedInSetupComponents.isDefinedComponent(rawName)) {
          return
        }
        if (definedInOptionComponents.isDefinedComponent(rawName)) {
          return
        }

        context.report({
          node: reportNode,
          messageId: scriptTypeOnlyNames.has(rawName) ? 'typeOnly' : 'undef',
          data: {
            name: rawName
          }
        })
      }
    } else {
      // For Options API
      const definedInOptionComponents = new DefinedInOptionComponents()

      scriptVisitor = utils.executeOnVue(context, (obj) => {
        definedInOptionComponents.addName(
          ...utils.getRegisteredComponents(obj).map(({ name }) => name)
        )

        const nameProperty = utils.findProperty(obj, 'name')

        if (nameProperty && utils.isStringLiteral(nameProperty.value)) {
          const name = utils.getStringLiteralValue(nameProperty.value)
          if (name) {
            definedInOptionComponents.addName(name)
          }
        }
      })

      verifyName = (rawName, reportNode) => {
        if (!isVerifyTargetComponent(rawName)) {
          return
        }
        if (definedInOptionComponents.isDefinedComponent(rawName)) {
          return
        }

        context.report({
          node: reportNode,
          messageId: 'undef',
          data: {
            name: rawName
          }
        })
      }

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

        if (node.value.expression.type === 'Literal') {
          verifyName(`${node.value.expression.value}`, node)
        }
      }
    }

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