(refactor) simplify extraction of string literals
This commit is contained in:
parent
02fc705bc0
commit
4fd7efa2dc
@ -4,13 +4,8 @@ import {
|
|||||||
CallExpression,
|
CallExpression,
|
||||||
Node,
|
Node,
|
||||||
SyntaxKind,
|
SyntaxKind,
|
||||||
StringLiteral,
|
StringLiteral
|
||||||
isStringLiteralLike,
|
|
||||||
isBinaryExpression,
|
|
||||||
isTemplateLiteralToken,
|
|
||||||
isArrayLiteralExpression
|
|
||||||
} from 'typescript';
|
} from 'typescript';
|
||||||
import { yellow } from 'colorette';
|
|
||||||
|
|
||||||
export abstract class AbstractAstParser {
|
export abstract class AbstractAstParser {
|
||||||
|
|
||||||
@ -23,31 +18,14 @@ export abstract class AbstractAstParser {
|
|||||||
/**
|
/**
|
||||||
* Get strings from function call's first argument
|
* Get strings from function call's first argument
|
||||||
*/
|
*/
|
||||||
protected getCallArgStrings(callNode: CallExpression): string[] {
|
protected getStringLiterals(callNode: CallExpression): string[] {
|
||||||
if (!callNode.arguments.length) {
|
if (!callNode.arguments.length) {
|
||||||
return;
|
return[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = callNode.arguments[0];
|
const firstArg = callNode.arguments[0];
|
||||||
|
return this.findNodes(firstArg, SyntaxKind.StringLiteral)
|
||||||
if (isStringLiteralLike(node) || isTemplateLiteralToken(node)) {
|
.map((node: StringLiteral) => node.text);
|
||||||
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 [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -62,27 +40,4 @@ export abstract class AbstractAstParser {
|
|||||||
}, initialValue);
|
}, 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 { ParserInterface } from './parser.interface';
|
||||||
import { AbstractAstParser } from './abstract-ast.parser';
|
import { AbstractAstParser } from './abstract-ast.parser';
|
||||||
import { TranslationCollection } from '../utils/translation.collection';
|
import { TranslationCollection } from '../utils/translation.collection';
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
export class FunctionParser extends AbstractAstParser implements ParserInterface {
|
export class FunctionParser extends AbstractAstParser implements ParserInterface {
|
||||||
|
|
||||||
protected functionIdentifier: string = '_';
|
protected functionIdentifier: string = '_';
|
||||||
@ -22,24 +22,23 @@ export class FunctionParser extends AbstractAstParser implements ParserInterface
|
|||||||
|
|
||||||
const callNodes = this.findCallNodes();
|
const callNodes = this.findCallNodes();
|
||||||
callNodes.forEach(callNode => {
|
callNodes.forEach(callNode => {
|
||||||
const keys: string[] = this.getCallArgStrings(callNode);
|
const keys: string[] = this.getStringLiterals(callNode);
|
||||||
if (keys && keys.length) {
|
if (keys && keys.length) {
|
||||||
collection = collection.addKeys(keys);
|
collection = collection.addKeys(keys);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all calls to marker function
|
* Find all calls to marker function
|
||||||
*/
|
*/
|
||||||
protected findCallNodes(node?: ts.Node): ts.CallExpression[] {
|
protected findCallNodes(node?: Node): CallExpression[] {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
node = this.sourceFile;
|
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
|
callNodes = callNodes
|
||||||
.filter(callNode => {
|
.filter(callNode => {
|
||||||
// Only call expressions with arguments
|
// Only call expressions with arguments
|
||||||
@ -47,7 +46,7 @@ export class FunctionParser extends AbstractAstParser implements ParserInterface
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const identifier = (callNode.getChildAt(0) as ts.Identifier).text;
|
const identifier = (callNode.getChildAt(0) as Identifier).text;
|
||||||
if (identifier !== this.functionIdentifier) {
|
if (identifier !== this.functionIdentifier) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,22 @@
|
|||||||
|
import {
|
||||||
|
SourceFile,
|
||||||
|
Node,
|
||||||
|
ConstructorDeclaration,
|
||||||
|
Identifier,
|
||||||
|
TypeReferenceNode,
|
||||||
|
ClassDeclaration,
|
||||||
|
SyntaxKind,
|
||||||
|
CallExpression,
|
||||||
|
PropertyAccessExpression
|
||||||
|
} from 'typescript';
|
||||||
|
|
||||||
import { ParserInterface } from './parser.interface';
|
import { ParserInterface } from './parser.interface';
|
||||||
import { AbstractAstParser } from './abstract-ast.parser';
|
import { AbstractAstParser } from './abstract-ast.parser';
|
||||||
import { TranslationCollection } from '../utils/translation.collection';
|
import { TranslationCollection } from '../utils/translation.collection';
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
|
|
||||||
export class ServiceParser extends AbstractAstParser implements ParserInterface {
|
export class ServiceParser extends AbstractAstParser implements ParserInterface {
|
||||||
|
|
||||||
protected sourceFile: ts.SourceFile;
|
protected sourceFile: SourceFile;
|
||||||
|
|
||||||
public extract(contents: string, path?: string): TranslationCollection {
|
public extract(contents: string, path?: string): TranslationCollection {
|
||||||
let collection: TranslationCollection = new TranslationCollection();
|
let collection: TranslationCollection = new TranslationCollection();
|
||||||
@ -26,7 +36,7 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface
|
|||||||
|
|
||||||
const callNodes = this.findCallNodes(classNode, propertyName);
|
const callNodes = this.findCallNodes(classNode, propertyName);
|
||||||
callNodes.forEach(callNode => {
|
callNodes.forEach(callNode => {
|
||||||
const keys: string[] = this.getCallArgStrings(callNode);
|
const keys: string[] = this.getStringLiterals(callNode);
|
||||||
if (keys && keys.length) {
|
if (keys && keys.length) {
|
||||||
collection = collection.addKeys(keys);
|
collection = collection.addKeys(keys);
|
||||||
}
|
}
|
||||||
@ -40,7 +50,7 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface
|
|||||||
* Detect what the TranslateService instance property
|
* Detect what the TranslateService instance property
|
||||||
* is called by inspecting constructor arguments
|
* is called by inspecting constructor arguments
|
||||||
*/
|
*/
|
||||||
protected findTranslateServicePropertyName(constructorNode: ts.ConstructorDeclaration): string {
|
protected findTranslateServicePropertyName(constructorNode: ConstructorDeclaration): string {
|
||||||
if (!constructorNode) {
|
if (!constructorNode) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -57,7 +67,7 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure className is of the correct type
|
// 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) {
|
if (!parameterType) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -70,22 +80,22 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
return (result.name as ts.Identifier).text;
|
return (result.name as Identifier).text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find class nodes
|
* Find class nodes
|
||||||
*/
|
*/
|
||||||
protected findClassNodes(node: ts.Node): ts.ClassDeclaration[] {
|
protected findClassNodes(node: Node): ClassDeclaration[] {
|
||||||
return this.findNodes(node, ts.SyntaxKind.ClassDeclaration) as ts.ClassDeclaration[];
|
return this.findNodes(node, SyntaxKind.ClassDeclaration) as ClassDeclaration[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find constructor
|
* Find constructor
|
||||||
*/
|
*/
|
||||||
protected findConstructorNode(node: ts.ClassDeclaration): ts.ConstructorDeclaration {
|
protected findConstructorNode(node: ClassDeclaration): ConstructorDeclaration {
|
||||||
const constructorNodes = this.findNodes(node, ts.SyntaxKind.Constructor) as ts.ConstructorDeclaration[];
|
const constructorNodes = this.findNodes(node, SyntaxKind.Constructor) as ConstructorDeclaration[];
|
||||||
if (constructorNodes) {
|
if (constructorNodes) {
|
||||||
return constructorNodes[0];
|
return constructorNodes[0];
|
||||||
}
|
}
|
||||||
@ -94,8 +104,8 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface
|
|||||||
/**
|
/**
|
||||||
* Find all calls to TranslateService methods
|
* Find all calls to TranslateService methods
|
||||||
*/
|
*/
|
||||||
protected findCallNodes(node: ts.Node, propertyIdentifier: string): ts.CallExpression[] {
|
protected findCallNodes(node: Node, propertyIdentifier: string): CallExpression[] {
|
||||||
let callNodes = this.findNodes(node, ts.SyntaxKind.CallExpression) as ts.CallExpression[];
|
let callNodes = this.findNodes(node, SyntaxKind.CallExpression) as CallExpression[];
|
||||||
callNodes = callNodes
|
callNodes = callNodes
|
||||||
.filter(callNode => {
|
.filter(callNode => {
|
||||||
// Only call expressions with arguments
|
// Only call expressions with arguments
|
||||||
@ -103,19 +113,19 @@ export class ServiceParser extends AbstractAstParser implements ParserInterface
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const propAccess = callNode.getChildAt(0).getChildAt(0) as ts.PropertyAccessExpression;
|
const propAccess = callNode.getChildAt(0).getChildAt(0) as PropertyAccessExpression;
|
||||||
if (!propAccess || propAccess.kind !== ts.SyntaxKind.PropertyAccessExpression) {
|
if (!propAccess || propAccess.kind !== SyntaxKind.PropertyAccessExpression) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!propAccess.getFirstToken() || propAccess.getFirstToken().kind !== ts.SyntaxKind.ThisKeyword) {
|
if (!propAccess.getFirstToken() || propAccess.getFirstToken().kind !== SyntaxKind.ThisKeyword) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (propAccess.name.text !== propertyIdentifier) {
|
if (propAccess.name.text !== propertyIdentifier) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const methodAccess = callNode.getChildAt(0) as ts.PropertyAccessExpression;
|
const methodAccess = callNode.getChildAt(0) as PropertyAccessExpression;
|
||||||
if (!methodAccess || methodAccess.kind !== ts.SyntaxKind.PropertyAccessExpression) {
|
if (!methodAccess || methodAccess.kind !== SyntaxKind.PropertyAccessExpression) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!methodAccess.name || (methodAccess.name.text !== 'get' && methodAccess.name.text !== 'instant' && methodAccess.name.text !== 'stream')) {
|
if (!methodAccess.name || (methodAccess.name.text !== 'get' && methodAccess.name.text !== 'instant' && methodAccess.name.text !== 'stream')) {
|
||||||
|
@ -19,9 +19,11 @@ describe('FunctionParser', () => {
|
|||||||
_('Hello world');
|
_('Hello world');
|
||||||
_(['I', 'am', 'extracted']);
|
_(['I', 'am', 'extracted']);
|
||||||
otherFunction('But I am not');
|
otherFunction('But I am not');
|
||||||
|
_(message || 'binary expression');
|
||||||
|
_(message ? message : 'conditional operator');
|
||||||
`;
|
`;
|
||||||
const keys = parser.extract(contents, componentFilename).keys();
|
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();
|
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', () => {
|
it('should extract strings in TranslateService\'s get() method', () => {
|
||||||
const contents = `
|
const contents = `
|
||||||
@Component({ })
|
@Component({ })
|
||||||
|
Loading…
x
Reference in New Issue
Block a user