rxjs-tslint-rules
Version:
TSLint rules for RxJS
200 lines (199 loc) • 9.29 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Walker = exports.Rule = void 0;
var tslib_1 = require("tslib");
var decamelize_1 = tslib_1.__importDefault(require("decamelize"));
var Lint = tslib_1.__importStar(require("tslint"));
var tsutils = tslib_1.__importStar(require("tsutils"));
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) {
return this.applyWithWalker(new Walker(sourceFile, this.getOptions(), program));
};
Rule.metadata = {
description: "Disallows unsafe switchMap usage in effects and epics.",
options: {
properties: {
allow: {
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } },
],
},
disallow: {
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } },
],
},
observable: {
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } },
],
},
},
type: "object",
},
optionsDescription: Lint.Utils.dedent(templateObject_1 || (templateObject_1 = tslib_1.__makeTemplateObject(["\n An optional object with optional `allow`, `disallow` and `observable` properties.\n The properties can be specifed as regular expression strings or as arrays of words.\n The `allow` or `disallow` properties are mutually exclusive. Whether or not\n `switchMap` is allowed will depend upon the matching of action types with `allow` or `disallow`.\n The `observable` property is used to identify the action observables from which effects and epics are composed."], ["\n An optional object with optional \\`allow\\`, \\`disallow\\` and \\`observable\\` properties.\n The properties can be specifed as regular expression strings or as arrays of words.\n The \\`allow\\` or \\`disallow\\` properties are mutually exclusive. Whether or not\n \\`switchMap\\` is allowed will depend upon the matching of action types with \\`allow\\` or \\`disallow\\`.\n The \\`observable\\` property is used to identify the action observables from which effects and epics are composed."]))),
requiresTypeInfo: true,
ruleName: "rxjs-no-unsafe-switchmap",
type: "functionality",
typescriptOnly: true,
};
Rule.FAILURE_STRING = "Unsafe switchMap usage in effects and epics is 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;
var _a = tslib_1.__read(_this.getOptions(), 1), options = _a[0];
if (options && (options.allow || options.disallow)) {
_this.allowRegExp = Walker.createRegExp(options.allow);
_this.disallowRegExp = Walker.createRegExp(options.disallow);
_this.observableRegExp = new RegExp(options.observable || Walker.DEFAULT_OBSERVABLE, "i");
}
else {
_this.allowRegExp = null;
_this.disallowRegExp = Walker.createRegExp(Walker.DEFAULT_DISALLOW);
_this.observableRegExp = new RegExp(Walker.DEFAULT_OBSERVABLE, "i");
}
return _this;
}
Walker.createRegExp = function (value) {
if (!value || !value.length) {
return null;
}
var flags = "i";
if (typeof value === "string") {
return new RegExp(value, flags);
}
var words = value;
var joined = words
.map(function (word) { return String.raw(templateObject_2 || (templateObject_2 = tslib_1.__makeTemplateObject(["(\b|_)", "(\b|_)"], ["(\\b|_)", "(\\b|_)"])), word); })
.join("|");
return new RegExp("(" + joined + ")", flags);
};
Walker.prototype.visitCallExpression = function (node) {
var propertyAccessExpression = node.expression;
if (tsutils.isPropertyAccessExpression(propertyAccessExpression)) {
var observableExpression = propertyAccessExpression.expression;
var observableIdentifier = undefined;
if (tsutils.isIdentifier(observableExpression)) {
observableIdentifier = observableExpression;
}
else if (tsutils.isPropertyAccessExpression(observableExpression)) {
observableIdentifier = observableExpression.name;
}
if (observableIdentifier &&
this.observableRegExp.test(observableIdentifier.getText())) {
var propertyName = propertyAccessExpression.name.getText();
var typeChecker = this.getTypeChecker();
var type = typeChecker.getTypeAtLocation(observableExpression);
if (util_1.isReferenceType(type) &&
Walker.METHODS_REGEXP.test(propertyName) &&
util_1.couldBeType(type.target, "Observable")) {
switch (propertyName) {
case "ofType":
this.walkPatchedTypes(node);
break;
case "pipe":
this.walkPipedTypes(node);
break;
default:
break;
}
}
}
}
_super.prototype.visitCallExpression.call(this, node);
};
Walker.prototype.shouldDisallow = function (args) {
var _this = this;
var names = args.map(function (arg) { return decamelize_1.default(arg.getText()); });
if (this.allowRegExp) {
return !names.every(function (name) { return _this.allowRegExp.test(name); });
}
if (this.disallowRegExp) {
return names.some(function (name) { return _this.disallowRegExp.test(name); });
}
return false;
};
Walker.prototype.walkPatchedOperators = function (node) {
var name = undefined;
for (var parent_1 = node.parent; parent_1; parent_1 = parent_1.parent) {
if (tsutils.isCallExpression(parent_1)) {
if (name) {
switch (name.getText()) {
case "pipe":
this.walkPipedOperators(parent_1);
break;
case "switchMap":
this.addFailureAtNode(name, Rule.FAILURE_STRING);
break;
default:
break;
}
}
}
else if (tsutils.isPropertyAccessExpression(parent_1)) {
name = parent_1.name;
}
else {
break;
}
}
};
Walker.prototype.walkPatchedTypes = function (node) {
if (this.shouldDisallow(node.arguments)) {
this.walkPatchedOperators(node);
}
};
Walker.prototype.walkPipedOperators = function (node) {
var _this = this;
node.arguments.forEach(function (arg) {
if (tsutils.isCallExpression(arg)) {
var expression = arg.expression;
if (tsutils.isIdentifier(expression) &&
expression.getText() === "switchMap") {
_this.addFailureAtNode(expression, Rule.FAILURE_STRING);
}
}
});
};
Walker.prototype.walkPipedTypes = function (node) {
var _this = this;
node.arguments.forEach(function (arg) {
if (tsutils.isCallExpression(arg)) {
var expression = arg.expression;
if (tsutils.isIdentifier(expression) &&
expression.getText() === "ofType") {
if (_this.shouldDisallow(arg.arguments)) {
_this.walkPipedOperators(node);
}
}
}
});
};
Walker.METHODS_REGEXP = /(ofType|pipe)/;
Walker.DEFAULT_DISALLOW = [
"add",
"create",
"delete",
"post",
"put",
"remove",
"set",
"update",
];
Walker.DEFAULT_OBSERVABLE = String.raw(templateObject_3 || (templateObject_3 = tslib_1.__makeTemplateObject(["action(s|$)?"], ["action(s|\\$)?"])));
return Walker;
}(Lint.ProgramAwareRuleWalker));
exports.Walker = Walker;
var templateObject_1, templateObject_2, templateObject_3;