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

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

// eslint-disable-next-line internal/no-invalid-meta
module.exports = wrapStylisticOrCoreRule(
  {
    core: 'func-call-spacing',
    stylistic: 'function-call-spacing',
    vue: 'func-call-spacing'
  },
  {
    skipDynamicArguments: true,
    applyDocument: true
  }
)


code:
/**
 * @fileoverview Disallow usage of button without an explicit type attribute
 * @author Jonathan Santerre <jonathan@santerre.dev>
 */
'use strict'

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

/**
 *
 * @param {string} type
 * @returns {type is 'button' | 'submit' | 'reset'}
 */
function isButtonType(type) {
  return type === 'button' || type === 'submit' || type === 'reset'
}

const optionDefaults = {
  button: true,
  submit: true,
  reset: true
}

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description:
        'disallow usage of button without an explicit type attribute',
      categories: null,
      url: 'https://eslint.vuejs.org/rules/html-button-has-type.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          button: {
            default: optionDefaults.button,
            type: 'boolean'
          },
          submit: {
            default: optionDefaults.submit,
            type: 'boolean'
          },
          reset: {
            default: optionDefaults.reset,
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      missingTypeAttribute: 'Missing an explicit type attribute for button.',
      invalidTypeAttribute:
        '{{value}} is an invalid value for button type attribute.',
      forbiddenTypeAttribute:
        '{{value}} is a forbidden value for button type attribute.',
      emptyTypeAttribute: 'A value must be set for button type attribute.'
    }
  },

  /**
   * @param {RuleContext} context - The rule context.
   * @returns {RuleListener} AST event handlers.
   */
  create(context) {
    /**
     * @typedef {object} Configuration
     * @property {boolean} button
     * @property {boolean} submit
     * @property {boolean} reset
     */
    /** @type {Configuration} */
    const configuration = Object.assign({}, optionDefaults, context.options[0])

    /**
     * @param {ASTNode} node
     * @param {string} messageId
     * @param {any} [data]
     */
    function report(node, messageId, data) {
      context.report({
        node,
        messageId,
        data
      })
    }

    /**
     * @param {VAttribute} attribute
     */
    function validateAttribute(attribute) {
      const value = attribute.value
      if (!value || !value.value) {
        report(value || attribute, 'emptyTypeAttribute')
        return
      }

      const strValue = value.value
      if (!isButtonType(strValue)) {
        report(value, 'invalidTypeAttribute', { value: strValue })
      } else if (!configuration[strValue]) {
        report(value, 'forbiddenTypeAttribute', { value: strValue })
      }
    }

    /**
     * @param {VDirective} directive
     */
    function validateDirective(directive) {
      const value = directive.value
      if (!value || !value.expression) {
        report(value || directive, 'emptyTypeAttribute')
      }
    }

    return utils.defineTemplateBodyVisitor(context, {
      /**
       * @param {VElement} node
       */
      "VElement[rawName='button']"(node) {
        const typeAttr = utils.getAttribute(node, 'type')
        if (typeAttr) {
          validateAttribute(typeAttr)
          return
        }
        const typeDir = utils.getDirective(node, 'bind', 'type')
        if (typeDir) {
          validateDirective(typeDir)
          return
        }

        report(node.startTag, 'missingTypeAttribute')
      }
    })
  }
}

license: mit


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

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

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

/**
 * @typedef LineBreakBehavior
 * @type {('always'|'never')}
 */

/**
 * @typedef LineType
 * @type {('singleline'|'multiline')}
 */

/**
 * @typedef RuleOptions
 * @type {object}
 * @property {LineBreakBehavior} singleline - The behavior for single line tags.
 * @property {LineBreakBehavior} multiline - The behavior for multiline tags.
 * @property {object} selfClosingTag
 * @property {LineBreakBehavior} selfClosingTag.singleline - The behavior for single line self closing tags.
 * @property {LineBreakBehavior} selfClosingTag.multiline - The behavior for multiline self closing tags.
 */

/**
 * @param {VStartTag | VEndTag} node - The node representing a start or end tag.
 * @param {RuleOptions} options - The options for line breaks.
 * @param {LineType} type - The type of line break.
 * @returns {number} - The expected line breaks.
 */
function getExpectedLineBreaks(node, options, type) {
  const isSelfClosingTag = node.type === 'VStartTag' && node.selfClosing
  if (
    isSelfClosingTag &&
    options.selfClosingTag &&
    options.selfClosingTag[type]
  ) {
    return options.selfClosingTag[type] === 'always' ? 1 : 0
  }

  return options[type] === 'always' ? 1 : 0
}

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description:
        "require or disallow a line break before tag's closing brackets",
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/html-closing-bracket-newline.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        type: 'object',
        properties: {
          singleline: { enum: ['always', 'never'] },
          multiline: { enum: ['always', 'never'] },
          selfClosingTag: {
            type: 'object',
            properties: {
              singleline: { enum: ['always', 'never'] },
              multiline: { enum: ['always', 'never'] }
            },
            additionalProperties: false,
            minProperties: 1
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      expectedBeforeClosingBracket:
        'Expected {{expected}} before closing bracket, but {{actual}} found.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = Object.assign(
      {},
      {
        singleline: 'never',
        multiline: 'always'
      },
      context.options[0] || {}
    )
    const sourceCode = context.getSourceCode()
    const template =
      sourceCode.parserServices.getTemplateBodyTokenStore &&
      sourceCode.parserServices.getTemplateBodyTokenStore()

    return utils.defineDocumentVisitor(context, {
      /** @param {VStartTag | VEndTag} node */
      'VStartTag, VEndTag'(node) {
        const closingBracketToken = template.getLastToken(node)
        if (
          closingBracketToken.type !== 'HTMLSelfClosingTagClose' &&
          closingBracketToken.type !== 'HTMLTagClose'
        ) {
          return
        }

        const prevToken = template.getTokenBefore(closingBracketToken)
        const type =
          node.loc.start.line === prevToken.loc.end.line
            ? 'singleline'
            : 'multiline'

        const expectedLineBreaks = getExpectedLineBreaks(node, options, type)

        const actualLineBreaks =
          closingBracketToken.loc.start.line - prevToken.loc.end.line

        if (actualLineBreaks !== expectedLineBreaks) {
          context.report({
            node,
            loc: {
              start: prevToken.loc.end,
              end: closingBracketToken.loc.start
            },
            messageId: 'expectedBeforeClosingBracket',
            data: {
              expected: getPhrase(expectedLineBreaks),
              actual: getPhrase(actualLineBreaks)
            },
            fix(fixer) {
              /** @type {Range} */
              const range = [prevToken.range[1], closingBracketToken.range[0]]
              const text = '\n'.repeat(expectedLineBreaks)
              return fixer.replaceTextRange(range, text)
            }
          })
        }
      }
    })
  }
}


code:
/**
 * @author Toru Nagashima <https://github.com/mysticatea>
 */

'use strict'

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

/**
 * @typedef { {startTag?:"always"|"never",endTag?:"always"|"never",selfClosingTag?:"always"|"never"} } Options
 */

/**
 * Normalize options.
 * @param {Options} options The options user configured.
 * @param {ParserServices.TokenStore} tokens The token store of template body.
 * @returns {Options & { detectType: (node: VStartTag | VEndTag) => 'never' | 'always' | null }} The normalized options.
 */
function parseOptions(options, tokens) {
  const opts = Object.assign(
    {
      startTag: 'never',
      endTag: 'never',
      selfClosingTag: 'always'
    },
    options
  )
  return Object.assign(opts, {
    /**
     * @param {VStartTag | VEndTag} node
     * @returns {'never' | 'always' | null}
     */
    detectType(node) {
      const openType = tokens.getFirstToken(node).type
      const closeType = tokens.getLastToken(node).type

      if (openType === 'HTMLEndTagOpen' && closeType === 'HTMLTagClose') {
        return opts.endTag
      }
      if (openType === 'HTMLTagOpen' && closeType === 'HTMLTagClose') {
        return opts.startTag
      }
      if (
        openType === 'HTMLTagOpen' &&
        closeType === 'HTMLSelfClosingTagClose'
      ) {
        return opts.selfClosingTag
      }
      return null
    }
  })
}

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description: "require or disallow a space before tag's closing brackets",
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/html-closing-bracket-spacing.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        type: 'object',
        properties: {
          startTag: { enum: ['always', 'never'] },
          endTag: { enum: ['always', 'never'] },
          selfClosingTag: { enum: ['always', 'never'] }
        },
        additionalProperties: false
      }
    ],
    messages: {
      missing: "Expected a space before '{{bracket}}', but not found.",
      unexpected: "Expected no space before '{{bracket}}', but found."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()
    const tokens =
      sourceCode.parserServices.getTemplateBodyTokenStore &&
      sourceCode.parserServices.getTemplateBodyTokenStore()
    const options = parseOptions(context.options[0], tokens)

    return utils.defineDocumentVisitor(context, {
      /** @param {VStartTag | VEndTag} node */
      'VStartTag, VEndTag'(node) {
        const type = options.detectType(node)
        const lastToken = tokens.getLastToken(node)
        const prevToken = tokens.getLastToken(node, 1)

        // Skip if EOF exists in the tag or linebreak exists before `>`.
        if (
          type == null ||
          prevToken == null ||
          prevToken.loc.end.line !== lastToken.loc.start.line
        ) {
          return
        }

        // Check and report.
        const hasSpace = prevToken.range[1] !== lastToken.range[0]
        if (type === 'always' && !hasSpace) {
          context.report({
            node,
            loc: lastToken.loc,
            messageId: 'missing',
            data: { bracket: sourceCode.getText(lastToken) },
            fix: (fixer) => fixer.insertTextBefore(lastToken, ' ')
          })
        } else if (type === 'never' && hasSpace) {
          context.report({
            node,
            loc: {
              start: prevToken.loc.end,
              end: lastToken.loc.end
            },
            messageId: 'unexpected',
            data: { bracket: sourceCode.getText(lastToken) },
            fix: (fixer) =>
              fixer.removeRange([prevToken.range[1], lastToken.range[0]])
          })
        }
      }
    })
  }
}


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

