From 4289b4e8048fc08963ae1a9d526920ec221c1c69 Mon Sep 17 00:00:00 2001 From: Harttle Date: Sun, 10 Jul 2022 00:24:00 +0800 Subject: [PATCH] refactor: rename `toValue` to `toValueSync` --- src/liquid.ts | 10 ++++----- src/util/async.ts | 35 +++++++++-------------------- src/util/underscore.ts | 8 +++++++ test/integration/liquid/liquid.ts | 27 ++++++++++++++++++++++ test/unit/render/expression.ts | 19 +++++++++++++++- test/unit/template/filter/filter.ts | 2 +- test/unit/util/async.ts | 13 ++++++----- 7 files changed, 77 insertions(+), 37 deletions(-) diff --git a/src/liquid.ts b/src/liquid.ts index f08bbe043..c83fb0d81 100644 --- a/src/liquid.ts +++ b/src/liquid.ts @@ -12,7 +12,7 @@ import { TagMap } from './template/tag/tag-map' import { FilterMap } from './template/filter/filter-map' import { LiquidOptions, normalizeDirectoryList, NormalizedFullOptions, normalize, RenderOptions } from './liquid-options' import { FilterImplOptions } from './template/filter/filter-impl-options' -import { toPromise, toValue } from './util/async' +import { toPromise, toValueSync } from './util/async' export * from './util/error' export * from './types' @@ -47,7 +47,7 @@ export class Liquid { return toPromise(this._render(tpl, scope, { ...renderOptions, sync: false })) } public renderSync (tpl: Template[], scope?: object, renderOptions?: RenderOptions): any { - return toValue(this._render(tpl, scope, { ...renderOptions, sync: true })) + return toValueSync(this._render(tpl, scope, { ...renderOptions, sync: true })) } public renderToNodeStream (tpl: Template[], scope?: object, renderOptions: RenderOptions = {}): NodeJS.ReadableStream { const ctx = new Context(scope, this.options, renderOptions) @@ -62,7 +62,7 @@ export class Liquid { return toPromise(this._parseAndRender(html, scope, { ...renderOptions, sync: false })) } public parseAndRenderSync (html: string, scope?: object, renderOptions?: RenderOptions): any { - return toValue(this._parseAndRender(html, scope, { ...renderOptions, sync: true })) + return toValueSync(this._parseAndRender(html, scope, { ...renderOptions, sync: true })) } public _parsePartialFile (file: string, sync?: boolean, currentFile?: string) { @@ -75,7 +75,7 @@ export class Liquid { return toPromise(this.parser.parseFile(file, false)) } public parseFileSync (file: string): Template[] { - return toValue(this.parser.parseFile(file, true)) + return toValueSync(this.parser.parseFile(file, true)) } public async renderFile (file: string, ctx?: object, renderOptions?: RenderOptions) { const templates = await this.parseFile(file) @@ -98,7 +98,7 @@ export class Liquid { return toPromise(this._evalValue(str, ctx)) } public evalValueSync (str: string, ctx: Context): any { - return toValue(this._evalValue(str, ctx)) + return toValueSync(this._evalValue(str, ctx)) } public registerFilter (name: string, filter: FilterImplOptions) { diff --git a/src/util/async.ts b/src/util/async.ts index 8200eff2b..3dba5cb96 100644 --- a/src/util/async.ts +++ b/src/util/async.ts @@ -1,23 +1,8 @@ -import { isFunction } from './underscore' - -type resolver = (x?: any) => any - -export interface Thenable { - then (resolve: resolver, reject?: resolver): Thenable; - catch (reject: resolver): Thenable; -} - -function isThenable (val: any): val is Thenable { - return val && isFunction(val.then) -} - -function isAsyncIterator (val: any): val is IterableIterator { - return val && isFunction(val.next) && isFunction(val.throw) && isFunction(val.return) -} +import { isPromise, isIterator } from './underscore' // convert an async iterator to a Promise -export async function toPromise (val: Generator | Thenable | T): Promise { - if (!isAsyncIterator(val)) return val +export async function toPromise (val: Generator | Promise | T): Promise { + if (!isIterator(val)) return val let value: unknown let done = false let next = 'next' @@ -27,8 +12,8 @@ export async function toPromise (val: Generator | Thenab value = state.value next = 'next' try { - if (isAsyncIterator(value)) value = toPromise(value) - if (isThenable(value)) value = await value + if (isIterator(value)) value = toPromise(value) + if (isPromise(value)) value = await value } catch (err) { next = 'throw' value = err @@ -37,9 +22,9 @@ export async function toPromise (val: Generator | Thenab return value as T } -// convert an async iterator to a value in a synchronous maner -export function toValue (val: Generator | T): T { - if (!isAsyncIterator(val)) return val +// convert an async iterator to a value in a synchronous manner +export function toValueSync (val: Generator | T): T { + if (!isIterator(val)) return val let value: any let done = false let next = 'next' @@ -48,9 +33,9 @@ export function toValue (val: Generator | T): T { done = state.done value = state.value next = 'next' - if (isAsyncIterator(value)) { + if (isIterator(value)) { try { - value = toValue(value) + value = toValueSync(value) } catch (err) { next = 'throw' value = err diff --git a/src/util/underscore.ts b/src/util/underscore.ts index 64107321b..002b19d65 100644 --- a/src/util/underscore.ts +++ b/src/util/underscore.ts @@ -14,6 +14,14 @@ export function isFunction (value: any): value is Function { return typeof value === 'function' } +export function isPromise (val: any): val is Promise { + return val && isFunction(val.then) +} + +export function isIterator (val: any): val is IterableIterator { + return val && isFunction(val.next) && isFunction(val.throw) && isFunction(val.return) +} + export function escapeRegex (str: string) { return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') } diff --git a/test/integration/liquid/liquid.ts b/test/integration/liquid/liquid.ts index 129339764..921cf0b40 100644 --- a/test/integration/liquid/liquid.ts +++ b/test/integration/liquid/liquid.ts @@ -58,6 +58,33 @@ describe('Liquid', function () { expect(html).to.equal('FOO') }) }) + describe('#parseAndRenderSync', function () { + const engine = new Liquid() + it('should parse and render variable output', function () { + const html = engine.parseAndRenderSync('{{"foo"}}') + expect(html).to.equal('foo') + }) + it('should parse and render complex output', function () { + const tpl = '{{ "Welcome|to]Liquid" | split: "|" | join: "("}}' + const html = engine.parseAndRenderSync(tpl) + expect(html).to.equal('Welcome(to]Liquid') + }) + it('should support for-in with variable', function () { + const src = '{% assign total = 3 | minus: 1 %}' + + '{% for i in (1..total) %}{{ i }}{% endfor %}' + const html = engine.parseAndRenderSync(src, {}) + return expect(html).to.equal('12') + }) + it('should support `globals` render option', function () { + const src = '{{ foo }}' + const html = engine.parseAndRenderSync(src, {}, { globals: { foo: 'FOO' } }) + return expect(html).to.equal('FOO') + }) + it('should support `strictVariables` render option', function () { + const src = '{{ foo }}' + return expect(() => engine.parseAndRenderSync(src, {}, { strictVariables: true })).throw(/undefined variable/) + }) + }) describe('#express()', function () { const liquid = new Liquid({ root: '/root' }) const render = liquid.express() diff --git a/test/unit/render/expression.ts b/test/unit/render/expression.ts index 5abf08583..128d064a2 100644 --- a/test/unit/render/expression.ts +++ b/test/unit/render/expression.ts @@ -2,7 +2,7 @@ import { Tokenizer } from '../../../src/parser/tokenizer' import { expect } from 'chai' import { Drop } from '../../../src/drop/drop' import { Context } from '../../../src/context/context' -import { toPromise } from '../../../src/util/async' +import { toPromise, toValueSync } from '../../../src/util/async' import { defaultOperators } from '../../../src/render/operator' import { createTrie } from '../../../src/util/operator-trie' @@ -158,4 +158,21 @@ describe('Expression', function () { expect(await toPromise(create('obj[keys["what\'s this"]]').evaluate(ctx, false))).to.equal('FOO') }) }) + + describe('sync', function () { + it('should eval literal', function () { + expect(toValueSync(create('2.4').evaluate(ctx, false))).to.equal(2.4) + }) + it('should return false for "1==2"', () => { + expect(toValueSync(create('1==2').evaluate(ctx, false))).to.equal(false) + }) + it('should escape quote', function () { + const ctx = new Context({ quote: '"' }) + expect(toValueSync(create('"\\"" == quote').evaluate(ctx, false))).to.equal(true) + }) + it('should allow nested property access', function () { + const ctx = new Context({ obj: { foo: 'FOO' }, keys: { "what's this": 'foo' } }) + expect(toValueSync(create('obj[keys["what\'s this"]]').evaluate(ctx, false))).to.equal('FOO') + }) + }) }) diff --git a/test/unit/template/filter/filter.ts b/test/unit/template/filter/filter.ts index 7d4dbba71..438587647 100644 --- a/test/unit/template/filter/filter.ts +++ b/test/unit/template/filter/filter.ts @@ -76,6 +76,6 @@ describe('filter', function () { it('should support key value pairs', async function () { filters.set('add', (a, b) => b[0] + ':' + (a + b[1])) const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined) - expect(await toPromise((filters.create('add', [['num', two]]).render(3, ctx)))).to.equal('num:5') + expect(await toPromise(filters.create('add', [['num', two]]).render(3, ctx))).to.equal('num:5') }) }) diff --git a/test/unit/util/async.ts b/test/unit/util/async.ts index 9cd83a3ee..5129ac626 100644 --- a/test/unit/util/async.ts +++ b/test/unit/util/async.ts @@ -1,4 +1,4 @@ -import { toPromise, toValue } from '../../../src/util/async' +import { toPromise, toValueSync } from '../../../src/util/async' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' @@ -75,7 +75,7 @@ describe('utils/async', () => { expect(ret).to.equal('barfoo') }) }) - describe('#toValue()', function () { + describe('#toValueSync()', function () { it('should throw Error if dependency throws syncly', () => { function * foo (): Generator> { return yield bar() @@ -83,7 +83,7 @@ describe('utils/async', () => { function * bar (): Generator { throw new Error('bar') } - expect(() => toValue(foo())).to.throw('bar') + expect(() => toValueSync(foo())).to.throw('bar') }) it('should resume yield after catch', () => { function * foo (): Generator { @@ -95,7 +95,7 @@ describe('utils/async', () => { function * bar (): Generator { throw new Error('bar') } - expect(toValue(foo())).to.equal('foo') + expect(toValueSync(foo())).to.equal('foo') }) it('should resume return after catch', () => { function * foo (): Generator, string> { @@ -107,7 +107,10 @@ describe('utils/async', () => { function * bar (): Generator { throw new Error('bar') } - expect(toValue(foo())).to.equal('foo') + expect(toValueSync(foo())).to.equal('foo') + }) + it('should return non iterator value as it is', () => { + expect(toValueSync('foo')).to.equal('foo') }) }) })