Skip to content

Commit

Permalink
feat: 🎸 parse public service fields
Browse files Browse the repository at this point in the history
  • Loading branch information
nivekcode committed Aug 6, 2024
1 parent ecd5514 commit 404dbc3
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/parser/services/service.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { NgParselOutput } from '../shared/model/types.model.js';
import { NgParselMethod } from '../shared/model/method.model.js';
import { NgParselField } from '../shared/model/field.model.js';

export interface NgParselService extends NgParselOutput {
fieldsPublicExplicit: NgParselField[];
methodsPublicExplicit: NgParselMethod[];
}
48 changes: 48 additions & 0 deletions src/parser/services/service.parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe('ServiceParser', () => {
type: NgParselOutputType.SERVICE,
className,
filePath,
fieldsPublicExplicit: [],
methodsPublicExplicit: [
{
name: 'foo',
Expand Down Expand Up @@ -59,6 +60,53 @@ describe('ServiceParser', () => {
type: NgParselOutputType.SERVICE,
className,
filePath,
fieldsPublicExplicit: [],
methodsPublicExplicit: [],
};

const parselOutput = parseService(ast, filePath);
expect(parselOutput).toEqual(expectedOutput);
});

it('should parse a Angular Service with explicit public fields', function () {
const className = 'MyTestService';
const mockService = `
export class ${className} {
public counter = signal(0);
public counter$ = new BehaviorSubject(0);
public increment = new Subject<number>();
private notVisible = 'blub'
foo(bar: string): string {
}
bar(foo: string): string {}
}
`;
const ast = tsquery.ast(mockService);
const filePath = 'foo.service.ts';

const expectedOutput = {
type: NgParselOutputType.SERVICE,
className,
filePath,
fieldsPublicExplicit: [
{
name: 'counter',
type: 'inferred',
value: 'signal(0)',
},
{
name: 'counter$',
type: 'inferred',
value: 'BehaviorSubject(0)',
},
{
name: 'increment',
type: 'Subject<number>',
},
],
methodsPublicExplicit: [],
};

Expand Down
2 changes: 2 additions & 0 deletions src/parser/services/service.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { parseClassName } from '../shared/parser/class.parser.js';
import { parseExplicitPublicMethods } from '../shared/parser/method.parser.js';

import { NgParselService } from './service.model.js';
import { parseExplicitPublicFields } from '../shared/parser/field.parser.js';

export function parseService(ast: ts.SourceFile, filePath: string): NgParselService {
return {
type: NgParselOutputType.SERVICE,
className: parseClassName(ast),
filePath,
fieldsPublicExplicit: parseExplicitPublicFields(ast),
methodsPublicExplicit: parseExplicitPublicMethods(ast),
};
}
5 changes: 5 additions & 0 deletions src/parser/shared/model/field.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface NgParselField {
name: string;
type: string | 'inferred';
value?: any;
}
84 changes: 84 additions & 0 deletions src/parser/shared/parser/field.parser.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { parseExplicitPublicFields } from './field.parser.js';
import { tsquery } from '@phenomnomnominal/tsquery';

describe('Field parser', () => {
it('should extract a public Signal field', () => {
const ast = tsquery.ast(`
export class MyTestClass {
public visible = signal('0');
private notVisibleToYou = 'foo';
}
`);

const expectedFields = [
{
name: 'visible',
type: 'inferred',
value: `signal('0')`,
},
];

const fieldsPublicExplicit = parseExplicitPublicFields(ast);
expect(fieldsPublicExplicit).toEqual(expectedFields);
});

it('should extract a public property field', () => {
const ast = tsquery.ast(`
export class MyTestClass {
public visible = 'some value';
private notVisibleToYou = 'foo';
}
`);

const expectedFields = [
{
name: 'visible',
type: 'inferred',
value: `'some value'`,
},
];

const fieldsPublicExplicit = parseExplicitPublicFields(ast);
expect(fieldsPublicExplicit).toEqual(expectedFields);
});

it('should extract a public Subject', () => {
const ast = tsquery.ast(`
export class MyTestClass {
public visible = new Subject('some value');
private notVisibleToYou = 'foo';
}
`);

const expectedFields = [
{
name: 'visible',
type: 'inferred',
value: `Subject('some value')`,
},
];

const fieldsPublicExplicit = parseExplicitPublicFields(ast);
expect(fieldsPublicExplicit).toEqual(expectedFields);
});

it('should extract a public Subject with type void', () => {
const ast = tsquery.ast(`
export class MyTestClass {
public visible = new Subject<void>();
private notVisibleToYou = 'foo';
}
`);

const expectedFields = [
{
name: 'visible',
type: 'Subject<void>',
value: undefined,
},
];

const fieldsPublicExplicit = parseExplicitPublicFields(ast);
expect(fieldsPublicExplicit).toEqual(expectedFields);
});
});
40 changes: 40 additions & 0 deletions src/parser/shared/parser/field.parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as ts from 'typescript';
import { tsquery } from '@phenomnomnominal/tsquery';

import { NgParselField } from '../model/field.model.js';

export function parseExplicitPublicFields(ast: ts.SourceFile): NgParselField[] {
const fieldsExplicitPublic: NgParselField[] = [];

const publicFields = tsquery(ast, 'PropertyDeclaration:has(PublicKeyword)');

publicFields.forEach((field) => {
const nameNode = tsquery(field.getText(), 'Identifier')[0];
let typeNode = null;
const valueNode =
tsquery(field.getText(), 'CallExpression')[0] ||
tsquery(
field.getText(),
'NewExpression:has(NullKeyword, ObjectLiteralExpression, ArrayLiteralExpression, TrueKeyword, FalseKeyword, StringLiteral, Identifier[name=undefined], NumericLiteral, TemplateExpression, NoSubstitutionTemplateLiteral)'
)[0] ||
tsquery(
field.getText(),
'NullKeyword, ObjectLiteralExpression, ArrayLiteralExpression, TrueKeyword, FalseKeyword, StringLiteral, Identifier[name=undefined], NumericLiteral, TemplateExpression, NoSubstitutionTemplateLiteral'
)[0];

if (!valueNode) {
typeNode =
tsquery(field.getText(), 'TypeReference')[0] ||
tsquery(field.getText(), 'NewExpression')[0] ||
tsquery(field.getText(), 'CallExpression')[0];
}

fieldsExplicitPublic.push({
name: nameNode!.getText(),
type: typeNode?.getText().replace('new ', '').replace('()', '') || 'inferred',
value: valueNode?.getText().replace('new ', '').replace('()', ''),
});
});

return fieldsExplicitPublic;
}
15 changes: 15 additions & 0 deletions test-spa/src/app/core/counter.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Injectable, signal } from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';

@Injectable({
providedIn: 'root',
})
export class CounterService {
public counter = signal(0);
public counter$ = new BehaviorSubject(0);
public increment = new Subject<number>();

public foo = true;

private notVisible = 'blub';
}

0 comments on commit 404dbc3

Please sign in to comment.