UNPKG

rxjs-tslint-rules

Version:
164 lines (163 loc) 9.28 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 tsutils = require("tsutils"); 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 componentClassDeclarations = tsquery_1.tsquery(sourceFile, "ClassDeclaration:has(Decorator[expression.expression.name='Component'])"); componentClassDeclarations.forEach(function (componentClassDeclaration) { failures.push.apply(failures, tslib_1.__spread(_this.checkComponentClassDeclaration(sourceFile, program, componentClassDeclaration))); }); return failures; }; Rule.prototype.checkComponentClassDeclaration = function (sourceFile, program, componentClassDeclaration) { var _this = this; var failures = []; var typeChecker = program.getTypeChecker(); var destroySubjectNamesBySubscribes = new Map(); var subscribePropertyAccessExpressions = tsquery_1.tsquery(componentClassDeclaration, "CallExpression > PropertyAccessExpression[name.name=\"subscribe\"]"); subscribePropertyAccessExpressions.forEach(function (propertyAccessExpression) { var type = typeChecker.getTypeAtLocation(propertyAccessExpression.expression); if (util_1.couldBeType(type, "Observable")) { failures.push.apply(failures, tslib_1.__spread(_this.checkSubscribe(sourceFile, propertyAccessExpression, function (name) { var names = destroySubjectNamesBySubscribes.get(propertyAccessExpression.name); if (!names) { names = new Set(); destroySubjectNamesBySubscribes.set(propertyAccessExpression.name, names); } names.add(name); }))); } }); if (destroySubjectNamesBySubscribes.size > 0) { failures.push.apply(failures, tslib_1.__spread(this.checkNgOnDestroy(sourceFile, componentClassDeclaration, destroySubjectNamesBySubscribes))); } return failures; }; Rule.prototype.checkSubscribe = function (sourceFile, subscribe, addDestroySubjectName) { var _this = this; var failures = []; var subscribeContext = subscribe.expression; var takeUntilFound = false; if (tsutils.isCallExpression(subscribeContext) && tsutils.isPropertyAccessExpression(subscribeContext.expression) && subscribeContext.expression.name.text === "pipe") { var pipedOperators = subscribeContext.arguments; pipedOperators.forEach(function (pipedOperator) { if (tsutils.isCallExpression(pipedOperator)) { var destroySubjectName = _this.checkOperator(pipedOperator); if (destroySubjectName) { takeUntilFound = true; addDestroySubjectName(destroySubjectName); } } }); } if (!takeUntilFound) { failures.push(new Lint.RuleFailure(sourceFile, subscribe.name.getStart(), subscribe.name.getStart() + subscribe.name.getWidth(), Rule.FAILURE_STRING_NO_TAKEUNTIL, this.ruleName)); } return failures; }; Rule.prototype.checkOperator = function (operator) { if (tsutils.isIdentifier(operator.expression) && operator.expression.text === "takeUntil") { var _a = tslib_1.__read(operator.arguments, 1), arg = _a[0]; if (ts.isPropertyAccessExpression(arg) && util_1.isThis(arg.expression)) { return arg.name.text; } else if (ts.isIdentifier(arg)) { return arg.text; } } return undefined; }; Rule.prototype.checkNgOnDestroy = function (sourceFile, classDeclaration, destroySubjectNamesBySubscribes) { var _this = this; var failures = []; var ngOnDestroyMethod = classDeclaration.members.find(function (member) { return member.name && member.name.getText() === "ngOnDestroy"; }); if (ngOnDestroyMethod) { var destroySubjectNames_1 = new Set(); destroySubjectNamesBySubscribes.forEach(function (names) { return names.forEach(function (name) { return destroySubjectNames_1.add(name); }); }); var destroySubjectResultsByName_1 = new Map(); destroySubjectNames_1.forEach(function (name) { destroySubjectResultsByName_1.set(name, { failures: tslib_1.__spread(_this.checkDestroySubjectDeclaration(sourceFile, classDeclaration, name), _this.checkDestroySubjectMethodInvocation(sourceFile, ngOnDestroyMethod, name, "next"), _this.checkDestroySubjectMethodInvocation(sourceFile, ngOnDestroyMethod, name, "complete")), report: false }); }); destroySubjectNamesBySubscribes.forEach(function (names) { var report = tslib_1.__spread(names).every(function (name) { return destroySubjectResultsByName_1.get(name).failures.length > 0; }); if (report) { names.forEach(function (name) { return (destroySubjectResultsByName_1.get(name).report = true); }); } }); destroySubjectResultsByName_1.forEach(function (result) { if (result.report) { failures.push.apply(failures, tslib_1.__spread(result.failures)); } }); } else { failures.push(new Lint.RuleFailure(sourceFile, classDeclaration.name.getStart(), classDeclaration.name.getStart() + classDeclaration.name.getWidth(), Rule.FAILURE_STRING_NO_DESTROY, this.ruleName)); } return failures; }; Rule.prototype.checkDestroySubjectDeclaration = function (sourceFile, classDeclaration, destroySubjectName) { var failures = []; var propertyDeclarations = tsquery_1.tsquery(classDeclaration, "PropertyDeclaration[name.text=\"" + destroySubjectName + "\"]"); if (propertyDeclarations.length === 0) { var name_1 = classDeclaration.name; failures.push(new Lint.RuleFailure(sourceFile, name_1.getStart(), name_1.getStart() + name_1.getWidth(), Rule.FAILURE_MESSAGE_NOT_DECLARED(destroySubjectName), this.ruleName)); } return failures; }; Rule.prototype.checkDestroySubjectMethodInvocation = function (sourceFile, ngOnDestroyMethod, destroySubjectName, methodName) { var failures = []; var destroySubjectMethodInvocations = tsquery_1.tsquery(ngOnDestroyMethod, "CallExpression > PropertyAccessExpression[name.name=\"" + methodName + "\"]"); if (!destroySubjectMethodInvocations.some(function (methodInvocation) { return (tsutils.isPropertyAccessExpression(methodInvocation.expression) && util_1.isThis(methodInvocation.expression.expression) && methodInvocation.expression.name.text === destroySubjectName) || (tsutils.isIdentifier(methodInvocation.expression) && methodInvocation.expression.text === destroySubjectName); })) { failures.push(new Lint.RuleFailure(sourceFile, ngOnDestroyMethod.name.getStart(), ngOnDestroyMethod.name.getStart() + ngOnDestroyMethod.name.getWidth(), Rule.FAILURE_MESSAGE_NOT_CALLED(destroySubjectName, methodName), this.ruleName)); } return failures; }; Rule.metadata = { deprecationMessage: peer.v5 ? peer.v5NotSupportedMessage : undefined, description: Lint.Utils .dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["Enforces the application of the takeUntil operator\n when calling subscribe within an Angular component."], ["Enforces the application of the takeUntil operator\n when calling subscribe within an Angular component."]))), options: null, optionsDescription: "", requiresTypeInfo: true, ruleName: "rxjs-prefer-angular-takeuntil", type: "functionality", typescriptOnly: true }; Rule.FAILURE_STRING_NO_TAKEUNTIL = "Subscribing without takeUntil is forbidden"; Rule.FAILURE_STRING_NO_DESTROY = "ngOnDestroy not implemented"; Rule.FAILURE_MESSAGE_NOT_CALLED = function (name, method) { return "'" + name + "." + method + "()' not called"; }; Rule.FAILURE_MESSAGE_NOT_DECLARED = function (name) { return "Subject '" + name + "' not a class property"; }; return Rule; }(Lint.Rules.TypedRule)); exports.Rule = Rule; var templateObject_1;