rxjs-tslint-rules
Version:
TSLint rules for RxJS
164 lines (163 loc) • 9.28 kB
JavaScript
;
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;