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

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

const {
  findVariable,
  isOpeningBraceToken,
  isClosingBraceToken,
  isOpeningBracketToken
} = require('@eslint-community/eslint-utils')
const utils = require('../utils')
const { capitalize } = require('../utils/casing')

const FIX_EMITS_AFTER_OPTIONS = new Set([
  'setup',
  'data',
  'computed',
  'watch',
  'methods',
  'template',
  'render',
  'renderError',

  // lifecycle hooks
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'activated',
  'deactivated',
  'beforeUnmount',
  'unmounted',
  'beforeDestroy',
  'destroyed',
  'renderTracked',
  'renderTriggered',
  'errorCaptured'
])

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

/**
 * Check if the given name matches defineEmitsNode variable name
 * @param {string} name
 * @param {CallExpression | undefined} defineEmitsNode
 * @returns {boolean}
 */
function isEmitVariableName(name, defineEmitsNode) {
  const node = defineEmitsNode?.parent

  if (node?.type === 'VariableDeclarator' && node.id.type === 'Identifier') {
    return name === node.id.name
  }

  return false
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'require `emits` option with name triggered by `$emit()`',
      categories: ['vue3-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/require-explicit-emits.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: [
      {
        type: 'object',
        properties: {
          allowProps: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      missing:
        'The "{{name}}" event has been triggered but not declared on {{emitsKind}}.',
      addOneOption: 'Add the "{{name}}" to {{emitsKind}}.',
      addArrayEmitsOption:
        'Add the {{emitsKind}} with array syntax and define "{{name}}" event.',
      addObjectEmitsOption:
        'Add the {{emitsKind}} with object syntax and define "{{name}}" event.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || {}
    const allowProps = !!options.allowProps
    /** @type {Map<ObjectExpression | Program, { contextReferenceIds: Set<Identifier>, emitReferenceIds: Set<Identifier> }>} */
    const setupContexts = new Map()
    /** @type {Map<ObjectExpression | Program, ComponentEmit[]>} */
    const vueEmitsDeclarations = new Map()
    /** @type {Map<ObjectExpression | Program, ComponentProp[]>} */
    const vuePropsDeclarations = new Map()

    /**
     * @typedef {object} VueTemplateDefineData
     * @property {'export' | 'mark' | 'definition' | 'setup'} type
     * @property {ObjectExpression | Program} define
     * @property {ComponentEmit[]} emits
     * @property {ComponentProp[]} props
     * @property {CallExpression} [defineEmits]
     */
    /** @type {VueTemplateDefineData | null} */
    let vueTemplateDefineData = null

    /**
     * @param {ComponentEmit[]} emits
     * @param {ComponentProp[]} props
     * @param {NameWithLoc} nameWithLoc
     * @param {ObjectExpression | Program} vueDefineNode
     */
    function verifyEmit(emits, props, nameWithLoc, vueDefineNode) {
      const name = nameWithLoc.name
      if (emits.some((e) => e.emitName === name || e.emitName == null)) {
        return
      }
      if (allowProps) {
        const key = `on${capitalize(name)}`
        if (props.some((e) => e.propName === key || e.propName == null)) {
          return
        }
      }
      context.report({
        loc: nameWithLoc.loc,
        messageId: 'missing',
        data: {
          name,
          emitsKind:
            vueDefineNode.type === 'ObjectExpression'
              ? '`emits` option'
              : '`defineEmits`'
        },
        suggest: buildSuggest(vueDefineNode, emits, nameWithLoc, context)
      })
    }

    const programNode = context.getSourceCode().ast
    if (utils.isScriptSetup(context)) {
      // init
      vueTemplateDefineData = {
        type: 'setup',
        define: programNode,
        emits: [],
        props: []
      }
    }

    const callVisitor = {
      /**
       * @param {CallExpression} node
       * @param {VueObjectData} [info]
       */
      CallExpression(node, info) {
        const callee = utils.skipChainExpression(node.callee)
        const nameWithLoc = getNameParamNode(node)
        if (!nameWithLoc) {
          // cannot check
          return
        }
        const vueDefineNode = info ? info.node : programNode
        const emitsDeclarations = vueEmitsDeclarations.get(vueDefineNode)
        if (!emitsDeclarations) {
          return
        }

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

        // verify setup context
        const setupContext = setupContexts.get(vueDefineNode)
        if (setupContext) {
          const { contextReferenceIds, emitReferenceIds } = setupContext
          if (callee.type === 'Identifier' && emitReferenceIds.has(callee)) {
            // verify setup(props,{emit}) {emit()}
            verifyEmit(
              emitsDeclarations,
              vuePropsDeclarations.get(vueDefineNode) || [],
              nameWithLoc,
              vueDefineNode
            )
          } else if (emit && emit.name === 'emit') {
            const memObject = utils.skipChainExpression(emit.member.object)
            if (
              memObject.type === 'Identifier' &&
              contextReferenceIds.has(memObject)
            ) {
              // verify setup(props,context) {context.emit()}
              verifyEmit(
                emitsDeclarations,
                vuePropsDeclarations.get(vueDefineNode) || [],
                nameWithLoc,
                vueDefineNode
              )
            }
          }
        }

        // verify $emit
        if (emit && emit.name === '$emit') {
          const memObject = utils.skipChainExpression(emit.member.object)
          if (utils.isThis(memObject, context)) {
            // verify this.$emit()
            verifyEmit(
              emitsDeclarations,
              vuePropsDeclarations.get(vueDefineNode) || [],
              nameWithLoc,
              vueDefineNode
            )
          }
        }
      }
    }

    return utils.defineTemplateBodyVisitor(
      context,
      {
        /** @param { CallExpression } node */
        CallExpression(node) {
          const callee = utils.skipChainExpression(node.callee)
          const nameWithLoc = getNameParamNode(node)
          if (!nameWithLoc) {
            // cannot check
            return
          }
          if (!vueTemplateDefineData) {
            return
          }

          // e.g. $emit() / emit() in template
          if (
            callee.type === 'Identifier' &&
            (callee.name === '$emit' ||
              isEmitVariableName(
                callee.name,
                vueTemplateDefineData.defineEmits
              ))
          ) {
            verifyEmit(
              vueTemplateDefineData.emits,
              vueTemplateDefineData.props,
              nameWithLoc,
              vueTemplateDefineData.define
            )
          }
        }
      },
      utils.compositingVisitors(
        utils.defineScriptSetupVisitor(context, {
          onDefineEmitsEnter(node, emits) {
            vueEmitsDeclarations.set(programNode, emits)

            if (
              vueTemplateDefineData &&
              vueTemplateDefineData.type === 'setup'
            ) {
              vueTemplateDefineData.emits = emits
              vueTemplateDefineData.defineEmits = node
            }

            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) {
              if (!reference.isRead()) {
                continue
              }

              emitReferenceIds.add(reference.identifier)
            }
            setupContexts.set(programNode, {
              contextReferenceIds: new Set(),
              emitReferenceIds
            })
          },
          onDefinePropsEnter(_node, props) {
            if (allowProps) {
              vuePropsDeclarations.set(programNode, props)

              if (
                vueTemplateDefineData &&
                vueTemplateDefineData.type === 'setup'
              ) {
                vueTemplateDefineData.props = props
              }
            }
          },
          ...callVisitor
        }),
        utils.defineVueVisitor(context, {
          onVueObjectEnter(node) {
            vueEmitsDeclarations.set(
              node,
              utils.getComponentEmitsFromOptions(node)
            )
            if (allowProps) {
              vuePropsDeclarations.set(
                node,
                utils.getComponentPropsFromOptions(node)
              )
            }
          },
          onSetupFunctionEnter(node, { node: vueNode }) {
            const contextParam = node.params[1]
            if (!contextParam) {
              // no arguments
              return
            }
            if (contextParam.type === 'RestElement') {
              // cannot check
              return
            }
            if (contextParam.type === 'ArrayPattern') {
              // 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) {
                if (!reference.isRead()) {
                  continue
                }

                emitReferenceIds.add(reference.identifier)
              }
            } else if (contextParam.type === 'Identifier') {
              // `setup(props, context)`
              const variable = findVariable(
                utils.getScope(context, contextParam),
                contextParam
              )
              if (!variable) {
                return
              }
              for (const reference of variable.references) {
                if (!reference.isRead()) {
                  continue
                }

                contextReferenceIds.add(reference.identifier)
              }
            }
            setupContexts.set(vueNode, {
              contextReferenceIds,
              emitReferenceIds
            })
          },
          ...callVisitor,
          onVueObjectExit(node, { type }) {
            const emits = vueEmitsDeclarations.get(node)
            if (
              (!vueTemplateDefineData ||
                (vueTemplateDefineData.type !== 'export' &&
                  vueTemplateDefineData.type !== 'setup')) &&
              emits &&
              (type === 'mark' || type === 'export' || type === 'definition')
            ) {
              vueTemplateDefineData = {
                type,
                define: node,
                emits,
                props: vuePropsDeclarations.get(node) || []
              }
            }
            setupContexts.delete(node)
            vueEmitsDeclarations.delete(node)
            vuePropsDeclarations.delete(node)
          }
        })
      )
    )
  }
}

/**
 * @param {ObjectExpression|Program} define
 * @param {ComponentEmit[]} emits
 * @param {NameWithLoc} nameWithLoc
 * @param {RuleContext} context
 * @returns {Rule.SuggestionReportDescriptor[]}
 */
