eslint-plugin-vue
Version:
Official ESLint plugin for Vue.js
478 lines (475 loc) • 17.1 kB
JavaScript
'use strict';
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.js');
const require_index = require('./index.js');
const require_property_references$1 = require('./property-references.js');
//#region lib/utils/ref-object-references.js
/**
* @author Yosuke Ota
* @copyright 2022 Yosuke Ota. All rights reserved.
* See LICENSE file in root directory for full license.
*/
var require_ref_object_references = /* @__PURE__ */ require_rolldown_runtime.__commonJSMin(((exports, module) => {
const utils = require_index.default;
const eslintUtils = require("@eslint-community/eslint-utils");
const { definePropertyReferenceExtractor } = require_property_references$1.default;
const { ReferenceTracker } = eslintUtils;
/**
* @typedef {object} RefObjectReferenceForExpression
* @property {'expression'} type
* @property {MemberExpression | CallExpression} node
* @property {string} method
* @property {CallExpression} define
* @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
*
* @typedef {object} RefObjectReferenceForPattern
* @property {'pattern'} type
* @property {ObjectPattern} node
* @property {string} method
* @property {CallExpression} define
* @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
*
* @typedef {object} RefObjectReferenceForIdentifier
* @property {'expression' | 'pattern'} type
* @property {Identifier} node
* @property {VariableDeclarator | null} variableDeclarator
* @property {VariableDeclaration | null} variableDeclaration
* @property {string} method
* @property {CallExpression} define
* @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
*
* @typedef {RefObjectReferenceForIdentifier | RefObjectReferenceForExpression | RefObjectReferenceForPattern} RefObjectReference
*/
/**
* @typedef {object} ReactiveVariableReference
* @property {Identifier} node
* @property {boolean} escape Within escape hint (`$$()`)
* @property {VariableDeclaration} variableDeclaration
* @property {string} method
* @property {CallExpression} define
*/
/**
* @typedef {object} RefObjectReferences
* @property {<T extends Identifier | Expression | Pattern | Super> (node: T) =>
* T extends Identifier ?
* RefObjectReferenceForIdentifier | null :
* T extends Expression ?
* RefObjectReferenceForExpression | null :
* T extends Pattern ?
* RefObjectReferenceForPattern | null :
* null} get
*/
/**
* @typedef {object} ReactiveVariableReferences
* @property {(node: Identifier) => ReactiveVariableReference | null} get
*/
const REF_MACROS = [
"$ref",
"$computed",
"$shallowRef",
"$customRef",
"$toRef",
"$"
];
/** @type {WeakMap<Program, RefObjectReferences>} */
const cacheForRefObjectReferences = /* @__PURE__ */ new WeakMap();
/** @type {WeakMap<Program, ReactiveVariableReferences>} */
const cacheForReactiveVariableReferences = /* @__PURE__ */ new WeakMap();
/**
* Iterate the call expressions that define the ref object.
* @param {import('eslint').Scope.Scope} globalScope
* @returns {Iterable<{ node: CallExpression, name: string }>}
*/
function* iterateDefineRefs(globalScope) {
const tracker = new ReferenceTracker(globalScope);
for (const { node, path } of utils.iterateReferencesTraceMap(tracker, {
ref: { [ReferenceTracker.CALL]: true },
computed: { [ReferenceTracker.CALL]: true },
toRef: { [ReferenceTracker.CALL]: true },
customRef: { [ReferenceTracker.CALL]: true },
shallowRef: { [ReferenceTracker.CALL]: true },
toRefs: { [ReferenceTracker.CALL]: true }
})) yield {
node,
name: path.at(-1)
};
}
/**
* Iterate the call expressions that defineModel() macro.
* @param {import('eslint').Scope.Scope} globalScope
* @returns {Iterable<{ node: CallExpression }>}
*/
function* iterateDefineModels(globalScope) {
for (const { identifier } of iterateMacroReferences()) if (identifier.parent.type === "CallExpression" && identifier.parent.callee === identifier) yield { node: identifier.parent };
/**
* Iterate macro reference.
* @returns {Iterable<Reference>}
*/
function* iterateMacroReferences() {
const variable = globalScope.set.get("defineModel");
if (variable && variable.defs.length === 0) yield* variable.references;
for (const ref of globalScope.through) if (ref.identifier.name === "defineModel") yield ref;
}
}
/**
* Iterate the call expressions that define the reactive variables.
* @param {import('eslint').Scope.Scope} globalScope
* @returns {Iterable<{ node: CallExpression, name: string }>}
*/
function* iterateDefineReactiveVariables(globalScope) {
for (const { identifier } of iterateRefMacroReferences()) if (identifier.parent.type === "CallExpression" && identifier.parent.callee === identifier) yield {
node: identifier.parent,
name: identifier.name
};
/**
* Iterate ref macro reference.
* @returns {Iterable<Reference>}
*/
function* iterateRefMacroReferences() {
yield* REF_MACROS.map((m) => globalScope.set.get(m)).filter(utils.isDef).flatMap((v) => v.references);
for (const ref of globalScope.through) if (REF_MACROS.includes(ref.identifier.name)) yield ref;
}
}
/**
* Iterate the call expressions that the escape hint values.
* @param {import('eslint').Scope.Scope} globalScope
* @returns {Iterable<CallExpression>}
*/
function* iterateEscapeHintValueRefs(globalScope) {
for (const { identifier } of iterateEscapeHintReferences()) if (identifier.parent.type === "CallExpression" && identifier.parent.callee === identifier) yield identifier.parent;
/**
* Iterate escape hint reference.
* @returns {Iterable<Reference>}
*/
function* iterateEscapeHintReferences() {
const escapeHint = globalScope.set.get("$$");
if (escapeHint) yield* escapeHint.references;
for (const ref of globalScope.through) if (ref.identifier.name === "$$") yield ref;
}
}
/**
* Extract identifier from given pattern node.
* @param {Pattern} node
* @returns {Iterable<Identifier>}
*/
function* extractIdentifier(node) {
switch (node.type) {
case "Identifier":
yield node;
break;
case "ObjectPattern":
for (const property of node.properties) if (property.type === "Property") yield* extractIdentifier(property.value);
else if (property.type === "RestElement") yield* extractIdentifier(property);
break;
case "ArrayPattern":
for (const element of node.elements) if (element) yield* extractIdentifier(element);
break;
case "AssignmentPattern":
yield* extractIdentifier(node.left);
break;
case "RestElement":
yield* extractIdentifier(node.argument);
break;
case "MemberExpression": break;
}
}
/**
* Iterate references of the given identifier.
* @param {Identifier} id
* @param {import('eslint').Scope.Scope} globalScope
* @returns {Iterable<import('eslint').Scope.Reference>}
*/
function* iterateIdentifierReferences(id, globalScope) {
const variable = eslintUtils.findVariable(globalScope, id);
if (!variable) return;
for (const reference of variable.references) yield reference;
}
/**
* @param {RuleContext} context The rule context.
*/
function getGlobalScope(context) {
const sourceCode = context.sourceCode;
return sourceCode.scopeManager.globalScope || sourceCode.scopeManager.scopes[0];
}
module.exports = {
iterateDefineRefs,
extractRefObjectReferences,
extractReactiveVariableReferences
};
/**
* @typedef {object} RefObjectReferenceContext
* @property {string} method
* @property {CallExpression} define
* @property {(CallExpression | Identifier | MemberExpression)[]} defineChain Holds the initialization path for assignment of ref objects.
*/
/**
* @implements {RefObjectReferences}
*/
var RefObjectReferenceExtractor = class {
/**
* @param {RuleContext} context The rule context.
*/
constructor(context) {
this.context = context;
/** @type {Map<Identifier | MemberExpression | CallExpression | ObjectPattern, RefObjectReference>} */
this.references = /* @__PURE__ */ new Map();
/** @type {Set<Identifier>} */
this._processedIds = /* @__PURE__ */ new Set();
}
/**
* @template {Identifier | Expression | Pattern | Super} T
* @param {T} node
* @returns {T extends Identifier ?
* RefObjectReferenceForIdentifier | null :
* T extends Expression ?
* RefObjectReferenceForExpression | null :
* T extends Pattern ?
* RefObjectReferenceForPattern | null :
* null}
*/
get(node) {
return this.references.get(node) || null;
}
/**
* @param {CallExpression} node
* @param {string} method
*/
processDefineRef(node, method) {
const parent = node.parent;
/** @type {Pattern | null} */
let pattern = null;
if (parent.type === "VariableDeclarator") pattern = parent.id;
else if (parent.type === "AssignmentExpression" && parent.operator === "=") pattern = parent.left;
else {
if (method !== "toRefs") this.references.set(node, {
type: "expression",
node,
method,
define: node,
defineChain: [node]
});
return;
}
const ctx = {
method,
define: node,
defineChain: [node]
};
if (method === "toRefs") {
const propertyReferences = definePropertyReferenceExtractor(this.context).extractFromPattern(pattern);
for (const name of propertyReferences.allProperties().keys()) for (const nest of propertyReferences.getNestNodes(name)) if (nest.type === "expression") this.processMemberExpression(nest.node, ctx);
else if (nest.type === "pattern") this.processPattern(nest.node, ctx);
} else this.processPattern(pattern, ctx);
}
/**
* @param {CallExpression} node
*/
processDefineModel(node) {
const parent = node.parent;
/** @type {Pattern | null} */
let pattern = null;
if (parent.type === "VariableDeclarator") pattern = parent.id;
else if (parent.type === "AssignmentExpression" && parent.operator === "=") pattern = parent.left;
else return;
const ctx = {
method: "defineModel",
define: node,
defineChain: [node]
};
if (pattern.type === "ArrayPattern" && pattern.elements[0]) pattern = pattern.elements[0];
this.processPattern(pattern, ctx);
}
/**
* @param {MemberExpression | Identifier} node
* @param {RefObjectReferenceContext} ctx
*/
processExpression(node, ctx) {
const parent = node.parent;
if (parent.type === "AssignmentExpression") {
if (parent.operator === "=" && parent.right === node) {
this.processPattern(parent.left, {
...ctx,
defineChain: [node, ...ctx.defineChain]
});
return true;
}
} else if (parent.type === "VariableDeclarator" && parent.init === node) {
this.processPattern(parent.id, {
...ctx,
defineChain: [node, ...ctx.defineChain]
});
return true;
}
return false;
}
/**
* @param {MemberExpression} node
* @param {RefObjectReferenceContext} ctx
*/
processMemberExpression(node, ctx) {
if (this.processExpression(node, ctx)) return;
this.references.set(node, {
type: "expression",
node,
...ctx
});
}
/**
* @param {Pattern} node
* @param {RefObjectReferenceContext} ctx
*/
processPattern(node, ctx) {
switch (node.type) {
case "Identifier":
this.processIdentifierPattern(node, ctx);
break;
case "ArrayPattern":
case "RestElement":
case "MemberExpression": return;
case "ObjectPattern":
this.references.set(node, {
type: "pattern",
node,
...ctx
});
return;
case "AssignmentPattern":
this.processPattern(node.left, ctx);
return;
}
}
/**
* @param {Identifier} node
* @param {RefObjectReferenceContext} ctx
*/
processIdentifierPattern(node, ctx) {
if (this._processedIds.has(node)) return;
this._processedIds.add(node);
for (const reference of iterateIdentifierReferences(node, getGlobalScope(this.context))) {
const def = reference.resolved && reference.resolved.defs.length === 1 && reference.resolved.defs[0].type === "Variable" ? reference.resolved.defs[0] : null;
if (def && def.name === reference.identifier) continue;
if (reference.isRead() && this.processExpression(reference.identifier, ctx)) continue;
this.references.set(reference.identifier, {
type: reference.isWrite() ? "pattern" : "expression",
node: reference.identifier,
variableDeclarator: def ? def.node : null,
variableDeclaration: def ? def.parent : null,
...ctx
});
}
}
};
/**
* Extracts references of all ref objects.
* @param {RuleContext} context The rule context.
* @returns {RefObjectReferences}
*/
function extractRefObjectReferences(context) {
const sourceCode = context.sourceCode;
const cachedReferences = cacheForRefObjectReferences.get(sourceCode.ast);
if (cachedReferences) return cachedReferences;
const references = new RefObjectReferenceExtractor(context);
const globalScope = getGlobalScope(context);
for (const { node, name } of iterateDefineRefs(globalScope)) references.processDefineRef(node, name);
for (const { node } of iterateDefineModels(globalScope)) references.processDefineModel(node);
cacheForRefObjectReferences.set(sourceCode.ast, references);
return references;
}
/**
* @implements {ReactiveVariableReferences}
*/
var ReactiveVariableReferenceExtractor = class {
/**
* @param {RuleContext} context The rule context.
*/
constructor(context) {
this.context = context;
/** @type {Map<Identifier, ReactiveVariableReference>} */
this.references = /* @__PURE__ */ new Map();
/** @type {Set<Identifier>} */
this._processedIds = /* @__PURE__ */ new Set();
/** @type {Set<CallExpression>} */
this._escapeHintValueRefs = new Set(iterateEscapeHintValueRefs(getGlobalScope(context)));
}
/**
* @param {Identifier} node
* @returns {ReactiveVariableReference | null}
*/
get(node) {
return this.references.get(node) || null;
}
/**
* @param {CallExpression} node
* @param {string} method
*/
processDefineReactiveVariable(node, method) {
const parent = node.parent;
if (parent.type !== "VariableDeclarator") return;
/** @type {Pattern | null} */
const pattern = parent.id;
if (method === "$") for (const id of extractIdentifier(pattern)) this.processIdentifierPattern(id, method, node);
else if (pattern.type === "Identifier") this.processIdentifierPattern(pattern, method, node);
}
/**
* @param {Identifier} node
* @param {string} method
* @param {CallExpression} define
*/
processIdentifierPattern(node, method, define) {
if (this._processedIds.has(node)) return;
this._processedIds.add(node);
for (const reference of iterateIdentifierReferences(node, getGlobalScope(this.context))) {
const def = reference.resolved && reference.resolved.defs.length === 1 && reference.resolved.defs[0].type === "Variable" ? reference.resolved.defs[0] : null;
if (!def || def.name === reference.identifier) continue;
this.references.set(reference.identifier, {
node: reference.identifier,
escape: this.withinEscapeHint(reference.identifier),
method,
define,
variableDeclaration: def.parent
});
}
}
/**
* Checks whether the given identifier node within the escape hints (`$$()`) or not.
* @param {Identifier} node
*/
withinEscapeHint(node) {
/** @type {Identifier | ObjectExpression | ArrayExpression | SpreadElement | Property | AssignmentProperty} */
let target = node;
/** @type {ASTNode | null} */
let parent = target.parent;
while (parent) {
if (parent.type === "CallExpression") {
if (parent.arguments.includes(target) && this._escapeHintValueRefs.has(parent)) return true;
return false;
}
if (parent.type === "Property" && parent.value === target || parent.type === "ObjectExpression" && parent.properties.includes(target) || parent.type === "ArrayExpression" || parent.type === "SpreadElement") {
target = parent;
parent = target.parent;
} else return false;
}
return false;
}
};
/**
* Extracts references of all reactive variables.
* @param {RuleContext} context The rule context.
* @returns {ReactiveVariableReferences}
*/
function extractReactiveVariableReferences(context) {
const sourceCode = context.sourceCode;
const cachedReferences = cacheForReactiveVariableReferences.get(sourceCode.ast);
if (cachedReferences) return cachedReferences;
const references = new ReactiveVariableReferenceExtractor(context);
for (const { node, name } of iterateDefineReactiveVariables(getGlobalScope(context))) references.processDefineReactiveVariable(node, name);
cacheForReactiveVariableReferences.set(sourceCode.ast, references);
return references;
}
}));
//#endregion
Object.defineProperty(exports, 'default', {
enumerable: true,
get: function () {
return require_ref_object_references();
}
});