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

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

// https://github.com/vuejs/vue-next/blob/64e2f4643602c5980361e66674141e61ba60ef70/packages/compiler-core/src/parse.ts#L405
const SPECIAL_TEMPLATE_DIRECTIVES = new Set([
  'if',
  'else',
  'else-if',
  'for',
  'slot'
])

/**
 * @param {VAttribute | VDirective} attr
 */
function getKeyName(attr) {
  if (attr.directive) {
    if (attr.key.name.name !== 'bind') {
      // no v-bind
      return null
    }
    if (
      !attr.key.argument ||
      attr.key.argument.type === 'VExpressionContainer'
    ) {
      // unknown
      return null
    }
    return attr.key.argument.name
  }
  return attr.key.name
}

/**
 * @param {VAttribute | VDirective} attr
 */
function isFragmentTemplateAttribute(attr) {
  if (attr.directive) {
    const directiveName = attr.key.name.name
    if (SPECIAL_TEMPLATE_DIRECTIVES.has(directiveName)) {
      return true
    }
    if (directiveName === 'slot-scope') {
      // `slot-scope` is deprecated in Vue.js 2.6
      return true
    }
    if (directiveName === 'scope') {
      // `scope` is deprecated in Vue.js 2.5
      return true
    }
  }

  const keyName = getKeyName(attr)
  if (keyName === 'slot') {
    // `slot` is deprecated in Vue.js 2.6
    return true
  }

  return false
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'disallow useless attribute on `<template>`',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/no-useless-template-attributes.html'
    },
    fixable: null,
    schema: [],
    messages: {
      unexpectedAttr: 'Unexpected useless attribute on `<template>`.',
      unexpectedDir: 'Unexpected useless directive on `<template>`.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VStartTag} node */
      "VElement[name='template'][parent.type='VElement'] > VStartTag"(node) {
        if (!node.attributes.some(isFragmentTemplateAttribute)) {
          return
        }

        for (const attr of node.attributes) {
          if (isFragmentTemplateAttribute(attr)) {
            continue
          }
          const keyName = getKeyName(attr)
          if (keyName === 'key') {
            continue
          }
          context.report({
            node: attr,
            messageId: attr.directive ? 'unexpectedDir' : 'unexpectedAttr'
          })
        }
      }
    })
  }
}


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

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

const DOUBLE_QUOTES_RE = /"/gu
const SINGLE_QUOTES_RE = /'/gu

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

    /**
     * Report if the value expression is string literals
     * @param {VDirective} node the node to check
     */
    function verify(node) {
      const { value, key } = node
      if (!value || key.modifiers.length > 0) {
        return
      }
      const { expression } = value
      if (!expression) {
        return
      }
      /** @type {string} */
      let strValue
      /** @type {string} */
      let rawValue
      if (expression.type === 'Literal') {
        if (typeof expression.value !== 'string') {
          return
        }
        strValue = expression.value
        rawValue = sourceCode.getText(expression).slice(1, -1)
      } else if (expression.type === 'TemplateLiteral') {
        if (expression.expressions.length > 0) {
          return
        }
        strValue = expression.quasis[0].value.cooked
        rawValue = expression.quasis[0].value.raw
      } else {
        return
      }

      const tokenStore = sourceCode.parserServices.getTemplateBodyTokenStore()
      const hasComment = tokenStore
        .getTokens(value, { includeComments: true })
        .some((t) => t.type === 'Block' || t.type === 'Line')
      if (ignoreIncludesComment && hasComment) {
        return
      }

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

      context.report({
        node,
        messageId: 'unexpected',
        *fix(fixer) {
          if (hasComment || hasEscape) {
            // cannot fix
            return
          }
          const text = sourceCode.getText(value)
          const quoteChar = text[0]

          const shorthand = key.name.rawName === ':'
          /** @type {Range} */
          const keyDirectiveRange = [
            key.name.range[0],
            key.name.range[1] + (shorthand ? 0 : 1)
          ]

          yield fixer.removeRange(keyDirectiveRange)

          let attrValue
          if (quoteChar === '"') {
            attrValue = strValue.replace(DOUBLE_QUOTES_RE, '"')
            attrValue = quoteChar + attrValue + quoteChar
          } else if (quoteChar === "'") {
            attrValue = strValue.replace(SINGLE_QUOTES_RE, ''')
            attrValue = quoteChar + attrValue + quoteChar
          } else {
            attrValue = strValue
              .replace(DOUBLE_QUOTES_RE, '"')
              .replace(SINGLE_QUOTES_RE, ''')
          }
          yield fixer.replaceText(value, attrValue)
        }
      })
    }

    return utils.defineTemplateBodyVisitor(context, {
      "VAttribute[directive=true][key.name.name='bind'][key.argument!=null]":
        verify
    })
  }
}

license: mit


code:
/**
 * @author Yosuke Ota
 * This rule is based on X_V_FOR_TEMPLATE_KEY_PLACEMENT error of Vue 3.
 * see https://github.com/vuejs/vue-next/blob/b0d01e9db9ffe5781cce5a2d62c8552db3d615b0/packages/compiler-core/src/errors.ts#L70
 */
'use strict'

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

/**
 * Check whether the given attribute is using the variables which are defined by `v-for` directives.
 * @param {VDirective} vFor The attribute node of `v-for` to check.
 * @param {VDirective} vBindKey The attribute node of `v-bind:key` to check.
 * @returns {boolean} `true` if the node is using the variables which are defined by `v-for` directives.
 */
function isUsingIterationVar(vFor, vBindKey) {
  if (vBindKey.value == null) {
    return false
  }
  const references = vBindKey.value.references
  const variables = vFor.parent.parent.variables
  return references.some((reference) =>
    variables.some(
      (variable) =>
        variable.id.name === reference.id.name && variable.kind === 'v-for'
    )
  )
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description:
        'disallow key of `<template v-for>` placed on child elements',
      categories: ['vue3-essential'],
      url: 'https://eslint.vuejs.org/rules/no-v-for-template-key-on-child.html'
    },
    fixable: null,
    schema: [],
    messages: {
      vForTemplateKeyPlacement:
        '`<template v-for>` key should be placed on the `<template>` tag.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VDirective} node */
      "VElement[name='template'] > VStartTag > VAttribute[directive=true][key.name.name='for']"(
        node
      ) {
        const template = node.parent.parent
        const vBindKeyOnTemplate = utils.getDirective(template, 'bind', 'key')
        if (
          vBindKeyOnTemplate &&
          isUsingIterationVar(node, vBindKeyOnTemplate)
        ) {
          return
        }

        for (const child of template.children.filter(utils.isVElement)) {
          if (
            utils.hasDirective(child, 'if') ||
            utils.hasDirective(child, 'else-if') ||
            utils.hasDirective(child, 'else') ||
            utils.hasDirective(child, 'for')
          ) {
            continue
          }
          const vBindKeyOnChild = utils.getDirective(child, 'bind', 'key')
          if (vBindKeyOnChild && isUsingIterationVar(node, vBindKeyOnChild)) {
            context.report({
              node: vBindKeyOnChild,
              loc: vBindKeyOnChild.loc,
              messageId: 'vForTemplateKeyPlacement'
            })
          }
        }
      }
    })
  }
}


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

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'disallow `key` attribute on `<template v-for>`',
      categories: ['vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/no-v-for-template-key.html'
    },
    fixable: null,
    schema: [],
    messages: {
      disallow:
        "'<template v-for>' cannot be keyed. Place the key on real elements instead."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VDirective} node */
      "VElement[name='template'] > VStartTag > VAttribute[directive=true][key.name.name='for']"(
        node
      ) {
        const element = node.parent.parent
        const keyNode =
          utils.getAttribute(element, 'key') ||
          utils.getDirective(element, 'bind', 'key')
        if (keyNode) {
          context.report({
            node: keyNode,
            loc: keyNode.loc,
            messageId: 'disallow'
          })
        }
      }
    })
  }
}


code:
/**
 * @fileoverview Restrict or warn use of v-html to prevent XSS attack
 * @author Nathan Zeplowitz
 */
'use strict'
const utils = require('../utils')

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow use of v-html to prevent XSS attack',
      categories: ['vue3-recommended', 'vue2-recommended'],
      url: 'https://eslint.vuejs.org/rules/no-v-html.html'
    },
    fixable: null,
    schema: [],
    messages: {
      unexpected: "'v-html' directive can lead to XSS attack."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VDirective} node */
      "VAttribute[directive=true][key.name.name='html']"(node) {
        context.report({
          node,
          loc: node.loc,
          messageId: 'unexpected'
        })
      }
    })
  }
}


code:
/**
 * @author Przemyslaw Falowski (@przemkow)
 * @fileoverview This rule checks whether v-model used on custom component do not have an argument
 */
