UNPKG

rxjs-tslint-rules

Version:
213 lines (212 loc) 9.84 kB
"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;