From 9481008f2bf9dae597c1f9078d63524181ef8d49 Mon Sep 17 00:00:00 2001 From: Yang Jun Date: Thu, 9 Jul 2026 23:13:19 +0800 Subject: [PATCH] feat: drop keepOutputType option (#838) (#930) Remove KeepingTypeEmitter and always stringify via SimpleEmitter. On next, map breaking commits to patch so alpha stays on 11.x. Co-authored-by: Cursor --- .releaserc.cjs | 18 ++++-- src/emitters/index.ts | 1 - src/emitters/keeping-type-emitter.ts | 19 ------- src/liquid-options.ts | 4 -- src/render/render.ts | 4 +- src/template/output.spec.ts | 56 ------------------- .../integration/liquid/keepoutput-opt.spec.ts | 39 ------------- 7 files changed, 16 insertions(+), 125 deletions(-) delete mode 100644 src/emitters/keeping-type-emitter.ts delete mode 100644 test/integration/liquid/keepoutput-opt.spec.ts diff --git a/.releaserc.cjs b/.releaserc.cjs index f2bff46a2..caa3c84c5 100644 --- a/.releaserc.cjs +++ b/.releaserc.cjs @@ -17,16 +17,26 @@ const githubPlugin = [ } ] +// next is branch-protected (PR-only); skip @semantic-release/git there and publish to npm only. +const onMaster = process.env.GITHUB_REF === 'refs/heads/master' +const onNext = process.env.GITHUB_REF === 'refs/heads/next' + +// On next, breaking changes are v11 WIP — bump alpha prerelease only, not major. +const commitAnalyzer = onNext + ? ['@semantic-release/commit-analyzer', { + releaseRules: [ + { breaking: true, release: 'patch' } + ] + }] + : '@semantic-release/commit-analyzer' + const basePlugins = [ - '@semantic-release/commit-analyzer', + commitAnalyzer, '@semantic-release/release-notes-generator', '@semantic-release/changelog', '@semantic-release/npm' ] -// next is branch-protected (PR-only); skip @semantic-release/git there and publish to npm only. -const onMaster = process.env.GITHUB_REF === 'refs/heads/master' - module.exports = { branches: [ 'master', diff --git a/src/emitters/index.ts b/src/emitters/index.ts index 0e27dd0e5..8300754f8 100644 --- a/src/emitters/index.ts +++ b/src/emitters/index.ts @@ -1,4 +1,3 @@ export * from './emitter' export * from './simple-emitter' export * from './streamed-emitter' -export * from './keeping-type-emitter' diff --git a/src/emitters/keeping-type-emitter.ts b/src/emitters/keeping-type-emitter.ts deleted file mode 100644 index 91d406286..000000000 --- a/src/emitters/keeping-type-emitter.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { stringify, toValue } from '../util' -import { Emitter } from './emitter' - -export class KeepingTypeEmitter implements Emitter { - public buffer: any = ''; - - public write (html: any) { - html = toValue(html) - // This will only preserve the type if the value is isolated. - // I.E: - // {{ my-port }} -> 42 - // {{ my-host }}:{{ my-port }} -> 'host:42' - if (typeof html !== 'string' && this.buffer === '') { - this.buffer = html - } else { - this.buffer = stringify(this.buffer) + stringify(html) - } - } -} diff --git a/src/liquid-options.ts b/src/liquid-options.ts index bd7a83497..f41ba1ec8 100644 --- a/src/liquid-options.ts +++ b/src/liquid-options.ts @@ -79,8 +79,6 @@ export interface LiquidOptions { templates?: {[key: string]: string}; /** the global scope passed down to all partial and layout templates, i.e. templates included by `include`, `layout` and `render` tags. */ globals?: object; - /** Whether or not to keep value type when writing the Output, not working for streamed rendering. Defaults to `false`. */ - keepOutputType?: boolean; /** Default escape filter applied to output values, when set, you'll have to add `| raw` for values don't need to be escaped. Defaults to `undefined`. */ outputEscape?: OutputEscapeOption; /** An object of operators for conditional statements. Defaults to the regular Liquid operators. */ @@ -160,7 +158,6 @@ export interface NormalizedFullOptions extends NormalizedOptions { preserveTimezones: boolean; greedy: boolean; globals: object; - keepOutputType: boolean; operators: Operators; parseLimit: number; renderLimit: number; @@ -196,7 +193,6 @@ export const defaultOptions: NormalizedFullOptions = { ownPropertyOnly: true, lenientIf: false, globals: {}, - keepOutputType: false, operators: defaultOperators, memoryLimit: Infinity, parseLimit: Infinity, diff --git a/src/render/render.ts b/src/render/render.ts index 8d0b7d806..15d6ab865 100644 --- a/src/render/render.ts +++ b/src/render/render.ts @@ -2,7 +2,7 @@ import { getPerformance } from '../util/performance' import { toPromise, RenderError, LiquidErrors, LiquidError } from '../util' import { Context } from '../context' import { Template } from '../template' -import { Emitter, KeepingTypeEmitter, StreamedEmitter, SimpleEmitter } from '../emitters' +import { Emitter, StreamedEmitter, SimpleEmitter } from '../emitters' export class Render { public renderTemplatesToNodeStream (templates: Template[], ctx: Context): NodeJS.ReadableStream { @@ -13,7 +13,7 @@ export class Render { } public * renderTemplates (templates: Template[], ctx: Context, emitter?: Emitter): IterableIterator { if (!emitter) { - emitter = ctx.opts.keepOutputType ? new KeepingTypeEmitter() : new SimpleEmitter() + emitter = new SimpleEmitter() } ctx.renderLimit.check(getPerformance().now()) const errors = [] diff --git a/src/template/output.spec.ts b/src/template/output.spec.ts index fa8ac03e0..bbc78324a 100644 --- a/src/template/output.spec.ts +++ b/src/template/output.spec.ts @@ -2,7 +2,6 @@ import { toPromise } from '../util' import { Context } from '../context' import { Output } from '../template' import { OutputToken } from '../tokens' -import { defaultOptions } from '../liquid-options' describe('Output', function () { const emitter: any = { write: (html: string) => (emitter.html += html), html: '' } @@ -30,59 +29,4 @@ describe('Output', function () { await toPromise(output.render(scope, emitter)) return expect(emitter.html).toBe('FOO') }) - it('should respect to .toString()', async () => { - const scope = new Context({ obj: { toString: () => 'FOO' } }) - const output = new Output(token, liquid) - await toPromise(output.render(scope, emitter)) - return expect(emitter.html).toBe('FOO') - }) - describe('when keepOutputType is enabled', () => { - const emitter: any = { - write: (html: any) => { - if (emitter.keepOutputType && typeof html !== 'string') { - emitter.html = html - } else { - emitter.html += html as string - } - }, - html: '', - keepOutputType: true - } - const token = { content: 'foo', input: 'foo' } as OutputToken - - beforeEach(() => { emitter.html = '' }) - - it('should respect output variable number type', async () => { - const scope = new Context({ - foo: 42 - }, { ...defaultOptions, keepOutputType: true }) - const output = new Output(token, liquid) - await toPromise(output.render(scope, emitter)) - return expect(emitter.html).toBe(42) - }) - it('should respect output variable boolean type', async () => { - const scope = new Context({ - foo: true - }, { ...defaultOptions, keepOutputType: true }) - const output = new Output(token, liquid) - await toPromise(output.render(scope, emitter)) - return expect(emitter.html).toBe(true) - }) - it('should respect output variable object type', async () => { - const scope = new Context({ - foo: 'test' - }, { ...defaultOptions, keepOutputType: true }) - const output = new Output(token, liquid) - await toPromise(output.render(scope, emitter)) - return expect(emitter.html).toBe('test') - }) - it('should respect output variable string type', async () => { - const scope = new Context({ - foo: { a: { b: 42 } } - }, { ...defaultOptions, keepOutputType: true }) - const output = new Output(token, liquid) - await toPromise(output.render(scope, emitter)) - return expect(emitter.html).toEqual({ a: { b: 42 } }) - }) - }) }) diff --git a/test/integration/liquid/keepoutput-opt.spec.ts b/test/integration/liquid/keepoutput-opt.spec.ts deleted file mode 100644 index bacf1bd73..000000000 --- a/test/integration/liquid/keepoutput-opt.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Liquid } from '../../../src/liquid' - -describe('LiquidOptions#*keepOutputType*', function () { - it('should respect keepOutputType', async function () { - const engine = new Liquid({ - keepOutputType: true - }) - const context = { - 'my-boolean': true, - 'my-number': 42, - 'my-string': 'test' - } - const booleanHtml = await engine.parseAndRender('{{my-boolean}}', context) - expect(booleanHtml).toBe(true) - const numberHtml = await engine.parseAndRender('{{my-number}}', context) - expect(numberHtml).toBe(42) - const html = await engine.parseAndRender('{{my-string}}', context) - expect(html).toBe('test') - const composedHtml = await engine.parseAndRender('{{my-string}}:{{my-number}}', context) - expect(composedHtml).toBe('test:42') - }) - - it('should respect keepOutputType = false as default', async function () { - const engine = new Liquid() - const context = { - 'my-boolean': true, - 'my-number': 42, - 'my-string': 'test' - } - const booleanHtml = await engine.parseAndRender('{{my-boolean}}', context) - expect(booleanHtml).toBe('true') - const numberHtml = await engine.parseAndRender('{{my-number}}', context) - expect(numberHtml).toBe('42') - const html = await engine.parseAndRender('{{my-string}}', context) - expect(html).toBe('test') - const composedHtml = await engine.parseAndRender('{{my-string}}:{{my-number}}', context) - expect(composedHtml).toBe('test:42') - }) -})