'use strict'

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description:
        'disallow adding an argument to `v-model` used in custom component',
      categories: ['vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/no-v-model-argument.html'
    },
    fixable: null,
    schema: [],
    messages: {
      vModelRequireNoArgument: "'v-model' directives require no argument."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VDirective} node */
      "VAttribute[directive=true][key.name.name='model']"(node) {
        const element = node.parent.parent

        if (node.key.argument && utils.isCustomComponent(element)) {
          context.report({
            node,
            loc: node.loc,
            messageId: 'vModelRequireNoArgument'
          })
        }
      }
    })
  }
}


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

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'disallow v-text / v-html on component',
      categories: ['vue2-essential', 'vue3-essential'],
      url: 'https://eslint.vuejs.org/rules/no-v-text-v-html-on-component.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          allow: {
            type: 'array',
            items: {
              type: 'string'
            },
            uniqueItems: true
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      disallow:
        "Using {{directiveName}} on component may break component's content."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || {}
    /** @type {Set<string>} */
    const allow = new Set(options.allow)

    /**
     * Check whether the given node is an allowed component or not.
     * @param {VElement} node The start tag node to check.
     * @returns {boolean} `true` if the node is an allowed component.
     */
    function isAllowedComponent(node) {
      const componentName = node.rawName
      return (
        allow.has(componentName) ||
        allow.has(casing.pascalCase(componentName)) ||
        allow.has(casing.kebabCase(componentName))
      )
    }

    /**
     * Verify for v-text and v-html directive
     * @param {VDirective} node
     */
    function verify(node) {
      const element = node.parent.parent
      if (utils.isCustomComponent(element) && !isAllowedComponent(element)) {
        context.report({
          node,
          loc: node.loc,
          messageId: 'disallow',
          data: {
            directiveName: `v-${node.key.name.name}`
          }
        })
      }
    }

    return utils.defineTemplateBodyVisitor(context, {
      "VAttribute[directive=true][key.name.name='text']": verify,
      "VAttribute[directive=true][key.name.name='html']": verify
    })
  }
}


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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow use of v-text',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-v-text.html'
    },
    fixable: null,
    schema: [],
    messages: {
      unexpected: "Don't use 'v-text'."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VDirective} node */
      "VAttribute[directive=true][key.name.name='text']"(node) {
        context.report({
          node,
          loc: node.loc,
          messageId: 'unexpected'
        })
      }
    })
  }
}


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

/**
 * @param {CallExpression | ChainExpression} node
 * @returns {boolean}
 */
function isMaybeUsedStopHandle(node) {
  const parent = node.parent
  if (parent) {
    if (parent.type === 'VariableDeclarator') {
      // var foo = watch()
      return true
    }
    if (parent.type === 'AssignmentExpression') {
      // foo = watch()
      return true
    }
    if (parent.type === 'CallExpression') {
      // foo(watch())
      return true
    }
    if (parent.type === 'Property') {
      // {foo: watch()}
      return true
    }
    if (parent.type === 'ArrayExpression') {
      // [watch()]
      return true
    }
    if (parent.type === 'ChainExpression') {
      return isMaybeUsedStopHandle(parent)
    }
  }
  return false
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow asynchronously registered `watch`',
      categories: ['vue3-essential'],
      url: 'https://eslint.vuejs.org/rules/no-watch-after-await.html'
    },
    fixable: null,
    schema: [],
    messages: {
      forbidden: '`watch` is forbidden after an `await` expression.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const watchCallNodes = new Set()
    /**
     * @typedef {object} SetupScopeData
     * @property {boolean} afterAwait
     * @property {[number,number]} range
     */
    /** @type {Map<FunctionExpression | ArrowFunctionExpression | FunctionDeclaration, SetupScopeData>} */
    const setupScopes = new Map()

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

    return utils.compositingVisitors(
      {
        /** @param {Program} program */
        Program(program) {
          const tracker = new ReferenceTracker(utils.getScope(context, program))

          for (const { node } of utils.iterateReferencesTraceMap(tracker, {
            watch: {
              [ReferenceTracker.CALL]: true
            },
            watchEffect: {
              [ReferenceTracker.CALL]: true
            }
          })) {
            watchCallNodes.add(node)
          }
        }
      },
      utils.defineVueVisitor(context, {
        onSetupFunctionEnter(node) {
          setupScopes.set(node, {
            afterAwait: false,
            range: node.range
          })
        },
        /** @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} node */
        ':function'(node) {
          scopeStack = {
            upper: scopeStack,
            scopeNode: node
          }
        },
        ':function:exit'() {
          scopeStack = scopeStack && scopeStack.upper
        },
        /** @param {AwaitExpression} node */
        AwaitExpression(node) {
          if (!scopeStack) {
            return
          }
          const setupScope = setupScopes.get(scopeStack.scopeNode)
          if (!setupScope || !utils.inRange(setupScope.range, node)) {
            return
          }
          setupScope.afterAwait = true
        },
        /** @param {CallExpression} node */
        CallExpression(node) {
          if (!scopeStack) {
            return
          }
          const setupScope = setupScopes.get(scopeStack.scopeNode)
          if (
            !setupScope ||
            !setupScope.afterAwait ||
            !utils.inRange(setupScope.range, node)
          ) {
            return
          }

          if (watchCallNodes.has(node) && !isMaybeUsedStopHandle(node)) {
            context.report({
              node,
              messageId: 'forbidden'
            })
          }
        },
        onSetupFunctionExit(node) {
          setupScopes.delete(node)
        }
      })
    )
  }
}


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

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

// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapStylisticOrCoreRule('object-curly-newline', {
  skipDynamicArguments: true
})


code:
/**
 * @author Toru Nagashima
 */
'use strict'

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

// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapStylisticOrCoreRule('object-curly-spacing', {
  skipDynamicArguments: true
})


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

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

// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapStylisticOrCoreRule('object-property-newline', {
  skipDynamicArguments: true
})


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

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

// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapCoreRule('object-shorthand', {
  skipDynamicArguments: true
})


code:
/**
 * @fileoverview enforce that each component should be in its own file
 * @author Armano
 */
'use strict'
const utils = require('../utils')
const { getVueComponentDefinitionType } = require('../utils')

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce that each component should be in its own file',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/one-component-per-file.html'
    },
    fixable: null,
    schema: [],
    messages: {
      toManyComponents: 'There is more than one component in this file.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {ObjectExpression[]} */
    const components = []

    return Object.assign(
      {},
      utils.executeOnVueComponent(context, (node, type) => {
        if (type === 'definition') {
          const defType = getVueComponentDefinitionType(node)
          if (defType === 'mixin') {
            return
          }
        }
        components.push(node)
      }),
      {
        'Program:exit'() {
          if (components.length > 1) {
            for (const node of components) {
              context.report({
                node,
                messageId: 'toManyComponents'
              })
            }
          }
        }
      }
    )
  }
}


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

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

// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapStylisticOrCoreRule('operator-linebreak')


code:
/**
 * @fileoverview Keep order of properties in components
 * @author Michał Sajnóg
 */
'use strict'

const utils = require('../utils')
const traverseNodes = require('vue-eslint-parser').AST.traverseNodes

/**
 * @typedef {import('eslint-visitor-keys').VisitorKeys} VisitorKeys
 */

const defaultOrder = [
  // Side Effects (triggers effects outside the component)
  'el',

  // Global Awareness (requires knowledge beyond the component)
  'name',
  'key', // for Nuxt
  'parent',

  // Component Type (changes the type of the component)
  'functional',

  // Template Modifiers (changes the way templates are compiled)
  ['delimiters', 'comments'],

  // Template Dependencies (assets used in the template)
  ['components', 'directives', 'filters'],

  // Composition (merges properties into the options)
  'extends',
  'mixins',
  ['provide', 'inject'], // for Vue.js 2.2.0+

  // Page Options (component rendered as a router page)
  'ROUTER_GUARDS', // for Vue Router
  'layout', // for Nuxt
  'middleware', // for Nuxt
  'validate', // for Nuxt
  'scrollToTop', // for Nuxt
  'transition', // for Nuxt
  'loading', // for Nuxt

  // Interface (the interface to the component)
  'inheritAttrs',
  'model',
  ['props', 'propsData'],
  'emits', // for Vue.js 3.x

  // Note:
  // The `setup` option is included in the "Composition" category,
  // but the behavior of the `setup` option requires the definition of "Interface",
  // so we prefer to put the `setup` option after the "Interface".
  'setup', // for Vue 3.x

  // Local State (local reactive properties)
  'asyncData', // for Nuxt
  'data',
  'fetch', // for Nuxt
  'head', // for Nuxt
  'computed',

  // Events (callbacks triggered by reactive events)
  'watch',
  'watchQuery', // for Nuxt
  'LIFECYCLE_HOOKS',

  // Non-Reactive Properties (instance properties independent of the reactivity system)
  'methods',

  // Rendering (the declarative description of the component output)
  ['template', 'render'],
  'renderError'
]

/** @type { { [key: string]: string[] } } */
const groups = {
  LIFECYCLE_HOOKS: [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'activated',
    'deactivated',
    'beforeUnmount', // for Vue.js 3.x
    'unmounted', // for Vue.js 3.x
    'beforeDestroy',
    'destroyed',
    'renderTracked', // for Vue.js 3.x
    'renderTriggered', // for Vue.js 3.x
    'errorCaptured' // for Vue.js 2.5.0+
  ],
  ROUTER_GUARDS: ['beforeRouteEnter', 'beforeRouteUpdate', 'beforeRouteLeave']
}

/**
 * @param {(string | string[])[]} order
 */
function getOrderMap(order) {
  /** @type {Map<string, number>} */
  const orderMap = new Map()

  for (const [i, property] of order.entries()) {
    if (Array.isArray(property)) {
      for (const p of property) {
        orderMap.set(p, i)
      }
    } else {
      orderMap.set(property, i)
    }
  }

  return orderMap
}

/**
 * @param {Token} node
 */
function isComma(node) {
  return node.type === 'Punctuator' && node.value === ','
}

const ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '**' /* es2016 */]
const BITWISE_OPERATORS = ['&', '|', '^', '~', '<<', '>>', '>>>']
const COMPARISON_OPERATORS = ['==', '!=', '===', '!==', '>', '>=', '<', '<=']
const RELATIONAL_OPERATORS = ['in', 'instanceof']
const ALL_BINARY_OPERATORS = new Set([
  ...ARITHMETIC_OPERATORS,
  ...BITWISE_OPERATORS,
  ...COMPARISON_OPERATORS,
  ...RELATIONAL_OPERATORS
])
const LOGICAL_OPERATORS = new Set(['&&', '||', '??' /* es2020 */])

