UNPKG

eslint-plugin-vue

Version:

Official ESLint plugin for Vue.js

308 lines (306 loc) 12.6 kB
const require_runtime = require('../_virtual/_rolldown/runtime.js'); const require_index = require('../utils/index.js'); const require_casing = require('../utils/casing.js'); let _eslint_community_eslint_utils = require("@eslint-community/eslint-utils"); //#region lib/rules/require-explicit-emits.ts var import_utils = /* @__PURE__ */ require_runtime.__toESM(require_index.default); const FIX_EMITS_AFTER_OPTIONS = new Set([ "setup", "data", "computed", "watch", "methods", "template", "render", "renderError", "beforeCreate", "created", "beforeMount", "mounted", "beforeUpdate", "updated", "activated", "deactivated", "beforeUnmount", "unmounted", "beforeDestroy", "destroyed", "renderTracked", "renderTriggered", "errorCaptured" ]); /** * Get the name param node from the given CallExpression */ function getNameParamNode(node) { const nameLiteralNode = node.arguments[0]; if (nameLiteralNode && import_utils.default.isStringLiteral(nameLiteralNode)) { const name = import_utils.default.getStringLiteralValue(nameLiteralNode); if (name != null) return { name, loc: nameLiteralNode.loc, range: nameLiteralNode.range }; } return null; } var require_explicit_emits_default = { meta: { type: "suggestion", docs: { description: "require `emits` option with name triggered by `$emit()`", categories: ["vue3-strongly-recommended"], url: "https://eslint.vuejs.org/rules/require-explicit-emits.html" }, fixable: null, hasSuggestions: true, schema: [{ type: "object", properties: { allowProps: { type: "boolean" } }, additionalProperties: false }], messages: { missing: "The \"{{name}}\" event has been triggered but not declared on {{emitsKind}}.", addOneOption: "Add the \"{{name}}\" to {{emitsKind}}.", addArrayEmitsOption: "Add the {{emitsKind}} with array syntax and define \"{{name}}\" event.", addObjectEmitsOption: "Add the {{emitsKind}} with object syntax and define \"{{name}}\" event." } }, create(context) { const allowProps = !!(context.options[0] || {}).allowProps; const setupContexts = /* @__PURE__ */ new Map(); const vueEmitsDeclarations = /* @__PURE__ */ new Map(); const vuePropsDeclarations = /* @__PURE__ */ new Map(); let emitParamName = ""; let vueTemplateDefineData = null; function verifyEmit(emits, props, nameWithLoc, vueDefineNode) { const name = nameWithLoc.name; if (emits.some((e) => e.emitName === name || e.emitName == null)) return; if (allowProps) { const key = `on${require_casing.capitalize(name)}`; if (props.some((e) => e.propName === key || e.propName == null)) return; } context.report({ loc: nameWithLoc.loc, messageId: "missing", data: { name, emitsKind: vueDefineNode.type === "ObjectExpression" ? "`emits` option" : "`defineEmits`" }, suggest: buildSuggest(vueDefineNode, emits, nameWithLoc, context) }); } const programNode = context.sourceCode.ast; if (import_utils.default.isScriptSetup(context)) vueTemplateDefineData = { type: "setup", define: programNode, emits: [], props: [] }; const callVisitor = { CallExpression(node, info) { const callee = import_utils.default.skipChainExpression(node.callee); const nameWithLoc = getNameParamNode(node); if (!nameWithLoc) return; const vueDefineNode = info ? info.node : programNode; const emitsDeclarations = vueEmitsDeclarations.get(vueDefineNode); if (!emitsDeclarations) return; let emit; if (callee.type === "MemberExpression") { const name = import_utils.default.getStaticPropertyName(callee); if (name === "emit" || name === "$emit") emit = { name, member: callee }; } const setupContext = setupContexts.get(vueDefineNode); if (setupContext) { const { contextReferenceIds, emitReferenceIds } = setupContext; if (callee.type === "Identifier" && emitReferenceIds.has(callee)) verifyEmit(emitsDeclarations, vuePropsDeclarations.get(vueDefineNode) || [], nameWithLoc, vueDefineNode); else if (emit && emit.name === "emit") { const memObject = import_utils.default.skipChainExpression(emit.member.object); if (memObject.type === "Identifier" && contextReferenceIds.has(memObject)) verifyEmit(emitsDeclarations, vuePropsDeclarations.get(vueDefineNode) || [], nameWithLoc, vueDefineNode); } } if (emit && emit.name === "$emit") { const memObject = import_utils.default.skipChainExpression(emit.member.object); if (import_utils.default.isThis(memObject, context)) verifyEmit(emitsDeclarations, vuePropsDeclarations.get(vueDefineNode) || [], nameWithLoc, vueDefineNode); } } }; return import_utils.default.defineTemplateBodyVisitor(context, { CallExpression(node) { const callee = import_utils.default.skipChainExpression(node.callee); const nameWithLoc = getNameParamNode(node); if (!nameWithLoc) return; if (!vueTemplateDefineData) return; if (callee.type === "Identifier" && (callee.name === "$emit" || callee.name === emitParamName)) verifyEmit(vueTemplateDefineData.emits, vueTemplateDefineData.props, nameWithLoc, vueTemplateDefineData.define); } }, import_utils.default.compositingVisitors(import_utils.default.defineScriptSetupVisitor(context, { onDefineEmitsEnter(node, emits) { vueEmitsDeclarations.set(programNode, emits); if (vueTemplateDefineData && vueTemplateDefineData.type === "setup") { vueTemplateDefineData.emits = emits; vueTemplateDefineData.defineEmits = node; } if (!node.parent || node.parent.type !== "VariableDeclarator" || node.parent.init !== node) return; const emitParam = node.parent.id; if (emitParam.type !== "Identifier") return; emitParamName = emitParam.name; const variable = (0, _eslint_community_eslint_utils.findVariable)(import_utils.default.getScope(context, emitParam), emitParam); if (!variable) return; const emitReferenceIds = /* @__PURE__ */ new Set(); for (const reference of variable.references) { if (!reference.isRead()) continue; emitReferenceIds.add(reference.identifier); } setupContexts.set(programNode, { contextReferenceIds: /* @__PURE__ */ new Set(), emitReferenceIds }); }, onDefinePropsEnter(_node, props) { if (allowProps) { vuePropsDeclarations.set(programNode, props); if (vueTemplateDefineData && vueTemplateDefineData.type === "setup") vueTemplateDefineData.props = props; } }, ...callVisitor }), import_utils.default.defineVueVisitor(context, { onVueObjectEnter(node) { vueEmitsDeclarations.set(node, import_utils.default.getComponentEmitsFromOptions(node)); if (allowProps) vuePropsDeclarations.set(node, import_utils.default.getComponentPropsFromOptions(node)); }, onSetupFunctionEnter(node, { node: vueNode }) { const contextParam = node.params[1]; if (!contextParam) return; if (contextParam.type === "RestElement") return; if (contextParam.type === "ArrayPattern") return; const contextReferenceIds = /* @__PURE__ */ new Set(); const emitReferenceIds = /* @__PURE__ */ new Set(); if (contextParam.type === "ObjectPattern") { const emitProperty = import_utils.default.findAssignmentProperty(contextParam, "emit"); if (!emitProperty) return; const emitParam = emitProperty.value; const variable = emitParam.type === "Identifier" ? (0, _eslint_community_eslint_utils.findVariable)(import_utils.default.getScope(context, emitParam), emitParam) : null; if (!variable) return; for (const reference of variable.references) { if (!reference.isRead()) continue; emitReferenceIds.add(reference.identifier); } } else if (contextParam.type === "Identifier") { const variable = (0, _eslint_community_eslint_utils.findVariable)(import_utils.default.getScope(context, contextParam), contextParam); if (!variable) return; for (const reference of variable.references) { if (!reference.isRead()) continue; contextReferenceIds.add(reference.identifier); } } setupContexts.set(vueNode, { contextReferenceIds, emitReferenceIds }); }, ...callVisitor, onVueObjectExit(node, { type }) { const emits = vueEmitsDeclarations.get(node); if ((!vueTemplateDefineData || vueTemplateDefineData.type !== "export" && vueTemplateDefineData.type !== "setup") && emits && (type === "mark" || type === "export" || type === "definition")) vueTemplateDefineData = { type, define: node, emits, props: vuePropsDeclarations.get(node) || [] }; setupContexts.delete(node); vueEmitsDeclarations.delete(node); vuePropsDeclarations.delete(node); } }))); } }; function buildSuggest(define, emits, nameWithLoc, context) { const emitsKind = define.type === "ObjectExpression" ? "`emits` option" : "`defineEmits`"; const lastEmit = emits.filter((e) => e.type === "array" || e.type === "object").at(-1); if (lastEmit) return [{ messageId: "addOneOption", data: { name: nameWithLoc.name, emitsKind }, fix(fixer) { if (lastEmit.type === "array") return fixer.insertTextAfter(lastEmit.node, `, '${nameWithLoc.name}'`); else if (lastEmit.type === "object") return fixer.insertTextAfter(lastEmit.node, `, '${nameWithLoc.name}': null`); else return null; } }]; if (define.type !== "ObjectExpression") return []; const object = define; const propertyNodes = object.properties.filter(import_utils.default.isProperty); const emitsOption = propertyNodes.find((p) => import_utils.default.getStaticPropertyName(p) === "emits"); if (emitsOption) { const sourceCode = context.sourceCode; const emitsOptionValue = emitsOption.value; if (emitsOptionValue.type === "ArrayExpression") { const leftBracket = sourceCode.getFirstToken(emitsOptionValue, _eslint_community_eslint_utils.isOpeningBracketToken); return [{ messageId: "addOneOption", data: { name: `${nameWithLoc.name}`, emitsKind }, fix(fixer) { return fixer.insertTextAfter(leftBracket, `'${nameWithLoc.name}'${emitsOptionValue.elements.length > 0 ? "," : ""}`); } }]; } else if (emitsOptionValue.type === "ObjectExpression") { const leftBrace = sourceCode.getFirstToken(emitsOptionValue, _eslint_community_eslint_utils.isOpeningBraceToken); return [{ messageId: "addOneOption", data: { name: `${nameWithLoc.name}`, emitsKind }, fix(fixer) { return fixer.insertTextAfter(leftBrace, `'${nameWithLoc.name}': null${emitsOptionValue.properties.length > 0 ? "," : ""}`); } }]; } return []; } const sourceCode = context.sourceCode; const afterOptionNode = propertyNodes.find((p) => FIX_EMITS_AFTER_OPTIONS.has(import_utils.default.getStaticPropertyName(p) || "")); return [{ messageId: "addArrayEmitsOption", data: { name: `${nameWithLoc.name}`, emitsKind }, fix(fixer) { if (afterOptionNode) return fixer.insertTextAfter(sourceCode.getTokenBefore(afterOptionNode), `\nemits: ['${nameWithLoc.name}'],`); const lastPropertyNode = object.properties.at(-1); if (lastPropertyNode) { const before = propertyNodes.at(-1) || lastPropertyNode; return fixer.insertTextAfter(before, `,\nemits: ['${nameWithLoc.name}']`); } else { const objectLeftBrace = sourceCode.getFirstToken(object, _eslint_community_eslint_utils.isOpeningBraceToken); const objectRightBrace = sourceCode.getLastToken(object, _eslint_community_eslint_utils.isClosingBraceToken); return fixer.insertTextAfter(objectLeftBrace, `\nemits: ['${nameWithLoc.name}']${objectLeftBrace.loc.end.line < objectRightBrace.loc.start.line ? "" : "\n"}`); } } }, { messageId: "addObjectEmitsOption", data: { name: `${nameWithLoc.name}`, emitsKind }, fix(fixer) { if (afterOptionNode) return fixer.insertTextAfter(sourceCode.getTokenBefore(afterOptionNode), `\nemits: {'${nameWithLoc.name}': null},`); const lastPropertyNode = object.properties.at(-1); if (lastPropertyNode) { const before = propertyNodes.at(-1) || lastPropertyNode; return fixer.insertTextAfter(before, `,\nemits: {'${nameWithLoc.name}': null}`); } else { const objectLeftBrace = sourceCode.getFirstToken(object, _eslint_community_eslint_utils.isOpeningBraceToken); const objectRightBrace = sourceCode.getLastToken(object, _eslint_community_eslint_utils.isClosingBraceToken); return fixer.insertTextAfter(objectLeftBrace, `\nemits: {'${nameWithLoc.name}': null}${objectLeftBrace.loc.end.line < objectRightBrace.loc.start.line ? "" : "\n"}`); } } }]; } //#endregion exports.default = require_explicit_emits_default;