code:
/**
 * @fileoverview enforce ordering of attributes
 * @author Erin Depew
 */
'use strict'
const utils = require('../utils')

/**
 * @typedef { VDirective & { key: VDirectiveKey & { name: VIdentifier & { name: 'bind' } } } } VBindDirective
 */

const ATTRS = {
  DEFINITION: 'DEFINITION',
  LIST_RENDERING: 'LIST_RENDERING',
  CONDITIONALS: 'CONDITIONALS',
  RENDER_MODIFIERS: 'RENDER_MODIFIERS',
  GLOBAL: 'GLOBAL',
  UNIQUE: 'UNIQUE',
  SLOT: 'SLOT',
  TWO_WAY_BINDING: 'TWO_WAY_BINDING',
  OTHER_DIRECTIVES: 'OTHER_DIRECTIVES',
  OTHER_ATTR: 'OTHER_ATTR',
  ATTR_STATIC: 'ATTR_STATIC',
  ATTR_DYNAMIC: 'ATTR_DYNAMIC',
  ATTR_SHORTHAND_BOOL: 'ATTR_SHORTHAND_BOOL',
  EVENTS: 'EVENTS',
  CONTENT: 'CONTENT'
}

/**
 * Check whether the given attribute is `v-bind` directive.
 * @param {VAttribute | VDirective | undefined | null} node
 * @returns { node is VBindDirective }
 */
function isVBind(node) {
  return Boolean(node && node.directive && node.key.name.name === 'bind')
}
/**
 * Check whether the given attribute is `v-model` directive.
 * @param {VAttribute | VDirective | undefined | null} node
 * @returns { node is VDirective }
 */
function isVModel(node) {
  return Boolean(node && node.directive && node.key.name.name === 'model')
}
/**
 * Check whether the given attribute is plain attribute.
 * @param {VAttribute | VDirective | undefined | null} node
 * @returns { node is VAttribute }
 */
function isVAttribute(node) {
  return Boolean(node && !node.directive)
}
/**
 * Check whether the given attribute is plain attribute, `v-bind` directive or `v-model` directive.
 * @param {VAttribute | VDirective | undefined | null} node
 * @returns { node is VAttribute }
 */
function isVAttributeOrVBindOrVModel(node) {
  return isVAttribute(node) || isVBind(node) || isVModel(node)
}

/**
 * Check whether the given attribute is `v-bind="..."` directive.
 * @param {VAttribute | VDirective | undefined | null} node
 * @returns { node is VBindDirective }
 */
function isVBindObject(node) {
  return isVBind(node) && node.key.argument == null
}

/**
 * Check whether the given attribute is a shorthand boolean like `selected`.
 * @param {VAttribute | VDirective | undefined | null} node
 * @returns { node is VAttribute }
 */
function isVShorthandBoolean(node) {
  return isVAttribute(node) && !node.value
}

/**
 * @param {VAttribute | VDirective} attribute
 * @param {SourceCode} sourceCode
 */
function getAttributeName(attribute, sourceCode) {
  if (attribute.directive) {
    if (isVBind(attribute)) {
      return attribute.key.argument
        ? sourceCode.getText(attribute.key.argument)
        : ''
    } else {
      return getDirectiveKeyName(attribute.key, sourceCode)
    }
  } else {
    return attribute.key.name
  }
}

/**
 * @param {VDirectiveKey} directiveKey
 * @param {SourceCode} sourceCode
 */
function getDirectiveKeyName(directiveKey, sourceCode) {
  let text = `v-${directiveKey.name.name}`
  if (directiveKey.argument) {
    text += `:${sourceCode.getText(directiveKey.argument)}`
  }
  for (const modifier of directiveKey.modifiers) {
    text += `.${modifier.name}`
  }
  return text
}

/**
 * @param {VAttribute | VDirective} attribute
 */
function getAttributeType(attribute) {
  let propName
  if (attribute.directive) {
    if (!isVBind(attribute)) {
      const name = attribute.key.name.name
      switch (name) {
        case 'for': {
          return ATTRS.LIST_RENDERING
        }
        case 'if':
        case 'else-if':
        case 'else':
        case 'show':
        case 'cloak': {
          return ATTRS.CONDITIONALS
        }
        case 'pre':
        case 'once': {
          return ATTRS.RENDER_MODIFIERS
        }
        case 'model': {
          return ATTRS.TWO_WAY_BINDING
        }
        case 'on': {
          return ATTRS.EVENTS
        }
        case 'html':
        case 'text': {
          return ATTRS.CONTENT
        }
        case 'slot': {
          return ATTRS.SLOT
        }
        case 'is': {
          return ATTRS.DEFINITION
        }
        default: {
          return ATTRS.OTHER_DIRECTIVES
        }
      }
    }
    propName =
      attribute.key.argument && attribute.key.argument.type === 'VIdentifier'
        ? attribute.key.argument.rawName
        : ''
  } else {
    propName = attribute.key.name
  }
  switch (propName) {
    case 'is': {
      return ATTRS.DEFINITION
    }
    case 'id': {
      return ATTRS.GLOBAL
    }
    case 'ref':
    case 'key': {
      return ATTRS.UNIQUE
    }
    case 'slot':
    case 'slot-scope': {
      return ATTRS.SLOT
    }
    default: {
      if (isVBind(attribute)) {
        return ATTRS.ATTR_DYNAMIC
      }
      if (isVShorthandBoolean(attribute)) {
        return ATTRS.ATTR_SHORTHAND_BOOL
      }
      return ATTRS.ATTR_STATIC
    }
  }
}

/**
 * @param {VAttribute | VDirective} attribute
 * @param { { [key: string]: number } } attributePosition
 * @returns {number | null} If the value is null, the order is omitted. Do not force the order.
 */
function getPosition(attribute, attributePosition) {
  const attributeType = getAttributeType(attribute)
  return attributePosition[attributeType] == null
    ? null
    : attributePosition[attributeType]
}

/**
 * @param {VAttribute | VDirective} prevNode
 * @param {VAttribute | VDirective} currNode
 * @param {SourceCode} sourceCode
 */
function isAlphabetical(prevNode, currNode, sourceCode) {
  const prevName = getAttributeName(prevNode, sourceCode)
  const currName = getAttributeName(currNode, sourceCode)
  if (prevName === currName) {
    const prevIsBind = isVBind(prevNode)
    const currIsBind = isVBind(currNode)
    return prevIsBind <= currIsBind
  }
  return prevName < currName
}

/**
 * @param {RuleContext} context - The rule context.
 * @returns {RuleListener} AST event handlers.
 */
function create(context) {
  const sourceCode = context.getSourceCode()
  const otherAttrs = [
    ATTRS.ATTR_DYNAMIC,
    ATTRS.ATTR_STATIC,
    ATTRS.ATTR_SHORTHAND_BOOL
  ]
  let attributeOrder = [
    ATTRS.DEFINITION,
    ATTRS.LIST_RENDERING,
    ATTRS.CONDITIONALS,
    ATTRS.RENDER_MODIFIERS,
    ATTRS.GLOBAL,
    [ATTRS.UNIQUE, ATTRS.SLOT],
    ATTRS.TWO_WAY_BINDING,
    ATTRS.OTHER_DIRECTIVES,
    otherAttrs,
    ATTRS.EVENTS,
    ATTRS.CONTENT
  ]
  if (context.options[0] && context.options[0].order) {
    attributeOrder = [...context.options[0].order]

    // check if `OTHER_ATTR` is valid
    for (const item of attributeOrder.flat()) {
      if (item === ATTRS.OTHER_ATTR) {
        for (const attribute of attributeOrder.flat()) {
          if (otherAttrs.includes(attribute)) {
            throw new Error(
              `Value "${ATTRS.OTHER_ATTR}" is not allowed with "${attribute}".`
            )
          }
        }
      }
    }

    // expand `OTHER_ATTR` alias
    for (const [index, item] of attributeOrder.entries()) {
      if (item === ATTRS.OTHER_ATTR) {
        attributeOrder[index] = otherAttrs
      } else if (Array.isArray(item) && item.includes(ATTRS.OTHER_ATTR)) {
        const attributes = item.filter((i) => i !== ATTRS.OTHER_ATTR)
        attributes.push(...otherAttrs)
        attributeOrder[index] = attributes
      }
    }
  }
  const alphabetical = Boolean(
    context.options[0] && context.options[0].alphabetical
  )

  /** @type { { [key: string]: number } } */
  const attributePosition = {}
  for (const [i, item] of attributeOrder.entries()) {
    if (Array.isArray(item)) {
      for (const attr of item) {
        attributePosition[attr] = i
      }
    } else attributePosition[item] = i
  }

  /**
   * @param {VAttribute | VDirective} node
   * @param {VAttribute | VDirective} previousNode
   */
  function reportIssue(node, previousNode) {
    const currentNode = sourceCode.getText(node.key)
    const prevNode = sourceCode.getText(previousNode.key)
    context.report({
      node,
      messageId: 'expectedOrder',
      data: {
        currentNode,
        prevNode
      },

      fix(fixer) {
        const attributes = node.parent.attributes

        /** @type { (node: VAttribute | VDirective | undefined) => boolean } */
        let isMoveUp

        if (isVBindObject(node)) {
          // prev, v-bind:foo, v-bind -> v-bind:foo, v-bind, prev
          isMoveUp = isVAttributeOrVBindOrVModel
        } else if (isVAttributeOrVBindOrVModel(node)) {
          // prev, v-bind, v-bind:foo -> v-bind, v-bind:foo, prev
          isMoveUp = isVBindObject
        } else {
          isMoveUp = () => false
        }

        const previousNodes = attributes.slice(
          attributes.indexOf(previousNode),
          attributes.indexOf(node)
        )
        const moveNodes = [node]
        for (const node of previousNodes) {
          if (isMoveUp(node)) {
            moveNodes.unshift(node)
          } else {
            moveNodes.push(node)
          }
        }

        return moveNodes.map((moveNode, index) => {
          const text = sourceCode.getText(moveNode)
          return fixer.replaceText(previousNodes[index] || node, text)
        })
      }
    })
  }

  return utils.defineTemplateBodyVisitor(context, {
    VStartTag(node) {
      const attributeAndPositions = getAttributeAndPositionList(node)
      if (attributeAndPositions.length <= 1) {
        return
      }

      let { attr: previousNode, position: previousPosition } =
        attributeAndPositions[0]
      for (let index = 1; index < attributeAndPositions.length; index++) {
        const { attr, position } = attributeAndPositions[index]

        let valid = previousPosition <= position
        if (valid && alphabetical && previousPosition === position) {
          valid = isAlphabetical(previousNode, attr, sourceCode)
        }
        if (valid) {
          previousNode = attr
          previousPosition = position
        } else {
          reportIssue(attr, previousNode)
        }
      }
    }
  })

  /**
   * @param {VStartTag} node
   * @returns { { attr: ( VAttribute | VDirective ), position: number }[] }
   */
  function getAttributeAndPositionList(node) {
    const attributes = node.attributes.filter((node, index, attributes) => {
      if (
        isVBindObject(node) &&
        (isVAttributeOrVBindOrVModel(attributes[index - 1]) ||
          isVAttributeOrVBindOrVModel(attributes[index + 1]))
      ) {
        // In Vue 3, ignore `v-bind="object"`, which is
        // a pair of `v-bind:foo="..."` and `v-bind="object"` and
        // a pair of `v-model="..."` and `v-bind="object"`,
        // because changing the order behaves differently.
        return false
      }
      return true
    })

    const results = []
    for (const [index, attr] of attributes.entries()) {
      const position = getPositionFromAttrIndex(index)
      if (position == null) {
        // The omitted order is skipped.
        continue
      }
      results.push({ attr, position })
    }

    return results

    /**
     * @param {number} index
     * @returns {number | null}
     */
    function getPositionFromAttrIndex(index) {
      const node = attributes[index]
      if (isVBindObject(node)) {
        // node is `v-bind ="object"` syntax

        // In Vue 3, if change the order of `v-bind:foo="..."`, `v-model="..."` and `v-bind="object"`,
        // the behavior will be different, so adjust so that there is no change in behavior.

        const len = attributes.length
        for (let nextIndex = index + 1; nextIndex < len; nextIndex++) {
          const next = attributes[nextIndex]

          if (isVAttributeOrVBindOrVModel(next) && !isVBindObject(next)) {
            // It is considered to be in the same order as the next bind prop node.
            return getPositionFromAttrIndex(nextIndex)
          }
        }
      }
      return getPosition(node, attributePosition)
    }
  }
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce order of attributes',
      categories: ['vue3-recommended', 'vue2-recommended'],
      url: 'https://eslint.vuejs.org/rules/attributes-order.html'
    },
    fixable: 'code',
    schema: [
      {
        type: 'object',
        properties: {
          order: {
            type: 'array',
            items: {
              anyOf: [
                { enum: Object.values(ATTRS) },
                {
                  type: 'array',
                  items: {
                    enum: Object.values(ATTRS),
                    uniqueItems: true,
                    additionalItems: false
                  }
                }
              ]
            },
            uniqueItems: true,
            additionalItems: false
          },
          alphabetical: { type: 'boolean' }
        },
        additionalProperties: false
      }
    ],
    messages: {
      expectedOrder: `Attribute "{{currentNode}}" should go before "{{prevNode}}".`
    }
  },
  create
}