function buildSuggest(define, emits, nameWithLoc, context) {
  const emitsKind =
    define.type === 'ObjectExpression' ? '`emits` option' : '`defineEmits`'
  const certainEmits = emits.filter(
    /** @returns {e is ComponentEmit & {type:'array'|'object'}} */
    (e) => e.type === 'array' || e.type === 'object'
  )
  if (certainEmits.length > 0) {
    const last = certainEmits[certainEmits.length - 1]
    return [
      {
        messageId: 'addOneOption',
        data: {
          name: nameWithLoc.name,
          emitsKind
        },
        fix(fixer) {
          if (last.type === 'array') {
            // Array
            return fixer.insertTextAfter(last.node, `, '${nameWithLoc.name}'`)
          } else if (last.type === 'object') {
            // Object
            return fixer.insertTextAfter(
              last.node,
              `, '${nameWithLoc.name}': null`
            )
          } else {
            // type
            // The argument is unknown and cannot be suggested.
            return null
          }
        }
      }
    ]
  }

  if (define.type !== 'ObjectExpression') {
    // We don't know where to put defineEmits.
    return []
  }

  const object = define

  const propertyNodes = object.properties.filter(utils.isProperty)

  const emitsOption = propertyNodes.find(
    (p) => utils.getStaticPropertyName(p) === 'emits'
  )
  if (emitsOption) {
    const sourceCode = context.getSourceCode()
    const emitsOptionValue = emitsOption.value
    if (emitsOptionValue.type === 'ArrayExpression') {
      const leftBracket = /** @type {Token} */ (
        sourceCode.getFirstToken(emitsOptionValue, isOpeningBracketToken)
      )
      return [
        {
          messageId: 'addOneOption',
          data: { name: `${nameWithLoc.name}`, emitsKind },
          fix(fixer) {
            return fixer.insertTextAfter(
              leftBracket,
              `'${nameWithLoc.name}'${
                emitsOptionValue.elements.length > 0 ? ',' : ''
              }`
            )
          }
        }
      ]
    } else if (emitsOptionValue.type === 'ObjectExpression') {
      const leftBrace = /** @type {Token} */ (
        sourceCode.getFirstToken(emitsOptionValue, isOpeningBraceToken)
      )
      return [
        {
          messageId: 'addOneOption',
          data: { name: `${nameWithLoc.name}`, emitsKind },
          fix(fixer) {
            return fixer.insertTextAfter(
              leftBrace,
              `'${nameWithLoc.name}': null${
                emitsOptionValue.properties.length > 0 ? ',' : ''
              }`
            )
          }
        }
      ]
    }
    return []
  }

  const sourceCode = context.getSourceCode()
  const afterOptionNode = propertyNodes.find((p) =>
    FIX_EMITS_AFTER_OPTIONS.has(utils.getStaticPropertyName(p) || '')
  )
  return [
    {
      messageId: 'addArrayEmitsOption',
      data: { name: `${nameWithLoc.name}`, emitsKind },
      fix(fixer) {
        if (afterOptionNode) {
          return fixer.insertTextAfter(
            sourceCode.getTokenBefore(afterOptionNode),
            `\nemits: ['${nameWithLoc.name}'],`
          )
        } else if (object.properties.length > 0) {
          const before =
            propertyNodes[propertyNodes.length - 1] ||
            object.properties[object.properties.length - 1]
          return fixer.insertTextAfter(
            before,
            `,\nemits: ['${nameWithLoc.name}']`
          )
        } else {
          const objectLeftBrace = /** @type {Token} */ (
            sourceCode.getFirstToken(object, isOpeningBraceToken)
          )
          const objectRightBrace = /** @type {Token} */ (
            sourceCode.getLastToken(object, isClosingBraceToken)
          )
          return fixer.insertTextAfter(
            objectLeftBrace,
            `\nemits: ['${nameWithLoc.name}']${
              objectLeftBrace.loc.end.line < objectRightBrace.loc.start.line
                ? ''
                : '\n'
            }`
          )
        }
      }
    },
    {
      messageId: 'addObjectEmitsOption',
      data: { name: `${nameWithLoc.name}`, emitsKind },
      fix(fixer) {
        if (afterOptionNode) {
          return fixer.insertTextAfter(
            sourceCode.getTokenBefore(afterOptionNode),
            `\nemits: {'${nameWithLoc.name}': null},`
          )
        } else if (object.properties.length > 0) {
          const before =
            propertyNodes[propertyNodes.length - 1] ||
            object.properties[object.properties.length - 1]
          return fixer.insertTextAfter(
            before,
            `,\nemits: {'${nameWithLoc.name}': null}`
          )
        } else {
          const objectLeftBrace = /** @type {Token} */ (
            sourceCode.getFirstToken(object, isOpeningBraceToken)
          )
          const objectRightBrace = /** @type {Token} */ (
            sourceCode.getLastToken(object, isClosingBraceToken)
          )
          return fixer.insertTextAfter(
            objectLeftBrace,
            `\nemits: {'${nameWithLoc.name}': null}${
              objectLeftBrace.loc.end.line < objectRightBrace.loc.start.line
                ? ''
                : '\n'
            }`
          )
        }
      }
    }
  ]
}


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

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

/**
 * @typedef {import('@typescript-eslint/types').TSESTree.TypeNode} TypeNode
 * @typedef {import('@typescript-eslint/types').TSESTree.TypeElement} TypeElement
 */

/**
 * @param {TypeElement} node
 * @return {string | null}
 */
function getSlotsName(node) {
  if (
    node.type !== 'TSMethodSignature' &&
    node.type !== 'TSPropertySignature'
  ) {
    return null
  }

  const key = node.key
  if (key.type === 'Literal') {
    return typeof key.value === 'string' ? key.value : null
  }

  if (key.type === 'Identifier') {
    return key.name
  }

  return null
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'require slots to be explicitly defined',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/require-explicit-slots.html'
    },
    fixable: null,
    schema: [],
    messages: {
      requireExplicitSlots: 'Slots must be explicitly defined.',
      alreadyDefinedSlot: 'Slot {{slotName}} is already defined.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()
    const documentFragment =
      sourceCode.parserServices.getDocumentFragment &&
      sourceCode.parserServices.getDocumentFragment()
    if (!documentFragment) {
      return {}
    }
    const scripts = documentFragment.children.filter(
      /** @returns {element is VElement} */
      (element) => utils.isVElement(element) && element.name === 'script'
    )
    if (scripts.every((script) => !utils.hasAttribute(script, 'lang', 'ts'))) {
      return {}
    }
    const slotsDefined = new Set()

    return utils.compositingVisitors(
      utils.defineScriptSetupVisitor(context, {
        onDefineSlotsEnter(node) {
          const typeArguments =
            'typeArguments' in node ? node.typeArguments : node.typeParameters
          const param = /** @type {TypeNode|undefined} */ (
            typeArguments?.params[0]
          )
          if (!param) return

          if (param.type === 'TSTypeLiteral') {
            for (const memberNode of param.members) {
              const slotName = getSlotsName(memberNode)
              if (!slotName) continue

              if (slotsDefined.has(slotName)) {
                context.report({
                  node: memberNode,
                  messageId: 'alreadyDefinedSlot',
                  data: {
                    slotName
                  }
                })
              } else {
                slotsDefined.add(slotName)
              }
            }
          }
        }
      }),
      utils.executeOnVue(context, (obj) => {
        const slotsProperty = utils.findProperty(obj, 'slots')
        if (!slotsProperty) return

        const slotsTypeHelper =
          slotsProperty.value.type === 'TSAsExpression' &&
          slotsProperty.value.typeAnnotation?.typeName.name === 'SlotsType' &&
          slotsProperty.value.typeAnnotation
        if (!slotsTypeHelper) return

        const typeArguments =
          'typeArguments' in slotsTypeHelper
            ? slotsTypeHelper.typeArguments
            : slotsTypeHelper.typeParameters
        const param = /** @type {TypeNode|undefined} */ (
          typeArguments?.params[0]
        )
        if (!param) return

        if (param.type === 'TSTypeLiteral') {
          for (const memberNode of param.members) {
            const slotName = getSlotsName(memberNode)
            if (!slotName) continue

            if (slotsDefined.has(slotName)) {
              context.report({
                node: memberNode,
                messageId: 'alreadyDefinedSlot',
                data: {
                  slotName
                }
              })
            } else {
              slotsDefined.add(slotName)
            }
          }
        }
      }),
      utils.defineTemplateBodyVisitor(context, {
        "VElement[name='slot']"(node) {
          let slotName = 'default'

          const slotNameAttr = utils.getAttribute(node, 'name')

          if (slotNameAttr?.value) {
            slotName = slotNameAttr.value.value
          }

          if (!slotsDefined.has(slotName)) {
            context.report({
              node,
              messageId: 'requireExplicitSlots'
            })
          }
        }
      })
    )
  }
}

license: mit


code:
/**
 * @fileoverview Require `expose` in Vue components
 * @author Yosuke Ota <https://github.com/ota-meshi>
 */
'use strict'

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

const FIX_EXPOSE_BEFORE_OPTIONS = new Set([
  'name',
  'components',
  'directives',
  'extends',
  'mixins',
  'provide',
  'inject',
  'inheritAttrs',
  'props',
  'emits'
])

/**
 * @param {Property | SpreadElement} node
 * @returns {node is ObjectExpressionProperty}
 */
function isExposeProperty(node) {
  return (
    node.type === 'Property' &&
    utils.getStaticPropertyName(node) === 'expose' &&
    !node.computed
  )
}

