Skip to content

Commit f5af53c

Browse files
committed
feat: liquid returns result with type as type of variable
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' ```
1 parent fb5e241 commit f5af53c

File tree

6 files changed

+165
-11
lines changed

6 files changed

+165
-11
lines changed

src/transform/liquid/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@ function liquid<
124124
output = applySubstitutions(output, vars, path);
125125
}
126126

127-
output = conditionsInCode ? output : repairCode(output, codes);
127+
if (!conditionsInCode && typeof output === 'string') {
128+
output = repairCode(output, codes);
129+
}
130+
128131
codes.length = 0;
129132

130133
if (withSourceMap) {

src/transform/liquid/lexical.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const quoted = new RegExp(`${singleQuoted.source}|${doubleQuoted.source}`);
66
export const quoteBalanced = new RegExp(`(?:${quoted.source}|[^'"])*`);
77

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

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

6768
export const isLiteral = (str: string) => literalLine.test(str);
6869
export const isVariable = (str: string) => variableLine.test(str);
70+
export const isSingleVariable = (str: string) => singleVariable.test(str);
6971

7072
export function parseLiteral(str: string) {
7173
let res = str.match(numberLine);

src/transform/liquid/substitutions.ts

+43-9
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,35 @@ import ArgvService from './services/argv';
44
import getObject from '../getObject';
55
import {evalExp} from './evaluation';
66
import {log} from '../log';
7-
import {isVariable, vars as varsRe} from './lexical';
7+
import {
8+
isSingleVariable,
9+
isVariable,
10+
singleVariable as singleVariableRe,
11+
vars as varsRe,
12+
} from './lexical';
813

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

17+
if (isSingleVariable(str)) {
18+
const match = str.match(singleVariableRe);
19+
20+
if (!match) {
21+
return str;
22+
}
23+
24+
const trimVarPath = match[1].trim();
25+
const value = substituteVariable(trimVarPath, builtVars);
26+
27+
if (value === undefined) {
28+
logNotFoundVariable(trimVarPath, path);
29+
30+
return str;
31+
}
32+
33+
return value;
34+
}
35+
1236
return str.replace(varsRe, (match, _groupNotVar, flag, groupVar, groupVarValue) => {
1337
if (flag) {
1438
return keepNotVar ? _groupNotVar : groupVar;
@@ -20,21 +44,31 @@ const substitutions = (str: string, builtVars: Record<string, unknown>, path?: s
2044
return groupVar;
2145
}
2246

23-
let value;
24-
if (isVariable(trimVarPath)) {
25-
value = getObject(trimVarPath, builtVars);
26-
} else {
27-
value = evalExp(trimVarPath, builtVars);
28-
}
47+
const value = substituteVariable(trimVarPath, builtVars);
2948

3049
if (value === undefined) {
31-
value = match;
50+
logNotFoundVariable(trimVarPath, path);
3251

33-
log.warn(`Variable ${bold(trimVarPath)} not found${path ? ` in ${bold(path)}` : ''}`);
52+
return match;
3453
}
3554

3655
return value;
3756
});
3857
};
3958

59+
function logNotFoundVariable(varPath: string, path?: string) {
60+
log.warn(`Variable ${bold(varPath)} not found${path ? ` in ${bold(path)}` : ''}`);
61+
}
62+
63+
function substituteVariable(varPath: string, builtVars: Record<string, unknown>) {
64+
let value;
65+
if (isVariable(varPath)) {
66+
value = getObject(varPath, builtVars);
67+
} else {
68+
value = evalExp(varPath, builtVars);
69+
}
70+
71+
return value;
72+
}
73+
4074
export = substitutions;

test/liquid/filters.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('Filters', () => {
2020
).toEqual('Users count: 2');
2121
});
2222
test('Test2', () => {
23-
expect(substitutions('{{ test | length }}', {test: 'hello world'})).toEqual('11');
23+
expect(substitutions('{{ test | length }}', {test: 'hello world'})).toEqual(11);
2424
});
2525
});
2626

