Skip to content

Commit

Permalink
feat: liquid returns result with type as type of variable
Browse files Browse the repository at this point in the history
If liquid receives a string containing only one variable substitution,
it will return a result with the type as the type of this variable

```
typeof liquid('{{count}}', {count: 10}) === 'number'
```
  • Loading branch information
yndx-birman committed Jun 5, 2024
1 parent fb5e241 commit f5af53c
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 11 deletions.
5 changes: 4 additions & 1 deletion src/transform/liquid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,10 @@ function liquid<
output = applySubstitutions(output, vars, path);
}

output = conditionsInCode ? output : repairCode(output, codes);
if (!conditionsInCode && typeof output === 'string') {
output = repairCode(output, codes);
}

codes.length = 0;

if (withSourceMap) {
Expand Down
2 changes: 2 additions & 0 deletions src/transform/liquid/lexical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const quoted = new RegExp(`${singleQuoted.source}|${doubleQuoted.source}`);
export const quoteBalanced = new RegExp(`(?:${quoted.source}|[^'"])*`);

export const vars = /((not_var)?({{2}([. \w-|(),]+)}{2}))/gm;
export const singleVariable = /^{{2}([. \w-|(),]+)}{2}$/;

// basic types
const number = /-?\d+\.?\d*|\.?\d+/;
Expand Down Expand Up @@ -66,6 +67,7 @@ export const getParsedMethod = (exp: String) => {

export const isLiteral = (str: string) => literalLine.test(str);
export const isVariable = (str: string) => variableLine.test(str);
export const isSingleVariable = (str: string) => singleVariable.test(str);

export function parseLiteral(str: string) {
let res = str.match(numberLine);
Expand Down
52 changes: 43 additions & 9 deletions src/transform/liquid/substitutions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,35 @@ import ArgvService from './services/argv';
import getObject from '../getObject';
import {evalExp} from './evaluation';
import {log} from '../log';
import {isVariable, vars as varsRe} from './lexical';
import {
isSingleVariable,
isVariable,
singleVariable as singleVariableRe,
vars as varsRe,
} from './lexical';

const substitutions = (str: string, builtVars: Record<string, unknown>, path?: string) => {
const {keepNotVar} = ArgvService.getConfig();

if (isSingleVariable(str)) {
const match = str.match(singleVariableRe);

if (!match) {
return str;
}

const trimVarPath = match[1].trim();
const value = substituteVariable(trimVarPath, builtVars);

if (value === undefined) {
logNotFoundVariable(trimVarPath, path);

return str;
}

return value;
}

return str.replace(varsRe, (match, _groupNotVar, flag, groupVar, groupVarValue) => {
if (flag) {
return keepNotVar ? _groupNotVar : groupVar;
Expand All @@ -20,21 +44,31 @@ const substitutions = (str: string, builtVars: Record<string, unknown>, path?: s
return groupVar;
}

let value;
if (isVariable(trimVarPath)) {
value = getObject(trimVarPath, builtVars);
} else {
value = evalExp(trimVarPath, builtVars);
}
const value = substituteVariable(trimVarPath, builtVars);

if (value === undefined) {
value = match;
logNotFoundVariable(trimVarPath, path);

log.warn(`Variable ${bold(trimVarPath)} not found${path ? ` in ${bold(path)}` : ''}`);
return match;
}

return value;
});
};

function logNotFoundVariable(varPath: string, path?: string) {
log.warn(`Variable ${bold(varPath)} not found${path ? ` in ${bold(path)}` : ''}`);
}

function substituteVariable(varPath: string, builtVars: Record<string, unknown>) {
let value;
if (isVariable(varPath)) {
value = getObject(varPath, builtVars);
} else {
value = evalExp(varPath, builtVars);
}

return value;
}

export = substitutions;
2 changes: 1 addition & 1 deletion test/liquid/filters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('Filters', () => {
).toEqual('Users count: 2');
});
test('Test2', () => {
expect(substitutions('{{ test | length }}', {test: 'hello world'})).toEqual('11');
expect(substitutions('{{ test | length }}', {test: 'hello world'})).toEqual(11);
});
});

Expand Down
53 changes: 53 additions & 0 deletions test/liquid/lexical.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {isSingleVariable} from '../../src/transform/liquid/lexical';

describe('Lexical functions', () => {
describe('isSingleVariable', () => {
test('Valid single variable without surrounding text', () => {
expect(isSingleVariable('{{variable}}')).toEqual(true);
});

test('Two variables should return false', () => {
expect(isSingleVariable('{{variable1}} {{variable2}}')).toEqual(false);
});

test('Text before variable should return false', () => {
expect(isSingleVariable('some text {{variable}}')).toEqual(false);
});

test('Text after variable should return false', () => {
expect(isSingleVariable('{{variable}} some text')).toEqual(false);
});

test('Valid single variable with filter', () => {
expect(isSingleVariable('{{ variable | filter }}')).toEqual(true);
});

test('Single variable with leading and trailing space should return false', () => {
expect(isSingleVariable(' {{variable}} ')).toEqual(false);
});

test('Single variable with multiple leading and trailing spaces should return false', () => {
expect(isSingleVariable(' {{variable}} ')).toEqual(false);
});

test('Single variable with tabs and newlines should return false', () => {
expect(isSingleVariable('\t{{variable}} \n')).toEqual(false);
});

test('Empty string should return false', () => {
expect(isSingleVariable('')).toEqual(false);
});

test('Text without variables should return false', () => {
expect(isSingleVariable('just some text')).toEqual(false);
});

test('Single curly braces should return false', () => {
expect(isSingleVariable('{variable}')).toEqual(false);
});

test('Unmatched curly braces should return false', () => {
expect(isSingleVariable('{{variable}')).toEqual(false);
});
});
});
62 changes: 62 additions & 0 deletions test/liquid/substitutions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,66 @@ describe('Substitutions', () => {
}),
).toEqual('Hello not_var{{ user.name }}!');
});