/**
 * Result `true` if the node is sure that there are no side effects
 *
 * Currently known side effects types
 *
 * node.type === 'CallExpression'
 * node.type === 'NewExpression'
 * node.type === 'UpdateExpression'
 * node.type === 'AssignmentExpression'
 * node.type === 'TaggedTemplateExpression'
 * node.type === 'UnaryExpression' && node.operator === 'delete'
 *
 * @param  {ASTNode} node target node
 * @param  {VisitorKeys} visitorKeys sourceCode.visitorKey
 * @returns {boolean} no side effects
 */
function isNotSideEffectsNode(node, visitorKeys) {
  let result = true
  /** @type {ASTNode | null} */
  let skipNode = null
  traverseNodes(node, {
    visitorKeys,
    /** @param {ASTNode} node */
    enterNode(node) {
      if (!result || skipNode) {
        return
      }

      if (
        // no side effects node
        node.type === 'FunctionExpression' ||
        node.type === 'Identifier' ||
        node.type === 'Literal' ||
        // es2015
        node.type === 'ArrowFunctionExpression' ||
        node.type === 'TemplateElement' ||
        // typescript
        node.type === 'TSAsExpression'
      ) {
        skipNode = node
      } else if (
        node.type !== 'Property' &&
        node.type !== 'ObjectExpression' &&
        node.type !== 'ArrayExpression' &&
        (node.type !== 'UnaryExpression' ||
          !['!', '~', '+', '-', 'typeof'].includes(node.operator)) &&
        (node.type !== 'BinaryExpression' ||
          !ALL_BINARY_OPERATORS.has(node.operator)) &&
        (node.type !== 'LogicalExpression' ||
          !LOGICAL_OPERATORS.has(node.operator)) &&
        node.type !== 'MemberExpression' &&
        node.type !== 'ConditionalExpression' &&
        // es2015
        node.type !== 'SpreadElement' &&
        node.type !== 'TemplateLiteral' &&
        // es2020
        node.type !== 'ChainExpression'
      ) {
        // Can not be sure that a node has no side effects
        result = false
      }
    },
    /** @param {ASTNode} node */
    leaveNode(node) {
      if (skipNode === node) {
        skipNode = null
      }
    }
  })

  return result
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce order of properties in components',
      categories: ['vue3-recommended', 'vue2-recommended'],
      url: 'https://eslint.vuejs.org/rules/order-in-components.html'
    },
    fixable: 'code', // null or "code" or "whitespace"
    hasSuggestions: true,
    schema: [
      {
        type: 'object',
        properties: {
          order: {
            type: 'array'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      order:
        'The "{{name}}" property should be above the "{{firstUnorderedPropertyName}}" property on line {{line}}.',
      reorderWithSideEffects:
        'Manually move "{{name}}" property above "{{firstUnorderedPropertyName}}" property on line {{line}} (might break side effects).'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || {}
    /** @type {(string|string[])[]} */
    const order = options.order || defaultOrder
    /** @type {(string|string[])[]} */
    const extendedOrder = order.map(
      (property) =>
        (typeof property === 'string' && groups[property]) || property
    )
    const orderMap = getOrderMap(extendedOrder)
    const sourceCode = context.getSourceCode()

    /**
     * @param {string} name
     */
    function getOrderPosition(name) {
      const num = orderMap.get(name)
      return num == null ? -1 : num
    }

    /**
     * @param {RuleFixer} fixer
     * @param {Property} propertyNode
     * @param {Property} unorderedPropertyNode
     */
    function* handleFix(fixer, propertyNode, unorderedPropertyNode) {
      const afterComma = sourceCode.getTokenAfter(propertyNode)
      const hasAfterComma = isComma(afterComma)

      const beforeComma = sourceCode.getTokenBefore(propertyNode)
      const codeStart = beforeComma.range[1] // to include comments
      const codeEnd = hasAfterComma
        ? afterComma.range[1]
        : propertyNode.range[1]

      const removeStart = hasAfterComma ? codeStart : beforeComma.range[0]
      yield fixer.removeRange([removeStart, codeEnd])

      const propertyCode =
        sourceCode.text.slice(codeStart, codeEnd) + (hasAfterComma ? '' : ',')
      const insertTarget = sourceCode.getTokenBefore(unorderedPropertyNode)

      yield fixer.insertTextAfter(insertTarget, propertyCode)
    }

    /**
     * @param {(Property | SpreadElement)[]} propertiesNodes
     */
    function checkOrder(propertiesNodes) {
      const properties = propertiesNodes
        .filter(utils.isProperty)
        .map((property) => ({
          node: property,
          name:
            utils.getStaticPropertyName(property) ||
            (property.key.type === 'Identifier' && property.key.name) ||
            ''
        }))

      for (const [i, property] of properties.entries()) {
        const orderPos = getOrderPosition(property.name)
        if (orderPos < 0) {
          continue
        }
        const propertiesAbove = properties.slice(0, i)
        const unorderedProperties = propertiesAbove
          .filter(
            (p) => getOrderPosition(p.name) > getOrderPosition(property.name)
          )
          .sort((p1, p2) =>
            getOrderPosition(p1.name) > getOrderPosition(p2.name) ? 1 : -1
          )

        const firstUnorderedProperty = unorderedProperties[0]

        if (firstUnorderedProperty) {
          const line = firstUnorderedProperty.node.loc.start.line
          const propertyNode = property.node
          const firstUnorderedPropertyNode = firstUnorderedProperty.node
          const hasSideEffectsPossibility = propertiesNodes
            .slice(
              propertiesNodes.indexOf(firstUnorderedPropertyNode),
              propertiesNodes.indexOf(propertyNode) + 1
            )
            .some(
              (property) =>
                !isNotSideEffectsNode(property, sourceCode.visitorKeys)
            )

          context.report({
            node: property.node,
            messageId: 'order',
            data: {
              name: property.name,
              firstUnorderedPropertyName: firstUnorderedProperty.name,
              line
            },
            fix: hasSideEffectsPossibility
              ? undefined
              : (fixer) =>
                  handleFix(fixer, propertyNode, firstUnorderedPropertyNode),
            suggest: hasSideEffectsPossibility
              ? [
                  {
                    messageId: 'reorderWithSideEffects',
                    data: {
                      name: property.name,
                      firstUnorderedPropertyName: firstUnorderedProperty.name,
                      line
                    },
                    fix: (fixer) =>
                      handleFix(fixer, propertyNode, firstUnorderedPropertyNode)
                  }
                ]
              : undefined
          })
        }
      }
    }

    return utils.compositingVisitors(
      utils.executeOnVue(context, (obj) => {
        checkOrder(obj.properties)
      }),
      utils.defineScriptSetupVisitor(context, {
        onDefineOptionsEnter(node) {
          if (node.arguments.length === 0) return
          const define = node.arguments[0]
          if (define.type !== 'ObjectExpression') return
          checkOrder(define.properties)
        }
      })
    )
  }
}


code:
/**
 * @fileoverview Require or disallow padding lines between blocks
 * @author Yosuke Ota
 */
'use strict'
const utils = require('../utils')

/**
 * Split the source code into multiple lines based on the line delimiters.
 * @param {string} text Source code as a string.
 * @returns {string[]} Array of source code lines.
 */
function splitLines(text) {
  return text.split(/\r\n|[\r\n\u2028\u2029]/gu)
}

/**
 * Check and report blocks for `never` configuration.
 * This autofix removes blank lines between the given 2 blocks.
 * @param {RuleContext} context The rule context to report.
 * @param {VElement} prevBlock The previous block to check.
 * @param {VElement} nextBlock The next block to check.
 * @param {Token[]} betweenTokens The array of tokens between blocks.
 * @returns {void}
 * @private
 */
function verifyForNever(context, prevBlock, nextBlock, betweenTokens) {
  if (prevBlock.loc.end.line === nextBlock.loc.start.line) {
    // same line
    return
  }
  const tokenOrNodes = [...betweenTokens, nextBlock]
  /** @type {ASTNode | Token} */
  let prev = prevBlock
  /** @type {[ASTNode | Token, ASTNode | Token][]} */
  const paddingLines = []
  for (const tokenOrNode of tokenOrNodes) {
    const numOfLineBreaks = tokenOrNode.loc.start.line - prev.loc.end.line
    if (numOfLineBreaks > 1) {
      paddingLines.push([prev, tokenOrNode])
    }
    prev = tokenOrNode
  }
  if (paddingLines.length === 0) {
    return
  }

  context.report({
    node: nextBlock,
    messageId: 'never',
    *fix(fixer) {
      for (const [prevToken, nextToken] of paddingLines) {
        const start = prevToken.range[1]
        const end = nextToken.range[0]
        const paddingText = context.getSourceCode().text.slice(start, end)
        const lastSpaces = splitLines(paddingText).pop()
        yield fixer.replaceTextRange([start, end], `\n${lastSpaces}`)
      }
    }
  })
}

/**
 * Check and report blocks for `always` configuration.
 * This autofix inserts a blank line between the given 2 blocks.
 * @param {RuleContext} context The rule context to report.
 * @param {VElement} prevBlock The previous block to check.
 * @param {VElement} nextBlock The next block to check.
 * @param {Token[]} betweenTokens The array of tokens between blocks.
 * @returns {void}
 * @private
 */
function verifyForAlways(context, prevBlock, nextBlock, betweenTokens) {
  const tokenOrNodes = [...betweenTokens, nextBlock]
  /** @type {ASTNode | Token} */
  let prev = prevBlock
  /** @type {ASTNode | Token | undefined} */
  let linebreak
  for (const tokenOrNode of tokenOrNodes) {
    const numOfLineBreaks = tokenOrNode.loc.start.line - prev.loc.end.line
    if (numOfLineBreaks > 1) {
      // Already padded.
      return
    }
    if (!linebreak && numOfLineBreaks > 0) {
      linebreak = prev
    }
    prev = tokenOrNode
  }

  context.report({
    node: nextBlock,
    messageId: 'always',
    fix(fixer) {
      if (linebreak) {
        return fixer.insertTextAfter(linebreak, '\n')
      }
      return fixer.insertTextAfter(prevBlock, '\n\n')
    }
  })
}

/**
 * Types of blank lines.
 * `never` and `always` are defined.
 * Those have `verify` method to check and report statements.
 * @private
 */
const PaddingTypes = {
  never: { verify: verifyForNever },
  always: { verify: verifyForAlways }
}

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description: 'require or disallow padding lines between blocks',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/padding-line-between-blocks.html'
    },
    // eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
    fixable: 'whitespace',
    schema: [
      {
        enum: Object.keys(PaddingTypes)
      }
    ],
    messages: {
      never: 'Unexpected blank line before this block.',
      always: 'Expected blank line before this block.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()
    if (!sourceCode.parserServices.getDocumentFragment) {
      return {}
    }
    const df = sourceCode.parserServices.getDocumentFragment()
    if (!df) {
      return {}
    }
    const documentFragment = df

    /** @type {'always' | 'never'} */
    const option = context.options[0] || 'always'
    const paddingType = PaddingTypes[option]

    /** @type {Token[]} */
    let tokens
    /**
     * @returns {VElement[]}
     */
    function getTopLevelHTMLElements() {
      return documentFragment.children.filter(utils.isVElement)
    }

    /**
     * @param {VElement} prev
     * @param {VElement} next
     */
    function getTokenAndCommentsBetween(prev, next) {
      // When there is no <template>, tokenStore.getTokensBetween cannot be used.
      if (!tokens) {
        tokens = [
          ...documentFragment.tokens.filter(
            (token) => token.type !== 'HTMLWhitespace'
          ),
          ...documentFragment.comments
        ].sort((a, b) => {
          if (a.range[0] > b.range[0]) return 1
          return a.range[0] < b.range[0] ? -1 : 0
        })
      }

      let token = tokens.shift()

      const results = []
      while (token) {
        if (prev.range[1] <= token.range[0]) {
          if (next.range[0] <= token.range[0]) {
            tokens.unshift(token)
            break
          } else {
            results.push(token)
          }
        }
        token = tokens.shift()
      }

      return results
    }

    return utils.defineTemplateBodyVisitor(
      context,
      {},
      {
        /** @param {Program} node */
        Program(node) {
          if (utils.hasInvalidEOF(node)) {
            return
          }
          const elements = [...getTopLevelHTMLElements()]

          let prev = elements.shift()
          for (const element of elements) {
            if (!prev) {
              return
            }
            const betweenTokens = getTokenAndCommentsBetween(prev, element)
            paddingType.verify(context, prev, element, betweenTokens)
            prev = element
          }
        }
      }
    )
  }
}


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

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