/**
 * 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: 'require declare public properties using `expose`',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/require-expose.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: [],
    messages: {
      requireExpose:
        'The public properties of the component must be explicitly declared using `expose`. If the component does not have public properties, declare it empty.',
      addExposeOptionForEmpty:
        'Add the `expose` option to give an empty array.',
      addExposeOptionForAll:
        'Add the `expose` option to declare all properties.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    if (utils.isScriptSetup(context)) {
      return {}
    }

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

    /** @type {Map<ObjectExpression, SetupContext>} */
    const setupContexts = new Map()
    /** @type {Set<ObjectExpression>} */
    const calledExpose = new Set()

    /**
     * @typedef {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} FunctionNode
     */
    /**
     * @typedef {object} ScopeStack
     * @property {ScopeStack | null} upper
     * @property {FunctionNode} functionNode
     * @property {boolean} returnFunction
     */
    /**
     * @type {ScopeStack | null}
     */
    let scopeStack = null
    /** @type {Map<FunctionNode, ObjectExpression>} */
    const setupFunctions = new Map()
    /** @type {Set<ObjectExpression>} */
    const setupRender = new Set()

    /**
     * @param {Expression} node
     * @returns {boolean}
     */
    function isFunction(node) {
      if (
        node.type === 'ArrowFunctionExpression' ||
        node.type === 'FunctionExpression'
      ) {
        return true
      }
      if (node.type === 'Identifier') {
        const variable = findVariable(utils.getScope(context, node), node)
        if (variable) {
          for (const def of variable.defs) {
            if (def.type === 'FunctionName') {
              return true
            }
            if (def.type === 'Variable' && def.node.init) {
              return isFunction(def.node.init)
            }
          }
        }
      }
      return false
    }
    return utils.defineVueVisitor(context, {
      onSetupFunctionEnter(node, { node: vueNode }) {
        setupFunctions.set(node, vueNode)
        const contextParam = node.params[1]
        if (!contextParam) {
          // no arguments
          return
        }
        if (contextParam.type === 'RestElement') {
          // cannot check
          return
        }
        if (contextParam.type === 'ArrayPattern') {
          // cannot check
          return
        }
        /** @type {Set<Identifier>} */
        const contextReferenceIds = new Set()
        /** @type {Set<Identifier>} */
        const exposeReferenceIds = new Set()
        if (contextParam.type === 'ObjectPattern') {
          const exposeProperty = utils.findAssignmentProperty(
            contextParam,
            'expose'
          )
          if (!exposeProperty) {
            return
          }
          const exposeParam = exposeProperty.value
          // `setup(props, {emit})`
          const variable =
            exposeParam.type === 'Identifier'
              ? findVariable(utils.getScope(context, exposeParam), exposeParam)
              : null
          if (!variable) {
            return
          }
          for (const reference of variable.references) {
            if (!reference.isRead()) {
              continue
            }
            exposeReferenceIds.add(reference.identifier)
          }
        } else if (contextParam.type === 'Identifier') {
          // `setup(props, context)`
          const variable = findVariable(
            utils.getScope(context, contextParam),
            contextParam
          )
          if (!variable) {
            return
          }
          for (const reference of variable.references) {
            if (!reference.isRead()) {
              continue
            }
            contextReferenceIds.add(reference.identifier)
          }
        }
        setupContexts.set(vueNode, {
          contextReferenceIds,
          exposeReferenceIds
        })
      },
      CallExpression(node, { node: vueNode }) {
        if (calledExpose.has(vueNode)) {
          // already called
          return
        }
        // find setup context
        const setupContext = setupContexts.get(vueNode)
        if (setupContext) {
          const { contextReferenceIds, exposeReferenceIds } = setupContext
          if (
            node.callee.type === 'Identifier' &&
            exposeReferenceIds.has(node.callee)
          ) {
            // setup(props,{expose}) {expose()}
            calledExpose.add(vueNode)
          } else {
            const expose = getCalleeMemberNode(node)
            if (
              expose &&
              expose.name === 'expose' &&
              expose.member.object.type === 'Identifier' &&
              contextReferenceIds.has(expose.member.object)
            ) {
              // setup(props,context) {context.emit()}
              calledExpose.add(vueNode)
            }
          }
        }
      },
      /** @param {FunctionNode} node */
      ':function'(node) {
        scopeStack = {
          upper: scopeStack,
          functionNode: node,
          returnFunction: false
        }

        if (
          node.type === 'ArrowFunctionExpression' &&
          node.expression &&
          isFunction(node.body)
        ) {
          scopeStack.returnFunction = true
        }
      },
      ReturnStatement(node) {
        if (!scopeStack) {
          return
        }
        if (
          !scopeStack.returnFunction &&
          node.argument &&
          isFunction(node.argument)
        ) {
          scopeStack.returnFunction = true
        }
      },
      ':function:exit'(node) {
        if (scopeStack && scopeStack.returnFunction) {
          const vueNode = setupFunctions.get(node)
          if (vueNode) {
            setupRender.add(vueNode)
          }
        }
        scopeStack = scopeStack && scopeStack.upper
      },
      onVueObjectExit(component, { type }) {
        if (calledExpose.has(component)) {
          // `expose` function is called
          return
        }
        if (setupRender.has(component)) {
          // `setup` function is render function
          return
        }
        if (type === 'definition') {
          const defType = getVueComponentDefinitionType(component)
          if (defType === 'mixin') {
            return
          }
        }

        if (component.properties.some(isExposeProperty)) {
          // has `expose`
          return
        }

        context.report({
          node: component,
          messageId: 'requireExpose',
          suggest: buildSuggest(component, context)
        })
      }
    })
  }
}

/**
 * @param {ObjectExpression} object
 * @param {RuleContext} context
 * @returns {Rule.SuggestionReportDescriptor[]}
 */
function buildSuggest(object, context) {
  const propertyNodes = object.properties.filter(utils.isProperty)

  const sourceCode = context.getSourceCode()
  const beforeOptionNode = propertyNodes.find((p) =>
    FIX_EXPOSE_BEFORE_OPTIONS.has(utils.getStaticPropertyName(p) || '')
  )
  const allProps = [
    ...new Set(
      utils.iterateProperties(
        object,
        new Set(['props', 'data', 'computed', 'setup', 'methods', 'watch'])
      )
    )
  ]
  return [
    {
      messageId: 'addExposeOptionForEmpty',
      fix: buildFix('expose: []')
    },
    ...(allProps.length > 0
      ? [
          {
            messageId: 'addExposeOptionForAll',
            fix: buildFix(
              `expose: [${allProps
                .map((p) => JSON.stringify(p.name))
                .join(', ')}]`
            )
          }
        ]
      : [])
  ]

  /**
   * @param {string} text
   */
  function buildFix(text) {
    /**
     * @param {RuleFixer} fixer
     */
    return (fixer) => {
      if (beforeOptionNode) {
        return fixer.insertTextAfter(beforeOptionNode, `,\n${text}`)
      } else if (object.properties.length > 0) {
        const after = propertyNodes[0] || object.properties[0]
        return fixer.insertTextAfter(
          sourceCode.getTokenBefore(after),
          `\n${text},`
        )
      } else {
        const objectLeftBrace = /** @type {Token} */ (
          sourceCode.getFirstToken(object, isOpeningBraceToken)
        )
        const objectRightBrace = /** @type {Token} */ (
          sourceCode.getLastToken(object, isClosingBraceToken)
        )
        return fixer.insertTextAfter(
          objectLeftBrace,
          `\n${text}${
            objectLeftBrace.loc.end.line < objectRightBrace.loc.start.line
              ? ''
              : '\n'
          }`
        )
      }
    }
  }
}


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

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