code:
/**
 * @fileoverview Disallow use other than available `lang`
 * @author Yosuke Ota
 */
'use strict'
const utils = require('../utils')

/**
 * @typedef {object} BlockOptions
 * @property {Set<string>} lang
 * @property {boolean} allowNoLang
 */
/**
 * @typedef { { [element: string]: BlockOptions | undefined } } Options
 */
/**
 * @typedef {object} UserBlockOptions
 * @property {string[] | string} [lang]
 * @property {boolean} [allowNoLang]
 */
/**
 * @typedef { { [element: string]: UserBlockOptions | undefined } } UserOptions
 */

/**
 * https://vuejs.github.io/vetur/guide/highlighting.html
 * <template lang="html"></template>
 * <style lang="css"></style>
 * <script lang="js"></script>
 * <script lang="javascript"></script>
 * @type {Record<string, string[] | undefined>}
 */
const DEFAULT_LANGUAGES = {
  template: ['html'],
  style: ['css'],
  script: ['js', 'javascript']
}

/**
 * @param {NonNullable<BlockOptions['lang']>} lang
 */
function getAllowsLangPhrase(lang) {
  const langs = [...lang].map((s) => `"${s}"`)
  switch (langs.length) {
    case 1: {
      return langs[0]
    }
    default: {
      return `${langs.slice(0, -1).join(', ')}, and ${langs[langs.length - 1]}`
    }
  }
}

/**
 * Normalizes a given option.
 * @param {string} blockName The block name.
 * @param {UserBlockOptions} option An option to parse.
 * @returns {BlockOptions} Normalized option.
 */
function normalizeOption(blockName, option) {
  /** @type {Set<string>} */
  let lang

  if (Array.isArray(option.lang)) {
    lang = new Set(option.lang)
  } else if (typeof option.lang === 'string') {
    lang = new Set([option.lang])
  } else {
    lang = new Set()
  }

  let hasDefault = false
  for (const def of DEFAULT_LANGUAGES[blockName] || []) {
    if (lang.has(def)) {
      lang.delete(def)
      hasDefault = true
    }
  }
  if (lang.size === 0) {
    return {
      lang,
      allowNoLang: true
    }
  }
  return {
    lang,
    allowNoLang: hasDefault || Boolean(option.allowNoLang)
  }
}
/**
 * Normalizes a given options.
 * @param { UserOptions } options An option to parse.
 * @returns {Options} Normalized option.
 */
function normalizeOptions(options) {
  if (!options) {
    return {}
  }

  /** @type {Options} */
  const normalized = {}

  for (const blockName of Object.keys(options)) {
    const value = options[blockName]
    if (value) {
      normalized[blockName] = normalizeOption(blockName, value)
    }
  }

  return normalized
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow use other than available `lang`',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/block-lang.html'
    },
    schema: [
      {
        type: 'object',
        patternProperties: {
          '^(?:\\S+)$': {
            oneOf: [
              {
                type: 'object',
                properties: {
                  lang: {
                    anyOf: [
                      { type: 'string' },
                      {
                        type: 'array',
                        items: {
                          type: 'string'
                        },
                        uniqueItems: true,
                        additionalItems: false
                      }
                    ]
                  },
                  allowNoLang: { type: 'boolean' }
                },
                additionalProperties: false
              }
            ]
          }
        },
        minProperties: 1,
        additionalProperties: false
      }
    ],
    messages: {
      expected:
        "Only {{allows}} can be used for the 'lang' attribute of '<{{tag}}>'.",
      missing: "The 'lang' attribute of '<{{tag}}>' is missing.",
      unexpected: "Do not specify the 'lang' attribute of '<{{tag}}>'.",
      useOrNot:
        "Only {{allows}} can be used for the 'lang' attribute of '<{{tag}}>'. Or, not specifying the `lang` attribute is allowed.",
      unexpectedDefault:
        "Do not explicitly specify the default language for the 'lang' attribute of '<{{tag}}>'."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = normalizeOptions(
      context.options[0] || {
        script: { allowNoLang: true },
        template: { allowNoLang: true },
        style: { allowNoLang: true }
      }
    )
    if (Object.keys(options).length === 0) {
      return {}
    }

    /**
     * @param {VElement} element
     * @returns {void}
     */
    function verify(element) {
      const tag = element.name
      const option = options[tag]
      if (!option) {
        return
      }
      const lang = utils.getAttribute(element, 'lang')
      if (lang == null || lang.value == null) {
        if (!option.allowNoLang) {
          context.report({
            node: element.startTag,
            messageId: 'missing',
            data: {
              tag
            }
          })
        }
        return
      }
      if (!option.lang.has(lang.value.value)) {
        let messageId
        if (!option.allowNoLang) {
          messageId = 'expected'
        } else if (option.lang.size === 0) {
          messageId = (DEFAULT_LANGUAGES[tag] || []).includes(lang.value.value)
            ? 'unexpectedDefault'
            : 'unexpected'
        } else {
          messageId = 'useOrNot'
        }
        context.report({
          node: lang,
          messageId,
          data: {
            tag,
            allows: getAllowsLangPhrase(option.lang)
          }
        })
      }
    }

    return utils.defineDocumentVisitor(context, {
      'VDocumentFragment > VElement': verify
    })
  }
}

license: mit


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

const utils = require('../utils')
const { parseSelector } = require('../utils/selector')

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

const DEFAULT_ORDER = Object.freeze([['script', 'template'], 'style'])

/**
 * @param {VElement} element
 * @return {string}
 */
