UNPKG

eslint-plugin-vue

Version:

Official ESLint plugin for Vue.js

349 lines (346 loc) 14 kB
'use strict'; const require_rolldown_runtime = require('../_virtual/rolldown_runtime.js'); const require_index = require('../utils/index.js'); const require_regexp$1 = require('../utils/regexp.js'); const require_index$1 = require('../utils/style-variables/index.js'); const require_property_references$1 = require('../utils/property-references.js'); const require_vue_reserved$1 = require('../utils/vue-reserved.js'); //#region lib/rules/no-undef-properties.js /** * @fileoverview Disallow undefined properties. * @author Yosuke Ota */ var require_no_undef_properties = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => { const utils = require_index.default; const reserved = require_vue_reserved$1.default; const { toRegExpGroupMatcher } = require_regexp$1.default; const { getStyleVariablesContext } = require_index$1.default; const { definePropertyReferenceExtractor } = require_property_references$1.default; /** * @typedef {import('../utils').VueObjectData} VueObjectData * @typedef {import('../utils/property-references').IPropertyReferences} IPropertyReferences */ /** * @typedef {object} PropertyData * @property {boolean} [hasNestProperty] * @property { (name: string) => PropertyData | null } [get] * @property {boolean} [isProps] */ const GROUP_PROPERTY = "props"; const GROUP_ASYNC_DATA = "asyncData"; const GROUP_DATA = "data"; const GROUP_COMPUTED_PROPERTY = "computed"; const GROUP_METHODS = "methods"; const GROUP_SETUP = "setup"; const GROUP_WATCHER = "watch"; const GROUP_EXPOSE = "expose"; const GROUP_INJECT = "inject"; /** * @param {ObjectExpression} object * @returns {Map<string, Property> | null} */ function getObjectPropertyMap(object) { /** @type {Map<string, Property>} */ const props = /* @__PURE__ */ new Map(); for (const p of object.properties) { if (p.type !== "Property") return null; const name = utils.getStaticPropertyName(p); if (name == null) return null; props.set(name, p); } return props; } /** * @param {Property | undefined} property * @returns {PropertyData | null} */ function getPropertyDataFromObjectProperty(property) { if (property == null) return null; const propertyMap = property.value.type === "ObjectExpression" ? getObjectPropertyMap(property.value) : null; return { hasNestProperty: Boolean(propertyMap), get(name) { if (!propertyMap) return null; return getPropertyDataFromObjectProperty(propertyMap.get(name)); } }; } module.exports = { meta: { type: "suggestion", docs: { description: "disallow undefined properties", categories: void 0, url: "https://eslint.vuejs.org/rules/no-undef-properties.html" }, fixable: null, schema: [{ type: "object", properties: { ignores: { type: "array", items: { type: "string" }, uniqueItems: true } }, additionalProperties: false }], messages: { undef: "'{{name}}' is not defined.", undefProps: "'{{name}}' is not defined in props." } }, create(context) { const { ignores = [String.raw`/^\$/`] } = context.options[0] || {}; const isIgnored = toRegExpGroupMatcher(ignores); const propertyReferenceExtractor = definePropertyReferenceExtractor(context); const programNode = context.sourceCode.ast; /** * Property names identified as defined via a Vuex or Pinia helpers * @type {Set<string>} */ const propertiesDefinedByStoreHelpers = /* @__PURE__ */ new Set(); /** * @param {ASTNode} node */ function isScriptSetupProgram(node) { return node === programNode; } /** Vue component context */ class VueComponentContext { constructor() { /** @type { Map<string, PropertyData> } */ this.defineProperties = /* @__PURE__ */ new Map(); /** @type { Set<string | ASTNode> } */ this.reported = /* @__PURE__ */ new Set(); this.hasUnknownProperty = false; } /** * Report * @param {IPropertyReferences} references * @param {object} [options] * @param {boolean} [options.props] */ verifyReferences(references, options) { if (this.hasUnknownProperty) return; const report = this.report.bind(this); verifyUndefProperties(this.defineProperties, references, null); /** * @param { { get?: (name: string) => PropertyData | null | undefined } } defineProperties * @param {IPropertyReferences|null} references * @param {string|null} pathName */ function verifyUndefProperties(defineProperties, references$1, pathName) { if (!references$1) return; for (const [refName, { nodes }] of references$1.allProperties()) { const referencePathName = pathName ? `${pathName}.${refName}` : refName; const prop = defineProperties.get && defineProperties.get(refName); if (prop) { if (options && options.props && !prop.isProps) { report(nodes[0], referencePathName, "undefProps"); continue; } } else { report(nodes[0], referencePathName, "undef"); continue; } if (prop.hasNestProperty) verifyUndefProperties(prop, references$1.getNest(refName), referencePathName); } } } /** * Report * @param {ASTNode} node * @param {string} name * @param {'undef' | 'undefProps'} messageId */ report(node, name, messageId = "undef") { if (reserved.includes(name) || isIgnored(name) || propertiesDefinedByStoreHelpers.has(name)) return; if (this.reported.has(node) || this.reported.has(name)) return; this.reported.add(node); this.reported.add(name); context.report({ node, messageId, data: { name } }); } markAsHasUnknownProperty() { this.hasUnknownProperty = true; } } /** @type {Map<ASTNode, VueComponentContext>} */ const vueComponentContextMap = /* @__PURE__ */ new Map(); /** * @param {ASTNode} node * @returns {VueComponentContext} */ function getVueComponentContext(node) { let ctx = vueComponentContextMap.get(node); if (!ctx) { ctx = new VueComponentContext(); vueComponentContextMap.set(node, ctx); } return ctx; } /** * @returns {VueComponentContext|void} */ function getVueComponentContextForTemplate() { const keys = [...vueComponentContextMap.keys()]; const exported = keys.find(isScriptSetupProgram) || keys.find(utils.isInExportDefault); return exported && vueComponentContextMap.get(exported); } /** * @param {Expression} node * @returns {Property|null} */ function getParentProperty(node) { if (!node.parent || node.parent.type !== "Property" || node.parent.value !== node) return null; const property = node.parent; if (!utils.isProperty(property)) return null; return property; } const scriptVisitor = utils.compositingVisitors({ Program() { if (!utils.isScriptSetup(context)) return; const ctx = getVueComponentContext(programNode); const globalScope = context.sourceCode.scopeManager.globalScope; if (globalScope) { for (const variable of globalScope.variables) ctx.defineProperties.set(variable.name, {}); const moduleScope = globalScope.childScopes.find((scope) => scope.type === "module"); for (const variable of moduleScope && moduleScope.variables || []) ctx.defineProperties.set(variable.name, {}); } } }, utils.defineScriptSetupVisitor(context, { onDefinePropsEnter(node, props) { const ctx = getVueComponentContext(programNode); for (const prop of props) { if (prop.type === "unknown") { ctx.markAsHasUnknownProperty(); return; } if (!prop.propName) continue; ctx.defineProperties.set(prop.propName, { isProps: true }); } let target = node; if (target.parent && target.parent.type === "CallExpression" && target.parent.arguments[0] === target && target.parent.callee.type === "Identifier" && target.parent.callee.name === "withDefaults") target = target.parent; if (!target.parent || target.parent.type !== "VariableDeclarator" || target.parent.init !== target) return; const pattern = target.parent.id; const propertyReferences = propertyReferenceExtractor.extractFromPattern(pattern); ctx.verifyReferences(propertyReferences); }, onDefineModelEnter(_node, model) { getVueComponentContext(programNode).defineProperties.set(model.name.modelName, { isProps: true }); } }), utils.defineVueVisitor(context, { CallExpression(node) { if (node.callee.type !== "Identifier") return; /** @type {'methods'|'computed'|null} */ let groupName = null; if (/^mapMutations|mapActions$/u.test(node.callee.name)) groupName = GROUP_METHODS; else if (/^mapState|mapGetters|mapWritableState$/u.test(node.callee.name)) groupName = GROUP_COMPUTED_PROPERTY; if (!groupName || node.arguments.length === 0) return; const arg = node.arguments.length === 2 ? node.arguments[1] : node.arguments[0]; if (arg.type === "ObjectExpression") for (const prop of arg.properties) { const name = prop.type === "SpreadElement" ? null : utils.getStaticPropertyName(prop); if (name) propertiesDefinedByStoreHelpers.add(name); } else if (arg.type === "ArrayExpression") for (const element of arg.elements) { if (!element || !utils.isStringLiteral(element)) continue; const name = utils.getStringLiteralValue(element); if (name) propertiesDefinedByStoreHelpers.add(name); } }, onVueObjectEnter(node) { const ctx = getVueComponentContext(node); for (const prop of utils.iterateProperties(node, new Set([ GROUP_PROPERTY, GROUP_ASYNC_DATA, GROUP_DATA, GROUP_COMPUTED_PROPERTY, GROUP_SETUP, GROUP_METHODS, GROUP_INJECT ]))) { const propertyMap = (prop.groupName === GROUP_DATA || prop.groupName === GROUP_ASYNC_DATA) && prop.type === "object" && prop.property.value.type === "ObjectExpression" ? getObjectPropertyMap(prop.property.value) : null; ctx.defineProperties.set(prop.name, { hasNestProperty: Boolean(propertyMap), isProps: prop.groupName === GROUP_PROPERTY, get(name) { if (!propertyMap) return null; return getPropertyDataFromObjectProperty(propertyMap.get(name)); } }); } for (const watcherOrExpose of utils.iterateProperties(node, new Set([GROUP_WATCHER, GROUP_EXPOSE]))) if (watcherOrExpose.groupName === GROUP_WATCHER) { const watcher = watcherOrExpose; ctx.verifyReferences(propertyReferenceExtractor.extractFromPath(watcher.name, watcher.node)); if (watcher.type === "object") { const property = watcher.property; if (property.kind === "init") for (const handlerValueNode of utils.iterateWatchHandlerValues(property)) ctx.verifyReferences(propertyReferenceExtractor.extractFromNameLiteral(handlerValueNode)); } } else if (watcherOrExpose.groupName === GROUP_EXPOSE) { const expose = watcherOrExpose; ctx.verifyReferences(propertyReferenceExtractor.extractFromName(expose.name, expose.node)); } }, "ObjectExpression > Property > :function[params.length>0]"(node, vueData) { let props = false; const property = getParentProperty(node); if (!property) return; if (property.parent === vueData.node) { if (utils.getStaticPropertyName(property) !== "data") return; props = true; } else { const parentProperty = getParentProperty(property.parent); if (!parentProperty) return; if (parentProperty.parent === vueData.node) { if (utils.getStaticPropertyName(parentProperty) !== "computed") return; } else { const parentParentProperty = getParentProperty(parentProperty.parent); if (!parentParentProperty) return; if (parentParentProperty.parent === vueData.node) { if (utils.getStaticPropertyName(parentParentProperty) !== "computed" || utils.getStaticPropertyName(property) !== "get") return; } else return; } } const propertyReferences = propertyReferenceExtractor.extractFromFunctionParam(node, 0); getVueComponentContext(vueData.node).verifyReferences(propertyReferences, { props }); }, onSetupFunctionEnter(node, vueData) { const propertyReferences = propertyReferenceExtractor.extractFromFunctionParam(node, 0); getVueComponentContext(vueData.node).verifyReferences(propertyReferences, { props: true }); }, onRenderFunctionEnter(node, vueData) { const ctx = getVueComponentContext(vueData.node); const propertyReferences = propertyReferenceExtractor.extractFromFunctionParam(node, 0); ctx.verifyReferences(propertyReferences); if (vueData.functional) { const propertyReferencesForV2 = propertyReferenceExtractor.extractFromFunctionParam(node, 1); ctx.verifyReferences(propertyReferencesForV2.getNest("props"), { props: true }); } }, "ThisExpression, Identifier"(node, vueData) { if (!utils.isThis(node, context)) return; const ctx = getVueComponentContext(vueData.node); const propertyReferences = propertyReferenceExtractor.extractFromExpression(node, false); ctx.verifyReferences(propertyReferences); } }), { "Program:exit"() { const ctx = getVueComponentContextForTemplate(); if (!ctx) return; const styleVars = getStyleVariablesContext(context); if (styleVars) ctx.verifyReferences(propertyReferenceExtractor.extractFromStyleVariablesContext(styleVars)); } }); return utils.defineTemplateBodyVisitor(context, { VExpressionContainer(node) { const ctx = getVueComponentContextForTemplate(); if (!ctx) return; ctx.verifyReferences(propertyReferenceExtractor.extractFromVExpressionContainer(node, { ignoreGlobals: true })); } }, scriptVisitor); } }; })); //#endregion Object.defineProperty(exports, 'default', { enumerable: true, get: function () { return require_no_undef_properties(); } });