const DEFAULT_OPTIONS = {
  defineProps: 'props',
  defineEmits: 'emit',
  defineSlots: 'slots',
  useSlots: 'slots',
  useAttrs: 'attrs'
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'require a certain macro variable name',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/require-macro-variable-name.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: [
      {
        type: 'object',
        properties: {
          defineProps: {
            type: 'string',
            default: DEFAULT_OPTIONS.defineProps
          },
          defineEmits: {
            type: 'string',
            default: DEFAULT_OPTIONS.defineEmits
          },
          defineSlots: {
            type: 'string',
            default: DEFAULT_OPTIONS.defineSlots
          },
          useSlots: {
            type: 'string',
            default: DEFAULT_OPTIONS.useSlots
          },
          useAttrs: {
            type: 'string',
            default: DEFAULT_OPTIONS.useAttrs
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      requireName:
        'The variable name of "{{macroName}}" must be "{{variableName}}".',
      changeName: 'Change the variable name to "{{variableName}}".'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || DEFAULT_OPTIONS
    const relevantMacros = new Set([
      ...Object.keys(DEFAULT_OPTIONS),
      'withDefaults'
    ])

    return utils.defineScriptSetupVisitor(context, {
      VariableDeclarator(node) {
        if (
          node.init &&
          node.init.type === 'CallExpression' &&
          node.init.callee.type === 'Identifier' &&
          relevantMacros.has(node.init.callee.name)
        ) {
          const macroName =
            node.init.callee.name === 'withDefaults'
              ? 'defineProps'
              : node.init.callee.name

          if (
            node.id.type === 'Identifier' &&
            node.id.name !== options[macroName]
          ) {
            context.report({
              node: node.id,
              loc: node.id.loc,
              messageId: 'requireName',
              data: {
                macroName,
                variableName: options[macroName]
              },
              suggest: [
                {
                  messageId: 'changeName',
                  data: {
                    variableName: options[macroName]
                  },
                  fix(fixer) {
                    return fixer.replaceText(node.id, options[macroName])
                  }
                }
              ]
            })
          }
        }
      }
    })
  }
}


code:
/**
 * @fileoverview Require a name property in Vue components
 * @author LukeeeeBennett
 */
'use strict'

const path = require('path')
const utils = require('../utils')
const { getVueComponentDefinitionType } = require('../utils')
/**
 * Get the text of the empty indentation part of the line which the given token is on.
 * @param {SourceCode} sourceCode the source code object
 * @param {Token} token the token to get the indentation text of the line which the token is on
 * @returns {string} The text of indentation part.
 */
function getLineEmptyIndent(sourceCode, token) {
  const LT_CHAR = /[\n\r\u2028\u2029]/
  const EMPTY_CHAR = /\s/
  const text = sourceCode.text
  let i = token.range[0] - 1

  while (i >= 0 && !LT_CHAR.test(text[i])) {
    i -= 1
  }
  let j = i
  while (EMPTY_CHAR.test(text[j])) {
    j += 1
  }

  return text.slice(i + 1, j)
}
/**
 * @param {Property | SpreadElement} node
 * @returns {node is ObjectExpressionProperty}
 */
function isNameProperty(node) {
  return (
    node.type === 'Property' &&
    utils.getStaticPropertyName(node) === 'name' &&
    !node.computed
  )
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'require a name property in Vue components',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/require-name-property.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: [],
    messages: {
      missingName: 'Required name property is not set.',
      addName: 'Add name property to component.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    if (utils.isScriptSetup(context)) {
      return {}
    }
    return utils.executeOnVue(context, (component, type) => {
      if (type === 'definition') {
        const defType = getVueComponentDefinitionType(component)
        if (defType === 'mixin') {
          return
        }
      }

      if (component.properties.some(isNameProperty)) return

      context.report({
        node: component,
        messageId: 'missingName',
        suggest: [
          {
            messageId: 'addName',
            fix(fixer) {
              const extension = path.extname(context.getFilename())
              const filename = path.basename(context.getFilename(), extension)
              const sourceCode = context.getSourceCode()
              // fix only when property is not empty
              if (component.properties.length > 0) {
                const firstToken = sourceCode.getFirstToken(
                  component.properties[0]
                )
                const indentText = getLineEmptyIndent(sourceCode, firstToken)
                // insert name property before the first property
                return fixer.insertTextBefore(
                  component.properties[0],
                  `name: '${filename}',\n${indentText}`
                )
              }

              const firstToken = sourceCode.getFirstToken(component)
              const lastToken = sourceCode.getLastToken(component)
              // if the component is empty, insert name property and indent
              if (firstToken.value === '{' && lastToken.value === '}') {
                const indentText = getLineEmptyIndent(sourceCode, firstToken)
                return fixer.replaceTextRange(
                  [firstToken.range[1], lastToken.range[0]],
                  `\n${indentText}  name: '${filename}'\n${indentText}`
                )
              }
              return null
            }
          }
        ]
      })
    })
  }
}


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

const utils = require('../utils')
const { isBlockComment, isJSDocComment } = require('../utils/comments.js')

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'require props to have a comment',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/require-prop-comment.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          type: { enum: ['JSDoc', 'line', 'block', 'any'] }
        },
        additionalProperties: false
      }
    ],
    messages: {
      requireAnyComment: 'The "{{name}}" property should have a comment.',
      requireLineComment: 'The "{{name}}" property should have a line comment.',
      requireBlockComment:
        'The "{{name}}" property should have a block comment.',
      requireJSDocComment:
        'The "{{name}}" property should have a JSDoc comment.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {{type?: "JSDoc" | "line" | "block" | "any"}|undefined} */
    const schema = context.options[0]
    const type = (schema && schema.type) || 'JSDoc'

    const sourceCode = context.getSourceCode()

    /** @param {Comment | undefined} comment */
    const verifyBlock = (comment) =>
      comment && isBlockComment(comment) ? undefined : 'requireBlockComment'

    /** @param {Comment | undefined} comment */
    const verifyLine = (comment) =>
      comment && comment.type === 'Line' ? undefined : 'requireLineComment'

    /** @param {Comment | undefined} comment */
    const verifyAny = (comment) => (comment ? undefined : 'requireAnyComment')

    /** @param {Comment | undefined} comment */
    const verifyJSDoc = (comment) =>
      comment && isJSDocComment(comment) ? undefined : 'requireJSDocComment'

    /**
     * @param {import('../utils').ComponentProp[]} props
     */
    function verifyProps(props) {
      for (const prop of props) {
        if (!prop.propName || prop.type === 'infer-type') {
          continue
        }

        const precedingComments = sourceCode.getCommentsBefore(prop.node)
        const lastPrecedingComment =
          precedingComments.length > 0
            ? precedingComments[precedingComments.length - 1]
            : undefined

        /** @type {string|undefined} */
        let messageId

        switch (type) {
          case 'block': {
            messageId = verifyBlock(lastPrecedingComment)
            break
          }
          case 'line': {
            messageId = verifyLine(lastPrecedingComment)
            break
          }
          case 'any': {
            messageId = verifyAny(lastPrecedingComment)
            break
          }
          default: {
            messageId = verifyJSDoc(lastPrecedingComment)
            break
          }
        }

        if (!messageId) {
          continue
        }

        context.report({
          node: prop.node,
          messageId,
          data: {
            name: prop.propName
          }
        })
      }
    }

    return utils.compositingVisitors(
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(_node, props) {
          verifyProps(props)
        }
      }),
      utils.defineVueVisitor(context, {
        onVueObjectEnter(node) {
          verifyProps(utils.getComponentPropsFromOptions(node))
        }
      })
    )
  }
}


code:
/**
 * @fileoverview require prop type to be a constructor
 * @author Michał Sajnóg
 */
'use strict'

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

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

const forbiddenTypes = new Set([
  'Literal',
  'TemplateLiteral',
  'BinaryExpression',
  'UpdateExpression'
])

/**
 * @param {ESNode} node
 */
function isForbiddenType(node) {
  return (
    forbiddenTypes.has(node.type) &&
    !(node.type === 'Literal' && node.value == null && !node.bigint)
  )
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'require prop type to be a constructor',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/require-prop-type-constructor.html'
    },
    fixable: 'code',
    schema: [],
    messages: {
      propTypeConstructor: 'The "{{name}}" property should be a constructor.'
    }
  },

  /** @param {RuleContext} context */
  create(context) {
    /**
     * @param {string} propName
     * @param {ESNode} node
     */
    function checkPropertyNode(propName, node) {
      /** @type {ESNode[]} */
      const nodes =
        node.type === 'ArrayExpression' ? node.elements.filter(isDef) : [node]

      for (const prop of nodes) {
        if (!isForbiddenType(prop)) continue

        context.report({
          node: prop,
          messageId: 'propTypeConstructor',
          data: {
            name: propName
          },
          fix: (fixer) => {
            if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') {
              const newText = utils.getStringLiteralValue(prop, true)

              if (newText) {
                return fixer.replaceText(prop, newText)
              }
            }
            return null
          }
        })
      }
    }

    /** @param {ComponentProp[]} props */
    function verifyProps(props) {
      for (const prop of props) {
        if (prop.type !== 'object' || prop.propName == null) {
          continue
        }
        if (
          isForbiddenType(prop.value) ||
          prop.value.type === 'ArrayExpression'
        ) {
          checkPropertyNode(prop.propName, prop.value)
        } else if (prop.value.type === 'ObjectExpression') {
          const typeProperty = utils.findProperty(prop.value, 'type')

          if (!typeProperty) continue

          checkPropertyNode(prop.propName, typeProperty.value)
        }
      }
    }

    return utils.compositingVisitors(
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(_node, props) {
          verifyProps(props)
        }
      }),
      utils.executeOnVueComponent(context, (obj) => {
        verifyProps(utils.getComponentPropsFromOptions(obj))
      })
    )
  }
}


code:
/**
 * @fileoverview Prop definitions should be detailed
 * @author Armano
 */
'use strict'

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

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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'require type definitions in props',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/require-prop-types.html'
    },
    fixable: null,
    schema: [],
    messages: {
      requireType: 'Prop "{{name}}" should define at least its type.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /**
     * @param {Expression} node
     * @returns {boolean|null}
     */
    function optionHasType(node) {
      switch (node.type) {
        case 'ObjectExpression': {
          // foo: {
          return objectHasType(node)
        }
        case 'ArrayExpression': {
          // foo: [
          return node.elements.length > 0
        }
        case 'FunctionExpression':
        case 'ArrowFunctionExpression': {
          return false
        }
      }

      // Unknown
      return null
    }
    /**
     * @param {ObjectExpression} node
     * @returns {boolean}
     */
    function objectHasType(node) {
      const typeProperty = node.properties.find(
        (p) =>
          p.type === 'Property' &&
          utils.getStaticPropertyName(p) === 'type' &&
          (p.value.type !== 'ArrayExpression' || p.value.elements.length > 0)
      )
      const validatorProperty = node.properties.find(
        (p) =>
          p.type === 'Property' &&
          utils.getStaticPropertyName(p) === 'validator'
      )
      return Boolean(typeProperty || validatorProperty)
    }
    /**
     * @param {ComponentProp} prop
     */
    function checkProperty(prop) {
      if (prop.type !== 'object' && prop.type !== 'array') {
        return
      }
      const hasType =
        prop.type === 'array' ? false : (optionHasType(prop.value) ?? true)

      if (!hasType) {
        const { node, propName } = prop
        const name =
          propName ||
          (node.type === 'Identifier' && node.name) ||
          'Unknown prop'
        context.report({
          node,
          messageId: 'requireType',
          data: {
            name
          }
        })
      }
    }

    return utils.compositingVisitors(
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(_node, props) {
          for (const prop of props) {
            checkProperty(prop)
          }
        },
        onDefineModelEnter(node, model) {
          if (model.typeNode) return
          if (model.options && (optionHasType(model.options) ?? true)) {
            return
          }
          context.report({
            node: model.options || node,
            messageId: 'requireType',
            data: {
              name: model.name.modelName
            }
          })
        }
      }),
      utils.executeOnVue(context, (obj) => {
        const props = utils.getComponentPropsFromOptions(obj)

        for (const prop of props) {
          checkProperty(prop)
        }
      })
    )
  }
}


code:
/**
 * @fileoverview Enforces render function to always return value.
 * @author Armano
 */
