1
1
import * as ts from 'typescript' ;
2
2
import * as Lint from 'tslint' ;
3
- import * as tsutils from 'tsutils' ;
4
3
5
4
/**
6
5
* Rule that catches cases where a property of a `SimpleChanges` object is accessed directly,
@@ -24,30 +23,45 @@ class Walker extends Lint.ProgramAwareRuleWalker {
24
23
// Walk through all the nodes and look for property access expressions
25
24
// (e.g. `changes.something`). Note that this is different from element access
26
25
// expressions which look like `changes['something']`.
27
- if ( tsutils . isPropertyAccessExpression ( node ) ) {
28
- const symbol = this . getTypeChecker ( ) . getTypeAtLocation ( node . expression ) . symbol ;
29
-
30
- // Add a failure if we're trying to access a property on a SimpleChanges object
31
- // directly, because it can cause issues with Closure's property renaming.
32
- if ( symbol && symbol . name === 'SimpleChanges' ) {
33
- const expressionName = node . expression . getText ( ) ;
34
- const propName = node . name . getText ( ) ;
35
-
36
- this . addFailureAtNode ( node , 'Accessing properties of SimpleChanges objects directly ' +
37
- 'is not allowed. Use index access instead (e.g. ' +
38
- `${ expressionName } .${ propName } should be ` +
39
- `${ expressionName } ['${ propName } ']).` ) ;
40
- }
26
+ if ( ts . isPropertyAccessExpression ( node ) && this . _isSimpleChangesAccess ( method , node ) ) {
27
+ const expressionName = node . expression . getText ( ) ;
28
+ const propName = node . name . getText ( ) ;
29
+
30
+ this . addFailureAtNode ( node , 'Accessing properties of SimpleChanges objects directly ' +
31
+ 'is not allowed. Use index access instead (e.g. ' +
32
+ `${ expressionName } .${ propName } should be ` +
33
+ `${ expressionName } ['${ propName } ']).` ) ;
41
34
}
42
35
43
- // Don't walk the property accesses inside of call expressions. This prevents us
44
- // from flagging cases like `changes.hasOwnProperty('something')` incorrectly.
45
- if ( ! tsutils . isCallExpression ( node ) ) {
36
+ // Don't walk calls to `hasOwnProperty` since they can be used for null checking.
37
+ if ( ! ts . isCallExpression ( node ) || ! ts . isPropertyAccessExpression ( node . expression ) ||
38
+ ! ts . isIdentifier ( node . expression . name ) || node . expression . name . text !== 'hasOwnProperty' ) {
46
39
node . forEachChild ( walkChildren ) ;
47
40
}
48
41
} ;
49
42
50
43
method . body . forEachChild ( walkChildren ) ;
51
44
super . visitMethodDeclaration ( method ) ;
52
45
}
46
+
47
+ /** Checks whether a property access is operating on a `SimpleChanges` object. */
48
+ private _isSimpleChangesAccess ( method : ts . MethodDeclaration , node : ts . PropertyAccessExpression ) {
49
+ const changesParam = method . parameters [ 0 ] ;
50
+ const changesName = changesParam && ts . isParameter ( changesParam ) &&
51
+ ts . isIdentifier ( changesParam . name ) ? changesParam . name . text : null ;
52
+ const receiverName = ts . isIdentifier ( node . expression ) ? node . expression . text : null ;
53
+
54
+ // Try to resolve based on the name. This should be quicker and more robust since it doesn't
55
+ // require the type checker to be present and to have been configured correctly. Note that
56
+ // we filter out property accesses inside of other property accesses since we only want to
57
+ // look at top-level ones so that we don't flag something like `foo.bar.changes`.
58
+ if ( changesName && receiverName && changesName === receiverName &&
59
+ ! ts . isPropertyAccessExpression ( node . parent ) ) {
60
+ return true ;
61
+ }
62
+
63
+ // Fall back to trying to resolve using the type checker.
64
+ const symbol = this . getTypeChecker ( ) . getTypeAtLocation ( node . expression ) . symbol ;
65
+ return symbol != null && symbol . name === 'SimpleChanges' ;
66
+ }
53
67
}
0 commit comments