/**
 * Split the source code into multiple lines based on the line delimiters.
 * Copied from padding-line-between-blocks
 * @param {string} text Source code as a string.
 * @returns {string[]} Array of source code lines.
 */
function splitLines(text) {
  return text.split(/\r\n|[\r\n\u2028\u2029]/gu)
}

/**
 * @param {RuleContext} context
 * @param {VElement} tag
 * @param {VElement} sibling
 * @param {number} lineDifference
 */
function insertNewLine(context, tag, sibling, lineDifference) {
  const endTag = tag.endTag || tag.startTag

  if (lineDifference === 1) {
    context.report({
      messageId: 'always',
      loc: sibling.loc,
      // @ts-ignore
      fix(fixer) {
        return fixer.insertTextAfter(tag, '\n')
      }
    })
  } else if (lineDifference === 0) {
    context.report({
      messageId: 'always',
      loc: sibling.loc,
      // @ts-ignore
      fix(fixer) {
        const lastSpaces = /** @type {RegExpExecArray} */ (
          /^\s*/.exec(context.getSourceCode().lines[endTag.loc.start.line - 1])
        )[0]

        return fixer.insertTextAfter(endTag, `\n\n${lastSpaces}`)
      }
    })
  }
}

/**
 * @param {RuleContext} context
 * @param {VEndTag | VStartTag} endTag
 * @param {VElement} sibling
 * @param {number} lineDifference
 */
function removeExcessLines(context, endTag, sibling, lineDifference) {
  if (lineDifference > 1) {
    let hasOnlyTextBetween = true
    for (
      let i = endTag.loc.start.line;
      i < sibling.loc.start.line - 1 && hasOnlyTextBetween;
      i++
    ) {
      hasOnlyTextBetween = !/^\s*$/.test(context.getSourceCode().lines[i])
    }
    if (!hasOnlyTextBetween) {
      context.report({
        messageId: 'never',
        loc: sibling.loc,
        // @ts-ignore
        fix(fixer) {
          const start = endTag.range[1]
          const end = sibling.range[0]
          const paddingText = context.getSourceCode().text.slice(start, end)
          const textBetween = splitLines(paddingText)
          let newTextBetween = `\n${textBetween.pop()}`
          for (let i = textBetween.length - 1; i >= 0; i--) {
            if (!/^\s*$/.test(textBetween[i])) {
              newTextBetween = `${i === 0 ? '' : '\n'}${
                textBetween[i]
              }${newTextBetween}`
            }
          }
          return fixer.replaceTextRange([start, end], `${newTextBetween}`)
        }
      })
    }
  }
}

/**
 * @param {RuleContext} context
 */
function checkNewline(context) {
  /** @type {Array<{blankLine: "always" | "never" | "consistent", prev: string, next: string}>} */
  const configureList = context.options[0] || [
    { blankLine: 'always', prev: '*', next: '*' }
  ]

  const reverseConfigureList = [...configureList].reverse()

  /**
   * It has the style of the first `blankLine="consistent"`.
   * @type {Map<VElement, "always" | "never">}
   */
  const firstConsistentBlankLines = new Map()

  /**
   * @param {VElement} block
   */
  return (block) => {
    if (!block.parent.parent) {
      return
    }

    const endTag = block.endTag || block.startTag
    const lowerSiblings = block.parent.children
      .filter(
        (element) =>
          element.type === 'VElement' && element.range !== block.range
      )
      .filter((sibling) => sibling.range[0] - endTag.range[1] >= 0)

    if (lowerSiblings.length === 0) {
      return
    }

    const closestSibling = /** @type {VElement} */ (lowerSiblings[0])

    const configure = reverseConfigureList.find(
      (configure) =>
        (configure.prev === '*' || block.name === configure.prev) &&
        (configure.next === '*' || closestSibling.name === configure.next)
    )

    if (!configure) {
      return
    }
    const lineDifference = closestSibling.loc.start.line - endTag.loc.end.line

    let blankLine = configure.blankLine
    if (blankLine === 'consistent') {
      const firstConsistentBlankLine = firstConsistentBlankLines.get(
        block.parent
      )
      if (firstConsistentBlankLine == null) {
        firstConsistentBlankLines.set(
          block.parent,
          lineDifference > 1 ? 'always' : 'never'
        )
        return
      }
      blankLine = firstConsistentBlankLine
    }

    if (blankLine === 'always') {
      insertNewLine(context, block, closestSibling, lineDifference)
    } else {
      removeExcessLines(context, endTag, closestSibling, lineDifference)
    }
  }
}

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description:
        'require or disallow newlines between sibling tags in template',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/padding-line-between-tags.html'
    },
    // eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
    fixable: 'whitespace',
    schema: [
      {
        type: 'array',
        items: {
          type: 'object',
          properties: {
            blankLine: { enum: ['always', 'never', 'consistent'] },
            prev: { type: 'string' },
            next: { type: 'string' }
          },
          additionalProperties: false,
          required: ['blankLine', 'prev', 'next']
        }
      }
    ],
    messages: {
      never: 'Unexpected blank line before this tag.',
      always: 'Expected blank line before this tag.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return utils.defineTemplateBodyVisitor(context, {
      VElement: checkNewline(context)
    })
  }
}


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

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

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

const AvailablePaddingOptions = {
  Never: 'never',
  Always: 'always',
  Ignore: 'ignore'
}
const OptionKeys = {
  BetweenOptions: 'betweenOptions',
  WithinOption: 'withinOption',
  BetweenItems: 'betweenItems',
  WithinEach: 'withinEach',
  GroupSingleLineProperties: 'groupSingleLineProperties'
}

/**
 * @param {Token} node
 */
function isComma(node) {
  return node.type === 'Punctuator' && node.value === ','
}

/**
 * @typedef {Exclude<ComponentProp | ComponentEmit, {type:'infer-type'}> & { node: {type: 'Property' | 'SpreadElement'} }} ValidComponentPropOrEmit
 */
/**
 * @template {ComponentProp | ComponentEmit} T
 * @param {T} propOrEmit
 * @returns {propOrEmit is ValidComponentPropOrEmit & T}
 */
function isValidProperties(propOrEmit) {
  return Boolean(
    propOrEmit.type !== 'infer-type' &&
      propOrEmit.node &&
      ['Property', 'SpreadElement'].includes(propOrEmit.node.type)
  )
}

/**
 * Split the source code into multiple lines based on the line delimiters.
 * @param {string} text Source code as a string.
 * @returns {string[]} Array of source code lines.
 */
function splitLines(text) {
  return text.split(/\r\n|[\r\n\u2028\u2029]/gu)
}

/**
 * @param {any} initialOption
 * @param {string} optionKey
 * @private
 * */
