UNPKG

eslint-plugin-vue

Version:

Official ESLint plugin for Vue.js

478 lines (475 loc) 17.1 kB
'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(); } });