Compare commits
	
		
			2 Commits
		
	
	
		
			master
			...
			refactor-p
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 0d74fb4f55 | ||
|  | b928ca7555 | 
| @@ -1,11 +1,16 @@ | |||||||
| import { | import { | ||||||
| 	AST, | 	AST, | ||||||
| 	TmplAstNode, | 	ASTWithSource, | ||||||
|  | 	TmplAstNode as Node, | ||||||
|  | 	TmplAstBoundText as BoundText, | ||||||
|  | 	TmplAstElement as Element, | ||||||
|  | 	TmplAstTemplate as Template, | ||||||
|  | 	TmplAstBoundAttribute as BoundAttribute, | ||||||
|  | 	TmplAstBoundEvent as BoundEvent, | ||||||
| 	parseTemplate, | 	parseTemplate, | ||||||
| 	BindingPipe, | 	BindingPipe, | ||||||
| 	LiteralPrimitive, | 	LiteralPrimitive, | ||||||
| 	Conditional, | 	Conditional, | ||||||
| 	TmplAstTextAttribute, |  | ||||||
| 	Binary, | 	Binary, | ||||||
| 	LiteralMap, | 	LiteralMap, | ||||||
| 	LiteralArray, | 	LiteralArray, | ||||||
| @@ -17,142 +22,99 @@ import { TranslationCollection } from '../utils/translation.collection'; | |||||||
| import { isPathAngularComponent, extractComponentInlineTemplate } from '../utils/utils'; | import { isPathAngularComponent, extractComponentInlineTemplate } from '../utils/utils'; | ||||||
|  |  | ||||||
| const TRANSLATE_PIPE_NAME = 'translate'; | const TRANSLATE_PIPE_NAME = 'translate'; | ||||||
|  | type ElementLike = Element | Template; | ||||||
|  |  | ||||||
| export class PipeParser implements ParserInterface { | export class PipeParser implements ParserInterface { | ||||||
| 	public extract(source: string, filePath: string): TranslationCollection | null { | 	public extract(source: string, filePath: string): TranslationCollection | null { | ||||||
|  | 		let collection: TranslationCollection = new TranslationCollection(); | ||||||
|  |  | ||||||
| 		if (filePath && isPathAngularComponent(filePath)) { | 		if (filePath && isPathAngularComponent(filePath)) { | ||||||
| 			source = extractComponentInlineTemplate(source); | 			source = extractComponentInlineTemplate(source); | ||||||
| 		} | 		} | ||||||
|  | 		const nodes: Node[] = this.parseTemplate(source, filePath); | ||||||
|  | 		const pipes = this.getBindingPipes(nodes, TRANSLATE_PIPE_NAME).filter((pipe) => !this.pipeHasConcatenatedString(pipe)); | ||||||
|  |  | ||||||
| 		let collection: TranslationCollection = new TranslationCollection(); |  | ||||||
| 		const nodes: TmplAstNode[] = this.parseTemplate(source, filePath); |  | ||||||
| 		const pipes: BindingPipe[] = nodes.map((node) => this.findPipesInNode(node)).flat(); |  | ||||||
| 		pipes.forEach((pipe) => { | 		pipes.forEach((pipe) => { | ||||||
| 			this.parseTranslationKeysFromPipe(pipe).forEach((key: string) => { | 			this.visitEachChild(pipe, (child) => { | ||||||
| 				collection = collection.add(key); | 				if (child instanceof LiteralPrimitive) { | ||||||
|  | 					collection = collection.add(child.value); | ||||||
|  | 				} | ||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		return collection; | 		return collection; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	protected findPipesInNode(node: any): BindingPipe[] { | 	protected getBindingPipes(nodes: any[], name: string): BindingPipe[] { | ||||||
| 		let ret: BindingPipe[] = []; | 		let pipes: BindingPipe[] = []; | ||||||
|  | 		nodes.forEach((node) => { | ||||||
|  | 			if (this.isElementLike(node)) { | ||||||
|  | 				pipes = [ | ||||||
|  | 					...pipes, | ||||||
|  | 					...this.getBindingPipes([ | ||||||
|  | 						...node.inputs, | ||||||
|  | 						...node.children | ||||||
|  | 					], name) | ||||||
|  | 				]; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 		if (node?.children) { | 			this.visitEachChild(node, (exp) => { | ||||||
| 			ret = node.children.reduce( | 				if (exp instanceof BindingPipe && exp.name === name) { | ||||||
| 				(result: BindingPipe[], childNode: TmplAstNode) => { | 					pipes = [...pipes, exp]; | ||||||
| 					const children = this.findPipesInNode(childNode); |  | ||||||
| 					return result.concat(children); |  | ||||||
| 				}, |  | ||||||
| 				[ret] |  | ||||||
| 			); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (node?.value?.ast) { |  | ||||||
| 			ret.push(...this.getTranslatablesFromAst(node.value.ast)); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (node?.attributes) { |  | ||||||
| 			const translateableAttributes = node.attributes.filter((attr: TmplAstTextAttribute) => { |  | ||||||
| 				return attr.name === TRANSLATE_PIPE_NAME; |  | ||||||
| 			}); |  | ||||||
| 			ret = [...ret, ...translateableAttributes]; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (node?.inputs) { |  | ||||||
| 			node.inputs.forEach((input: any) => { |  | ||||||
| 				// <element [attrib]="'identifier' | translate"> |  | ||||||
| 				if (input?.value?.ast) { |  | ||||||
| 					ret.push(...this.getTranslatablesFromAst(input.value.ast)); |  | ||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
| 		} | 		}); | ||||||
|  | 		return pipes; | ||||||
| 		return ret; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	protected parseTranslationKeysFromPipe(pipeContent: BindingPipe | LiteralPrimitive | Conditional): string[] { | 	protected visitEachChild(exp: AST, visitor: (child: AST) => void): void { | ||||||
| 		const ret: string[] = []; | 		visitor(exp); | ||||||
| 		if (pipeContent instanceof LiteralPrimitive) { |  | ||||||
| 			ret.push(pipeContent.value); | 		let children: AST[] = []; | ||||||
| 		} else if (pipeContent instanceof Conditional) { | 		if (exp instanceof BoundText) { | ||||||
| 			const trueExp: LiteralPrimitive | Conditional = pipeContent.trueExp as any; | 			children = [exp.value]; | ||||||
| 			ret.push(...this.parseTranslationKeysFromPipe(trueExp)); | 		} else if (exp instanceof BoundAttribute) { | ||||||
| 			const falseExp: LiteralPrimitive | Conditional = pipeContent.falseExp as any; | 			children = [exp.value]; | ||||||
| 			ret.push(...this.parseTranslationKeysFromPipe(falseExp)); | 		} else if (exp instanceof BoundEvent) { | ||||||
| 		} else if (pipeContent instanceof BindingPipe) { | 			children = [exp.handler]; | ||||||
| 			ret.push(...this.parseTranslationKeysFromPipe(pipeContent.exp as any)); | 		} else if (exp instanceof Interpolation) { | ||||||
|  | 			children = exp.expressions; | ||||||
|  | 		} else if (exp instanceof LiteralArray) { | ||||||
|  | 			children = exp.expressions; | ||||||
|  | 		} else if (exp instanceof LiteralMap) { | ||||||
|  | 			children = exp.values; | ||||||
|  | 		} else if (exp instanceof BindingPipe) { | ||||||
|  | 			children = [exp.exp, ...exp.args]; | ||||||
|  | 		} else if (exp instanceof Conditional) { | ||||||
|  | 			children = [exp.trueExp, exp.falseExp]; | ||||||
|  | 		} else if (exp instanceof Binary) { | ||||||
|  | 			children = [exp.left, exp.right]; | ||||||
|  | 		} else if (exp instanceof ASTWithSource) { | ||||||
|  | 			children = [exp.ast]; | ||||||
| 		} | 		} | ||||||
| 		return ret; |  | ||||||
|  | 		children.forEach((child) => { | ||||||
|  | 			this.visitEachChild(child, visitor); | ||||||
|  | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	protected getTranslatablesFromAst(ast: AST): BindingPipe[] { | 	/** | ||||||
| 		// the entire expression is the translate pipe, e.g.: | 	 * Check if node type is ElementLike | ||||||
| 		// - 'foo' | translate | 	 * @param node | ||||||
| 		// - (condition ? 'foo' : 'bar') | translate | 	 */ | ||||||
| 		if (this.expressionIsOrHasBindingPipe(ast)) { | 	protected isElementLike(node: Node): node is ElementLike { | ||||||
| 			return [ast]; | 		return node instanceof Element || node instanceof Template; | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// angular double curly bracket interpolation, e.g.: |  | ||||||
| 		// - {{ expressions }} |  | ||||||
| 		if (ast instanceof Interpolation) { |  | ||||||
| 			return this.getTranslatablesFromAsts(ast.expressions); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// ternary operator, e.g.: |  | ||||||
| 		// - condition ? null : ('foo' | translate) |  | ||||||
| 		// - condition ? ('foo' | translate) : null |  | ||||||
| 		if (ast instanceof Conditional) { |  | ||||||
| 			return this.getTranslatablesFromAsts([ast.trueExp, ast.falseExp]); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// string concatenation, e.g.: |  | ||||||
| 		// - 'foo' + 'bar' + ('baz' | translate) |  | ||||||
| 		if (ast instanceof Binary) { |  | ||||||
| 			return this.getTranslatablesFromAsts([ast.left, ast.right]); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// a pipe on the outer expression, but not the translate pipe - ignore the pipe, visit the expression, e.g.: |  | ||||||
| 		// - { foo: 'Hello' | translate } | json |  | ||||||
| 		if (ast instanceof BindingPipe) { |  | ||||||
| 			return this.getTranslatablesFromAst(ast.exp); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// object - ignore the keys, visit all values, e.g.: |  | ||||||
| 		// - { key1: 'value1' | translate, key2: 'value2' | translate } |  | ||||||
| 		if (ast instanceof LiteralMap) { |  | ||||||
| 			return this.getTranslatablesFromAsts(ast.values); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// array - visit all its values, e.g.: |  | ||||||
| 		// - [ 'value1' | translate, 'value2' | translate ] |  | ||||||
| 		if (ast instanceof LiteralArray) { |  | ||||||
| 			return this.getTranslatablesFromAsts(ast.expressions); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return []; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	protected getTranslatablesFromAsts(asts: AST[]): BindingPipe[] { | 	/** | ||||||
| 		return this.flatten(asts.map((ast) => this.getTranslatablesFromAst(ast))); | 	 * Check if pipe concatenates string (in that case we don't want to extract it) | ||||||
|  | 	 * @param pipe | ||||||
|  | 	 */ | ||||||
|  | 	protected pipeHasConcatenatedString(pipe: BindingPipe): boolean { | ||||||
|  | 		return pipe?.exp instanceof Binary && pipe.exp.operation === '+'; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	protected flatten<T extends AST>(array: T[][]): T[] { | 	protected parseTemplate(template: string, path: string): Node[] { | ||||||
| 		return [].concat(...array); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	protected expressionIsOrHasBindingPipe(exp: any): exp is BindingPipe { |  | ||||||
| 		if (exp.name && exp.name === TRANSLATE_PIPE_NAME) { |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
| 		if (exp.exp && exp.exp instanceof BindingPipe) { |  | ||||||
| 			return this.expressionIsOrHasBindingPipe(exp.exp); |  | ||||||
| 		} |  | ||||||
| 		return false; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	protected parseTemplate(template: string, path: string): TmplAstNode[] { |  | ||||||
| 		return parseTemplate(template, path).nodes; | 		return parseTemplate(template, path).nodes; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user