tslint-clean-code
Version:
TSLint rules for enforcing Clean Code
226 lines • 9.35 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var ts = require("typescript");
var Lint = require("tslint");
var ErrorTolerantWalker_1 = require("./utils/ErrorTolerantWalker");
var AstUtils_1 = require("./utils/AstUtils");
var Utils_1 = require("./utils/Utils");
var FAILURE_STRING = 'The cohesion of this class is too low. Consider splitting this class into multiple cohesive classes: ';
var Rule = (function (_super) {
__extends(Rule, _super);
function Rule() {
return _super !== null && _super.apply(this, arguments) || this;
}
Rule.prototype.apply = function (sourceFile) {
return this.applyWithWalker(new MinClassCohesionRuleWalker(sourceFile, this.getOptions()));
};
Rule.metadata = {
ruleName: 'min-class-cohesion',
type: 'maintainability',
description: 'High cohesion means the methods and variables of the class are co-dependent and hang together as a logical whole.',
options: null,
optionsDescription: '',
typescriptOnly: true,
issueClass: 'Non-SDL',
issueType: 'Warning',
severity: 'Important',
level: 'Opportunity for Excellence',
group: 'Correctness',
recommendation: '[true, 0.5],',
commonWeaknessEnumeration: '',
};
return Rule;
}(Lint.Rules.AbstractRule));
exports.Rule = Rule;
var MinClassCohesionRuleWalker = (function (_super) {
__extends(MinClassCohesionRuleWalker, _super);
function MinClassCohesionRuleWalker(sourceFile, options) {
var _this = _super.call(this, sourceFile, options) || this;
_this.minClassCohesion = 0.5;
_this.parseOptions();
return _this;
}
MinClassCohesionRuleWalker.prototype.visitClassDeclaration = function (node) {
if (!this.isClassCohesive(node)) {
var className = node.name == null ? '<unknown>' : node.name.text;
this.addFailureAt(node.getStart(), node.getWidth(), FAILURE_STRING + className);
}
_super.prototype.visitClassDeclaration.call(this, node);
};
MinClassCohesionRuleWalker.prototype.isClassCohesive = function (node) {
var classNode = new ClassDeclarationHelper(node);
if (classNode.extendsSomething) {
return true;
}
var cohesionScore = classNode.cohesionScore;
return cohesionScore >= this.minClassCohesion;
};
MinClassCohesionRuleWalker.prototype.parseOptions = function () {
var _this = this;
this.getOptions().forEach(function (opt) {
if (typeof opt === 'boolean') {
return;
}
if (typeof opt === 'number') {
_this.minClassCohesion = opt;
return;
}
throw new Error("Rule min-class-cohesion only supports option of type number, not " + typeof opt + ".");
});
};
return MinClassCohesionRuleWalker;
}(ErrorTolerantWalker_1.ErrorTolerantWalker));
var ClassDeclarationHelper = (function () {
function ClassDeclarationHelper(node) {
this.node = node;
}
Object.defineProperty(ClassDeclarationHelper.prototype, "cohesionScore", {
get: function () {
var _this = this;
var _a = this, fieldNames = _a.fieldNames, methods = _a.methods;
if (methods.length === 0) {
return 1.0;
}
var numFields = fieldNames.length;
if (numFields === 0) {
return 0.0;
}
var methodScores = methods.map(function (method) {
var used = _this.numberOfFieldsUsedByMethod(fieldNames, method);
return used / numFields;
});
var sumScores = methodScores.reduce(function (total, current) { return total + current; }, 0);
return sumScores / methods.length;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ClassDeclarationHelper.prototype, "fieldNames", {
get: function () {
var parameterNames = this.constructorParameterNames;
var instanceFields = this.instanceFieldNames;
return parameterNames.concat(instanceFields);
},
enumerable: true,
configurable: true
});
Object.defineProperty(ClassDeclarationHelper.prototype, "methods", {
get: function () {
return this.node.members.filter(function (classElement) {
switch (classElement.kind) {
case ts.SyntaxKind.MethodDeclaration:
case ts.SyntaxKind.GetAccessor:
case ts.SyntaxKind.SetAccessor:
return !AstUtils_1.AstUtils.isStatic(classElement);
default:
return false;
}
});
},
enumerable: true,
configurable: true
});
ClassDeclarationHelper.prototype.numberOfFieldsUsedByMethod = function (fieldNames, method) {
var fields = ClassDeclarationHelper.fieldsUsedByMethod(method);
return fieldNames.reduce(function (count, fieldName) {
if (fields[fieldName]) {
return count + 1;
}
return count;
}, 0);
};
Object.defineProperty(ClassDeclarationHelper.prototype, "constructorParameterNames", {
get: function () {
return this.constructorParameters.map(function (param) { return param.name.getText(); });
},
enumerable: true,
configurable: true
});
Object.defineProperty(ClassDeclarationHelper.prototype, "constructorParameters", {
get: function () {
var ctor = this.constructorDeclaration;
if (ctor) {
return ctor.parameters.filter(function (param) {
return (AstUtils_1.AstUtils.hasModifier(param.modifiers, ts.SyntaxKind.PublicKeyword) ||
AstUtils_1.AstUtils.hasModifier(param.modifiers, ts.SyntaxKind.PrivateKeyword) ||
AstUtils_1.AstUtils.hasModifier(param.modifiers, ts.SyntaxKind.ProtectedKeyword) ||
AstUtils_1.AstUtils.hasModifier(param.modifiers, ts.SyntaxKind.ReadonlyKeyword));
});
}
return [];
},
enumerable: true,
configurable: true
});
Object.defineProperty(ClassDeclarationHelper.prototype, "constructorDeclaration", {
get: function () {
return (this.node.members.find(function (element) { return element.kind === ts.SyntaxKind.Constructor; }));
},
enumerable: true,
configurable: true
});
Object.defineProperty(ClassDeclarationHelper.prototype, "instanceFieldNames", {
get: function () {
return this.instanceFields.map(function (param) { return param.name.getText(); });
},
enumerable: true,
configurable: true
});
Object.defineProperty(ClassDeclarationHelper.prototype, "instanceFields", {
get: function () {
return (this.node.members.filter(function (classElement) { return classElement.kind === ts.SyntaxKind.PropertyDeclaration; }));
},
enumerable: true,
configurable: true
});
ClassDeclarationHelper.fieldsUsedByMethod = function (method) {
var walker = new ClassMethodWalker();
walker.walk(method);
return walker.fieldsUsed;
};
Object.defineProperty(ClassDeclarationHelper.prototype, "extendsSomething", {
get: function () {
return Utils_1.Utils.exists(this.node.heritageClauses, function (clause) {
return clause.token === ts.SyntaxKind.ExtendsKeyword;
});
},
enumerable: true,
configurable: true
});
Object.defineProperty(ClassDeclarationHelper.prototype, "name", {
get: function () {
return this.node.name == null ? '<unknown>' : this.node.name.text;
},
enumerable: true,
configurable: true
});
return ClassDeclarationHelper;
}());
var ClassMethodWalker = (function (_super) {
__extends(ClassMethodWalker, _super);
function ClassMethodWalker() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.fieldsUsed = {};
return _this;
}
ClassMethodWalker.prototype.visitPropertyAccessExpression = function (node) {
var isOnThis = node.expression.kind === ts.SyntaxKind.ThisKeyword;
if (isOnThis) {
var field = node.name.text;
this.fieldsUsed[field] = true;
}
_super.prototype.visitPropertyAccessExpression.call(this, node);
};
return ClassMethodWalker;
}(Lint.SyntaxWalker));
//# sourceMappingURL=minClassCohesionRule.js.map