'use strict'

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'enforce render function to always return value',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/require-render-return.html'
    },
    fixable: null,
    schema: [],
    messages: {
      expectedReturn: 'Expected to return a value in render function.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {Map<ESNode, Property['key']>} */
    const renderFunctions = new Map()

    return utils.compositingVisitors(
      utils.defineVueVisitor(context, {
        onRenderFunctionEnter(node) {
          renderFunctions.set(node, node.parent.key)
        }
      }),
      utils.executeOnFunctionsWithoutReturn(true, (node) => {
        const key = renderFunctions.get(node)
        if (key) {
          context.report({
            node: key,
            messageId: 'expectedReturn'
          })
        }
      })
    )
  }
}


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

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'enforce properties of `$slots` to be used as a function',
      categories: ['vue3-essential'],
      url: 'https://eslint.vuejs.org/rules/require-slots-as-functions.html'
    },
    fixable: null,
    schema: [],
    messages: {
      unexpected: 'Property in `$slots` should be used as function.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /**
     * Verify the given node
     * @param {MemberExpression | Identifier | ChainExpression} node The node to verify
     * @param {Expression} reportNode The node to report
     */
    function verify(node, reportNode) {
      const parent = node.parent

      if (
        parent.type === 'VariableDeclarator' &&
        parent.id.type === 'Identifier'
      ) {
        // const children = this.$slots.foo
        verifyReferences(parent.id, reportNode)
        return
      }

      if (
        parent.type === 'AssignmentExpression' &&
        parent.right === node &&
        parent.left.type === 'Identifier'
      ) {
        // children = this.$slots.foo
        verifyReferences(parent.left, reportNode)
        return
      }

      if (parent.type === 'ChainExpression') {
        // (this.$slots?.foo).x
        verify(parent, reportNode)
        return
      }

      if (
        // this.$slots.foo.xxx
        parent.type === 'MemberExpression' ||
        // var [foo] = this.$slots.foo
        parent.type === 'VariableDeclarator' ||
        // [...this.$slots.foo]
        parent.type === 'SpreadElement' ||
        // [this.$slots.foo]
        parent.type === 'ArrayExpression'
      ) {
        context.report({
          node: reportNode,
          messageId: 'unexpected'
        })
      }
    }
    /**
     * Verify the references of the given node.
     * @param {Identifier} node The node to verify
     * @param {Expression} reportNode The node to report
     */
    function verifyReferences(node, reportNode) {
      const variable = findVariable(utils.getScope(context, node), node)
      if (!variable) {
        return
      }
      for (const reference of variable.references) {
        if (!reference.isRead()) {
          continue
        }
        /** @type {Identifier} */
        const id = reference.identifier
        verify(id, reportNode)
      }
    }

    return utils.defineVueVisitor(context, {
      /** @param {MemberExpression} node */
      MemberExpression(node) {
        const object = utils.skipChainExpression(node.object)
        if (object.type !== 'MemberExpression') {
          return
        }
        if (utils.getStaticPropertyName(object) !== '$slots') {
          return
        }
        if (!utils.isThis(object.object, context)) {
          return
        }
        if (node.property.type === 'PrivateIdentifier') {
          // Unreachable
          return
        }
        verify(node, node.property)
      }
    })
  }
}


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

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

/**
 * @param {VDirective} vBindAppear
 */
function isValidBindAppear(vBindAppear) {
  if (
    vBindAppear.value?.expression &&
    vBindAppear.value.expression.type === 'Literal'
  ) {
    return vBindAppear.value.expression?.value !== false
  }

  return true
}

/**
 * @param {string[]} directives
 */
function createDirectiveList(directives) {
  let str = ''

  for (const [index, directive] of directives.entries()) {
    if (index === 0) {
      str += `\`v-${directive}\``
    } else if (index < directives.length - 1) {
      str += `, \`v-${directive}\``
    } else {
      str += ` or \`v-${directive}\``
    }
  }

  return str
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description:
        'require control the display of the content inside `<transition>`',
      categories: ['vue3-essential'],
      url: 'https://eslint.vuejs.org/rules/require-toggle-inside-transition.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          additionalDirectives: {
            type: 'array',
            items: {
              type: 'string'
            },
            uniqueItems: true
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      expected:
        'The element inside `<transition>` is expected to have a {{allowedDirectives}} directive.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {Array<string>} */
    const additionalDirectives =
      (context.options[0] && context.options[0].additionalDirectives) || []
    const allowedDirectives = ['if', 'show', ...additionalDirectives]
    const allowedDirectivesString = createDirectiveList(allowedDirectives)

    /**
     * Check if the given element has display control.
     * @param {VElement} element The element node to check.
     */
    function verifyInsideElement(element) {
      if (utils.isCustomComponent(element)) {
        return
      }

      /** @type VElement */ // @ts-expect-error
      const parent = element.parent
      const vBindAppear = utils.getDirective(parent, 'bind', 'appear')
      if (
        utils.hasAttribute(parent, 'appear') ||
        (vBindAppear && isValidBindAppear(vBindAppear))
      ) {
        return
      }

      if (
        element.name !== 'slot' &&
        !allowedDirectives.some((directive) =>
          utils.hasDirective(element, directive)
        ) &&
        !utils.hasDirective(element, 'bind', 'key')
      ) {
        context.report({
          node: element.startTag,
          loc: element.startTag.loc,
          messageId: 'expected',
          data: {
            allowedDirectives: allowedDirectivesString
          }
        })
      }
    }

    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VElement} node */
      "VElement[name='transition'] > VElement"(node) {
        const child = node.parent.children.find(utils.isVElement)
        if (child !== node) {
          return
        }

        verifyInsideElement(node)
      }
    })
  }
}


code:
/**
 * @author Przemysław Jan Beigert
 * See LICENSE file in root directory for full license.
 */
'use strict'

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

/**
 * @param {RuleContext} context
 * @param {Identifier} identifierNode
 */
const checkPropIdentifierType = (context, identifierNode) => {
  if (identifierNode.name === 'Object' || identifierNode.name === 'Array') {
    const arrayTypeSuggestion = identifierNode.name === 'Array' ? '[]' : ''
    context.report({
      node: identifierNode,
      messageId: 'expectedTypeAnnotation',
      suggest: [
        {
          messageId: 'addTypeAnnotation',
          data: { type: `any${arrayTypeSuggestion}` },
          fix(fixer) {
            return fixer.insertTextAfter(
              identifierNode,
              ` as PropType<any${arrayTypeSuggestion}>`
            )
          }
        },
        {
          messageId: 'addTypeAnnotation',
          data: { type: `unknown${arrayTypeSuggestion}` },
          fix(fixer) {
            return fixer.insertTextAfter(
              identifierNode,
              ` as PropType<unknown${arrayTypeSuggestion}>`
            )
          }
        }
      ]
    })
  }
}

/**
 * @param {RuleContext} context
 * @param {ArrayExpression} arrayNode
 */
const checkPropArrayType = (context, arrayNode) => {
  for (const elementNode of arrayNode.elements) {
    if (elementNode?.type === 'Identifier') {
      checkPropIdentifierType(context, elementNode)
    }
  }
}

/**
 * @param {RuleContext} context
 * @param {ObjectExpression} objectNode
 */
const checkPropObjectType = (context, objectNode) => {
  const typeProperty = objectNode.properties.find(
    (prop) =>
      prop.type === 'Property' &&
      prop.key.type === 'Identifier' &&
      prop.key.name === 'type'
  )
  if (!typeProperty || typeProperty.type !== 'Property') {
    return
  }

  if (typeProperty.value.type === 'Identifier') {
    // `foo: { type: String }`
    checkPropIdentifierType(context, typeProperty.value)
  } else if (typeProperty.value.type === 'ArrayExpression') {
    // `foo: { type: [String, Boolean] }`
    checkPropArrayType(context, typeProperty.value)
  }
}

/**
 * @param {import('../utils').ComponentProp} prop
 * @param {RuleContext} context
 */
const checkProp = (prop, context) => {
  if (prop.type !== 'object') {
    return
  }

  switch (prop.node.value.type) {
    case 'Identifier': {
      // e.g. `foo: String`
      checkPropIdentifierType(context, prop.node.value)
      break
    }
    case 'ArrayExpression': {
      // e.g. `foo: [String, Boolean]`
      checkPropArrayType(context, prop.node.value)
      break
    }
    case 'ObjectExpression': {
      // e.g. `foo: { type: … }`
      checkPropObjectType(context, prop.node.value)
      return
    }
  }
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce adding type declarations to object props',
      categories: undefined,
      recommended: false,
      url: 'https://eslint.vuejs.org/rules/require-typed-object-prop.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: [],
    messages: {
      expectedTypeAnnotation: 'Expected type annotation on object prop.',
      addTypeAnnotation: 'Add `{{ type }}` type annotation.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const filename = context.getFilename()
    if (!utils.isVueFile(filename) && !utils.isTypeScriptFile(filename)) {
      return {}
    }

    if (utils.isVueFile(filename)) {
      const sourceCode = context.getSourceCode()
      const documentFragment =
        sourceCode.parserServices.getDocumentFragment &&
        sourceCode.parserServices.getDocumentFragment()
      if (!documentFragment) {
        return {}
      }
      const scripts = documentFragment.children.filter(
        /** @returns {element is VElement} */
        (element) => utils.isVElement(element) && element.name === 'script'
      )
      if (
        scripts.every((script) => !utils.hasAttribute(script, 'lang', 'ts'))
      ) {
        return {}
      }
    }

    return utils.compositingVisitors(
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(_node, props) {
          for (const prop of props) {
            checkProp(prop, context)
          }
        }
      }),
      utils.executeOnVue(context, (obj) => {
        const props = utils.getComponentPropsFromOptions(obj)

        for (const prop of props) {
          checkProp(prop, context)
        }
      })
    )
  }
}


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

const { iterateDefineRefs } = require('../utils/ref-object-references')
const utils = require('../utils')

