Skip to content

Commit 57cbcb1

Browse files
crisbetommalerba
authored andcommitted
build: add lint rule to catch SimpleChanges direct property access (#15659)
Adds a lint rule to help catch cases like #15206 until we can get something going through tsetse. Note this is only scoped to `ngOnChanges` and accesses of the `SimpleChanges` objects since that's where we've been having issues.
1 parent 6c2fda8 commit 57cbcb1

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as ts from 'typescript';
2+
import * as Lint from 'tslint';
3+
import * as tsutils from 'tsutils';
4+
5+
/**
6+
* Rule that catches cases where a property of a `SimpleChanges` object is accessed directly,
7+
* rather than through a literal. Accessing properties of `SimpleChanges` directly can break
8+
* when using Closure's property renaming.
9+
*/
10+
export class Rule extends Lint.Rules.TypedRule {
11+
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
12+
return this.applyWithWalker(new Walker(sourceFile, this.getOptions(), program));
13+
}
14+
}
15+
16+
class Walker extends Lint.ProgramAwareRuleWalker {
17+
visitMethodDeclaration(method: ts.MethodDeclaration) {
18+
// Walk through all of the `ngOnChanges` methods that have at least one parameter.
19+
if (method.name.getText() !== 'ngOnChanges' || !method.parameters.length || !method.body) {
20+
return;
21+
}
22+
23+
const walkChildren = (node: ts.Node) => {
24+
// Walk through all the nodes and look for property access expressions
25+
// (e.g. `changes.something`). Note that this is different from element access
26+
// 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+
}
41+
}
42+
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)) {
46+
node.forEachChild(walkChildren);
47+
}
48+
};
49+
50+
method.body.forEachChild(walkChildren);
51+
super.visitMethodDeclaration(method);
52+
}
53+
}

tslint.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
"no-import-spacing": true,
100100
"no-private-getters": true,
101101
"setters-after-getters": true,
102+
"ng-on-changes-property-access": true,
102103
"rxjs-imports": true,
103104
"require-breaking-change-version": true,
104105
"no-host-decorator-in-concrete": [

0 commit comments

Comments
 (0)