mirror of
https://github.com/vector-im/element-call.git
synced 2026-06-30 18:02:56 +00:00
The rule should only care about enclosing function/class scopes. For example if an ObservableScope is received as a parameter to a function and then simply used inside an 'if' block (technically a different scope), that's not a problem.
79 lines
2.6 KiB
JavaScript
79 lines
2.6 KiB
JavaScript
/*
|
|
Copyright 2026 Element Creations Ltd.
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
|
|
// These ObservableScope methods will not generally cause resource leaks even if
|
|
// called from a callback
|
|
const safeScopeMethods = new Set(["bind", "end"]);
|
|
|
|
/**
|
|
* Determines whether the variable with the given name is local to
|
|
* the enclosing function or class scope.
|
|
*/
|
|
function isLocal(name, scope) {
|
|
// If it is nowhere to be found in the "through" scope, it is local.
|
|
if (!scope.through.some(({ identifier }) => identifier.name === name))
|
|
return true;
|
|
if (scope.type === "function" || scope.type === "class") return false;
|
|
// If this is something other than a function or class scope, check its outer
|
|
// scope.
|
|
return !scope.upper || isLocal(name, scope.upper);
|
|
}
|
|
|
|
const rule = ESLintUtils.RuleCreator(
|
|
() => "https://github.com/element-hq/element-call",
|
|
)({
|
|
name: "no-observablescope-leak",
|
|
meta: {
|
|
type: "problem",
|
|
docs: {
|
|
description:
|
|
"Require referenced ObservableScopes to be defined in the very same scope to avoid resource leaks.",
|
|
},
|
|
messages: {
|
|
scopeLeak:
|
|
"Do not reference ObservableScopes defined in an outer scope; this may create resource leaks.",
|
|
},
|
|
schema: [],
|
|
},
|
|
create(context) {
|
|
return {
|
|
Identifier(node) {
|
|
const scope = context.sourceCode.getScope(node);
|
|
if (
|
|
// Is this a reference to a variable defined in an outer scope?
|
|
!isLocal(node.name, scope) &&
|
|
// Exclude calls to "safe" ObservableScope methods
|
|
node.parent?.type === "MemberExpression" &&
|
|
node.parent.object === node &&
|
|
node.parent.property.type === "Identifier" &&
|
|
!safeScopeMethods.has(node.parent.property.name) &&
|
|
/(^s|S)cope$/.test(node.name)
|
|
) {
|
|
// TODO: Once oxlint supports lint rules that rely on TypeScript type-awareness,
|
|
// Verify that the variable is actually of type ObservableScope rather than just
|
|
// checking its name. This is expensive so we should do this last.
|
|
//
|
|
// const services = ESLintUtils.getParserServices(context);
|
|
// const type = services.getTypeAtLocation(node);
|
|
// if (type.symbol?.name === "ObservableScope") { ... }
|
|
|
|
// This ObservableScope method call may be causing resource leaks.
|
|
context.report({
|
|
messageId: "scopeLeak",
|
|
loc: node.loc,
|
|
node,
|
|
});
|
|
}
|
|
},
|
|
};
|
|
},
|
|
});
|
|
|
|
export default rule;
|