- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
832 lines
40 KiB
JavaScript
832 lines
40 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const utils_1 = require("@typescript-eslint/utils");
|
|
const estraverse_1 = require("estraverse");
|
|
const utils_2 = require("../utils");
|
|
const { findVariable, getFunctionHeadLocation } = utils_1.ASTUtils;
|
|
const createRule = utils_1.ESLintUtils.RuleCreator.withoutDocs;
|
|
class ScopeStackItem {
|
|
node;
|
|
trackedScopes = [];
|
|
unnamedDerivedSignals = new Set();
|
|
hasJSX = false;
|
|
constructor(node) {
|
|
this.node = node;
|
|
}
|
|
}
|
|
class ScopeStack extends Array {
|
|
currentScope = () => this[this.length - 1];
|
|
parentScope = () => this[this.length - 2];
|
|
pushSignal(variable, declarationScope = this.currentScope().node) {
|
|
this.signals.push({
|
|
references: variable.references.filter((reference) => !reference.init),
|
|
variable,
|
|
declarationScope,
|
|
});
|
|
}
|
|
pushUniqueSignal(variable, declarationScope) {
|
|
const foundSignal = this.signals.find((s) => s.variable === variable);
|
|
if (!foundSignal) {
|
|
this.pushSignal(variable, declarationScope);
|
|
}
|
|
else {
|
|
foundSignal.declarationScope = this.findDeepestDeclarationScope(foundSignal.declarationScope, declarationScope);
|
|
}
|
|
}
|
|
pushProps(variable, declarationScope = this.currentScope().node) {
|
|
this.props.push({
|
|
references: variable.references.filter((reference) => !reference.init),
|
|
variable,
|
|
declarationScope,
|
|
});
|
|
}
|
|
syncCallbacks = new Set();
|
|
*consumeSignalReferencesInScope() {
|
|
yield* this.consumeReferencesInScope(this.signals);
|
|
this.signals = this.signals.filter((variable) => variable.references.length !== 0);
|
|
}
|
|
*consumePropsReferencesInScope() {
|
|
yield* this.consumeReferencesInScope(this.props);
|
|
this.props = this.props.filter((variable) => variable.references.length !== 0);
|
|
}
|
|
*consumeReferencesInScope(variables) {
|
|
for (const variable of variables) {
|
|
const { references } = variable;
|
|
const inScope = [], notInScope = [];
|
|
references.forEach((reference) => {
|
|
if (this.isReferenceInCurrentScope(reference)) {
|
|
inScope.push(reference);
|
|
}
|
|
else {
|
|
notInScope.push(reference);
|
|
}
|
|
});
|
|
yield* inScope.map((reference) => ({
|
|
reference,
|
|
declarationScope: variable.declarationScope,
|
|
}));
|
|
variable.references = notInScope;
|
|
}
|
|
}
|
|
findDeepestDeclarationScope = (a, b) => {
|
|
if (a === b)
|
|
return a;
|
|
for (let i = this.length - 1; i >= 0; i -= 1) {
|
|
const { node } = this[i];
|
|
if (a === node || b === node) {
|
|
return node;
|
|
}
|
|
}
|
|
throw new Error("This should never happen");
|
|
};
|
|
isReferenceInCurrentScope(reference) {
|
|
let parentFunction = (0, utils_2.findParent)(reference.identifier, utils_2.isProgramOrFunctionNode);
|
|
while ((0, utils_2.isFunctionNode)(parentFunction) && this.syncCallbacks.has(parentFunction)) {
|
|
parentFunction = (0, utils_2.findParent)(parentFunction, utils_2.isProgramOrFunctionNode);
|
|
}
|
|
return parentFunction === this.currentScope().node;
|
|
}
|
|
signals = [];
|
|
props = [];
|
|
}
|
|
const getNthDestructuredVar = (id, n, scope) => {
|
|
if (id?.type === "ArrayPattern") {
|
|
const el = id.elements[n];
|
|
if (el?.type === "Identifier") {
|
|
return findVariable(scope, el.name);
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
const getReturnedVar = (id, scope) => {
|
|
if (id.type === "Identifier") {
|
|
return findVariable(scope, id.name);
|
|
}
|
|
return null;
|
|
};
|
|
exports.default = createRule({
|
|
meta: {
|
|
type: "problem",
|
|
docs: {
|
|
description: "Enforce that reactivity (props, signals, memos, etc.) is properly used, so changes in those values will be tracked and update the view as expected.",
|
|
url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/docs/reactivity.md",
|
|
},
|
|
schema: [
|
|
{
|
|
type: "object",
|
|
properties: {
|
|
customReactiveFunctions: {
|
|
description: "List of function names to consider as reactive functions (allow signals to be safely passed as arguments). In addition, any create* or use* functions are automatically included.",
|
|
type: "array",
|
|
items: {
|
|
type: "string",
|
|
},
|
|
default: [],
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
messages: {
|
|
noWrite: "The reactive variable '{{name}}' should not be reassigned or altered directly.",
|
|
untrackedReactive: "The reactive variable '{{name}}' should be used within JSX, a tracked scope (like createEffect), or inside an event handler function, or else changes will be ignored.",
|
|
expectedFunctionGotExpression: "The reactive variable '{{name}}' should be wrapped in a function for reactivity. This includes event handler bindings on native elements, which are not reactive like other JSX props.",
|
|
badSignal: "The reactive variable '{{name}}' should be called as a function when used in {{where}}.",
|
|
badUnnamedDerivedSignal: "This function should be passed to a tracked scope (like createEffect) or an event handler because it contains reactivity, or else changes will be ignored.",
|
|
shouldDestructure: "For proper analysis, array destructuring should be used to capture the {{nth}}result of this function call.",
|
|
shouldAssign: "For proper analysis, a variable should be used to capture the result of this function call.",
|
|
noAsyncTrackedScope: "This tracked scope should not be async. Solid's reactivity only tracks synchronously.",
|
|
},
|
|
},
|
|
defaultOptions: [
|
|
{
|
|
customReactiveFunctions: [],
|
|
},
|
|
],
|
|
create(context, [options]) {
|
|
const warnShouldDestructure = (node, nth) => context.report({
|
|
node,
|
|
messageId: "shouldDestructure",
|
|
data: nth ? { nth: nth + " " } : undefined,
|
|
});
|
|
const warnShouldAssign = (node) => context.report({ node, messageId: "shouldAssign" });
|
|
const sourceCode = context.getSourceCode();
|
|
const scopeStack = new ScopeStack();
|
|
const { currentScope, parentScope } = scopeStack;
|
|
const { matchImport, handleImportDeclaration } = (0, utils_2.trackImports)();
|
|
const markPropsOnCondition = (node, cb) => {
|
|
if (node.params.length === 1 &&
|
|
node.params[0].type === "Identifier" &&
|
|
node.parent?.type !== "JSXExpressionContainer" &&
|
|
node.parent?.type !== "TemplateLiteral" &&
|
|
cb(node.params[0])) {
|
|
const propsParam = findVariable(context.getScope(), node.params[0]);
|
|
if (propsParam) {
|
|
scopeStack.pushProps(propsParam, node);
|
|
}
|
|
}
|
|
};
|
|
const onFunctionEnter = (node) => {
|
|
if ((0, utils_2.isFunctionNode)(node)) {
|
|
if (scopeStack.syncCallbacks.has(node)) {
|
|
return;
|
|
}
|
|
markPropsOnCondition(node, (props) => (0, utils_2.isPropsByName)(props.name));
|
|
}
|
|
scopeStack.push(new ScopeStackItem(node));
|
|
};
|
|
const matchTrackedScope = (trackedScope, node) => {
|
|
switch (trackedScope.expect) {
|
|
case "function":
|
|
case "called-function":
|
|
return node === trackedScope.node;
|
|
case "expression":
|
|
return Boolean((0, utils_2.findInScope)(node, currentScope().node, (node) => node === trackedScope.node));
|
|
}
|
|
};
|
|
const handleTrackedScopes = (identifier, declarationScope) => {
|
|
const currentScopeNode = currentScope().node;
|
|
if (!currentScope().trackedScopes.find((trackedScope) => matchTrackedScope(trackedScope, identifier))) {
|
|
const matchedExpression = currentScope().trackedScopes.find((trackedScope) => matchTrackedScope({ ...trackedScope, expect: "expression" }, identifier));
|
|
if (declarationScope === currentScopeNode) {
|
|
let parentMemberExpression = null;
|
|
if (identifier.parent?.type === "MemberExpression") {
|
|
parentMemberExpression = identifier.parent;
|
|
while (parentMemberExpression.parent?.type === "MemberExpression") {
|
|
parentMemberExpression = parentMemberExpression.parent;
|
|
}
|
|
}
|
|
const parentCallExpression = identifier.parent?.type === "CallExpression" ? identifier.parent : null;
|
|
context.report({
|
|
node: parentMemberExpression ?? parentCallExpression ?? identifier,
|
|
messageId: matchedExpression ? "expectedFunctionGotExpression" : "untrackedReactive",
|
|
data: {
|
|
name: parentMemberExpression
|
|
? sourceCode.getText(parentMemberExpression)
|
|
: identifier.name,
|
|
},
|
|
});
|
|
}
|
|
else {
|
|
if (!parentScope() || !(0, utils_2.isFunctionNode)(currentScopeNode)) {
|
|
throw new Error("this shouldn't happen!");
|
|
}
|
|
const pushUnnamedDerivedSignal = () => (parentScope().unnamedDerivedSignals ??= new Set()).add(currentScopeNode);
|
|
if (currentScopeNode.type === "FunctionDeclaration") {
|
|
const functionVariable = sourceCode.scopeManager?.getDeclaredVariables(currentScopeNode)?.[0];
|
|
if (functionVariable) {
|
|
scopeStack.pushUniqueSignal(functionVariable, declarationScope);
|
|
}
|
|
else {
|
|
pushUnnamedDerivedSignal();
|
|
}
|
|
}
|
|
else if (currentScopeNode.parent?.type === "VariableDeclarator") {
|
|
const declarator = currentScopeNode.parent;
|
|
const functionVariable = sourceCode.scopeManager?.getDeclaredVariables(declarator)?.[0];
|
|
if (functionVariable) {
|
|
scopeStack.pushUniqueSignal(functionVariable, declarationScope);
|
|
}
|
|
else {
|
|
pushUnnamedDerivedSignal();
|
|
}
|
|
}
|
|
else if (currentScopeNode.parent?.type === "Property") {
|
|
}
|
|
else {
|
|
pushUnnamedDerivedSignal();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const onFunctionExit = (currentScopeNode) => {
|
|
if ((0, utils_2.isFunctionNode)(currentScopeNode)) {
|
|
markPropsOnCondition(currentScopeNode, (props) => {
|
|
if (!(0, utils_2.isPropsByName)(props.name) &&
|
|
currentScope().hasJSX) {
|
|
const functionName = (0, utils_2.getFunctionName)(currentScopeNode);
|
|
if (functionName && !/^[a-z]/.test(functionName))
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
if ((0, utils_2.isFunctionNode)(currentScopeNode) && scopeStack.syncCallbacks.has(currentScopeNode)) {
|
|
return;
|
|
}
|
|
for (const { reference, declarationScope } of scopeStack.consumeSignalReferencesInScope()) {
|
|
const identifier = reference.identifier;
|
|
if (reference.isWrite()) {
|
|
context.report({
|
|
node: identifier,
|
|
messageId: "noWrite",
|
|
data: {
|
|
name: identifier.name,
|
|
},
|
|
});
|
|
}
|
|
else if (identifier.type === "Identifier") {
|
|
const reportBadSignal = (where) => context.report({
|
|
node: identifier,
|
|
messageId: "badSignal",
|
|
data: { name: identifier.name, where },
|
|
});
|
|
if (identifier.parent?.type === "CallExpression" ||
|
|
(identifier.parent?.type === "ArrayExpression" &&
|
|
identifier.parent.parent?.type === "CallExpression")) {
|
|
handleTrackedScopes(identifier, declarationScope);
|
|
}
|
|
else if (identifier.parent?.type === "TemplateLiteral") {
|
|
reportBadSignal("template literals");
|
|
}
|
|
else if (identifier.parent?.type === "BinaryExpression" &&
|
|
[
|
|
"<",
|
|
"<=",
|
|
">",
|
|
">=",
|
|
"<<",
|
|
">>",
|
|
">>>",
|
|
"+",
|
|
"-",
|
|
"*",
|
|
"/",
|
|
"%",
|
|
"**",
|
|
"|",
|
|
"^",
|
|
"&",
|
|
"in",
|
|
].includes(identifier.parent.operator)) {
|
|
reportBadSignal("arithmetic or comparisons");
|
|
}
|
|
else if (identifier.parent?.type === "UnaryExpression" &&
|
|
["-", "+", "~"].includes(identifier.parent.operator)) {
|
|
reportBadSignal("unary expressions");
|
|
}
|
|
else if (identifier.parent?.type === "MemberExpression" &&
|
|
identifier.parent.computed &&
|
|
identifier.parent.property === identifier) {
|
|
reportBadSignal("property accesses");
|
|
}
|
|
else if (identifier.parent?.type === "JSXExpressionContainer" &&
|
|
!currentScope().trackedScopes.find((trackedScope) => trackedScope.node === identifier &&
|
|
(trackedScope.expect === "function" || trackedScope.expect === "called-function"))) {
|
|
const elementOrAttribute = identifier.parent.parent;
|
|
if ((0, utils_2.isJSXElementOrFragment)(elementOrAttribute) ||
|
|
(elementOrAttribute?.type === "JSXAttribute" &&
|
|
elementOrAttribute.parent?.type === "JSXOpeningElement" &&
|
|
elementOrAttribute.parent.name.type === "JSXIdentifier" &&
|
|
(0, utils_2.isDOMElementName)(elementOrAttribute.parent.name.name))) {
|
|
reportBadSignal("JSX");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (const { reference, declarationScope } of scopeStack.consumePropsReferencesInScope()) {
|
|
const identifier = reference.identifier;
|
|
if (reference.isWrite()) {
|
|
context.report({
|
|
node: identifier,
|
|
messageId: "noWrite",
|
|
data: {
|
|
name: identifier.name,
|
|
},
|
|
});
|
|
}
|
|
else if (identifier.parent?.type === "MemberExpression" &&
|
|
identifier.parent.object === identifier) {
|
|
const { parent } = identifier;
|
|
if (parent.parent?.type === "AssignmentExpression" && parent.parent.left === parent) {
|
|
context.report({
|
|
node: identifier,
|
|
messageId: "noWrite",
|
|
data: {
|
|
name: identifier.name,
|
|
},
|
|
});
|
|
}
|
|
else if (parent.property.type === "Identifier" &&
|
|
/^(?:initial|default|static)[A-Z]/.test(parent.property.name)) {
|
|
}
|
|
else {
|
|
handleTrackedScopes(identifier, declarationScope);
|
|
}
|
|
}
|
|
else if (identifier.parent?.type === "AssignmentExpression" ||
|
|
identifier.parent?.type === "VariableDeclarator") {
|
|
context.report({
|
|
node: identifier,
|
|
messageId: "untrackedReactive",
|
|
data: { name: identifier.name },
|
|
});
|
|
}
|
|
}
|
|
const { unnamedDerivedSignals } = currentScope();
|
|
if (unnamedDerivedSignals) {
|
|
for (const node of unnamedDerivedSignals) {
|
|
if (!currentScope().trackedScopes.find((trackedScope) => matchTrackedScope(trackedScope, node))) {
|
|
context.report({
|
|
loc: getFunctionHeadLocation(node, sourceCode),
|
|
messageId: "badUnnamedDerivedSignal",
|
|
});
|
|
}
|
|
}
|
|
}
|
|
scopeStack.pop();
|
|
};
|
|
const checkForSyncCallbacks = (node) => {
|
|
if (node.arguments.length === 1 &&
|
|
(0, utils_2.isFunctionNode)(node.arguments[0]) &&
|
|
!node.arguments[0].async) {
|
|
if (node.callee.type === "Identifier" &&
|
|
matchImport(["batch", "produce"], node.callee.name)) {
|
|
scopeStack.syncCallbacks.add(node.arguments[0]);
|
|
}
|
|
else if (node.callee.type === "MemberExpression" &&
|
|
!node.callee.computed &&
|
|
node.callee.object.type !== "ObjectExpression" &&
|
|
/^(?:forEach|map|flatMap|reduce|reduceRight|find|findIndex|filter|every|some)$/.test(node.callee.property.name)) {
|
|
scopeStack.syncCallbacks.add(node.arguments[0]);
|
|
}
|
|
}
|
|
if (node.callee.type === "Identifier") {
|
|
if (matchImport(["createSignal", "createStore"], node.callee.name) &&
|
|
node.parent?.type === "VariableDeclarator") {
|
|
const setter = getNthDestructuredVar(node.parent.id, 1, context.getScope());
|
|
if (setter) {
|
|
for (const reference of setter.references) {
|
|
const { identifier } = reference;
|
|
if (!reference.init &&
|
|
reference.isRead() &&
|
|
identifier.parent?.type === "CallExpression") {
|
|
for (const arg of identifier.parent.arguments) {
|
|
if ((0, utils_2.isFunctionNode)(arg) && !arg.async) {
|
|
scopeStack.syncCallbacks.add(arg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (matchImport(["mapArray", "indexArray"], node.callee.name)) {
|
|
const arg1 = node.arguments[1];
|
|
if ((0, utils_2.isFunctionNode)(arg1)) {
|
|
scopeStack.syncCallbacks.add(arg1);
|
|
}
|
|
}
|
|
}
|
|
if ((0, utils_2.isFunctionNode)(node.callee)) {
|
|
scopeStack.syncCallbacks.add(node.callee);
|
|
}
|
|
};
|
|
const checkForReactiveAssignment = (id, init) => {
|
|
init = (0, utils_2.ignoreTransparentWrappers)(init);
|
|
if (init.type === "CallExpression" && init.callee.type === "Identifier") {
|
|
const { callee } = init;
|
|
if (matchImport(["createSignal", "useTransition"], callee.name)) {
|
|
const signal = id && getNthDestructuredVar(id, 0, context.getScope());
|
|
if (signal) {
|
|
scopeStack.pushSignal(signal, currentScope().node);
|
|
}
|
|
else {
|
|
warnShouldDestructure(id ?? init, "first");
|
|
}
|
|
}
|
|
else if (matchImport(["createMemo", "createSelector"], callee.name)) {
|
|
const memo = id && getReturnedVar(id, context.getScope());
|
|
if (memo) {
|
|
scopeStack.pushSignal(memo, currentScope().node);
|
|
}
|
|
else {
|
|
warnShouldAssign(id ?? init);
|
|
}
|
|
}
|
|
else if (matchImport("createStore", callee.name)) {
|
|
const store = id && getNthDestructuredVar(id, 0, context.getScope());
|
|
if (store) {
|
|
scopeStack.pushProps(store, currentScope().node);
|
|
}
|
|
else {
|
|
warnShouldDestructure(id ?? init, "first");
|
|
}
|
|
}
|
|
else if (matchImport("mergeProps", callee.name)) {
|
|
const merged = id && getReturnedVar(id, context.getScope());
|
|
if (merged) {
|
|
scopeStack.pushProps(merged, currentScope().node);
|
|
}
|
|
else {
|
|
warnShouldAssign(id ?? init);
|
|
}
|
|
}
|
|
else if (matchImport("splitProps", callee.name)) {
|
|
if (id?.type === "ArrayPattern") {
|
|
const vars = id.elements
|
|
.map((_, i) => getNthDestructuredVar(id, i, context.getScope()))
|
|
.filter(Boolean);
|
|
if (vars.length === 0) {
|
|
warnShouldDestructure(id);
|
|
}
|
|
else {
|
|
vars.forEach((variable) => {
|
|
scopeStack.pushProps(variable, currentScope().node);
|
|
});
|
|
}
|
|
}
|
|
else {
|
|
const vars = id && getReturnedVar(id, context.getScope());
|
|
if (vars) {
|
|
scopeStack.pushProps(vars, currentScope().node);
|
|
}
|
|
}
|
|
}
|
|
else if (matchImport("createResource", callee.name)) {
|
|
const resourceReturn = id && getNthDestructuredVar(id, 0, context.getScope());
|
|
if (resourceReturn) {
|
|
scopeStack.pushProps(resourceReturn, currentScope().node);
|
|
}
|
|
}
|
|
else if (matchImport("createMutable", callee.name)) {
|
|
const mutable = id && getReturnedVar(id, context.getScope());
|
|
if (mutable) {
|
|
scopeStack.pushProps(mutable, currentScope().node);
|
|
}
|
|
}
|
|
else if (matchImport("mapArray", callee.name)) {
|
|
const arg1 = init.arguments[1];
|
|
if ((0, utils_2.isFunctionNode)(arg1) &&
|
|
arg1.params.length >= 2 &&
|
|
arg1.params[1].type === "Identifier") {
|
|
const indexSignal = findVariable(context.getScope(), arg1.params[1]);
|
|
if (indexSignal) {
|
|
scopeStack.pushSignal(indexSignal);
|
|
}
|
|
}
|
|
}
|
|
else if (matchImport("indexArray", callee.name)) {
|
|
const arg1 = init.arguments[1];
|
|
if ((0, utils_2.isFunctionNode)(arg1) &&
|
|
arg1.params.length >= 1 &&
|
|
arg1.params[0].type === "Identifier") {
|
|
const valueSignal = findVariable(context.getScope(), arg1.params[0]);
|
|
if (valueSignal) {
|
|
scopeStack.pushSignal(valueSignal);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
const checkForTrackedScopes = (node) => {
|
|
const pushTrackedScope = (node, expect) => {
|
|
currentScope().trackedScopes.push({ node, expect });
|
|
if (expect !== "called-function" && (0, utils_2.isFunctionNode)(node) && node.async) {
|
|
context.report({
|
|
node,
|
|
messageId: "noAsyncTrackedScope",
|
|
});
|
|
}
|
|
};
|
|
const permissivelyTrackNode = (node) => {
|
|
(0, estraverse_1.traverse)(node, {
|
|
enter(cn) {
|
|
const childNode = cn;
|
|
const traced = (0, utils_2.trace)(childNode, context.getScope());
|
|
if ((0, utils_2.isFunctionNode)(traced) ||
|
|
(traced.type === "Identifier" &&
|
|
traced.parent.type !== "MemberExpression" &&
|
|
!(traced.parent.type === "CallExpression" && traced.parent.callee === traced))) {
|
|
pushTrackedScope(childNode, "called-function");
|
|
this.skip();
|
|
}
|
|
},
|
|
});
|
|
};
|
|
if (node.type === "JSXExpressionContainer") {
|
|
if (node.parent?.type === "JSXAttribute" &&
|
|
/^on[:A-Z]/.test(sourceCode.getText(node.parent.name)) &&
|
|
node.parent.parent?.type === "JSXOpeningElement" &&
|
|
node.parent.parent.name.type === "JSXIdentifier" &&
|
|
(0, utils_2.isDOMElementName)(node.parent.parent.name.name)) {
|
|
pushTrackedScope(node.expression, "called-function");
|
|
}
|
|
else if (node.parent?.type === "JSXAttribute" &&
|
|
node.parent.name.type === "JSXNamespacedName" &&
|
|
node.parent.name.namespace.name === "use" &&
|
|
(0, utils_2.isFunctionNode)(node.expression)) {
|
|
pushTrackedScope(node.expression, "called-function");
|
|
}
|
|
else if (node.parent?.type === "JSXAttribute" &&
|
|
node.parent.name.name === "value" &&
|
|
node.parent.parent?.type === "JSXOpeningElement" &&
|
|
((node.parent.parent.name.type === "JSXIdentifier" &&
|
|
node.parent.parent.name.name.endsWith("Provider")) ||
|
|
(node.parent.parent.name.type === "JSXMemberExpression" &&
|
|
node.parent.parent.name.property.name === "Provider"))) {
|
|
}
|
|
else if (node.parent?.type === "JSXAttribute" &&
|
|
node.parent.name?.type === "JSXIdentifier" &&
|
|
/^static[A-Z]/.test(node.parent.name.name) &&
|
|
node.parent.parent?.type === "JSXOpeningElement" &&
|
|
node.parent.parent.name.type === "JSXIdentifier" &&
|
|
!(0, utils_2.isDOMElementName)(node.parent.parent.name.name)) {
|
|
}
|
|
else if (node.parent?.type === "JSXAttribute" &&
|
|
node.parent.name.name === "ref" &&
|
|
(0, utils_2.isFunctionNode)(node.expression)) {
|
|
pushTrackedScope(node.expression, "called-function");
|
|
}
|
|
else if ((0, utils_2.isJSXElementOrFragment)(node.parent) && (0, utils_2.isFunctionNode)(node.expression)) {
|
|
pushTrackedScope(node.expression, "function");
|
|
}
|
|
else {
|
|
pushTrackedScope(node.expression, "expression");
|
|
}
|
|
}
|
|
else if (node.type === "JSXSpreadAttribute") {
|
|
pushTrackedScope(node.argument, "expression");
|
|
}
|
|
else if (node.type === "NewExpression") {
|
|
const { callee, arguments: { 0: arg0 }, } = node;
|
|
if (callee.type === "Identifier" &&
|
|
arg0 &&
|
|
[
|
|
"IntersectionObserver",
|
|
"MutationObserver",
|
|
"PerformanceObserver",
|
|
"ReportingObserver",
|
|
"ResizeObserver",
|
|
].includes(callee.name)) {
|
|
pushTrackedScope(arg0, "called-function");
|
|
}
|
|
}
|
|
else if (node.type === "CallExpression") {
|
|
if (node.callee.type === "Identifier") {
|
|
const { callee, arguments: { 0: arg0, 1: arg1 }, } = node;
|
|
if (matchImport([
|
|
"createMemo",
|
|
"children",
|
|
"createEffect",
|
|
"createRenderEffect",
|
|
"createDeferred",
|
|
"createComputed",
|
|
"createSelector",
|
|
"untrack",
|
|
"mapArray",
|
|
"indexArray",
|
|
"observable",
|
|
], callee.name) ||
|
|
(matchImport("createResource", callee.name) && node.arguments.length >= 2)) {
|
|
pushTrackedScope(arg0, "function");
|
|
}
|
|
else if (matchImport(["onMount", "onCleanup", "onError"], callee.name) ||
|
|
[
|
|
"setInterval",
|
|
"setTimeout",
|
|
"setImmediate",
|
|
"requestAnimationFrame",
|
|
"requestIdleCallback",
|
|
].includes(callee.name)) {
|
|
pushTrackedScope(arg0, "called-function");
|
|
}
|
|
else if (matchImport("on", callee.name)) {
|
|
if (arg0) {
|
|
if (arg0.type === "ArrayExpression") {
|
|
arg0.elements.forEach((element) => {
|
|
if (element && element?.type !== "SpreadElement") {
|
|
pushTrackedScope(element, "function");
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
pushTrackedScope(arg0, "function");
|
|
}
|
|
}
|
|
if (arg1) {
|
|
pushTrackedScope(arg1, "called-function");
|
|
}
|
|
}
|
|
else if (matchImport("createStore", callee.name) && arg0?.type === "ObjectExpression") {
|
|
for (const property of arg0.properties) {
|
|
if (property.type === "Property" &&
|
|
property.kind === "get" &&
|
|
(0, utils_2.isFunctionNode)(property.value)) {
|
|
pushTrackedScope(property.value, "function");
|
|
}
|
|
}
|
|
}
|
|
else if (matchImport("runWithOwner", callee.name)) {
|
|
if (arg1) {
|
|
let isTrackedScope = true;
|
|
const owner = arg0.type === "Identifier" && findVariable(context.getScope(), arg0);
|
|
if (owner) {
|
|
const decl = owner.defs[0];
|
|
if (decl &&
|
|
decl.node.type === "VariableDeclarator" &&
|
|
decl.node.init?.type === "CallExpression" &&
|
|
decl.node.init.callee.type === "Identifier" &&
|
|
matchImport("getOwner", decl.node.init.callee.name)) {
|
|
const ownerFunction = (0, utils_2.findParent)(decl.node, utils_2.isProgramOrFunctionNode);
|
|
const scopeStackIndex = scopeStack.findIndex(({ node }) => ownerFunction === node);
|
|
if ((scopeStackIndex >= 1 &&
|
|
!scopeStack[scopeStackIndex - 1].trackedScopes.some((trackedScope) => trackedScope.expect === "function" && trackedScope.node === ownerFunction)) ||
|
|
scopeStackIndex === 0) {
|
|
isTrackedScope = false;
|
|
}
|
|
}
|
|
}
|
|
if (isTrackedScope) {
|
|
pushTrackedScope(arg1, "function");
|
|
}
|
|
}
|
|
}
|
|
else if (/^(?:use|create)[A-Z]/.test(callee.name) ||
|
|
options.customReactiveFunctions.includes(callee.name)) {
|
|
for (const arg of node.arguments) {
|
|
permissivelyTrackNode(arg);
|
|
}
|
|
}
|
|
}
|
|
else if (node.callee.type === "MemberExpression") {
|
|
const { property } = node.callee;
|
|
if (property.type === "Identifier" &&
|
|
property.name === "addEventListener" &&
|
|
node.arguments.length >= 2) {
|
|
pushTrackedScope(node.arguments[1], "called-function");
|
|
}
|
|
else if (property.type === "Identifier" &&
|
|
(/^(?:use|create)[A-Z]/.test(property.name) ||
|
|
options.customReactiveFunctions.includes(property.name))) {
|
|
for (const arg of node.arguments) {
|
|
permissivelyTrackNode(arg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (node.type === "VariableDeclarator") {
|
|
if (node.init?.type === "CallExpression" && node.init.callee.type === "Identifier") {
|
|
if (matchImport(["createReactive", "createReaction"], node.init.callee.name)) {
|
|
const track = getReturnedVar(node.id, context.getScope());
|
|
if (track) {
|
|
for (const reference of track.references) {
|
|
if (!reference.init &&
|
|
reference.isReadOnly() &&
|
|
reference.identifier.parent?.type === "CallExpression" &&
|
|
reference.identifier.parent.callee === reference.identifier) {
|
|
const arg0 = reference.identifier.parent.arguments[0];
|
|
arg0 && pushTrackedScope(arg0, "function");
|
|
}
|
|
}
|
|
}
|
|
if ((0, utils_2.isFunctionNode)(node.init.arguments[0])) {
|
|
pushTrackedScope(node.init.arguments[0], "called-function");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (node.type === "AssignmentExpression") {
|
|
if (node.left.type === "MemberExpression" &&
|
|
node.left.property.type === "Identifier" &&
|
|
(0, utils_2.isFunctionNode)(node.right) &&
|
|
/^on[a-z]+$/.test(node.left.property.name)) {
|
|
pushTrackedScope(node.right, "called-function");
|
|
}
|
|
}
|
|
else if (node.type === "TaggedTemplateExpression") {
|
|
for (const expression of node.quasi.expressions) {
|
|
if ((0, utils_2.isFunctionNode)(expression)) {
|
|
pushTrackedScope(expression, "called-function");
|
|
for (const param of expression.params) {
|
|
if (param.type === "Identifier" && (0, utils_2.isPropsByName)(param.name)) {
|
|
const variable = findVariable(context.getScope(), param);
|
|
if (variable)
|
|
scopeStack.pushProps(variable, currentScope().node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
return {
|
|
ImportDeclaration: handleImportDeclaration,
|
|
JSXExpressionContainer(node) {
|
|
checkForTrackedScopes(node);
|
|
},
|
|
JSXSpreadAttribute(node) {
|
|
checkForTrackedScopes(node);
|
|
},
|
|
CallExpression(node) {
|
|
checkForTrackedScopes(node);
|
|
checkForSyncCallbacks(node);
|
|
const parent = node.parent && (0, utils_2.ignoreTransparentWrappers)(node.parent, true);
|
|
if (parent?.type !== "AssignmentExpression" && parent?.type !== "VariableDeclarator") {
|
|
checkForReactiveAssignment(null, node);
|
|
}
|
|
},
|
|
NewExpression(node) {
|
|
checkForTrackedScopes(node);
|
|
},
|
|
VariableDeclarator(node) {
|
|
if (node.init) {
|
|
checkForReactiveAssignment(node.id, node.init);
|
|
checkForTrackedScopes(node);
|
|
}
|
|
},
|
|
AssignmentExpression(node) {
|
|
if (node.left.type !== "MemberExpression") {
|
|
checkForReactiveAssignment(node.left, node.right);
|
|
}
|
|
checkForTrackedScopes(node);
|
|
},
|
|
TaggedTemplateExpression(node) {
|
|
checkForTrackedScopes(node);
|
|
},
|
|
"JSXElement > JSXExpressionContainer > :function"(node) {
|
|
if ((0, utils_2.isFunctionNode)(node) &&
|
|
node.parent?.type === "JSXExpressionContainer" &&
|
|
node.parent.parent?.type === "JSXElement") {
|
|
const element = node.parent.parent;
|
|
if (element.openingElement.name.type === "JSXIdentifier") {
|
|
const tagName = element.openingElement.name.name;
|
|
if (matchImport("For", tagName) &&
|
|
node.params.length === 2 &&
|
|
node.params[1].type === "Identifier") {
|
|
const index = findVariable(context.getScope(), node.params[1]);
|
|
if (index) {
|
|
scopeStack.pushSignal(index, currentScope().node);
|
|
}
|
|
}
|
|
else if (matchImport("Index", tagName) &&
|
|
node.params.length >= 1 &&
|
|
node.params[0].type === "Identifier") {
|
|
const item = findVariable(context.getScope(), node.params[0]);
|
|
if (item) {
|
|
scopeStack.pushSignal(item, currentScope().node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
FunctionExpression: onFunctionEnter,
|
|
ArrowFunctionExpression: onFunctionEnter,
|
|
FunctionDeclaration: onFunctionEnter,
|
|
Program: onFunctionEnter,
|
|
"FunctionExpression:exit": onFunctionExit,
|
|
"ArrowFunctionExpression:exit": onFunctionExit,
|
|
"FunctionDeclaration:exit": onFunctionExit,
|
|
"Program:exit": onFunctionExit,
|
|
JSXElement() {
|
|
if (scopeStack.length) {
|
|
currentScope().hasJSX = true;
|
|
}
|
|
},
|
|
JSXFragment() {
|
|
if (scopeStack.length) {
|
|
currentScope().hasJSX = true;
|
|
}
|
|
},
|
|
};
|
|
},
|
|
});
|