forked from mgechev/codelyzer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtemplateConditionalComplexityRule.ts
More file actions
115 lines (95 loc) · 3.65 KB
/
templateConditionalComplexityRule.ts
File metadata and controls
115 lines (95 loc) · 3.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { AST, ASTWithSource, Binary, BoundDirectivePropertyAst, Lexer, Parser } from '@angular/compiler';
import { sprintf } from 'sprintf-js';
import { IRuleMetadata, RuleFailure, Rules } from 'tslint/lib';
import { SourceFile } from 'typescript/lib/typescript';
import { NgWalker, NgWalkerConfig } from './angular/ngWalker';
import { BasicTemplateAstVisitor } from './angular/templates/basicTemplateAstVisitor';
export class Rule extends Rules.AbstractRule {
static readonly metadata: IRuleMetadata = {
description: "The condition complexity shouldn't exceed a rational limit in a template.",
optionExamples: [true, [true, 4]],
options: {
items: {
type: 'string',
},
maxLength: 1,
minLength: 0,
type: 'array',
},
optionsDescription: 'Determine the maximum number of Boolean operators allowed.',
rationale: 'An important complexity complicates the tests and the maintenance.',
ruleName: 'template-conditional-complexity',
type: 'maintainability',
typescriptOnly: true,
};
static readonly DEFAULT_MAX_COMPLEXITY = 3;
static readonly FAILURE_STRING =
"The condition complexity (cost '%s') exceeded the defined limit (cost '%s'). The conditional expression should be moved into the component.";
apply(sourceFile: SourceFile): RuleFailure[] {
const walkerConfig: NgWalkerConfig = { templateVisitorCtrl: TemplateVisitorCtrl };
const walker = new NgWalker(sourceFile, this.getOptions(), walkerConfig);
return this.applyWithWalker(walker);
}
isEnabled(): boolean {
const {
metadata: {
options: { maxLength, minLength },
},
} = Rule;
const { length, [0]: maxComplexity } = this.ruleArguments;
return super.isEnabled() && length >= minLength && length <= maxLength && (maxComplexity === undefined || maxComplexity > 0);
}
}
export const getFailureMessage = (totalComplexity: number, maxComplexity = Rule.DEFAULT_MAX_COMPLEXITY): string => {
return sprintf(Rule.FAILURE_STRING, totalComplexity, maxComplexity);
};
const getTotalComplexity = (ast: AST): number => {
const expr = ((ast as ASTWithSource).source || '').replace(/\s/g, '');
const expressionParser = new Parser(new Lexer());
const astWithSource = expressionParser.parseAction(expr, null, 0);
const conditions: Binary[] = [];
let totalComplexity = 0;
let condition = astWithSource.ast as Binary;
if (condition.operation) {
totalComplexity++;
conditions.push(condition);
}
while (conditions.length > 0) {
condition = conditions.pop()!;
if (!condition.operation) {
continue;
}
if (condition.left instanceof Binary) {
totalComplexity++;
conditions.push(condition.left);
}
if (condition.right instanceof Binary) {
conditions.push(condition.right);
}
}
return totalComplexity;
};
class TemplateVisitorCtrl extends BasicTemplateAstVisitor {
visitDirectiveProperty(prop: BoundDirectivePropertyAst, context: BasicTemplateAstVisitor): any {
this.validateDirective(prop);
super.visitDirectiveProperty(prop, context);
}
private validateDirective(prop: BoundDirectivePropertyAst): void {
const { templateName, value } = prop;
if (templateName !== 'ngIf') {
return;
}
const maxComplexity: number = this.getOptions()[0] || Rule.DEFAULT_MAX_COMPLEXITY;
const totalComplexity = getTotalComplexity(value);
if (totalComplexity <= maxComplexity) {
return;
}
const {
sourceSpan: {
end: { offset: endOffset },
start: { offset: startOffset },
},
} = prop;
this.addFailureFromStartToEnd(startOffset, endOffset, getFailureMessage(totalComplexity, maxComplexity));
}
}