function getAttributeString(element) {
  return element.startTag.attributes
    .map((attribute) => {
      if (attribute.value && attribute.value.type !== 'VLiteral') {
        return ''
      }

      return `${attribute.key.name}${
        attribute.value && attribute.value.value
          ? `=${attribute.value.value}`
          : ''
      }`
    })
    .join(' ')
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce order of component top-level elements',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/block-order.html'
    },
    fixable: 'code',
    schema: [
      {
        type: 'object',
        properties: {
          order: {
            type: 'array',
            items: {
              anyOf: [
                { type: 'string' },
                { type: 'array', items: { type: 'string' }, uniqueItems: true }
              ]
            },
            uniqueItems: true,
            additionalItems: false
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      unexpected:
        "'<{{elementName}}{{elementAttributes}}>' should be above '<{{firstUnorderedName}}{{firstUnorderedAttributes}}>' on line {{line}}."
    }
  },
  /**
   * @param {RuleContext} context - The rule context.
   * @returns {RuleListener} AST event handlers.
   */
  create(context) {
    /**
     * @typedef {object} OrderElement
     * @property {string} selectorText
     * @property {VElementSelector} selector
     * @property {number} index
     */
    /** @type {OrderElement[]} */
    const orders = []
    /** @type {(string|string[])[]} */
    const orderOptions =
      (context.options[0] && context.options[0].order) || DEFAULT_ORDER
    for (const [index, selectorOrSelectors] of orderOptions.entries()) {
      if (Array.isArray(selectorOrSelectors)) {
        for (const selector of selectorOrSelectors) {
          orders.push({
            selectorText: selector,
            selector: parseSelector(selector, context),
            index
          })
        }
      } else {
        orders.push({
          selectorText: selectorOrSelectors,
          selector: parseSelector(selectorOrSelectors, context),
          index
        })
      }
    }

    /**
     * @param {VElement} element
     */
    function getOrderElement(element) {
      return orders.find((o) => o.selector.test(element))
    }
    const sourceCode = context.getSourceCode()
    const documentFragment =
      sourceCode.parserServices.getDocumentFragment &&
      sourceCode.parserServices.getDocumentFragment()

    function getTopLevelHTMLElements() {
      if (documentFragment) {
        return documentFragment.children.filter(utils.isVElement)
      }
      return []
    }

    return {
      Program(node) {
        if (utils.hasInvalidEOF(node)) {
          return
        }
        const elements = getTopLevelHTMLElements()

        const elementsWithOrder = elements.flatMap((element) => {
          const order = getOrderElement(element)
          return order ? [{ order, element }] : []
        })
        const sourceCode = context.getSourceCode()
        for (const [index, elementWithOrders] of elementsWithOrder.entries()) {
          const { order: expected, element } = elementWithOrders
          const firstUnordered = elementsWithOrder
            .slice(0, index)
            .filter(({ order }) => expected.index < order.index)
            .sort((e1, e2) => e1.order.index - e2.order.index)[0]
          if (firstUnordered) {
            const firstUnorderedAttributes = getAttributeString(
              firstUnordered.element
            )
            const elementAttributes = getAttributeString(element)

            context.report({
              node: element,
              loc: element.loc,
              messageId: 'unexpected',
              data: {
                elementName: element.name,
                elementAttributes: elementAttributes
                  ? ` ${elementAttributes}`
                  : '',
                firstUnorderedName: firstUnordered.element.name,
                firstUnorderedAttributes: firstUnorderedAttributes
                  ? ` ${firstUnorderedAttributes}`
                  : '',
                line: firstUnordered.element.loc.start.line
              },
              *fix(fixer) {
                // insert element before firstUnordered
                const fixedElements = elements.flatMap((it) => {
                  if (it === firstUnordered.element) {
                    return [element, it]
                  } else if (it === element) {
                    return []
                  }
                  return [it]
                })
                for (let i = elements.length - 1; i >= 0; i--) {
                  if (elements[i] !== fixedElements[i]) {
                    yield fixer.replaceTextRange(
                      elements[i].range,
                      sourceCode.text.slice(...fixedElements[i].range)
                    )
                  }
                }
              }
            })
          }
        }
      }
    }
  }
}


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

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

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


code:
/**
 * @fileoverview Enforce line breaks style after opening and before closing block-level tags.
 * @author Yosuke Ota
 */
'use strict'
const utils = require('../utils')

/**
 * @typedef { 'always' | 'never' | 'consistent' | 'ignore' } OptionType
 * @typedef { { singleline?: OptionType, multiline?: OptionType, maxEmptyLines?: number } } ContentsOptions
 * @typedef { ContentsOptions & { blocks?: { [element: string]: ContentsOptions } } } Options
 * @typedef { Required<ContentsOptions> } ArgsOptions
 */

/**
 * @param {string} text Source code as a string.
 * @returns {number}
 */
function getLinebreakCount(text) {
  return text.split(/\r\n|[\r\n\u2028\u2029]/gu).length - 1
}

/**
 * @param {number} lineBreaks
 */
function getPhrase(lineBreaks) {
  switch (lineBreaks) {
    case 1: {
      return '1 line break'
    }
    default: {
      return `${lineBreaks} line breaks`
    }
  }
}

const ENUM_OPTIONS = { enum: ['always', 'never', 'consistent', 'ignore'] }
module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description:
        'enforce line breaks after opening and before closing block-level tags',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/block-tag-newline.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        type: 'object',
        properties: {
          singleline: ENUM_OPTIONS,
          multiline: ENUM_OPTIONS,
          maxEmptyLines: { type: 'number', minimum: 0 },
          blocks: {
            type: 'object',
            patternProperties: {
              '^(?:\\S+)$': {
                type: 'object',
                properties: {
                  singleline: ENUM_OPTIONS,
                  multiline: ENUM_OPTIONS,
                  maxEmptyLines: { type: 'number', minimum: 0 }
                },
                additionalProperties: false
              }
            },
            additionalProperties: false
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      unexpectedOpeningLinebreak:
        "There should be no line break after '<{{tag}}>'.",
      expectedOpeningLinebreak:
        "Expected {{expected}} after '<{{tag}}>', but {{actual}} found.",
      expectedClosingLinebreak:
        "Expected {{expected}}  before '</{{tag}}>', but {{actual}} found.",
      missingOpeningLinebreak: "A line break is required after '<{{tag}}>'.",
      missingClosingLinebreak: "A line break is required before '</{{tag}}>'."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()
    const df =
      sourceCode.parserServices.getDocumentFragment &&
      sourceCode.parserServices.getDocumentFragment()
    if (!df) {
      return {}
    }

    /**
     * @param {VStartTag} startTag
     * @param {string} beforeText
     * @param {number} beforeLinebreakCount
     * @param {'always' | 'never'} beforeOption
     * @param {number} maxEmptyLines
     * @returns {void}
     */
    function verifyBeforeSpaces(
      startTag,
      beforeText,
      beforeLinebreakCount,
      beforeOption,
      maxEmptyLines
    ) {
      if (beforeOption === 'always') {
        if (beforeLinebreakCount === 0) {
          context.report({
            loc: {
              start: startTag.loc.end,
              end: startTag.loc.end
            },
            messageId: 'missingOpeningLinebreak',
            data: { tag: startTag.parent.name },
            fix(fixer) {
              return fixer.insertTextAfter(startTag, '\n')
            }
          })
        } else if (maxEmptyLines < beforeLinebreakCount - 1) {
          context.report({
            loc: {
              start: startTag.loc.end,
              end: sourceCode.getLocFromIndex(
                startTag.range[1] + beforeText.length
              )
            },
            messageId: 'expectedOpeningLinebreak',
            data: {
              tag: startTag.parent.name,
              expected: getPhrase(maxEmptyLines + 1),
              actual: getPhrase(beforeLinebreakCount)
            },
            fix(fixer) {
              return fixer.replaceTextRange(
                [startTag.range[1], startTag.range[1] + beforeText.length],
                '\n'.repeat(maxEmptyLines + 1)
              )
            }
          })
        }
      } else {
        if (beforeLinebreakCount > 0) {
          context.report({
            loc: {
              start: startTag.loc.end,
              end: sourceCode.getLocFromIndex(
                startTag.range[1] + beforeText.length
              )
            },
            messageId: 'unexpectedOpeningLinebreak',
            data: { tag: startTag.parent.name },
            fix(fixer) {
              return fixer.removeRange([
                startTag.range[1],
                startTag.range[1] + beforeText.length
              ])
            }
          })
        }
      }
    }
    /**
     * @param {VEndTag} endTag
     * @param {string} afterText
     * @param {number} afterLinebreakCount
     * @param {'always' | 'never'} afterOption
     * @param {number} maxEmptyLines
     * @returns {void}
     */
    function verifyAfterSpaces(
      endTag,
      afterText,
      afterLinebreakCount,
      afterOption,
      maxEmptyLines
    ) {
      if (afterOption === 'always') {
        if (afterLinebreakCount === 0) {
          context.report({
            loc: {
              start: endTag.loc.start,
              end: endTag.loc.start
            },
            messageId: 'missingClosingLinebreak',
            data: { tag: endTag.parent.name },
            fix(fixer) {
              return fixer.insertTextBefore(endTag, '\n')
            }
          })
        } else if (maxEmptyLines < afterLinebreakCount - 1) {
          context.report({
            loc: {
              start: sourceCode.getLocFromIndex(
                endTag.range[0] - afterText.length
              ),
              end: endTag.loc.start
            },
            messageId: 'expectedClosingLinebreak',
            data: {
              tag: endTag.parent.name,
              expected: getPhrase(maxEmptyLines + 1),
              actual: getPhrase(afterLinebreakCount)
            },
            fix(fixer) {
              return fixer.replaceTextRange(
                [endTag.range[0] - afterText.length, endTag.range[0]],
                '\n'.repeat(maxEmptyLines + 1)
              )
            }
          })
        }
      } else {
        if (afterLinebreakCount > 0) {
          context.report({
            loc: {
              start: sourceCode.getLocFromIndex(
                endTag.range[0] - afterText.length
              ),
              end: endTag.loc.start
            },
            messageId: 'unexpectedOpeningLinebreak',
            data: { tag: endTag.parent.name },
            fix(fixer) {
              return fixer.removeRange([
                endTag.range[0] - afterText.length,
                endTag.range[0]
              ])
            }
          })
        }
      }
    }
    /**
     * @param {VElement} element
     * @param {ArgsOptions} options
     * @returns {void}
     */
    function verifyElement(element, options) {
      const { startTag, endTag } = element
      if (startTag.selfClosing || endTag == null) {
        return
      }
      const text = sourceCode.text.slice(startTag.range[1], endTag.range[0])

      const trimText = text.trim()
      if (!trimText) {
        return
      }

      const option =
        options.multiline !== options.singleline &&
        /[\n\r\u2028\u2029]/u.test(text.trim())
          ? options.multiline
          : options.singleline
      if (option === 'ignore') {
        return
      }
      const beforeText = /** @type {RegExpExecArray} */ (/^\s*/u.exec(text))[0]
      const afterText = /** @type {RegExpExecArray} */ (/\s*$/u.exec(text))[0]
      const beforeLinebreakCount = getLinebreakCount(beforeText)
      const afterLinebreakCount = getLinebreakCount(afterText)

      /** @type {'always' | 'never'} */
      let beforeOption
      /** @type {'always' | 'never'} */
      let afterOption
      if (option === 'always' || option === 'never') {
        beforeOption = option
        afterOption = option
      } else {
        // consistent
        if (beforeLinebreakCount > 0 === afterLinebreakCount > 0) {
          return
        }
        beforeOption = 'always'
        afterOption = 'always'
      }

      verifyBeforeSpaces(
        startTag,
        beforeText,
        beforeLinebreakCount,
        beforeOption,
        options.maxEmptyLines
      )

      verifyAfterSpaces(
        endTag,
        afterText,
        afterLinebreakCount,
        afterOption,
        options.maxEmptyLines
      )
    }

    /**
     * Normalizes a given option value.
     * @param { Options | undefined } option An option value to parse.
     * @returns { (element: VElement) => void } Verify function.
     */
    function normalizeOptionValue(option) {
      if (!option) {
        return normalizeOptionValue({})
      }

      /** @type {ContentsOptions} */
      const contentsOptions = option
      /** @type {ArgsOptions} */
      const options = {
        singleline: contentsOptions.singleline || 'consistent',
        multiline: contentsOptions.multiline || 'always',
        maxEmptyLines: contentsOptions.maxEmptyLines || 0
      }
      const { blocks } = option
      if (!blocks) {
        return (element) => verifyElement(element, options)
      }

      return (element) => {
        const { name } = element
        const elementsOptions = blocks[name]
        if (elementsOptions) {
          normalizeOptionValue({
            singleline: elementsOptions.singleline || options.singleline,
            multiline: elementsOptions.multiline || options.multiline,
            maxEmptyLines:
              elementsOptions.maxEmptyLines == null
                ? options.maxEmptyLines
                : elementsOptions.maxEmptyLines
          })(element)
        } else {
          verifyElement(element, options)
        }
      }
    }

    const documentFragment = df

    const verify = normalizeOptionValue(context.options[0])

    return utils.defineTemplateBodyVisitor(
      context,
      {},
      {
        /** @param {Program} node */
        Program(node) {
          if (utils.hasInvalidEOF(node)) {
            return
          }

          for (const element of documentFragment.children) {
            if (utils.isVElement(element)) {
              verify(element)
            }
          }
        }
      }
    )
  }
}


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

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

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


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

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

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


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

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

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


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

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

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


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

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

// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapStylisticOrCoreRule('comma-style', {
  create(_context, { baseHandlers }) {
    return {
      VSlotScopeExpression(node) {
        if (baseHandlers.FunctionExpression) {
          // @ts-expect-error -- Process params of VSlotScopeExpression as FunctionExpression.
          baseHandlers.FunctionExpression(node)
        }
      }
    }
  }
})


