tslint-clean-code
Version:
TSLint rules for enforcing Clean Code
559 lines • 22.5 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 __());
};
})();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
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 DirectedAcyclicGraph_1 = require("./utils/DirectedAcyclicGraph");
var Memoize = require("memoize-decorator");
exports.FAILURE_CLASS_STRING = 'The class does not read like a Newspaper. Reorder the methods of the class: ';
exports.FAILURE_FILE_STRING = 'The functions in the file do not read like a Newspaper. Reorder the functions in the file: ';
exports.FAILURE_BLOCK_STRING = 'The functions in the block do not read like a Newspaper. Reorder the functions in the block: ';
var END_OF_DEPENDENCIES_MARKER = '--end-of-dependencies-marker--';
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 NewspaperOrderRuleWalker(sourceFile, this.getOptions()));
};
Rule.metadata = {
ruleName: 'newspaper-order',
type: 'maintainability',
description: 'We would like a source file to be like a newspaper article. ' +
'Detail should increase as we move downward, ' +
'until at the end we find the lowest level functions and details in the source file.',
options: null,
optionsDescription: '',
typescriptOnly: true,
issueClass: 'Non-SDL',
issueType: 'Warning',
severity: 'Important',
level: 'Opportunity for Excellence',
group: 'Correctness',
recommendation: 'true,',
commonWeaknessEnumeration: '',
};
return Rule;
}(Lint.Rules.AbstractRule));
exports.Rule = Rule;
var NewspaperOrderRuleWalker = (function (_super) {
__extends(NewspaperOrderRuleWalker, _super);
function NewspaperOrderRuleWalker() {
return _super !== null && _super.apply(this, arguments) || this;
}
NewspaperOrderRuleWalker.prototype.visitBlock = function (node) {
var blockNode = new BlockHelper(node);
this.checkAndReportFailure(blockNode, exports.FAILURE_BLOCK_STRING);
_super.prototype.visitBlock.call(this, node);
};
NewspaperOrderRuleWalker.prototype.visitClassDeclaration = function (node) {
var classNode = new ClassDeclarationHelper(node);
this.checkAndReportFailure(classNode, exports.FAILURE_CLASS_STRING);
_super.prototype.visitClassDeclaration.call(this, node);
};
NewspaperOrderRuleWalker.prototype.visitSourceFile = function (node) {
var sourceNode = new SourceFileHelper(node);
this.checkAndReportFailure(sourceNode, exports.FAILURE_FILE_STRING);
_super.prototype.visitSourceFile.call(this, node);
};
NewspaperOrderRuleWalker.prototype.checkAndReportFailure = function (nodeHelper, failureString) {
if (!this.readsLikeNewspaper(nodeHelper)) {
var failureMessage = this.makeClassFailureMessage(nodeHelper, failureString);
this.addFailureAt(nodeHelper.start, nodeHelper.width, failureMessage);
}
};
NewspaperOrderRuleWalker.prototype.makeClassFailureMessage = function (nodeHelper, failureString) {
var nodeName = nodeHelper.nodeName, completeOrderedMethodNames = nodeHelper.completeOrderedMethodNames, methodNames = nodeHelper.methodNames;
var correctSymbol = '✓';
var incorrectSymbol = 'x';
var help = '\n\nMethods order:\n' +
completeOrderedMethodNames
.map(function (method, index) {
var isCorrect = methodNames[index] === method;
var status = isCorrect ? correctSymbol : incorrectSymbol;
return index + 1 + ". " + status + " " + method;
})
.join('\n');
return failureString + nodeName + help;
};
NewspaperOrderRuleWalker.prototype.readsLikeNewspaper = function (nodeHelper) {
return nodeHelper.readsLikeNewspaper;
};
return NewspaperOrderRuleWalker;
}(ErrorTolerantWalker_1.ErrorTolerantWalker));
var NewspaperHelper = (function () {
function NewspaperHelper(node) {
this.node = node;
}
Object.defineProperty(NewspaperHelper.prototype, "readsLikeNewspaper", {
get: function () {
var _a = this, methodNames = _a.methodNames, completeOrderedMethodNames = _a.completeOrderedMethodNames, ignoredMethods = _a.ignoredMethods;
var ignoringAllMethods = ignoredMethods.length === methodNames.length;
var hasNoDeps = completeOrderedMethodNames.length === 0;
if (ignoringAllMethods || hasNoDeps) {
return true;
}
return Utils_1.Utils.arraysShallowEqual(methodNames, completeOrderedMethodNames);
},
enumerable: true,
configurable: true
});
Object.defineProperty(NewspaperHelper.prototype, "width", {
get: function () {
return this.end - this.start;
},
enumerable: true,
configurable: true
});
Object.defineProperty(NewspaperHelper.prototype, "end", {
get: function () {
var len = this.incorrectMethodNames.length;
var lastIncorrectFunctionName = len > 0 ? this.incorrectMethodNames[len - 1] : undefined;
if (lastIncorrectFunctionName) {
var lastIncorrectFunction = this.methodForName(lastIncorrectFunctionName);
return lastIncorrectFunction.getEnd();
}
return this.node.getEnd();
},
enumerable: true,
configurable: true
});
Object.defineProperty(NewspaperHelper.prototype, "start", {
get: function () {
var firstIncorrectFunctionName = this.incorrectMethodNames[0];
if (firstIncorrectFunctionName) {
var firstIncorrectFunction = this.methodForName(firstIncorrectFunctionName);
return firstIncorrectFunction.getStart();
}
return this.node.getStart();
},
enumerable: true,
configurable: true
});
Object.defineProperty(NewspaperHelper.prototype, "incorrectMethodNames", {
get: function () {
var _a = this, completeOrderedMethodNames = _a.completeOrderedMethodNames, methodNames = _a.methodNames;
return methodNames.filter(function (methodName, index) {
return methodName !== completeOrderedMethodNames[index];
});
},
enumerable: true,
configurable: true
});
Object.defineProperty(NewspaperHelper.prototype, "completeOrderedMethodNames", {
get: function () {
var _a = this, orderedMethodNames = _a.orderedMethodNames, ignoredMethods = _a.ignoredMethods;
return orderedMethodNames.concat(ignoredMethods);
},
enumerable: true,
configurable: true
});
Object.defineProperty(NewspaperHelper.prototype, "ignoredMethods", {
get: function () {
var _a = this, methodNames = _a.methodNames, orderedMethodNames = _a.orderedMethodNames;
return methodNames.filter(function (methodName) {
return orderedMethodNames.indexOf(methodName) === -1;
});
},
enumerable: true,
configurable: true
});
Object.defineProperty(NewspaperHelper.prototype, "orderedMethodNames", {
get: function () {
var _a = this, methodGraph = _a.methodGraph, methodNames = _a.methodNames;
try {
var top_1 = new TopologicalSortUtil(methodGraph);
var ordered = top_1.closestList(methodNames);
if (ordered[ordered.length - 1] === END_OF_DEPENDENCIES_MARKER) {
ordered.pop();
}
return ordered;
}
catch (error) {
return [];
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(NewspaperHelper.prototype, "methodGraph", {
get: function () {
var methodDependencies = this.methodDependencies;
return Object.keys(methodDependencies)
.sort()
.reduce(function (graph, methodName) {
var deps = Object.keys(methodDependencies[methodName]).sort();
deps.forEach(function (depName) {
var shouldIgnore = !methodDependencies.hasOwnProperty(depName) || methodName === depName;
if (shouldIgnore) {
return;
}
var edge = [methodName, depName];
graph.push(edge);
});
graph.push([methodName, END_OF_DEPENDENCIES_MARKER]);
return graph;
}, []);
},
enumerable: true,
configurable: true
});
Object.defineProperty(NewspaperHelper.prototype, "methodDependencies", {
get: function () {
var _this = this;
return this.methods.reduce(function (result, method) {
result[method.name.getText()] = _this.dependenciesForMethod(method);
return result;
}, {});
},
enumerable: true,
configurable: true
});
Object.defineProperty(NewspaperHelper.prototype, "methodNames", {
get: function () {
return this.methods.map(function (method) { return method.name.getText(); });
},
enumerable: true,
configurable: true
});
NewspaperHelper.prototype.methodForName = function (methodName) {
return this.methodsIndex[methodName];
};
Object.defineProperty(NewspaperHelper.prototype, "methodsIndex", {
get: function () {
return this.methods.reduce(function (index, method) {
var name = method.name.getText();
index[name] = method;
return index;
}, {});
},
enumerable: true,
configurable: true
});
__decorate([
Memoize
], NewspaperHelper.prototype, "readsLikeNewspaper", null);
__decorate([
Memoize
], NewspaperHelper.prototype, "end", null);
__decorate([
Memoize
], NewspaperHelper.prototype, "start", null);
__decorate([
Memoize
], NewspaperHelper.prototype, "incorrectMethodNames", null);
__decorate([
Memoize
], NewspaperHelper.prototype, "completeOrderedMethodNames", null);
__decorate([
Memoize
], NewspaperHelper.prototype, "ignoredMethods", null);
__decorate([
Memoize
], NewspaperHelper.prototype, "orderedMethodNames", null);
__decorate([
Memoize
], NewspaperHelper.prototype, "methodGraph", null);
__decorate([
Memoize
], NewspaperHelper.prototype, "methodDependencies", null);
__decorate([
Memoize
], NewspaperHelper.prototype, "methodNames", null);
__decorate([
Memoize
], NewspaperHelper.prototype, "methodsIndex", null);
return NewspaperHelper;
}());
var ClassDeclarationHelper = (function (_super) {
__extends(ClassDeclarationHelper, _super);
function ClassDeclarationHelper(node) {
var _this = _super.call(this, node) || this;
_this.node = node;
return _this;
}
ClassDeclarationHelper.prototype.dependenciesForMethod = function (method) {
var walker = new ClassMethodWalker();
walker.walk(method);
return walker.dependencies;
};
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
});
Object.defineProperty(ClassDeclarationHelper.prototype, "nodeName", {
get: function () {
return this.node.name == null ? '<unknown>' : this.node.name.text;
},
enumerable: true,
configurable: true
});
__decorate([
Memoize
], ClassDeclarationHelper.prototype, "methods", null);
__decorate([
Memoize
], ClassDeclarationHelper.prototype, "nodeName", null);
return ClassDeclarationHelper;
}(NewspaperHelper));
var BlockLikeHelper = (function (_super) {
__extends(BlockLikeHelper, _super);
function BlockLikeHelper(node) {
var _this = _super.call(this, node) || this;
_this.node = node;
return _this;
}
Object.defineProperty(BlockLikeHelper.prototype, "methods", {
get: function () {
var _this = this;
var functionDeclarations = (this.node.statements.filter(function (node) { return ts.isFunctionDeclaration(node); }));
var variableStatements = (this.node.statements.filter(function (node) { return ts.isVariableStatement(node); }));
var variableFunctionDeclarations = variableStatements
.map(function (node) { return node.declarationList.declarations; })
.map(function (declarations) { return declarations.map(_this.createFuncDeclarFromVarDeclar).filter(function (node) { return node; }); })
.reduce(function (result, item) { return result.concat(item); }, []);
return functionDeclarations.concat(variableFunctionDeclarations);
},
enumerable: true,
configurable: true
});
BlockLikeHelper.prototype.createFuncDeclarFromVarDeclar = function (declaration) {
var name = declaration.name, initializer = declaration.initializer;
if (ts.isIdentifier(name) && ts.isFunctionExpression(initializer)) {
var node = ts.createFunctionDeclaration([], [], undefined, name, [], [], undefined, initializer.body);
node.pos = declaration.pos;
node.end = declaration.end;
return node;
}
return null;
};
BlockLikeHelper.prototype.dependenciesForMethod = function (method) {
var walker = new FunctionWalker();
walker.walk(method);
return walker.dependencies;
};
__decorate([
Memoize
], BlockLikeHelper.prototype, "methods", null);
return BlockLikeHelper;
}(NewspaperHelper));
var SourceFileHelper = (function (_super) {
__extends(SourceFileHelper, _super);
function SourceFileHelper(node) {
var _this = _super.call(this, node) || this;
_this.node = node;
return _this;
}
Object.defineProperty(SourceFileHelper.prototype, "nodeName", {
get: function () {
return this.node.fileName == null ? '<unknown>' : this.node.fileName;
},
enumerable: true,
configurable: true
});
__decorate([
Memoize
], SourceFileHelper.prototype, "nodeName", null);
return SourceFileHelper;
}(BlockLikeHelper));
var BlockHelper = (function (_super) {
__extends(BlockHelper, _super);
function BlockHelper(node) {
var _this = _super.call(this, node) || this;
_this.node = node;
return _this;
}
Object.defineProperty(BlockHelper.prototype, "nodeName", {
get: function () {
var node = this.node;
if (node.parent) {
if (node.parent.kind === ts.SyntaxKind.FunctionDeclaration) {
return node.parent.name.getText() || '<anonymous>';
}
}
return '<anonymous>';
},
enumerable: true,
configurable: true
});
__decorate([
Memoize
], BlockHelper.prototype, "nodeName", null);
return BlockHelper;
}(BlockLikeHelper));
var ClassMethodWalker = (function (_super) {
__extends(ClassMethodWalker, _super);
function ClassMethodWalker() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.dependencies = {};
return _this;
}
ClassMethodWalker.prototype.visitPropertyAccessExpression = function (node) {
var isOnThis = node.expression.kind === ts.SyntaxKind.ThisKeyword;
if (isOnThis) {
var field = node.name.text;
this.dependencies[field] = true;
}
_super.prototype.visitPropertyAccessExpression.call(this, node);
};
ClassMethodWalker.prototype.visitBindingElement = function (node) {
var isOnThis = node.parent.parent.initializer.kind === ts.SyntaxKind.ThisKeyword;
if (isOnThis) {
var field = node.name.getText();
this.dependencies[field] = true;
}
_super.prototype.visitBindingElement.call(this, node);
};
return ClassMethodWalker;
}(Lint.SyntaxWalker));
var FunctionWalker = (function (_super) {
__extends(FunctionWalker, _super);
function FunctionWalker() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.dependencies = {};
return _this;
}
FunctionWalker.prototype.visitCallExpression = function (node) {
var _this = this;
if (node.expression.kind === ts.SyntaxKind.Identifier) {
var field = node.expression.getText();
this.dependencies[field] = true;
}
if (Array.isArray(node.arguments)) {
node.arguments.forEach(function (arg) {
if (arg.kind === ts.SyntaxKind.Identifier) {
_this.dependencies[arg.getText()] = true;
}
});
}
_super.prototype.visitCallExpression.call(this, node);
};
return FunctionWalker;
}(Lint.SyntaxWalker));
var TopologicalSortUtil = (function () {
function TopologicalSortUtil(graph) {
this.graph = graph;
}
TopologicalSortUtil.prototype.closestList = function (currentList) {
var _this = this;
if (currentList.length === 0) {
return [];
}
var allLists = this.allLists;
if (allLists.length === 0) {
return [];
}
var bestList = [];
var bestDistance = Infinity;
allLists.forEach(function (list) {
var dist = _this.distanceBetweenLists(currentList, list);
if (dist < bestDistance) {
bestDistance = dist;
bestList = list;
}
});
return bestList;
};
TopologicalSortUtil.prototype.distanceBetweenLists = function (srcList, destList) {
var positionMap = destList.reduce(function (result, key, index) {
result[key] = index;
return result;
}, {});
return srcList.reduce(function (total, key, index) {
var destIndex = positionMap[key];
if (destIndex) {
return total + Math.abs(destIndex - index);
}
return total;
}, 0);
};
Object.defineProperty(TopologicalSortUtil.prototype, "allLists", {
get: function () {
var _a = this, dag = _a.dag, list = _a.list;
var indexMap = list.reduce(function (result, key, index) {
result[index] = key;
return result;
}, {});
return dag.alltopologicalSort().map(function (currList) {
return currList.map(function (index) { return indexMap[index]; });
});
},
enumerable: true,
configurable: true
});
Object.defineProperty(TopologicalSortUtil.prototype, "dag", {
get: function () {
var _a = this, graph = _a.graph, list = _a.list;
var positionMap = list.reduce(function (result, key, index) {
result[key] = index;
return result;
}, {});
var dag = new DirectedAcyclicGraph_1.DirectedAcyclicGraph(list.length);
graph.forEach(function (_a) {
var from = _a[0], to = _a[1];
var ai = positionMap[from];
var bi = positionMap[to];
dag.addEdge(ai, bi);
});
return dag;
},
enumerable: true,
configurable: true
});
Object.defineProperty(TopologicalSortUtil.prototype, "list", {
get: function () {
var graph = this.graph;
var index = {};
graph.forEach(function (_a) {
var from = _a[0], to = _a[1];
index[from] = true;
index[to] = true;
});
return Object.keys(index);
},
enumerable: true,
configurable: true
});
__decorate([
Memoize
], TopologicalSortUtil.prototype, "allLists", null);
__decorate([
Memoize
], TopologicalSortUtil.prototype, "dag", null);
__decorate([
Memoize
], TopologicalSortUtil.prototype, "list", null);
return TopologicalSortUtil;
}());
//# sourceMappingURL=newspaperOrderRule.js.map