diff --git a/grammar/liquid-html.ohm b/grammar/liquid-html.ohm
index 9a673184..fd92628e 100644
--- a/grammar/liquid-html.ohm
+++ b/grammar/liquid-html.ohm
@@ -104,12 +104,9 @@ LiquidHTML {
// TODO
liquidExpression =
- | liquidLiteral
-
- // TODO
- liquidLiteral =
| liquidString
| liquidNumber
+ | liquidLiteral
liquidString = liquidSingleQuotedString | liquidDoubleQuotedString
liquidSingleQuotedString = "'" anyExceptStar<("'"| "%}" | "}}")> "'"
@@ -122,6 +119,14 @@ LiquidHTML {
// coming from shopify/liquid...
liquidFloat = "-"? digit (digit | ".")+
+ liquidLiteral =
+ | "true"
+ | "false"
+ | "blank"
+ | "empty"
+ | "nil"
+ | "null"
+
// https://www.w3.org/TR/2011/WD-html-markup-20110113/syntax.html#void-element
// Cheating a bit with by stretching it to the doctype
voidElementName =
diff --git a/src/parser/ast.spec.ts b/src/parser/ast.spec.ts
index 4b534cc9..a9823128 100644
--- a/src/parser/ast.spec.ts
+++ b/src/parser/ast.spec.ts
@@ -54,6 +54,28 @@ describe('Unit: toLiquidHtmlAST', () => {
expectPosition(ast, 'children.0.markup.expression');
});
});
+
+ it('should parse numbers as LiquidVariable > LiquidLiteral', () => {
+ [
+ { expression: `nil`, value: null },
+ { expression: `null`, value: null },
+ { expression: `true`, value: true },
+ { expression: `blank`, value: '' },
+ { expression: `empty`, value: '' },
+ ].forEach(({ expression, value }) => {
+ ast = toLiquidHtmlAST(`{{ ${expression} }}`);
+ expectPath(ast, 'children.0').to.exist;
+ expectPath(ast, 'children.0.type').to.eql('LiquidDrop');
+ expectPath(ast, 'children.0.markup.type').to.eql('LiquidVariable');
+ expectPath(ast, 'children.0.markup.rawSource').to.eql(expression);
+ expectPath(ast, 'children.0.markup.expression.type').to.eql('LiquidLiteral');
+ expectPath(ast, 'children.0.markup.expression.keyword').to.eql(expression);
+ expectPath(ast, 'children.0.markup.expression.value').to.eql(value);
+ expectPosition(ast, 'children.0');
+ expectPosition(ast, 'children.0.markup');
+ expectPosition(ast, 'children.0.markup.expression');
+ });
+ });
});
it('should transform a basic Liquid Tag into a LiquidTag', () => {
diff --git a/src/parser/ast.ts b/src/parser/ast.ts
index ea454265..7533215b 100644
--- a/src/parser/ast.ts
+++ b/src/parser/ast.ts
@@ -15,6 +15,7 @@ import {
ConcreteAttrDoubleQuoted,
ConcreteAttrUnquoted,
ConcreteLiquidVariable,
+ ConcreteLiquidLiteral,
ConcreteLiquidFilters,
ConcreteLiquidExpression,
} from '~/parser/cst';
@@ -130,7 +131,7 @@ interface LiquidVariable extends ASTNode {
}
// TODO
-type LiquidExpression = LiquidString | LiquidNumber;
+type LiquidExpression = LiquidString | LiquidNumber | LiquidLiteral;
// TODO
type LiquidFilter = undefined;
@@ -144,6 +145,11 @@ interface LiquidNumber extends ASTNode {
value: string;
}
+interface LiquidLiteral extends ASTNode {
+ keyword: ConcreteLiquidLiteral['keyword'];
+ value: ConcreteLiquidLiteral['value'];
+}
+
export type HtmlNode =
| HtmlComment
| HtmlElement
@@ -647,6 +653,15 @@ function toExpression(
source,
};
}
+ case ConcreteNodeTypes.LiquidLiteral: {
+ return {
+ type: NodeTypes.LiquidLiteral,
+ position: position(node),
+ value: node.value,
+ keyword: node.keyword,
+ source,
+ };
+ }
default: {
return assertNever(node);
}
diff --git a/src/parser/cst.spec.ts b/src/parser/cst.spec.ts
index 510046ab..422e92f7 100644
--- a/src/parser/cst.spec.ts
+++ b/src/parser/cst.spec.ts
@@ -198,6 +198,29 @@ describe('Unit: toLiquidHtmlCST(text)', () => {
expectLocation(cst, '0.markup.expression');
});
});
+
+ it('should parse Liquid literals', () => {
+ [
+ { expression: `nil`, value: null },
+ { expression: `null`, value: null },
+ { expression: `true`, value: true },
+ { expression: `blank`, value: '' },
+ { expression: `empty`, value: '' },
+ ].forEach(({ expression, value }) => {
+ cst = toLiquidHtmlCST(`{{ ${expression} }}`);
+ expectPath(cst, '0.type').to.equal('LiquidDrop');
+ expectPath(cst, '0.markup.type').to.equal('LiquidVariable', expression);
+ expectPath(cst, '0.markup.rawSource').to.equal(expression);
+ expectPath(cst, '0.markup.expression.type').to.equal('LiquidLiteral');
+ expectPath(cst, '0.markup.expression.keyword').to.equal(expression);
+ expectPath(cst, '0.markup.expression.value').to.equal(value);
+ expectPath(cst, '0.whitespaceStart').to.equal(null);
+ expectPath(cst, '0.whitespaceEnd').to.equal(null);
+ expectLocation(cst, '0');
+ expectLocation(cst, '0.markup');
+ expectLocation(cst, '0.markup.expression');
+ });
+ });
});
describe('Case: LiquidNode', () => {
diff --git a/src/parser/cst.ts b/src/parser/cst.ts
index dda412a0..6c0d2a2e 100644
--- a/src/parser/cst.ts
+++ b/src/parser/cst.ts
@@ -23,10 +23,20 @@ export enum ConcreteNodeTypes {
TextNode = 'TextNode',
LiquidVariable = 'LiquidVariable',
+ LiquidLiteral = 'LiquidLiteral',
String = 'String',
Number = 'Number',
}
+export const LiquidLiteralValues = {
+ nil: null,
+ null: null,
+ true: true as true,
+ false: false as false,
+ blank: '' as '',
+ empty: '' as '',
+};
+
export interface Parsers {
[astFormat: string]: Parser;
}
@@ -149,7 +159,8 @@ export type ConcreteLiquidFilters = undefined; // TODO
// TODO
export type ConcreteLiquidExpression =
| ConcreteStringLiteral
- | ConcreteNumberLiteral;
+ | ConcreteNumberLiteral
+ | ConcreteLiquidLiteral;
export interface ConcreteStringLiteral
extends ConcreteBasicNode {
@@ -162,6 +173,12 @@ export interface ConcreteNumberLiteral
value: string; // float parsing is weird but supported
}
+export interface ConcreteLiquidLiteral
+ extends ConcreteBasicNode {
+ keyword: keyof typeof LiquidLiteralValues;
+ value: typeof LiquidLiteralValues[keyof typeof LiquidLiteralValues];
+}
+
export type ConcreteHtmlNode =
| ConcreteHtmlComment
| ConcreteHtmlRawTag
@@ -342,7 +359,17 @@ export function toLiquidHtmlCST(text: string): LiquidHtmlCST {
liquidDropCases: 0,
liquidExpression: 0,
- liquidLiteral: 0,
+ liquidLiteral: {
+ type: ConcreteNodeTypes.LiquidLiteral,
+ value: (tokens: Node[]) => {
+ const keyword = tokens[0]
+ .sourceString as keyof typeof LiquidLiteralValues;
+ return LiquidLiteralValues[keyword];
+ },
+ keyword: 0,
+ locStart,
+ locEnd,
+ },
liquidDropBaseCase: (sw: Node) => sw.sourceString.trimEnd(),
liquidVariable: {
type: ConcreteNodeTypes.LiquidVariable,
diff --git a/src/printer/preprocess/augment-with-css-properties.ts b/src/printer/preprocess/augment-with-css-properties.ts
index be63986e..a61d3b21 100644
--- a/src/printer/preprocess/augment-with-css-properties.ts
+++ b/src/printer/preprocess/augment-with-css-properties.ts
@@ -81,6 +81,7 @@ function getCssDisplay(
return 'block';
case NodeTypes.LiquidVariable:
+ case NodeTypes.LiquidLiteral:
case NodeTypes.String:
case NodeTypes.Number:
return 'should not be relevant';
@@ -128,6 +129,7 @@ function getNodeCssStyleWhiteSpace(node: AugmentedNode): string {
return CSS_WHITE_SPACE_DEFAULT;
case NodeTypes.LiquidVariable:
+ case NodeTypes.LiquidLiteral:
case NodeTypes.String:
case NodeTypes.Number:
return 'should not be relevant';
diff --git a/src/printer/printer-liquid-html.ts b/src/printer/printer-liquid-html.ts
index 68290f62..47821cc2 100644
--- a/src/printer/printer-liquid-html.ts
+++ b/src/printer/printer-liquid-html.ts
@@ -390,6 +390,14 @@ function printNode(
return node.value;
}
+ case NodeTypes.LiquidLiteral: {
+ // We prefer nil over null.
+ if (node.keyword === 'null') {
+ return 'nil';
+ }
+ return node.keyword;
+ }
+
case NodeTypes.LiquidVariable: {
// TODO this is where you'll do the pipe first/last logic.
return [path.call(print, 'expression')];
diff --git a/src/types.ts b/src/types.ts
index 304b6991..f72b9581 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -24,6 +24,7 @@ export enum NodeTypes {
TextNode = 'TextNode',
LiquidVariable = 'LiquidVariable',
+ LiquidLiteral = 'LiquidLiteral',
String = 'String',
Number = 'Number',
// Range = 'Range',
diff --git a/test/liquid-drop-liquid-literal/fixed.liquid b/test/liquid-drop-liquid-literal/fixed.liquid
new file mode 100644
index 00000000..b6d48752
--- /dev/null
+++ b/test/liquid-drop-liquid-literal/fixed.liquid
@@ -0,0 +1,9 @@
+It should print literals
+{{ true }}
+{{ false }}
+{{ empty }}
+{{ blank }}
+
+It should prefer nil over null
+{{ nil }}
+{{ nil }}
diff --git a/test/liquid-drop-liquid-literal/index.liquid b/test/liquid-drop-liquid-literal/index.liquid
new file mode 100644
index 00000000..ce9a5d36
--- /dev/null
+++ b/test/liquid-drop-liquid-literal/index.liquid
@@ -0,0 +1,9 @@
+It should print literals
+{{ true }}
+{{ false }}
+{{ empty }}
+{{ blank }}
+
+It should prefer nil over null
+{{ nil }}
+{{ null }}
diff --git a/test/liquid-drop-liquid-literal/index.spec.ts b/test/liquid-drop-liquid-literal/index.spec.ts
new file mode 100644
index 00000000..4587999a
--- /dev/null
+++ b/test/liquid-drop-liquid-literal/index.spec.ts
@@ -0,0 +1,6 @@
+import { assertFormattedEqualsFixed } from '../test-helpers';
+import * as path from 'path';
+
+describe(`Unit: ${path.basename(__dirname)}`, () => {
+ assertFormattedEqualsFixed(__dirname);
+});