code:
/**
 * @author Toru Nagashima <https://github.com/mysticatea>
 */
/* eslint-disable eslint-plugin/report-message-format */

'use strict'

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

/**
 * @typedef {object} RuleAndLocation
 * @property {string} RuleAndLocation.ruleId
 * @property {number} RuleAndLocation.index
 * @property {string} [RuleAndLocation.key]
 */

const COMMENT_DIRECTIVE_B = /^\s*(eslint-(?:en|dis)able)(?:\s+|$)/
const COMMENT_DIRECTIVE_L = /^\s*(eslint-disable(?:-next)?-line)(?:\s+|$)/

/**
 * Remove the ignored part from a given directive comment and trim it.
 * @param {string} value The comment text to strip.
 * @returns {string} The stripped text.
 */
function stripDirectiveComment(value) {
  return value.split(/\s-{2,}\s/u)[0]
}

/**
 * Parse a given comment.
 * @param {RegExp} pattern The RegExp pattern to parse.
 * @param {string} comment The comment value to parse.
 * @returns {({type:string,rules:RuleAndLocation[]})|null} The parsing result.
 */
function parse(pattern, comment) {
  const text = stripDirectiveComment(comment)
  const match = pattern.exec(text)
  if (match == null) {
    return null
  }

  const type = match[1]

  /** @type {RuleAndLocation[]} */
  const rules = []

  const rulesRe = /([^\s,]+)[\s,]*/g
  let startIndex = match[0].length
  rulesRe.lastIndex = startIndex

  let res
  while ((res = rulesRe.exec(text))) {
    const ruleId = res[1].trim()
    rules.push({
      ruleId,
      index: startIndex
    })
    startIndex = rulesRe.lastIndex
  }

  return { type, rules }
}

/**
 * Enable rules.
 * @param {RuleContext} context The rule context.
 * @param {{line:number,column:number}} loc The location information to enable.
 * @param { 'block' | 'line' } group The group to enable.
 * @param {string | null} rule The rule ID to enable.
 * @returns {void}
 */
function enable(context, loc, group, rule) {
  if (rule) {
    context.report({
      loc,
      messageId: group === 'block' ? 'enableBlockRule' : 'enableLineRule',
      data: { rule }
    })
  } else {
    context.report({
      loc,
      messageId: group === 'block' ? 'enableBlock' : 'enableLine'
    })
  }
}

/**
 * Disable rules.
 * @param {RuleContext} context The rule context.
 * @param {{line:number,column:number}} loc The location information to disable.
 * @param { 'block' | 'line' } group The group to disable.
 * @param {string | null} rule The rule ID to disable.
 * @param {string} key The disable directive key.
 * @returns {void}
 */
function disable(context, loc, group, rule, key) {
  if (rule) {
    context.report({
      loc,
      messageId: group === 'block' ? 'disableBlockRule' : 'disableLineRule',
      data: { rule, key }
    })
  } else {
    context.report({
      loc,
      messageId: group === 'block' ? 'disableBlock' : 'disableLine',
      data: { key }
    })
  }
}

/**
 * Process a given comment token.
 * If the comment is `eslint-disable` or `eslint-enable` then it reports the comment.
 * @param {RuleContext} context The rule context.
 * @param {Token} comment The comment token to process.
 * @param {boolean} reportUnusedDisableDirectives To report unused eslint-disable comments.
 * @returns {void}
 */
function processBlock(context, comment, reportUnusedDisableDirectives) {
  const parsed = parse(COMMENT_DIRECTIVE_B, comment.value)
  if (parsed === null) return

  if (parsed.type === 'eslint-disable') {
    if (parsed.rules.length > 0) {
      const rules = reportUnusedDisableDirectives
        ? reportUnusedRules(context, comment, parsed.type, parsed.rules)
        : parsed.rules
      for (const rule of rules) {
        disable(
          context,
          comment.loc.start,
          'block',
          rule.ruleId,
          rule.key || '*'
        )
      }
    } else {
      const key = reportUnusedDisableDirectives
        ? reportUnused(context, comment, parsed.type)
        : ''
      disable(context, comment.loc.start, 'block', null, key)
    }
  } else {
    if (parsed.rules.length > 0) {
      for (const rule of parsed.rules) {
        enable(context, comment.loc.start, 'block', rule.ruleId)
      }
    } else {
      enable(context, comment.loc.start, 'block', null)
    }
  }
}

/**
 * Process a given comment token.
 * If the comment is `eslint-disable-line` or `eslint-disable-next-line` then it reports the comment.
 * @param {RuleContext} context The rule context.
 * @param {Token} comment The comment token to process.
 * @param {boolean} reportUnusedDisableDirectives To report unused eslint-disable comments.
 * @returns {void}
 */
function processLine(context, comment, reportUnusedDisableDirectives) {
  const parsed = parse(COMMENT_DIRECTIVE_L, comment.value)
  if (parsed != null && comment.loc.start.line === comment.loc.end.line) {
    const line =
      comment.loc.start.line + (parsed.type === 'eslint-disable-line' ? 0 : 1)
    const column = -1
    if (parsed.rules.length > 0) {
      const rules = reportUnusedDisableDirectives
        ? reportUnusedRules(context, comment, parsed.type, parsed.rules)
        : parsed.rules
      for (const rule of rules) {
        disable(context, { line, column }, 'line', rule.ruleId, rule.key || '')
        enable(context, { line: line + 1, column }, 'line', rule.ruleId)
      }
    } else {
      const key = reportUnusedDisableDirectives
        ? reportUnused(context, comment, parsed.type)
        : ''
      disable(context, { line, column }, 'line', null, key)
      enable(context, { line: line + 1, column }, 'line', null)
    }
  }
}

/**
 * Reports unused disable directive.
 * Do not check the use of directives here. Filter the directives used with postprocess.
 * @param {RuleContext} context The rule context.
 * @param {Token} comment The comment token to report.
 * @param {string} kind The comment directive kind.
 * @returns {string} The report key
 */
function reportUnused(context, comment, kind) {
  const loc = comment.loc

  context.report({
    loc,
    messageId: 'unused',
    data: { kind }
  })

  return locToKey(loc.start)
}

/**
 * Reports unused disable directive rules.
 * Do not check the use of directives here. Filter the directives used with postprocess.
 * @param {RuleContext} context The rule context.
 * @param {Token} comment The comment token to report.
 * @param {string} kind The comment directive kind.
 * @param {RuleAndLocation[]} rules To report rule.
 * @returns { { ruleId: string, key: string }[] }
 */
function reportUnusedRules(context, comment, kind, rules) {
  const sourceCode = context.getSourceCode()
  const commentStart = comment.range[0] + 4 /* <!-- */

  return rules.map((rule) => {
    const start = sourceCode.getLocFromIndex(commentStart + rule.index)
    const end = sourceCode.getLocFromIndex(
      commentStart + rule.index + rule.ruleId.length
    )

    context.report({
      loc: { start, end },
      messageId: 'unusedRule',
      data: { rule: rule.ruleId, kind }
    })

    return {
      ruleId: rule.ruleId,
      key: locToKey(start)
    }
  })
}

/**
 * Gets the key of location
 * @param {Position} location The location
 * @returns {string} The key
 */
function locToKey(location) {
  return `line:${location.line},column${location.column}`
}

/**
 * Extracts the top-level elements in document fragment.
 * @param {VDocumentFragment} documentFragment The document fragment.
 * @returns {VElement[]} The top-level elements
 */
function extractTopLevelHTMLElements(documentFragment) {
  return documentFragment.children.filter(utils.isVElement)
}
/**
 * Extracts the top-level comments in document fragment.
 * @param {VDocumentFragment} documentFragment The document fragment.
 * @returns {Token[]} The top-level comments
 */
function extractTopLevelDocumentFragmentComments(documentFragment) {
  const elements = extractTopLevelHTMLElements(documentFragment)

  return documentFragment.comments.filter((comment) =>
    elements.every(
      (element) =>
        comment.range[1] <= element.range[0] ||
        element.range[1] <= comment.range[0]
    )
  )
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'support comment-directives in `<template>`', // eslint-disable-line eslint-plugin/require-meta-docs-description
      categories: ['base'],
      url: 'https://eslint.vuejs.org/rules/comment-directive.html'
    },
    schema: [
      {
        type: 'object',
        properties: {
          reportUnusedDisableDirectives: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      disableBlock: '--block {{key}}',
      enableBlock: '++block',
      disableLine: '--line {{key}}',
      enableLine: '++line',
      disableBlockRule: '-block {{rule}} {{key}}',
      enableBlockRule: '+block {{rule}}',
      disableLineRule: '-line {{rule}} {{key}}',
      enableLineRule: '+line {{rule}}',
      clear: 'clear',

      unused: 'Unused {{kind}} directive (no problems were reported).',
      unusedRule:
        "Unused {{kind}} directive (no problems were reported from '{{rule}}')."
    }
  },
  /**
   * @param {RuleContext} context - The rule context.
   * @returns {RuleListener} AST event handlers.
   */
  create(context) {
    const options = context.options[0] || {}
    /** @type {boolean} */
    const reportUnusedDisableDirectives = options.reportUnusedDisableDirectives
    const sourceCode = context.getSourceCode()
    const documentFragment =
      sourceCode.parserServices.getDocumentFragment &&
      sourceCode.parserServices.getDocumentFragment()

    return {
      Program(node) {
        if (node.templateBody) {
          // Send directives to the post-process.
          for (const comment of node.templateBody.comments) {
            processBlock(context, comment, reportUnusedDisableDirectives)
            processLine(context, comment, reportUnusedDisableDirectives)
          }

          // Send a clear mark to the post-process.
          context.report({
            loc: node.templateBody.loc.end,
            messageId: 'clear'
          })
        }
        if (documentFragment) {
          // Send directives to the post-process.
          for (const comment of extractTopLevelDocumentFragmentComments(
            documentFragment
          )) {
            processBlock(context, comment, reportUnusedDisableDirectives)
            processLine(context, comment, reportUnusedDisableDirectives)
          }

          // Send a clear mark to the post-process.
          for (const element of extractTopLevelHTMLElements(documentFragment)) {
            context.report({
              loc: element.loc.end,
              messageId: 'clear'
            })
          }
        }
      }
    }
  }
}


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

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

