"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.analyzePropsScope = exports.analyzeStoreScope = exports.analyzeReactiveScope = exports.analyzeScope = void 0; const eslint_scope_1 = require("eslint-scope"); const traverse_1 = require("../traverse"); const scope_1 = require("../scope"); const utils_1 = require("../utils"); /** * Analyze scope */ function analyzeScope(node, parserOptions = {}) { const ecmaVersion = parserOptions.ecmaVersion || 2020; const ecmaFeatures = parserOptions.ecmaFeatures || {}; const sourceType = parserOptions.sourceType || "module"; const root = node.type === "Program" ? node : { type: "Program", body: [node], sourceType, }; return (0, eslint_scope_1.analyze)(root, { ignoreEval: true, nodejsScope: false, impliedStrict: ecmaFeatures.impliedStrict, ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 2022, sourceType, fallback: traverse_1.getFallbackKeys, }); } exports.analyzeScope = analyzeScope; /** Analyze reactive scope */ function analyzeReactiveScope(scopeManager) { for (const reference of [...scopeManager.globalScope.through]) { const parent = reference.writeExpr && getParent(reference.writeExpr); if ((parent === null || parent === void 0 ? void 0 : parent.type) === "AssignmentExpression") { const pp = getParent(parent); if ((pp === null || pp === void 0 ? void 0 : pp.type) === "ExpressionStatement") { const ppp = getParent(pp); if ((ppp === null || ppp === void 0 ? void 0 : ppp.type) === "SvelteReactiveStatement" && ppp.label.name === "$") { const referenceScope = reference.from; if (referenceScope.type === "module") { // It is computed transformComputedVariable(parent, ppp, reference); continue; } } } } } /** Transform ref to ComputedVariable */ function transformComputedVariable(node, parent, reference) { const referenceScope = reference.from; const name = reference.identifier.name; let variable = referenceScope.set.get(name); if (!variable) { variable = new eslint_scope_1.Variable(); variable.scope = referenceScope; variable.name = name; (0, utils_1.addElementToSortedArray)(variable.defs, { type: "ComputedVariable", node: node, parent: parent, name: reference.identifier, }, (a, b) => a.node.range[0] - b.node.range[0]); (0, scope_1.addVariable)(referenceScope.variables, variable); referenceScope.set.set(name, variable); } (0, utils_1.addElementToSortedArray)(variable.identifiers, reference.identifier, (a, b) => a.range[0] - b.range[0]); reference.resolved = variable; removeReferenceFromThrough(reference, referenceScope); } } exports.analyzeReactiveScope = analyzeReactiveScope; /** * Analyze store scope. e.g. $count */ function analyzeStoreScope(scopeManager) { const moduleScope = scopeManager.scopes.find((scope) => scope.type === "module"); if (!moduleScope) { return; } const toBeMarkAsUsedReferences = []; for (const reference of [...scopeManager.globalScope.through]) { if (reference.identifier.name.startsWith("$")) { const realName = reference.identifier.name.slice(1); const variable = moduleScope.set.get(realName); if (variable) { if (reference.isWriteOnly()) { // Need mark as used toBeMarkAsUsedReferences.push(reference); } // It does not write directly to the original variable. // Therefore, this variable is always a reference. reference.isWrite = () => false; reference.isWriteOnly = () => false; reference.isReadWrite = () => false; reference.isReadOnly = () => true; reference.isRead = () => true; (0, scope_1.addReference)(variable.references, reference); reference.resolved = variable; removeReferenceFromThrough(reference, moduleScope); } } } for (const variable of new Set(toBeMarkAsUsedReferences.map((ref) => ref.resolved))) { if (variable.references.some((ref) => !toBeMarkAsUsedReferences.includes(ref) && ref.identifier !== variable.identifiers[0])) { // It is already used. continue; } // Add the virtual reference for reading. addVirtualReference(variable.identifiers[0], variable, moduleScope, { read: true, }).svelteMarkAsUsed = true; } } exports.analyzeStoreScope = analyzeStoreScope; /** Transform props exports */ function analyzePropsScope(body, scopeManager) { const moduleScope = scopeManager.scopes.find((scope) => scope.type === "module"); if (!moduleScope) { return; } for (const node of body.body) { if (node.type !== "ExportNamedDeclaration") { continue; } if (node.declaration) { if (node.declaration.type === "VariableDeclaration") { for (const decl of node.declaration.declarations) { if (decl.id.type === "Identifier") { addPropsReference(decl.id, moduleScope); } } } } else { for (const spec of node.specifiers) { addPropsReference(spec.local, moduleScope); } } } /** Add virtual props reference */ function addPropsReference(node, scope) { for (const variable of scope.variables) { if (variable.name !== node.name) { continue; } if (variable.references.some((ref) => ref.sveltePropReference)) { continue; } // Add the virtual reference for writing. const reference = addVirtualReference(Object.assign(Object.assign({}, node), { // @ts-expect-error -- ignore parent: body, loc: { start: Object.assign({}, node.loc.start), end: Object.assign({}, node.loc.end), }, range: [...node.range] }), variable, scope, { write: true, read: true, }); reference.sveltePropReference = true; } } } exports.analyzePropsScope = analyzePropsScope; /** Remove reference from through */ function removeReferenceFromThrough(reference, baseScope) { const variable = reference.resolved; const name = reference.identifier.name; let scope = baseScope; while (scope) { scope.through = scope.through.filter((ref) => { if (reference === ref) { return false; } else if (ref.identifier.name === name) { ref.resolved = variable; if (!variable.references.includes(ref)) { (0, scope_1.addReference)(variable.references, ref); } return false; } return true; }); scope = scope.upper; } } /** * Add the virtual reference. */ function addVirtualReference(node, variable, scope, readWrite) { const reference = new eslint_scope_1.Reference(); reference.svelteVirtualReference = true; reference.from = scope; reference.identifier = node; reference.isWrite = () => Boolean(readWrite.write); reference.isWriteOnly = () => Boolean(readWrite.write) && !readWrite.read; reference.isRead = () => Boolean(readWrite.read); reference.isReadOnly = () => Boolean(readWrite.read) && !readWrite.write; reference.isReadWrite = () => Boolean(readWrite.read && readWrite.write); (0, scope_1.addReference)(variable.references, reference); reference.resolved = variable; return reference; } /** Get parent node */ function getParent(node) { return node.parent; }