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' ;
42import { 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' ;
65import { NgWalker } from './angular/ngWalker' ;
7- import * as compiler from '@angular/compiler' ;
8- import { Binary } from '@angular/compiler' ;
6+ import { BasicTemplateAstVisitor } from './angular/templates/basicTemplateAstVisitor' ;
97
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 = {
1410 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]' ] ,
1612 options : {
17- type : 'array' ,
1813 items : {
1914 type : 'string'
2015 } ,
16+ maxLength : 2 ,
2117 minLength : 0 ,
22- maxLength : 2
18+ type : 'array'
2319 } ,
24- optionExamples : [ 'true' , '[true, 4]' ] ,
2520 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' ,
2624 typescriptOnly : true
2725 } ;
2826
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 ;
3229
33- public apply ( sourceFile : ts . SourceFile ) : Lint . RuleFailure [ ] {
30+ apply ( sourceFile : SourceFile ) : RuleFailure [ ] {
3431 return this . applyWithWalker (
3532 new NgWalker ( sourceFile , this . getOptions ( ) , {
3633 templateVisitorCtrl : TemplateConditionalComplexityVisitor
@@ -39,53 +36,69 @@ export class Rule extends Lint.Rules.AbstractRule {
3936 }
4037}
4138
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 ;
8861 }
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 ) ;
8979 super . visitDirectiveProperty ( prop , context ) ;
9080 }
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+ }
91104}
0 commit comments