/**
 * @typedef { 'script-setup' | 'composition' | 'composition-vue2' | 'options' } PreferOption
 *
 * @typedef {PreferOption[]} UserPreferOption
 *
 * @typedef {object} NormalizeOptions
 * @property {object} allowsSFC
 * @property {boolean} [allowsSFC.scriptSetup]
 * @property {boolean} [allowsSFC.composition]
 * @property {boolean} [allowsSFC.compositionVue2]
 * @property {boolean} [allowsSFC.options]
 * @property {object} allowsOther
 * @property {boolean} [allowsOther.composition]
 * @property {boolean} [allowsOther.compositionVue2]
 * @property {boolean} [allowsOther.options]
 */

/** @type {PreferOption[]} */
const STYLE_OPTIONS = [
  'script-setup',
  'composition',
  'composition-vue2',
  'options'
]

/**
 * Normalize options.
 * @param {any[]} options The options user configured.
 * @returns {NormalizeOptions} The normalized options.
 */
function parseOptions(options) {
  /** @type {NormalizeOptions} */
  const opts = { allowsSFC: {}, allowsOther: {} }

  /** @type {UserPreferOption} */
  const preferOptions = options[0] || ['script-setup', 'composition']
  for (const prefer of preferOptions) {
    switch (prefer) {
      case 'script-setup': {
        opts.allowsSFC.scriptSetup = true
        break
      }
      case 'composition': {
        opts.allowsSFC.composition = true
        opts.allowsOther.composition = true
        break
      }
      case 'composition-vue2': {
        opts.allowsSFC.compositionVue2 = true
        opts.allowsOther.compositionVue2 = true
        break
      }
      case 'options': {
        opts.allowsSFC.options = true
        opts.allowsOther.options = true
        break
      }
    }
  }

  if (
    !opts.allowsOther.composition &&
    !opts.allowsOther.compositionVue2 &&
    !opts.allowsOther.options
  ) {
    opts.allowsOther.composition = true
    opts.allowsOther.compositionVue2 = true
    opts.allowsOther.options = true
  }

  return opts
}

const OPTIONS_API_OPTIONS = new Set([
  'mixins',
  'extends',
  // state
  'data',
  'computed',
  'methods',
  'watch',
  'provide',
  'inject',
  // lifecycle
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'activated',
  'deactivated',
  'beforeDestroy',
  'beforeUnmount',
  'destroyed',
  'unmounted',
  'render',
  'renderTracked',
  'renderTriggered',
  'errorCaptured',
  // public API
  'expose'
])
const COMPOSITION_API_OPTIONS = new Set(['setup'])

const COMPOSITION_API_VUE2_OPTIONS = new Set([
  'setup',
  'render', // https://github.com/vuejs/composition-api#template-refs
  'renderTracked', // https://github.com/vuejs/composition-api#missing-apis
  'renderTriggered' // https://github.com/vuejs/composition-api#missing-apis
])

const LIFECYCLE_HOOK_OPTIONS = new Set([
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'activated',
  'deactivated',
  'beforeDestroy',
  'beforeUnmount',
  'destroyed',
  'unmounted',
  'renderTracked',
  'renderTriggered',
  'errorCaptured'
])

/**
 * @typedef { 'script-setup' | 'composition' | 'options' } ApiStyle
 */

/**
 * @param {object} allowsOpt
 * @param {boolean} [allowsOpt.scriptSetup]
 * @param {boolean} [allowsOpt.composition]
 * @param {boolean} [allowsOpt.compositionVue2]
 * @param {boolean} [allowsOpt.options]
 */
function buildAllowedPhrase(allowsOpt) {
  const phrases = []
  if (allowsOpt.scriptSetup) {
    phrases.push('`<script setup>`')
  }
  if (allowsOpt.composition) {
    phrases.push('Composition API')
  }
  if (allowsOpt.compositionVue2) {
    phrases.push('Composition API (Vue 2)')
  }
  if (allowsOpt.options) {
    phrases.push('Options API')
  }
  return phrases.length > 2
    ? `${phrases.slice(0, -1).join(', ')} or ${phrases.slice(-1)[0]}`
    : phrases.join(' or ')
}

/**
 * @param {object} allowsOpt
 * @param {boolean} [allowsOpt.scriptSetup]
 * @param {boolean} [allowsOpt.composition]
 * @param {boolean} [allowsOpt.compositionVue2]
 * @param {boolean} [allowsOpt.options]
 */
function isPreferScriptSetup(allowsOpt) {
  if (
    !allowsOpt.scriptSetup ||
    allowsOpt.composition ||
    allowsOpt.compositionVue2 ||
    allowsOpt.options
  ) {
    return false
  }
  return true
}

/**
 * @param {string} name
 */
function buildOptionPhrase(name) {
  if (LIFECYCLE_HOOK_OPTIONS.has(name)) return `\`${name}\` lifecycle hook`

  return name === 'setup' || name === 'render'
    ? `\`${name}\` function`
    : `\`${name}\` option`
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce component API style',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/component-api-style.html'
    },
    fixable: null,
    schema: [
      {
        type: 'array',
        items: {
          enum: STYLE_OPTIONS,
          uniqueItems: true,
          additionalItems: false
        },
        minItems: 1
      }
    ],
    messages: {
      disallowScriptSetup:
        '`<script setup>` is not allowed in your project. Use {{allowedApis}} instead.',
      disallowComponentOption:
        '{{disallowedApi}} is not allowed in your project. {{optionPhrase}} is part of the {{disallowedApi}}. Use {{allowedApis}} instead.',
      disallowComponentOptionPreferScriptSetup:
        '{{disallowedApi}} is not allowed in your project. Use `<script setup>` instead.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = parseOptions(context.options)

    return utils.compositingVisitors(
      {
        Program() {
          if (options.allowsSFC.scriptSetup) {
            return
          }
          const scriptSetup = utils.getScriptSetupElement(context)
          if (scriptSetup) {
            context.report({
              node: scriptSetup.startTag,
              messageId: 'disallowScriptSetup',
              data: {
                allowedApis: buildAllowedPhrase(options.allowsSFC)
              }
            })
          }
        }
      },
      utils.defineVueVisitor(context, {
        onVueObjectEnter(node) {
          const allows = utils.isSFCObject(context, node)
            ? options.allowsSFC
            : options.allowsOther
          if (
            (allows.composition || allows.compositionVue2) &&
            allows.options
          ) {
            return
          }
          const apis = [
            {
              allow: allows.composition,
              options: COMPOSITION_API_OPTIONS,
              apiName: 'Composition API'
            },
            {
              allow: allows.options,
              options: OPTIONS_API_OPTIONS,
              apiName: 'Options API'
            },
            {
              allow: allows.compositionVue2,
              options: COMPOSITION_API_VUE2_OPTIONS,
              apiName: 'Composition API (Vue 2)'
            }
          ]
          for (const prop of node.properties) {
            if (prop.type !== 'Property') {
              continue
            }
            const name = utils.getStaticPropertyName(prop)
            if (!name) {
              continue
            }
            const disallowApi =
              !apis.some((api) => api.allow && api.options.has(name)) &&
              apis.find((api) => !api.allow && api.options.has(name))

            if (disallowApi) {
              context.report({
                node: prop.key,
                messageId: isPreferScriptSetup(allows)
                  ? 'disallowComponentOptionPreferScriptSetup'
                  : 'disallowComponentOption',
                data: {
                  disallowedApi: disallowApi.apiName,
                  optionPhrase: buildOptionPhrase(name),
                  allowedApis: buildAllowedPhrase(allows)
                }
              })
            }
          }
        }
      })
    )
  }
}


code:
/**
 * @fileoverview enforce specific casing for component definition name
 * @author Armano
 */
'use strict'

const utils = require('../utils')
const casing = require('../utils/casing')
const allowedCaseOptions = ['PascalCase', 'kebab-case']

/**
 * @param {Expression | SpreadElement} node
 * @returns {node is (Literal | TemplateLiteral)}
 */
function canConvert(node) {
  return (
    node.type === 'Literal' ||
    (node.type === 'TemplateLiteral' &&
      node.expressions.length === 0 &&
      node.quasis.length === 1)
  )
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce specific casing for component definition name',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/component-definition-name-casing.html'
    },
    fixable: 'code',
    schema: [
      {
        enum: allowedCaseOptions
      }
    ],
    messages: {
      incorrectCase: 'Property name "{{value}}" is not {{caseType}}.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0]
    const caseType = allowedCaseOptions.includes(options)
      ? options
      : 'PascalCase'

    /**
     * @param {Literal | TemplateLiteral} node
     */
    function convertName(node) {
      /** @type {string} */
      let nodeValue
      /** @type {Range} */
      let range
      if (node.type === 'TemplateLiteral') {
        const quasis = node.quasis[0]
        nodeValue = quasis.value.cooked
        range = quasis.range
      } else {
        nodeValue = `${node.value}`
        range = node.range
      }

      if (!casing.getChecker(caseType)(nodeValue)) {
        context.report({
          node,
          messageId: 'incorrectCase',
          data: {
            value: nodeValue,
            caseType
          },
          fix: (fixer) =>
            fixer.replaceTextRange(
              [range[0] + 1, range[1] - 1],
              casing.getExactConverter(caseType)(nodeValue)
            )
        })
      }
    }

    return utils.compositingVisitors(
      utils.executeOnCallVueComponent(context, (node) => {
        if (node.arguments.length === 2) {
          const argument = node.arguments[0]

          if (canConvert(argument)) {
            convertName(argument)
          }
        }
      }),
      utils.executeOnVue(context, (obj) => {
        const node = utils.findProperty(obj, 'name')

        if (!node) return
        if (!canConvert(node.value)) return
        convertName(node.value)
      }),
      utils.defineScriptSetupVisitor(context, {
        onDefineOptionsEnter(node) {
          if (node.arguments.length === 0) return
          const define = node.arguments[0]
          if (define.type !== 'ObjectExpression') return
          const nameNode = utils.findProperty(define, 'name')
          if (!nameNode) return
          if (!canConvert(nameNode.value)) return
          convertName(nameNode.value)
        }
      })
    )
  }
}


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

