extract strings when TranslateService is accessed directly via constructor parameter. Closes #50 and #106
This commit is contained in:
		| @@ -1,8 +1,9 @@ | |||||||
|  | import { ClassDeclaration, CallExpression } from 'typescript'; | ||||||
| import { tsquery } from '@phenomnomnominal/tsquery'; | import { tsquery } from '@phenomnomnominal/tsquery'; | ||||||
|  |  | ||||||
| import { ParserInterface } from './parser.interface'; | import { ParserInterface } from './parser.interface'; | ||||||
| import { TranslationCollection } from '../utils/translation.collection'; | import { TranslationCollection } from '../utils/translation.collection'; | ||||||
| import { findClassDeclarations, findClassPropertyByType, findMethodCallExpressions, getStringsFromExpression } from '../utils/ast-helpers'; | import { findClassDeclarations, findClassPropertyByType, findPropertyCallExpressions, findMethodCallExpressions, getStringsFromExpression, findMethodParameterByType, findConstructorDeclaration } from '../utils/ast-helpers'; | ||||||
|  |  | ||||||
| const TRANSLATE_SERVICE_TYPE_REFERENCE = 'TranslateService'; | const TRANSLATE_SERVICE_TYPE_REFERENCE = 'TranslateService'; | ||||||
| const TRANSLATE_SERVICE_METHOD_NAMES = ['get', 'instant', 'stream']; | const TRANSLATE_SERVICE_METHOD_NAMES = ['get', 'instant', 'stream']; | ||||||
| @@ -19,12 +20,11 @@ export class ServiceParser implements ParserInterface { | |||||||
| 		let collection: TranslationCollection = new TranslationCollection(); | 		let collection: TranslationCollection = new TranslationCollection(); | ||||||
|  |  | ||||||
| 		classDeclarations.forEach(classDeclaration => { | 		classDeclarations.forEach(classDeclaration => { | ||||||
| 			const propName: string = findClassPropertyByType(classDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE); | 			const callExpressions = [ | ||||||
| 			if (!propName) { | 				...this.findConstructorParamCallExpressions(classDeclaration), | ||||||
| 				return; | 				...this.findPropertyCallExpressions(classDeclaration) | ||||||
| 			} | 			]; | ||||||
|  |  | ||||||
| 			const callExpressions = findMethodCallExpressions(classDeclaration, propName, TRANSLATE_SERVICE_METHOD_NAMES); |  | ||||||
| 			callExpressions.forEach(callExpression => { | 			callExpressions.forEach(callExpression => { | ||||||
| 				const [firstArg] = callExpression.arguments; | 				const [firstArg] = callExpression.arguments; | ||||||
| 				if (!firstArg) { | 				if (!firstArg) { | ||||||
| @@ -36,4 +36,21 @@ export class ServiceParser implements ParserInterface { | |||||||
| 		}); | 		}); | ||||||
| 		return collection; | 		return collection; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	protected findConstructorParamCallExpressions(classDeclaration: ClassDeclaration): CallExpression[] { | ||||||
|  | 		const constructorDeclaration = findConstructorDeclaration(classDeclaration); | ||||||
|  | 		if (!constructorDeclaration) { | ||||||
|  | 			return []; | ||||||
|  | 		} | ||||||
|  | 		const paramName = findMethodParameterByType(constructorDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE); | ||||||
|  | 		return findMethodCallExpressions(constructorDeclaration, paramName, TRANSLATE_SERVICE_METHOD_NAMES); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	protected findPropertyCallExpressions(classDeclaration: ClassDeclaration): CallExpression[] { | ||||||
|  | 		const propName: string = findClassPropertyByType(classDeclaration, TRANSLATE_SERVICE_TYPE_REFERENCE); | ||||||
|  | 		if (!propName) { | ||||||
|  | 			return []; | ||||||
|  | 		} | ||||||
|  | 		return findPropertyCallExpressions(classDeclaration, propName, TRANSLATE_SERVICE_METHOD_NAMES); | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,15 +1,16 @@ | |||||||
| import { tsquery } from '@phenomnomnominal/tsquery'; | import { tsquery } from '@phenomnomnominal/tsquery'; | ||||||
| import { | import { | ||||||
|  | 	SyntaxKind, | ||||||
| 	Node, | 	Node, | ||||||
| 	NamedImports, | 	NamedImports, | ||||||
| 	Identifier, | 	Identifier, | ||||||
| 	ClassDeclaration, | 	ClassDeclaration, | ||||||
| 	CallExpression, | 	ConstructorDeclaration, | ||||||
| 	isStringLiteralLike, | 	isStringLiteralLike, | ||||||
| 	isArrayLiteralExpression, | 	isArrayLiteralExpression, | ||||||
|  | 	CallExpression, | ||||||
| 	Expression, | 	Expression, | ||||||
| 	isBinaryExpression, | 	isBinaryExpression, | ||||||
| 	SyntaxKind, |  | ||||||
| 	isConditionalExpression, | 	isConditionalExpression, | ||||||
| 	PropertyAccessExpression | 	PropertyAccessExpression | ||||||
| } from 'typescript'; | } from 'typescript'; | ||||||
| @@ -45,6 +46,30 @@ export function findClassPropertyByType(node: ClassDeclaration, type: string): s | |||||||
| 	return findClassPropertyConstructorParameterByType(node, type) || findClassPropertyDeclarationByType(node, type); | 	return findClassPropertyConstructorParameterByType(node, type) || findClassPropertyDeclarationByType(node, type); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export function findConstructorDeclaration(node: ClassDeclaration): ConstructorDeclaration { | ||||||
|  | 	const query = `Constructor`; | ||||||
|  | 	const [result] = tsquery<ConstructorDeclaration>(node, query); | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function findMethodParameterByType(node: Node, type: string): string | null { | ||||||
|  | 	const query = `Parameter:has(TypeReference > Identifier[name="${type}"]) > Identifier`; | ||||||
|  | 	const [result] = tsquery<Identifier>(node, query); | ||||||
|  | 	if (result) { | ||||||
|  | 		return result.text; | ||||||
|  | 	} | ||||||
|  | 	return null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function findMethodCallExpressions(node: Node, propName: string, fnName: string | string[]): CallExpression[] { | ||||||
|  | 	if (Array.isArray(fnName)) { | ||||||
|  | 		fnName = fnName.join('|'); | ||||||
|  | 	} | ||||||
|  | 	const query = `CallExpression > PropertyAccessExpression:has(Identifier[name=/^(${fnName})$/]):has(PropertyAccessExpression:has(Identifier[name="${propName}"]):not(:has(ThisKeyword)))`; | ||||||
|  | 	const nodes = tsquery<PropertyAccessExpression>(node, query).map(n => n.parent as CallExpression); | ||||||
|  | 	return nodes; | ||||||
|  | } | ||||||
|  |  | ||||||
| export function findClassPropertyConstructorParameterByType(node: ClassDeclaration, type: string): string | null { | export function findClassPropertyConstructorParameterByType(node: ClassDeclaration, type: string): string | null { | ||||||
| 	const query = `Constructor Parameter:has(TypeReference > Identifier[name="${type}"]):has(PublicKeyword,ProtectedKeyword,PrivateKeyword) > Identifier`; | 	const query = `Constructor Parameter:has(TypeReference > Identifier[name="${type}"]):has(PublicKeyword,ProtectedKeyword,PrivateKeyword) > Identifier`; | ||||||
| 	const [result] = tsquery<Identifier>(node, query); | 	const [result] = tsquery<Identifier>(node, query); | ||||||
| @@ -72,7 +97,7 @@ export function findFunctionCallExpressions(node: Node, fnName: string | string[ | |||||||
| 	return nodes; | 	return nodes; | ||||||
| } | } | ||||||
|  |  | ||||||
| export function findMethodCallExpressions(node: Node, prop: string, fnName: string | string[]): CallExpression[] { | export function findPropertyCallExpressions(node: Node, prop: string, fnName: string | string[]): CallExpression[] { | ||||||
| 	if (Array.isArray(fnName)) { | 	if (Array.isArray(fnName)) { | ||||||
| 		fnName = fnName.join('|'); | 		fnName = fnName.join('|'); | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -11,6 +11,18 @@ describe('ServiceParser', () => { | |||||||
| 		parser = new ServiceParser(); | 		parser = new ServiceParser(); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|  | 	it('should extract strings when TranslateService is accessed directly via constructor parameter', () => { | ||||||
|  | 		const contents = ` | ||||||
|  | 			@Component({ }) | ||||||
|  | 			export class MyComponent { | ||||||
|  | 				public constructor(protected translateService: TranslateService) { | ||||||
|  | 					translateService.get('It works!'); | ||||||
|  | 				} | ||||||
|  | 		`; | ||||||
|  | 		const keys = parser.extract(contents, componentFilename).keys(); | ||||||
|  | 		expect(keys).to.deep.equal(['It works!']); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
| 	it('should support extracting binary expressions', () => { | 	it('should support extracting binary expressions', () => { | ||||||
| 		const contents = ` | 		const contents = ` | ||||||
| 			@Component({ }) | 			@Component({ }) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user