1
1
import { sprintf } from 'sprintf-js' ;
2
- import { IRuleMetadata , RuleFailure } from 'tslint' ;
2
+ import { IRuleMetadata , RuleFailure , WalkContext } from 'tslint' ;
3
3
import { AbstractRule } from 'tslint/lib/rules' ;
4
4
import { dedent } from 'tslint/lib/utils' ;
5
- import { createNodeArray , Decorator , isClassDeclaration , SourceFile } from 'typescript' ;
6
- import { NgWalker } from './angular/ngWalker' ;
5
+ import {
6
+ AccessorDeclaration ,
7
+ createNodeArray ,
8
+ Decorator ,
9
+ forEachChild ,
10
+ isAccessor ,
11
+ isMethodDeclaration ,
12
+ isParameterPropertyDeclaration ,
13
+ isPropertyDeclaration ,
14
+ MethodDeclaration ,
15
+ Node ,
16
+ ParameterPropertyDeclaration ,
17
+ PropertyDeclaration ,
18
+ SourceFile
19
+ } from 'typescript' ;
20
+ import { isNotNullOrUndefined } from './util/isNotNullOrUndefined' ;
7
21
import {
8
22
ANGULAR_CLASS_DECORATOR_MAPPER ,
9
23
AngularClassDecoratorKeys ,
10
24
AngularClassDecorators ,
11
- AngularInnerClassDecoratorKeys ,
12
25
AngularInnerClassDecorators ,
13
26
getDecoratorName ,
14
27
getNextToLastParentNode ,
@@ -17,77 +30,83 @@ import {
17
30
} from './util/utils' ;
18
31
19
32
interface FailureParameters {
20
- readonly className : string ;
21
33
readonly classDecoratorName : AngularClassDecoratorKeys ;
22
- readonly innerClassDecoratorName : AngularInnerClassDecoratorKeys ;
23
34
}
24
35
25
- export const getFailureMessage = ( failureParameters : FailureParameters ) : string =>
26
- sprintf (
27
- Rule . FAILURE_STRING ,
28
- failureParameters . innerClassDecoratorName ,
29
- failureParameters . className ,
30
- failureParameters . classDecoratorName
31
- ) ;
36
+ type DeclarationLike = AccessorDeclaration | MethodDeclaration | ParameterPropertyDeclaration | PropertyDeclaration ;
37
+
38
+ export const getFailureMessage = ( failureParameters : FailureParameters ) : string => {
39
+ return sprintf ( Rule . FAILURE_STRING , failureParameters . classDecoratorName ) ;
40
+ } ;
32
41
33
42
export class Rule extends AbstractRule {
34
43
static readonly metadata : IRuleMetadata = {
35
- description : 'Ensures that classes use allowed decorator in its body.' ,
44
+ description : 'Ensures that classes use contextual decorators in its body.' ,
36
45
options : null ,
37
46
optionsDescription : 'Not configurable.' ,
38
47
rationale : dedent `
39
- Some decorators can only be used in certain class types.
40
- For example, an @${ AngularInnerClassDecorators . Input } should not be used
41
- in an @${ AngularClassDecorators . Injectable } class .
48
+ Some decorators should only be used in certain class types. For example,
49
+ the decorator @${ AngularInnerClassDecorators . Input } () should
50
+ not be used in a class decorated with @${ AngularClassDecorators . Injectable } () .
42
51
` ,
43
52
ruleName : 'contextual-decorator' ,
44
53
type : 'functionality' ,
45
54
typescriptOnly : true
46
55
} ;
47
56
48
- static readonly FAILURE_STRING = 'The decorator "%s" is not allowed for class "%s" because it is decorated with "%s "' ;
57
+ static readonly FAILURE_STRING = 'Decorator out of context for "@%s() "' ;
49
58
50
59
apply ( sourceFile : SourceFile ) : RuleFailure [ ] {
51
- const walker = new Walker ( sourceFile , this . getOptions ( ) ) ;
52
-
53
- return this . applyWithWalker ( walker ) ;
60
+ return this . applyWithFunction ( sourceFile , walk ) ;
54
61
}
55
62
}
56
63
57
- class Walker extends NgWalker {
58
- protected visitMethodDecorator ( decorator : Decorator ) : void {
59
- this . validateDecorator ( decorator ) ;
60
- super . visitMethodDecorator ( decorator ) ;
61
- }
64
+ const callbackHandler = ( walkContext : WalkContext , node : Node ) : void => {
65
+ if ( isDeclarationLike ( node ) ) validateDeclaration ( walkContext , node ) ;
66
+ } ;
62
67
63
- protected visitPropertyDecorator ( decorator : Decorator ) : void {
64
- this . validateDecorator ( decorator ) ;
65
- super . visitPropertyDecorator ( decorator ) ;
66
- }
68
+ const getClassDecoratorName = ( klass : Node ) : AngularClassDecoratorKeys | undefined => {
69
+ return createNodeArray ( klass . decorators )
70
+ . map ( getDecoratorName )
71
+ . filter ( isNotNullOrUndefined )
72
+ . find ( isAngularClassDecorator ) ;
73
+ } ;
67
74
68
- private validateDecorator ( decorator : Decorator ) : void {
69
- const klass = getNextToLastParentNode ( decorator ) ;
75
+ const isDeclarationLike = ( node : Node ) : node is DeclarationLike => {
76
+ return isAccessor ( node ) || isMethodDeclaration ( node ) || isParameterPropertyDeclaration ( node ) || isPropertyDeclaration ( node ) ;
77
+ } ;
70
78
71
- if ( ! isClassDeclaration ( klass ) || ! klass . name ) return ;
79
+ const validateDeclaration = ( walkContext : WalkContext , node : DeclarationLike ) : void => {
80
+ const klass = getNextToLastParentNode ( node ) ;
81
+ const classDecoratorName = getClassDecoratorName ( klass ) ;
72
82
73
- const classDecoratorName = createNodeArray ( klass . decorators )
74
- . map ( x => x . expression . getText ( ) )
75
- . map ( x => x . replace ( / [ ^ a - z A - Z ] / g, '' ) )
76
- . find ( isAngularClassDecorator ) ;
83
+ if ( ! classDecoratorName ) return ;
77
84
78
- if ( ! classDecoratorName ) return ;
85
+ createNodeArray ( node . decorators ) . forEach ( decorator => validateDecorator ( walkContext , decorator , classDecoratorName ) ) ;
86
+ } ;
79
87
80
- const innerClassDecoratorName = getDecoratorName ( decorator ) ;
88
+ const validateDecorator = ( walkContext : WalkContext , node : Decorator , classDecoratorName : AngularClassDecoratorKeys ) : void => {
89
+ const decoratorName = getDecoratorName ( node ) ;
81
90
82
- if ( ! innerClassDecoratorName || ! isAngularInnerClassDecorator ( innerClassDecoratorName ) ) return ;
91
+ if ( ! decoratorName || ! isAngularInnerClassDecorator ( decoratorName ) ) return ;
83
92
84
- const allowedDecorators = ANGULAR_CLASS_DECORATOR_MAPPER . get ( classDecoratorName ) ;
93
+ const allowedDecorators = ANGULAR_CLASS_DECORATOR_MAPPER . get ( classDecoratorName ) ;
85
94
86
- if ( ! allowedDecorators || allowedDecorators . has ( innerClassDecoratorName ) ) return ;
95
+ if ( ! allowedDecorators || allowedDecorators . has ( decoratorName ) ) return ;
87
96
88
- const className = klass . name . getText ( ) ;
89
- const failure = getFailureMessage ( { classDecoratorName, className, innerClassDecoratorName } ) ;
97
+ const failure = getFailureMessage ( { classDecoratorName } ) ;
90
98
91
- this . addFailureAtNode ( decorator , failure ) ;
92
- }
93
- }
99
+ walkContext . addFailureAtNode ( node , failure ) ;
100
+ } ;
101
+
102
+ const walk = ( walkContext : WalkContext ) : void => {
103
+ const { sourceFile } = walkContext ;
104
+
105
+ const callback = ( node : Node ) : void => {
106
+ callbackHandler ( walkContext , node ) ;
107
+
108
+ forEachChild ( node , callback ) ;
109
+ } ;
110
+
111
+ forEachChild ( sourceFile , callback ) ;
112
+ } ;
0 commit comments