eslint-plugin-vue
Version:
Official ESLint plugin for Vue.js
349 lines (346 loc) • 14 kB
JavaScript
'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();
}
});