1 | import * as ts from 'typescript';
|
2 | import * as Lint from 'tslint';
|
3 |
|
4 | import { ErrorTolerantWalker } from './utils/ErrorTolerantWalker';
|
5 | import { ExtendedMetadata } from './utils/ExtendedMetadata';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | export class Rule extends Lint.Rules.AbstractRule {
|
11 | public static metadata: ExtendedMetadata = {
|
12 | ruleName: 'no-complex-conditionals',
|
13 | type: 'maintainability',
|
14 | description: 'Enforce the maximum complexity of conditional expressions.',
|
15 | options: null,
|
16 | optionsDescription: '',
|
17 | optionExamples: [],
|
18 | typescriptOnly: false,
|
19 | issueClass: 'Non-SDL',
|
20 | issueType: 'Warning',
|
21 | severity: 'Moderate',
|
22 | level: 'Opportunity for Excellence',
|
23 | group: 'Clarity',
|
24 | commonWeaknessEnumeration: '',
|
25 | };
|
26 |
|
27 | public static FAILURE_STRING: string =
|
28 | 'Conditional expression is too complex. ' + 'Consider moving expression to a variable or function with a meaningful name.';
|
29 |
|
30 | public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
|
31 | return this.applyWithWalker(new NoComplexConditionalsRuleWalker(sourceFile, this.getOptions()));
|
32 | }
|
33 | }
|
34 |
|
35 | class NoComplexConditionalsRuleWalker extends ErrorTolerantWalker {
|
36 | private threshold: number = 3;
|
37 |
|
38 | constructor(sourceFile: ts.SourceFile, options: Lint.IOptions) {
|
39 | super(sourceFile, options);
|
40 | this.parseOptions();
|
41 | }
|
42 |
|
43 | protected visitIfStatement(node: ts.IfStatement): void {
|
44 | const { expression } = node;
|
45 | const complexity = this.countComplexity(expression);
|
46 |
|
47 | if (complexity > this.threshold) {
|
48 | this.addFailureAtNode(expression, Rule.FAILURE_STRING);
|
49 | }
|
50 | super.visitIfStatement(node);
|
51 | }
|
52 |
|
53 | private countComplexity(expression: ts.Expression): number {
|
54 | let complexity = 0;
|
55 | const cb = (node: ts.Node) => {
|
56 |
|
57 | if (this.increasesComplexity(node)) {
|
58 | complexity = complexity + 1;
|
59 | }
|
60 | return ts.forEachChild(node, cb);
|
61 | };
|
62 |
|
63 | cb(expression);
|
64 | return complexity;
|
65 | }
|
66 |
|
67 | private increasesComplexity(node: ts.Node): boolean {
|
68 |
|
69 | switch (node.kind) {
|
70 | case ts.SyntaxKind.CaseClause:
|
71 | return (<ts.CaseClause>node).statements.length > 0;
|
72 | case ts.SyntaxKind.CatchClause:
|
73 | case ts.SyntaxKind.ConditionalExpression:
|
74 | case ts.SyntaxKind.DoStatement:
|
75 | case ts.SyntaxKind.ForStatement:
|
76 | case ts.SyntaxKind.ForInStatement:
|
77 | case ts.SyntaxKind.ForOfStatement:
|
78 | case ts.SyntaxKind.IfStatement:
|
79 | case ts.SyntaxKind.WhileStatement:
|
80 | return true;
|
81 |
|
82 | case ts.SyntaxKind.BinaryExpression:
|
83 | switch ((<ts.BinaryExpression>node).operatorToken.kind) {
|
84 | case ts.SyntaxKind.BarBarToken:
|
85 | case ts.SyntaxKind.AmpersandAmpersandToken:
|
86 | return true;
|
87 | default:
|
88 | return false;
|
89 | }
|
90 |
|
91 | default:
|
92 | return false;
|
93 | }
|
94 | }
|
95 |
|
96 | private parseOptions(): void {
|
97 | this.getOptions().forEach((opt: any) => {
|
98 | if (typeof opt === 'boolean') {
|
99 | return;
|
100 | }
|
101 | if (typeof opt === 'number') {
|
102 | this.threshold = opt;
|
103 | return;
|
104 | }
|
105 | });
|
106 | }
|
107 | }
|