Skip to content

Commit e6ee246

Browse files
committed
feat: 🎸 detect OnPush change detection strategy in parser
1 parent 84accd6 commit e6ee246

4 files changed

Lines changed: 58 additions & 0 deletions

File tree

src/converters/component/component.converter.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe('Component converter', () => {
1111
selector: 'first',
1212
standalone: true,
1313
cva: true,
14+
onPush: false,
1415
inputs: [],
1516
outputs: [],
1617
implementation: '',
@@ -25,6 +26,7 @@ describe('Component converter', () => {
2526
selector: 'second',
2627
standalone: false,
2728
cva: true,
29+
onPush: true,
2830
inputs: [],
2931
outputs: [],
3032
implementation: '',
@@ -39,6 +41,7 @@ describe('Component converter', () => {
3941
selector: 'third',
4042
standalone: true,
4143
cva: false,
44+
onPush: false,
4245
inputs: [],
4346
outputs: [],
4447
implementation: '',

src/parser/component/component.model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface NgParselComponent extends NgParselOutput {
77
selector: string;
88
standalone: boolean;
99
cva: boolean;
10+
onPush: boolean;
1011
inputs: NgParselFieldDecorator[];
1112
outputs: NgParselFieldDecorator[];
1213
implementation: string;

src/parser/component/component.parser.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ describe('ComponentParser', () => {
3838
selector: 'my-test-comp',
3939
standalone: false,
4040
cva: false,
41+
onPush: false,
4142
template: inlineTemplate,
4243
styles: [styles],
4344
inputs: [
@@ -47,6 +48,7 @@ describe('ComponentParser', () => {
4748
type: 'string',
4849
initializer: undefined,
4950
field: '@Input() foo: string',
51+
jsDoc: undefined,
5052
},
5153
],
5254
outputs: [
@@ -56,6 +58,7 @@ describe('ComponentParser', () => {
5658
type: undefined,
5759
initializer: 'new EventEmitter()',
5860
field: '@Output() bar = new EventEmitter()',
61+
jsDoc: undefined,
5962
},
6063
],
6164
implementation,
@@ -103,6 +106,7 @@ describe('ComponentParser', () => {
103106
selector: 'my-test-comp',
104107
standalone: true,
105108
cva: false,
109+
onPush: false,
106110
template: inlineTemplate,
107111
styles: [styles],
108112
inputs: [
@@ -112,6 +116,7 @@ describe('ComponentParser', () => {
112116
type: 'string',
113117
initializer: undefined,
114118
field: '@Input() foo: string',
119+
jsDoc: undefined,
115120
},
116121
],
117122
outputs: [
@@ -121,6 +126,7 @@ describe('ComponentParser', () => {
121126
type: undefined,
122127
initializer: 'new EventEmitter()',
123128
field: '@Output() bar = new EventEmitter()',
129+
jsDoc: undefined,
124130
},
125131
],
126132
implementation,
@@ -195,4 +201,35 @@ describe('ComponentParser', () => {
195201

196202
expect(parseComponent(ast, 'foo.component.ts').template).toBe('');
197203
});
204+
205+
it('should detect OnPush change detection strategy', () => {
206+
const ast = tsquery.ast(`
207+
@Component({
208+
selector: 'my-test-comp',
209+
template: '<h1>Foo</h1>',
210+
styles: [],
211+
changeDetection: ChangeDetectionStrategy.OnPush
212+
})
213+
export class MyTestComponent {}
214+
`);
215+
216+
jest.spyOn(fs, 'readFileSync').mockReturnValue('');
217+
218+
expect(parseComponent(ast, 'foo.component.ts').onPush).toBeTruthy();
219+
});
220+
221+
it('should default to false for change detection strategy when not specified', () => {
222+
const ast = tsquery.ast(`
223+
@Component({
224+
selector: 'my-test-comp',
225+
template: '<h1>Foo</h1>',
226+
styles: []
227+
})
228+
export class MyTestComponent {}
229+
`);
230+
231+
jest.spyOn(fs, 'readFileSync').mockReturnValue('');
232+
233+
expect(parseComponent(ast, 'foo.component.ts').onPush).toBeFalsy();
234+
});
198235
});

src/parser/component/component.parser.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function parseComponent(ast: ts.SourceFile, componentFilePath: string): N
3333
standalone: componentDecorators.standalone || false,
3434
template: template,
3535
cva: isCva(ast),
36+
onPush: isOnPushChangeDetection(ast),
3637
styles: styles,
3738
implementation: componentImplementation,
3839
inputs: inputsAndOutputs.inputs,
@@ -48,6 +49,22 @@ function isCva(ast: ts.SourceFile): boolean {
4849
);
4950
}
5051

52+
function isOnPushChangeDetection(ast: ts.SourceFile): boolean {
53+
const changeDetectionNodes = tsquery(
54+
ast,
55+
'Decorator > CallExpression > ObjectLiteralExpression > PropertyAssignment:has(Identifier[name="changeDetection"])'
56+
);
57+
58+
if (changeDetectionNodes.length === 0) {
59+
return false;
60+
}
61+
62+
const changeDetectionNode = changeDetectionNodes[0] as ts.PropertyAssignment;
63+
const initializer = changeDetectionNode.initializer.getText();
64+
65+
return initializer.includes('ChangeDetectionStrategy.OnPush');
66+
}
67+
5168
function fetchFileContent(filePath: string | undefined, componentFilePath: string): string {
5269
if (!filePath) {
5370
return '';

0 commit comments

Comments
 (0)