function parseOption(initialOption, optionKey) {
  return typeof initialOption === 'string'
    ? initialOption
    : initialOption[optionKey]
}

/**
 * @param {any} initialOption
 * @param {string} optionKey
 * @private
 * */
function parseBooleanOption(initialOption, optionKey) {
  if (typeof initialOption === 'string') {
    if (initialOption === AvailablePaddingOptions.Always) return true
    if (initialOption === AvailablePaddingOptions.Never) return false
  }
  return initialOption[optionKey]
}

/**
 * @param {(Property | SpreadElement)} currentProperty
 * @param {(Property | SpreadElement)} nextProperty
 * @param {boolean} option
 * @returns {boolean}
 * @private
 * */
function needGroupSingleLineProperties(currentProperty, nextProperty, option) {
  const isSingleCurrentProperty =
    currentProperty.loc.start.line === currentProperty.loc.end.line
  const isSingleNextProperty =
    nextProperty.loc.start.line === nextProperty.loc.end.line

  return isSingleCurrentProperty && isSingleNextProperty && option
}

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description: 'require or disallow padding lines in component definition',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/padding-lines-in-component-definition.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        oneOf: [
          {
            enum: [
              AvailablePaddingOptions.Always,
              AvailablePaddingOptions.Never
            ]
          },
          {
            type: 'object',
            additionalProperties: false,
            properties: {
              [OptionKeys.BetweenOptions]: {
                enum: Object.values(AvailablePaddingOptions)
              },
              [OptionKeys.WithinOption]: {
                oneOf: [
                  {
                    enum: Object.values(AvailablePaddingOptions)
                  },
                  {
                    type: 'object',
                    patternProperties: {
                      '^[a-zA-Z]*$': {
                        oneOf: [
                          {
                            enum: Object.values(AvailablePaddingOptions)
                          },
                          {
                            type: 'object',
                            properties: {
                              [OptionKeys.BetweenItems]: {
                                enum: Object.values(AvailablePaddingOptions)
                              },
                              [OptionKeys.WithinEach]: {
                                enum: Object.values(AvailablePaddingOptions)
                              }
                            },
                            additionalProperties: false
                          }
                        ]
                      }
                    },
                    minProperties: 1,
                    additionalProperties: false
                  }
                ]
              },
              [OptionKeys.GroupSingleLineProperties]: {
                type: 'boolean'
              }
            }
          }
        ]
      }
    ],
    messages: {
      never: 'Unexpected blank line before this definition.',
      always: 'Expected blank line before this definition.',
      groupSingleLineProperties:
        'Unexpected blank line between single line properties.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || AvailablePaddingOptions.Always
    const sourceCode = context.getSourceCode()

    /**
     * @param {(Property | SpreadElement)} currentProperty
     * @param {(Property | SpreadElement | Token)} nextProperty
     * @param {RuleFixer} fixer
     * */
    function replaceLines(currentProperty, nextProperty, fixer) {
      const commaToken = sourceCode.getTokenAfter(currentProperty, isComma)

      const start = commaToken ? commaToken.range[1] : currentProperty.range[1]
      const end = nextProperty.range[0]

      const paddingText = sourceCode.text.slice(start, end)
      const newText = `\n${splitLines(paddingText).pop()}`

      return fixer.replaceTextRange([start, end], newText)
    }

    /**
     * @param {(Property | SpreadElement)} currentProperty
     * @param {(Property | SpreadElement | Token)} nextProperty
     * @param {RuleFixer} fixer
     * @param {number} betweenLinesRange
     * */
    function insertLines(
      currentProperty,
      nextProperty,
      fixer,
      betweenLinesRange
    ) {
      const commaToken = sourceCode.getTokenAfter(currentProperty, isComma)

      const lineBeforeNextProperty =
        sourceCode.lines[nextProperty.loc.start.line - 1]
      const lastSpaces = /** @type {RegExpExecArray} */ (
        /^\s*/.exec(lineBeforeNextProperty)
      )[0]

      const newText = betweenLinesRange === 0 ? `\n\n${lastSpaces}` : '\n'
      return fixer.insertTextAfter(commaToken || currentProperty, newText)
    }

    /**
     * @param {(Property | SpreadElement)[]} properties
     * @param {any} option
     * @param {any} nextOption
     * */
    function verify(properties, option, nextOption) {
      const groupSingleLineProperties = parseBooleanOption(
        options,
        OptionKeys.GroupSingleLineProperties
      )

      for (const [i, currentProperty] of properties.entries()) {
        const nextProperty = properties[i + 1]

        if (nextProperty && option !== AvailablePaddingOptions.Ignore) {
          const tokenBeforeNext = sourceCode.getTokenBefore(nextProperty, {
            includeComments: true
          })
          const isCommentBefore = isCommentToken(tokenBeforeNext)
          const reportNode = isCommentBefore ? tokenBeforeNext : nextProperty

          const betweenLinesRange =
            reportNode.loc.start.line - currentProperty.loc.end.line

          if (
            needGroupSingleLineProperties(
              currentProperty,
              nextProperty,
              groupSingleLineProperties
            )
          ) {
            if (betweenLinesRange > 1) {
              context.report({
                node: reportNode,
                messageId: 'groupSingleLineProperties',
                loc: reportNode.loc,
                fix(fixer) {
                  return replaceLines(currentProperty, reportNode, fixer)
                }
              })
            }
            continue
          }

          if (
            betweenLinesRange <= 1 &&
            option === AvailablePaddingOptions.Always
          ) {
            context.report({
              node: reportNode,
              messageId: 'always',
              loc: reportNode.loc,
              fix(fixer) {
                return insertLines(
                  currentProperty,
                  reportNode,
                  fixer,
                  betweenLinesRange
                )
              }
            })
          } else if (
            betweenLinesRange > 1 &&
            option === AvailablePaddingOptions.Never
          ) {
            context.report({
              node: reportNode,
              messageId: 'never',
              loc: reportNode.loc,
              fix(fixer) {
                return replaceLines(currentProperty, reportNode, fixer)
              }
            })
          }
        }

        if (!nextOption) return

        const name = /** @type {GroupName | null} */ (
          currentProperty.type === 'Property' &&
            utils.getStaticPropertyName(currentProperty)
        )
        if (!name) continue

        const propertyOption = parseOption(nextOption, name)
        if (!propertyOption) continue

        const nestedProperties =
          currentProperty.type === 'Property' &&
          currentProperty.value.type === 'ObjectExpression' &&
          currentProperty.value.properties
        if (!nestedProperties) continue

        verify(
          nestedProperties,
          parseOption(propertyOption, OptionKeys.BetweenItems),
          parseOption(propertyOption, OptionKeys.WithinEach)
        )
      }
    }

    return utils.compositingVisitors(
      utils.defineVueVisitor(context, {
        onVueObjectEnter(node) {
          verify(
            node.properties,
            parseOption(options, OptionKeys.BetweenOptions),
            parseOption(options, OptionKeys.WithinOption)
          )
        }
      }),
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(_, props) {
          const propNodes = props
            .filter(isValidProperties)
            .map((prop) => prop.node)

          const withinOption = parseOption(options, OptionKeys.WithinOption)
          const propsOption = withinOption && parseOption(withinOption, 'props')
          if (!propsOption) return

          verify(
            propNodes,
            parseOption(propsOption, OptionKeys.BetweenItems),
            parseOption(propsOption, OptionKeys.WithinEach)
          )
        },
        onDefineEmitsEnter(_, emits) {
          const emitNodes = emits
            .filter(isValidProperties)
            .map((emit) => emit.node)

          const withinOption = parseOption(options, OptionKeys.WithinOption)
          const emitsOption = withinOption && parseOption(withinOption, 'emits')
          if (!emitsOption) return

          verify(
            emitNodes,
            parseOption(emitsOption, OptionKeys.BetweenItems),
            parseOption(emitsOption, OptionKeys.WithinEach)
          )
        },
        onDefineOptionsEnter(node) {
          if (node.arguments.length === 0) return
          const define = node.arguments[0]
          if (define.type !== 'ObjectExpression') return
          verify(
            define.properties,
            parseOption(options, OptionKeys.BetweenOptions),
            parseOption(options, OptionKeys.WithinOption)
          )
        }
      })
    )
  }
}


code:
/**
 * @author Yosuke Ota <https://github.com/ota-meshi>
 * See LICENSE file in root directory for full license.
 */
