UNPKG

eslint-plugin-vue

Version:

Official ESLint plugin for Vue.js

1,286 lines (1,284 loc) 76.7 kB
'use strict'; const require_runtime = require('../_virtual/_rolldown/runtime.js'); const require_scope$1 = require('./scope.js'); const require_html_elements$1 = require('./html-elements.js'); const require_svg_elements$1 = require('./svg-elements.js'); const require_math_elements$1 = require('./math-elements.js'); const require_void_elements$1 = require('./void-elements.js'); const require_vue2_builtin_components$1 = require('./vue2-builtin-components.js'); const require_vue3_builtin_components$1 = require('./vue3-builtin-components.js'); const require_vue_builtin_elements$1 = require('./vue-builtin-elements.js'); const require_index = require('./ts-utils/index.js'); //#region lib/utils/index.js /** * @author Toru Nagashima <https://github.com/mysticatea> * @copyright 2017 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */ var require_utils = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, module) => { const { getScope } = require_scope$1.default; /** * @typedef {import('eslint').Rule.RuleModule} RuleModule * @typedef {import('estree').Position} Position * @typedef {import('eslint').Rule.CodePath} CodePath * @typedef {import('eslint').Rule.CodePathSegment} CodePathSegment */ /** * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentArrayProp} ComponentArrayProp * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectProp} ComponentObjectProp * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeProp} ComponentTypeProp * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentInferTypeProp} ComponentInferTypeProp * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownProp} ComponentUnknownProp * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentProp} ComponentProp * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentArrayEmit} ComponentArrayEmit * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectEmit} ComponentObjectEmit * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeEmit} ComponentTypeEmit * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentInferTypeEmit} ComponentInferTypeEmit * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownEmit} ComponentUnknownEmit * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentEmit} ComponentEmit * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeSlot} ComponentTypeSlot * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentInferTypeSlot} ComponentInferTypeSlot * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownSlot} ComponentUnknownSlot * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentSlot} ComponentSlot * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentModelName} ComponentModelName * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentModel} ComponentModel */ /** * @typedef { {key: string | null, value: BlockStatement | null} } ComponentComputedProperty */ /** * @typedef { 'props' | 'asyncData' | 'data' | 'computed' | 'setup' | 'watch' | 'methods' | 'provide' | 'inject' | 'expose' } GroupName * @typedef { { type: 'array', name: string, groupName: GroupName, node: Literal | TemplateLiteral } } ComponentArrayPropertyData * @typedef { { type: 'object', name: string, groupName: GroupName, node: Identifier | Literal | TemplateLiteral, property: Property } } ComponentObjectPropertyData * @typedef { ComponentArrayPropertyData | ComponentObjectPropertyData } ComponentPropertyData */ /** * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').VueObjectType} VueObjectType * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').VueObjectData} VueObjectData * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').VueVisitor} VueVisitor * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ScriptSetupVisitor} ScriptSetupVisitor */ const HTML_ELEMENT_NAMES = new Set(require_html_elements$1.default); const SVG_ELEMENT_NAMES = new Set(require_svg_elements$1.default); const MATH_ELEMENT_NAMES = new Set(require_math_elements$1.default); const VOID_ELEMENT_NAMES = new Set(require_void_elements$1.default); const VUE2_BUILTIN_COMPONENT_NAMES = new Set(require_vue2_builtin_components$1.default); const VUE3_BUILTIN_COMPONENT_NAMES = new Set(require_vue3_builtin_components$1.default); const VUE_BUILTIN_ELEMENT_NAMES = new Set(require_vue_builtin_elements$1.default); const { createRequire } = require("node:module"); const path = require("node:path"); const { traverseNodes, getFallbackKeys, NS } = require("vue-eslint-parser").AST; const { findVariable, ReferenceTracker } = require("@eslint-community/eslint-utils"); const { getComponentPropsFromTypeDefine, getComponentEmitsFromTypeDefine, getComponentSlotsFromTypeDefine, isTypeNode } = require_index.default; /** * @type { WeakMap<RuleContext, Token[]> } */ const componentComments = /* @__PURE__ */ new WeakMap(); /** @type { Map<string, RuleModule> | null } */ let coreRuleMap = null; /** @type { Map<string, RuleModule> } */ const stylisticRuleMap = /* @__PURE__ */ new Map(); /** * Get the core rule implementation from the rule name * @param {string} name * @returns {RuleModule | null} */ function getCoreRule(name) { const eslint = require("eslint"); try { return (coreRuleMap || (coreRuleMap = new eslint.Linter().getRules())).get(name) || null; } catch {} const { builtinRules } = require("eslint/use-at-your-own-risk"); return builtinRules.get(name) || null; } /** * Get ESLint Stylistic rule implementation from the rule name * @param {string} name * @param {'@stylistic/eslint-plugin' | '@stylistic/eslint-plugin-ts' | '@stylistic/eslint-plugin-js'} [preferModule] * @returns {RuleModule | null} */ function getStylisticRule(name, preferModule) { if (!preferModule) { const cached = stylisticRuleMap.get(name); if (cached) return cached; } const stylisticPluginNames = [ "@stylistic/eslint-plugin", "@stylistic/eslint-plugin-ts", "@stylistic/eslint-plugin-js" ]; if (preferModule) stylisticPluginNames.unshift(preferModule); for (const stylisticPluginName of stylisticPluginNames) try { const rule = createRequire(`${process.cwd()}/__placeholder__.js`)(stylisticPluginName)?.rules?.[name]; if (!preferModule) stylisticRuleMap.set(name, rule); return rule; } catch {} return null; } /** * @template {object} T * @param {T} target * @param {Partial<T>[]} propsArray * @returns {T} */ function newProxy(target, ...propsArray) { return new Proxy({}, { get(_object, key) { for (const props of propsArray) if (key in props) return props[key]; return target[key]; }, has(_object, key) { return key in target; }, ownKeys(_object) { return Reflect.ownKeys(target); }, getPrototypeOf(_object) { return Reflect.getPrototypeOf(target); } }); } /** * Wrap the rule context object to override methods which access to tokens (such as getTokenAfter). * @param {RuleContext} context The rule context object. * @param {ParserServices.TokenStore} tokenStore The token store object for template. * @param {Object} options The option of this rule. * @param {boolean} [options.applyDocument] If `true`, apply check to document fragment. * @returns {RuleContext} */ function wrapContextToOverrideTokenMethods(context, tokenStore, options) { const eslintSourceCode = context.sourceCode; const rootNode = options.applyDocument ? eslintSourceCode.parserServices.getDocumentFragment && eslintSourceCode.parserServices.getDocumentFragment() : eslintSourceCode.ast.templateBody; /** @type {Token[] | null} */ let tokensAndComments = null; function getTokensAndComments() { if (tokensAndComments) return tokensAndComments; tokensAndComments = rootNode ? tokenStore.getTokens(rootNode, { includeComments: true }) : []; return tokensAndComments; } /** @param {number} index */ function getNodeByRangeIndex(index) { if (!rootNode) return eslintSourceCode.ast; /** @type {ASTNode} */ let result = eslintSourceCode.ast; /** @type {ASTNode[]} */ const skipNodes = []; let breakFlag = false; traverseNodes(rootNode, { enterNode(node, parent) { if (breakFlag) return; if (skipNodes[0] === parent) { skipNodes.unshift(node); return; } if (node.range[0] <= index && index < node.range[1]) result = node; else skipNodes.unshift(node); }, leaveNode(node) { if (breakFlag) return; if (result === node) breakFlag = true; else if (skipNodes[0] === node) skipNodes.shift(); } }); return result; } const sourceCode = newProxy(eslintSourceCode, { get tokensAndComments() { return getTokensAndComments(); }, getNodeByRangeIndex, getDeclaredVariables }, tokenStore); /** @type {WeakMap<ASTNode, import('eslint').Scope.ScopeManager>} */ const containerScopes = /* @__PURE__ */ new WeakMap(); /** * @param {ASTNode} node * @returns {import('eslint').Scope.ScopeManager|null} */ function getContainerScope(node) { const exprContainer = getVExpressionContainer(node); if (!exprContainer) return null; const cache = containerScopes.get(exprContainer); if (cache) return cache; const programNode = eslintSourceCode.ast; const parserOptions = context.languageOptions?.parserOptions ?? context.parserOptions ?? {}; const ecmaFeatures = parserOptions.ecmaFeatures || {}; const ecmaVersion = context.languageOptions?.ecmaVersion ?? parserOptions.ecmaVersion ?? 2020; const sourceType = programNode.sourceType; try { const eslintScope = createRequire(require.resolve("eslint"))("eslint-scope"); const scopeProgram = newProxy(programNode, { body: [newProxy(exprContainer, { type: "ExpressionStatement" })] }); const scope = eslintScope.analyze(scopeProgram, { ignoreEval: true, nodejsScope: false, impliedStrict: ecmaFeatures.impliedStrict, ecmaVersion, sourceType, fallback: getFallbackKeys }); containerScopes.set(exprContainer, scope); return scope; } catch {} return null; } return newProxy(context, { getSourceCode() { return sourceCode; }, get sourceCode() { return sourceCode; }, getDeclaredVariables }); /** * @param {ESNode} node * @returns {Variable[]} */ function getDeclaredVariables(node) { return getContainerScope(node)?.getDeclaredVariables(node) ?? []; } } /** * Wrap the rule context object to override report method to skip the dynamic argument. * @param {RuleContext} context The rule context object. * @returns {RuleContext} */ function wrapContextToOverrideReportMethodToSkipDynamicArgument(context) { const sourceCode = context.sourceCode; const templateBody = sourceCode.ast.templateBody; if (!templateBody) return context; /** @type {Range[]} */ const directiveKeyRanges = []; traverseNodes(templateBody, { enterNode(node, parent) { if (parent && parent.type === "VDirectiveKey" && node.type === "VExpressionContainer") directiveKeyRanges.push(node.range); }, leaveNode() {} }); return newProxy(context, { report(descriptor, ...args) { let range = null; if (descriptor.loc) { const startLoc = descriptor.loc.start || descriptor.loc; const endLoc = descriptor.loc.end || startLoc; range = [sourceCode.getIndexFromLoc(startLoc), sourceCode.getIndexFromLoc(endLoc)]; } else if (descriptor.node) range = descriptor.node.range; if (range) { for (const directiveKeyRange of directiveKeyRanges) if (range[0] < directiveKeyRange[1] && directiveKeyRange[0] < range[1]) return; } context.report(descriptor, ...args); } }); } /** * @callback WrapRuleCreate * @param {RuleContext} ruleContext * @param {WrapRuleCreateContext} wrapContext * @returns {TemplateListener} * * @typedef {object} WrapRuleCreateContext * @property {RuleListener} baseHandlers */ /** * @callback WrapRulePreprocess * @param {RuleContext} ruleContext * @param {WrapRulePreprocessContext} wrapContext * @returns {void} * * @typedef {object} WrapRulePreprocessContext * @property { (override: Partial<RuleContext>) => RuleContext } wrapContextToOverrideProperties Wrap the rule context object to override * @property { (visitor: TemplateListener) => void } defineVisitor Define template body visitor */ /** * @typedef {object} WrapRuleOptions * @property {string[]} [categories] The categories of this rule. * @property {boolean} [skipDynamicArguments] If `true`, skip validation within dynamic arguments. * @property {boolean} [skipDynamicArgumentsReport] If `true`, skip report within dynamic arguments. * @property {boolean} [applyDocument] If `true`, apply check to document fragment. * @property {boolean} [skipBaseHandlers] If `true`, skip base rule handlers. * @property {WrapRulePreprocess} [preprocess] Preprocess to calling create of base rule. * @property {WrapRuleCreate} [create] If define, extend base rule. */ /** * Wrap a given core rule to apply it to Vue.js template. * @param {string} coreRuleName The name of the core rule implementation to wrap. * @param {WrapRuleOptions} [options] The option of this rule. * @returns {RuleModule} The wrapped rule implementation. */ function wrapCoreRule(coreRuleName, options) { const coreRule = getCoreRule(coreRuleName); if (!coreRule) return { meta: { type: "problem", docs: { url: `https://eslint.vuejs.org/rules/${coreRuleName}.html` } }, create(context) { return defineTemplateBodyVisitor(context, { "VElement[name='template'][parent.type='VDocumentFragment']"(node) { context.report({ node, message: `Failed to extend ESLint core rule "${coreRuleName}". You may be able to use this rule by upgrading the version of ESLint. If you cannot upgrade it, turn off this rule.` }); } }); } }; const rule = wrapRuleModule(coreRule, coreRuleName, options); const meta = { ...rule.meta, docs: { ...rule.meta.docs, extensionSource: { url: coreRule.meta.docs.url, name: "ESLint core" } } }; return { ...rule, meta }; } /** * @typedef {object} RuleNames * @property {string} core The name of the core rule implementation to wrap. * @property {string} stylistic The name of ESLint Stylistic rule implementation to wrap. * @property {string} vue The name of the wrapped rule */ /** * Wrap a core rule or ESLint Stylistic rule to apply it to Vue.js template. * @param {RuleNames|string} ruleNames The names of the rule implementation to wrap. * @param {WrapRuleOptions} [options] The option of this rule. * @returns {RuleModule} The wrapped rule implementation. */ function wrapStylisticOrCoreRule(ruleNames, options) { const stylisticRuleName = typeof ruleNames === "string" ? ruleNames : ruleNames.stylistic; const coreRuleName = typeof ruleNames === "string" ? ruleNames : ruleNames.core; const vueRuleName = typeof ruleNames === "string" ? ruleNames : ruleNames.vue; const stylisticRule = getStylisticRule(stylisticRuleName); const baseRule = stylisticRule || getCoreRule(coreRuleName); if (!baseRule) return { meta: { type: "problem", docs: { url: `https://eslint.vuejs.org/rules/${vueRuleName}.html` } }, create(context) { return defineTemplateBodyVisitor(context, { "VElement[name='template'][parent.type='VDocumentFragment']"(node) { context.report({ node, message: `Failed to extend ESLint Stylistic rule "${stylisticRule}". You may be able to use this rule by installing ESLint Stylistic plugin (https://eslint.style/). If you cannot install it, turn off this rule.` }); } }); } }; const rule = wrapRuleModule(baseRule, vueRuleName, options); const jsRule = getStylisticRule(stylisticRuleName, "@stylistic/eslint-plugin-js"); const description = stylisticRule ? `${jsRule?.meta.docs.description} in \`<template>\`` : rule.meta.docs.description; const meta = { ...rule.meta, docs: { ...rule.meta.docs, description, extensionSource: { url: baseRule.meta.docs.url, name: stylisticRule ? "ESLint Stylistic" : "ESLint core" } }, deprecated: void 0, replacedBy: void 0 }; return { ...rule, meta }; } /** * Wrap a given rule to apply it to Vue.js template. * @param {RuleModule} baseRule The rule implementation to wrap. * @param {string} ruleName The name of the wrapped rule. * @param {WrapRuleOptions} [options] The option of this rule. * @returns {RuleModule} The wrapped rule implementation. */ function wrapRuleModule(baseRule, ruleName, options) { let description = baseRule.meta.docs.description; if (description) description += " in `<template>`"; const { categories, skipDynamicArguments, skipDynamicArgumentsReport, skipBaseHandlers, applyDocument, preprocess, create } = options || {}; return { create(context) { const sourceCode = context.sourceCode; const tokenStore = sourceCode.parserServices.getTemplateBodyTokenStore && sourceCode.parserServices.getTemplateBodyTokenStore(); if (tokenStore) context = wrapContextToOverrideTokenMethods(context, tokenStore, { applyDocument }); if (skipDynamicArgumentsReport) context = wrapContextToOverrideReportMethodToSkipDynamicArgument(context); /** @type {TemplateListener} */ const handlers = {}; if (preprocess) preprocess(context, { wrapContextToOverrideProperties(override) { context = newProxy(context, override); return context; }, defineVisitor(visitor) { compositingVisitors(handlers, visitor); } }); const baseHandlers = baseRule.create(context); if (!skipBaseHandlers) compositingVisitors(handlers, baseHandlers); if (handlers.Program) { handlers[applyDocument ? "VDocumentFragment" : "VElement[parent.type!='VElement']"] = handlers.Program; delete handlers.Program; } if (handlers["Program:exit"]) { handlers[applyDocument ? "VDocumentFragment:exit" : "VElement[parent.type!='VElement']:exit"] = handlers["Program:exit"]; delete handlers["Program:exit"]; } if (skipDynamicArguments) { let withinDynamicArguments = false; for (const name of Object.keys(handlers)) { const original = handlers[name]; /** @param {any[]} args */ handlers[name] = (...args) => { if (withinDynamicArguments) return; original(...args); }; } handlers["VDirectiveKey > VExpressionContainer"] = () => { withinDynamicArguments = true; }; handlers["VDirectiveKey > VExpressionContainer:exit"] = () => { withinDynamicArguments = false; }; } if (create) compositingVisitors(handlers, create(context, { baseHandlers })); if (applyDocument) return defineDocumentVisitor(context, handlers); return defineTemplateBodyVisitor(context, handlers); }, meta: Object.assign({}, baseRule.meta, { docs: Object.assign({}, baseRule.meta.docs, { description, category: null, categories, url: `https://eslint.vuejs.org/rules/${ruleName}.html` }) }) }; } module.exports = { defineTemplateBodyVisitor, defineDocumentVisitor, wrapCoreRule, wrapStylisticOrCoreRule, getCoreRule, isDef, flatten, prevSibling(node) { let prevElement = null; for (const siblingNode of node.parent && node.parent.children || []) { if (siblingNode === node) return prevElement; if (siblingNode.type === "VElement") prevElement = siblingNode; } return null; }, isEmptyValueDirective(node, context) { if (node.value == null) return false; if (node.value.expression != null) return false; let valueText = context.sourceCode.getText(node.value); if ((valueText[0] === "\"" || valueText[0] === "'") && valueText[0] === valueText.at(-1)) valueText = valueText.slice(1, -1); if (!valueText) return true; return false; }, isEmptyExpressionValueDirective(node, context) { if (node.value == null) return false; if (node.value.expression != null) return false; const sourceCode = context.sourceCode; const valueNode = node.value; const tokenStore = sourceCode.parserServices.getTemplateBodyTokenStore(); let quote1 = null; let quote2 = null; for (const token of tokenStore.getTokens(node)) { if (token.range[1] <= valueNode.range[0]) continue; if (valueNode.range[1] <= token.range[0]) return true; if (!quote1 && token.type === "Punctuator" && (token.value === "\"" || token.value === "'")) { quote1 = token; continue; } if (!quote2 && quote1 && token.type === "Punctuator" && token.value === quote1.value) { quote2 = token; continue; } return false; } return true; }, getAttribute, hasAttribute, getDirectives, getDirective, hasDirective, isVBindSameNameShorthand, getRegisteredComponents(componentObject) { const componentsNode = componentObject.properties.find( /** * @param {ESNode} p * @returns {p is (Property & { key: Identifier & {name: 'components'}, value: ObjectExpression })} */ (p) => p.type === "Property" && getStaticPropertyName(p) === "components" && p.value.type === "ObjectExpression" ); if (!componentsNode) return []; return componentsNode.value.properties.filter(isProperty).map((node) => { const name = getStaticPropertyName(node); return name ? { node, name } : null; }).filter(isDef); }, prevElementHasIf(node) { const prev = this.prevSibling(node); return prev != null && prev.startTag.attributes.some((a) => a.directive && (a.key.name.name === "if" || a.key.name.name === "else-if")); }, *iterateChildElementsChains(node) { let vIf = false; /** @type {VElement[]} */ let elementChain = []; for (const childNode of node.children) if (childNode.type === "VElement") { let connected; if (hasDirective(childNode, "if")) { connected = false; vIf = true; } else if (hasDirective(childNode, "else-if")) { connected = vIf; vIf = true; } else if (hasDirective(childNode, "else")) { connected = vIf; vIf = false; } else { connected = false; vIf = false; } if (connected) elementChain.push(childNode); else { if (elementChain.length > 0) yield elementChain; elementChain = [childNode]; } } else if (childNode.type !== "VText" || childNode.value.trim() !== "") vIf = false; if (elementChain.length > 0) yield elementChain; }, isStringLiteral(node) { return node.type === "Literal" && typeof node.value === "string" || node.type === "TemplateLiteral" && node.expressions.length === 0; }, isCustomComponent(node, ignoreElementNamespaces = false) { if (hasAttribute(node, "is") || hasDirective(node, "bind", "is") || hasDirective(node, "is")) return true; const isHtmlName = this.isHtmlWellKnownElementName(node.rawName); const isSvgName = this.isSvgWellKnownElementName(node.rawName); const isMathName = this.isMathWellKnownElementName(node.rawName); if (ignoreElementNamespaces) return !isHtmlName && !isSvgName && !isMathName; return this.isHtmlElementNode(node) && !isHtmlName || this.isSvgElementNode(node) && !isSvgName || this.isMathElementNode(node) && !isMathName; }, isHtmlElementNode(node) { return node.namespace === NS.HTML; }, isSvgElementNode(node) { return node.namespace === NS.SVG; }, isMathElementNode(node) { return node.namespace === NS.MathML; }, VUE2_BUILTIN_COMPONENT_NAMES, VUE3_BUILTIN_COMPONENT_NAMES, isHtmlWellKnownElementName(name) { return HTML_ELEMENT_NAMES.has(name); }, isSvgWellKnownElementName(name) { return SVG_ELEMENT_NAMES.has(name); }, isMathWellKnownElementName(name) { return MATH_ELEMENT_NAMES.has(name); }, isHtmlVoidElementName(name) { return VOID_ELEMENT_NAMES.has(name); }, isBuiltInComponentName(name) { return VUE3_BUILTIN_COMPONENT_NAMES.has(name) || VUE2_BUILTIN_COMPONENT_NAMES.has(name); }, isVueBuiltInElementName(name) { return VUE_BUILTIN_ELEMENT_NAMES.has(name.toLowerCase()); }, isBuiltInDirectiveName(name) { return name === "bind" || name === "on" || name === "text" || name === "html" || name === "show" || name === "if" || name === "else" || name === "else-if" || name === "for" || name === "model" || name === "slot" || name === "pre" || name === "cloak" || name === "once" || name === "memo" || name === "is"; }, getStaticPropertyName, getStringLiteralValue, getComponentPropsFromOptions, getComponentEmitsFromOptions, getComputedProperties(componentObject) { const computedPropertiesNode = componentObject.properties.find( /** * @param {ESNode} p * @returns {p is (Property & { key: Identifier & {name: 'computed'}, value: ObjectExpression })} */ (p) => p.type === "Property" && getStaticPropertyName(p) === "computed" && p.value.type === "ObjectExpression" ); if (!computedPropertiesNode) return []; return computedPropertiesNode.value.properties.filter(isProperty).map((cp) => { const key = getStaticPropertyName(cp); /** @type {Expression} */ const propValue = skipTSAsExpression(cp.value); /** @type {BlockStatement | null} */ let value = null; if (propValue.type === "FunctionExpression") value = propValue.body; else if (propValue.type === "ObjectExpression") { const get = findProperty(propValue, "get", (p) => p.value.type === "FunctionExpression"); value = get ? get.value.body : null; } return { key, value }; }); }, getGetterBodyFromComputedFunction(callExpression) { if (callExpression.arguments.length <= 0) return null; const arg = callExpression.arguments[0]; if (arg.type === "FunctionExpression" || arg.type === "ArrowFunctionExpression") return arg; if (arg.type === "ObjectExpression") { const getProperty = findProperty(arg, "get", (p) => p.value.type === "FunctionExpression" || p.value.type === "ArrowFunctionExpression"); return getProperty ? getProperty.value : null; } return null; }, isTypeScriptFile, isVueFile, isScriptSetup, getScriptSetupElement, executeOnVue(context, cb) { return compositingVisitors(this.executeOnVueComponent(context, cb), this.executeOnVueInstance(context, cb)); }, defineVueVisitor(context, visitor) { /** @type {VueObjectData | null} */ let vueStack = null; /** * @param {string} key * @param {ESNode} node */ function callVisitor(key, node) { if (visitor[key] && vueStack) visitor[key](node, vueStack); } /** @type {NodeListener} */ const vueVisitor = {}; for (const key in visitor) vueVisitor[key] = (node) => callVisitor(key, node); /** * @param {ObjectExpression} node */ vueVisitor.ObjectExpression = (node) => { const type = getVueObjectType(context, node); if (type) { vueStack = { node, type, parent: vueStack, get functional() { const functional = node.properties.find( /** * @param {Property | SpreadElement} p * @returns {p is Property} */ (p) => p.type === "Property" && getStaticPropertyName(p) === "functional" ); if (!functional) return false; if (functional.value.type === "Literal" && functional.value.value === false) return false; return true; } }; callVisitor("onVueObjectEnter", node); } callVisitor("ObjectExpression", node); }; vueVisitor["ObjectExpression:exit"] = (node) => { callVisitor("ObjectExpression:exit", node); if (vueStack && vueStack.node === node) { callVisitor("onVueObjectExit", node); vueStack = vueStack.parent; } }; if (visitor.onSetupFunctionEnter || visitor.onSetupFunctionExit || visitor.onRenderFunctionEnter) { const setups = /* @__PURE__ */ new Set(); /** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property } } node */ vueVisitor["Property[value.type=/^(Arrow)?FunctionExpression$/] > :function"] = (node) => { /** @type {Property} */ const prop = node.parent; if (vueStack && prop.parent === vueStack.node && prop.value === node) { const name = getStaticPropertyName(prop); if (name === "setup") { callVisitor("onSetupFunctionEnter", node); setups.add(node); } else if (name === "render") callVisitor("onRenderFunctionEnter", node); } callVisitor("Property[value.type=/^(Arrow)?FunctionExpression$/] > :function", node); }; if (visitor.onSetupFunctionExit) /** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property } } node */ vueVisitor["Property[value.type=/^(Arrow)?FunctionExpression$/] > :function:exit"] = (node) => { if (setups.has(node)) { callVisitor("onSetupFunctionExit", node); setups.delete(node); } }; } return vueVisitor; }, defineScriptSetupVisitor(context, visitor) { const scriptSetup = getScriptSetupElement(context); if (scriptSetup == null) return {}; const scriptSetupRange = scriptSetup.range; /** * @param {ESNode} node */ function inScriptSetup(node) { return scriptSetupRange[0] <= node.range[0] && node.range[1] <= scriptSetupRange[1]; } /** * @param {string} key * @param {ESNode} node * @param {any[]} args */ function callVisitor(key, node, ...args) { if (visitor[key] && (node.type === "Program" || inScriptSetup(node))) visitor[key](node, ...args); } /** @type {NodeListener} */ const scriptSetupVisitor = {}; for (const key in visitor) scriptSetupVisitor[key] = (node) => callVisitor(key, node); class MacroListener { /** * @param {string} name * @param {string} enterName * @param {string} exitName * @param {(candidateMacro: Expression | null, node: CallExpression) => boolean} isMacroNode * @param {(context: RuleContext, node: CallExpression) => unknown} buildParam */ constructor(name, enterName, exitName, isMacroNode, buildParam) { this.name = name; this.enterName = enterName; this.exitName = exitName; this.isMacroNode = isMacroNode; this.buildParam = buildParam; this.hasListener = Boolean(visitor[this.enterName] || visitor[this.exitName]); this.paramsMap = /* @__PURE__ */ new Map(); } } const macroListenerList = [ new MacroListener("defineProps", "onDefinePropsEnter", "onDefinePropsExit", (candidateMacro, node) => candidateMacro === node || candidateMacro === getWithDefaults(node), getComponentPropsFromDefineProps), new MacroListener("defineEmits", "onDefineEmitsEnter", "onDefineEmitsExit", (candidateMacro, node) => candidateMacro === node, getComponentEmitsFromDefineEmits), new MacroListener("defineOptions", "onDefineOptionsEnter", "onDefineOptionsExit", (candidateMacro, node) => candidateMacro === node, () => void 0), new MacroListener("defineSlots", "onDefineSlotsEnter", "onDefineSlotsExit", (candidateMacro, node) => candidateMacro === node, getComponentSlotsFromDefineSlots), new MacroListener("defineExpose", "onDefineExposeEnter", "onDefineExposeExit", (candidateMacro, node) => candidateMacro === node, () => void 0), new MacroListener("defineModel", "onDefineModelEnter", "onDefineModelExit", (candidateMacro, node) => candidateMacro === node, getComponentModelFromDefineModel) ].filter((m) => m.hasListener); if (macroListenerList.length > 0) { /** @type {Expression | null} */ let candidateMacro = null; /** @param {VariableDeclarator|ExpressionStatement} node */ scriptSetupVisitor["Program > VariableDeclaration > VariableDeclarator, Program > ExpressionStatement"] = (node) => { if (!candidateMacro) candidateMacro = node.type === "VariableDeclarator" ? node.init : node.expression; }; /** @param {VariableDeclarator|ExpressionStatement} node */ scriptSetupVisitor["Program > VariableDeclaration > VariableDeclarator, Program > ExpressionStatement:exit"] = (node) => { if (candidateMacro === (node.type === "VariableDeclarator" ? node.init : node.expression)) candidateMacro = null; }; /** * @param {CallExpression} node */ scriptSetupVisitor.CallExpression = (node) => { if (candidateMacro && inScriptSetup(node) && node.callee.type === "Identifier") for (const macroListener of macroListenerList) { if (node.callee.name !== macroListener.name || !macroListener.isMacroNode(candidateMacro, node)) continue; const param = macroListener.buildParam(context, node); callVisitor(macroListener.enterName, node, param); macroListener.paramsMap.set(node, param); break; } callVisitor("CallExpression", node); }; scriptSetupVisitor["CallExpression:exit"] = (node) => { callVisitor("CallExpression:exit", node); for (const macroListener of macroListenerList) if (macroListener.paramsMap.has(node)) { callVisitor(macroListener.exitName, node, macroListener.paramsMap.get(node)); macroListener.paramsMap.delete(node); } }; } return scriptSetupVisitor; }, hasWithDefaults, getWithDefaultsPropExpressions(node) { const map = getWithDefaultsProps(node); /** @type {Record<string, Expression | undefined>} */ const result = {}; for (const key of Object.keys(map)) { const prop = map[key]; result[key] = prop && prop.value; } return result; }, getWithDefaultsProps, getDefaultPropExpressionsForPropsDestructure, getLeftOfDefineProps, isUsingPropsDestructure(node) { return getLeftOfDefineProps(node)?.type === "ObjectPattern"; }, getPropsDestructure, getVueObjectType, getVueComponentDefinitionType, isSFCObject, compositingVisitors, executeOnVueInstance(context, cb) { return { "ObjectExpression:exit"(node) { const type = getVueObjectType(context, node); if (!type || type !== "instance") return; cb(node, type); } }; }, executeOnVueComponent(context, cb) { return { "ObjectExpression:exit"(node) { const type = getVueObjectType(context, node); if (!type || type !== "mark" && type !== "export" && type !== "definition") return; cb(node, type); } }; }, executeOnCallVueComponent(_context, cb) { return { "CallExpression > MemberExpression > Identifier[name='component']": (node) => { const callExpr = node.parent.parent; const callee = callExpr.callee; if (callee.type === "MemberExpression") { if (skipTSAsExpression(callee.object).type === "Identifier" && callee.property === node && callExpr.arguments.length > 0) cb(callExpr); } } }; }, *iterateProperties(node, groups) { for (const item of node.properties) { if (item.type !== "Property") continue; const name = getStaticPropertyName(item); if (!name || !groups.has(name)) continue; switch (item.value.type) { case "ArrayExpression": yield* this.iterateArrayExpression(item.value, name); break; case "ObjectExpression": yield* this.iterateObjectExpression(item.value, name); break; case "FunctionExpression": yield* this.iterateFunctionExpression(item.value, name); break; case "ArrowFunctionExpression": yield* this.iterateArrowFunctionExpression(item.value, name); break; } } }, *iterateArrayExpression(node, groupName) { for (const item of node.elements) if (item && (item.type === "Literal" || item.type === "TemplateLiteral")) { const name = getStringLiteralValue(item); if (name) yield { type: "array", name, groupName, node: item }; } }, *iterateObjectExpression(node, groupName) { /** @type {Set<Property> | undefined} */ let usedGetter; for (const item of node.properties) if (item.type === "Property") { const key = item.key; if (key.type === "Identifier" || key.type === "Literal" || key.type === "TemplateLiteral") { const name = getStaticPropertyName(item); if (name) { if (item.kind === "set" && node.properties.some((item2) => { if (item2.type === "Property" && item2.kind === "get") { if (!usedGetter) usedGetter = /* @__PURE__ */ new Set(); if (usedGetter.has(item2)) return false; if (getStaticPropertyName(item2) === name) { usedGetter.add(item2); return true; } } return false; })) continue; yield { type: "object", name, groupName, node: key, property: item }; } } } }, *iterateFunctionExpression(node, groupName) { if (node.body.type === "BlockStatement") { for (const item of node.body.body) if (item.type === "ReturnStatement" && item.argument && item.argument.type === "ObjectExpression") yield* this.iterateObjectExpression(item.argument, groupName); } }, *iterateArrowFunctionExpression(node, groupName) { const body = node.body; if (body.type === "BlockStatement") { for (const item of body.body) if (item.type === "ReturnStatement" && item.argument && item.argument.type === "ObjectExpression") yield* this.iterateObjectExpression(item.argument, groupName); } else if (body.type === "ObjectExpression") yield* this.iterateObjectExpression(body, groupName); }, executeOnFunctionsWithoutReturn(treatUndefinedAsUnspecified, cb) { /** * @typedef {object} FuncInfo * @property {FuncInfo | null} funcInfo * @property {CodePath} codePath * @property {boolean} hasReturn * @property {boolean} hasReturnValue * @property {ArrowFunctionExpression | FunctionExpression | FunctionDeclaration} node * @property {CodePathSegment[]} currentSegments */ /** @type {FuncInfo | null} */ let funcInfo = null; function isValidReturn() { if (!funcInfo) return true; if (funcInfo.currentSegments.some((segment) => segment.reachable)) return false; return !treatUndefinedAsUnspecified || funcInfo.hasReturnValue; } return { onCodePathStart(codePath, node) { if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration") funcInfo = { codePath, currentSegments: [], funcInfo, hasReturn: false, hasReturnValue: false, node }; }, onCodePathSegmentStart(segment) { funcInfo?.currentSegments.push(segment); }, onCodePathSegmentEnd() { funcInfo?.currentSegments.pop(); }, onCodePathEnd() { funcInfo = funcInfo && funcInfo.funcInfo; }, ReturnStatement(node) { if (funcInfo) { funcInfo.hasReturn = true; funcInfo.hasReturnValue = Boolean(node.argument); } }, "ArrowFunctionExpression:exit"(node) { if (funcInfo && !isValidReturn() && !node.expression) cb(funcInfo.node); }, "FunctionExpression:exit"() { if (funcInfo && !isValidReturn()) cb(funcInfo.node); } }; }, isSingleLine(node) { return node.loc.start.line === node.loc.end.line; }, hasInvalidEOF(node) { const body = node.templateBody; if (body == null || body.errors == null) return false; return body.errors.some((error) => typeof error.code === "string" && error.code.startsWith("eof-")); }, getMemberChaining(node) { /** @type {MemberExpression[]} */ const nodes = []; let n = skipChainExpression(node); while (n.type === "MemberExpression") { nodes.push(n); n = skipChainExpression(n.object); } return [n, ...nodes.reverse()]; }, editDistance(a, b) { if (a === b) return 0; const alen = a.length; const blen = b.length; const dp = Array.from({ length: alen + 1 }).map((_) => Array.from({ length: blen + 1 }).fill(0)); for (let i = 0; i <= alen; i++) dp[i][0] = i; for (let j = 0; j <= blen; j++) dp[0][j] = j; for (let i = 1; i <= alen; i++) for (let j = 1; j <= blen; j++) dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1; return dp[alen][blen]; }, inRange(rangeOrNode, target) { const range = Array.isArray(rangeOrNode) ? rangeOrNode : rangeOrNode.range; return range[0] <= target.range[0] && target.range[1] <= range[1]; }, isProperty, isAssignmentProperty, isVElement, findProperty, findAssignmentProperty, isPropertyChain, skipTSAsExpression, skipDefaultParamValue, skipChainExpression, withinTypeNode, findVariableByIdentifier, getScope, isInExportDefault, isThis(node, context) { if (node.type === "ThisExpression") return true; if (node.type !== "Identifier") return false; const parent = node.parent; if (parent.type === "MemberExpression" && parent.property === node || parent.type === "Property" && parent.key === node && !parent.computed) return false; const variable = findVariable(getScope(context, node), node); if (variable != null && variable.defs.length === 1) { const def = variable.defs[0]; if (def.type === "Variable" && def.parent.kind === "const" && def.node.id.type === "Identifier") return Boolean(def.node && def.node.init && def.node.init.type === "ThisExpression"); } return false; }, isObjectAssignCall(callExpr, targetNode) { const { callee, arguments: args } = callExpr; return args.length > 0 && args[0] === targetNode && callee?.type === "MemberExpression" && callee.object?.type === "Identifier" && callee.object.name === "Object" && callee.property?.type === "Identifier" && callee.property.name === "assign"; }, findMutating(props) { /** @type {MemberExpression[]} */ const pathNodes = []; /** @type {MemberExpression | Identifier | ChainExpression} */ let node = props; let target = node.parent; while (true) { switch (target.type) { case "AssignmentExpression": if (target.left === node) return { kind: "assignment", node: target, pathNodes }; break; case "UpdateExpression": return { kind: "update", node: target, pathNodes }; case "UnaryExpression": if (target.operator === "delete") return { kind: "update", node: target, pathNodes }; break; case "CallExpression": { const mem = pathNodes.at(-1); if (mem && target.callee === node) { const callName = getStaticPropertyName(mem); if (callName && /^(?:push|pop|shift|unshift|reverse|splice|sort|copyWithin|fill)$/u.test(callName)) { pathNodes.pop(); return { kind: "call", node: target, pathNodes }; } } if (this.isObjectAssignCall(target, node)) return { kind: "call", node: target, pathNodes }; break; } case "MemberExpression": if (target.object === node) { pathNodes.push(target); node = target; target = target.parent; continue; } break; case "ChainExpression": node = target; target = target.parent; continue; } return null; } }, iterateWatchHandlerValues, createCompositionApiTraceMap: (map) => ({ vue: map, "@vue/composition-api": map, "#imports": map }), *iterateReferencesTraceMap(tracker, map) { const esmTraceMap = this.createCompositionApiTraceMap({ ...map, [ReferenceTracker.ESM]: true }); for (const ref of tracker.iterateEsmReferences(esmTraceMap)) yield ref; for (const ref of tracker.iterateGlobalReferences(map)) yield ref; }, equalTokens(left, right, sourceCode) { const tokensL = sourceCode.getTokens(left); const tokensR = sourceCode.getTokens(right); if (tokensL.length !== tokensR.length) return false; return tokensL.every((token, i) => token.type === tokensR[i].type && token.value === tokensR[i].value); } }; /** * Checks whether the given value is defined. * @template T * @param {T | null | undefined} v * @returns {v is T} */ function isDef(v) { return v != null; } /** * Flattens arrays, objects and iterable objects. * @template T * @param {T | Iterable<T> | null | undefined} v * @returns {T[]} */ function flatten(v) { /** @type {T[]} */ const result = []; if (v) if (isIterable(v)) result.push(...v); else result.push(v); return result; } /** * @param {*} v * @returns {v is Iterable<any>} */ function isIterable(v) { return v && Symbol.iterator in v; } /** * Register the given visitor to parser services. * If the parser service of `vue-eslint-parser` was not found, * this generates a warning. * * @param {RuleContext} context The rule context to use parser services. * @param {TemplateListener} templateBodyVisitor The visitor to traverse the template body. * @param {RuleListener} [scriptVisitor] The visitor to traverse the script. * @param { { templateBodyTriggerSelector: "Program" | "Program:exit" } } [options] The options. * @returns {RuleListener} The merged visitor. */ function defineTemplateBodyVisitor(context, templateBodyVisitor, scriptVisitor, options) { const sourceCode = context.sourceCode; if (sourceCode.parserServices.defineTemplateBodyVisitor == null) { const filename = context.filename; if (path.extname(filename) === ".vue") context.report({ loc: { line: 1, column: 0 }, message: "Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error." }); return {}; } return sourceCode.parserServices.defineTemplateBodyVisitor(templateBodyVisitor, scriptVisitor, options); } /** * Register the given visitor to parser services. * If the parser service of `vue-eslint-parser` was not found, * this generates a warning. * * @param {RuleContext} context The rule context to use parser services. * @param {TemplateListener} documentVisitor The visitor to traverse the document. * @param { { triggerSelector: "Program" | "Program:exit" } } [options] The options. * @returns {RuleListener} The merged visitor. */ function defineDocumentVisitor(context, documentVisitor, options) { const sourceCode = context.sourceCode; if (sourceCode.parserServices.defineDocumentVisitor == null) { const filename = context.filename; if (path.extname(filename) === ".vue") context.report({ loc: { line: 1, column: 0 }, message: "Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error." }); return {}; } return sourceCode.parserServices.defineDocumentVisitor(documentVisitor, options); } /** * @template T * @param {T} visitor * @param {...(TemplateListener | RuleListener | NodeListener)} visitors * @returns {T} */ function compositingVisitors(visitor, ...visitors) { for (const v of visitors) for (const key in v) if (visitor[key]) { const o = visitor[key]; visitor[key] = (...args) => { o(...args); v[key](...args); }; } else visitor[key] = v[key]; return visitor; } /** * Find the variable of a given identifier. * @param {RuleContext} context The rule context * @param {Identifier} node The variable name to find. * @returns {Variable|null} The found variable or null. */ function findVariableByIdentifier(context, node) { return findVariable(getScope(context, node), node); } /** * Finds the property with the given name from the given ObjectExpression node. * @param {ObjectExpression} node * @param {string} name * @param { (p: Property) => boolean } [filter] * @returns { (Property) | null} */ function findProperty(node, name, filter) { const predicate = filter ? (prop) => isProperty(prop) && getStaticPropertyName(prop) === name && filter(prop) : (prop) => isProperty(prop) && getStaticPropertyName(prop) === name; return node.properties.find(predicate) || null; } /** * Finds the assignment property with the given name from the given ObjectPattern node. * @param {ObjectPattern} node * @param {string} name * @param { (p: AssignmentProperty) => boolean } [filter] * @returns { (AssignmentProperty) | null} */ function findAssignmentProperty(node, name, filter) { const predicate = filter ? (prop) => isAssignmentProperty(prop) && getStaticPropertyName(prop) === name && filter(prop) : (prop) => isAssignmentProperty(prop) && getStaticPropertyName(prop) === name; return node.properties.find(predicate) || null; } /** * Checks whether the given node is Property. * @param {Property | SpreadElement | AssignmentProperty} node * @returns {node is Property} */ function isProperty(node) { if (node.type !== "Property") return false; return !node.parent || node.parent.type === "ObjectExpression"; } /** * Checks whether the given node is AssignmentProperty. * @param {AssignmentProperty | RestElement} node * @returns {node is AssignmentProperty} */ function isAssignmentProperty(node) { return node.type === "Property"; } /** * Checks whether the given node is VElement. * @param {VElement | VExpressionContainer | VText | VDocumentFragment} node * @returns {node is VElement} */ function isVElement(node) { return node.type === "VElement"; } /** * Checks whether the given node is in export default. * @param {ASTNode} node * @returns {boolean} */ function isInExportDefault(node) { /** @type {ASTNode | null} */ let parent = node.parent; while (parent) { if (parent.type === "ExportDefaultDeclaration") return true; parent = parent.parent; } return false; } /** * Retrieve `TSAsExpression#expression` value if the given node a `TSAsExpression`