UNPKG

tslint-clean-code

Version:
559 lines 22.5 kB
"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