(refactor) simplify extraction of string literals
This commit is contained in:
parent
02fc705bc0
commit
4fd7efa2dc
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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')) {
|
||||
|
@ -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']);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -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({ })
|
||||
|
Loading…
x
Reference in New Issue
Block a user