eslint-plugin-vue
Version:
Official ESLint plugin for Vue.js
550 lines (547 loc) • 19.7 kB
JavaScript
'use strict';
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.js');
const require_index = require('./index.js');
//#region lib/utils/property-references.js
/**
* @author Yosuke Ota
* @copyright 2021 Yosuke Ota. All rights reserved.
* See LICENSE file in root directory for full license.
*/
var require_property_references = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
const utils = require_index.default;
const eslintUtils = require("@eslint-community/eslint-utils");
/**
* @typedef {import('./style-variables').StyleVariablesContext} StyleVariablesContext
*/
/**
* @typedef {object} IHasPropertyOption
* @property {boolean} [unknownCallAsAny]
*/
/**
* @typedef {object} NestPropertyNodeForExpression
* @property {'expression'} type
* @property {MemberExpression} node
*
* @typedef {object} NestPropertyNodeForPattern
* @property {'pattern'} type
* @property {MemberExpression | Identifier | ObjectPattern | ArrayPattern} node
*
* @typedef {NestPropertyNodeForExpression | NestPropertyNodeForPattern} NestPropertyNode
*/
/**
* @typedef {object} IPropertyReferences
* @property { (name: string, option?: IHasPropertyOption) => boolean } hasProperty
* @property { () => Map<string, {nodes:ASTNode[]}> } allProperties Get all properties.
* @property { (name: string) => IPropertyReferences } getNest Get the nesting property references.
* @property { (name: string) => Iterable<NestPropertyNode> } getNestNodes Get the nesting property nodes.
*/
/** @type {IPropertyReferences} */
const ANY = {
hasProperty: () => true,
allProperties: () => /* @__PURE__ */ new Map(),
getNest: () => ANY,
getNestNodes: () => []
};
/** @type {IPropertyReferences} */
const NEVER = {
hasProperty: () => false,
allProperties: () => /* @__PURE__ */ new Map(),
getNest: () => NEVER,
getNestNodes: () => []
};
/**
* @param {RuleContext} context
* @param {Identifier} id
* @returns {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration | null}
*/
function findFunction(context, id) {
const calleeVariable = utils.findVariableByIdentifier(context, id);
if (!calleeVariable) return null;
if (calleeVariable.defs.length === 1) {
const def = calleeVariable.defs[0];
if (def.node.type === "FunctionDeclaration") return def.node;
if (def.type === "Variable" && def.parent.kind === "const" && def.node.init) {
if (def.node.init.type === "FunctionExpression" || def.node.init.type === "ArrowFunctionExpression") return def.node.init;
if (def.node.init.type === "Identifier") return findFunction(context, def.node.init);
}
}
return null;
}
module.exports = {
definePropertyReferenceExtractor,
mergePropertyReferences
};
/**
* @param {RuleContext} context The rule context.
*/
function definePropertyReferenceExtractor(context, { unknownMemberAsUnreferenced = false, returnAsUnreferenced = false } = {}) {
/** @type {Map<Expression, IPropertyReferences>} */
const cacheForExpression = /* @__PURE__ */ new Map();
/** @type {Map<Pattern, IPropertyReferences>} */
const cacheForPattern = /* @__PURE__ */ new Map();
/** @type {Map<FunctionExpression | ArrowFunctionExpression | FunctionDeclaration, Map<number, IPropertyReferences>>} */
const cacheForFunction = /* @__PURE__ */ new Map();
/** @type {{ toRefNodes: Set<ESNode>, toRefsNodes: Set<ESNode>} | null} */
let toRefSet = null;
let isFunctionalTemplate = false;
const templateBody = context.sourceCode.ast.templateBody;
if (templateBody) isFunctionalTemplate = utils.hasAttribute(templateBody, "functional");
function getToRefSet() {
if (toRefSet) return toRefSet;
const tracker = new eslintUtils.ReferenceTracker(context.sourceCode.scopeManager.scopes[0]);
const toRefNodes = /* @__PURE__ */ new Set();
for (const { node } of utils.iterateReferencesTraceMap(tracker, {
[eslintUtils.ReferenceTracker.ESM]: true,
toRef: { [eslintUtils.ReferenceTracker.CALL]: true }
})) toRefNodes.add(node);
const toRefsNodes = /* @__PURE__ */ new Set();
for (const { node } of utils.iterateReferencesTraceMap(tracker, {
[eslintUtils.ReferenceTracker.ESM]: true,
toRefs: { [eslintUtils.ReferenceTracker.CALL]: true }
})) toRefsNodes.add(node);
return toRefSet = {
toRefNodes,
toRefsNodes
};
}
/**
* Collects the property references for member expr.
* @implements {IPropertyReferences}
*/
class PropertyReferencesForMember {
/**
*
* @param {MemberExpression} node
* @param {string} name
* @param {boolean} withInTemplate
*/
constructor(node, name, withInTemplate) {
this.node = node;
this.name = name;
this.withInTemplate = withInTemplate;
}
/**
* @param {string} name
*/
hasProperty(name) {
return name === this.name;
}
allProperties() {
return new Map([[this.name, { nodes: [this.node.property] }]]);
}
/**
* @param {string} name
* @returns {IPropertyReferences}
*/
getNest(name) {
return name === this.name ? extractFromExpression(this.node, this.withInTemplate) : NEVER;
}
/**
* @param {string} name
* @returns {Iterable<NestPropertyNodeForExpression>}
*/
*getNestNodes(name) {
if (name === this.name) yield {
type: "expression",
node: this.node
};
}
}
/**
* Collects the property references for object.
* @implements {IPropertyReferences}
*/
class PropertyReferencesForObject {
constructor() {
/** @type {Record<string, AssignmentProperty[]>} */
this.properties = Object.create(null);
}
/**
* @param {string} name
*/
hasProperty(name) {
return Boolean(this.properties[name]);
}
allProperties() {
const result = /* @__PURE__ */ new Map();
for (const [name, nodes] of Object.entries(this.properties)) result.set(name, { nodes: nodes.map((node) => node.key) });
return result;
}
/**
* @param {string} name
* @returns {IPropertyReferences}
*/
getNest(name) {
const properties = this.properties[name];
return properties ? mergePropertyReferences(properties.map((property) => getNestFromPattern(property.value))) : NEVER;
}
/**
* @param {string} name
* @returns {Iterable<NestPropertyNodeForPattern>}
*/
*getNestNodes(name) {
const properties = this.properties[name];
if (!properties) return;
const values = properties.map((property) => property.value);
let node;
while (node = values.shift()) if (node.type === "Identifier" || node.type === "MemberExpression" || node.type === "ObjectPattern" || node.type === "ArrayPattern") yield {
type: "pattern",
node
};
else if (node.type === "AssignmentPattern") values.unshift(node.left);
return properties ? properties.map((p) => p.value) : [];
}
}
/**
* @param {Pattern} pattern
* @returns {IPropertyReferences}
*/
function getNestFromPattern(pattern) {
if (pattern.type === "ObjectPattern") return extractFromObjectPattern(pattern);
if (pattern.type === "Identifier") return extractFromIdentifier(pattern);
else if (pattern.type === "AssignmentPattern") return getNestFromPattern(pattern.left);
return ANY;
}
/**
* Extract the property references from Expression.
* @param {Identifier | MemberExpression | ChainExpression | ThisExpression | CallExpression} node
* @param {boolean} withInTemplate
* @returns {IPropertyReferences}
*/
function extractFromExpression(node, withInTemplate) {
const ref = cacheForExpression.get(node);
if (ref) return ref;
cacheForExpression.set(node, ANY);
const result = extractWithoutCache();
cacheForExpression.set(node, result);
return result;
function extractWithoutCache() {
const parent = node.parent;
switch (parent.type) {
case "AssignmentExpression": return !withInTemplate && parent.right === node && parent.operator === "=" ? extractFromPattern(parent.left) : NEVER;
case "VariableDeclarator": return !withInTemplate && parent.init === node ? extractFromPattern(parent.id) : NEVER;
case "MemberExpression":
if (parent.object === node) {
const name = utils.getStaticPropertyName(parent);
if (name === "$props" && parent.parent.type === "MemberExpression") {
const propName = utils.getStaticPropertyName(parent.parent);
if (!propName) return unknownMemberAsUnreferenced ? NEVER : ANY;
return new PropertyReferencesForMember(parent.parent, propName, withInTemplate);
} else if (name) return new PropertyReferencesForMember(parent, name, withInTemplate);
else return unknownMemberAsUnreferenced ? NEVER : ANY;
}
return NEVER;
case "CallExpression": {
const argIndex = parent.arguments.indexOf(node);
return !withInTemplate && argIndex !== -1 ? extractFromCall(parent, argIndex) : NEVER;
}
case "ChainExpression": return extractFromExpression(parent, withInTemplate);
case "ArrowFunctionExpression":
case "VExpressionContainer":
case "Property":
case "ArrayExpression": return maybeExternalUsed(parent) ? ANY : NEVER;
case "ReturnStatement": if (returnAsUnreferenced) return NEVER;
else return maybeExternalUsed(parent) ? ANY : NEVER;
}
return NEVER;
}
/**
* @param {ASTNode} parentTarget
* @returns {boolean}
*/
function maybeExternalUsed(parentTarget) {
if (parentTarget.type === "ReturnStatement" || parentTarget.type === "VExpressionContainer") return true;
if (parentTarget.type === "ArrayExpression") return maybeExternalUsed(parentTarget.parent);
if (parentTarget.type === "Property") return maybeExternalUsed(parentTarget.parent.parent);
if (parentTarget.type === "ArrowFunctionExpression") return parentTarget.body === node;
return false;
}
}
/**
* Extract the property references from one parameter of the function.
* @param {Pattern} node
* @returns {IPropertyReferences}
*/
function extractFromPattern(node) {
const ref = cacheForPattern.get(node);
if (ref) return ref;
cacheForPattern.set(node, ANY);
const result = extractWithoutCache();
cacheForPattern.set(node, result);
return result;
function extractWithoutCache() {
while (node.type === "AssignmentPattern") node = node.left;
if (node.type === "RestElement" || node.type === "ArrayPattern") return NEVER;
if (node.type === "ObjectPattern") return extractFromObjectPattern(node);
if (node.type === "Identifier") return extractFromIdentifier(node);
return NEVER;
}
}
/**
* Extract the property references from ObjectPattern.
* @param {ObjectPattern} node
* @returns {IPropertyReferences}
*/
function extractFromObjectPattern(node) {
const refs = new PropertyReferencesForObject();
for (const prop of node.properties) if (prop.type === "Property") {
const name = utils.getStaticPropertyName(prop);
if (name) (refs.properties[name] || (refs.properties[name] = [])).push(prop);
else return ANY;
} else return ANY;
return refs;
}
/**
* Extract the property references from id.
* @param {Identifier} node
* @returns {IPropertyReferences}
*/
function extractFromIdentifier(node) {
const variable = utils.findVariableByIdentifier(context, node);
if (!variable) return NEVER;
return mergePropertyReferences(variable.references.map((reference) => {
const id = reference.identifier;
return extractFromExpression(id, false);
}));
}
/**
* Extract the property references from call.
* @param {CallExpression} node
* @param {number} argIndex
* @returns {IPropertyReferences}
*/
function extractFromCall(node, argIndex) {
if (node.callee.type !== "Identifier") return {
hasProperty(_name, options) {
return Boolean(options && options.unknownCallAsAny);
},
allProperties: () => /* @__PURE__ */ new Map(),
getNest: () => ANY,
getNestNodes: () => []
};
const fnNode = findFunction(context, node.callee);
if (!fnNode) {
if (argIndex === 0) {
if (getToRefSet().toRefNodes.has(node)) return extractFromToRef(node);
else if (getToRefSet().toRefsNodes.has(node)) return extractFromToRefs(node);
}
return {
hasProperty(_name, options) {
return Boolean(options && options.unknownCallAsAny);
},
allProperties: () => /* @__PURE__ */ new Map(),
getNest: () => ANY,
getNestNodes: () => []
};
}
return extractFromFunctionParam(fnNode, argIndex);
}
/**
* Extract the property references from function param.
* @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} node
* @param {number} argIndex
* @returns {IPropertyReferences}
*/
function extractFromFunctionParam(node, argIndex) {
let cacheForIndexes = cacheForFunction.get(node);
if (!cacheForIndexes) {
cacheForIndexes = /* @__PURE__ */ new Map();
cacheForFunction.set(node, cacheForIndexes);
}
const ref = cacheForIndexes.get(argIndex);
if (ref) return ref;
cacheForIndexes.set(argIndex, NEVER);
const arg = node.params[argIndex];
if (!arg) return NEVER;
const result = extractFromPattern(arg);
cacheForIndexes.set(argIndex, result);
return result;
}
/**
* Extract the property references from path.
* @param {string} pathString
* @param {Identifier | Literal | TemplateLiteral} node
* @returns {IPropertyReferences}
*/
function extractFromPath(pathString, node) {
return extractFromSegments(pathString.split("."));
/**
* @param {string[]} segments
* @returns {IPropertyReferences}
*/
function extractFromSegments(segments) {
if (segments.length === 0) return ANY;
const segmentName = segments[0];
return {
hasProperty: (name) => name === segmentName,
allProperties: () => new Map([[segmentName, { nodes: [node] }]]),
getNest: (name) => name === segmentName ? extractFromSegments(segments.slice(1)) : NEVER,
getNestNodes: () => []
};
}
}
/**
* Extract the property references from name literal.
* @param {Expression} node
* @returns {IPropertyReferences}
*/
function extractFromNameLiteral(node) {
const referenceName = node.type === "Literal" || node.type === "TemplateLiteral" ? utils.getStringLiteralValue(node) : null;
return referenceName ? {
hasProperty: (name) => name === referenceName,
allProperties: () => new Map([[referenceName, { nodes: [node] }]]),
getNest: (name) => name === referenceName ? ANY : NEVER,
getNestNodes: () => []
} : NEVER;
}
/**
* Extract the property references from name.
* @param {string} referenceName
* @param {Expression|SpreadElement} nameNode
* @param { () => IPropertyReferences } [getNest]
* @returns {IPropertyReferences}
*/
function extractFromName(referenceName, nameNode, getNest) {
return {
hasProperty: (name) => name === referenceName,
allProperties: () => new Map([[referenceName, { nodes: [nameNode] }]]),
getNest: (name) => name === referenceName ? getNest ? getNest() : ANY : NEVER,
getNestNodes: () => []
};
}
/**
* Extract the property references from toRef call.
* @param {CallExpression} node
* @returns {IPropertyReferences}
*/
function extractFromToRef(node) {
const nameNode = node.arguments[1];
const refName = nameNode && (nameNode.type === "Literal" || nameNode.type === "TemplateLiteral") ? utils.getStringLiteralValue(nameNode) : null;
if (!refName) return ANY;
return extractFromName(refName, nameNode, () => extractFromExpression(node, false).getNest("value"));
}
/**
* Extract the property references from toRefs call.
* @param {CallExpression} node
* @returns {IPropertyReferences}
*/
function extractFromToRefs(node) {
const base = extractFromExpression(node, false);
return {
hasProperty: (name, option) => base.hasProperty(name, option),
allProperties: () => base.allProperties(),
getNest: (name) => base.getNest(name).getNest("value"),
getNestNodes: (name) => base.getNest(name).getNestNodes("value")
};
}
/**
* Extract the property references from VExpressionContainer.
* @param {VExpressionContainer} node
* @param {object} [options]
* @param {boolean} [options.ignoreGlobals]
* @returns {IPropertyReferences}
*/
function extractFromVExpressionContainer(node, options) {
const ignoreGlobals = options && options.ignoreGlobals;
/** @type { (name:string)=>boolean } */
let ignoreRef = () => false;
if (ignoreGlobals) {
const globalScope = context.sourceCode.scopeManager.globalScope || context.sourceCode.scopeManager.scopes[0];
ignoreRef = (name) => globalScope.set.has(name);
}
/** @type {IPropertyReferences[]} */
const references = [];
for (const id of node.references.filter((ref) => ref.variable == null).map((ref) => ref.id)) {
if (ignoreRef(id.name)) continue;
if (isFunctionalTemplate) {
if (id.name === "props") references.push(extractFromExpression(id, true));
} else {
const referenceId = id.name === "$props" && id.parent.type === "MemberExpression" && id.parent.property.type === "Identifier" ? id.parent.property : id;
references.push(extractFromName(referenceId.name, referenceId, () => extractFromExpression(referenceId, true)));
}
}
return mergePropertyReferences(references);
}
/**
* Extract the property references from StyleVariablesContext.
* @param {StyleVariablesContext} ctx
* @returns {IPropertyReferences}
*/
function extractFromStyleVariablesContext(ctx) {
const references = [];
for (const { id } of ctx.references) references.push(extractFromName(id.name, id, () => extractFromExpression(id, true)));
return mergePropertyReferences(references);
}
return {
extractFromExpression,
extractFromPattern,
extractFromFunctionParam,
extractFromPath,
extractFromName,
extractFromNameLiteral,
extractFromVExpressionContainer,
extractFromStyleVariablesContext
};
}
/**
* @param {IPropertyReferences[]} references
* @returns {IPropertyReferences}
*/
function mergePropertyReferences(references) {
if (references.length === 0) return NEVER;
if (references.length === 1) return references[0];
return new PropertyReferencesForMerge(references);
}
/**
* Collects the property references for merge.
* @implements {IPropertyReferences}
*/
var PropertyReferencesForMerge = class {
/**
* @param {IPropertyReferences[]} references
*/
constructor(references) {
this.references = references;
}
/**
* @param {string} name
* @param {IHasPropertyOption} [option]
*/
hasProperty(name, option) {
return this.references.some((ref) => ref.hasProperty(name, option));
}
allProperties() {
const result = /* @__PURE__ */ new Map();
for (const reference of this.references) for (const [name, { nodes }] of reference.allProperties()) {
const r = result.get(name);
if (r) r.nodes = [...new Set([...r.nodes, ...nodes])];
else result.set(name, { nodes: [...nodes] });
}
return result;
}
/**
* @param {string} name
* @returns {IPropertyReferences}
*/
getNest(name) {
/** @type {IPropertyReferences[]} */
const nest = [];
for (const ref of this.references) if (ref.hasProperty(name)) nest.push(ref.getNest(name));
return mergePropertyReferences(nest);
}
/**
* @param {string} name
* @returns {Iterable<NestPropertyNode>}
*/
*getNestNodes(name) {
for (const ref of this.references) if (ref.hasProperty(name)) yield* ref.getNestNodes(name);
}
};
}));
//#endregion
Object.defineProperty(exports, 'default', {
enumerable: true,
get: function () {
return require_property_references();
}
});