const htmlComments = require('../utils/html-comments')

/**
 * @typedef { import('../utils/html-comments').ParsedHTMLComment } ParsedHTMLComment
 */

/**
 * @param {any} param
 */
function parseOption(param) {
  if (param && typeof param === 'string') {
    return {
      singleline: param,
      multiline: param
    }
  }
  return Object.assign(
    {
      singleline: 'never',
      multiline: 'always'
    },
    param
  )
}

module.exports = {
  meta: {
    type: 'layout',

    docs: {
      description: 'enforce unified line brake in HTML comments',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/html-comment-content-newline.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        anyOf: [
          {
            enum: ['always', 'never']
          },
          {
            type: 'object',
            properties: {
              singleline: { enum: ['always', 'never', 'ignore'] },
              multiline: { enum: ['always', 'never', 'ignore'] }
            },
            additionalProperties: false
          }
        ]
      },
      {
        type: 'object',
        properties: {
          exceptions: {
            type: 'array',
            items: {
              type: 'string'
            }
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      expectedAfterHTMLCommentOpen: "Expected line break after '<!--'.",
      expectedBeforeHTMLCommentOpen: "Expected line break before '-->'.",
      expectedAfterExceptionBlock: 'Expected line break after exception block.',
      expectedBeforeExceptionBlock:
        'Expected line break before exception block.',
      unexpectedAfterHTMLCommentOpen: "Unexpected line breaks after '<!--'.",
      unexpectedBeforeHTMLCommentOpen: "Unexpected line breaks before '-->'."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const option = parseOption(context.options[0])
    return htmlComments.defineVisitor(
      context,
      context.options[1],
      (comment) => {
        const { value, openDecoration, closeDecoration } = comment
        if (!value) {
          return
        }

        const startLine = openDecoration
          ? openDecoration.loc.end.line
          : value.loc.start.line
        const endLine = closeDecoration
          ? closeDecoration.loc.start.line
          : value.loc.end.line
        const newlineType =
          startLine === endLine ? option.singleline : option.multiline
        if (newlineType === 'ignore') {
          return
        }
        checkCommentOpen(comment, newlineType !== 'never')
        checkCommentClose(comment, newlineType !== 'never')
      }
    )

    /**
     * Reports the newline before the contents of a given comment if it's invalid.
     * @param {ParsedHTMLComment} comment - comment data.
     * @param {boolean} requireNewline - `true` if line breaks are required.
     * @returns {void}
     */
    function checkCommentOpen(comment, requireNewline) {
      const { value, openDecoration, open } = comment
      if (!value) {
        return
      }
      const beforeToken = openDecoration || open

      if (requireNewline) {
        if (beforeToken.loc.end.line < value.loc.start.line) {
          // Is valid
          return
        }
        context.report({
          loc: {
            start: beforeToken.loc.end,
            end: value.loc.start
          },
          messageId: openDecoration
            ? 'expectedAfterExceptionBlock'
            : 'expectedAfterHTMLCommentOpen',
          fix: openDecoration
            ? undefined
            : (fixer) => fixer.insertTextAfter(beforeToken, '\n')
        })
      } else {
        if (beforeToken.loc.end.line === value.loc.start.line) {
          // Is valid
          return
        }
        context.report({
          loc: {
            start: beforeToken.loc.end,
            end: value.loc.start
          },
          messageId: 'unexpectedAfterHTMLCommentOpen',
          fix: (fixer) =>
            fixer.replaceTextRange([beforeToken.range[1], value.range[0]], ' ')
        })
      }
    }

    /**
     * Reports the space after the contents of a given comment if it's invalid.
     * @param {ParsedHTMLComment} comment - comment data.
     * @param {boolean} requireNewline - `true` if line breaks are required.
     * @returns {void}
     */
    function checkCommentClose(comment, requireNewline) {
      const { value, closeDecoration, close } = comment
      if (!value) {
        return
      }
      const afterToken = closeDecoration || close

      if (requireNewline) {
        if (value.loc.end.line < afterToken.loc.start.line) {
          // Is valid
          return
        }
        context.report({
          loc: {
            start: value.loc.end,
            end: afterToken.loc.start
          },
          messageId: closeDecoration
            ? 'expectedBeforeExceptionBlock'
            : 'expectedBeforeHTMLCommentOpen',
          fix: closeDecoration
            ? undefined
            : (fixer) => fixer.insertTextBefore(afterToken, '\n')
        })
      } else {
        if (value.loc.end.line === afterToken.loc.start.line) {
          // Is valid
          return
        }
        context.report({
          loc: {
            start: value.loc.end,
            end: afterToken.loc.start
          },
          messageId: 'unexpectedBeforeHTMLCommentOpen',
          fix: (fixer) =>
            fixer.replaceTextRange([value.range[1], afterToken.range[0]], ' ')
        })
      }
    }
  }
}


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

const htmlComments = require('../utils/html-comments')

/**
 * @typedef { import('../utils/html-comments').ParsedHTMLComment } ParsedHTMLComment
 */

module.exports = {
  meta: {
    type: 'layout',

    docs: {
      description: 'enforce unified spacing in HTML comments',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/html-comment-content-spacing.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        enum: ['always', 'never']
      },
      {
        type: 'object',
        properties: {
          exceptions: {
            type: 'array',
            items: {
              type: 'string'
            }
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      expectedAfterHTMLCommentOpen: "Expected space after '<!--'.",
      expectedBeforeHTMLCommentOpen: "Expected space before '-->'.",
      expectedAfterExceptionBlock: 'Expected space after exception block.',
      expectedBeforeExceptionBlock: 'Expected space before exception block.',
      unexpectedAfterHTMLCommentOpen: "Unexpected space after '<!--'.",
      unexpectedBeforeHTMLCommentOpen: "Unexpected space before '-->'."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    // Unless the first option is never, require a space
    const requireSpace = context.options[0] !== 'never'
    return htmlComments.defineVisitor(
      context,
      context.options[1],
      (comment) => {
        checkCommentOpen(comment)
        checkCommentClose(comment)
      },
      { includeDirectives: true }
    )

    /**
     * Reports the space before the contents of a given comment if it's invalid.
     * @param {ParsedHTMLComment} comment - comment data.
     * @returns {void}
     */
    function checkCommentOpen(comment) {
      const { value, openDecoration, open } = comment
      if (!value) {
        return
      }
      const beforeToken = openDecoration || open
      if (beforeToken.loc.end.line !== value.loc.start.line) {
        // Ignore newline
        return
      }

      if (requireSpace) {
        if (beforeToken.range[1] < value.range[0]) {
          // Is valid
          return
        }
        context.report({
          loc: {
            start: beforeToken.loc.end,
            end: value.loc.start
          },
          messageId: openDecoration
            ? 'expectedAfterExceptionBlock'
            : 'expectedAfterHTMLCommentOpen',
          fix: openDecoration
            ? undefined
            : (fixer) => fixer.insertTextAfter(beforeToken, ' ')
        })
      } else {
        if (openDecoration) {
          // Ignore expection block
          return
        }
        if (beforeToken.range[1] === value.range[0]) {
          // Is valid
          return
        }
        context.report({
          loc: {
            start: beforeToken.loc.end,
            end: value.loc.start
          },
          messageId: 'unexpectedAfterHTMLCommentOpen',
          fix: (fixer) =>
            fixer.removeRange([beforeToken.range[1], value.range[0]])
        })
      }
    }

    /**
     * Reports the space after the contents of a given comment if it's invalid.
     * @param {ParsedHTMLComment} comment - comment data.
     * @returns {void}
     */
    function checkCommentClose(comment) {
      const { value, closeDecoration, close } = comment
      if (!value) {
        return
      }
      const afterToken = closeDecoration || close
      if (value.loc.end.line !== afterToken.loc.start.line) {
        // Ignore newline
        return
      }

      if (requireSpace) {
        if (value.range[1] < afterToken.range[0]) {
          // Is valid
          return
        }
        context.report({
          loc: {
            start: value.loc.end,
            end: afterToken.loc.start
          },
          messageId: closeDecoration
            ? 'expectedBeforeExceptionBlock'
            : 'expectedBeforeHTMLCommentOpen',
          fix: closeDecoration
            ? undefined
            : (fixer) => fixer.insertTextBefore(afterToken, ' ')
        })
      } else {
        if (closeDecoration) {
          // Ignore expection block
          return
        }
        if (value.range[1] === afterToken.range[0]) {
          // Is valid
          return
        }
        context.report({
          loc: {
            start: value.loc.end,
            end: afterToken.loc.start
          },
          messageId: 'unexpectedBeforeHTMLCommentOpen',
          fix: (fixer) =>
            fixer.removeRange([value.range[1], afterToken.range[0]])
        })
      }
    }
  }
}


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

const htmlComments = require('../utils/html-comments')

/**
 * Normalize options.
 * @param {number|"tab"|undefined} type The type of indentation.
 * @returns { { indentChar: string, indentSize: number, indentText: string } } Normalized options.
 */
function parseOptions(type) {
  const ret = {
    indentChar: ' ',
    indentSize: 2,
    indentText: ''
  }

  if (Number.isSafeInteger(type)) {
    ret.indentSize = Number(type)
  } else if (type === 'tab') {
    ret.indentChar = '\t'
    ret.indentSize = 1
  }
  ret.indentText = ret.indentChar.repeat(ret.indentSize)

  return ret
}

/**
 * @param {string} s
 * @param {string} [unitChar]
 */
function toDisplay(s, unitChar) {
  if (s.length === 0 && unitChar) {
    return `0 ${toUnit(unitChar)}s`
  }
  const char = s[0]
  if ((char === ' ' || char === '\t') && [...s].every((c) => c === char)) {
    return `${s.length} ${toUnit(char)}${s.length === 1 ? '' : 's'}`
  }

  return JSON.stringify(s)
}

/** @param {string} char */
function toUnit(char) {
  if (char === '\t') {
    return 'tab'
  }
  if (char === ' ') {
    return 'space'
  }
  return JSON.stringify(char)
}

module.exports = {
  meta: {
    type: 'layout',

    docs: {
      description: 'enforce consistent indentation in HTML comments',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/html-comment-indent.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        anyOf: [{ type: 'integer', minimum: 0 }, { enum: ['tab'] }]
      }
    ],
    messages: {
      unexpectedBaseIndentation:
        'Expected base point indentation of {{expected}}, but found {{actual}}.',
      missingBaseIndentation:
        'Expected base point indentation of {{expected}}, but not found.',
      unexpectedIndentationCharacter:
        'Expected {{expected}} character, but found {{actual}} character.',
      unexpectedIndentation:
        'Expected indentation of {{expected}} but found {{actual}}.',
      unexpectedRelativeIndentation:
        'Expected relative indentation of {{expected}} but found {{actual}}.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = parseOptions(context.options[0])
    const sourceCode = context.getSourceCode()
    return htmlComments.defineVisitor(
      context,
      null,
      (comment) => {
        const baseIndentText = getLineIndentText(comment.open.loc.start.line)
        let endLine
        if (comment.value) {
          const startLine = comment.value.loc.start.line
          endLine = comment.value.loc.end.line

          const checkStartLine =
            comment.open.loc.end.line === startLine ? startLine + 1 : startLine

          for (let line = checkStartLine; line <= endLine; line++) {
            validateIndentForLine(line, baseIndentText, 1)
          }
        } else {
          endLine = comment.open.loc.end.line
        }

        if (endLine < comment.close.loc.start.line) {
          // `-->`
          validateIndentForLine(comment.close.loc.start.line, baseIndentText, 0)
        }
      },
      { includeDirectives: true }
    )

    /**
     * Checks whether the given line is a blank line.
     * @param {number} line The number of line. Begins with 1.
     * @returns {boolean} `true` if the given line is a blank line
     */
    function isEmptyLine(line) {
      const lineText = sourceCode.getLines()[line - 1]
      return !lineText.trim()
    }

    /**
     * Get the actual indentation of the given line.
     * @param {number} line The number of line. Begins with 1.
     * @returns {string} The actual indentation text
     */
    function getLineIndentText(line) {
      const lineText = sourceCode.getLines()[line - 1]
      const charIndex = lineText.search(/\S/)
      // already checked
      // if (charIndex < 0) {
      //   return lineText
      // }
      return lineText.slice(0, charIndex)
    }

    /**
     * Define the function which fixes the problem.
     * @param {number} line The number of line.
     * @param {string} actualIndentText The actual indentation text.
     * @param {string} expectedIndentText The expected indentation text.
     * @returns { (fixer: RuleFixer) => Fix } The defined function.
     */
    function defineFix(line, actualIndentText, expectedIndentText) {
      return (fixer) => {
        const start = sourceCode.getIndexFromLoc({
          line,
          column: 0
        })
        return fixer.replaceTextRange(
          [start, start + actualIndentText.length],
          expectedIndentText
        )
      }
    }

    /**
     * Validate the indentation of a line.
     * @param {number} line The number of line. Begins with 1.
     * @param {string} baseIndentText The expected base indentation text.
     * @param {number} offset The number of the indentation offset.
     */
    function validateIndentForLine(line, baseIndentText, offset) {
      if (isEmptyLine(line)) {
        return
      }
      const actualIndentText = getLineIndentText(line)

      const expectedOffsetIndentText = options.indentText.repeat(offset)
      const expectedIndentText = baseIndentText + expectedOffsetIndentText

      // validate base indent
      if (
        baseIndentText &&
        (actualIndentText.length < baseIndentText.length ||
          !actualIndentText.startsWith(baseIndentText))
      ) {
        context.report({
          loc: {
            start: { line, column: 0 },
            end: { line, column: actualIndentText.length }
          },
          messageId: actualIndentText
            ? 'unexpectedBaseIndentation'
            : 'missingBaseIndentation',
          data: {
            expected: toDisplay(baseIndentText),
            actual: toDisplay(actualIndentText.slice(0, baseIndentText.length))
          },
          fix: defineFix(line, actualIndentText, expectedIndentText)
        })
        return
      }

      const actualOffsetIndentText = actualIndentText.slice(
        baseIndentText.length
      )

      // validate indent charctor
      for (const [i, char] of [...actualOffsetIndentText].entries()) {
        if (char !== options.indentChar) {
          context.report({
            loc: {
              start: { line, column: baseIndentText.length + i },
              end: { line, column: baseIndentText.length + i + 1 }
            },
            messageId: 'unexpectedIndentationCharacter',
            data: {
              expected: toUnit(options.indentChar),
              actual: toUnit(char)
            },
            fix: defineFix(line, actualIndentText, expectedIndentText)
          })
          return
        }
      }

      // validate indent length
      if (actualOffsetIndentText.length !== expectedOffsetIndentText.length) {
        context.report({
          loc: {
            start: { line, column: baseIndentText.length },
            end: { line, column: actualIndentText.length }
          },
          messageId: baseIndentText
            ? 'unexpectedRelativeIndentation'
            : 'unexpectedIndentation',
          data: {
            expected: toDisplay(expectedOffsetIndentText, options.indentChar),
            actual: toDisplay(actualOffsetIndentText, options.indentChar)
          },
          fix: defineFix(line, actualIndentText, expectedIndentText)
        })
      }
    }
  }
}


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

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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'enforce end tag style',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/html-end-tags.html'
    },
    fixable: 'code',
    schema: [],
    messages: {
      missingEndTag: "'<{{name}}>' should have end tag."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    let hasInvalidEOF = false

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

          const name = node.name
          const isVoid = utils.isHtmlVoidElementName(name)
          const isSelfClosing = node.startTag.selfClosing
          const hasEndTag = node.endTag != null

          if (!isVoid && !hasEndTag && !isSelfClosing) {
            context.report({
              node: node.startTag,
              loc: node.startTag.loc,
              messageId: 'missingEndTag',
              data: { name },
              fix: (fixer) => fixer.insertTextAfter(node, `</${name}>`)
            })
          }
        }
      },
      {
        Program(node) {
          hasInvalidEOF = utils.hasInvalidEOF(node)
        }
      }
    )
  }
}


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

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

module.exports = {
  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()
    const tokenStore =
      sourceCode.parserServices.getTemplateBodyTokenStore &&
      sourceCode.parserServices.getTemplateBodyTokenStore()
    const visitor = indentCommon.defineVisitor(context, tokenStore, {
      baseIndent: 1
    })

    return utils.defineTemplateBodyVisitor(context, visitor)
  },
  // eslint-disable-next-line eslint-plugin/prefer-message-ids
  meta: {
    type: 'layout',
    docs: {
      description: 'enforce consistent indentation in `<template>`',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/html-indent.html'
    },
    // eslint-disable-next-line eslint-plugin/require-meta-fixable -- fixer is not recognized
    fixable: 'whitespace',
    schema: [
      {
        anyOf: [{ type: 'integer', minimum: 1 }, { enum: ['tab'] }]
      },
      {
        type: 'object',
        properties: {
          attribute: { type: 'integer', minimum: 0 },
          baseIndent: { type: 'integer', minimum: 0 },
          closeBracket: {
            anyOf: [
              { type: 'integer', minimum: 0 },
              {
                type: 'object',
                properties: {
                  startTag: { type: 'integer', minimum: 0 },
                  endTag: { type: 'integer', minimum: 0 },
                  selfClosingTag: { type: 'integer', minimum: 0 }
                },
                additionalProperties: false
              }
            ]
          },
          switchCase: { type: 'integer', minimum: 0 },
          alignAttributesVertically: { type: 'boolean' },
          ignores: {
            type: 'array',
            items: {
              allOf: [
                { type: 'string' },
                { not: { type: 'string', pattern: ':exit$' } },
                { not: { type: 'string', pattern: String.raw`^\s*$` } }
              ]
            },
            uniqueItems: true,
            additionalItems: false
          }
        },
        additionalProperties: false
      }
    ]
  }
}


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

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

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description: 'enforce quotes style of HTML attributes',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/html-quotes.html'
    },
    fixable: 'code',
    schema: [
      { enum: ['double', 'single'] },
      {
        type: 'object',
        properties: {
          avoidEscape: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      expected: 'Expected to be enclosed by {{kind}}.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()
    const double = context.options[0] !== 'single'
    const avoidEscape =
      context.options[1] && context.options[1].avoidEscape === true
    const quoteChar = double ? '"' : "'"
    const quoteName = double ? 'double quotes' : 'single quotes'
    /** @type {boolean} */
    let hasInvalidEOF

    return utils.defineTemplateBodyVisitor(
      context,
      {
        'VAttribute[value!=null]'(node) {
          if (hasInvalidEOF) {
            return
          }

          if (utils.isVBindSameNameShorthand(node)) {
            // v-bind same-name shorthand (Vue 3.4+)
            return
          }

          const text = sourceCode.getText(node.value)
          const firstChar = text[0]

          if (firstChar !== quoteChar) {
            const quoted = firstChar === "'" || firstChar === '"'
            if (avoidEscape && quoted) {
              const contentText = text.slice(1, -1)
              if (contentText.includes(quoteChar)) {
                return
              }
            }

            context.report({
              node: node.value,
              loc: node.value.loc,
              messageId: 'expected',
              data: { kind: quoteName },
              fix(fixer) {
                const contentText = quoted ? text.slice(1, -1) : text

                let fixToDouble = double
                if (avoidEscape && !quoted && contentText.includes(quoteChar)) {
                  fixToDouble = double
                    ? contentText.includes("'")
                    : !contentText.includes('"')
                }

                const quotePattern = fixToDouble ? /"/g : /'/g
                const quoteEscaped = fixToDouble ? '"' : '''
                const fixQuoteChar = fixToDouble ? '"' : "'"

                const replacement =
                  fixQuoteChar +
                  contentText.replace(quotePattern, quoteEscaped) +
                  fixQuoteChar
                return fixer.replaceText(node.value, replacement)
              }
            })
          }
        }
      },
      {
        Program(node) {
          hasInvalidEOF = utils.hasInvalidEOF(node)
        }
      }
    )
  }
}


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

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

/**
 * These strings wil be displayed in error messages.
 */
const ELEMENT_TYPE_MESSAGES = Object.freeze({
  NORMAL: 'HTML elements',
  VOID: 'HTML void elements',
  COMPONENT: 'Vue.js custom components',
  SVG: 'SVG elements',
  MATH: 'MathML elements',
  UNKNOWN: 'unknown elements'
})

/**
 * @typedef {object} Options
 * @property {'always' | 'never'} NORMAL
 * @property {'always' | 'never'} VOID
 * @property {'always' | 'never'} COMPONENT
 * @property {'always' | 'never'} SVG
 * @property {'always' | 'never'} MATH
 * @property {null} UNKNOWN
 */

/**
 * Normalize the given options.
 * @param {any} options The raw options object.
 * @returns {Options} Normalized options.
 */
function parseOptions(options) {
  return {
    NORMAL: (options && options.html && options.html.normal) || 'always',
    VOID: (options && options.html && options.html.void) || 'never',
    COMPONENT: (options && options.html && options.html.component) || 'always',
    SVG: (options && options.svg) || 'always',
    MATH: (options && options.math) || 'always',
    UNKNOWN: null
  }
}

/**
 * Get the elementType of the given element.
 * @param {VElement} node The element node to get.
 * @returns {keyof Options} The elementType of the element.
 */
function getElementType(node) {
  if (utils.isCustomComponent(node)) {
    return 'COMPONENT'
  }
  if (utils.isHtmlElementNode(node)) {
    if (utils.isHtmlVoidElementName(node.name)) {
      return 'VOID'
    }
    return 'NORMAL'
  }
  if (utils.isSvgElementNode(node)) {
    return 'SVG'
  }
  if (utils.isMathElementNode(node)) {
    return 'MATH'
  }
  return 'UNKNOWN'
}

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

  return sourceCode.text.slice(start, end).trim() === ''
}

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description: 'enforce self-closing style',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/html-self-closing.html'
    },
    fixable: 'code',
    schema: {
      definitions: {
        optionValue: {
          enum: ['always', 'never', 'any']
        }
      },
      type: 'array',
      items: [
        {
          type: 'object',
          properties: {
            html: {
              type: 'object',
              properties: {
                normal: { $ref: '#/definitions/optionValue' },
                void: { $ref: '#/definitions/optionValue' },
                component: { $ref: '#/definitions/optionValue' }
              },
              additionalProperties: false
            },
            svg: { $ref: '#/definitions/optionValue' },
            math: { $ref: '#/definitions/optionValue' }
          },
          additionalProperties: false
        }
      ],
      maxItems: 1
    },
    messages: {
      requireSelfClosing:
        'Require self-closing on {{elementType}} (<{{name}}>).',
      disallowSelfClosing:
        'Disallow self-closing on {{elementType}} (<{{name}}/>).'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()
    const options = parseOptions(context.options[0])
    let hasInvalidEOF = false

    return utils.defineTemplateBodyVisitor(
      context,
      {
        VElement(node) {
          if (hasInvalidEOF || node.parent.type === 'VDocumentFragment') {
            return
          }

          const elementType = getElementType(node)
          const mode = options[elementType]

          if (
            mode === 'always' &&
            !node.startTag.selfClosing &&
            isEmpty(node, sourceCode)
          ) {
            context.report({
              node,
              loc: node.loc,
              messageId: 'requireSelfClosing',
              data: {
                elementType: ELEMENT_TYPE_MESSAGES[elementType],
                name: node.rawName
              },
              fix(fixer) {
                const tokens =
                  sourceCode.parserServices.getTemplateBodyTokenStore()
                const close = tokens.getLastToken(node.startTag)
                if (close.type !== 'HTMLTagClose') {
                  return null
                }
                return fixer.replaceTextRange(
                  [close.range[0], node.range[1]],
                  '/>'
                )
              }
            })
          }

          if (mode === 'never' && node.startTag.selfClosing) {
            context.report({
              node,
              loc: node.loc,
              messageId: 'disallowSelfClosing',
              data: {
                elementType: ELEMENT_TYPE_MESSAGES[elementType],
                name: node.rawName
              },
              fix(fixer) {
                const tokens =
                  sourceCode.parserServices.getTemplateBodyTokenStore()
                const close = tokens.getLastToken(node.startTag)
                if (close.type !== 'HTMLSelfClosingTagClose') {
                  return null
                }
                if (elementType === 'VOID') {
                  return fixer.replaceText(close, '>')
                }
                // If only `close` is targeted for replacement, it conflicts with `component-name-in-template-casing`,
                // so replace the entire element.
                // return fixer.replaceText(close, `></${node.rawName}>`)
                const elementPart = sourceCode.text.slice(
                  node.range[0],
                  close.range[0]
                )
                return fixer.replaceText(
                  node,
                  `${elementPart}></${node.rawName}>`
                )
              }
            })
          }
        }
      },
      {
        Program(node) {
          hasInvalidEOF = utils.hasInvalidEOF(node)
        }
      }
    )
  }
}


code:
// the following rule is based on yannickcr/eslint-plugin-react

/**
The MIT License (MIT)

Copyright (c) 2014 Yannick Croissant

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
 */

/**
 * @fileoverview Prevent variables used in JSX to be marked as unused
 * @author Yannick Croissant
 */
'use strict'

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

module.exports = {
  // eslint-disable-next-line eslint-plugin/prefer-message-ids
  meta: {
    type: 'problem',
    docs: {
      description: 'prevent variables used in JSX to be marked as unused', // eslint-disable-line eslint-plugin/require-meta-docs-description
      categories: ['base'],
      url: 'https://eslint.vuejs.org/rules/jsx-uses-vars.html'
    },
    schema: []
  },
  /**
   * @param {RuleContext} context - The rule context.
   * @returns {RuleListener} AST event handlers.
   */
  create(context) {
    return {
      JSXOpeningElement(node) {
        let name
        if (node.name.type === 'JSXIdentifier') {
          // <Foo>
          name = node.name.name
        } else if (node.name.type === 'JSXMemberExpression') {
          // <Foo...Bar>
          let parent = node.name.object
          while (parent.type === 'JSXMemberExpression') {
            parent = parent.object
          }
          name = parent.name
        } else {
          return
        }

        utils.markVariableAsUsed(context, name, node)
      }
    }
  }
}


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

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

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


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

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

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


code:
/**
 * @fileoverview Require component name property to match its file name
 * @author Rodrigo Pedra Brum <rodrigo.pedra@gmail.com>
 */
'use strict'

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

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

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'require component name property to match its file name',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/match-component-file-name.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: [
      {
        type: 'object',
        properties: {
          extensions: {
            type: 'array',
            items: {
              type: 'string'
            },
            uniqueItems: true,
            additionalItems: false
          },
          shouldMatchCase: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      shouldMatchFileName:
        'Component name `{{name}}` should match file name `{{filename}}`.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0]
    const shouldMatchCase = (options && options.shouldMatchCase) || false
    const extensionsArray = options && options.extensions
    const allowedExtensions = Array.isArray(extensionsArray)
      ? extensionsArray
      : ['jsx']

    const extension = path.extname(context.getFilename())
    const filename = path.basename(context.getFilename(), extension)

    /** @type {Rule.ReportDescriptor[]} */
    const errors = []
    let componentCount = 0

    if (!allowedExtensions.includes(extension.replace(/^\./, ''))) {
      return {}
    }

    /**
     * @param {string} name
     * @param {string} filename
     */
    function compareNames(name, filename) {
      if (shouldMatchCase) {
        return name === filename
      }

      return (
        casing.pascalCase(name) === filename ||
        casing.kebabCase(name) === filename
      )
    }

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

      if (!compareNames(name, filename)) {
        errors.push({
          node,
          messageId: 'shouldMatchFileName',
          data: { filename, name },
          suggest: [
            {
              desc: 'Rename component to match file name.',
              fix(fixer) {
                const quote =
                  node.type === 'TemplateLiteral' ? '`' : node.raw[0]
                return fixer.replaceText(node, `${quote}${filename}${quote}`)
              }
            }
          ]
        })
      }
    }

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

          if (canVerify(argument)) {
            verifyName(argument)
          }
        }
      }),
      utils.executeOnVue(context, (object) => {
        const node = utils.findProperty(object, 'name')

        componentCount++

        if (!node) return
        if (!canVerify(node.value)) return
        verifyName(node.value)
      }),
      utils.defineScriptSetupVisitor(context, {
        onDefineOptionsEnter(node) {
          componentCount++
          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 (!canVerify(nameNode.value)) return
          verifyName(nameNode.value)
        }
      }),
      {
        'Program:exit'() {
          if (componentCount > 1) return

          for (const error of errors) context.report(error)
        }
      }
    )
  }
}


code:
/**
 * @author Doug Wade <douglas.b.wade@gmail.com>
 * See LICENSE file in root directory for full license.
 */
'use strict'

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

/**
 * @param {Identifier} identifier
 * @return {Array<String>}
 */
function getExpectedNames(identifier) {
  return [casing.pascalCase(identifier.name), casing.kebabCase(identifier.name)]
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description:
        'require the registered component name to match the imported component name',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/match-component-import-name.html'
    },
    fixable: null,
    schema: [],
    messages: {
      unexpected:
        'Component alias {{importedName}} should be one of: {{expectedName}}.'
    }
  },
  /**
   * @param {RuleContext} context
   * @returns {RuleListener}
   */
  create(context) {
    return utils.executeOnVueComponent(context, (obj) => {
      const components = utils.findProperty(obj, 'components')
      if (
        !components ||
        !components.value ||
        components.value.type !== 'ObjectExpression'
      ) {
        return
      }

      for (const property of components.value.properties) {
        if (
          property.type === 'SpreadElement' ||
          property.value.type !== 'Identifier' ||
          property.computed === true
        ) {
          continue
        }

        const importedName = utils.getStaticPropertyName(property) || ''
        const expectedNames = getExpectedNames(property.value)
        if (!expectedNames.includes(importedName)) {
          context.report({
            node: property,
            messageId: 'unexpected',
            data: {
              importedName,
              expectedName: expectedNames.join(', ')
            }
          })
        }
      }
    })
  }
}


code:
/**
 * @fileoverview Define the number of attributes allows per line
 * @author Filipa Lacerda
 */
'use strict'

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

/**
 * @param {any} options
 */
function parseOptions(options) {
  const defaults = {
    singleline: 1,
    multiline: 1
  }

  if (options) {
    if (typeof options.singleline === 'number') {
      defaults.singleline = options.singleline
    } else if (
      typeof options.singleline === 'object' &&
      typeof options.singleline.max === 'number'
    ) {
      defaults.singleline = options.singleline.max
    }

    if (options.multiline) {
      if (typeof options.multiline === 'number') {
        defaults.multiline = options.multiline
      } else if (
        typeof options.multiline === 'object' &&
        typeof options.multiline.max === 'number'
      ) {
        defaults.multiline = options.multiline.max
      }
    }
  }

  return defaults
}

/**
 * @param {(VDirective | VAttribute)[]} attributes
 */
function groupAttrsByLine(attributes) {
  const propsPerLine = [[attributes[0]]]

  for (let index = 1; index < attributes.length; index++) {
    const previous = attributes[index - 1]
    const current = attributes[index]

    if (previous.loc.end.line === current.loc.start.line) {
      propsPerLine[propsPerLine.length - 1].push(current)
    } else {
      propsPerLine.push([current])
    }
  }

  return propsPerLine
}

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description: 'enforce the maximum number of attributes per line',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/max-attributes-per-line.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        type: 'object',
        properties: {
          singleline: {
            anyOf: [
              {
                type: 'number',
                minimum: 1
              },
              {
                type: 'object',
                properties: {
                  max: {
                    type: 'number',
                    minimum: 1
                  }
                },
                additionalProperties: false
              }
            ]
          },
          multiline: {
            anyOf: [
              {
                type: 'number',
                minimum: 1
              },
              {
                type: 'object',
                properties: {
                  max: {
                    type: 'number',
                    minimum: 1
                  }
                },
                additionalProperties: false
              }
            ]
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      shouldBeOnNewLine: "'{{name}}' should be on a new line."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const sourceCode = context.getSourceCode()
    const configuration = parseOptions(context.options[0])
    const multilineMaximum = configuration.multiline
    const singlelinemMaximum = configuration.singleline
    const template =
      sourceCode.parserServices.getTemplateBodyTokenStore &&
      sourceCode.parserServices.getTemplateBodyTokenStore()

    return utils.defineTemplateBodyVisitor(context, {
      VStartTag(node) {
        const numberOfAttributes = node.attributes.length

        if (!numberOfAttributes) return

        if (
          utils.isSingleLine(node) &&
          numberOfAttributes > singlelinemMaximum
        ) {
          showErrors(node.attributes.slice(singlelinemMaximum))
        }

        if (!utils.isSingleLine(node)) {
          for (const attrs of groupAttrsByLine(node.attributes)) {
            if (attrs.length > multilineMaximum) {
              showErrors(attrs.splice(multilineMaximum))
            }
          }
        }
      }
    })

    /**
     * @param {(VDirective | VAttribute)[]} attributes
     */
    function showErrors(attributes) {
      for (const [i, prop] of attributes.entries()) {
        context.report({
          node: prop,
          loc: prop.loc,
          messageId: 'shouldBeOnNewLine',
          data: { name: sourceCode.getText(prop.key) },
          fix(fixer) {
            if (i !== 0) return null

            // Find the closest token before the current prop
            // that is not a white space
            const prevToken = /** @type {Token} */ (
              template.getTokenBefore(prop, {
                filter: (token) => token.type !== 'HTMLWhitespace'
              })
            )

            /** @type {Range} */
            const range = [prevToken.range[1], prop.range[0]]

            return fixer.replaceTextRange(range, '\n')
          }
        })
      }
    }
  }
}


code:
/**
 * @author Yosuke Ota
 * @fileoverview Rule to check for max length on a line of Vue file.
 */
'use strict'

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

const OPTIONS_SCHEMA = {
  type: 'object',
  properties: {
    code: {
      type: 'integer',
      minimum: 0
    },
    template: {
      type: 'integer',
      minimum: 0
    },
    comments: {
      type: 'integer',
      minimum: 0
    },
    tabWidth: {
      type: 'integer',
      minimum: 0
    },
    ignorePattern: {
      type: 'string'
    },
    ignoreComments: {
      type: 'boolean'
    },
    ignoreTrailingComments: {
      type: 'boolean'
    },
    ignoreUrls: {
      type: 'boolean'
    },
    ignoreStrings: {
      type: 'boolean'
    },
    ignoreTemplateLiterals: {
      type: 'boolean'
    },
    ignoreRegExpLiterals: {
      type: 'boolean'
    },
    ignoreHTMLAttributeValues: {
      type: 'boolean'
    },
    ignoreHTMLTextContents: {
      type: 'boolean'
    }
  },
  additionalProperties: false
}

const OPTIONS_OR_INTEGER_SCHEMA = {
  anyOf: [
    OPTIONS_SCHEMA,
    {
      type: 'integer',
      minimum: 0
    }
  ]
}

/**
 * Computes the length of a line that may contain tabs. The width of each
 * tab will be the number of spaces to the next tab stop.
 * @param {string} line The line.
 * @param {number} tabWidth The width of each tab stop in spaces.
 * @returns {number} The computed line length.
 * @private
 */
function computeLineLength(line, tabWidth) {
  let extraCharacterCount = 0

  const re = /\t/gu
  let ret
  while ((ret = re.exec(line))) {
    const offset = ret.index
    const totalOffset = offset + extraCharacterCount
    const previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0
    const spaceCount = tabWidth - previousTabStopOffset
    extraCharacterCount += spaceCount - 1 // -1 for the replaced tab
  }

  return [...line].length + extraCharacterCount
}

/**
 * Tells if a given comment is trailing: it starts on the current line and
 * extends to or past the end of the current line.
 * @param {string} line The source line we want to check for a trailing comment on
 * @param {number} lineNumber The one-indexed line number for line
 * @param {Token | null} comment The comment to inspect
 * @returns {comment is Token} If the comment is trailing on the given line
 */
function isTrailingComment(line, lineNumber, comment) {
  return Boolean(
    comment &&
      comment.loc.start.line === lineNumber &&
      lineNumber <= comment.loc.end.line &&
      (comment.loc.end.line > lineNumber ||
        comment.loc.end.column === line.length)
  )
}

/**
 * Tells if a comment encompasses the entire line.
 * @param {string} line The source line with a trailing comment
 * @param {number} lineNumber The one-indexed line number this is on
 * @param {Token | null} comment The comment to remove
 * @returns {boolean} If the comment covers the entire line
 */
function isFullLineComment(line, lineNumber, comment) {
  if (!comment) {
    return false
  }
  const start = comment.loc.start
  const end = comment.loc.end
  const isFirstTokenOnLine = !line.slice(0, comment.loc.start.column).trim()

  return (
    comment &&
    (start.line < lineNumber ||
      (start.line === lineNumber && isFirstTokenOnLine)) &&
    (end.line > lineNumber ||
      (end.line === lineNumber && end.column === line.length))
  )
}

/**
 * Gets the line after the comment and any remaining trailing whitespace is
 * stripped.
 * @param {string} line The source line with a trailing comment
 * @param {Token} comment The comment to remove
 * @returns {string} Line without comment and trailing whitepace
 */
function stripTrailingComment(line, comment) {
  // loc.column is zero-indexed
  return line.slice(0, comment.loc.start.column).replace(/\s+$/u, '')
}

/**
 * Group AST nodes by line number, both start and end.
 *
 * @param {Token[]} nodes the AST nodes in question
 * @returns { { [key: number]: Token[] } } the grouped nodes
 * @private
 */
function groupByLineNumber(nodes) {
  /** @type { { [key: number]: Token[] } } */
  const grouped = {}
  for (const node of nodes) {
    for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) {
      if (!Array.isArray(grouped[i])) {
        grouped[i] = []
      }
      grouped[i].push(node)
    }
  }
  return grouped
}

module.exports = {
  meta: {
    type: 'layout',

    docs: {
      description: 'enforce a maximum line length in `.vue` files',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/max-len.html',
      extensionSource: {
        url: 'https://eslint.org/docs/rules/max-len',
        name: 'ESLint core'
      }
    },

    schema: [
      OPTIONS_OR_INTEGER_SCHEMA,
      OPTIONS_OR_INTEGER_SCHEMA,
      OPTIONS_SCHEMA
    ],
    messages: {
      max: 'This line has a length of {{lineLength}}. Maximum allowed is {{maxLength}}.',
      maxComment:
        'This line has a comment length of {{lineLength}}. Maximum allowed is {{maxCommentLength}}.'
    }
  },
  /**
   * @param {RuleContext} context - The rule context.
   * @returns {RuleListener} AST event handlers.
   */
  create(context) {
    /*
     * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however:
     * - They're matching an entire string that we know is a URI
     * - We're matching part of a string where we think there *might* be a URL
     * - We're only concerned about URLs, as picking out any URI would cause
     *   too many false positives
     * - We don't care about matching the entire URL, any small segment is fine
     */
    const URL_REGEXP = /[^:/?#]:\/\/[^?#]/u

    const sourceCode = context.getSourceCode()
    /** @type {Token[]} */
    const tokens = []
    /** @type {(HTMLComment | HTMLBogusComment | Comment)[]} */
    const comments = []
    /** @type {VLiteral[]} */
    const htmlAttributeValues = []

    // The options object must be the last option specified…
    const options = Object.assign(
      {},
      context.options[context.options.length - 1]
    )

    // …but max code length…
    if (typeof context.options[0] === 'number') {
      options.code = context.options[0]
    }

    // …and tabWidth can be optionally specified directly as integers.
    if (typeof context.options[1] === 'number') {
      options.tabWidth = context.options[1]
    }
    /** @type {number} */
    const scriptMaxLength = typeof options.code === 'number' ? options.code : 80
    /** @type {number} */
    const tabWidth = typeof options.tabWidth === 'number' ? options.tabWidth : 2 // default value of `vue/html-indent`
    /** @type {number} */
    const templateMaxLength =
      typeof options.template === 'number' ? options.template : scriptMaxLength
    const ignoreComments = !!options.ignoreComments
    const ignoreStrings = !!options.ignoreStrings
    const ignoreTemplateLiterals = !!options.ignoreTemplateLiterals
    const ignoreRegExpLiterals = !!options.ignoreRegExpLiterals
    const ignoreTrailingComments =
      !!options.ignoreTrailingComments || !!options.ignoreComments
    const ignoreUrls = !!options.ignoreUrls
    const ignoreHTMLAttributeValues = !!options.ignoreHTMLAttributeValues
    const ignoreHTMLTextContents = !!options.ignoreHTMLTextContents
    /** @type {number} */
    const maxCommentLength = options.comments
    /** @type {RegExp} */
    let ignorePattern = options.ignorePattern || null

    if (ignorePattern) {
      ignorePattern = new RegExp(ignorePattern, 'u')
    }

    /**
     * Retrieves an array containing all strings (" or ') in the source code.
     *
     * @returns {Token[]} An array of string nodes.
     */
    function getAllStrings() {
      return tokens.filter(
        (token) =>
          token.type === 'String' ||
          (token.type === 'JSXText' &&
            sourceCode.getNodeByRangeIndex(token.range[0] - 1).type ===
              'JSXAttribute')
      )
    }

    /**
     * Retrieves an array containing all template literals in the source code.
     *
     * @returns {Token[]} An array of template literal nodes.
     */
    function getAllTemplateLiterals() {
      return tokens.filter((token) => token.type === 'Template')
    }

    /**
     * Retrieves an array containing all RegExp literals in the source code.
     *
     * @returns {Token[]} An array of RegExp literal nodes.
     */
    function getAllRegExpLiterals() {
      return tokens.filter((token) => token.type === 'RegularExpression')
    }

    /**
     * Retrieves an array containing all HTML texts in the source code.
     *
     * @returns {Token[]} An array of HTML text nodes.
     */
    function getAllHTMLTextContents() {
      return tokens.filter((token) => token.type === 'HTMLText')
    }

    /**
     * Check the program for max length
     * @param {Program} node Node to examine
     * @returns {void}
     * @private
     */
    function checkProgramForMaxLength(node) {
      const programNode = node
      const templateBody = node.templateBody

      // setup tokens
      const scriptTokens = sourceCode.ast.tokens
      const scriptComments = sourceCode.getAllComments()

      if (sourceCode.parserServices.getTemplateBodyTokenStore && templateBody) {
        const tokenStore = sourceCode.parserServices.getTemplateBodyTokenStore()

        const templateTokens = tokenStore.getTokens(templateBody, {
          includeComments: true
        })

        if (templateBody.range[0] < programNode.range[0]) {
          tokens.push(...templateTokens, ...scriptTokens)
        } else {
          tokens.push(...scriptTokens, ...templateTokens)
        }
      } else {
        tokens.push(...scriptTokens)
      }

      if (ignoreComments || maxCommentLength || ignoreTrailingComments) {
        // list of comments to ignore
        if (templateBody) {
          if (templateBody.range[0] < programNode.range[0]) {
            comments.push(...templateBody.comments, ...scriptComments)
          } else {
            comments.push(...scriptComments, ...templateBody.comments)
          }
        } else {
          comments.push(...scriptComments)
        }
      }

      /** @type {Range | undefined} */
      let scriptLinesRange
      if (scriptTokens.length > 0) {
        scriptLinesRange =
          scriptComments.length > 0
            ? [
                Math.min(
                  scriptTokens[0].loc.start.line,
                  scriptComments[0].loc.start.line
                ),
                Math.max(
                  scriptTokens[scriptTokens.length - 1].loc.end.line,
                  scriptComments[scriptComments.length - 1].loc.end.line
                )
              ]
            : [
                scriptTokens[0].loc.start.line,
                scriptTokens[scriptTokens.length - 1].loc.end.line
              ]
      } else if (scriptComments.length > 0) {
        scriptLinesRange = [
          scriptComments[0].loc.start.line,
          scriptComments[scriptComments.length - 1].loc.end.line
        ]
      }
      const templateLinesRange = templateBody && [
        templateBody.loc.start.line,
        templateBody.loc.end.line
      ]

      // split (honors line-ending)
      const lines = sourceCode.lines

      const strings = getAllStrings()
      const stringsByLine = groupByLineNumber(strings)

      const templateLiterals = getAllTemplateLiterals()
      const templateLiteralsByLine = groupByLineNumber(templateLiterals)

      const regExpLiterals = getAllRegExpLiterals()
      const regExpLiteralsByLine = groupByLineNumber(regExpLiterals)

      const htmlAttributeValuesByLine = groupByLineNumber(htmlAttributeValues)

      const htmlTextContents = getAllHTMLTextContents()
      const htmlTextContentsByLine = groupByLineNumber(htmlTextContents)

      const commentsByLine = groupByLineNumber(comments)

      for (const [i, line] of lines.entries()) {
        // i is zero-indexed, line numbers are one-indexed
        const lineNumber = i + 1

        const inScript =
          scriptLinesRange &&
          scriptLinesRange[0] <= lineNumber &&
          lineNumber <= scriptLinesRange[1]
        const inTemplate =
          templateLinesRange &&
          templateLinesRange[0] <= lineNumber &&
          lineNumber <= templateLinesRange[1]
        // check if line is inside a script or template.
        if (!inScript && !inTemplate) {
          // out of range.
          continue
        }
        const maxLength = Math.max(
          inScript ? scriptMaxLength : 0,
          inTemplate ? templateMaxLength : 0
        )

        if (
          (ignoreStrings && stringsByLine[lineNumber]) ||
          (ignoreTemplateLiterals && templateLiteralsByLine[lineNumber]) ||
          (ignoreRegExpLiterals && regExpLiteralsByLine[lineNumber]) ||
          (ignoreHTMLAttributeValues &&
            htmlAttributeValuesByLine[lineNumber]) ||
          (ignoreHTMLTextContents && htmlTextContentsByLine[lineNumber])
        ) {
          // ignore this line
          continue
        }

        /*
         * if we're checking comment length; we need to know whether this
         * line is a comment
         */
        let lineIsComment = false
        let textToMeasure

        /*
         * comments to check.
         */
        if (commentsByLine[lineNumber]) {
          const commentList = [...commentsByLine[lineNumber]]

          let comment = commentList.pop() || null

          if (isFullLineComment(line, lineNumber, comment)) {
            lineIsComment = true
            textToMeasure = line
          } else if (
            ignoreTrailingComments &&
            isTrailingComment(line, lineNumber, comment)
          ) {
            textToMeasure = stripTrailingComment(line, comment)

            // ignore multiple trailing comments in the same line
            comment = commentList.pop() || null

            while (isTrailingComment(textToMeasure, lineNumber, comment)) {
              textToMeasure = stripTrailingComment(textToMeasure, comment)
            }
          } else {
            textToMeasure = line
          }
        } else {
          textToMeasure = line
        }

        if (
          (ignorePattern && ignorePattern.test(textToMeasure)) ||
          (ignoreUrls && URL_REGEXP.test(textToMeasure))
        ) {
          // ignore this line
          continue
        }

        const lineLength = computeLineLength(textToMeasure, tabWidth)
        const commentLengthApplies = lineIsComment && maxCommentLength

        if (lineIsComment && ignoreComments) {
          continue
        }

        if (commentLengthApplies) {
          if (lineLength > maxCommentLength) {
            context.report({
              node,
              loc: { line: lineNumber, column: 0 },
              messageId: 'maxComment',
              data: {
                lineLength,
                maxCommentLength
              }
            })
          }
        } else if (lineLength > maxLength) {
          context.report({
            node,
            loc: { line: lineNumber, column: 0 },
            messageId: 'max',
            data: {
              lineLength,
              maxLength
            }
          })
        }
      }
    }

    return utils.compositingVisitors(
      utils.defineTemplateBodyVisitor(context, {
        /** @param {VLiteral} node */
        'VAttribute[directive=false] > VLiteral'(node) {
          htmlAttributeValues.push(node)
        }
      }),
      {
        'Program:exit'(node) {
          checkProgramForMaxLength(node)
        }
      }
    )
  }
}


code:
/**
 * @author lsdsjy
 * @fileoverview Rule for checking the maximum number of lines in Vue SFC blocks.
 */
'use strict'

const { SourceCode } = require('eslint')
const utils = require('../utils')

/**
 * @param {string} text
 */
function isEmptyLine(text) {
  return !text.trim()
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'enforce maximum number of lines in Vue SFC blocks',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/max-lines-per-block.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          style: {
            type: 'integer',
            minimum: 1
          },
          template: {
            type: 'integer',
            minimum: 1
          },
          script: {
            type: 'integer',
            minimum: 1
          },
          skipBlankLines: {
            type: 'boolean',
            minimum: 0
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      tooManyLines:
        'Block has too many lines ({{lineCount}}). Maximum allowed is {{limit}}.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const option = context.options[0] || {}
    /**
     * @type {Record<string, number>}
     */
    const limits = {
      template: option.template,
      script: option.script,
      style: option.style
    }

    const code = context.getSourceCode()
    const sourceCode = context.getSourceCode()
    const documentFragment =
      sourceCode.parserServices.getDocumentFragment &&
      sourceCode.parserServices.getDocumentFragment()

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

    return {
      /** @param {Program} node */
      Program(node) {
        if (utils.hasInvalidEOF(node)) {
          return
        }
        for (const block of getTopLevelHTMLElements()) {
          if (limits[block.name]) {
            // We suppose the start tag and end tag occupy one single line respectively
            let lineCount = block.loc.end.line - block.loc.start.line - 1

            if (option.skipBlankLines) {
              const lines = SourceCode.splitLines(code.getText(block))
              lineCount -= lines.filter(isEmptyLine).length
            }

            if (lineCount > limits[block.name]) {
              context.report({
                node: block,
                messageId: 'tooManyLines',
                data: {
                  limit: limits[block.name],
                  lineCount
                }
              })
            }
          }
        }
      }
    }
  }
}


code:
/**
 * @author kevsommer Kevin Sommer
 * See LICENSE file in root directory for full license.
 */
'use strict'
const utils = require('../utils')

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'enforce maximum number of props in Vue component',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/max-props.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          maxProps: {
            type: 'integer',
            minimum: 1
          }
        },
        additionalProperties: false,
        minProperties: 1
      }
    ],
    messages: {
      tooManyProps:
        'Component has too many props ({{propCount}}). Maximum allowed is {{limit}}.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {Record<string, number>} */
    const option = context.options[0] || {}

    /**
     * @param {import('../utils').ComponentProp[]} props
     */
    function checkMaxNumberOfProps(props) {
      if (props.length > option.maxProps && props[0].node) {
        context.report({
          node: props[0].node.parent,
          messageId: 'tooManyProps',
          data: {
            propCount: props.length,
            limit: option.maxProps
          }
        })
      }
    }

    return utils.compositingVisitors(
      utils.executeOnVue(context, (obj) => {
        checkMaxNumberOfProps(utils.getComponentPropsFromOptions(obj))
      }),
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(_, props) {
          checkMaxNumberOfProps(props)
        }
      })
    )
  }
}


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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'enforce maximum depth of template',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/max-template-depth.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          maxDepth: {
            type: 'integer',
            minimum: 1
          }
        },
        additionalProperties: false,
        minProperties: 1
      }
    ],
    messages: {
      templateTooDeep:
        'Element is nested too deeply (depth of {{depth}}, maximum allowed is {{limit}}).'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const option = context.options[0] || {}

    /**
     * @param {VElement} element
     * @param {number} curDepth
     */
    function checkMaxDepth(element, curDepth) {
      if (curDepth > option.maxDepth) {
        context.report({
          node: element,
          messageId: 'templateTooDeep',
          data: {
            depth: curDepth,
            limit: option.maxDepth
          }
        })
      }

      if (!element.children) {
        return
      }

      for (const child of element.children) {
        if (child.type === 'VElement') {
          checkMaxDepth(child, curDepth + 1)
        }
      }
    }

    return {
      /** @param {Program} program */
      Program(program) {
        const element = program.templateBody

        if (element == null) {
          return
        }

        if (element.type !== 'VElement') {
          return
        }

        checkMaxDepth(element, 0)
      }
    }
  }
}


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

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

const RESERVED_NAMES_IN_VUE3 = new Set(
  require('../utils/vue3-builtin-components')
)

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'require component names to be always multi-word',
      categories: ['vue3-essential', 'vue2-essential'],
      url: 'https://eslint.vuejs.org/rules/multi-word-component-names.html'
    },
    schema: [
      {
        type: 'object',
        properties: {
          ignores: {
            type: 'array',
            items: { type: 'string' },
            uniqueItems: true,
            additionalItems: false
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      unexpected: 'Component name "{{value}}" should always be multi-word.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /** @type {Set<string>} */
    const ignores = new Set()
    ignores.add('App')
    ignores.add('app')
    for (const ignore of (context.options[0] && context.options[0].ignores) ||
      []) {
      ignores.add(ignore)
      if (casing.isPascalCase(ignore)) {
        // PascalCase
        ignores.add(casing.kebabCase(ignore))
      }
    }
    let hasVue = utils.isScriptSetup(context)
    let hasName = false

    /**
     * Returns true if the given component name is valid, otherwise false.
     * @param {string} name
     * */
    function isValidComponentName(name) {
      if (ignores.has(name) || RESERVED_NAMES_IN_VUE3.has(name)) {
        return true
      }
      const elements = casing.kebabCase(name).split('-')
      return elements.length > 1
    }

    /**
     * @param {Expression | SpreadElement} nameNode
     */
    function validateName(nameNode) {
      if (nameNode.type !== 'Literal') return
      const componentName = `${nameNode.value}`
      if (!isValidComponentName(componentName)) {
        context.report({
          node: nameNode,
          messageId: 'unexpected',
          data: {
            value: componentName
          }
        })
      }
    }

    return utils.compositingVisitors(
      utils.executeOnCallVueComponent(context, (node) => {
        hasVue = true
        if (node.arguments.length !== 2) return
        hasName = true
        validateName(node.arguments[0])
      }),
      utils.executeOnVue(context, (obj) => {
        hasVue = true
        const node = utils.findProperty(obj, 'name')
        if (!node) return
        hasName = true
        validateName(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
          hasName = true
          validateName(nameNode.value)
        }
      }),
      {
        /** @param {Program} node */
        'Program:exit'(node) {
          if (hasName) return
          if (!hasVue && node.body.length > 0) return
          const fileName = context.getFilename()
          const componentName = path.basename(fileName, path.extname(fileName))
          if (
            utils.isVueFile(fileName) &&
            !isValidComponentName(componentName)
          ) {
            context.report({
              messageId: 'unexpected',
              data: {
                value: componentName
              },
              loc: { line: 1, column: 0 }
            })
          }
        }
      }
    )
  }
}


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

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

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

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

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

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

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

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

    /**
     * @param {number} lineBreaks
     */
    function isInvalidLineBreaks(lineBreaks) {
      return allowEmptyLines ? lineBreaks === 0 : lineBreaks !== 1
    }

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

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

        if (!isMultilineElement(element)) {
          return
        }

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

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

        const beforeLineBreaks =
          contentFirst.loc.start.line - element.startTag.loc.end.line
        const afterLineBreaks =
          element.endTag.loc.start.line - contentLast.loc.end.line
        if (isInvalidLineBreaks(beforeLineBreaks)) {
          context.report({
            node: template.getLastToken(element.startTag),
            loc: {
              start: element.startTag.loc.end,
              end: contentFirst.loc.start
            },
            messageId: 'unexpectedAfterClosingBracket',
            data: {
              name: element.rawName,
              actual: getPhrase(beforeLineBreaks)
            },
            fix(fixer) {
              /** @type {Range} */
              const range = [element.startTag.range[1], contentFirst.range[0]]
              return fixer.replaceTextRange(range, '\n')
            }
          })
        }

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

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


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

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

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


code:
/**
 * @fileoverview enforce unified spacing in mustache interpolations.
 * @author Armano
 */
'use strict'

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

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description: 'enforce unified spacing in mustache interpolations',
      categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
      url: 'https://eslint.vuejs.org/rules/mustache-interpolation-spacing.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        enum: ['always', 'never']
      }
    ],
    messages: {
      expectedSpaceAfter: "Expected 1 space after '{{', but not found.",
      expectedSpaceBefore: "Expected 1 space before '}}', but not found.",
      unexpectedSpaceAfter: "Expected no space after '{{', but found.",
      unexpectedSpaceBefore: "Expected no space before '}}', but found."
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || 'always'
    const sourceCode = context.getSourceCode()
    const template =
      sourceCode.parserServices.getTemplateBodyTokenStore &&
      sourceCode.parserServices.getTemplateBodyTokenStore()

    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VExpressionContainer} node */
      'VExpressionContainer[expression!=null]'(node) {
        const openBrace = template.getFirstToken(node)
        const closeBrace = template.getLastToken(node)

        if (
          !openBrace ||
          !closeBrace ||
          openBrace.type !== 'VExpressionStart' ||
          closeBrace.type !== 'VExpressionEnd'
        ) {
          return
        }

        const firstToken = template.getTokenAfter(openBrace, {
          includeComments: true
        })
        const lastToken = template.getTokenBefore(closeBrace, {
          includeComments: true
        })

        if (options === 'always') {
          if (openBrace.range[1] === firstToken.range[0]) {
            context.report({
              node: openBrace,
              messageId: 'expectedSpaceAfter',
              fix: (fixer) => fixer.insertTextAfter(openBrace, ' ')
            })
          }
          if (closeBrace.range[0] === lastToken.range[1]) {
            context.report({
              node: closeBrace,
              messageId: 'expectedSpaceBefore',
              fix: (fixer) => fixer.insertTextBefore(closeBrace, ' ')
            })
          }
        } else {
          if (openBrace.range[1] !== firstToken.range[0]) {
            context.report({
              loc: {
                start: openBrace.loc.start,
                end: firstToken.loc.start
              },
              messageId: 'unexpectedSpaceAfter',
              fix: (fixer) =>
                fixer.removeRange([openBrace.range[1], firstToken.range[0]])
            })
          }
          if (closeBrace.range[0] !== lastToken.range[1]) {
            context.report({
              loc: {
                start: lastToken.loc.end,
                end: closeBrace.loc.end
              },
              messageId: 'unexpectedSpaceBefore',
              fix: (fixer) =>
                fixer.removeRange([lastToken.range[1], closeBrace.range[0]])
            })
          }
        }
      }
    })
  }
}


code:
/**
 * @fileoverview Enforce new lines between multi-line properties in Vue components.
 * @author IWANABETHATGUY
 */
'use strict'

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

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

/**
 * Check whether the between given nodes has empty line.
 * @param {SourceCode} sourceCode
 * @param {ASTNode} pre
 * @param {ASTNode} cur
 */
function* iterateBetweenTokens(sourceCode, pre, cur) {
  yield sourceCode.getLastToken(pre)
  yield* sourceCode.getTokensBetween(pre, cur, {
    includeComments: true
  })
  yield sourceCode.getFirstToken(cur)
}

/**
 * Check whether the between given nodes has empty line.
 * @param {SourceCode} sourceCode
 * @param {ASTNode} pre
 * @param {ASTNode} cur
 */
function hasEmptyLine(sourceCode, pre, cur) {
  /** @type {Token|null} */
  let preToken = null
  for (const token of iterateBetweenTokens(sourceCode, pre, cur)) {
    if (preToken && token.loc.start.line - preToken.loc.end.line >= 2) {
      return true
    }
    preToken = token
  }
  return false
}

module.exports = {
  meta: {
    type: 'layout',
    docs: {
      description:
        'enforce new lines between multi-line properties in Vue components',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/new-line-between-multi-line-property.html'
    },
    fixable: 'whitespace',
    schema: [
      {
        type: 'object',
        properties: {
          // number of line you want to insert after multi-line property
          minLineOfMultilineProperty: {
            type: 'number',
            minimum: 2
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      missingEmptyLine:
        'Enforce new lines between multi-line properties in Vue components.'
    }
  },

  /** @param {RuleContext} context */
  create(context) {
    let minLineOfMultilineProperty = 2
    if (
      context.options &&
      context.options[0] &&
      context.options[0].minLineOfMultilineProperty
    ) {
      minLineOfMultilineProperty = context.options[0].minLineOfMultilineProperty
    }

    /** @type {CallExpression[]} */
    const callStack = []
    const sourceCode = context.getSourceCode()
    return Object.assign(
      utils.defineVueVisitor(context, {
        CallExpression(node) {
          callStack.push(node)
        },
        'CallExpression:exit'() {
          callStack.pop()
        },

        /**
         * @param {ObjectExpression} node
         */
        ObjectExpression(node) {
          if (callStack.length > 0) {
            return
          }
          const properties = node.properties
          for (let i = 1; i < properties.length; i++) {
            const cur = properties[i]
            const pre = properties[i - 1]

            const lineCountOfPreProperty =
              pre.loc.end.line - pre.loc.start.line + 1
            if (lineCountOfPreProperty < minLineOfMultilineProperty) {
              continue
            }

            if (hasEmptyLine(sourceCode, pre, cur)) {
              continue
            }

            context.report({
              node: pre,
              loc: {
                start: pre.loc.end,
                end: cur.loc.start
              },
              messageId: 'missingEmptyLine',
              fix(fixer) {
                /** @type {Token|null} */
                let preToken = null
                for (const token of iterateBetweenTokens(
                  sourceCode,
                  pre,
                  cur
                )) {
                  if (
                    preToken &&
                    preToken.loc.end.line < token.loc.start.line
                  ) {
                    return fixer.insertTextAfter(preToken, '\n')
                  }
                  preToken = token
                }
                const commaToken = sourceCode.getTokenAfter(pre, isComma)
                return fixer.insertTextAfter(commaToken || pre, '\n\n')
              }
            })
          }
        }
      })
    )
  }
}