11import * as ts from 'typescript' ;
22import * as Lint from 'tslint' ;
3- import * as tsutils from 'tsutils' ;
43
54/**
65 * Rule that catches cases where a property of a `SimpleChanges` object is accessed directly,
@@ -24,30 +23,45 @@ class Walker extends Lint.ProgramAwareRuleWalker {
2423 // Walk through all the nodes and look for property access expressions
2524 // (e.g. `changes.something`). Note that this is different from element access
2625 // 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 } ']).` ) ;
4134 }
4235
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' ) {
4639 node . forEachChild ( walkChildren ) ;
4740 }
4841 } ;
4942
5043 method . body . forEachChild ( walkChildren ) ;
5144 super . visitMethodDeclaration ( method ) ;
5245 }
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+ }
5367}
0 commit comments