@@ -5,15 +5,16 @@ import * as minimatch from 'minimatch';
5
5
6
6
/**
7
7
* Rule that enforces certain decorator properties to be defined and to match a pattern.
8
- * Properties can be forbidden by prefixing their name with a `!`.
9
- * Supports whitelisting files via the third argument. E.g.
8
+ * Properties can be forbidden by prefixing their name with a `!`. Supports whitelisting
9
+ * files via the third argument, as well as validating all the arguments by passing in a regex . E.g.
10
10
*
11
11
* ```
12
12
* "validate-decorators": [true, {
13
13
* "Component": {
14
14
* "encapsulation": "\\.None$",
15
15
* "!styles": ".*"
16
- * }
16
+ * },
17
+ * "NgModule": "^(?!\\s*$).+"
17
18
* }, "src/lib"]
18
19
* ```
19
20
*/
@@ -31,7 +32,7 @@ type DecoratorRuleSet = {
31
32
32
33
/** Represents a map between decorator names and rule sets. */
33
34
type DecoratorRules = {
34
- [ key : string ] : DecoratorRuleSet
35
+ [ decorator : string ] : DecoratorRuleSet | RegExp
35
36
} ;
36
37
37
38
class Walker extends Lint . RuleWalker {
@@ -57,13 +58,7 @@ class Walker extends Lint.RuleWalker {
57
58
58
59
visitClassDeclaration ( node : ts . ClassDeclaration ) {
59
60
if ( this . _enabled && node . decorators ) {
60
- node . decorators
61
- . map ( decorator => decorator . expression as ts . CallExpression )
62
- . filter ( expression => {
63
- const args = expression . arguments ;
64
- return args && args . length && ( args [ 0 ] as ts . ObjectLiteralExpression ) . properties ;
65
- } )
66
- . forEach ( expression => this . _validatedDecorator ( expression ) ) ;
61
+ node . decorators . forEach ( decorator => this . _validatedDecorator ( decorator . expression ) ) ;
67
62
}
68
63
69
64
super . visitClassDeclaration ( node ) ;
@@ -82,8 +77,30 @@ class Walker extends Lint.RuleWalker {
82
77
return ;
83
78
}
84
79
80
+ // If the rule is a regex, extract the arguments as a string and run it against them.
81
+ if ( rules instanceof RegExp ) {
82
+ const decoratorText : string = decorator . getText ( ) ;
83
+ const openParenthesisIndex = decoratorText . indexOf ( '(' ) ;
84
+ const closeParenthesisIndex = decoratorText . lastIndexOf ( ')' ) ;
85
+ const argumentsText = openParenthesisIndex > - 1 ? decoratorText . substring (
86
+ openParenthesisIndex + 1 ,
87
+ closeParenthesisIndex > - 1 ? closeParenthesisIndex : decoratorText . length ) : '' ;
88
+
89
+ if ( ! rules . test ( argumentsText ) ) {
90
+ this . addFailureAtNode ( decorator . parent , `Expected decorator arguments to match "${ rules } "` ) ;
91
+ }
92
+
93
+ return ;
94
+ }
95
+
96
+ const args = decorator . arguments ;
97
+
98
+ if ( ! args || ! args . length || ! args [ 0 ] . properties ) {
99
+ return ;
100
+ }
101
+
85
102
// Extract the property names and values.
86
- const props = decorator . arguments [ 0 ] . properties
103
+ const props = args [ 0 ] . properties
87
104
. filter ( ( node : ts . PropertyAssignment ) => node . name && node . initializer )
88
105
. map ( ( node : ts . PropertyAssignment ) => ( {
89
106
name : node . name . getText ( ) ,
@@ -125,26 +142,32 @@ class Walker extends Lint.RuleWalker {
125
142
* @param config Config object passed in via the tslint.json.
126
143
* @returns Sanitized rules.
127
144
*/
128
- private _generateRules ( config : { [ key : string ] : { [ key : string ] : string } } ) : DecoratorRules {
145
+ private _generateRules ( config : { [ key : string ] : string | { [ key : string ] : string } } ) : DecoratorRules {
129
146
const output : DecoratorRules = { } ;
130
147
131
148
if ( config ) {
132
149
Object . keys ( config )
133
150
. filter ( decoratorName => Object . keys ( config [ decoratorName ] ) . length > 0 )
134
151
. forEach ( decoratorName => {
135
- output [ decoratorName ] = Object . keys ( config [ decoratorName ] ) . reduce ( ( accumulator , prop ) => {
136
- const isForbidden = prop . startsWith ( '!' ) ;
137
- const cleanName = isForbidden ? prop . slice ( 1 ) : prop ;
138
- const pattern = new RegExp ( config [ decoratorName ] [ prop ] ) ;
139
-
140
- if ( isForbidden ) {
141
- accumulator . forbidden [ cleanName ] = pattern ;
142
- } else {
143
- accumulator . required [ cleanName ] = pattern ;
144
- }
145
-
146
- return accumulator ;
147
- } , { required : { } , forbidden : { } } as DecoratorRuleSet ) ;
152
+ const decoratorConfig = config [ decoratorName ] ;
153
+
154
+ if ( typeof decoratorConfig === 'string' ) {
155
+ output [ decoratorName ] = new RegExp ( decoratorConfig ) ;
156
+ } else {
157
+ output [ decoratorName ] = Object . keys ( decoratorConfig ) . reduce ( ( rules , prop ) => {
158
+ const isForbidden = prop . startsWith ( '!' ) ;
159
+ const cleanName = isForbidden ? prop . slice ( 1 ) : prop ;
160
+ const pattern = new RegExp ( decoratorConfig [ prop ] ) ;
161
+
162
+ if ( isForbidden ) {
163
+ rules . forbidden [ cleanName ] = pattern ;
164
+ } else {
165
+ rules . required [ cleanName ] = pattern ;
166
+ }
167
+
168
+ return rules ;
169
+ } , { required : { } , forbidden : { } } as DecoratorRuleSet ) ;
170
+ }
148
171
} ) ;
149
172
}
150
173
0 commit comments