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
JavaScript
;
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;