'use strict'

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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce use of `defineOptions` instead of default export',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/prefer-define-options.html'
    },
    fixable: 'code',
    schema: [],
    messages: {
      preferDefineOptions: 'Use `defineOptions` instead of default export.'
    }
  },
  /**
   * @param {RuleContext} context
   * @returns {RuleListener}
   */
  create(context) {
    const scriptSetup = utils.getScriptSetupElement(context)
    if (!scriptSetup) {
      return {}
    }

    /** @type {CallExpression | null} */
    let defineOptionsNode = null
    /** @type {ExportDefaultDeclaration | null} */
    let exportDefaultDeclaration = null
    /** @type {ImportDeclaration|null} */
    let lastImportDeclaration = null

    return utils.compositingVisitors(
      utils.defineScriptSetupVisitor(context, {
        ImportDeclaration(node) {
          lastImportDeclaration = node
        },
        onDefineOptionsEnter(node) {
          defineOptionsNode = node
        }
      }),
      {
        ExportDefaultDeclaration(node) {
          exportDefaultDeclaration = node
        },
        'Program:exit'() {
          if (!exportDefaultDeclaration) {
            return
          }
          context.report({
            node: exportDefaultDeclaration,
            messageId: 'preferDefineOptions',
            fix: defineOptionsNode
              ? null
              : buildFix(exportDefaultDeclaration, scriptSetup)
          })
        }
      }
    )

    /**
     * @param {ExportDefaultDeclaration} node
     * @param {VElement} scriptSetup
     * @returns {(fixer: RuleFixer) => Fix[]}
     */
    function buildFix(node, scriptSetup) {
      return (fixer) => {
        const sourceCode = context.getSourceCode()

        // Calc remove range
        /** @type {Range} */
        let removeRange = [...node.range]

        const script = scriptSetup.parent.children
          .filter(utils.isVElement)
          .find(
            (node) =>
              node.name === 'script' && !utils.hasAttribute(node, 'setup')
          )
        if (
          script &&
          script.endTag &&
          sourceCode
            .getTokensBetween(script.startTag, script.endTag, {
              includeComments: true
            })
            .every(
              (token) =>
                removeRange[0] <= token.range[0] &&
                token.range[1] <= removeRange[1]
            )
        ) {
          removeRange = [...script.range]
        }
        const removeStartLoc = sourceCode.getLocFromIndex(removeRange[0])
        if (
          sourceCode.lines[removeStartLoc.line - 1]
            .slice(0, removeStartLoc.column)
            .trim() === ''
        ) {
          removeRange[0] =
            removeStartLoc.line === 1
              ? 0
              : sourceCode.getIndexFromLoc({
                  line: removeStartLoc.line - 1,
                  column: sourceCode.lines[removeStartLoc.line - 2].length
                })
        }

        /** @type {VStartTag | ImportDeclaration} */
        const insertAfterTag = lastImportDeclaration || scriptSetup.startTag

        return [
          fixer.removeRange(removeRange),
          fixer.insertTextAfter(
            insertAfterTag,
            `\ndefineOptions(${sourceCode.getText(node.declaration)})\n`
          )
        ]
      }
    }
  }
}


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

const vue3ExportNames = new Set(require('../utils/vue3-export-names.json'))

const TARGET_AT_VUE_MODULES = new Set([
  '@vue/runtime-dom',
  '@vue/runtime-core',
  '@vue/reactivity',
  '@vue/shared'
])
// Modules with the names of a subset of vue.
const SUBSET_AT_VUE_MODULES = new Set(['@vue/runtime-dom', '@vue/runtime-core'])

/**
 * @param {ImportDeclaration} node
 */
function* extractImportNames(node) {
  for (const specifier of node.specifiers) {
    switch (specifier.type) {
      case 'ImportDefaultSpecifier': {
        yield 'default'
        break
      }
      case 'ImportNamespaceSpecifier': {
        yield null // all
        break
      }
      case 'ImportSpecifier': {
        yield specifier.imported.name
        break
      }
    }
  }
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: "enforce import from 'vue' instead of import from '@vue/*'",
      categories: ['vue3-essential'],
      url: 'https://eslint.vuejs.org/rules/prefer-import-from-vue.html'
    },
    fixable: 'code',
    schema: [],
    messages: {
      importedAtVue: "Import from 'vue' instead of '{{source}}'."
    }
  },
  /**
   * @param {RuleContext} context
   * @returns {RuleListener}
   */
  create(context) {
    /**
     *
     * @param {Literal & { value: string }} source
     * @param { () => boolean } fixable
     */
    function verifySource(source, fixable) {
      if (!TARGET_AT_VUE_MODULES.has(source.value)) {
        return
      }

      context.report({
        node: source,
        messageId: 'importedAtVue',
        data: { source: source.value },
        fix: fixable()
          ? (fixer) =>
              fixer.replaceTextRange(
                [source.range[0] + 1, source.range[1] - 1],
                'vue'
              )
          : null
      })
    }

    return {
      ImportDeclaration(node) {
        // Skip imports without specifiers in `.d.ts` files
        if (
          node.specifiers.length === 0 &&
          context.getFilename().endsWith('.d.ts')
        )
          return

        verifySource(node.source, () => {
          if (SUBSET_AT_VUE_MODULES.has(node.source.value)) {
            // If the module is a subset of 'vue', we can safely change it to 'vue'.
            return true
          }
          for (const name of extractImportNames(node)) {
            if (name == null) {
              return false // import all
            }
            if (!vue3ExportNames.has(name)) {
              // If there is a name that is not exported from 'vue', it will not be auto-fixed.
              return false
            }
          }
          return true
        })
      },
      ExportNamedDeclaration(node) {
        if (node.source) {
          verifySource(node.source, () => {
            for (const specifier of node.specifiers) {
              if (!vue3ExportNames.has(specifier.local.name)) {
                // If there is a name that is not exported from 'vue', it will not be auto-fixed.
                return false
              }
            }
            return true
          })
        }
      },
      ExportAllDeclaration(node) {
        verifySource(
          node.source,
          // If we change it to `from 'vue'`, it will export more, so it will not be auto-fixed.
          () => false
        )
      }
    }
  }
}


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

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

/**
 * @param {ArrayExpression} node
 * @param {RuleContext} context
 */
function checkArrayExpression(node, context) {
  const booleanType = node.elements.find(
    (element) =>
      element && element.type === 'Identifier' && element.name === 'Boolean'
  )
  if (!booleanType) {
    return
  }
  const booleanTypeIndex = node.elements.indexOf(booleanType)
  if (booleanTypeIndex > 0) {
    context.report({
      node: booleanType,
      messageId: 'shouldBeFirst',
      suggest: [
        {
          messageId: 'moveToFirst',
          fix: (fixer) => {
            const sourceCode = context.getSourceCode()

            const elements = [...node.elements]
            elements.splice(booleanTypeIndex, 1)
            const code = elements
              .filter(utils.isDef)
              .map((element) => sourceCode.getText(element))
            code.unshift('Boolean')

            return fixer.replaceText(node, `[${code.join(', ')}]`)
          }
        }
      ]
    })
  }
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'enforce `Boolean` comes first in component prop types',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/prefer-prop-type-boolean-first.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: [],
    messages: {
      shouldBeFirst: 'Type `Boolean` should be at first in prop types.',
      moveToFirst: 'Move `Boolean` to be first in prop types.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /**
     * @param {import('../utils').ComponentProp} prop
     */
    function checkProperty(prop) {
      if (prop.type !== 'object') {
        return
      }
      const { value } = prop
      if (!value) {
        return
      }

      if (value.type === 'ArrayExpression') {
        checkArrayExpression(value, context)
      } else if (value.type === 'ObjectExpression') {
        const type = value.properties.find(
          /** @return {property is Property} */
          (property) =>
            property.type === 'Property' &&
            utils.getStaticPropertyName(property) === 'type'
        )
        if (!type || type.value.type !== 'ArrayExpression') {
          return
        }
        checkArrayExpression(type.value, context)
      }
    }

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


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

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

/**
 * @param {Expression | VForExpression | VOnExpression | VSlotScopeExpression | VFilterSequenceExpression} expressionNode
 * @returns {(Literal | TemplateLiteral | Identifier)[]}
 */
function findStaticClasses(expressionNode) {
  if (isStringLiteral(expressionNode)) {
    return [expressionNode]
  }

  if (expressionNode.type === 'ArrayExpression') {
    return expressionNode.elements.flatMap((element) => {
      if (element === null || element.type === 'SpreadElement') {
        return []
      }
      return findStaticClasses(element)
    })
  }

  if (expressionNode.type === 'ObjectExpression') {
    return expressionNode.properties.flatMap((property) => {
      if (
        property.type === 'Property' &&
        property.value.type === 'Literal' &&
        property.value.value === true &&
        (isStringLiteral(property.key) ||
          (property.key.type === 'Identifier' && !property.computed))
      ) {
        return [property.key]
      }
      return []
    })
  }

  return []
}

/**
 * @param {VAttribute | VDirective} attributeNode
 * @returns {attributeNode is VAttribute & { value: VLiteral }}
 */
function isStaticClassAttribute(attributeNode) {
  return (
    !attributeNode.directive &&
    attributeNode.key.name === 'class' &&
    attributeNode.value !== null
  )
}

/**
 * Removes the node together with the comma before or after the node.
 * @param {RuleFixer} fixer
 * @param {ParserServices.TokenStore} tokenStore
 * @param {ASTNode} node
 */
function* removeNodeWithComma(fixer, tokenStore, node) {
  const prevToken = tokenStore.getTokenBefore(node)
  if (prevToken.type === 'Punctuator' && prevToken.value === ',') {
    yield fixer.removeRange([prevToken.range[0], node.range[1]])
    return
  }

  const [nextToken, nextNextToken] = tokenStore.getTokensAfter(node, {
    count: 2
  })
  if (
    nextToken.type === 'Punctuator' &&
    nextToken.value === ',' &&
    (nextNextToken.type !== 'Punctuator' ||
      (nextNextToken.value !== ']' && nextNextToken.value !== '}'))
  ) {
    yield fixer.removeRange([node.range[0], nextNextToken.range[0]])
    return
  }

  yield fixer.remove(node)
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'require static class names in template to be in a separate `class` attribute',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/prefer-separate-static-class.html'
    },
    fixable: 'code',
    schema: [],
    messages: {
      preferSeparateStaticClass:
        'Static class "{{className}}" should be in a static `class` attribute.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return defineTemplateBodyVisitor(context, {
      /** @param {VDirectiveKey} directiveKeyNode */
      "VAttribute[directive=true] > VDirectiveKey[name.name='bind'][argument.name='class']"(
        directiveKeyNode
      ) {
        const attributeNode = directiveKeyNode.parent
        if (!attributeNode.value || !attributeNode.value.expression) {
          return
        }

        const expressionNode = attributeNode.value.expression
        const staticClassNameNodes = findStaticClasses(expressionNode)

        for (const staticClassNameNode of staticClassNameNodes) {
          const className =
            staticClassNameNode.type === 'Identifier'
              ? staticClassNameNode.name
              : getStringLiteralValue(staticClassNameNode, true)

          if (className === null) {
            continue
          }

          context.report({
            node: staticClassNameNode,
            messageId: 'preferSeparateStaticClass',
            data: { className },
            *fix(fixer) {
              let dynamicClassDirectiveRemoved = false

              yield* removeFromClassDirective()
              yield* addToClassAttribute()

              /**
               * Remove class from dynamic `:class` directive.
               */
              function* removeFromClassDirective() {
                if (isStringLiteral(expressionNode)) {
                  yield fixer.remove(attributeNode)
                  dynamicClassDirectiveRemoved = true
                  return
                }

                const listElement =
                  staticClassNameNode.parent.type === 'Property'
                    ? staticClassNameNode.parent
                    : staticClassNameNode

                const listNode = listElement.parent
                if (
                  listNode.type === 'ArrayExpression' ||
                  listNode.type === 'ObjectExpression'
                ) {
                  const elements =
                    listNode.type === 'ObjectExpression'
                      ? listNode.properties
                      : listNode.elements

                  if (elements.length === 1 && listNode === expressionNode) {
                    yield fixer.remove(attributeNode)
                    dynamicClassDirectiveRemoved = true
                    return
                  }

                  const sourceCode = context.getSourceCode()
                  const tokenStore =
                    sourceCode.parserServices.getTemplateBodyTokenStore()

                  if (elements.length === 1) {
                    yield* removeNodeWithComma(fixer, tokenStore, listNode)
                    return
                  }

                  yield* removeNodeWithComma(fixer, tokenStore, listElement)
                }
              }

              /**
               * Add class to static `class` attribute.
               */
              function* addToClassAttribute() {
                const existingStaticClassAttribute =
                  attributeNode.parent.attributes.find(isStaticClassAttribute)
                if (existingStaticClassAttribute) {
                  const literalNode = existingStaticClassAttribute.value
                  yield fixer.replaceText(
                    literalNode,
                    `"${literalNode.value} ${className}"`
                  )
                  return
                }

                // new static `class` attribute
                const separator = dynamicClassDirectiveRemoved ? '' : ' '
                yield fixer.insertTextBefore(
                  attributeNode,
                  `class="${className}"${separator}`
                )
              }
            }
          })
        }
      }
    })
  }
}


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

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

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


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

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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'require shorthand form attribute when `v-bind` value is `true`',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/prefer-true-attribute-shorthand.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: [{ enum: ['always', 'never'] }],
    messages: {
      expectShort:
        "Boolean prop with 'true' value should be written in shorthand form.",
      expectLong:
        "Boolean prop with 'true' value should be written in long form.",
      rewriteIntoShort: 'Rewrite this prop into shorthand form.',
      rewriteIntoLongVueProp:
        'Rewrite this prop into long-form Vue component prop.',
      rewriteIntoLongHtmlAttr:
        'Rewrite this prop into long-form HTML attribute.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {'always' | 'never'} */
    const option = context.options[0] || 'always'

    return utils.defineTemplateBodyVisitor(context, {
      VAttribute(node) {
        if (!utils.isCustomComponent(node.parent.parent)) {
          return
        }

        if (option === 'never' && !node.directive && !node.value) {
          context.report({
            node,
            messageId: 'expectLong',
            suggest: [
              {
                messageId: 'rewriteIntoLongVueProp',
                fix: (fixer) =>
                  fixer.replaceText(node, `:${node.key.rawName}="true"`)
              },
              {
                messageId: 'rewriteIntoLongHtmlAttr',
                fix: (fixer) =>
                  fixer.replaceText(
                    node,
                    `${node.key.rawName}="${node.key.rawName}"`
                  )
              }
            ]
          })
          return
        }

        if (option !== 'always') {
          return
        }

        if (
          !node.directive ||
          !node.value ||
          !node.value.expression ||
          node.value.expression.type !== 'Literal' ||
          node.value.expression.value !== true
        ) {
          return
        }

        const { argument } = node.key
        if (!argument) {
          return
        }

        context.report({
          node,
          messageId: 'expectShort',
          suggest: [
            {
              messageId: 'rewriteIntoShort',
              fix: (fixer) => {
                const sourceCode = context.getSourceCode()
                return fixer.replaceText(node, sourceCode.getText(argument))
              }
            }
          ]
        })
      }
    })
  }
}