test('Should return unchanged string if no variables present', () => {
const input = 'This is just a string';
expect(liquid(input, {})).toEqual(input);
});

test('Should return unchanged string if variable not found in context', () => {
const input = 'Variable {{ notFound }} not found';
expect(liquid(input, {})).toEqual(input);
});

test('Should substitute multiple occurrences of the same variable', () => {
const input = 'Repeated {{ variable }} here and also here: {{ variable }}';
const context = {variable: 'value'};
expect(liquid(input, context)).toEqual('Repeated value here and also here: value');
});

describe('Should save type of variable, if possible', () => {
const string = 'Example';
const number = 10;
const boolean = true;
const nullVar = null;
const array = ['item1', 'item2', 'item3'];
const object = {key1: 'value1', key2: 'value2'};
const undefinedVar = undefined;

test('Should substitute to string', () => {
expect(liquid('{{ string }}', {string})).toEqual(string);
});

test('Should substitute to number', () => {
expect(liquid('{{ number }}', {number})).toEqual(number);
});

test('Should substitute to boolean', () => {
expect(liquid('{{ boolean }}', {boolean})).toEqual(boolean);
});

test('Should substitute to null', () => {
expect(liquid('{{ nullVar }}', {nullVar})).toEqual(nullVar);
});

test('Should substitute to array', () => {
expect(liquid('{{ array }}', {array})).toEqual(array);
});

test('Should substitute to object', () => {
expect(liquid('{{ object }}', {object})).toEqual(object);
});

test('Should not substitute undefined vars', () => {
expect(liquid('{{ undefinedVar }}', {undefinedVar})).toEqual('{{ undefinedVar }}');
});

test('Should substitute to string if input contains more than one variable', () => {
expect(liquid('{{ number }} {{ boolean }}', {number, boolean})).toEqual(
`${number} ${boolean}`,
);

expect(liquid('{{ number }} postfix', {number})).toEqual(`${number} postfix`);
});
});
});

0 comments on commit f5af53c

Please sign in to comment.