test/liquid/lexical.test.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {isSingleVariable} from '../../src/transform/liquid/lexical';
2+
3+
describe('Lexical functions', () => {
4+
describe('isSingleVariable', () => {
5+
test('Valid single variable without surrounding text', () => {
6+
expect(isSingleVariable('{{variable}}')).toEqual(true);
7+
});
8+
9+
test('Two variables should return false', () => {
10+
expect(isSingleVariable('{{variable1}} {{variable2}}')).toEqual(false);
11+
});
12+
13+
test('Text before variable should return false', () => {
14+
expect(isSingleVariable('some text {{variable}}')).toEqual(false);
15+
});
16+
17+
test('Text after variable should return false', () => {
18+
expect(isSingleVariable('{{variable}} some text')).toEqual(false);
19+
});
20+
21+
test('Valid single variable with filter', () => {
22+
expect(isSingleVariable('{{ variable | filter }}')).toEqual(true);
23+
});
24+
25+
test('Single variable with leading and trailing space should return false', () => {
26+
expect(isSingleVariable(' {{variable}} ')).toEqual(false);
27+
});
28+
29+
test('Single variable with multiple leading and trailing spaces should return false', () => {
30+
expect(isSingleVariable(' {{variable}} ')).toEqual(false);
31+
});
32+
33+
test('Single variable with tabs and newlines should return false', () => {
34+
expect(isSingleVariable('\t{{variable}} \n')).toEqual(false);
35+
});
36+
37+
test('Empty string should return false', () => {
38+
expect(isSingleVariable('')).toEqual(false);
39+
});
40+
41+
test('Text without variables should return false', () => {
42+
expect(isSingleVariable('just some text')).toEqual(false);
43+
});
44+
45+
test('Single curly braces should return false', () => {
46+
expect(isSingleVariable('{variable}')).toEqual(false);
47+
});
48+
49+
test('Unmatched curly braces should return false', () => {
50+
expect(isSingleVariable('{{variable}')).toEqual(false);
51+
});
52+
});
53+
});

test/liquid/substitutions.test.ts

+62
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,66 @@ describe('Substitutions', () => {
1919
}),
2020
).toEqual('Hello not_var{{ user.name }}!');
2121
});
22+
23+
test('Should return unchanged string if no variables present', () => {
24+
const input = 'This is just a string';
25+
expect(liquid(input, {})).toEqual(input);
26+
});
27+
28+
test('Should return unchanged string if variable not found in context', () => {
29+
const input = 'Variable {{ notFound }} not found';
30+
expect(liquid(input, {})).toEqual(input);
31+
});
32+
33+
test('Should substitute multiple occurrences of the same variable', () => {
34+
const input = 'Repeated {{ variable }} here and also here: {{ variable }}';
35+
const context = {variable: 'value'};
36+
expect(liquid(input, context)).toEqual('Repeated value here and also here: value');
37+
});
38+
39+
describe('Should save type of variable, if possible', () => {
40+
const string = 'Example';
41+
const number = 10;
42+
const boolean = true;
43+
const nullVar = null;
44+
const array = ['item1', 'item2', 'item3'];
45+
const object = {key1: 'value1', key2: 'value2'};
46+
const undefinedVar = undefined;
47+
48+
test('Should substitute to string', () => {
49+
expect(liquid('{{ string }}', {string})).toEqual(string);
50+
});
51+
52+
test('Should substitute to number', () => {
53+
expect(liquid('{{ number }}', {number})).toEqual(number);
54+
});
55+
56+
test('Should substitute to boolean', () => {
57+
expect(liquid('{{ boolean }}', {boolean})).toEqual(boolean);
58+
});
59+
60+
test('Should substitute to null', () => {
61+
expect(liquid('{{ nullVar }}', {nullVar})).toEqual(nullVar);
62+
});
63+
64+
test('Should substitute to array', () => {
65+
expect(liquid('{{ array }}', {array})).toEqual(array);
66+
});
67+
68+
test('Should substitute to object', () => {
69+
expect(liquid('{{ object }}', {object})).toEqual(object);
70+
});
71+
72+
test('Should not substitute undefined vars', () => {
73+
expect(liquid('{{ undefinedVar }}', {undefinedVar})).toEqual('{{ undefinedVar }}');
74+
});
75+
76+
test('Should substitute to string if input contains more than one variable', () => {
77+
expect(liquid('{{ number }} {{ boolean }}', {number, boolean})).toEqual(
78+
`${number} ${boolean}`,
79+
);
80+
81+
expect(liquid('{{ number }} postfix', {number})).toEqual(`${number} postfix`);
82+
});
83+
});
2284
});

0 commit comments

Comments
 (0)