Skip to content

Commit 6348b6c

Browse files
committed
build: ngOnChanges property access rule not working
Follow-up to #21074. We have a lint rule that flags property accesses like `changes.foo` where `changes` is a `SimpleChanges` object, because they'll break under Closure compiler. The rule depends on the TS type checker to identify the objects, but it seems like somewhere along the way, tslint changed how they construct the `Program` which removed the necessary type information. These changes add a less complicated fallback which looks at whether the name of the receiver of a property access is the same as the name of the first method parameter.
1 parent 4399757 commit 6348b6c

File tree

1 file changed

+32
-18
lines changed

1 file changed

+32
-18
lines changed
Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as ts from 'typescript';
22
import * 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

Comments
 (0)