const utils = require('../utils')
const casing = require('../utils/casing')
const { toRegExp } = require('../utils/regexp')

const allowedCaseOptions = ['PascalCase', 'kebab-case']
const defaultCase = 'PascalCase'

/**
 * Checks whether the given variable is the type-only import object.
 * @param {Variable} variable
 * @returns {boolean} `true` if the given variable is the type-only import.
 */
function isTypeOnlyImport(variable) {
  if (variable.defs.length === 0) return false

  return variable.defs.every((def) => {
    if (def.type !== 'ImportBinding') {
      return false
    }
    if (def.parent.importKind === 'type') {
      // check for `import type Foo from './xxx'`
      return true
    }
    if (def.node.type === 'ImportSpecifier' && def.node.importKind === 'type') {
      // check for `import { type Foo } from './xxx'`
      return true
    }
    return false
  })
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'enforce specific casing for the component naming style in template',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/component-name-in-template-casing.html'
    },
    fixable: 'code',
    schema: [
      {
        enum: allowedCaseOptions
      },
      {
        type: 'object',
        properties: {
          globals: {
            type: 'array',
            items: { type: 'string' },
            uniqueItems: true
          },
          ignores: {
            type: 'array',
            items: { type: 'string' },
            uniqueItems: true,
            additionalItems: false
          },
          registeredComponentsOnly: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      incorrectCase: 'Component name "{{name}}" is not {{caseType}}.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const caseOption = context.options[0]
    const options = context.options[1] || {}
    const caseType = allowedCaseOptions.includes(caseOption)
      ? caseOption
      : defaultCase
    /** @type {RegExp[]} */
    const ignores = (options.ignores || []).map(toRegExp)
    /** @type {string[]} */
    const globals = (options.globals || []).map(casing.pascalCase)
    const registeredComponentsOnly = options.registeredComponentsOnly !== false
    const sourceCode = context.getSourceCode()
    const tokens =
      sourceCode.parserServices.getTemplateBodyTokenStore &&
      sourceCode.parserServices.getTemplateBodyTokenStore()

    /** @type { Set<string> } */
    const registeredComponents = new Set(globals)

    if (utils.isScriptSetup(context)) {
      // For <script setup>
      const globalScope = context.getSourceCode().scopeManager.globalScope
      if (globalScope) {
        // Only check find the import module
        const moduleScope = globalScope.childScopes.find(
          (scope) => scope.type === 'module'
        )
        for (const variable of (moduleScope && moduleScope.variables) || []) {
          if (!isTypeOnlyImport(variable)) {
            registeredComponents.add(variable.name)
          }
        }
      }
    }

    /**
     * Checks whether the given node is the verification target node.
     * @param {VElement} node element node
     * @returns {boolean} `true` if the given node is the verification target node.
     */
    function isVerifyTarget(node) {
      if (ignores.some((re) => re.test(node.rawName))) {
        // ignore
        return false
      }

      if (
        (!utils.isHtmlElementNode(node) &&
          !utils.isSvgElementNode(node) &&
          !utils.isMathElementNode(node)) ||
        utils.isHtmlWellKnownElementName(node.rawName) ||
        utils.isSvgWellKnownElementName(node.rawName) ||
        utils.isMathWellKnownElementName(node.rawName) ||
        utils.isVueBuiltInElementName(node.rawName)
      ) {
        return false
      }

      if (!registeredComponentsOnly) {
        // If the user specifies registeredComponentsOnly as false, it checks all component tags.
        return true
      }

      // We only verify the registered components.
      return registeredComponents.has(casing.pascalCase(node.rawName))
    }

    let hasInvalidEOF = false

    return utils.defineTemplateBodyVisitor(
      context,
      {
        VElement(node) {
          if (hasInvalidEOF) {
            return
          }

          if (!isVerifyTarget(node)) {
            return
          }

          const name = node.rawName
          if (!casing.getChecker(caseType)(name)) {
            const startTag = node.startTag
            const open = tokens.getFirstToken(startTag)
            const casingName = casing.getExactConverter(caseType)(name)
            context.report({
              node: open,
              loc: open.loc,
              messageId: 'incorrectCase',
              data: {
                name,
                caseType
              },
              *fix(fixer) {
                yield fixer.replaceText(open, `<${casingName}`)
                const endTag = node.endTag
                if (endTag) {
                  const endTagOpen = tokens.getFirstToken(endTag)
                  yield fixer.replaceText(endTagOpen, `</${casingName}`)
                }
              }
            })
          }
        }
      },
      {
        Program(node) {
          hasInvalidEOF = utils.hasInvalidEOF(node)
        },
        ...(registeredComponentsOnly
          ? utils.executeOnVue(context, (obj) => {
              for (const n of utils.getRegisteredComponents(obj)) {
                registeredComponents.add(n.name)
              }
            })
          : {})
      }
    )
  }
}


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

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

/**
 * @param {import('../../typings/eslint-plugin-vue/util-types/ast').Expression} node
 * @returns {string | null}
 */
function getOptionsComponentName(node) {
  if (node.type === 'Identifier') {
    return node.name
  }
  if (node.type === 'Literal') {
    return typeof node.value === 'string' ? node.value : null
  }
  return null
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'enforce the casing of component name in `components` options',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/component-options-name-casing.html'
    },
    fixable: 'code',
    hasSuggestions: true,
    schema: [{ enum: casing.allowedCaseOptions }],
    messages: {
      caseNotMatched: 'Component name "{{component}}" is not {{caseType}}.',
      possibleRenaming: 'Rename component name to be in {{caseType}}.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const caseType = context.options[0] || 'PascalCase'

    const canAutoFix = caseType === 'PascalCase'
    const checkCase = casing.getChecker(caseType)
    const convert = casing.getConverter(caseType)

    return utils.executeOnVue(context, (obj) => {
      const node = utils.findProperty(obj, 'components')
      if (!node || node.value.type !== 'ObjectExpression') {
        return
      }

      for (const property of node.value.properties) {
        if (property.type !== 'Property') {
          continue
        }

        const name = getOptionsComponentName(property.key)
        if (!name || checkCase(name)) {
          continue
        }

        context.report({
          node: property.key,
          messageId: 'caseNotMatched',
          data: {
            component: name,
            caseType
          },
          fix: canAutoFix
            ? (fixer) => {
                const converted = convert(name)
                return property.shorthand
                  ? fixer.replaceText(property, `${converted}: ${name}`)
                  : fixer.replaceText(property.key, converted)
              }
            : undefined,
          suggest: canAutoFix
            ? undefined
            : [
                {
                  messageId: 'possibleRenaming',
                  data: { caseType },
                  fix: (fixer) => {
                    const converted = convert(name)
                    if (caseType === 'kebab-case') {
                      return property.shorthand
                        ? fixer.replaceText(property, `'${converted}': ${name}`)
                        : fixer.replaceText(property.key, `'${converted}'`)
                    }
                    return property.shorthand
                      ? fixer.replaceText(property, `${converted}: ${name}`)
                      : fixer.replaceText(property.key, converted)
                  }
                }
              ]
        })
      }
    })
  }
}


code:
'use strict'

const baseRule = require('./block-order')

module.exports = {
  // eslint-disable-next-line eslint-plugin/require-meta-schema, eslint-plugin/prefer-message-ids -- inherit schema from base rule
  meta: {
    ...baseRule.meta,
    // eslint-disable-next-line eslint-plugin/meta-property-ordering
    type: baseRule.meta.type,
    docs: {
      description: baseRule.meta.docs.description,
      categories: ['vue3-recommended', 'vue2-recommended'],
      url: 'https://eslint.vuejs.org/rules/component-tags-order.html'
    },
    deprecated: true,
    replacedBy: ['block-order']
  },
  /** @param {RuleContext} context */
  create(context) {
    return baseRule.create(context)
  }
}


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

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

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

const ALLOWED_CASE_OPTIONS = ['kebab-case', 'camelCase']
const DEFAULT_CASE = 'camelCase'

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

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

  if (callee.type === 'MemberExpression') {
    const name = utils.getStaticPropertyName(callee)
    if (name) {
      return { name, member: callee }
    }
  }
  return null
}

