1
- import * as Lint from 'tslint' ;
2
- import * as ts from 'typescript' ;
3
- import * as ast from '@angular/compiler' ;
1
+ import { AST , ASTWithSource , Binary , BoundDirectivePropertyAst , Lexer , Parser } from '@angular/compiler' ;
4
2
import { sprintf } from 'sprintf-js' ;
5
- import { BasicTemplateAstVisitor } from './angular/templates/basicTemplateAstVisitor' ;
3
+ import { IRuleMetadata , RuleFailure , Rules } from 'tslint/lib' ;
4
+ import { SourceFile } from 'typescript/lib/typescript' ;
6
5
import { NgWalker } from './angular/ngWalker' ;
7
- import * as compiler from '@angular/compiler' ;
8
- import { Binary } from '@angular/compiler' ;
6
+ import { BasicTemplateAstVisitor } from './angular/templates/basicTemplateAstVisitor' ;
9
7
10
- export class Rule extends Lint . Rules . AbstractRule {
11
- public static metadata : Lint . IRuleMetadata = {
12
- ruleName : 'template-conditional-complexity' ,
13
- type : 'functionality' ,
8
+ export class Rule extends Rules . AbstractRule {
9
+ static readonly metadata : IRuleMetadata = {
14
10
description : "The condition complexity shouldn't exceed a rational limit in a template." ,
15
- rationale : 'An important complexity complicates the tests and the maintenance.' ,
11
+ optionExamples : [ 'true' , '[true, 4]' ] ,
16
12
options : {
17
- type : 'array' ,
18
13
items : {
19
14
type : 'string'
20
15
} ,
16
+ maxLength : 2 ,
21
17
minLength : 0 ,
22
- maxLength : 2
18
+ type : 'array'
23
19
} ,
24
- optionExamples : [ 'true' , '[true, 4]' ] ,
25
20
optionsDescription : 'Determine the maximum number of Boolean operators allowed.' ,
21
+ rationale : 'An important complexity complicates the tests and the maintenance.' ,
22
+ ruleName : 'template-conditional-complexity' ,
23
+ type : 'maintainability' ,
26
24
typescriptOnly : true
27
25
} ;
28
26
29
- static COMPLEXITY_FAILURE_STRING = "The condition complexity (cost '%s') exceeded the defined limit (cost '%s'). The conditional expression should be moved into the component." ;
30
-
31
- static COMPLEXITY_MAX = 3 ;
27
+ static readonly FAILURE_STRING = "The condition complexity (cost '%s') exceeded the defined limit (cost '%s'). The conditional expression should be moved into the component." ;
28
+ static readonly DEFAULT_MAX_COMPLEXITY = 3 ;
32
29
33
- public apply ( sourceFile : ts . SourceFile ) : Lint . RuleFailure [ ] {
30
+ apply ( sourceFile : SourceFile ) : RuleFailure [ ] {
34
31
return this . applyWithWalker (
35
32
new NgWalker ( sourceFile , this . getOptions ( ) , {
36
33
templateVisitorCtrl : TemplateConditionalComplexityVisitor
@@ -39,53 +36,69 @@ export class Rule extends Lint.Rules.AbstractRule {
39
36
}
40
37
}
41
38
42
- class TemplateConditionalComplexityVisitor extends BasicTemplateAstVisitor {
43
- visitDirectiveProperty ( prop : ast . BoundDirectivePropertyAst , context : BasicTemplateAstVisitor ) : any {
44
- if ( prop . sourceSpan ) {
45
- const directive = ( < any > prop . sourceSpan ) . toString ( ) ;
46
-
47
- if ( directive . startsWith ( '*ngIf' ) ) {
48
- // extract expression and drop characters new line and quotes
49
- const expr = directive
50
- . split ( / \* n g I f \s * = \s * / ) [ 1 ]
51
- . slice ( 1 , - 1 )
52
- . replace ( / [ \n \r ] / g, '' ) ;
53
-
54
- const expressionParser = new compiler . Parser ( new compiler . Lexer ( ) ) ;
55
- const ast = expressionParser . parseAction ( expr , null ) ;
56
-
57
- let complexity = 0 ;
58
- let conditions : Array < Binary > = [ ] ;
59
- let condition = ast . ast as Binary ;
60
- if ( condition . operation ) {
61
- complexity ++ ;
62
- conditions . push ( condition ) ;
63
- }
64
-
65
- while ( conditions . length > 0 ) {
66
- condition = conditions . pop ( ) ;
67
- if ( condition . operation ) {
68
- if ( condition . left instanceof Binary ) {
69
- complexity ++ ;
70
- conditions . push ( condition . left as Binary ) ;
71
- }
72
-
73
- if ( condition . right instanceof Binary ) {
74
- conditions . push ( condition . right as Binary ) ;
75
- }
76
- }
77
- }
78
- const options = this . getOptions ( ) ;
79
- const complexityMax : number = options . length ? options [ 0 ] : Rule . COMPLEXITY_MAX ;
80
-
81
- if ( complexity > complexityMax ) {
82
- const span = prop . sourceSpan ;
83
- let failureConfig : string [ ] = [ String ( complexity ) , String ( complexityMax ) ] ;
84
- failureConfig . unshift ( Rule . COMPLEXITY_FAILURE_STRING ) ;
85
- this . addFailure ( this . createFailure ( span . start . offset , span . end . offset - span . start . offset , sprintf . apply ( this , failureConfig ) ) ) ;
86
- }
87
- }
39
+ export const getFailureMessage = ( totalComplexity : number , maxComplexity = Rule . DEFAULT_MAX_COMPLEXITY ) : string => {
40
+ return sprintf ( Rule . FAILURE_STRING , totalComplexity , maxComplexity ) ;
41
+ } ;
42
+
43
+ const getTotalComplexity = ( ast : AST ) : number => {
44
+ const expr = ( ast as ASTWithSource ) . source . replace ( / \s / g, '' ) ;
45
+ const expressionParser = new Parser ( new Lexer ( ) ) ;
46
+ const astWithSource = expressionParser . parseAction ( expr , null ) ;
47
+ const conditions : Binary [ ] = [ ] ;
48
+ let totalComplexity = 0 ;
49
+ let condition = astWithSource . ast as Binary ;
50
+
51
+ if ( condition . operation ) {
52
+ totalComplexity ++ ;
53
+ conditions . push ( condition ) ;
54
+ }
55
+
56
+ while ( conditions . length > 0 ) {
57
+ condition = conditions . pop ( ) ;
58
+
59
+ if ( ! condition . operation ) {
60
+ continue ;
88
61
}
62
+
63
+ if ( condition . left instanceof Binary ) {
64
+ totalComplexity ++ ;
65
+ conditions . push ( condition . left ) ;
66
+ }
67
+
68
+ if ( condition . right instanceof Binary ) {
69
+ conditions . push ( condition . right ) ;
70
+ }
71
+ }
72
+
73
+ return totalComplexity ;
74
+ } ;
75
+
76
+ class TemplateConditionalComplexityVisitor extends BasicTemplateAstVisitor {
77
+ visitDirectiveProperty ( prop : BoundDirectivePropertyAst , context : BasicTemplateAstVisitor ) : any {
78
+ this . validateDirective ( prop ) ;
89
79
super . visitDirectiveProperty ( prop , context ) ;
90
80
}
81
+
82
+ private validateDirective ( prop : BoundDirectivePropertyAst ) : void {
83
+ const { templateName, value } = prop ;
84
+
85
+ if ( templateName !== 'ngIf' ) {
86
+ return ;
87
+ }
88
+
89
+ const maxComplexity : number = this . getOptions ( ) [ 0 ] || Rule . DEFAULT_MAX_COMPLEXITY ;
90
+ const totalComplexity = getTotalComplexity ( value ) ;
91
+
92
+ if ( totalComplexity <= maxComplexity ) {
93
+ return ;
94
+ }
95
+
96
+ const {
97
+ sourceSpan : {
98
+ end : { offset : endOffset } ,
99
+ start : { offset : startOffset }
100
+ }
101
+ } = prop ;
102
+ this . addFailureFromStartToEnd ( startOffset , endOffset , getFailureMessage ( totalComplexity , maxComplexity ) ) ;
103
+ }
91
104
}
0 commit comments