Skip to content

Commit 3d6b792

Browse files
committed
feat: cva for directives
1 parent d24517c commit 3d6b792

7 files changed

Lines changed: 66 additions & 15 deletions

File tree

src/converters/directive/directive.converter.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ describe('Directive converter', () => {
1010
className: 'first',
1111
selector: 'first',
1212
standalone: true,
13+
cva: false,
1314
inputs: [],
1415
outputs: [],
1516
implementation: '',
@@ -23,6 +24,7 @@ describe('Directive converter', () => {
2324
className: 'second',
2425
selector: 'second',
2526
standalone: false,
27+
cva: false,
2628
inputs: [],
2729
outputs: [],
2830
implementation: '',
@@ -36,6 +38,7 @@ describe('Directive converter', () => {
3638
className: 'third',
3739
selector: 'third',
3840
standalone: true,
41+
cva: false,
3942
inputs: [],
4043
outputs: [],
4144
implementation: '',

src/parser/component/component.parser.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import { readFileSync } from 'fs';
33
import * as ts from 'typescript';
44
import { tsquery } from '@phenomnomnominal/tsquery';
55

6-
import { parseInputsAndOutputs } from '../shared/parser/field-decorator.parser.js';
7-
import { parseExplicitPublicMethods, parseMethods } from '../shared/parser/method.parser.js';
6+
import { isCva } from '../shared/parser/cva.parser.js';
87
import { NgParselOutputType } from '../shared/model/types.model.js';
8+
import { parseInputsAndOutputs } from '../shared/parser/field-decorator.parser.js';
9+
import { getDecoratorProperties } from '../shared/parser/decorator.parser.js';
10+
import { parseExplicitPublicFields } from '../shared/parser/field.parser.js';
911
import { parseClassName, parseClassJsDoc } from '../shared/parser/class.parser.js';
12+
import { parseExplicitPublicMethods, parseMethods } from '../shared/parser/method.parser.js';
1013

1114
import { NgParselComponent } from './component.model.js';
12-
import { getDecoratorProperties } from '../shared/parser/decorator.parser.js';
13-
import { parseExplicitPublicFields } from '../shared/parser/field.parser.js';
1415

1516
export function parseComponent(ast: ts.SourceFile, componentFilePath: string): NgParselComponent {
1617
const componentDecorators = getDecoratorProperties(ast);
@@ -46,13 +47,6 @@ export function parseComponent(ast: ts.SourceFile, componentFilePath: string): N
4647
};
4748
}
4849

49-
function isCva(ast: ts.SourceFile): boolean {
50-
return (
51-
tsquery(ast, 'HeritageClause > ExpressionWithTypeArguments > Identifier:has([escapedText="ControlValueAccessor"])')
52-
.length > 0
53-
);
54-
}
55-
5650
function isOnPushChangeDetection(ast: ts.SourceFile): boolean {
5751
const changeDetectionNodes = tsquery(
5852
ast,

src/parser/directive/directive.model.ts

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

src/parser/directive/directive.parser.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('DirectiveParser', () => {
3131
filePath,
3232
selector: '[myTestDirective]',
3333
standalone: false,
34+
cva: false,
3435
inputs: [
3536
{
3637
decorator: '@Input()',
@@ -88,6 +89,7 @@ describe('DirectiveParser', () => {
8889
filePath,
8990
selector: '[myTestDirective]',
9091
standalone: true,
92+
cva: false,
9193
inputs: [
9294
{
9395
decorator: '@Input()',

src/parser/directive/directive.parser.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { readFileSync } from 'fs';
22
import * as ts from 'typescript';
33

4-
import { parseClassName, parseClassJsDoc } from '../shared/parser/class.parser.js';
4+
import { isCva } from '../shared/parser/cva.parser.js';
55
import { NgParselOutputType } from '../shared/model/types.model.js';
6-
import { getDecoratorProperties } from '../shared/parser/decorator.parser.js';
76
import { parseInputsAndOutputs } from '../shared/parser/field-decorator.parser.js';
7+
import { getDecoratorProperties } from '../shared/parser/decorator.parser.js';
8+
import { parseExplicitPublicFields } from '../shared/parser/field.parser.js';
9+
import { parseClassName, parseClassJsDoc } from '../shared/parser/class.parser.js';
10+
import { parseExplicitPublicMethods, parseMethods } from '../shared/parser/method.parser.js';
811

912
import { NgParselDirective } from './directive.model.js';
10-
import { parseExplicitPublicMethods, parseMethods } from '../shared/parser/method.parser.js';
11-
import { parseExplicitPublicFields } from '../shared/parser/field.parser.js';
1213

1314
export function parseDirective(ast: ts.SourceFile, directiveFilePath: string): NgParselDirective {
1415
const directiveDecorators = getDecoratorProperties(ast);
@@ -23,6 +24,7 @@ export function parseDirective(ast: ts.SourceFile, directiveFilePath: string): N
2324
filePath: directiveFilePath,
2425
selector: directiveDecorators.selector as string,
2526
standalone: directiveDecorators.standalone || false,
27+
cva: isCva(ast),
2628
implementation: directiveImplementation,
2729
inputs: inputsAndOutputs.inputs,
2830
outputs: inputsAndOutputs.outputs,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { tsquery } from '@phenomnomnominal/tsquery';
2+
import { isCva } from './cva.parser.js';
3+
4+
describe('isCva', () => {
5+
it('detects ControlValueAccessor on a component AST that implements it', () => {
6+
const ast = tsquery.ast(`
7+
@Component({ selector: 'cva-comp', template: '' })
8+
export class MyComponent implements ControlValueAccessor {}
9+
`);
10+
11+
expect(isCva(ast)).toEqual(true);
12+
});
13+
14+
it('does not detect ControlValueAccessor on a component AST that does not implement it', () => {
15+
const ast = tsquery.ast(`
16+
@Component({ selector: 'no-cva-comp', template: '' })
17+
export class MyComponent {}
18+
`);
19+
20+
expect(isCva(ast)).toEqual(false);
21+
});
22+
23+
it('detects ControlValueAccessor on a directive AST that implements it', () => {
24+
const ast = tsquery.ast(`
25+
@Directive({ selector: '[myDir]' })
26+
export class MyDirective implements ControlValueAccessor {}
27+
`);
28+
29+
expect(isCva(ast)).toEqual(true);
30+
});
31+
32+
it('does not detect ControlValueAccessor on a directive AST that does not implement it', () => {
33+
const ast = tsquery.ast(`
34+
@Directive({ selector: '[myDir]' })
35+
export class MyDirective {}
36+
`);
37+
38+
expect(isCva(ast)).toEqual(false);
39+
});
40+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as ts from 'typescript';
2+
import { tsquery } from '@phenomnomnominal/tsquery';
3+
4+
export function isCva(ast: ts.SourceFile): boolean {
5+
return (
6+
tsquery(ast, 'HeritageClause > ExpressionWithTypeArguments > Identifier:has([escapedText="ControlValueAccessor"])')
7+
.length > 0
8+
);
9+
}

0 commit comments

Comments
 (0)