UNPKG

4.24 kBPlain TextView Raw
1import * as ts from 'typescript';
2import * as Lint from 'tslint';
3
4import { ErrorTolerantWalker } from './utils/ErrorTolerantWalker';
5import { ExtendedMetadata } from './utils/ExtendedMetadata';
6
7/**
8 * Implementation of the no-complex-conditionals rule.
9 */
10export class Rule extends Lint.Rules.AbstractRule {
11 public static metadata: ExtendedMetadata = {
12 ruleName: 'no-complex-conditionals',
13 type: 'maintainability', // one of: 'functionality' | 'maintainability' | 'style' | 'typescript'
14 description: 'Enforce the maximum complexity of conditional expressions.',
15 options: null,
16 optionsDescription: '',
17 optionExamples: [], //Remove this property if the rule has no options
18 typescriptOnly: false,
19 issueClass: 'Non-SDL', // one of: 'SDL' | 'Non-SDL' | 'Ignored'
20 issueType: 'Warning', // one of: 'Error' | 'Warning'
21 severity: 'Moderate', // one of: 'Critical' | 'Important' | 'Moderate' | 'Low'
22 level: 'Opportunity for Excellence', // one of 'Mandatory' | 'Opportunity for Excellence'
23 group: 'Clarity', // one of 'Ignored' | 'Security' | 'Correctness' | 'Clarity' | 'Whitespace' | 'Configurable' | 'Deprecated'
24 commonWeaknessEnumeration: '', // if possible, please map your rule to a CWE (see cwe_descriptions.json and https://cwe.mitre.org)
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
35class 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 // console.log('complexity', complexity); //tslint:disable-line
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 // console.log('complexity', complexity); //tslint:disable-line
57 if (this.increasesComplexity(node)) {
58 complexity = complexity + 1;
59 }
60 return ts.forEachChild(node, cb);
61 };
62 // ts.forEachChild(expression, cb);
63 cb(expression);
64 return complexity;
65 }
66
67 private increasesComplexity(node: ts.Node): boolean {
68 // console.log(ts.SyntaxKind[node.kind] + ' ' + node.getText());
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}