UNPKG

rxjs-tslint-rules

Version:
182 lines (181 loc) 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Rule = void 0; var tslib_1 = require("tslib"); var tsquery_1 = require("@phenomnomnominal/tsquery"); var Lint = tslib_1.__importStar(require("tslint")); var tsutils = tslib_1.__importStar(require("tsutils")); var ts = tslib_1.__importStar(require("typescript")); var peer = tslib_1.__importStar(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 _a = tslib_1.__read(this.getOptions().ruleArguments, 1), options = _a[0]; var _b = options || {}, _c = _b.alias, alias = _c === void 0 ? [] : _c, _d = _b.checkDestroy, checkDestroy = _d === void 0 ? alias.length === 0 : _d, _e = _b.checkDecorators, checkDecorators = _e === void 0 ? ["Component"] : _e; var decoratorQuery = "/^(" + checkDecorators.join("|") + ")$/"; var classDeclarations = tsquery_1.tsquery(sourceFile, "ClassDeclaration:has(Decorator[expression.expression.name=" + decoratorQuery + "])"); classDeclarations.forEach(function (classDeclaration) { failures.push.apply(failures, tslib_1.__spreadArray([], tslib_1.__read(_this.checkClassDeclaration(sourceFile, program, { alias: alias, checkDestroy: checkDestroy, checkDecorators: checkDecorators }, classDeclaration)))); }); return failures; }; Rule.prototype.checkClassDeclaration = function (sourceFile, program, options, classDeclaration) { var _this = this; var failures = []; var typeChecker = program.getTypeChecker(); var destroySubjectNamesBySubscribes = new Map(); var subscribePropertyAccessExpressions = tsquery_1.tsquery(classDeclaration, "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.__spreadArray([], tslib_1.__read(_this.checkSubscribe(sourceFile, options, propertyAccessExpression, function (name) { var names = destroySubjectNamesBySubscribes.get(propertyAccessExpression.name); if (!names) { names = new Set(); destroySubjectNamesBySubscribes.set(propertyAccessExpression.name, names); } names.add(name); })))); } }); if (options.checkDestroy && destroySubjectNamesBySubscribes.size > 0) { failures.push.apply(failures, tslib_1.__spreadArray([], tslib_1.__read(this.checkNgOnDestroy(sourceFile, classDeclaration, destroySubjectNamesBySubscribes)))); } return failures; }; Rule.prototype.checkSubscribe = function (sourceFile, options, 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 _a = _this.checkOperator(options, pipedOperator), found = _a.found, name_1 = _a.name; takeUntilFound = takeUntilFound || found; if (name_1) { addDestroySubjectName(name_1); } } }); } 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 (options, operator) { if (!tsutils.isIdentifier(operator.expression)) { return { found: false }; } if (operator.expression.text === "takeUntil" || options.alias.includes(operator.expression.text)) { var _a = tslib_1.__read(operator.arguments, 1), arg = _a[0]; if (arg) { if (ts.isPropertyAccessExpression(arg) && util_1.isThis(arg.expression)) { return { found: true, name: arg.name.text }; } else if (arg && ts.isIdentifier(arg)) { return { found: true, name: arg.text }; } } if (!options.checkDestroy) { return { found: true }; } } return { found: false }; }; 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.__spreadArray(tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(_this.checkDestroySubjectDeclaration(sourceFile, classDeclaration, name))), tslib_1.__read(_this.checkDestroySubjectMethodInvocation(sourceFile, ngOnDestroyMethod, name, "next"))), tslib_1.__read(_this.checkDestroySubjectMethodInvocation(sourceFile, ngOnDestroyMethod, name, "complete"))), report: false, }); }); destroySubjectNamesBySubscribes.forEach(function (names) { var report = tslib_1.__spreadArray([], tslib_1.__read(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.__spreadArray([], tslib_1.__read(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_2 = classDeclaration.name; failures.push(new Lint.RuleFailure(sourceFile, name_2.getStart(), name_2.getStart() + name_2.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(["\n Enforces the application of the takeUntil operator\n when calling subscribe within Angular components\n (and, optionally, within services, directives, and pipes)."], ["\n Enforces the application of the takeUntil operator\n when calling subscribe within Angular components\n (and, optionally, within services, directives, and pipes)."]))), options: { properties: { alias: { type: "array", items: { type: "string" } }, checkDecorators: { type: "array", items: { type: "string" } }, checkDestroy: { type: "boolean" }, }, type: "object", }, optionsDescription: Lint.Utils.dedent(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["\n An optional object with optional `alias`, `checkDecorators` and `checkDestroy` properties.\n The `alias` property is an array containing the names of operators that aliases for `takeUntil`.\n The `checkDecorators` property is an array containing the names of the decorators that determine whether or not a class is checked.\n The `checkDestroy` property is a boolean that determines whether or not a `Subject`-based `ngOnDestroy` must be implemented."], ["\n An optional object with optional \\`alias\\`, \\`checkDecorators\\` and \\`checkDestroy\\` properties.\n The \\`alias\\` property is an array containing the names of operators that aliases for \\`takeUntil\\`.\n The \\`checkDecorators\\` property is an array containing the names of the decorators that determine whether or not a class is checked.\n The \\`checkDestroy\\` property is a boolean that determines whether or not a \\`Subject\\`-based \\`ngOnDestroy\\` must be implemented."]))), 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, templateObject_2;