/**
 * @param {Expression|SpreadElement} node
 */
function isNullOrUndefined(node) {
  return (
    (node.type === 'Literal' && node.value === null) ||
    (node.type === 'Identifier' && node.name === 'undefined')
  )
}

/**
 * @typedef {import('../utils/ref-object-references').RefObjectReferences} RefObjectReferences
 */

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'require `ref` and `shallowRef` functions to be strongly typed',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/require-typed-ref.html'
    },
    fixable: null,
    schema: [],
    messages: {
      noType:
        'Specify type parameter for `{{name}}` function, otherwise created variable will not be typechecked.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const filename = context.getFilename()
    if (!utils.isVueFile(filename) && !utils.isTypeScriptFile(filename)) {
      return {}
    }

    if (utils.isVueFile(filename)) {
      const sourceCode = context.getSourceCode()
      const documentFragment =
        sourceCode.parserServices.getDocumentFragment &&
        sourceCode.parserServices.getDocumentFragment()
      if (!documentFragment) {
        return {}
      }
      const scripts = documentFragment.children.filter(
        /** @returns {element is VElement} */
        (element) => utils.isVElement(element) && element.name === 'script'
      )
      if (
        scripts.every((script) => !utils.hasAttribute(script, 'lang', 'ts'))
      ) {
        return {}
      }
    }

    const defines = iterateDefineRefs(
      /** @type {import('eslint').Scope.Scope} */ (
        context.getSourceCode().scopeManager.globalScope
      )
    )

    /**
     * @param {string} name
     * @param {CallExpression} node
     */
    function report(name, node) {
      context.report({
        node,
        messageId: 'noType',
        data: {
          name
        }
      })
    }

    return {
      Program() {
        for (const ref of defines) {
          if (ref.name !== 'ref' && ref.name !== 'shallowRef') {
            continue
          }

          if (
            ref.node.arguments.length > 0 &&
            !isNullOrUndefined(ref.node.arguments[0])
          ) {
            continue
          }

          const typeArguments =
            'typeArguments' in ref.node
              ? ref.node.typeArguments
              : ref.node.typeParameters
          if (typeArguments == null) {
            if (
              ref.node.parent.type === 'VariableDeclarator' &&
              ref.node.parent.id.type === 'Identifier'
            ) {
              if (ref.node.parent.id.typeAnnotation == null) {
                report(ref.name, ref.node)
              }
            } else {
              report(ref.name, ref.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: 'require `v-bind:key` with `v-for` directives',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/require-v-for-key.html'
    },
    fixable: null,
    schema: [],
    messages: {
      requireKey:
        "Elements in iteration expect to have 'v-bind:key' directives."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /**
     * Check the given element about `v-bind:key` attributes.
     * @param {VElement} element The element node to check.
     */
    function checkKey(element) {
      if (utils.hasDirective(element, 'bind', 'key')) {
        return
      }
      if (element.name === 'template' || element.name === 'slot') {
        for (const child of element.children) {
          if (child.type === 'VElement') {
            checkKey(child)
          }
        }
      } else if (!utils.isCustomComponent(element)) {
        context.report({
          node: element.startTag,
          loc: element.startTag.loc,
          messageId: 'requireKey'
        })
      }
    }

    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VDirective} node */
      "VAttribute[directive=true][key.name.name='for']"(node) {
        checkKey(node.parent.parent)
      }
    })
  }
}


code:
/**
 * @fileoverview Enforces props default values to be valid.
 * @author Armano
 */
'use strict'
const utils = require('../utils')
const { capitalize } = require('../utils/casing')

/**
 * @typedef {import('../utils').ComponentProp} ComponentProp
 * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
 * @typedef {import('../utils').ComponentArrayProp} ComponentArrayProp
 * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
 * @typedef {import('../utils').ComponentInferTypeProp} ComponentInferTypeProp
 * @typedef {import('../utils').ComponentUnknownProp} ComponentUnknownProp
 * @typedef {import('../utils').VueObjectData} VueObjectData
 */

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

const FUNCTION_VALUE_TYPES = new Set(['Function', 'Object', 'Array'])

/**
 * @param {ObjectExpression} obj
 * @param {string} name
 * @returns {Property | null}
 */
function getPropertyNode(obj, name) {
  for (const p of obj.properties) {
    if (
      p.type === 'Property' &&
      !p.computed &&
      p.key.type === 'Identifier' &&
      p.key.name === name
    ) {
      return p
    }
  }
  return null
}

/**
 * @param {Expression} targetNode
 * @returns {string[]}
 */
function getTypes(targetNode) {
  const node = utils.skipTSAsExpression(targetNode)
  if (node.type === 'Identifier') {
    return [node.name]
  } else if (node.type === 'ArrayExpression') {
    return node.elements
      .filter(
        /**
         * @param {Expression | SpreadElement | null} item
         * @returns {item is Identifier}
         */
        (item) => item != null && item.type === 'Identifier'
      )
      .map((item) => item.name)
  }
  return []
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce props default values to be valid',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/require-valid-default-prop.html'
    },
    fixable: null,
    schema: [],
    messages: {
      invalidType:
        "Type of the default value for '{{name}}' prop must be a {{types}}."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /**
     * @typedef {object} StandardValueType
     * @property {string} type
     * @property {false} function
     */
    /**
     * @typedef {object} FunctionExprValueType
     * @property {'Function'} type
     * @property {true} function
     * @property {true} expression
     * @property {Expression} functionBody
     * @property {string | null} returnType
     */
    /**
     * @typedef {object} FunctionValueType
     * @property {'Function'} type
     * @property {true} function
     * @property {false} expression
     * @property {BlockStatement} functionBody
     * @property {ReturnType[]} returnTypes
     */
    /**
     * @typedef { ComponentObjectProp & { value: ObjectExpression } } ComponentObjectDefineProp
     * @typedef { { type: string, node: Expression } } ReturnType
     */
    /**
     * @typedef {object} PropDefaultFunctionContext
     * @property {ComponentObjectProp | ComponentTypeProp | ComponentInferTypeProp} prop
     * @property {Set<string>} types
     * @property {FunctionValueType} default
     */

    /**
     * @type {Map<ObjectExpression, PropDefaultFunctionContext[]>}
     */
    const vueObjectPropsContexts = new Map()
    /**
     * @type { {node: CallExpression, props:PropDefaultFunctionContext[]}[] }
     */
    const scriptSetupPropsContexts = []

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

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

    /**
     * @param {Expression} targetNode
     * @returns { StandardValueType | FunctionExprValueType | FunctionValueType | null }
     */
    function getValueType(targetNode) {
      const node = utils.skipChainExpression(targetNode)
      switch (node.type) {
        case 'CallExpression': {
          // Symbol(), Number() ...
          if (
            node.callee.type === 'Identifier' &&
            NATIVE_TYPES.has(node.callee.name)
          ) {
            return {
              function: false,
              type: node.callee.name
            }
          }
          break
        }
        case 'TemplateLiteral': {
          // String
          return {
            function: false,
            type: 'String'
          }
        }
        case 'Literal': {
          // String, Boolean, Number
          if (node.value === null && !node.bigint) return null
          const type = node.bigint ? 'BigInt' : capitalize(typeof node.value)
          if (NATIVE_TYPES.has(type)) {
            return {
              function: false,
              type
            }
          }
          break
        }
        case 'ArrayExpression': {
          // Array
          return {
            function: false,
            type: 'Array'
          }
        }
        case 'ObjectExpression': {
          // Object
          return {
            function: false,
            type: 'Object'
          }
        }
        case 'FunctionExpression': {
          return {
            function: true,
            expression: false,
            type: 'Function',
            functionBody: node.body,
            returnTypes: []
          }
        }
        case 'ArrowFunctionExpression': {
          if (node.expression) {
            const valueType = getValueType(node.body)
            return {
              function: true,
              expression: true,
              type: 'Function',
              functionBody: node.body,
              returnType: valueType ? valueType.type : null
            }
          }

          return {
            function: true,
            expression: false,
            type: 'Function',
            functionBody: node.body,
            returnTypes: []
          }
        }
      }
      return null
    }

    /**
     * @param {*} node
     * @param {ComponentObjectProp | ComponentTypeProp | ComponentInferTypeProp} prop
     * @param {Iterable<string>} expectedTypeNames
     */
    function report(node, prop, expectedTypeNames) {
      const propName =
        prop.propName == null
          ? `[${context.getSourceCode().getText(prop.node.key)}]`
          : prop.propName
      context.report({
        node,
        messageId: 'invalidType',
        data: {
          name: propName,
          types: [...expectedTypeNames].join(' or ').toLowerCase()
        }
      })
    }

    /**
     * @param {(ComponentObjectProp | ComponentTypeProp | ComponentInferTypeProp)[]} props
     * @param {(propName: string) => Expression[]} otherDefaultProvider
     */
    function processPropDefs(props, otherDefaultProvider) {
      /** @type {PropDefaultFunctionContext[]} */
      const propContexts = []
      for (const prop of props) {
        let typeList
        /** @type {Expression[]} */
        const defExprList = []
        if (prop.type === 'object') {
          if (prop.value.type === 'ObjectExpression') {
            const type = getPropertyNode(prop.value, 'type')
            if (!type) continue

            typeList = getTypes(type.value)

            const def = getPropertyNode(prop.value, 'default')
            if (!def) continue

            defExprList.push(def.value)
          } else {
            typeList = getTypes(prop.value)
          }
        } else {
          typeList = prop.types
        }
        if (prop.propName != null) {
          defExprList.push(...otherDefaultProvider(prop.propName))
        }

        if (defExprList.length === 0) continue

        const typeNames = new Set(
          typeList.filter((item) => NATIVE_TYPES.has(item))
        )
        // There is no native types detected
        if (typeNames.size === 0) continue

        for (const defExpr of defExprList) {
          const defType = getValueType(defExpr)

          if (!defType) continue

          if (defType.function) {
            if (typeNames.has('Function')) {
              continue
            }
            if (defType.expression) {
              if (!defType.returnType || typeNames.has(defType.returnType)) {
                continue
              }
              report(defType.functionBody, prop, typeNames)
            } else {
              propContexts.push({
                prop,
                types: typeNames,
                default: defType
              })
            }
          } else {
            if (
              typeNames.has(defType.type) &&
              !FUNCTION_VALUE_TYPES.has(defType.type)
            ) {
              continue
            }
            report(
              defExpr,
              prop,
              [...typeNames].map((type) =>
                FUNCTION_VALUE_TYPES.has(type) ? 'Function' : type
              )
            )
          }
        }
      }
      return propContexts
    }

    return utils.compositingVisitors(
      {
        /**
         * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
         */
        ':function'(node) {
          scopeStack = {
            upper: scopeStack,
            body: node.body,
            returnTypes: null
          }
        },
        /**
         * @param {ReturnStatement} node
         */
        ReturnStatement(node) {
          if (!scopeStack) {
            return
          }
          if (scopeStack.returnTypes && node.argument) {
            const type = getValueType(node.argument)
            if (type) {
              scopeStack.returnTypes.push({
                type: type.type,
                node: node.argument
              })
            }
          }
        },
        ':function:exit': onFunctionExit
      },
      utils.defineVueVisitor(context, {
        onVueObjectEnter(obj) {
          /** @type {ComponentObjectDefineProp[]} */
          const props = utils.getComponentPropsFromOptions(obj).filter(
            /**
             * @param {ComponentObjectProp | ComponentArrayProp | ComponentUnknownProp} prop
             * @returns {prop is ComponentObjectDefineProp}
             */
            (prop) =>
              Boolean(
                prop.type === 'object' && prop.value.type === 'ObjectExpression'
              )
          )
          const propContexts = processPropDefs(props, () => [])
          vueObjectPropsContexts.set(obj, propContexts)
        },
        /**
         * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
         * @param {VueObjectData} data
         */
        ':function'(node, { node: vueNode }) {
          const data = vueObjectPropsContexts.get(vueNode)
          if (!data || !scopeStack) {
            return
          }

          for (const { default: defType } of data) {
            if (node.body === defType.functionBody) {
              scopeStack.returnTypes = defType.returnTypes
            }
          }
        },
        onVueObjectExit(obj) {
          const data = vueObjectPropsContexts.get(obj)
          if (!data) {
            return
          }
          for (const { prop, types: typeNames, default: defType } of data) {
            for (const returnType of defType.returnTypes) {
              if (typeNames.has(returnType.type)) continue

              report(returnType.node, prop, typeNames)
            }
          }
        }
      }),
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(node, baseProps) {
          const props = baseProps.filter(
            /**
             * @param {ComponentProp} prop
             * @returns {prop is ComponentObjectProp | ComponentInferTypeProp | ComponentTypeProp}
             */
            (prop) =>
              Boolean(
                prop.type === 'type' ||
                  prop.type === 'infer-type' ||
                  prop.type === 'object'
              )
          )
          const defaultsByWithDefaults =
            utils.getWithDefaultsPropExpressions(node)
          const defaultsByAssignmentPatterns =
            utils.getDefaultPropExpressionsForPropsDestructure(node)
          const propContexts = processPropDefs(props, (propName) =>
            [
              defaultsByWithDefaults[propName],
              defaultsByAssignmentPatterns[propName]?.expression
            ].filter(utils.isDef)
          )
          scriptSetupPropsContexts.push({ node, props: propContexts })
        },
        /**
         * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
         */
        ':function'(node) {
          const data =
            scriptSetupPropsContexts[scriptSetupPropsContexts.length - 1]
          if (!data || !scopeStack) {
            return
          }

          for (const { default: defType } of data.props) {
            if (node.body === defType.functionBody) {
              scopeStack.returnTypes = defType.returnTypes
            }
          }
        },
        onDefinePropsExit() {
          scriptSetupPropsContexts.pop()
        }
      })
    )
  }
}