const OBJECT_OPTION_SCHEMA = {
  type: 'object',
  properties: {
    ignores: {
      type: 'array',
      items: { type: 'string' },
      uniqueItems: true,
      additionalItems: false
    }
  },
  additionalProperties: false
}
module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce specific casing for custom event name',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/custom-event-name-casing.html'
    },
    fixable: null,
    schema: {
      anyOf: [
        {
          type: 'array',
          items: [
            {
              enum: ALLOWED_CASE_OPTIONS
            },
            OBJECT_OPTION_SCHEMA
          ]
        },
        // For backward compatibility
        {
          type: 'array',
          items: [OBJECT_OPTION_SCHEMA]
        }
      ]
    },
    messages: {
      unexpected: "Custom event name '{{name}}' must be {{caseType}}."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {Map<ObjectExpression|Program, {contextReferenceIds:Set<Identifier>,emitReferenceIds:Set<Identifier>}>} */
    const setupContexts = new Map()
    const options =
      context.options.length === 1 && typeof context.options[0] !== 'string'
        ? // For backward compatibility
          [undefined, context.options[0]]
        : context.options
    const caseType = options[0] || DEFAULT_CASE
    const objectOption = options[1] || {}
    const caseChecker = casing.getChecker(caseType)
    /** @type {RegExp[]} */
    const ignores = (objectOption.ignores || []).map(toRegExp)

    /**
     * Check whether the given event name is valid.
     * @param {string} name The name to check.
     * @returns {boolean} `true` if the given event name is valid.
     */
    function isValidEventName(name) {
      return caseChecker(name) || name.startsWith('update:')
    }

    /**
     * @param { NameWithLoc } nameWithLoc
     */
    function verify(nameWithLoc) {
      const name = nameWithLoc.name
      if (isValidEventName(name) || ignores.some((re) => re.test(name))) {
        return
      }
      context.report({
        loc: nameWithLoc.loc,
        messageId: 'unexpected',
        data: {
          name,
          caseType
        }
      })
    }

    const programNode = context.getSourceCode().ast

    const callVisitor = {
      /**
       * @param {CallExpression} node
       * @param {VueObjectData} [info]
       */
      CallExpression(node, info) {
        const nameWithLoc = getNameParamNode(node)
        if (!nameWithLoc) {
          // cannot check
          return
        }

        // verify setup context
        const setupContext = setupContexts.get(info ? info.node : programNode)
        if (setupContext) {
          const { contextReferenceIds, emitReferenceIds } = setupContext
          if (
            node.callee.type === 'Identifier' &&
            emitReferenceIds.has(node.callee)
          ) {
            // verify setup(props,{emit}) {emit()}
            verify(nameWithLoc)
          } else {
            const emit = getCalleeMemberNode(node)
            if (
              emit &&
              emit.name === 'emit' &&
              emit.member.object.type === 'Identifier' &&
              contextReferenceIds.has(emit.member.object)
            ) {
              // verify setup(props,context) {context.emit()}
              verify(nameWithLoc)
            }
          }
        }
      }
    }

    return utils.defineTemplateBodyVisitor(
      context,
      {
        CallExpression(node) {
          const callee = node.callee
          const nameWithLoc = getNameParamNode(node)
          if (!nameWithLoc) {
            // cannot check
            return
          }
          if (callee.type === 'Identifier' && callee.name === '$emit') {
            verify(nameWithLoc)
          }
        }
      },
      utils.compositingVisitors(
        utils.defineScriptSetupVisitor(context, {
          onDefineEmitsEnter(node) {
            if (
              !node.parent ||
              node.parent.type !== 'VariableDeclarator' ||
              node.parent.init !== node
            ) {
              return
            }

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


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

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

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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce declaration style of `defineEmits`',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/define-emits-declaration.html'
    },
    fixable: null,
    schema: [
      {
        enum: ['type-based', 'type-literal', 'runtime']
      }
    ],
    messages: {
      hasArg: 'Use type based declaration instead of runtime declaration.',
      hasTypeArg: 'Use runtime declaration instead of type based declaration.',
      hasTypeCallArg:
        'Use new type literal declaration instead of the old call signature declaration.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const scriptSetup = utils.getScriptSetupElement(context)
    if (!scriptSetup || !utils.hasAttribute(scriptSetup, 'lang', 'ts')) {
      return {}
    }

    const defineType = context.options[0] || 'type-based'
    return utils.defineScriptSetupVisitor(context, {
      onDefineEmitsEnter(node) {
        switch (defineType) {
          case 'type-based': {
            if (node.arguments.length > 0) {
              context.report({
                node,
                messageId: 'hasArg'
              })
            }
            break
          }

          case 'type-literal': {
            verifyTypeLiteral(node)
            break
          }

          case 'runtime': {
            const typeArguments =
              'typeArguments' in node ? node.typeArguments : node.typeParameters
            if (typeArguments && typeArguments.params.length > 0) {
              context.report({
                node,
                messageId: 'hasTypeArg'
              })
            }
            break
          }
        }
      }
    })

    /** @param {CallExpression} node */
    function verifyTypeLiteral(node) {
      if (node.arguments.length > 0) {
        context.report({
          node,
          messageId: 'hasArg'
        })
        return
      }

      const typeArguments = node.typeArguments || node.typeParameters
      const param = /** @type {TypeNode|undefined} */ (typeArguments?.params[0])
      if (!param) return
      if (param.type === 'TSTypeLiteral') {
        for (const memberNode of param.members) {
          if (memberNode.type !== 'TSPropertySignature') {
            context.report({
              node: memberNode,
              messageId: 'hasTypeCallArg'
            })
          }
        }
      } else if (param.type === 'TSFunctionType') {
        context.report({
          node: param,
          messageId: 'hasTypeCallArg'
        })
      }
    }
  }
}


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

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

const MACROS_EMITS = 'defineEmits'
const MACROS_PROPS = 'defineProps'
const MACROS_OPTIONS = 'defineOptions'
const MACROS_SLOTS = 'defineSlots'
const MACROS_MODEL = 'defineModel'
const MACROS_EXPOSE = 'defineExpose'
const KNOWN_MACROS = new Set([
  MACROS_EMITS,
  MACROS_PROPS,
  MACROS_OPTIONS,
  MACROS_SLOTS,
  MACROS_MODEL,
  MACROS_EXPOSE
])
const DEFAULT_ORDER = [MACROS_PROPS, MACROS_EMITS]

/**
 * @param {VElement} scriptSetup
 * @param {ASTNode} node
 */
function inScriptSetup(scriptSetup, node) {
  return (
    scriptSetup.range[0] <= node.range[0] &&
    node.range[1] <= scriptSetup.range[1]
  )
}

/**
 * @param {ASTNode} node
 */
function isUseStrictStatement(node) {
  return (
    node.type === 'ExpressionStatement' &&
    node.expression.type === 'Literal' &&
    node.expression.value === 'use strict'
  )
}

/**
 * Get an index of the first statement after imports and interfaces in order
 * to place defineEmits and defineProps before this statement
 * @param {VElement} scriptSetup
 * @param {Program} program
 */
function getTargetStatementPosition(scriptSetup, program) {
  const skipStatements = new Set([
    'ImportDeclaration',
    'TSInterfaceDeclaration',
    'TSTypeAliasDeclaration',
    'DebuggerStatement',
    'EmptyStatement',
    'ExportNamedDeclaration'
  ])

  for (const [index, item] of program.body.entries()) {
    if (
      inScriptSetup(scriptSetup, item) &&
      !skipStatements.has(item.type) &&
      !isUseStrictStatement(item)
    ) {
      return index
    }
  }

  return -1
}

/**
 * We need to handle cases like "const props = defineProps(...)"
 * Define macros must be used only on top, so we can look for "Program" type
 * inside node.parent.type
 * @param {CallExpression|ASTNode} node
 * @return {ASTNode}
 */
function getDefineMacrosStatement(node) {
  if (!node.parent) {
    throw new Error('Node has no parent')
  }

  if (node.parent.type === 'Program') {
    return node
  }

  return getDefineMacrosStatement(node.parent)
}

/** @param {RuleContext} context */
function create(context) {
  const scriptSetup = utils.getScriptSetupElement(context)

  if (!scriptSetup) {
    return {}
  }

  const sourceCode = context.getSourceCode()
  const options = context.options
  /** @type {[string, string]} */
  const order = (options[0] && options[0].order) || DEFAULT_ORDER
  /** @type {boolean} */
  const defineExposeLast = (options[0] && options[0].defineExposeLast) || false
  /** @type {Map<string, ASTNode[]>} */
  const macrosNodes = new Map()
  /** @type {ASTNode} */
  let defineExposeNode

  if (order.includes(MACROS_EXPOSE) && defineExposeLast) {
    throw new Error(
      "`defineExpose` macro can't be in the `order` array if `defineExposeLast` is true."
    )
  }

  return utils.compositingVisitors(
    utils.defineScriptSetupVisitor(context, {
      onDefinePropsExit(node) {
        macrosNodes.set(MACROS_PROPS, [getDefineMacrosStatement(node)])
      },
      onDefineEmitsExit(node) {
        macrosNodes.set(MACROS_EMITS, [getDefineMacrosStatement(node)])
      },
      onDefineOptionsExit(node) {
        macrosNodes.set(MACROS_OPTIONS, [getDefineMacrosStatement(node)])
      },
      onDefineSlotsExit(node) {
        macrosNodes.set(MACROS_SLOTS, [getDefineMacrosStatement(node)])
      },
      onDefineModelExit(node) {
        const currentModelMacros = macrosNodes.get(MACROS_MODEL) ?? []
        currentModelMacros.push(getDefineMacrosStatement(node))
        macrosNodes.set(MACROS_MODEL, currentModelMacros)
      },
      onDefineExposeExit(node) {
        defineExposeNode = getDefineMacrosStatement(node)
      },

      /** @param {CallExpression} node */
      'Program > ExpressionStatement > CallExpression, Program > VariableDeclaration > VariableDeclarator > CallExpression'(
        node
      ) {
        if (
          node.callee &&
          node.callee.type === 'Identifier' &&
          order.includes(node.callee.name) &&
          !KNOWN_MACROS.has(node.callee.name)
        ) {
          macrosNodes.set(node.callee.name, [getDefineMacrosStatement(node)])
        }
      }
    }),
    {
      'Program:exit'(program) {
        /**
         * @typedef {object} OrderedData
         * @property {string} name
         * @property {ASTNode} node
         */
        const firstStatementIndex = getTargetStatementPosition(
          scriptSetup,
          program
        )
        const orderedList = order
          .flatMap((name) => {
            const nodes = macrosNodes.get(name) ?? []
            return nodes.map((node) => ({ name, node }))
          })
          .filter(
            /** @returns {data is OrderedData} */
            (data) => utils.isDef(data.node)
          )

        // check last node
        if (defineExposeLast) {
          const lastNode = program.body[program.body.length - 1]
          if (defineExposeNode && lastNode !== defineExposeNode) {
            reportExposeNotOnBottom(defineExposeNode, lastNode)
          }
        }

        for (const [index, should] of orderedList.entries()) {
          const targetStatement = program.body[firstStatementIndex + index]

          if (should.node !== targetStatement) {
            let moveTargetNodes = orderedList
              .slice(index)
              .map(({ node }) => node)
            const targetStatementIndex =
              moveTargetNodes.indexOf(targetStatement)

            if (targetStatementIndex >= 0) {
              moveTargetNodes = moveTargetNodes.slice(0, targetStatementIndex)
            }
            reportNotOnTop(should.name, moveTargetNodes, targetStatement)
            return
          }
        }
      }
    }
  )

  /**
   * @param {string} macro
   * @param {ASTNode[]} nodes
   * @param {ASTNode} before
   */
  function reportNotOnTop(macro, nodes, before) {
    context.report({
      node: nodes[0],
      loc: nodes[0].loc,
      messageId: 'macrosNotOnTop',
      data: {
        macro
      },
      *fix(fixer) {
        for (const node of nodes) {
          yield* moveNodeBefore(fixer, node, before)
        }
      }
    })
  }

  /**
   * @param {ASTNode} node
   * @param {ASTNode} lastNode
   */
  function reportExposeNotOnBottom(node, lastNode) {
    context.report({
      node,
      loc: node.loc,
      messageId: 'defineExposeNotTheLast',
      suggest: [
        {
          messageId: 'putExposeAtTheLast',
          fix(fixer) {
            return moveNodeToLast(fixer, node, lastNode)
          }
        }
      ]
    })
  }

  /**
   * Move all lines of "node" with its comments to after the "target"
   * @param {RuleFixer} fixer
   * @param {ASTNode} node
   * @param {ASTNode} target
   */
  function moveNodeToLast(fixer, node, target) {
    // get comments under tokens(if any)
    const beforeNodeToken = sourceCode.getTokenBefore(node)
    const nodeComment = sourceCode.getTokenAfter(beforeNodeToken, {
      includeComments: true
    })
    const nextNodeComment = sourceCode.getTokenAfter(node, {
      includeComments: true
    })

    // remove position: node (and comments) to next node (and comments)
    const cutStart = getLineStartIndex(nodeComment, beforeNodeToken)
    const cutEnd = getLineStartIndex(nextNodeComment, node)

    // insert text: comment + node
    const textNode = sourceCode.getText(
      node,
      node.range[0] - beforeNodeToken.range[1]
    )

    return [
      fixer.insertTextAfter(target, textNode),
      fixer.removeRange([cutStart, cutEnd])
    ]
  }

  /**
   * Move all lines of "node" with its comments to before the "target"
   * @param {RuleFixer} fixer
   * @param {ASTNode} node
   * @param {ASTNode} target
   */
  function moveNodeBefore(fixer, node, target) {
    // get comments under tokens(if any)
    const beforeNodeToken = sourceCode.getTokenBefore(node)
    const nodeComment = sourceCode.getTokenAfter(beforeNodeToken, {
      includeComments: true
    })
    const nextNodeComment = sourceCode.getTokenAfter(node, {
      includeComments: true
    })
    // get positions of what we need to remove
    const cutStart = getLineStartIndex(nodeComment, beforeNodeToken)
    const cutEnd = getLineStartIndex(nextNodeComment, node)
    // get space before target
    const beforeTargetToken = sourceCode.getTokenBefore(target)
    const targetComment = sourceCode.getTokenAfter(beforeTargetToken, {
      includeComments: true
    })
    // make insert text: comments + node + space before target
    const textNode = sourceCode.getText(
      node,
      node.range[0] - nodeComment.range[0]
    )
    const insertText = getInsertText(textNode, target)

    return [
      fixer.insertTextBefore(targetComment, insertText),
      fixer.removeRange([cutStart, cutEnd])
    ]
  }

  /**
   * Get result text to insert
   * @param {string} textNode
   * @param {ASTNode} target
   */
  function getInsertText(textNode, target) {
    const afterTargetComment = sourceCode.getTokenAfter(target, {
      includeComments: true
    })
    const afterText = sourceCode.text.slice(
      target.range[1],
      afterTargetComment.range[0]
    )
    // handle case when a();b() -> b()a();
    const invalidResult = !textNode.endsWith(';') && !afterText.includes('\n')

    return textNode + afterText + (invalidResult ? ';' : '')
  }

  /**
   * Get position of the beginning of the token's line(or prevToken end if no line)
   * @param {ASTNode|Token} token
   * @param {ASTNode|Token} prevToken
   */
  function getLineStartIndex(token, prevToken) {
    // if we have next token on the same line - get index right before that token
    if (token.loc.start.line === prevToken.loc.end.line) {
      return prevToken.range[1]
    }

    return sourceCode.getIndexFromLoc({
      line: token.loc.start.line,
      column: 0
    })
  }
}

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description:
        'enforce order of compiler macros (`defineProps`, `defineEmits`, etc.)',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/define-macros-order.html'
    },
    fixable: 'code',
    hasSuggestions: true,
    schema: [
      {
        type: 'object',
        properties: {
          order: {
            type: 'array',
            items: {
              type: 'string',
              minLength: 1
            },
            uniqueItems: true,
            additionalItems: false
          },
          defineExposeLast: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      macrosNotOnTop:
        '{{macro}} should be the first statement in `<script setup>` (after any potential import statements or type definitions).',
      defineExposeNotTheLast:
        '`defineExpose` should be the last statement in `<script setup>`.',
      putExposeAtTheLast:
        'Put `defineExpose` as the last statement in `<script setup>`.'
    }
  },
  create
}


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

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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce declaration style of `defineProps`',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/define-props-declaration.html'
    },
    fixable: null,
    schema: [
      {
        enum: ['type-based', 'runtime']
      }
    ],
    messages: {
      hasArg: 'Use type-based declaration instead of runtime declaration.',
      hasTypeArg: 'Use runtime declaration instead of type-based declaration.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const scriptSetup = utils.getScriptSetupElement(context)
    if (!scriptSetup || !utils.hasAttribute(scriptSetup, 'lang', 'ts')) {
      return {}
    }

    const defineType = context.options[0] || 'type-based'
    return utils.defineScriptSetupVisitor(context, {
      onDefinePropsEnter(node) {
        switch (defineType) {
          case 'type-based': {
            if (node.arguments.length > 0) {
              context.report({
                node,
                messageId: 'hasArg'
              })
            }
            break
          }

          case 'runtime': {
            const typeArguments =
              'typeArguments' in node ? node.typeArguments : node.typeParameters
            if (typeArguments && typeArguments.params.length > 0) {
              context.report({
                node,
                messageId: 'hasTypeArg'
              })
            }
            break
          }
        }
      }
    })
  }
}


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

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

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


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

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

// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapStylisticOrCoreRule('dot-notation', {
  applyDocument: true
})


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

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

