tslint-clean-code
Version:
TSLint rules for enforcing Clean Code
252 lines • 9.73 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 Rule = (function (_super) {
__extends(Rule, _super);
function Rule() {
return _super !== null && _super.apply(this, arguments) || this;
}
Rule.FAILURE_STRING = function (feature) {
var methodName = feature.methodName, className = feature.className, otherClassName = feature.otherClassName;
var failureMessage = "Method \"" + methodName + "\" uses \"" + otherClassName + "\" more than its own class \"" + className + "\".";
var recommendation = "Extract or Move Method from \"" + methodName + "\" into \"" + otherClassName + "\".";
return failureMessage + " " + recommendation;
};
Rule.prototype.apply = function (sourceFile) {
return this.applyWithWalker(new NoFeatureEnvyRuleWalker(sourceFile, this.getOptions()));
};
Rule.metadata = {
ruleName: 'no-feature-envy',
type: 'maintainability',
description: 'A method accesses the data of another object more than its own data.',
options: null,
optionsDescription: '',
optionExamples: [],
recommendation: '[true, 1, ["_"]],',
typescriptOnly: false,
issueClass: 'Non-SDL',
issueType: 'Warning',
severity: 'Moderate',
level: 'Opportunity for Excellence',
group: 'Clarity',
commonWeaknessEnumeration: '',
};
return Rule;
}(Lint.Rules.AbstractRule));
exports.Rule = Rule;
var NoFeatureEnvyRuleWalker = (function (_super) {
__extends(NoFeatureEnvyRuleWalker, _super);
function NoFeatureEnvyRuleWalker(sourceFile, options) {
var _this = _super.call(this, sourceFile, options) || this;
_this.threshold = 0;
_this.exclude = [];
_this.parseOptions();
return _this;
}
NoFeatureEnvyRuleWalker.prototype.visitClassDeclaration = function (node) {
this.checkAndReport(node);
_super.prototype.visitClassDeclaration.call(this, node);
};
NoFeatureEnvyRuleWalker.prototype.checkAndReport = function (node) {
var _this = this;
this.getFeatureMethodsForClass(node).forEach(function (feature) {
var failureMessage = Rule.FAILURE_STRING(feature);
_this.addFailureAtNode(feature.methodNode, failureMessage);
});
};
NoFeatureEnvyRuleWalker.prototype.getFeatureMethodsForClass = function (classNode) {
var _this = this;
var methods = this.methodsForClass(classNode);
return methods
.map(function (method) {
var walker = new ClassMethodWalker(classNode, method);
return walker.features();
})
.map(function (features) { return _this.getTopFeature(features); })
.filter(function (feature) { return feature !== undefined; });
};
NoFeatureEnvyRuleWalker.prototype.getTopFeature = function (features) {
var filteredFeatures = this.filterFeatures(features);
return filteredFeatures.reduce(function (best, current) {
if (!best) {
return current;
}
if (current.featureEnvy() > best.featureEnvy()) {
return current;
}
return best;
}, undefined);
};
NoFeatureEnvyRuleWalker.prototype.filterFeatures = function (features) {
var _this = this;
return features.filter(function (feature) {
var isExcluded = _this.exclude.indexOf(feature.otherClassName) !== -1;
if (isExcluded) {
return false;
}
return feature.featureEnvy() > _this.threshold;
});
};
NoFeatureEnvyRuleWalker.prototype.methodsForClass = function (classNode) {
return classNode.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;
}
});
};
NoFeatureEnvyRuleWalker.prototype.parseOptions = function () {
var _this = this;
this.getOptions().forEach(function (opt) {
if (typeof opt === 'boolean') {
return;
}
if (typeof opt === 'number') {
_this.threshold = opt;
return;
}
if (Array.isArray(opt)) {
_this.exclude = opt;
return;
}
});
};
return NoFeatureEnvyRuleWalker;
}(ErrorTolerantWalker_1.ErrorTolerantWalker));
var ClassMethodWalker = (function (_super) {
__extends(ClassMethodWalker, _super);
function ClassMethodWalker(classNode, methodNode) {
var _this = _super.call(this) || this;
_this.classNode = classNode;
_this.methodNode = methodNode;
_this.featureEnvyMap = {};
_this.walk(_this.methodNode);
return _this;
}
ClassMethodWalker.prototype.features = function () {
var _this = this;
var thisClassAccesses = this.getCountForClass('this');
return this.classesUsed.map(function (className) {
var otherClassAccesses = _this.getCountForClass(className);
return new MethodFeature({
classNode: _this.classNode,
methodNode: _this.methodNode,
otherClassName: className,
thisClassAccesses: thisClassAccesses,
otherClassAccesses: otherClassAccesses,
});
});
};
ClassMethodWalker.prototype.getCountForClass = function (className) {
return this.featureEnvyMap[className] || 0;
};
Object.defineProperty(ClassMethodWalker.prototype, "classesUsed", {
get: function () {
return Object.keys(this.featureEnvyMap).filter(function (className) { return className !== 'this'; });
},
enumerable: true,
configurable: true
});
ClassMethodWalker.prototype.visitPropertyAccessExpression = function (node) {
if (this.isTopPropertyAccess(node)) {
var className = this.classNameForPropertyAccess(node);
this.incrementCountForClass(className);
}
_super.prototype.visitPropertyAccessExpression.call(this, node);
};
ClassMethodWalker.prototype.incrementCountForClass = function (className) {
if (this.featureEnvyMap[className] !== undefined) {
this.featureEnvyMap[className] += 1;
}
else {
this.featureEnvyMap[className] = 1;
}
};
ClassMethodWalker.prototype.isTopPropertyAccess = function (node) {
switch (node.expression.kind) {
case ts.SyntaxKind.Identifier:
case ts.SyntaxKind.ThisKeyword:
case ts.SyntaxKind.SuperKeyword:
return true;
}
return false;
};
ClassMethodWalker.prototype.classNameForPropertyAccess = function (node) {
var expression = node.expression;
if (ts.isThisTypeNode(node)) {
return 'this';
}
if (expression.kind === ts.SyntaxKind.SuperKeyword) {
return 'this';
}
if (this.classNode.name.getText() === expression.getText()) {
return 'this';
}
return expression.getText();
};
return ClassMethodWalker;
}(Lint.SyntaxWalker));
var MethodFeature = (function () {
function MethodFeature(data) {
this.data = data;
}
Object.defineProperty(MethodFeature.prototype, "className", {
get: function () {
return this.classNode.name.text;
},
enumerable: true,
configurable: true
});
Object.defineProperty(MethodFeature.prototype, "classNode", {
get: function () {
return this.data.classNode;
},
enumerable: true,
configurable: true
});
Object.defineProperty(MethodFeature.prototype, "methodName", {
get: function () {
return this.methodNode.name.getText();
},
enumerable: true,
configurable: true
});
Object.defineProperty(MethodFeature.prototype, "methodNode", {
get: function () {
return this.data.methodNode;
},
enumerable: true,
configurable: true
});
MethodFeature.prototype.featureEnvy = function () {
var _a = this.data, thisClassAccesses = _a.thisClassAccesses, otherClassAccesses = _a.otherClassAccesses;
return otherClassAccesses - thisClassAccesses;
};
Object.defineProperty(MethodFeature.prototype, "otherClassName", {
get: function () {
return this.data.otherClassName;
},
enumerable: true,
configurable: true
});
return MethodFeature;
}());
exports.MethodFeature = MethodFeature;
//# sourceMappingURL=noFeatureEnvyRule.js.map