UNPKG

rune-sdk

Version:

Build a multiplayer game played by millions! Your game runs inside the Rune app with 10 million installs across [iOS](https://apps.apple.com/app/rune-games-and-voice-chat/id1450358364) and [Android](https://play.google.com/store/apps/details?id=ai.rune.ti

190 lines (189 loc) 7.38 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.create = exports.meta = void 0; exports.meta = { type: "problem", docs: { description: "Only allow mutating and assigning variables in the current function scope", recommended: true, }, schema: [], messages: { noParentScopeMutation: "Variables from parent scope may not be mutated.", }, }; var create = function (context) { var sourceCode = context.sourceCode; var findVariable = function (name, scope) { var variable = scope.variables.find(function (v) { return v.name === name; }); if (variable) { return variable; } else if (scope.upper) { return findVariable(name, scope.upper); } return null; }; var resolveVariable = function (variable) { var _a, _b; if (!variable) { return null; } var def = variable.defs[0]; if (((_a = def === null || def === void 0 ? void 0 : def.node) === null || _a === void 0 ? void 0 : _a.type) === "VariableDeclarator" && ((_b = def.node.init) === null || _b === void 0 ? void 0 : _b.type) === "Identifier") { var ref = findVariable(def.node.init.name, variable.scope); if (ref !== variable) { return resolveVariable(ref); } return ref; } return variable; }; var findFunctionScope = function (scope) { if (scope.type === "function" || scope.type === "global") { return scope; } else if (scope.upper) { return findFunctionScope(scope.upper); } throw new Error("Unexpected scope"); }; var isRuntimeGlobalVariable = function (reportNode, variable) { return variable ? (variable.scope.type === "global" || variable.scope.type === "module") && (variable.identifiers.length === 0 || findFunctionScope(variable.scope) !== findFunctionScope(sourceCode.getScope(reportNode))) : false; }; var checkLocalVariableMutation = function (reportNode, variableName) { var scope = sourceCode.getScope(reportNode); var variable = resolveVariable(findVariable(variableName, scope)); if (variable && // undefined variables are handled by builtin rule isRuntimeGlobalVariable(reportNode, variable)) { context.report({ node: reportNode, messageId: "noParentScopeMutation", }); } }; var checkPatternVariableName = function (sourceNode, node) { switch (node.type) { case "Identifier": { checkLocalVariableMutation(node, node.name); break; } case "MemberExpression": { switch (node.object.type) { case "Identifier": case "MemberExpression": checkPatternVariableName(sourceNode, node.object); } break; } case "ArrayPattern": for (var _i = 0, _a = node.elements; _i < _a.length; _i++) { var element = _a[_i]; if (element) { checkPatternVariableName(sourceNode, element); } } break; case "ObjectPattern": for (var _b = 0, _c = node.properties; _b < _c.length; _b++) { var property = _c[_b]; checkPatternVariableName(sourceNode, property.type === "Property" ? property.value : property.argument); } break; case "RestElement": checkPatternVariableName(sourceNode, node.argument); break; case "AssignmentPattern": checkPatternVariableName(sourceNode, node.left); break; } }; return { UnaryExpression: function (node) { if (node.operator === "delete") { switch (node.argument.type) { case "Identifier": case "MemberExpression": checkPatternVariableName(node, node.argument); break; } } }, UpdateExpression: function (node) { if (node.argument.type === "MemberExpression" && node.argument.object.type === "Identifier") { checkLocalVariableMutation(node, node.argument.object.name); } else if (node.argument.type === "Identifier") { checkLocalVariableMutation(node, node.argument.name); } }, AssignmentExpression: function (node) { /* * Assigning to local variable is ok, but global is not. * OK: local = 'value' * NOK: global = 'value' */ checkPatternVariableName(node, node.left); }, MemberExpression: function (node) { /* * Prevent common mutations. This is not an exhaustive list. * NOK: array.push('value') */ if (node.object.type === "Identifier" && node.property.type === "Identifier") { switch (node.property.name) { case "push": case "pop": case "unshift": case "splice": case "fill": case "delete": case "clear": case "set": case "add": case "remove": checkLocalVariableMutation(node.parent, node.object.name); break; } } }, Identifier: function (node) { switch (node.parent.type) { case "MemberExpression": { /* * Allow mutating local objects with Object functions but not globals. * OK: Object.assign(local, { key: 'value' }) * NOK: Object.assign(global, { key: 'value' }) */ if ((node.name === "assign" || node.name === "defineProperty" || node.name === "defineProperties" || node.name === "__defineGetter__" || node.name === "__defineSetter__" || node.name === "setPrototypeOf") && node.parent.object.type === "Identifier" && node.parent.object.name === "Object" && node.parent.parent.type === "CallExpression") { var arg = node.parent.parent.arguments[0]; switch (arg.type) { case "Identifier": case "MemberExpression": checkPatternVariableName(node, arg); } } break; } } }, }; }; exports.create = create;