/**
 * check whether a tag has the `scoped` attribute
 * @param {VElement} componentBlock
 */
function isScoped(componentBlock) {
  return componentBlock.startTag.attributes.some(
    (attribute) => !attribute.directive && attribute.key.name === 'scoped'
  )
}

/**
 * check whether a tag has the `module` attribute
 * @param {VElement} componentBlock
 */
function isModule(componentBlock) {
  return componentBlock.startTag.attributes.some(
    (attribute) => !attribute.directive && attribute.key.name === 'module'
  )
}

/**
 * check if a tag doesn't have either the `scoped` nor `module` attribute
 * @param {VElement} componentBlock
 */
function isPlain(componentBlock) {
  return !isScoped(componentBlock) && !isModule(componentBlock)
}

/** @param {RuleContext} context */
function getUserDefinedAllowedAttrs(context) {
  if (context.options[0] && context.options[0].allow) {
    return context.options[0].allow
  }
  return []
}

const defaultAllowedAttrs = ['scoped']

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/enforce-style-attribute.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          allow: {
            type: 'array',
            minItems: 1,
            uniqueItems: true,
            items: {
              type: 'string',
              enum: ['plain', 'scoped', 'module']
            }
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      notAllowedScoped:
        'The scoped attribute is not allowed. Allowed: {{ allowedAttrsString }}.',
      notAllowedModule:
        'The module attribute is not allowed. Allowed: {{ allowedAttrsString }}.',
      notAllowedPlain:
        'Plain <style> tags are not allowed. Allowed: {{ allowedAttrsString }}.'
    }
  },

  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()
    if (!sourceCode.parserServices.getDocumentFragment) {
      return {}
    }
    const documentFragment = sourceCode.parserServices.getDocumentFragment()
    if (!documentFragment) {
      return {}
    }

    const topLevelStyleTags = documentFragment.children.filter(
      /** @returns {element is VElement} */
      (element) => isVElement(element) && element.rawName === 'style'
    )

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

    const userDefinedAllowedAttrs = getUserDefinedAllowedAttrs(context)
    const allowedAttrs =
      userDefinedAllowedAttrs.length > 0
        ? userDefinedAllowedAttrs
        : defaultAllowedAttrs

    const allowsPlain = allowedAttrs.includes('plain')
    const allowsScoped = allowedAttrs.includes('scoped')
    const allowsModule = allowedAttrs.includes('module')
    const allowedAttrsString = [...allowedAttrs].sort().join(', ')

    return {
      Program() {
        for (const styleTag of topLevelStyleTags) {
          if (!allowsPlain && isPlain(styleTag)) {
            context.report({
              node: styleTag,
              messageId: 'notAllowedPlain',
              data: {
                allowedAttrsString
              }
            })
            return
          }

          if (!allowsScoped && isScoped(styleTag)) {
            context.report({
              node: styleTag,
              messageId: 'notAllowedScoped',
              data: {
                allowedAttrsString
              }
            })
            return
          }

          if (!allowsModule && isModule(styleTag)) {
            context.report({
              node: styleTag,
              messageId: 'notAllowedModule',
              data: {
                allowedAttrsString
              }
            })
            return
          }
        }
      }
    }
  }
}


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

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

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


code:
/**
 * @fileoverview Enforce the location of first attribute
 * @author Yosuke Ota
 */
'use strict'

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

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description: 'enforce the location of first attribute',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/first-attribute-linebreak.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        type: 'object',
        properties: {
          multiline: { enum: ['below', 'beside', 'ignore'] },
          singleline: { enum: ['below', 'beside', 'ignore'] }
        },
        additionalProperties: false
      }
    ],
    messages: {
      expected: 'Expected a linebreak before this attribute.',
      unexpected: 'Expected no linebreak before this attribute.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {"below" | "beside" | "ignore"} */
    const singleline =
      (context.options[0] && context.options[0].singleline) || 'ignore'
    /** @type {"below" | "beside" | "ignore"} */
    const multiline =
      (context.options[0] && context.options[0].multiline) || 'below'

    const sourceCode = context.getSourceCode()
    const template =
      sourceCode.parserServices.getTemplateBodyTokenStore &&
      sourceCode.parserServices.getTemplateBodyTokenStore()

    /**
     * Report attribute
     * @param {VAttribute | VDirective} firstAttribute
     * @param { "below" | "beside"} location
     */
    function report(firstAttribute, location) {
      context.report({
        node: firstAttribute,
        messageId: location === 'beside' ? 'unexpected' : 'expected',
        fix(fixer) {
          const prevToken = template.getTokenBefore(firstAttribute, {
            includeComments: true
          })
          return fixer.replaceTextRange(
            [prevToken.range[1], firstAttribute.range[0]],
            location === 'beside' ? ' ' : '\n'
          )
        }
      })
    }

    return utils.defineTemplateBodyVisitor(context, {
      VStartTag(node) {
        const firstAttribute = node.attributes[0]
        if (!firstAttribute) return

        const lastAttribute = node.attributes[node.attributes.length - 1]

        const location =
          firstAttribute.loc.start.line === lastAttribute.loc.end.line
            ? singleline
            : multiline
        if (location === 'ignore') {
          return
        }

        if (location === 'beside') {
          if (node.loc.start.line === firstAttribute.loc.start.line) {
            return
          }
        } else {
          if (node.loc.start.line < firstAttribute.loc.start.line) {
            return
          }
        }
        report(firstAttribute, location)
      }
    })
  }
}