diff --git a/src/engine.ts b/src/engine.ts index 17952c9..0e6972a 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -3,7 +3,7 @@ import { JsonTemplateLexer } from './lexer'; import { JsonTemplateParser } from './parser'; import { JsonTemplateTranslator } from './translator'; import { EngineOptions, Expression, FlatMappingAST, FlatMappingPaths } from './types'; -import { ConverterUtils, CommonUtils } from './utils'; +import { CreateAsyncFunction, convertToObjectMapping } from './utils'; export class JsonTemplateEngine { private readonly fn: Function; @@ -24,7 +24,7 @@ export class JsonTemplateEngine { templateOrExpr: string | Expression | FlatMappingPaths[], options?: EngineOptions, ): Function { - return CommonUtils.CreateAsyncFunction( + return CreateAsyncFunction( DATA_PARAM_KEY, BINDINGS_PARAM_KEY, this.translate(templateOrExpr, options), @@ -76,12 +76,12 @@ export class JsonTemplateEngine { } let templateExpr = template as Expression; if (Array.isArray(template)) { - templateExpr = ConverterUtils.convertToObjectMapping(this.parseMappingPaths(template)); + templateExpr = convertToObjectMapping(this.parseMappingPaths(template)); } return this.translateExpression(templateExpr); } - evaluate(data: any, bindings: Record = {}): any { + evaluate(data: unknown, bindings: Record = {}): unknown { return this.fn(data ?? {}, bindings); } } diff --git a/src/parser.ts b/src/parser.ts index a652894..110372b 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -38,7 +38,7 @@ import { TokenType, UnaryExpression, } from './types'; -import { CommonUtils } from './utils/common'; +import { convertToStatementsExpr, getLastElement, toArray } from './utils/common'; export class JsonTemplateParser { private lexer: JsonTemplateLexer; @@ -225,7 +225,7 @@ export class JsonTemplateParser { let parts: Expression[] = []; let newParts: Expression[] | undefined; // eslint-disable-next-line no-cond-assign - while ((newParts = CommonUtils.toArray(this.parsePathPart()))) { + while ((newParts = toArray(this.parsePathPart()))) { parts = parts.concat(newParts); if (newParts[0].type === SyntaxType.FUNCTION_CALL_EXPR) { break; @@ -1137,7 +1137,7 @@ export class JsonTemplateParser { const expr = this.parseBaseExpr(); return { type: SyntaxType.FUNCTION_EXPR, - body: CommonUtils.convertToStatementsExpr(expr), + body: convertToStatementsExpr(expr), params: ['...args'], async: asyncFn, }; @@ -1308,7 +1308,7 @@ export class JsonTemplateParser { return { type: SyntaxType.FUNCTION_EXPR, block: true, - body: CommonUtils.convertToStatementsExpr(expr), + body: convertToStatementsExpr(expr), }; } @@ -1342,7 +1342,7 @@ export class JsonTemplateParser { fnExpr: FunctionCallExpression, pathExpr: PathExpression, ): FunctionCallExpression | PathExpression { - const lastPart = CommonUtils.getLastElement(pathExpr.parts); + const lastPart = getLastElement(pathExpr.parts); // Updated const newFnExpr = fnExpr; if (lastPart?.type === SyntaxType.SELECTOR) { @@ -1401,13 +1401,13 @@ export class JsonTemplateParser { } const shouldConvertAsBlock = JsonTemplateParser.pathContainsVariables(newPathExpr.parts); - let lastPart = CommonUtils.getLastElement(newPathExpr.parts); + let lastPart = getLastElement(newPathExpr.parts); let fnExpr: FunctionCallExpression | undefined; if (lastPart?.type === SyntaxType.FUNCTION_CALL_EXPR) { fnExpr = newPathExpr.parts.pop() as FunctionCallExpression; } - lastPart = CommonUtils.getLastElement(newPathExpr.parts); + lastPart = getLastElement(newPathExpr.parts); if (lastPart?.type === SyntaxType.PATH_OPTIONS) { newPathExpr.parts.pop(); newPathExpr.returnAsArray = lastPart.options?.toArray; diff --git a/src/translator.ts b/src/translator.ts index a0ccb23..91f4482 100644 --- a/src/translator.ts +++ b/src/translator.ts @@ -41,7 +41,7 @@ import { LoopControlExpression, Keyword, } from './types'; -import { CommonUtils } from './utils/common'; +import { convertToStatementsExpr, escapeStr } from './utils/common'; export class JsonTemplateTranslator { private vars: string[] = []; @@ -390,7 +390,7 @@ export class JsonTemplateTranslator { const valuesCode = JsonTemplateTranslator.returnObjectValues(ctx); code.push(`${dest} = ${valuesCode}.flat();`); } else if (prop) { - const propStr = CommonUtils.escapeStr(prop); + const propStr = escapeStr(prop); code.push(`if(${ctx} && Object.prototype.hasOwnProperty.call(${ctx}, ${propStr})){`); code.push(`${dest}=${ctx}[${propStr}];`); code.push('} else {'); @@ -418,7 +418,7 @@ export class JsonTemplateTranslator { const result = this.acquireVar(); code.push(JsonTemplateTranslator.generateAssignmentCode(result, '[]')); const { prop } = expr; - const propStr = CommonUtils.escapeStr(prop?.value); + const propStr = escapeStr(prop?.value); code.push(`${ctxs}=[${baseCtx}];`); code.push(`while(${ctxs}.length > 0) {`); code.push(`${currCtx} = ${ctxs}.shift();`); @@ -454,7 +454,7 @@ export class JsonTemplateTranslator { } const fnExpr: FunctionExpression = { type: SyntaxType.FUNCTION_EXPR, - body: CommonUtils.convertToStatementsExpr(...expr.statements), + body: convertToStatementsExpr(...expr.statements), block: true, }; return this.translateExpr(fnExpr, dest, ctx); @@ -558,7 +558,7 @@ export class JsonTemplateTranslator { private getSimplePathSelector(expr: SelectorExpression, isAssignment: boolean): string { if (expr.prop?.type === TokenType.STR) { - return `${isAssignment ? '' : '?.'}[${CommonUtils.escapeStr(expr.prop?.value)}]`; + return `${isAssignment ? '' : '?.'}[${escapeStr(expr.prop?.value)}]`; } return `${isAssignment ? '' : '?'}.${expr.prop?.value}`; } @@ -703,7 +703,7 @@ export class JsonTemplateTranslator { private translateLiteral(type: TokenType, val: any): string { if (type === TokenType.STR) { - return CommonUtils.escapeStr(val); + return escapeStr(val); } return String(val); } diff --git a/src/utils/common.test.ts b/src/utils/common.test.ts index 2d9020e..16d0cd8 100644 --- a/src/utils/common.test.ts +++ b/src/utils/common.test.ts @@ -1,6 +1,6 @@ import { EMPTY_EXPR } from '../constants'; import { SyntaxType } from '../types'; -import { CommonUtils } from './common'; +import * as CommonUtils from './common'; describe('Common Utils tests', () => { describe('toArray', () => { diff --git a/src/utils/common.ts b/src/utils/common.ts index 7a4a77d..4fa1b49 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,36 +1,34 @@ -import { Expression, StatementsExpression, SyntaxType } from '../types'; +import { type Expression, type StatementsExpression, SyntaxType } from '../types'; -export class CommonUtils { - static toArray(val: any): any[] | undefined { - if (val === undefined || val === null) { - return undefined; - } - return Array.isArray(val) ? val : [val]; +export function toArray(val: any): any[] | undefined { + if (val === undefined || val === null) { + return undefined; } + return Array.isArray(val) ? val : [val]; +} - static getLastElement(arr: T[]): T | undefined { - if (!arr.length) { - return undefined; - } - return arr[arr.length - 1]; +export function getLastElement(arr: T[]): T | undefined { + if (!arr.length) { + return undefined; } + return arr[arr.length - 1]; +} - static convertToStatementsExpr(...expressions: Expression[]): StatementsExpression { - return { - type: SyntaxType.STATEMENTS_EXPR, - statements: expressions, - }; - } +export function convertToStatementsExpr(...expressions: Expression[]): StatementsExpression { + return { + type: SyntaxType.STATEMENTS_EXPR, + statements: expressions, + }; +} - static CreateAsyncFunction(...args) { - // eslint-disable-next-line @typescript-eslint/no-empty-function, func-names - return async function () {}.constructor(...args); - } +export function CreateAsyncFunction(...args) { + // eslint-disable-next-line @typescript-eslint/no-empty-function, func-names + return async function () {}.constructor(...args); +} - static escapeStr(s?: string): string { - if (typeof s !== 'string') { - return ''; - } - return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`; +export function escapeStr(s?: string): string { + if (typeof s !== 'string') { + return ''; } + return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`; } diff --git a/src/utils/converter.test.ts b/src/utils/converter.test.ts index de62bbd..efde4fd 100644 --- a/src/utils/converter.test.ts +++ b/src/utils/converter.test.ts @@ -1,13 +1,13 @@ /* eslint-disable sonarjs/no-duplicate-string */ import { DATA_PARAM_KEY } from '../constants'; import { PathType, SyntaxType, TokenType } from '../types'; -import { ConverterUtils } from './converter'; +import { convertToObjectMapping } from './converter'; import { JsonTemplateEngine } from '../engine'; describe('Converter Utils Tests', () => { describe('convertToObjectMapping', () => { it('should convert single simple flat mapping to object mapping', () => { - const objectMapping = ConverterUtils.convertToObjectMapping( + const objectMapping = convertToObjectMapping( JsonTemplateEngine.parseMappingPaths([ { input: '.a.b', @@ -51,7 +51,7 @@ describe('Converter Utils Tests', () => { }); }); it('should convert single simple flat mapping with array index to object mapping', () => { - const objectMapping = ConverterUtils.convertToObjectMapping( + const objectMapping = convertToObjectMapping( JsonTemplateEngine.parseMappingPaths([ { input: '.a.b', @@ -100,7 +100,7 @@ describe('Converter Utils Tests', () => { }); }); it('should convert single flat array mapping to object mapping', () => { - const objectMapping = ConverterUtils.convertToObjectMapping( + const objectMapping = convertToObjectMapping( JsonTemplateEngine.parseMappingPaths([ { input: '.a[*].b', @@ -155,7 +155,7 @@ describe('Converter Utils Tests', () => { }); }); it('should convert multiple flat array mapping to object mapping', () => { - const objectMapping = ConverterUtils.convertToObjectMapping( + const objectMapping = convertToObjectMapping( JsonTemplateEngine.parseMappingPaths([ { input: '.a[*].b', @@ -229,7 +229,7 @@ describe('Converter Utils Tests', () => { }); }); it('should convert multiple flat array mapping to object mapping with root level mapping', () => { - const objectMapping = ConverterUtils.convertToObjectMapping( + const objectMapping = convertToObjectMapping( JsonTemplateEngine.parseMappingPaths([ { input: '~j $.root', @@ -326,7 +326,7 @@ describe('Converter Utils Tests', () => { }); }); it('should convert multiple flat nested array mapping to object mapping with root level mapping', () => { - const objectMapping = ConverterUtils.convertToObjectMapping( + const objectMapping = convertToObjectMapping( JsonTemplateEngine.parseMappingPaths([ { input: '~j $.root', diff --git a/src/utils/converter.ts b/src/utils/converter.ts index 8614212..978f276 100644 --- a/src/utils/converter.ts +++ b/src/utils/converter.ts @@ -16,87 +16,85 @@ type OutputObject = { /** * Convert Flat to Object Mappings */ -export class ConverterUtils { - // eslint-disable-next-line sonarjs/cognitive-complexity - static convertToObjectMapping(flatMappingAST: FlatMappingAST[]): ObjectExpression { - const outputAST: ObjectExpression = { - type: SyntaxType.OBJECT_EXPR, - props: [] as ObjectPropExpression[], - }; - const outputObject: OutputObject = {}; - for (const flatMapping of flatMappingAST) { - let currentOutputObject = outputObject; - let currentOutputAST = outputAST.props; - let currentInputAST = flatMapping.input; - const numOutputParts = flatMapping.output.parts.length; - for (let i = 0; i < numOutputParts; i++) { - const outputPart = flatMapping.output.parts[i]; - if (outputPart.type === SyntaxType.SELECTOR && outputPart.prop?.value) { - const key = outputPart.prop.value; - // If it's the last part, assign the value - if (i === numOutputParts - 1) { - currentOutputAST.push({ - type: SyntaxType.OBJECT_PROP_EXPR, - key, - value: currentInputAST, - } as ObjectPropExpression); - break; - } - - const nextOuptutPart = flatMapping.output.parts[i + 1] as ArrayFilterExpression; - const items = [] as ObjectPropExpression[]; - const objectExpr: ObjectExpression = { - type: SyntaxType.OBJECT_EXPR, - props: items, - }; +// eslint-disable-next-line sonarjs/cognitive-complexity +export function convertToObjectMapping(flatMappingAST: FlatMappingAST[]): ObjectExpression { + const outputAST: ObjectExpression = { + type: SyntaxType.OBJECT_EXPR, + props: [] as ObjectPropExpression[], + }; + const outputObject: OutputObject = {}; + for (const flatMapping of flatMappingAST) { + let currentOutputObject = outputObject; + let currentOutputAST = outputAST.props; + let currentInputAST = flatMapping.input; + const numOutputParts = flatMapping.output.parts.length; + for (let i = 0; i < numOutputParts; i++) { + const outputPart = flatMapping.output.parts[i]; + if (outputPart.type === SyntaxType.SELECTOR && outputPart.prop?.value) { + const key = outputPart.prop.value; + // If it's the last part, assign the value + if (i === numOutputParts - 1) { + currentOutputAST.push({ + type: SyntaxType.OBJECT_PROP_EXPR, + key, + value: currentInputAST, + } as ObjectPropExpression); + break; + } - if (!currentOutputObject[key]) { - const outputPropAST = { - type: SyntaxType.OBJECT_PROP_EXPR, - key, - value: objectExpr, - } as ObjectPropExpression; - if (nextOuptutPart.filter?.type === SyntaxType.ARRAY_INDEX_FILTER_EXPR) { - const arrayExpr: ArrayExpression = { - type: SyntaxType.ARRAY_EXPR, - elements: [], - }; - arrayExpr.elements[nextOuptutPart.filter.indexes.elements[0].value] = objectExpr; - outputPropAST.value = arrayExpr; - } + const nextOuptutPart = flatMapping.output.parts[i + 1] as ArrayFilterExpression; + const items = [] as ObjectPropExpression[]; + const objectExpr: ObjectExpression = { + type: SyntaxType.OBJECT_EXPR, + props: items, + }; - currentOutputObject[key] = { - $___items: items, - $___ast: outputPropAST, + if (!currentOutputObject[key]) { + const outputPropAST = { + type: SyntaxType.OBJECT_PROP_EXPR, + key, + value: objectExpr, + } as ObjectPropExpression; + if (nextOuptutPart.filter?.type === SyntaxType.ARRAY_INDEX_FILTER_EXPR) { + const arrayExpr: ArrayExpression = { + type: SyntaxType.ARRAY_EXPR, + elements: [], }; - currentOutputAST.push(outputPropAST); + arrayExpr.elements[nextOuptutPart.filter.indexes.elements[0].value] = objectExpr; + outputPropAST.value = arrayExpr; } - if (nextOuptutPart.filter?.type === SyntaxType.ALL_FILTER_EXPR) { - const filterIndex = currentInputAST.parts.findIndex( - (part) => part.type === SyntaxType.ARRAY_FILTER_EXPR, - ); - if (filterIndex !== -1) { - const inputRemainingParts = currentInputAST.parts.splice(filterIndex + 1); - currentInputAST.returnAsArray = true; - const outputPropAST = currentOutputObject[key].$___ast as ObjectPropExpression; - if (outputPropAST.value.type !== SyntaxType.PATH) { - currentInputAST.parts.push(outputPropAST.value); - outputPropAST.value = currentInputAST; - } - currentInputAST = { - type: SyntaxType.PATH, - pathType: currentInputAST.pathType, - parts: inputRemainingParts, - } as PathExpression; + + currentOutputObject[key] = { + $___items: items, + $___ast: outputPropAST, + }; + currentOutputAST.push(outputPropAST); + } + if (nextOuptutPart.filter?.type === SyntaxType.ALL_FILTER_EXPR) { + const filterIndex = currentInputAST.parts.findIndex( + (part) => part.type === SyntaxType.ARRAY_FILTER_EXPR, + ); + if (filterIndex !== -1) { + const inputRemainingParts = currentInputAST.parts.splice(filterIndex + 1); + currentInputAST.returnAsArray = true; + const outputPropAST = currentOutputObject[key].$___ast as ObjectPropExpression; + if (outputPropAST.value.type !== SyntaxType.PATH) { + currentInputAST.parts.push(outputPropAST.value); + outputPropAST.value = currentInputAST; } + currentInputAST = { + type: SyntaxType.PATH, + pathType: currentInputAST.pathType, + parts: inputRemainingParts, + } as PathExpression; } - // Move to the next level - currentOutputAST = currentOutputObject[key].$___items as ObjectPropExpression[]; - currentOutputObject = currentOutputObject[key] as OutputObject; } + // Move to the next level + currentOutputAST = currentOutputObject[key].$___items as ObjectPropExpression[]; + currentOutputObject = currentOutputObject[key] as OutputObject; } } - - return outputAST; } + + return outputAST; }