code:
/**
 * @fileoverview Enforces that a return statement is present in computed property (return-in-computed-property)
 * @author Armano
 */
'use strict'
const { ReferenceTracker } = require('@eslint-community/eslint-utils')
const utils = require('../utils')

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description:
        'enforce that a return statement is present in computed property',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/return-in-computed-property.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          treatUndefinedAsUnspecified: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      expectedReturnInFunction:
        'Expected to return a value in computed function.',
      expectedReturnInProperty:
        'Expected to return a value in "{{name}}" computed property.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || {}
    const treatUndefinedAsUnspecified = !(
      options.treatUndefinedAsUnspecified === false
    )

    /**
     * @type {Set<ComponentComputedProperty>}
     */
    const computedProperties = new Set()
    /** @type {(FunctionExpression | ArrowFunctionExpression)[]} */
    const computedFunctionNodes = []

    return Object.assign(
      {
        /** @param {Program} program */
        Program(program) {
          const tracker = new ReferenceTracker(utils.getScope(context, program))
          const map = {
            computed: {
              [ReferenceTracker.CALL]: true
            }
          }

          for (const { node } of utils.iterateReferencesTraceMap(
            tracker,
            map
          )) {
            if (node.type !== 'CallExpression') {
              continue
            }

            const getter = utils.getGetterBodyFromComputedFunction(node)
            if (getter) {
              computedFunctionNodes.push(getter)
            }
          }
        }
      },
      utils.defineVueVisitor(context, {
        onVueObjectEnter(obj) {
          for (const computedProperty of utils.getComputedProperties(obj)) {
            computedProperties.add(computedProperty)
          }
        }
      }),
      utils.executeOnFunctionsWithoutReturn(
        treatUndefinedAsUnspecified,
        (node) => {
          for (const cp of computedProperties) {
            if (cp.value && cp.value.parent === node) {
              context.report({
                node,
                messageId: 'expectedReturnInProperty',
                data: {
                  name: cp.key || 'Unknown'
                }
              })
            }
          }
          for (const cf of computedFunctionNodes) {
            if (cf === node) {
              context.report({
                node,
                messageId: 'expectedReturnInFunction'
              })
            }
          }
        }
      )
    )
  }
}


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

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

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

/**
 * Checks if the given node value is falsy.
 * @param {Expression} node The node to check
 * @returns {boolean} If `true`, the given node value is falsy.
 */
function isFalsy(node) {
  if (node.type === 'Literal') {
    if (node.bigint) {
      return node.bigint === '0'
    }
    if (!node.value) {
      return true
    }
  }

  return (
    node.type === 'Identifier' &&
    (node.name === 'undefined' || node.name === 'NaN')
  )
}
module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description:
        'enforce that a return statement is present in emits validator',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/return-in-emits-validator.html'
    },
    fixable: null,
    schema: [],
    messages: {
      expectedTrue:
        'Expected to return a true value in "{{name}}" emits validator.',
      expectedBoolean:
        'Expected to return a boolean value in "{{name}}" emits validator.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {ComponentObjectEmit[]} */
    const emitsValidators = []

    /**
     * @typedef {object} ScopeStack
     * @property {ScopeStack | null} upper
     * @property {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} functionNode
     * @property {boolean} hasReturnValue
     * @property {boolean} possibleOfReturnTrue
     */
    /**
     * @type {ScopeStack | null}
     */
    let scopeStack = null

    /**
     * @param {ComponentEmit[]} emits
     */
    function processEmits(emits) {
      for (const emit of emits) {
        if (emit.type !== 'object' || !emit.value) {
          continue
        }
        if (
          emit.value.type !== 'FunctionExpression' &&
          emit.value.type !== 'ArrowFunctionExpression'
        ) {
          continue
        }
        emitsValidators.push(emit)
      }
    }
    return utils.compositingVisitors(
      utils.defineScriptSetupVisitor(context, {
        onDefineEmitsEnter(_node, emits) {
          processEmits(emits)
        }
      }),
      utils.defineVueVisitor(context, {
        /** @param {ObjectExpression} obj */
        onVueObjectEnter(obj) {
          processEmits(utils.getComponentEmitsFromOptions(obj))
        }
      }),
      {
        /** @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node */
        ':function'(node) {
          scopeStack = {
            upper: scopeStack,
            functionNode: node,
            hasReturnValue: false,
            possibleOfReturnTrue: false
          }

          if (node.type === 'ArrowFunctionExpression' && node.expression) {
            scopeStack.hasReturnValue = true

            if (!isFalsy(node.body)) {
              scopeStack.possibleOfReturnTrue = true
            }
          }
        },
        /** @param {ReturnStatement} node */
        ReturnStatement(node) {
          if (!scopeStack) {
            return
          }
          if (node.argument) {
            scopeStack.hasReturnValue = true

            if (!isFalsy(node.argument)) {
              scopeStack.possibleOfReturnTrue = true
            }
          }
        },
        /** @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node */
        ':function:exit'(node) {
          if (scopeStack && !scopeStack.possibleOfReturnTrue) {
            const emits = emitsValidators.find((e) => e.value === node)
            if (emits) {
              context.report({
                node,
                messageId: scopeStack.hasReturnValue
                  ? 'expectedTrue'
                  : 'expectedBoolean',
                data: {
                  name: emits.emitName || 'Unknown'
                }
              })
            }
          }

          scopeStack = scopeStack && scopeStack.upper
        }
      }
    )
  }
}


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

const indentCommon = require('../utils/indent-common')