code:
/**
 * @fileoverview Requires specific casing for the Prop name in Vue components
 * @author Yu Kimura
 */
'use strict'

const utils = require('../utils')
const casing = require('../utils/casing')
const allowedCaseOptions = ['camelCase', 'snake_case']

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

/** @param {RuleContext} context */
function create(context) {
  const options = context.options[0]
  const caseType = allowedCaseOptions.includes(options) ? options : 'camelCase'
  const checker = casing.getChecker(caseType)

  /**
   * @param {ComponentProp[]} props
   */
  function processProps(props) {
    for (const item of props) {
      const propName = item.propName
      if (propName == null) {
        continue
      }
      if (!checker(propName)) {
        context.report({
          node: item.node,
          messageId: 'invalidCase',
          data: {
            name: propName,
            caseType
          }
        })
      }
    }
  }
  return utils.compositingVisitors(
    utils.defineScriptSetupVisitor(context, {
      onDefinePropsEnter(_node, props) {
        processProps(props)
      }
    }),
    utils.executeOnVue(context, (obj) => {
      processProps(utils.getComponentPropsFromOptions(obj))
    })
  )
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'enforce specific casing for the Prop name in Vue components',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/prop-name-casing.html'
    },
    fixable: null, // null or "code" or "whitespace"
    schema: [
      {
        enum: allowedCaseOptions
      }
    ],
    messages: {
      invalidCase: 'Prop "{{name}}" is not in {{caseType}}.'
    }
  },
  create
}


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

const { wrapStylisticOrCoreRule, flatten } = require('../utils')

// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapStylisticOrCoreRule('quote-props', {
  skipDynamicArguments: true,
  preprocess(context, { wrapContextToOverrideProperties, defineVisitor }) {
    const sourceCode = context.getSourceCode()
    /**
     * @type {'"' | "'" | null}
     */
    let htmlQuote = null
    defineVisitor({
      /** @param {VExpressionContainer} node */
      'VAttribute > VExpressionContainer.value'(node) {
        const text = sourceCode.getText(node)
        const firstChar = text[0]
        htmlQuote = firstChar === "'" || firstChar === '"' ? firstChar : null
      },
      'VAttribute > VExpressionContainer.value:exit'() {
        htmlQuote = null
      }
    })

    wrapContextToOverrideProperties({
      // Override the report method and replace the quotes in the fixed text with safe quotes.
      report(descriptor) {
        if (htmlQuote) {
          const expectedQuote = htmlQuote === '"' ? "'" : '"'
          context.report({
            ...descriptor,
            *fix(fixer) {
              for (const fix of flatten(
                descriptor.fix && descriptor.fix(fixer)
              )) {
                yield fixer.replaceTextRange(
                  fix.range,
                  fix.text.replace(/["']/gu, expectedQuote)
                )
              }
            }
          })
        } else {
          context.report(descriptor)
        }
      }
    })
  }
})


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:is` of `<component>` elements',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/require-component-is.html'
    },
    fixable: null,
    schema: [],
    messages: {
      requireComponentIs:
        "Expected '<component>' elements to have 'v-bind:is' attribute."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VElement} node */
      "VElement[name='component']"(node) {
        if (!utils.hasDirective(node, 'bind', 'is')) {
          context.report({
            node,
            loc: node.loc,
            messageId: 'requireComponentIs'
          })
        }
      }
    })
  }
}


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

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'require components to be the default export',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/require-default-export.html'
    },
    fixable: null,
    schema: [],
    messages: {
      missing: 'Missing default export.',
      mustBeDefaultExport: 'Component must be the default export.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()
    const documentFragment = sourceCode.parserServices.getDocumentFragment?.()

    const hasScript =
      documentFragment &&
      documentFragment.children.some(
        (e) => utils.isVElement(e) && e.name === 'script'
      )

    if (utils.isScriptSetup(context) || !hasScript) {
      return {}
    }

    let hasDefaultExport = false
    let hasDefinedComponent = false

    return utils.compositingVisitors(
      utils.defineVueVisitor(context, {
        onVueObjectExit() {
          hasDefinedComponent = true
        }
      }),

      {
        'Program > ExportDefaultDeclaration'() {
          hasDefaultExport = true
        },

        /**
         * @param {Program} node
         */
        'Program:exit'(node) {
          if (!hasDefaultExport && node.body.length > 0) {
            context.report({
              loc: node.tokens[node.tokens.length - 1].loc,
              messageId: hasDefinedComponent ? 'mustBeDefaultExport' : 'missing'
            })
          }
        }
      }
    )
  }
}


code:
/**
 * @fileoverview Require default value for props
 * @author Michał Sajnóg <msajnog93@gmail.com> (https://github.com/michalsnik)
 */
'use strict'

/**
 * @typedef {import('../utils').ComponentProp} ComponentProp
 * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
 * @typedef {import('../utils').ComponentTypeProp} ComponentTypeProp
 * @typedef {ComponentObjectProp & { value: ObjectExpression} } ComponentObjectPropObject
 */

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

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

/**
 * Detects whether given value node is a Boolean type
 * @param {Expression} value
 * @return {boolean}
 */
function isValueNodeOfBooleanType(value) {
  if (value.type === 'Identifier' && value.name === 'Boolean') {
    return true
  }
  if (value.type === 'ArrayExpression') {
    const elements = value.elements.filter(isDef)
    return (
      elements.length === 1 &&
      elements[0].type === 'Identifier' &&
      elements[0].name === 'Boolean'
    )
  }
  return false
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'require default value for props',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/require-default-prop.html'
    },
    fixable: null,
    schema: [],
    messages: {
      missingDefault: `Prop '{{propName}}' requires default value to be set.`
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /**
     * Checks if the passed prop is required
     * @param {ObjectExpression} propValue - ObjectExpression AST node for a single prop
     * @return {boolean}
     */
    function propIsRequired(propValue) {
      const propRequiredNode = propValue.properties.find(
        (p) =>
          p.type === 'Property' &&
          utils.getStaticPropertyName(p) === 'required' &&
          p.value.type === 'Literal' &&
          p.value.value === true
      )

      return Boolean(propRequiredNode)
    }

    /**
     * Checks if the passed prop has a default value
     * @param {ObjectExpression} propValue - ObjectExpression AST node for a single prop
     * @return {boolean}
     */
    function propHasDefault(propValue) {
      const propDefaultNode = propValue.properties.find(
        (p) =>
          p.type === 'Property' && utils.getStaticPropertyName(p) === 'default'
      )

      return Boolean(propDefaultNode)
    }

    /**
     * Checks whether the given props that don't have a default value
     * @param {ComponentObjectProp} prop Vue component's "props" node
     * @return {boolean}
     */
    function isWithoutDefaultValue(prop) {
      if (prop.value.type !== 'ObjectExpression') {
        if (prop.value.type === 'Identifier') {
          return NATIVE_TYPES.has(prop.value.name)
        }
        if (
          prop.value.type === 'CallExpression' ||
          prop.value.type === 'MemberExpression'
        ) {
          // OK
          return false
        }
        // NG
        return true
      }

      return !propIsRequired(prop.value) && !propHasDefault(prop.value)
    }

    /**
     * Detects whether given prop node is a Boolean
     * @param {ComponentObjectProp} prop
     * @return {Boolean}
     */
    function isBooleanProp(prop) {
      const value = utils.skipTSAsExpression(prop.value)

      return (
        isValueNodeOfBooleanType(value) ||
        (value.type === 'ObjectExpression' &&
          value.properties.some(
            (p) =>
              p.type === 'Property' &&
              p.key.type === 'Identifier' &&
              p.key.name === 'type' &&
              isValueNodeOfBooleanType(p.value)
          ))
      )
    }

    /**
     * @param {ComponentProp[]} props
     * @param {(prop: ComponentObjectProp|ComponentTypeProp)=>boolean} [ignore]
     */
    function processProps(props, ignore) {
      for (const prop of props) {
        if (prop.type === 'object') {
          if (prop.node.shorthand) {
            continue
          }
          if (!isWithoutDefaultValue(prop)) {
            continue
          }
          if (isBooleanProp(prop)) {
            continue
          }
          if (ignore?.(prop)) {
            continue
          }
          const propName =
            prop.propName == null
              ? `[${context.getSourceCode().getText(prop.node.key)}]`
              : prop.propName

          context.report({
            node: prop.node,
            messageId: `missingDefault`,
            data: {
              propName
            }
          })
        } else if (prop.type === 'type') {
          if (prop.required) {
            continue
          }
          if (prop.types.length === 1 && prop.types[0] === 'Boolean') {
            continue
          }
          if (ignore?.(prop)) {
            continue
          }
          context.report({
            node: prop.node,
            messageId: `missingDefault`,
            data: {
              propName: prop.propName
            }
          })
        }
      }
    }

    return utils.compositingVisitors(
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(node, props) {
          const hasWithDefaults = utils.hasWithDefaults(node)
          const defaultsByWithDefaults =
            utils.getWithDefaultsPropExpressions(node)
          const isUsingPropsDestructure = utils.isUsingPropsDestructure(node)
          const defaultsByAssignmentPatterns =
            utils.getDefaultPropExpressionsForPropsDestructure(node)

          processProps(props, (prop) => {
            if (prop.type === 'type') {
              if (!hasWithDefaults) {
                // If don't use withDefaults(), exclude it from the report.
                return true
              }
              if (defaultsByWithDefaults[prop.propName]) {
                return true
              }
            }
            if (!isUsingPropsDestructure) {
              return false
            }
            if (prop.propName == null) {
              // If using Props Destructure but the property name cannot be determined,
              // it will be ignored.
              return true
            }
            return Boolean(defaultsByAssignmentPatterns[prop.propName])
          })
        }
      }),
      utils.executeOnVue(context, (obj) => {
        processProps(utils.getComponentPropsFromOptions(obj))
      })
    )
  }
}


code:
/**
 * @fileoverview require the component to be directly exported
 * @author Hiroki Osame <hiroki.osame@gmail.com>
 */
'use strict'

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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'require the component to be directly exported',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/require-direct-export.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          disallowFunctionalComponentFunction: { type: 'boolean' }
        },
        additionalProperties: false
      }
    ],
    messages: {
      expectedDirectExport:
        'Expected the component literal to be directly exported.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const filePath = context.getFilename()
    if (!utils.isVueFile(filePath)) return {}

    const disallowFunctional = (context.options[0] || {})
      .disallowFunctionalComponentFunction

    /**
     * @typedef {object} ScopeStack
     * @property {ScopeStack | null} upper
     * @property {boolean} withinVue3FunctionalBody
     */

    /** @type { { body: BlockStatement, hasReturnArgument: boolean } } */
    let maybeVue3Functional
    /** @type {ScopeStack | null} */
    let scopeStack = null

    return {
      /** @param {Declaration | Expression} node */
      'ExportDefaultDeclaration > *'(node) {
        if (node.type === 'ObjectExpression') {
          // OK
          return
        }
        if (node.type === 'CallExpression') {
          const {
            callee,
            arguments: [firstArg]
          } = node
          if (
            firstArg &&
            firstArg.type === 'ObjectExpression' &&
            ((callee.type === 'Identifier' &&
              callee.name === 'defineComponent') ||
              (callee.type === 'MemberExpression' &&
                callee.object.type === 'Identifier' &&
                callee.object.name === 'Vue' &&
                callee.property.type === 'Identifier' &&
                callee.property.name === 'extend'))
          ) {
            return
          }
        }
        if (!disallowFunctional) {
          if (node.type === 'ArrowFunctionExpression') {
            if (node.body.type !== 'BlockStatement') {
              // OK
              return
            }
            maybeVue3Functional = {
              body: node.body,
              hasReturnArgument: false
            }
            return
          }
          if (
            node.type === 'FunctionExpression' ||
            node.type === 'FunctionDeclaration'
          ) {
            maybeVue3Functional = {
              body: node.body,
              hasReturnArgument: false
            }
            return
          }
        }

        context.report({
          node: node.parent,
          messageId: 'expectedDirectExport'
        })
      },
      ...(disallowFunctional
        ? {}
        : {
            /** @param {BlockStatement} node */
            ':function > BlockStatement'(node) {
              if (!maybeVue3Functional) {
                return
              }
              scopeStack = {
                upper: scopeStack,
                withinVue3FunctionalBody: maybeVue3Functional.body === node
              }
            },
            /** @param {ReturnStatement} node */
            ReturnStatement(node) {
              if (
                scopeStack &&
                scopeStack.withinVue3FunctionalBody &&
                node.argument
              ) {
                maybeVue3Functional.hasReturnArgument = true
              }
            },
            ':function > BlockStatement:exit'() {
              scopeStack = scopeStack && scopeStack.upper
            },
            /** @param {ExportDefaultDeclaration} node */
            'ExportDefaultDeclaration:exit'(node) {
              if (!maybeVue3Functional) {
                return
              }
              if (!maybeVue3Functional.hasReturnArgument) {
                context.report({
                  node,
                  messageId: 'expectedDirectExport'
                })
              }
            }
          })
    }
  }
}


code:
/**
 * @fileoverview Emit definitions should be detailed
 * @author Pig Fang
 */
'use strict'

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

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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'require type definitions in emits',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/require-emit-validator.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: [],
    messages: {
      missing: 'Emit "{{name}}" should define at least its validator function.',
      skipped:
        'Emit "{{name}}" should not skip validation, or you may define a validator function with no parameters.',
      emptyValidation: 'Replace with a validator function with no parameters.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /**
     * @param {ComponentEmit} emit
     */
    function checker(emit) {
      /** @type {Expression|null} */
      let value = null
      let hasType = false
      if (emit.type === 'object') {
        value = emit.value
        hasType =
          value.type === 'ArrowFunctionExpression' ||
          value.type === 'FunctionExpression' ||
          // validator may from outer scope
          value.type === 'Identifier'
      } else if (emit.type !== 'array') {
        return
      }

      if (!hasType) {
        const { node, emitName } = emit
        const name =
          emitName ||
          (node.type === 'Identifier' && node.name) ||
          'Unknown emit'

        if (value && value.type === 'Literal' && value.value === null) {
          const valueNode = value
          context.report({
            node,
            messageId: 'skipped',
            data: { name },
            suggest: [
              {
                messageId: 'emptyValidation',
                fix: (fixer) => fixer.replaceText(valueNode, '() => true')
              }
            ]
          })

          return
        }

        context.report({
          node,
          messageId: 'missing',
          data: { name }
        })
      }
    }

    return utils.compositingVisitors(
      utils.executeOnVue(context, (obj) => {
        for (const emit of utils.getComponentEmitsFromOptions(obj)) {
          checker(emit)
        }
      }),
      utils.defineScriptSetupVisitor(context, {
        onDefineEmitsEnter(_node, emits) {
          for (const emit of emits) {
            checker(emit)
          }
        }
      })
    )
  }
}