Skip to content

Commit

Permalink
fix: Fix parser not locating TranslateService in private fields usi…
Browse files Browse the repository at this point in the history
…ng the `#` syntax (#55)

Fixes #54
  • Loading branch information
pmpak authored Nov 18, 2024
1 parent cdbedb5 commit 5d47e23
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 22 deletions.
49 changes: 27 additions & 22 deletions src/utils/ast-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import pkg, {
CallExpression,
Expression,
StringLiteral,
SourceFile
SourceFile,
PropertyDeclaration,
PropertyAccessExpression,
isPropertyAccessExpression,
isCallExpression
} from 'typescript';
const { SyntaxKind, isStringLiteralLike, isArrayLiteralExpression, isBinaryExpression, isConditionalExpression } = pkg;

Expand Down Expand Up @@ -142,15 +146,15 @@ export function findClassPropertiesConstructorParameterByType(node: ClassDeclara
}

export function findClassPropertiesDeclarationByType(node: ClassDeclaration, type: string): string[] {
const query = `PropertyDeclaration:has(TypeReference > Identifier[name="${type}"]) > Identifier`;
const result = tsquery<Identifier>(node, query);
return result.map((n) => n.text);
const query = `PropertyDeclaration:has(TypeReference > Identifier[name="${type}"])`;
const result = tsquery<PropertyDeclaration>(node, query);
return result.map((n) => n.name.getText());
}

export function findClassPropertiesDeclarationByInject(node: ClassDeclaration, type: string): string[] {
const query = `PropertyDeclaration:has(CallExpression > Identifier[name="inject"]):has(CallExpression > Identifier[name="${type}"]) > Identifier`;
const result = tsquery<Identifier>(node, query);
return result.map((n) => n.text);
const query = `PropertyDeclaration:has(CallExpression > Identifier[name="inject"]):has(CallExpression > Identifier[name="${type}"])`;
const result = tsquery<PropertyDeclaration>(node, query);
return result.map((n) => n.name.getText());
}

export function findClassPropertiesGetterByType(node: ClassDeclaration, type: string): string[] {
Expand Down Expand Up @@ -179,22 +183,23 @@ export function findPropertyCallExpressions(node: Node, prop: string, fnName: st
if (Array.isArray(fnName)) {
fnName = fnName.join('|');
}
const query = 'CallExpression > ' +
`PropertyAccessExpression:has(Identifier[name=/^(${fnName})$/]):has(PropertyAccessExpression:has(Identifier[name="${prop}"]):has(ThisKeyword)) > ` +
`PropertyAccessExpression:has(ThisKeyword) > Identifier[name="${prop}"]`;
const nodes = tsquery<Identifier>(node, query);
// Since the direct descendant operator (>) is not supported in :has statements, we need to
// check manually whether everything is correctly matched
const filtered = nodes.reduce<CallExpression[]>((result: CallExpression[], n: Node) => {
if (
tsquery(n.parent, 'PropertyAccessExpression > ThisKeyword').length > 0 &&
tsquery(n.parent.parent, `PropertyAccessExpression > Identifier[name=/^(${fnName})$/]`).length > 0
) {
result.push(n.parent.parent.parent as CallExpression);

const query = `CallExpression > PropertyAccessExpression:has(Identifier[name=/^(${fnName})$/]):has(PropertyAccessExpression:has(ThisKeyword))`;
const result = tsquery<PropertyAccessExpression>(node, query);

const nodes: CallExpression[] = [];
result.forEach((n) => {
const identifier = isPropertyAccessExpression(n.expression) ? n.expression.name : null;
const property = identifier?.parent;
const method = property?.parent;
const callExpression = method?.parent;

if (identifier?.getText() === prop && isCallExpression(callExpression)) {
nodes.push(callExpression);
}
return result;
}, []);
return filtered;
});

return nodes;
}

export function getStringsFromExpression(expression: Expression): string[] {
Expand Down
33 changes: 33 additions & 0 deletions tests/parsers/service.parser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,22 @@ describe('ServiceParser', () => {
expect(keys).to.deep.equal(['Hello World']);
});

it('should extract strings when TranslateService is declared as a private property', () => {
const contents = `
export class MyComponent {
readonly #translate: TranslateService;
public constructor(translateService: TranslateService) {
this.#translate = translateService;
}
public test() {
this.#translate.instant('Hello World');
}
}
`;
const keys = parser.extract(contents, componentFilename)?.keys();
expect(keys).to.deep.equal(['Hello World']);
});

it('should extract strings when TranslateService is injected using the inject function ', () => {
const contents = `
export class MyComponent {
Expand All @@ -328,6 +344,23 @@ describe('ServiceParser', () => {
expect(keys).to.deep.equal(['Hello World']);
});

it('should locate TranslateService when it is a JavaScript native private property', () => {
const contents = `
@Component({})
export class MyComponent {
readonly #translate = inject(TranslateService);
public test() {
this.#translate.get('get.works');
this.#translate.stream('stream.works');
this.#translate.instant('instant.works');
}
}
`;
const keys = parser.extract(contents, componentFilename)?.keys();
expect(keys).to.deep.equal(['get.works', 'stream.works', 'instant.works']);
});

it('should extract strings passed to TranslateServices methods only', () => {
const contents = `
export class AppComponent implements OnInit {
Expand Down

0 comments on commit 5d47e23

Please sign in to comment.