Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a23ccde

Browse filesBrowse files
mattlewis92mgechev
authored andcommitted
feat(component-change-detection): add change detection strategy rule (#737)
Closes #135
1 parent a75c204 commit a23ccde
Copy full SHA for a23ccde

3 files changed

+127Lines changed: 127 additions & 0 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file
+79Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { Utils } from 'tslint/lib';
2+
import * as Lint from 'tslint';
3+
import * as ts from 'typescript';
4+
import { getDecoratorArgument, getDecoratorName } from './util/utils';
5+
import { sprintf } from 'sprintf-js';
6+
import { NgWalker } from './angular';
7+
8+
export class Rule extends Lint.Rules.AbstractRule {
9+
public static metadata: Lint.IRuleMetadata = {
10+
ruleName: 'component-change-detection',
11+
type: 'functionality',
12+
description: 'Enforce the preferred component change detection type as ChangeDetectionStrategy.OnPush.',
13+
descriptionDetails: Utils.dedent`
14+
See more at https://angular.io/api/core/ChangeDetectionStrategy
15+
`,
16+
options: null,
17+
optionsDescription: 'Not configurable.',
18+
rationale: Utils.dedent`
19+
By using OnPush for change detection, Angular will only run a change detection cycle when that
20+
components inputs or outputs change
21+
`,
22+
typescriptOnly: true
23+
};
24+
25+
static CHANGE_DETECTION_INVALID_FAILURE =
26+
'The changeDetection value of the component "%s" should be set to ChangeDetectionStrategy.OnPush';
27+
28+
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
29+
return this.applyWithWalker(new ComponentChangeDetectionValidatorWalker(sourceFile, this));
30+
}
31+
}
32+
33+
export class ComponentChangeDetectionValidatorWalker extends NgWalker {
34+
constructor(sourceFile: ts.SourceFile, private rule: Rule) {
35+
super(sourceFile, rule.getOptions());
36+
}
37+
38+
visitClassDeclaration(node: ts.ClassDeclaration) {
39+
ts.createNodeArray(node.decorators).forEach(this.validateDecorator.bind(this, node.name!.text));
40+
super.visitClassDeclaration(node);
41+
}
42+
43+
private validateDecorator(className: string, decorator: ts.Decorator) {
44+
const argument = getDecoratorArgument(decorator)!;
45+
const name = getDecoratorName(decorator);
46+
47+
// Run only for Components
48+
if (name === 'Component') {
49+
this.validateComponentChangeDetection(className, decorator, argument);
50+
}
51+
}
52+
53+
private validateComponentChangeDetection(className: string, decorator: ts.Decorator, arg: ts.Node) {
54+
if (!ts.isObjectLiteralExpression(arg)) {
55+
return;
56+
}
57+
58+
const changeDetectionAssignment = arg.properties
59+
.filter(prop => ts.isPropertyAssignment(prop) && this.validateProperty(prop))
60+
.map(prop => (ts.isPropertyAssignment(prop) ? prop.initializer : undefined))
61+
.filter(Boolean)[0] as ts.PropertyAccessExpression;
62+
63+
if (!changeDetectionAssignment) {
64+
this.addFailureAtNode(decorator, sprintf(Rule.CHANGE_DETECTION_INVALID_FAILURE, className));
65+
} else {
66+
if (!this.validateChangeDetectionType(changeDetectionAssignment!.name.escapedText as string)) {
67+
this.addFailureAtNode(changeDetectionAssignment, sprintf(Rule.CHANGE_DETECTION_INVALID_FAILURE, className));
68+
}
69+
}
70+
}
71+
72+
private validateProperty(p: ts.PropertyAssignment): boolean {
73+
return ts.isIdentifier(p.name) && p.name.text === 'changeDetection';
74+
}
75+
76+
private validateChangeDetectionType(changeDetectionValue: string): boolean {
77+
return changeDetectionValue === 'OnPush';
78+
}
79+
}
Collapse file

‎src/index.ts‎

Copy file name to clipboardExpand all lines: src/index.ts
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export { Rule as UsePipeDecoratorRule } from './usePipeDecoratorRule';
3939
export { Rule as UsePipeTransformInterfaceRule } from './usePipeTransformInterfaceRule';
4040
export { Rule as UseViewEncapsulationRule } from './useViewEncapsulationRule';
4141
export { Rule as RelativePathExternalResourcesRule } from './relativeUrlPrefixRule';
42+
export { Rule as ComponentChangeDetectionRule } from './componentChangeDetectionRule';
4243

4344
export * from './angular';
4445

Collapse file
+47Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { assertSuccess, assertAnnotated } from './testHelper';
2+
3+
describe('component-change-detection', () => {
4+
describe('invalid component change detection', () => {
5+
it('should fail when component used without preferred change detection type', () => {
6+
let source = `
7+
@Component({
8+
changeDetection: ChangeDetectionStrategy.Default
9+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10+
})
11+
class Test {}
12+
`;
13+
assertAnnotated({
14+
ruleName: 'component-change-detection',
15+
message: 'The changeDetection value of the component "Test" should be set to ChangeDetectionStrategy.OnPush',
16+
source
17+
});
18+
});
19+
20+
it('should fail when component change detection is not set', () => {
21+
let source = `
22+
@Component({
23+
~~~~~~~~~~~~
24+
selector: 'foo'
25+
})
26+
~~
27+
class Test {}`;
28+
assertAnnotated({
29+
ruleName: 'component-change-detection',
30+
message: 'The changeDetection value of the component "Test" should be set to ChangeDetectionStrategy.OnPush',
31+
source
32+
});
33+
});
34+
});
35+
36+
describe('valid component selector', () => {
37+
it('should succeed when a valid change detection strategy is set on @Component', () => {
38+
let source = `
39+
@Component({
40+
changeDetection: ChangeDetectionStrategy.OnPush
41+
})
42+
class Test {}
43+
`;
44+
assertSuccess('component-change-detection', source);
45+
});
46+
});
47+
});

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.