rxjs-tslint-rules
Version:
TSLint rules for RxJS
213 lines (212 loc) • 9.84 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Rule = void 0;
var tslib_1 = require("tslib");
var Lint = tslib_1.__importStar(require("tslint"));
var tsutils = tslib_1.__importStar(require("tsutils"));
var ts = tslib_1.__importStar(require("typescript"));
var scope_walker_1 = require("../support/scope-walker");
var util_1 = require("../support/util");
var knownGlobalRegExp = /^(Array|BigInt|Date|Intl|JSON|Math|Number|Object|Promise|Proxy|Reflect|String|Symbol|console)$/;
var Rule = (function (_super) {
tslib_1.__extends(Rule, _super);
function Rule() {
return _super !== null && _super.apply(this, arguments) || this;
}
Rule.prototype.applyWithProgram = function (sourceFile, program) {
return this.applyWithWalker(new Walker(sourceFile, this.getOptions(), program));
};
Rule.metadata = {
description: "Disallows the use of variables/properties from unsafe/outer scopes in operator callbacks.",
options: {
properties: {
allowDo: { type: "boolean" },
allowMethods: { type: "boolean" },
allowParameters: { type: "boolean" },
allowProperties: { type: "boolean" },
allowSubscribe: { type: "boolean" },
allowTap: { type: "boolean" },
},
type: "object",
},
optionsDescription: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n An optional object with optional `allowDo`, `allowParameters` and `allowTap` properties all of which default to `true`.\n If the `allowDo` and `allowTap` options are `true`, the rule is not applied within `do` and `tap` operators respectively.\n If the `allowParameters` option is `true`, referencing function parameters from outer scopes is allowed.\n If the `allowMethods` option is `true`, calling methods via `this` is allowed.\n If the `allowProperties` option is `true`, accessing properties via `this` is allowed.\n If the `allowSubscribe` option is `true`, the rule is not applied within `subscribe` callbacks."], ["\n An optional object with optional \\`allowDo\\`, \\`allowParameters\\` and \\`allowTap\\` properties all of which default to \\`true\\`.\n If the \\`allowDo\\` and \\`allowTap\\` options are \\`true\\`, the rule is not applied within \\`do\\` and \\`tap\\` operators respectively.\n If the \\`allowParameters\\` option is \\`true\\`, referencing function parameters from outer scopes is allowed.\n If the \\`allowMethods\\` option is \\`true\\`, calling methods via \\`this\\` is allowed.\n If the \\`allowProperties\\` option is \\`true\\`, accessing properties via \\`this\\` is allowed.\n If the \\`allowSubscribe\\` option is \\`true\\`, the rule is not applied within \\`subscribe\\` callbacks."]))),
requiresTypeInfo: true,
ruleName: "rxjs-no-unsafe-scopes",
type: "functionality",
typescriptOnly: true,
};
Rule.FAILURE_STRING = "Unsafe scopes are forbidden";
return Rule;
}(Lint.Rules.TypedRule));
exports.Rule = Rule;
var Walker = (function (_super) {
tslib_1.__extends(Walker, _super);
function Walker(sourceFile, rawOptions, program) {
var _this = _super.call(this, sourceFile, rawOptions, program) || this;
_this.allowDo = true;
_this.allowMethods = true;
_this.allowParameters = true;
_this.allowProperties = false;
_this.allowSubscribe = true;
_this.allowTap = true;
var _a = tslib_1.__read(_this.getOptions(), 1), options = _a[0];
if (options) {
_this.allowDo =
options.allowDo !== undefined ? options.allowDo : _this.allowDo;
_this.allowMethods =
options.allowMethods !== undefined
? options.allowMethods
: _this.allowMethods;
_this.allowParameters =
options.allowParameters !== undefined
? options.allowParameters
: _this.allowParameters;
_this.allowProperties =
options.allowProperties !== undefined
? options.allowProperties
: _this.allowProperties;
_this.allowSubscribe =
options.allowSubscribe !== undefined
? options.allowSubscribe
: _this.allowSubscribe;
_this.allowTap =
options.allowTap !== undefined ? options.allowTap : _this.allowTap;
}
_this.knownNames = _this.allowSubscribe ? {} : { subscribe: true };
return _this;
}
Walker.prototype.visitNode = function (node) {
if (this.callbackStack.length) {
var validateNode = tsutils.isIdentifier(node) || util_1.isThis(node);
if (validateNode) {
var failureNode = this.isUnsafe(node);
if (failureNode) {
this.addFailureAtNode(failureNode, Rule.FAILURE_STRING);
}
}
}
_super.prototype.visitNode.call(this, node);
};
Walker.prototype.isUnsafe = function (node) {
var _a = this, callbackMap = _a.callbackMap, callbackStack = _a.callbackStack;
var leafCallback = callbackStack[callbackStack.length - 1];
var leafOperator = callbackMap.get(leafCallback);
var rootCallback = callbackStack[0];
var typeChecker = this.getTypeChecker();
if (this.allowDo && leafOperator === "do") {
return undefined;
}
if (this.allowTap && leafOperator === "tap") {
return undefined;
}
if (tsutils.isPropertyAccessExpression(node.parent)) {
if (!isPropertyAccessExpressionLeaf(node)) {
return undefined;
}
var declaration = util_1.findDeclaration(node, typeChecker);
if (!declaration) {
return undefined;
}
if (tsutils.hasModifier(declaration.modifiers, ts.SyntaxKind.ReadonlyKeyword)) {
return undefined;
}
if (tsutils.isTypeFlagSet(typeChecker.getTypeAtLocation(node), ts.TypeFlags.EnumLiteral)) {
return undefined;
}
var called = util_1.isWithinCallExpressionExpression(node);
var root = getPropertyAccessExpressionRoot(node.parent);
if (!root) {
return undefined;
}
if (util_1.isThis(root)) {
if (called) {
return this.allowMethods ? undefined : root;
}
else {
return this.allowProperties ? undefined : root;
}
}
var rootText = root.getText();
if (knownGlobalRegExp.test(rootText)) {
return undefined;
}
if (/^[A-Z]/.test(rootText)) {
if (called) {
return this.allowMethods ? undefined : root;
}
else {
return this.allowProperties ? undefined : root;
}
}
return this.isUnsafeRoot(root, rootCallback);
}
return this.isUnsafeRoot(node, rootCallback);
};
Walker.prototype.isUnsafeRoot = function (node, callback) {
var typeChecker = this.getTypeChecker();
if (ts.isQualifiedName(node.parent)) {
return undefined;
}
if (util_1.isInstanceofCtor(node)) {
return undefined;
}
var declaration = util_1.findDeclaration(node, typeChecker);
if (!declaration) {
return undefined;
}
if (isWithinClosure(declaration, callback)) {
return undefined;
}
if (this.allowParameters && util_1.isWithinParameterDeclaration(declaration)) {
return undefined;
}
if (tsutils.isCallExpression(node.parent) &&
node === node.parent.expression) {
return undefined;
}
if (tsutils.isTaggedTemplateExpression(node.parent) &&
node === node.parent.tag) {
return undefined;
}
if (tsutils.isNewExpression(node.parent)) {
return undefined;
}
if (tsutils.isTypeReferenceNode(node.parent)) {
return undefined;
}
if (util_1.isConstDeclaration(declaration)) {
return undefined;
}
if (tsutils.isImportSpecifier(declaration)) {
return undefined;
}
if (tsutils.isNamespaceImport(declaration)) {
return undefined;
}
return node;
};
return Walker;
}(scope_walker_1.ScopeWalker));
function getPropertyAccessExpressionRoot(node) {
var expression = node.expression;
while (tsutils.isPropertyAccessExpression(expression)) {
expression = expression.expression;
}
return util_1.isThis(expression) || tsutils.isIdentifier(expression)
? expression
: undefined;
}
function isWithinClosure(declaration, callback) {
return declaration.pos >= callback.pos && declaration.pos < callback.end;
}
function isPropertyAccessExpressionLeaf(node) {
var parent = node.parent;
if (!tsutils.isPropertyAccessExpression(parent)) {
return false;
}
if (node !== parent.name) {
return false;
}
return !tsutils.isPropertyAccessExpression(parent.parent);
}
var templateObject_1;