UNPKG

rxjs-tslint-rules

Version:
171 lines (170 loc) 7.82 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var tsquery_1 = require("@phenomnomnominal/tsquery"); var Lint = require("tslint"); var ts = require("typescript"); var peer = require("../support/peer"); var util_1 = require("../support/util"); 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) { var _this = this; var failures = []; var typeChecker = program.getTypeChecker(); var classDeclarations = tsquery_1.tsquery(sourceFile, "ClassDeclaration:has(Decorator[expression.expression.name=\"Component\"])"); classDeclarations.forEach(function (classDeclaration) { var subscriptions = new Set(); var callExpressions = tsquery_1.tsquery(classDeclaration, "CallExpression[expression.name.text=\"subscribe\"]"); callExpressions.forEach(function (callExpression) { var expression = callExpression.expression; if (ts.isPropertyAccessExpression(expression)) { var object = expression.expression, property = expression.name; var type = typeChecker.getTypeAtLocation(object); if (!util_1.couldBeType(type, "Observable")) { return; } if (isComposed(callExpression, typeChecker, subscriptions)) { return; } failures.push(new Lint.RuleFailure(sourceFile, property.getStart(), property.getStart() + property.getWidth(), Rule.FAILURE_STRING_NOT_COMPOSED, _this.ruleName)); } }); if (callExpressions.length === 0) { return; } var methodDeclarations = tsquery_1.tsquery(classDeclaration, "MethodDeclaration[name.text=\"ngOnDestroy\"]"); if (methodDeclarations.length === 0) { var name_1 = classDeclaration.name; failures.push(new Lint.RuleFailure(sourceFile, name_1.getStart(), name_1.getStart() + name_1.getWidth(), Rule.FAILURE_STRING_NOT_IMPLEMENTED, _this.ruleName)); return; } var _a = tslib_1.__read(methodDeclarations, 1), methodDeclaration = _a[0]; subscriptions.forEach(function (subscription) { var propertyDeclarations = tsquery_1.tsquery(classDeclaration, "PropertyDeclaration[name.text=\"" + subscription + "\"]"); if (propertyDeclarations.length === 0) { var name_2 = classDeclaration.name; failures.push(new Lint.RuleFailure(sourceFile, name_2.getStart(), name_2.getStart() + name_2.getWidth(), Rule.FAILURE_MESSAGE_NOT_DECLARED(subscription), _this.ruleName)); return; } var _a = tslib_1.__read(propertyDeclarations, 1), propertyDeclaration = _a[0]; var callExpressions = tsquery_1.tsquery(methodDeclaration, "CallExpression[expression.expression.name.text=\"" + subscription + "\"][expression.name.text=\"unsubscribe\"], CallExpression[expression.expression.text=\"" + subscription + "\"][expression.name.text=\"unsubscribe\"]"); if (callExpressions.length === 0) { var name_3 = propertyDeclaration.name; failures.push(new Lint.RuleFailure(sourceFile, name_3.getStart(), name_3.getStart() + name_3.getWidth(), Rule.FAILURE_STRING_NOT_UNSUBSCRIBED, _this.ruleName)); } }); }); return failures; }; Rule.metadata = { deprecationMessage: peer.v5 ? peer.v5NotSupportedMessage : undefined, description: "Enforces the composition of subscriptions within an Angular component.", options: null, optionsDescription: "Not configurable.", requiresTypeInfo: true, ruleName: "rxjs-prefer-angular-composition", type: "style", typescriptOnly: true }; Rule.FAILURE_STRING_NOT_COMPOSED = "Subscription not composed"; Rule.FAILURE_STRING_NOT_IMPLEMENTED = "ngOnDestroy not implemented"; Rule.FAILURE_STRING_NOT_UNSUBSCRIBED = "Composed subscription not unsubscribed"; Rule.FAILURE_MESSAGE_NOT_DECLARED = function (name) { return "Composed subscription '" + name + "' not a class property"; }; return Rule; }(Lint.Rules.TypedRule)); exports.Rule = Rule; function getBlock(node) { var parent = node.parent; while (parent && !ts.isBlock(parent)) { parent = parent.parent; } return parent; } function getCalledName(node) { if (ts.isIdentifier(node)) { return node; } else if (ts.isPropertyAccessExpression(node) && util_1.isThis(node.expression)) { return node.name; } return undefined; } function getCalledObject(callExpression) { var expression = callExpression.expression; if (ts.isPropertyAccessExpression(expression)) { return expression.expression; } return undefined; } function isComposed(callExpression, typeChecker, subscriptions) { var parent = callExpression.parent; if (ts.isCallExpression(parent) && ts.isPropertyAccessExpression(parent.expression)) { var _a = parent.expression, object = _a.expression, property = _a.name; var text = property.text; if (text !== "add") { return false; } if (!util_1.couldBeType(typeChecker.getTypeAtLocation(object), "Subscription")) { return false; } var name_4 = getCalledName(object); if (!name_4) { return false; } subscriptions.add(name_4.text); return true; } if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) { return isVariableComposed(parent.name, typeChecker, subscriptions); } if (ts.isBinaryExpression(parent) && ts.isIdentifier(parent.left) && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) { return isVariableComposed(parent.left, typeChecker, subscriptions); } return false; } function isVariableComposed(identifier, typeChecker, subscriptions) { var text = identifier.text; var block = getBlock(identifier); if (block) { var callExpressions = tsquery_1.tsquery(block, "CallExpression[expression.name.text=\"add\"] > Identifier[text=\"" + text + "\"]") .map(function (identifier) { return identifier.parent; }) .filter(function (callExpression) { if (callExpression.end < identifier.pos) { return false; } var object = getCalledObject(callExpression); if (!object) { return false; } if (!util_1.couldBeType(typeChecker.getTypeAtLocation(object), "Subscription")) { return false; } return true; }); if (callExpressions.length === 0) { return false; } var _a = tslib_1.__read(callExpressions, 1), callExpression = _a[0]; var expression = callExpression.expression; if (!ts.isPropertyAccessExpression(expression)) { return false; } var object = expression.expression; var name_5 = getCalledName(object); if (!name_5) { return false; } subscriptions.add(name_5.text); return true; } return false; }