module.exports = {
  // eslint-disable-next-line eslint-plugin/prefer-message-ids
  meta: {
    type: 'layout',
    docs: {
      description: 'enforce consistent indentation in `<script>`',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/script-indent.html'
    },
    // eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
    fixable: 'whitespace',
    schema: [
      {
        anyOf: [{ type: 'integer', minimum: 1 }, { enum: ['tab'] }]
      },
      {
        type: 'object',
        properties: {
          baseIndent: { type: 'integer', minimum: 0 },
          switchCase: { type: 'integer', minimum: 0 },
          ignores: {
            type: 'array',
            items: {
              allOf: [
                { type: 'string' },
                { not: { type: 'string', pattern: ':exit$' } },
                { not: { type: 'string', pattern: String.raw`^\s*$` } }
              ]
            },
            uniqueItems: true,
            additionalItems: false
          }
        },
        additionalProperties: false
      }
    ]
  },
  /** @param {RuleContext} context */
  create(context) {
    return indentCommon.defineVisitor(context, context.getSourceCode(), {})
  }
}


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

const { getStyleVariablesContext } = require('../utils/style-variables')
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/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/shared/src/index.ts#L116
 * @param {string} str
 */
function camelize(str) {
  return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
}

module.exports = {
  // eslint-disable-next-line eslint-plugin/prefer-message-ids
  meta: {
    type: 'problem',
    docs: {
      description:
        'prevent `<script setup>` variables used in `<template>` to be marked as unused', // eslint-disable-line eslint-plugin/require-meta-docs-description
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/script-setup-uses-vars.html'
    },
    deprecated: true,
    schema: []
  },
  /**
   * @param {RuleContext} context - The rule context.
   * @returns {RuleListener} AST event handlers.
   */
  create(context) {
    if (!utils.isScriptSetup(context)) {
      return {}
    }
    const sourceCode = context.getSourceCode()
    /** @type {Set<string>} */
    const scriptVariableNames = new Set()
    const globalScope = sourceCode.scopeManager.globalScope
    if (globalScope) {
      for (const variable of globalScope.variables) {
        scriptVariableNames.add(variable.name)
      }
      const moduleScope = globalScope.childScopes.find(
        (scope) => scope.type === 'module'
      )
      for (const variable of (moduleScope && moduleScope.variables) || []) {
        scriptVariableNames.add(variable.name)
      }
    }

    /** @param {string} name  */
    function markVariableAsUsed(name) {
      utils.markVariableAsUsed(context, name, sourceCode.ast)
    }

    /**
     * @see https://github.com/vuejs/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/compiler-core/src/transforms/transformElement.ts#L333
     * @param {string} name
     */
    function markSetupReferenceVariableAsUsed(name) {
      if (scriptVariableNames.has(name)) {
        markVariableAsUsed(name)
        return true
      }
      const camelName = camelize(name)
      if (scriptVariableNames.has(camelName)) {
        markVariableAsUsed(camelName)
        return true
      }
      const pascalName = casing.capitalize(camelName)
      if (scriptVariableNames.has(pascalName)) {
        markVariableAsUsed(pascalName)
        return true
      }
      return false
    }

    return utils.defineTemplateBodyVisitor(
      context,
      {
        VExpressionContainer(node) {
          for (const ref of node.references.filter(
            (ref) => ref.variable == null
          )) {
            markVariableAsUsed(ref.id.name)
          }
        },
        VElement(node) {
          if (
            (!utils.isHtmlElementNode(node) &&
              !utils.isSvgElementNode(node) &&
              !utils.isMathElementNode(node)) ||
            (node.rawName === node.name &&
              (utils.isHtmlWellKnownElementName(node.rawName) ||
                utils.isSvgWellKnownElementName(node.rawName) ||
                utils.isMathWellKnownElementName(node.rawName))) ||
            utils.isBuiltInComponentName(node.rawName)
          ) {
            return
          }
          if (!markSetupReferenceVariableAsUsed(node.rawName)) {
            // Check namespace
            // https://github.com/vuejs/vue-next/blob/2749c15170ad4913e6530a257db485d4e7ed2283/packages/compiler-core/src/transforms/transformElement.ts#L304
            const dotIndex = node.rawName.indexOf('.')
            if (dotIndex > 0) {
              markSetupReferenceVariableAsUsed(node.rawName.slice(0, dotIndex))
            }
          }
        },
        /** @param {VDirective} node */
        'VAttribute[directive=true]'(node) {
          if (utils.isBuiltInDirectiveName(node.key.name.name)) {
            return
          }
          markSetupReferenceVariableAsUsed(`v-${node.key.name.rawName}`)
        },
        /** @param {VAttribute} node */
        'VAttribute[directive=false]'(node) {
          if (node.key.name === 'ref' && node.value) {
            markVariableAsUsed(node.value.value)
          }
        }
      },
      {
        Program() {
          const styleVars = getStyleVariablesContext(context)
          if (styleVars) {
            for (const ref of styleVars.references) {
              markVariableAsUsed(ref.id.name)
            }
          }
        }
      },
      {
        templateBodyTriggerSelector: 'Program'
      }
    )
  }
}


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

const utils = require('../utils')
const casing = require('../utils/casing')
const INLINE_ELEMENTS = require('../utils/inline-non-void-elements.json')

/**
 * @param {VElement & { endTag: VEndTag } } element
 */
function isSinglelineElement(element) {
  return element.loc.start.line === element.endTag.loc.start.line
}

/**
 * @param {any} options
 */
function parseOptions(options) {
  return Object.assign(
    {
      ignores: ['pre', 'textarea', ...INLINE_ELEMENTS],
      externalIgnores: [],
      ignoreWhenNoAttributes: true,
      ignoreWhenEmpty: true
    },
    options
  )
}

/**
 * Check whether the given element is empty or not.
 * This ignores whitespaces, doesn't ignore comments.
 * @param {VElement & { endTag: VEndTag } } node The element node to check.
 * @param {SourceCode} sourceCode The source code object of the current context.
 * @returns {boolean} `true` if the element is empty.
 */
function isEmpty(node, sourceCode) {
  const start = node.startTag.range[1]
  const end = node.endTag.range[0]
  return sourceCode.text.slice(start, end).trim() === ''
}

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description:
        'require a line break before and after the contents of a singleline element',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/singleline-html-element-content-newline.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        type: 'object',
        properties: {
          ignoreWhenNoAttributes: {
            type: 'boolean'
          },
          ignoreWhenEmpty: {
            type: 'boolean'
          },
          ignores: {
            type: 'array',
            items: { type: 'string' },
            uniqueItems: true,
            additionalItems: false
          },
          externalIgnores: {
            type: 'array',
            items: { type: 'string' },
            uniqueItems: true,
            additionalItems: false
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      unexpectedAfterClosingBracket:
        'Expected 1 line break after opening tag (`<{{name}}>`), but no line breaks found.',
      unexpectedBeforeOpeningBracket:
        'Expected 1 line break before closing tag (`</{{name}}>`), but no line breaks found.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = parseOptions(context.options[0])
    const ignores = new Set([...options.ignores, ...options.externalIgnores])
    const ignoreWhenNoAttributes = options.ignoreWhenNoAttributes
    const ignoreWhenEmpty = options.ignoreWhenEmpty
    const sourceCode = context.getSourceCode()
    const template =
      sourceCode.parserServices.getTemplateBodyTokenStore &&
      sourceCode.parserServices.getTemplateBodyTokenStore()

    /** @type {VElement | null} */
    let inIgnoreElement = null

    /** @param {VElement} node */
    function isIgnoredElement(node) {
      return (
        ignores.has(node.name) ||
        ignores.has(casing.pascalCase(node.rawName)) ||
        ignores.has(casing.kebabCase(node.rawName))
      )
    }

    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VElement} node */
      VElement(node) {
        if (inIgnoreElement) {
          return
        }
        if (isIgnoredElement(node)) {
          // ignore element name
          inIgnoreElement = node
          return
        }
        if (node.startTag.selfClosing || !node.endTag) {
          // self closing
          return
        }

        const elem = /** @type {VElement & { endTag: VEndTag } } */ (node)

        if (!isSinglelineElement(elem)) {
          return
        }
        if (ignoreWhenNoAttributes && elem.startTag.attributes.length === 0) {
          return
        }

        /** @type {SourceCode.CursorWithCountOptions} */
        const getTokenOption = {
          includeComments: true,
          filter: (token) => token.type !== 'HTMLWhitespace'
        }
        if (
          ignoreWhenEmpty &&
          elem.children.length === 0 &&
          template.getFirstTokensBetween(
            elem.startTag,
            elem.endTag,
            getTokenOption
          ).length === 0
        ) {
          return
        }

        const contentFirst = /** @type {Token} */ (
          template.getTokenAfter(elem.startTag, getTokenOption)
        )
        const contentLast = /** @type {Token} */ (
          template.getTokenBefore(elem.endTag, getTokenOption)
        )

        context.report({
          node: template.getLastToken(elem.startTag),
          loc: {
            start: elem.startTag.loc.end,
            end: contentFirst.loc.start
          },
          messageId: 'unexpectedAfterClosingBracket',
          data: {
            name: elem.rawName
          },
          fix(fixer) {
            /** @type {Range} */
            const range = [elem.startTag.range[1], contentFirst.range[0]]
            return fixer.replaceTextRange(range, '\n')
          }
        })

        if (isEmpty(elem, sourceCode)) {
          return
        }

        context.report({
          node: template.getFirstToken(elem.endTag),
          loc: {
            start: contentLast.loc.end,
            end: elem.endTag.loc.start
          },
          messageId: 'unexpectedBeforeOpeningBracket',
          data: {
            name: elem.rawName
          },
          fix(fixer) {
            /** @type {Range} */
            const range = [contentLast.range[1], elem.endTag.range[0]]
            return fixer.replaceTextRange(range, '\n')
          }
        })
      },
      /** @param {VElement} node */
      'VElement:exit'(node) {
        if (inIgnoreElement === node) {
          inIgnoreElement = null
        }
      }
    })
  }
}