From c5bc9cdb01fcea518e6a9ac6bed6b7d74b1f4c46 Mon Sep 17 00:00:00 2001 From: Edward Gibbs Date: Fri, 6 Mar 2026 16:41:00 +0000 Subject: [PATCH] feat: add newline filter --- docs/source/filters/newline.md | 17 +++++++++++++++++ src/filters/string.ts | 15 +++++++++++++++ test/integration/filters/string.spec.ts | 14 ++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 docs/source/filters/newline.md diff --git a/docs/source/filters/newline.md b/docs/source/filters/newline.md new file mode 100644 index 0000000000..0d991ce035 --- /dev/null +++ b/docs/source/filters/newline.md @@ -0,0 +1,17 @@ +# newline + +Appends a newline (`\n`) to the input string if it does not already end with one. + +This is useful for scenarios where multi-line string inputs are prevented and we rely on a single line definition for the liquid template. + +## Example + +```liquid +{{ "Hello" | newline }}World +``` + +Output: + +``` +Hello\nWorld +``` diff --git a/src/filters/string.ts b/src/filters/string.ts index ec163d717f..f2c0d16e88 100644 --- a/src/filters/string.ts +++ b/src/filters/string.ts @@ -199,3 +199,18 @@ export function array_to_sentence_string (this: FilterImpl, array: unknown[], co return `${array.slice(0, -1).join(', ')}, ${connector} ${array[array.length - 1]}` } } + +export function newline (this: FilterImpl, v: unknown) { + let str = stringify(v) + + // Normalize windows newline + if (str.endsWith('\r\n')) { + str = str.slice(0, -2) + '\n' + } + + this.context.memoryLimit.use(str.length + 1) + + // Handle the case where newline is already added + if (str.endsWith('\n')) return str + return str + '\n' +} diff --git a/test/integration/filters/string.spec.ts b/test/integration/filters/string.spec.ts index 9ea5bd0888..8a0820eeee 100644 --- a/test/integration/filters/string.spec.ts +++ b/test/integration/filters/string.spec.ts @@ -347,4 +347,18 @@ describe('filters/string', function () { expect(html).toEqual('foo, bar, and baz') }) }) + describe('newline', function () { + it('should add newline character', async () => { + const result = await liquid.parseAndRender('{{"Hello" | newline}}World') + expect(result).toEqual('Hello\nWorld') + }) + it('should ignore existing newline characters', async () => { + const result = await liquid.parseAndRender('{{"Hello\n" | newline}}World') + expect(result).toEqual('Hello\nWorld') + }) + it('should ignore windows newline characters', async () => { + const result = await liquid.parseAndRender('{{"Hello\r\n" | newline}}World') + expect(result).toEqual('Hello\nWorld') + }) + }) })