From 4fd7efa2dc281a3900d00c504684ec13d7800632 Mon Sep 17 00:00:00 2001 From: Kim Biesbjerg Date: Thu, 13 Jun 2019 11:23:37 +0200 Subject: [PATCH] (refactor) simplify extraction of string literals --- src/parsers/abstract-ast.parser.ts | 57 +++------------------------ src/parsers/function.parser.ts | 13 +++--- src/parsers/service.parser.ts | 46 ++++++++++++--------- tests/parsers/function.parser.spec.ts | 4 +- tests/parsers/service.parser.spec.ts | 28 +++++++++++++ 5 files changed, 71 insertions(+), 77 deletions(-) diff --git a/src/parsers/abstract-ast.parser.ts b/src/parsers/abstract-ast.parser.ts index 3a1e755..7cfee3d 100644 --- a/src/parsers/abstract-ast.parser.ts +++ b/src/parsers/abstract-ast.parser.ts @@ -4,13 +4,8 @@ import { CallExpression, Node, SyntaxKind, - StringLiteral, - isStringLiteralLike, - isBinaryExpression, - isTemplateLiteralToken, - isArrayLiteralExpression + StringLiteral } from 'typescript'; -import { yellow } from 'colorette'; export abstract class AbstractAstParser { @@ -23,31 +18,14 @@ export abstract class AbstractAstParser { /** * Get strings from function call's first argument */ - protected getCallArgStrings(callNode: CallExpression): string[] { + protected getStringLiterals(callNode: CallExpression): string[] { if (!callNode.arguments.length) { - return; + return[]; } - const node = callNode.arguments[0]; - - if (isStringLiteralLike(node) || isTemplateLiteralToken(node)) { - return [node.text]; - } - - if (isArrayLiteralExpression(node)) { - return node.elements - .map((element: StringLiteral) => element.text); - } - - if (isBinaryExpression(node)) { - return [node.right] - .filter(childNode => isStringLiteralLike(childNode)) - .map((childNode: StringLiteral) => childNode.text); - } - - console.log(yellow(`Unsupported syntax kind in line %d: %s`), this.getLineNumber(node), this.syntaxKindToName(node.kind)); - - return []; + const firstArg = callNode.arguments[0]; + return this.findNodes(firstArg, SyntaxKind.StringLiteral) + .map((node: StringLiteral) => node.text); } /** @@ -62,27 +40,4 @@ export abstract class AbstractAstParser { }, initialValue); } - protected getLineNumber(node: Node): number { - const { line } = this.sourceFile.getLineAndCharacterOfPosition(node.pos); - return line + 1; - } - - protected syntaxKindToName(kind: SyntaxKind): string { - return SyntaxKind[kind]; - } - - protected printAllChildren(sourceFile: SourceFile, node: Node, depth = 0): void { - console.log( - new Array(depth + 1).join('----'), - `[${node.kind}]`, - this.syntaxKindToName(node.kind), - `[pos: ${node.pos}-${node.end}]`, - ':\t\t\t', - node.getFullText(sourceFile).trim() - ); - - depth++; - node.getChildren(sourceFile).forEach(childNode => this.printAllChildren(sourceFile, childNode, depth)); - } - } diff --git a/src/parsers/function.parser.ts b/src/parsers/function.parser.ts index f6d178f..747f64a 100644 --- a/src/parsers/function.parser.ts +++ b/src/parsers/function.parser.ts @@ -1,9 +1,9 @@ +import { Node, CallExpression, SyntaxKind, Identifier } from 'typescript'; + import { ParserInterface } from './parser.interface'; import { AbstractAstParser } from './abstract-ast.parser'; import { TranslationCollection } from '../utils/translation.collection'; -import * as ts from 'typescript'; - export class FunctionParser extends AbstractAstParser implements ParserInterface { protected functionIdentifier: string = '_'; @@ -22,24 +22,23 @@ export class FunctionParser extends AbstractAstParser implements ParserInterface const callNodes = this.findCallNodes(); callNodes.forEach(callNode => { - const keys: string[] = this.getCallArgStrings(callNode); + const keys: string[] = this.getStringLiterals(callNode); if (keys && keys.length) { collection = collection.addKeys(keys); } }); - return collection; } /** * Find all calls to marker function */ - protected findCallNodes(node?: ts.Node): ts.CallExpression[] { + protected findCallNodes(node?: Node): CallExpression[] { if (!node) { node = this.sourceFile; } - let callNodes = this.findNodes(node, ts.SyntaxKind.CallExpression) as ts.CallExpression[]; + let callNodes = this.findNodes(node, SyntaxKind.CallExpression) as CallExpression[]; callNodes = callNodes .filter(callNode => { // Only call expressions with arguments @@ -47,7 +46,7 @@ export class FunctionParser extends AbstractAstParser implements ParserInterface return false; } - const identifier = (callNode.getChildAt(0) as ts.Identifier).text; + const identifier = (callNode.getChildAt(0) as Identifier).text; if (identifier !== this.functionIdentifier) { return false; } diff --git a/src/parsers/service.parser.ts b/src/parsers/service.parser.ts index e6018c8..8ac5227 100644 --- a/src/parsers/service.parser.ts +++ b/src/parsers/service.parser.ts @@ -1,12 +1,22 @@ +import { + SourceFile, + Node, + ConstructorDeclaration, + Identifier, + TypeReferenceNode, + ClassDeclaration, + SyntaxKind, + CallExpression, + PropertyAccessExpression +} from 'typescript'; + import { ParserInterface } from './parser.interface'; import { AbstractAstParser } from './abstract-ast.parser'; import { TranslationCollection } from '../utils/translation.collection'; -import * as ts from 'typescript'; - export class ServiceParser extends AbstractAstParser implements ParserInterface { - protected sourceFile: ts.SourceFile; + protected sourceFile: SourceFile; public extract(contents: string, path?: string): TranslationCollection { let collection: TranslationCollection = new TranslationCollection(); @@ -26,7 +36,7 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface const callNodes = this.findCallNodes(classNode, propertyName); callNodes.forEach(callNode => { - const keys: string[] = this.getCallArgStrings(callNode); + const keys: string[] = this.getStringLiterals(callNode); if (keys && keys.length) { collection = collection.addKeys(keys); } @@ -40,7 +50,7 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface * Detect what the TranslateService instance property * is called by inspecting constructor arguments */ - protected findTranslateServicePropertyName(constructorNode: ts.ConstructorDeclaration): string { + protected findTranslateServicePropertyName(constructorNode: ConstructorDeclaration): string { if (!constructorNode) { return null; } @@ -57,7 +67,7 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface } // Make sure className is of the correct type - const parameterType: ts.Identifier = (parameter.type as ts.TypeReferenceNode).typeName as ts.Identifier; + const parameterType: Identifier = (parameter.type as TypeReferenceNode).typeName as Identifier; if (!parameterType) { return false; } @@ -70,22 +80,22 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface }); if (result) { - return (result.name as ts.Identifier).text; + return (result.name as Identifier).text; } } /** * Find class nodes */ - protected findClassNodes(node: ts.Node): ts.ClassDeclaration[] { - return this.findNodes(node, ts.SyntaxKind.ClassDeclaration) as ts.ClassDeclaration[]; + protected findClassNodes(node: Node): ClassDeclaration[] { + return this.findNodes(node, SyntaxKind.ClassDeclaration) as ClassDeclaration[]; } /** * Find constructor */ - protected findConstructorNode(node: ts.ClassDeclaration): ts.ConstructorDeclaration { - const constructorNodes = this.findNodes(node, ts.SyntaxKind.Constructor) as ts.ConstructorDeclaration[]; + protected findConstructorNode(node: ClassDeclaration): ConstructorDeclaration { + const constructorNodes = this.findNodes(node, SyntaxKind.Constructor) as ConstructorDeclaration[]; if (constructorNodes) { return constructorNodes[0]; } @@ -94,8 +104,8 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface /** * Find all calls to TranslateService methods */ - protected findCallNodes(node: ts.Node, propertyIdentifier: string): ts.CallExpression[] { - let callNodes = this.findNodes(node, ts.SyntaxKind.CallExpression) as ts.CallExpression[]; + protected findCallNodes(node: Node, propertyIdentifier: string): CallExpression[] { + let callNodes = this.findNodes(node, SyntaxKind.CallExpression) as CallExpression[]; callNodes = callNodes .filter(callNode => { // Only call expressions with arguments @@ -103,19 +113,19 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface return false; } - const propAccess = callNode.getChildAt(0).getChildAt(0) as ts.PropertyAccessExpression; - if (!propAccess || propAccess.kind !== ts.SyntaxKind.PropertyAccessExpression) { + const propAccess = callNode.getChildAt(0).getChildAt(0) as PropertyAccessExpression; + if (!propAccess || propAccess.kind !== SyntaxKind.PropertyAccessExpression) { return false; } - if (!propAccess.getFirstToken() || propAccess.getFirstToken().kind !== ts.SyntaxKind.ThisKeyword) { + if (!propAccess.getFirstToken() || propAccess.getFirstToken().kind !== SyntaxKind.ThisKeyword) { return false; } if (propAccess.name.text !== propertyIdentifier) { return false; } - const methodAccess = callNode.getChildAt(0) as ts.PropertyAccessExpression; - if (!methodAccess || methodAccess.kind !== ts.SyntaxKind.PropertyAccessExpression) { + const methodAccess = callNode.getChildAt(0) as PropertyAccessExpression; + if (!methodAccess || methodAccess.kind !== SyntaxKind.PropertyAccessExpression) { return false; } if (!methodAccess.name || (methodAccess.name.text !== 'get' && methodAccess.name.text !== 'instant' && methodAccess.name.text !== 'stream')) { diff --git a/tests/parsers/function.parser.spec.ts b/tests/parsers/function.parser.spec.ts index bc5319d..50b9cec 100644 --- a/tests/parsers/function.parser.spec.ts +++ b/tests/parsers/function.parser.spec.ts @@ -19,9 +19,11 @@ describe('FunctionParser', () => { _('Hello world'); _(['I', 'am', 'extracted']); otherFunction('But I am not'); + _(message || 'binary expression'); + _(message ? message : 'conditional operator'); `; const keys = parser.extract(contents, componentFilename).keys(); - expect(keys).to.deep.equal(['Hello world', 'I', 'am', 'extracted']); + expect(keys).to.deep.equal(['Hello world', 'I', 'am', 'extracted', 'binary expression', 'conditional operator']); }); }); diff --git a/tests/parsers/service.parser.spec.ts b/tests/parsers/service.parser.spec.ts index 892b2eb..b2f723d 100644 --- a/tests/parsers/service.parser.spec.ts +++ b/tests/parsers/service.parser.spec.ts @@ -16,6 +16,34 @@ describe('ServiceParser', () => { parser = new TestServiceParser(); }); + it('should support extracting binary expressions', () => { + const contents = ` + @Component({ }) + export class AppComponent { + public constructor(protected _translateService: TranslateService) { } + public test() { + const message = 'The Message'; + this._translateService.get(message || 'Fallback message'); + } + `; + const keys = parser.extract(contents, componentFilename).keys(); + expect(keys).to.deep.equal(['Fallback message']); + }); + + it('should support conditional operator', () => { + const contents = ` + @Component({ }) + export class AppComponent { + public constructor(protected _translateService: TranslateService) { } + public test() { + const message = 'The Message'; + this._translateService.get(message ? message : 'Fallback message'); + } + `; + const keys = parser.extract(contents, componentFilename).keys(); + expect(keys).to.deep.equal(['Fallback message']); + }); + it('should extract strings in TranslateService\'s get() method', () => { const contents = ` @Component({ })