diff --git a/.eslintrc.json b/.eslintrc.json index 1e0962b61..9d9835c88 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -19,6 +19,7 @@ "no-unused-vars": "off", "indent": "off", "no-dupe-class-members": "off", + "no-useless-constructor": "off", "@typescript-eslint/indent": ["error", 2], "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-empty-function": "off", diff --git a/package-lock.json b/package-lock.json index 9715779a8..544a4f635 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "liquidjs", - "version": "9.6.2", + "version": "9.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -905,9 +905,9 @@ "dev": true }, "@types/body-parser": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", - "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", "dev": true, "requires": { "@types/connect": "*", @@ -930,9 +930,9 @@ } }, "@types/connect": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", - "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", "dev": true, "requires": { "@types/node": "*" @@ -963,9 +963,9 @@ "dev": true }, "@types/express": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.0.tgz", - "integrity": "sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz", + "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==", "dev": true, "requires": { "@types/body-parser": "*", @@ -974,9 +974,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.16.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.7.tgz", - "integrity": "sha512-847KvL8Q1y3TtFLRTXcVakErLJQgdpFSaq+k043xefz9raEf0C7HalpSY7OW5PyjCnY8P7bPW5t/Co9qqp+USg==", + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz", + "integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==", "dev": true, "requires": { "@types/node": "*", @@ -1060,9 +1060,9 @@ "dev": true }, "@types/serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", "dev": true, "requires": { "@types/express-serve-static-core": "*", diff --git a/package.json b/package.json index b4c7ba085..48c6994ad 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@types/benchmark": "^1.0.31", "@types/chai": "^4.1.7", "@types/chai-as-promised": "^7.1.0", - "@types/express": "^4.16.1", + "@types/express": "^4.17.2", "@types/jsdom": "^12.2.2", "@types/mocha": "^5.2.6", "@types/sinon": "^7.0.6", @@ -118,6 +118,7 @@ { "assets": [ "package.json", + "package-lock.json", "docs", "CHANGELOG.md" ], diff --git a/src/builtin/tags/assign.ts b/src/builtin/tags/assign.ts index 75c4a7c9b..5198021d7 100644 --- a/src/builtin/tags/assign.ts +++ b/src/builtin/tags/assign.ts @@ -1,6 +1,6 @@ import { assert } from '../../util/assert' import { identifier } from '../../parser/lexical' -import { ITagImplOptions, TagToken, Context } from '../../types' +import { TagImplOptions, TagToken, Context } from '../../types' const re = new RegExp(`(${identifier.source})\\s*=([^]*)`) @@ -14,4 +14,4 @@ export default { render: function * (ctx: Context) { ctx.front()[this.key] = yield this.liquid._evalValue(this.value, ctx) } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/block.ts b/src/builtin/tags/block.ts index c8fbe2083..e591aa2cf 100644 --- a/src/builtin/tags/block.ts +++ b/src/builtin/tags/block.ts @@ -1,14 +1,14 @@ import BlockMode from '../../context/block-mode' -import { ParseStream, TagToken, Token, ITemplate, Context, ITagImplOptions, Emitter, Hash } from '../../types' +import { ParseStream, TagToken, Token, Template, Context, TagImplOptions, Emitter, Hash } from '../../types' export default { parse: function (token: TagToken, remainTokens: Token[]) { const match = /\w+/.exec(token.args) this.block = match ? match[0] : '' - this.tpls = [] as ITemplate[] + this.tpls = [] as Template[] const stream: ParseStream = this.liquid.parser.parseStream(remainTokens) .on('tag:endblock', () => stream.stop()) - .on('template', (tpl: ITemplate) => this.tpls.push(tpl)) + .on('template', (tpl: Template) => this.tpls.push(tpl)) .on('end', () => { throw new Error(`tag ${token.raw} not closed`) }) @@ -28,4 +28,4 @@ export default { } emitter.write(html) } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/capture.ts b/src/builtin/tags/capture.ts index 89eec503f..10c47d918 100644 --- a/src/builtin/tags/capture.ts +++ b/src/builtin/tags/capture.ts @@ -1,6 +1,6 @@ import { assert } from '../../util/assert' import { identifier } from '../../parser/lexical' -import { ITemplate, Context, ITagImplOptions, TagToken, Token } from '../../types' +import { Template, Context, TagImplOptions, TagToken, Token } from '../../types' const re = new RegExp(`(${identifier.source})`) @@ -14,7 +14,7 @@ export default { const stream = this.liquid.parser.parseStream(remainTokens) stream.on('tag:endcapture', () => stream.stop()) - .on('template', (tpl: ITemplate) => this.templates.push(tpl)) + .on('template', (tpl: Template) => this.templates.push(tpl)) .on('end', () => { throw new Error(`tag ${tagToken.raw} not closed`) }) @@ -25,4 +25,4 @@ export default { const html = yield r.renderTemplates(this.templates, ctx) ctx.front()[this.variable] = html } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/case.ts b/src/builtin/tags/case.ts index 14ea8b7e9..f5935f83b 100644 --- a/src/builtin/tags/case.ts +++ b/src/builtin/tags/case.ts @@ -1,4 +1,4 @@ -import { Expression, Hash, Emitter, TagToken, Token, Context, ITemplate, ITagImplOptions, ParseStream } from '../../types' +import { Expression, Hash, Emitter, TagToken, Token, Context, Template, TagImplOptions, ParseStream } from '../../types' export default { parse: function (tagToken: TagToken, remainTokens: Token[]) { @@ -6,7 +6,7 @@ export default { this.cases = [] this.elseTemplates = [] - let p: ITemplate[] = [] + let p: Template[] = [] const stream: ParseStream = this.liquid.parser.parseStream(remainTokens) .on('tag:when', (token: TagToken) => { this.cases.push({ @@ -16,7 +16,7 @@ export default { }) .on('tag:else', () => (p = this.elseTemplates)) .on('tag:endcase', () => stream.stop()) - .on('template', (tpl: ITemplate) => p.push(tpl)) + .on('template', (tpl: Template) => p.push(tpl)) .on('end', () => { throw new Error(`tag ${tagToken.raw} not closed`) }) @@ -37,4 +37,4 @@ export default { } yield r.renderTemplates(this.elseTemplates, ctx, emitter) } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/comment.ts b/src/builtin/tags/comment.ts index 0c5bb0952..0d36aec07 100644 --- a/src/builtin/tags/comment.ts +++ b/src/builtin/tags/comment.ts @@ -1,6 +1,6 @@ import { TagToken } from '../../parser/tag-token' import { Token } from '../../parser/token' -import { ITagImplOptions } from '../../template/tag/itag-impl-options' +import { TagImplOptions } from '../../template/tag/tag-impl-options' export default { parse: function (tagToken: TagToken, remainTokens: Token[]) { @@ -14,4 +14,4 @@ export default { }) stream.start() } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/cycle.ts b/src/builtin/tags/cycle.ts index 9d359b443..811b45ed1 100644 --- a/src/builtin/tags/cycle.ts +++ b/src/builtin/tags/cycle.ts @@ -1,6 +1,6 @@ import { assert } from '../../util/assert' import { value as rValue } from '../../parser/lexical' -import { Emitter, Expression, TagToken, Context, ITagImplOptions, Hash } from '../../types' +import { Emitter, Expression, TagToken, Context, TagImplOptions, Hash } from '../../types' const groupRE = new RegExp(`^(?:(${rValue.source})\\s*:\\s*)?(.*)$`) const candidatesRE = new RegExp(rValue.source, 'g') @@ -37,4 +37,4 @@ export default { const html = yield new Expression(candidate).value(ctx) emitter.write(html) } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/decrement.ts b/src/builtin/tags/decrement.ts index 6a7f15c51..228d39d03 100644 --- a/src/builtin/tags/decrement.ts +++ b/src/builtin/tags/decrement.ts @@ -1,6 +1,6 @@ import { assert } from '../../util/assert' import { identifier } from '../../parser/lexical' -import { Emitter, TagToken, Context, ITagImplOptions, Hash } from '../../types' +import { Emitter, TagToken, Context, TagImplOptions, Hash } from '../../types' import { isNumber, stringify } from '../../util/underscore' export default { @@ -16,4 +16,4 @@ export default { } emitter.write(stringify(--scope[this.variable])) } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/for.ts b/src/builtin/tags/for.ts index ccef0f6c0..bd6ee1abf 100644 --- a/src/builtin/tags/for.ts +++ b/src/builtin/tags/for.ts @@ -1,4 +1,4 @@ -import { Emitter, TagToken, Token, Context, ITemplate, ITagImplOptions, ParseStream } from '../../types' +import { Emitter, TagToken, Token, Context, Template, TagImplOptions, ParseStream } from '../../types' import { isString, isObject, isArray } from '../../util/underscore' import { Expression } from '../../render/expression' import { assert } from '../../util/assert' @@ -29,7 +29,7 @@ export default { .on('start', () => (p = this.templates)) .on('tag:else', () => (p = this.elseTemplates)) .on('tag:endfor', () => stream.stop()) - .on('template', (tpl: ITemplate) => p.push(tpl)) + .on('template', (tpl: Template) => p.push(tpl)) .on('end', () => { throw new Error(`tag ${tagToken.raw} not closed`) }) @@ -72,4 +72,4 @@ export default { } ctx.pop() } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/if.ts b/src/builtin/tags/if.ts index be9a47cae..6b14a0df3 100644 --- a/src/builtin/tags/if.ts +++ b/src/builtin/tags/if.ts @@ -1,4 +1,4 @@ -import { Hash, Emitter, isTruthy, Expression, TagToken, Token, Context, ITemplate, ITagImplOptions, ParseStream } from '../../types' +import { Hash, Emitter, isTruthy, Expression, TagToken, Token, Context, Template, TagImplOptions, ParseStream } from '../../types' export default { parse: function (tagToken: TagToken, remainTokens: Token[]) { @@ -19,7 +19,7 @@ export default { }) .on('tag:else', () => (p = this.elseTemplates)) .on('tag:endif', () => stream.stop()) - .on('template', (tpl: ITemplate) => p.push(tpl)) + .on('template', (tpl: Template) => p.push(tpl)) .on('end', () => { throw new Error(`tag ${tagToken.raw} not closed`) }) @@ -39,4 +39,4 @@ export default { } yield r.renderTemplates(this.elseTemplates, ctx, emitter) } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/include.ts b/src/builtin/tags/include.ts index 986f5fc40..e9ad0dfce 100644 --- a/src/builtin/tags/include.ts +++ b/src/builtin/tags/include.ts @@ -1,5 +1,5 @@ import { assert } from '../../util/assert' -import { Expression, Hash, Emitter, TagToken, Context, ITagImplOptions } from '../../types' +import { Expression, Hash, Emitter, TagToken, Context, TagImplOptions } from '../../types' import { value, quotedLine } from '../../parser/lexical' import BlockMode from '../../context/block-mode' @@ -46,4 +46,4 @@ export default { ctx.setRegister('blocks', originBlocks) ctx.setRegister('blockMode', originBlockMode) } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/increment.ts b/src/builtin/tags/increment.ts index 18ca13fbc..8b0d3ec6b 100644 --- a/src/builtin/tags/increment.ts +++ b/src/builtin/tags/increment.ts @@ -1,7 +1,7 @@ import { assert } from '../../util/assert' import { identifier } from '../../parser/lexical' import { isNumber, stringify } from '../../util/underscore' -import { Emitter, TagToken, Context, ITagImplOptions, Hash } from '../../types' +import { Emitter, TagToken, Context, TagImplOptions, Hash } from '../../types' export default { parse: function (token: TagToken) { @@ -18,4 +18,4 @@ export default { scope[this.variable]++ emitter.write(stringify(val)) } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/index.ts b/src/builtin/tags/index.ts index 924f62f83..cb6a322be 100644 --- a/src/builtin/tags/index.ts +++ b/src/builtin/tags/index.ts @@ -16,9 +16,9 @@ import tablerow from './tablerow' import unless from './unless' import Break from './break' import Continue from './continue' -import { ITagImplOptions } from '../../template/tag/itag-impl-options' +import { TagImplOptions } from '../../template/tag/tag-impl-options' -const tags: { [key: string]: ITagImplOptions } = { +const tags: { [key: string]: TagImplOptions } = { assign, 'for': For, capture, 'case': Case, comment, include, render, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue } diff --git a/src/builtin/tags/layout.ts b/src/builtin/tags/layout.ts index e8168f8af..1970cb46e 100644 --- a/src/builtin/tags/layout.ts +++ b/src/builtin/tags/layout.ts @@ -1,6 +1,6 @@ import { assert } from '../../util/assert' import { value as rValue } from '../../parser/lexical' -import { Emitter, Hash, Expression, TagToken, Token, Context, ITagImplOptions } from '../../types' +import { Emitter, Hash, Expression, TagToken, Token, Context, TagImplOptions } from '../../types' import BlockMode from '../../context/block-mode' const staticFileRE = /\S+/ @@ -40,4 +40,4 @@ export default { ctx.pop() emitter.write(partial) } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/raw.ts b/src/builtin/tags/raw.ts index 791dfbe2e..cd55a51f9 100644 --- a/src/builtin/tags/raw.ts +++ b/src/builtin/tags/raw.ts @@ -1,4 +1,4 @@ -import { TagToken, Token, ITagImplOptions } from '../../types' +import { TagToken, Token, TagImplOptions } from '../../types' export default { parse: function (tagToken: TagToken, remainTokens: Token[]) { @@ -18,4 +18,4 @@ export default { render: function () { return this.tokens.map((token: Token) => token.raw).join('') } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/render.ts b/src/builtin/tags/render.ts index b41fc75fa..108304a41 100644 --- a/src/builtin/tags/render.ts +++ b/src/builtin/tags/render.ts @@ -1,5 +1,5 @@ import { assert } from '../../util/assert' -import { Expression, Hash, Emitter, TagToken, Context, ITagImplOptions } from '../../types' +import { Expression, Hash, Emitter, TagToken, Context, TagImplOptions } from '../../types' import { value, quotedLine } from '../../parser/lexical' import BlockMode from '../../context/block-mode' @@ -47,4 +47,4 @@ export default { childCtx.setRegister('blocks', originBlocks) childCtx.setRegister('blockMode', originBlockMode) } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/tablerow.ts b/src/builtin/tags/tablerow.ts index a338ac3fb..198820ba5 100644 --- a/src/builtin/tags/tablerow.ts +++ b/src/builtin/tags/tablerow.ts @@ -1,5 +1,5 @@ import { assert } from '../../util/assert' -import { Expression, Emitter, Hash, TagToken, Token, Context, ITemplate, ITagImplOptions, ParseStream } from '../../types' +import { Expression, Emitter, Hash, TagToken, Token, Context, Template, TagImplOptions, ParseStream } from '../../types' import { identifier, value, hash } from '../../parser/lexical' import { TablerowloopDrop } from '../../drop/tablerowloop-drop' @@ -20,7 +20,7 @@ export default { const stream: ParseStream = this.liquid.parser.parseStream(remainTokens) .on('start', () => (p = this.templates)) .on('tag:endtablerow', () => stream.stop()) - .on('template', (tpl: ITemplate) => p.push(tpl)) + .on('template', (tpl: Template) => p.push(tpl)) .on('end', () => { throw new Error(`tag ${tagToken.raw} not closed`) }) @@ -54,4 +54,4 @@ export default { if (collection.length) emitter.write('') ctx.pop() } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/builtin/tags/unless.ts b/src/builtin/tags/unless.ts index 58954d929..28ade8cd4 100644 --- a/src/builtin/tags/unless.ts +++ b/src/builtin/tags/unless.ts @@ -1,4 +1,4 @@ -import { Emitter, Expression, isFalsy, ParseStream, Context, ITagImplOptions, Token, Hash, TagToken } from '../../types' +import { Emitter, Expression, isFalsy, ParseStream, Context, TagImplOptions, Token, Hash, TagToken } from '../../types' export default { parse: function (tagToken: TagToken, remainTokens: Token[]) { @@ -27,4 +27,4 @@ export default { ? r.renderTemplates(this.templates, ctx, emitter) : r.renderTemplates(this.elseTemplates, ctx, emitter)) } -} as ITagImplOptions +} as TagImplOptions diff --git a/src/liquid.ts b/src/liquid.ts index 89c96a9a3..6be321ee5 100644 --- a/src/liquid.ts +++ b/src/liquid.ts @@ -1,16 +1,16 @@ import { Context } from './context/context' import fs from './fs/node' import * as _ from './util/underscore' -import { ITemplate } from './template/itemplate' +import { Template } from './template/template' import { Tokenizer } from './parser/tokenizer' import { Render } from './render/render' -import { Tag } from './template/tag/tag' -import { Filter } from './template/filter/filter' import Parser from './parser/parser' -import { ITagImplOptions } from './template/tag/itag-impl-options' +import { TagImplOptions } from './template/tag/tag-impl-options' import { Value } from './template/value' import builtinTags from './builtin/tags' import builtinFilters from './builtin/filters' +import { TagMap } from './template/tag/tag-map' +import { FilterMap } from './template/filter/filter-map' import { LiquidOptions, normalizeStringArray, NormalizedFullOptions, applyDefault, normalize } from './liquid-options' import { FilterImplOptions } from './template/filter/filter-impl-options' import IFS from './fs/ifs' @@ -22,6 +22,8 @@ export class Liquid { public options: NormalizedFullOptions public renderer: Render public parser: Parser + public filters: FilterMap + public tags: TagMap private cache: object = {} private tokenizer: Tokenizer private fs: IFS @@ -32,24 +34,26 @@ export class Liquid { this.renderer = new Render() this.tokenizer = new Tokenizer(this.options) this.fs = opts.fs || fs + this.filters = new FilterMap(this.options.strictFilters) + this.tags = new TagMap() _.forOwn(builtinTags, (conf, name) => this.registerTag(name, conf)) _.forOwn(builtinFilters, (handler, name) => this.registerFilter(name, handler)) } - public parse (html: string, filepath?: string): ITemplate[] { + public parse (html: string, filepath?: string): Template[] { const tokens = this.tokenizer.tokenize(html, filepath) return this.parser.parse(tokens) } - public _render (tpl: ITemplate[], scope?: object, opts?: LiquidOptions, sync?: boolean): IterableIterator { + public _render (tpl: Template[], scope?: object, opts?: LiquidOptions, sync?: boolean): IterableIterator { const options = { ...this.options, ...normalize(opts) } const ctx = new Context(scope, options, sync) return this.renderer.renderTemplates(tpl, ctx) } - public async render (tpl: ITemplate[], scope?: object, opts?: LiquidOptions): Promise { + public async render (tpl: Template[], scope?: object, opts?: LiquidOptions): Promise { return toThenable(this._render(tpl, scope, opts, false)) } - public renderSync (tpl: ITemplate[], scope?: object, opts?: LiquidOptions): string { + public renderSync (tpl: Template[], scope?: object, opts?: LiquidOptions): string { return toValue(this._render(tpl, scope, opts, true)) } @@ -80,10 +84,10 @@ export class Liquid { } throw this.lookupError(file, options.root) } - public async parseFile (file: string, opts?: LiquidOptions): Promise { + public async parseFile (file: string, opts?: LiquidOptions): Promise { return toThenable(this._parseFile(file, opts, false)) } - public parseFileSync (file: string, opts?: LiquidOptions): ITemplate[] { + public parseFileSync (file: string, opts?: LiquidOptions): Template[] { return toValue(this._parseFile(file, opts, true)) } public async renderFile (file: string, ctx?: object, opts?: LiquidOptions) { @@ -97,7 +101,7 @@ export class Liquid { } public _evalValue (str: string, ctx: Context): IterableIterator { - const value = new Value(str, this.options.strictFilters) + const value = new Value(str, this.filters) return value.value(ctx) } public async evalValue (str: string, ctx: Context): Promise { @@ -108,19 +112,19 @@ export class Liquid { } public registerFilter (name: string, filter: FilterImplOptions) { - return Filter.register(name, filter) + this.filters.set(name, filter) } - public registerTag (name: string, tag: ITagImplOptions) { - return Tag.register(name, tag) + public registerTag (name: string, tag: TagImplOptions) { + this.tags.set(name, tag) } public plugin (plugin: (this: Liquid, L: typeof Liquid) => void) { return plugin.call(this, Liquid) } public express () { const self = this // eslint-disable-line - return function (this: any, filePath: string, ctx: object, cb: (err: Error | null, html?: string) => void) { + return function (this: any, filePath: string, ctx: object, callback: (err: Error | null, rendered: string) => void) { const opts = { root: [...normalizeStringArray(this.root), ...self.options.root] } - self.renderFile(filePath, ctx, opts).then(html => cb(null, html), cb) + self.renderFile(filePath, ctx, opts).then(html => callback(null, html) as any, callback as any) } } @@ -134,13 +138,13 @@ export class Liquid { /** * @deprecated use parseFile instead */ - public async getTemplate (file: string, opts?: LiquidOptions): Promise { + public async getTemplate (file: string, opts?: LiquidOptions): Promise { return this.parseFile(file, opts) } /** * @deprecated use parseFileSync instead */ - public getTemplateSync (file: string, opts?: LiquidOptions): ITemplate[] { + public getTemplateSync (file: string, opts?: LiquidOptions): Template[] { return this.parseFileSync(file, opts) } } diff --git a/src/parser/parse-stream.ts b/src/parser/parse-stream.ts index 5c4f8e4fd..f8f022567 100644 --- a/src/parser/parse-stream.ts +++ b/src/parser/parse-stream.ts @@ -1,8 +1,8 @@ import { Token } from '../parser/token' -import { ITemplate } from '../template/itemplate' +import { Template } from '../template/template' import { TagToken } from './tag-token' -type ParseToken = ((token: Token, remainTokens: Token[]) => ITemplate) +type ParseToken = ((token: Token, remainTokens: Token[]) => Template) export class ParseStream { private tokens: Token[] @@ -14,11 +14,11 @@ export class ParseStream { this.tokens = tokens this.parseToken = parseToken } - public on (name: string, cb: (arg: T) => void): ParseStream { + public on (name: string, cb: (arg: T) => void): ParseStream { this.handlers[name] = cb return this } - private trigger (event: string, arg?: T) { + private trigger (event: string, arg?: T) { const h = this.handlers[event] return h ? (h(arg), true) : false } diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 4f3d23ce4..8cb613a1c 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -7,7 +7,7 @@ import { OutputToken } from './output-token' import { Tag } from '../template/tag/tag' import { Output } from '../template/output' import { HTML } from '../template/html' -import { ITemplate } from '../template/itemplate' +import { Template } from '../template/template' export default class Parser { private liquid: Liquid @@ -17,7 +17,7 @@ export default class Parser { } public parse (tokens: Token[]) { let token - const templates: ITemplate[] = [] + const templates: Template[] = [] while ((token = tokens.shift())) { templates.push(this.parseToken(token, tokens)) } @@ -29,7 +29,7 @@ export default class Parser { return new Tag(token, remainTokens, this.liquid) } if (OutputToken.is(token)) { - return new Output(token as OutputToken, this.liquid.options.strictFilters) + return new Output(token as OutputToken, this.liquid.filters) } return new HTML(token) } catch (e) { diff --git a/src/render/render.ts b/src/render/render.ts index 7e5fcb9d1..76e8096b5 100644 --- a/src/render/render.ts +++ b/src/render/render.ts @@ -1,10 +1,10 @@ import { RenderError } from '../util/error' import { Context } from '../context/context' -import { ITemplate } from '../template/itemplate' +import { Template } from '../template/template' import { Emitter } from './emitter' export class Render { - public * renderTemplates (templates: ITemplate[], ctx: Context, emitter = new Emitter()): IterableIterator { + public * renderTemplates (templates: Template[], ctx: Context, emitter = new Emitter()): IterableIterator { for (const tpl of templates) { try { const html = yield tpl.render(ctx, emitter) diff --git a/src/template/filter/filter-map.ts b/src/template/filter/filter-map.ts new file mode 100644 index 000000000..83df9ff37 --- /dev/null +++ b/src/template/filter/filter-map.ts @@ -0,0 +1,23 @@ +import { FilterImplOptions } from './filter-impl-options' +import { Filter, FilterArgs } from './filter' +import { assert } from '../../util/assert' + +export class FilterMap { + private impls: {[key: string]: FilterImplOptions} = {} + + constructor (private readonly strictFilters: boolean) {} + + get (name: string) { + const impl = this.impls[name] + assert(impl || !this.strictFilters, `undefined filter: ${name}`) + return impl + } + + set (name: string, impl: FilterImplOptions) { + this.impls[name] = impl + } + + create (name: string, args: FilterArgs) { + return new Filter(name, this.get(name), args) + } +} diff --git a/src/template/filter/filter.ts b/src/template/filter/filter.ts index 8ec070068..614fee54d 100644 --- a/src/template/filter/filter.ts +++ b/src/template/filter/filter.ts @@ -11,12 +11,8 @@ export class Filter { public name: string public args: FilterArgs private impl: FilterImplOptions - private static impls: {[key: string]: FilterImplOptions} = {} - - public constructor (name: string, args: FilterArgs, strictFilters: boolean) { - const impl = Filter.impls[name] - if (!impl && strictFilters) throw new TypeError(`undefined filter: ${name}`) + public constructor (name: string, impl: FilterImplOptions, args: FilterArgs) { this.name = name this.impl = impl || identify this.args = args @@ -29,12 +25,6 @@ export class Filter { } return this.impl.apply({ context }, [value, ...argv]) } - public static register (name: string, filter: FilterImplOptions) { - Filter.impls[name] = filter - } - public static clear () { - Filter.impls = {} - } } function isKeyValuePair (arr: FilterArg): arr is KeyValuePair { diff --git a/src/template/html.ts b/src/template/html.ts index bcf863535..5f9a70b1c 100644 --- a/src/template/html.ts +++ b/src/template/html.ts @@ -1,10 +1,10 @@ +import { TemplateImpl } from '../template/template-impl' import { Template } from '../template/template' -import { ITemplate } from '../template/itemplate' import { HTMLToken } from '../parser/html-token' import { Context } from '../context/context' import { Emitter } from '../render/emitter' -export class HTML extends Template implements ITemplate { +export class HTML extends TemplateImpl implements Template { private str: string public constructor (token: HTMLToken) { super(token) diff --git a/src/template/itemplate.ts b/src/template/itemplate.ts deleted file mode 100644 index 4a314eba0..000000000 --- a/src/template/itemplate.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Context } from '../context/context' -import { Token } from '../parser/token' -import { Emitter } from '../render/emitter' - -export interface ITemplate { - token: Token; - render(ctx: Context, emitter: Emitter): any; -} diff --git a/src/template/output.ts b/src/template/output.ts index deb560077..a74615858 100644 --- a/src/template/output.ts +++ b/src/template/output.ts @@ -1,16 +1,17 @@ import { Value } from './value' +import { FilterMap } from './filter/filter-map' import { stringify, toValue } from '../util/underscore' +import { TemplateImpl } from '../template/template-impl' import { Template } from '../template/template' -import { ITemplate } from '../template/itemplate' import { Context } from '../context/context' import { Emitter } from '../render/emitter' import { OutputToken } from '../parser/output-token' -export class Output extends Template implements ITemplate { +export class Output extends TemplateImpl implements Template { private value: Value - public constructor (token: OutputToken, strictFilters: boolean) { + public constructor (token: OutputToken, filters: FilterMap) { super(token) - this.value = new Value(token.value, strictFilters) + this.value = new Value(token.value, filters) } public * render (ctx: Context, emitter: Emitter) { const val = yield this.value.value(ctx) diff --git a/src/template/tag/itag-impl.ts b/src/template/tag/itag-impl.ts deleted file mode 100644 index 218d3ff73..000000000 --- a/src/template/tag/itag-impl.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Liquid } from '../../liquid' -import { ITagImplOptions } from './itag-impl-options' - -export interface ITagImpl extends ITagImplOptions { - liquid: Liquid; - [key: string]: any; -} diff --git a/src/template/tag/itag-impl-options.ts b/src/template/tag/tag-impl-options.ts similarity index 50% rename from src/template/tag/itag-impl-options.ts rename to src/template/tag/tag-impl-options.ts index acf026a5d..954890f84 100644 --- a/src/template/tag/itag-impl-options.ts +++ b/src/template/tag/tag-impl-options.ts @@ -1,11 +1,11 @@ import { Context } from '../../context/context' import { TagToken } from '../../parser/tag-token' import { Token } from '../../parser/token' -import { ITagImpl } from './itag-impl' +import { TagImpl } from './tag-impl' import { Hash } from '../../template/tag/hash' import { Emitter } from '../../render/emitter' -export interface ITagImplOptions { - parse?: (this: ITagImpl, token: TagToken, remainingTokens: Token[]) => void; - render: (this: ITagImpl, ctx: Context, hash: Hash, emitter: Emitter) => any; +export interface TagImplOptions { + parse?: (this: TagImpl, token: TagToken, remainingTokens: Token[]) => void; + render: (this: TagImpl, ctx: Context, hash: Hash, emitter: Emitter) => any; } diff --git a/src/template/tag/tag-impl.ts b/src/template/tag/tag-impl.ts new file mode 100644 index 000000000..904b833f7 --- /dev/null +++ b/src/template/tag/tag-impl.ts @@ -0,0 +1,7 @@ +import { Liquid } from '../../liquid' +import { TagImplOptions } from './tag-impl-options' + +export interface TagImpl extends TagImplOptions { + liquid: Liquid; + [key: string]: any; +} diff --git a/src/template/tag/tag-map.ts b/src/template/tag/tag-map.ts new file mode 100644 index 000000000..fe0ef5ec4 --- /dev/null +++ b/src/template/tag/tag-map.ts @@ -0,0 +1,16 @@ +import { TagImplOptions } from './tag-impl-options' +import { assert } from '../../util/assert' + +export class TagMap { + private impls: {[key: string]: TagImplOptions} = {} + + get (name: string) { + const impl = this.impls[name] + assert(impl, `tag "${name}" not found`) + return impl + } + + set (name: string, impl: TagImplOptions) { + this.impls[name] = impl + } +} diff --git a/src/template/tag/tag.ts b/src/template/tag/tag.ts index 0676919b9..f6931d687 100644 --- a/src/template/tag/tag.ts +++ b/src/template/tag/tag.ts @@ -1,21 +1,19 @@ import { isFunction } from '../../util/underscore' -import { assert } from '../../util/assert' import { Liquid } from '../../liquid' -import { Template } from '../../template/template' -import { Emitter, Hash, Context, ITagImplOptions, TagToken, ITemplate, Token } from '../../types' -import { ITagImpl } from './itag-impl' +import { TemplateImpl } from '../../template/template-impl' +import { Emitter, Hash, Context, TagImplOptions, TagToken, Template, Token } from '../../types' +import { TagImpl } from './tag-impl' -export class Tag extends Template implements ITemplate { +export class Tag extends TemplateImpl implements Template { public name: string - private impl: ITagImpl - private static impls: { [key: string]: ITagImplOptions } = {} + private impl: TagImpl + private static impls: { [key: string]: TagImplOptions } = {} public constructor (token: TagToken, tokens: Token[], liquid: Liquid) { super(token) this.name = token.name - const impl = Tag.impls[token.name] - assert(impl, `tag ${token.name} not found`) + const impl = liquid.tags.get(token.name) this.impl = Object.create(impl) this.impl.liquid = liquid @@ -28,10 +26,4 @@ export class Tag extends Template implements ITemplate { const impl = this.impl if (isFunction(impl.render)) return yield impl.render(ctx, hash, emitter) } - public static register (name: string, tag: ITagImplOptions) { - Tag.impls[name] = tag - } - public static clear () { - Tag.impls = {} - } } diff --git a/src/template/template-impl.ts b/src/template/template-impl.ts new file mode 100644 index 000000000..3b8e9c88b --- /dev/null +++ b/src/template/template-impl.ts @@ -0,0 +1,6 @@ +export abstract class TemplateImpl { + public token: T; + public constructor (token: T) { + this.token = token + } +} diff --git a/src/template/template.ts b/src/template/template.ts index 16731c33e..5ca052fdf 100644 --- a/src/template/template.ts +++ b/src/template/template.ts @@ -1,6 +1,8 @@ -export abstract class Template { - public token: T; - public constructor (token: T) { - this.token = token - } +import { Context } from '../context/context' +import { Token } from '../parser/token' +import { Emitter } from '../render/emitter' + +export interface Template { + token: Token; + render(ctx: Context, emitter: Emitter): any; } diff --git a/src/template/value.ts b/src/template/value.ts index 5bf2d4bb0..0ed413775 100644 --- a/src/template/value.ts +++ b/src/template/value.ts @@ -1,18 +1,17 @@ import { Expression } from '../render/expression' +import { FilterMap } from '../template/filter/filter-map' import { FilterArgs, Filter } from './filter/filter' import { Context } from '../context/context' export class Value { public readonly filters: Filter[] = [] public readonly initial: string - private strictFilters: boolean /** * @param str value string, like: "i have a dream | truncate: 3 */ - public constructor (str: string, strictFilters: boolean) { + public constructor (str: string, private readonly filterMap: FilterMap) { const tokens = Value.tokenize(str) - this.strictFilters = strictFilters this.initial = tokens[0] this.parseFilters(tokens, 1) } @@ -45,7 +44,7 @@ export class Value { argValue = tokens[i] } } - this.filters.push(new Filter(name, args, this.strictFilters)) + this.filters.push(new Filter(name, this.filterMap.get(name), args)) } public * value (ctx: Context) { let val = yield new Expression(this.initial).evaluate(ctx) diff --git a/src/types.ts b/src/types.ts index cfa3605e4..2c55551ea 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,8 +5,8 @@ export { Expression } from './render/expression' export { isFalsy, isTruthy } from './render/boolean' export { TagToken } from './parser/tag-token' export { Context } from './context/context' -export { ITemplate } from './template/itemplate' -export { ITagImplOptions } from './template/tag/itag-impl-options' +export { Template } from './template/template' +export { TagImplOptions } from './template/tag/tag-impl-options' export { ParseStream } from './parser/parse-stream' export { Token } from './parser/token' export { Hash } from './template/tag/hash' diff --git a/src/util/error.ts b/src/util/error.ts index f0fec86a1..860355b50 100644 --- a/src/util/error.ts +++ b/src/util/error.ts @@ -1,6 +1,6 @@ import * as _ from './underscore' import { Token } from '../parser/token' -import { ITemplate } from '../template/itemplate' +import { Template } from '../template/template' abstract class LiquidError extends Error { private token: Token @@ -37,7 +37,7 @@ export class ParseError extends LiquidError { } export class RenderError extends LiquidError { - public constructor (err: Error, tpl: ITemplate) { + public constructor (err: Error, tpl: Template) { super(err, tpl.token) this.name = 'RenderError' this.message = err.message diff --git a/test/integration/liquid/register-filters.ts b/test/integration/liquid/register-filters.ts index 447cff36b..6b665603e 100644 --- a/test/integration/liquid/register-filters.ts +++ b/test/integration/liquid/register-filters.ts @@ -9,13 +9,13 @@ describe('liquid#registerFilter()', function () { return JSON.stringify(args) }) it('should support object', async () => { - const src = `{{ "a" | obj_test: k1: "v1", k2: foo }}`, + const src = `{{ "a" | obj_test: k1: "v1", k2: foo }}` const dst = '["a",["k1","v1"],["k2","bar"]]' const html = await liquid.parseAndRender(src, { foo: 'bar' }) return expect(html).to.equal(dst) }) it('should support mixed object', async () => { - const src = `{{ "a" | obj_test: "something", k1: "v1", k2: foo }}`, + const src = `{{ "a" | obj_test: "something", k1: "v1", k2: foo }}` const dst = '["a","something",["k1","v1"],["k2","bar"]]' const html = await liquid.parseAndRender(src, { foo: 'bar' }) return expect(html).to.equal(dst) diff --git a/test/integration/util/error.ts b/test/integration/util/error.ts index 039cfce32..ddad51ba3 100644 --- a/test/integration/util/error.ts +++ b/test/integration/util/error.ts @@ -214,12 +214,12 @@ describe('error', function () { const src = '{%if true%}\naaa{%endif%}\n{% -a %}\n3' const err = await expect(engine.parseAndRender(src)).be.rejected expect(err.name).to.equal('ParseError') - expect(err.message).to.contain('tag -a not found') + expect(err.message).to.contain('tag "-a" not found') }) it('should throw ParseError when tag not exist', async function () { const err = await expect(engine.parseAndRender('{% a %}')).be.rejected expect(err.name).to.equal('ParseError') - expect(err.message).to.contain('tag a not found') + expect(err.message).to.contain('tag "a" not found') }) it('should contain template context in err.stack', async function () { @@ -231,10 +231,10 @@ describe('error', function () { ' 5| 5th', ' 6| 6th', ' 7| 7th', - 'ParseError: tag a not found' + 'ParseError: tag "a" not found' ] const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected - expect(err.message).to.equal('tag a not found, line:4, col:2') + expect(err.message).to.equal('tag "a" not found, line:4, col:2') expect(err.stack).to.contain(message.join('\n')) expect(err.name).to.equal('ParseError') }) @@ -246,10 +246,10 @@ describe('error', function () { '>> 2| X{% a %} {% enda %} Y', ' 3| 3rd', ' 4| 4th', - 'ParseError: tag a not found' + 'ParseError: tag "a" not found' ] const err = await expect(engine.parseAndRender(html.join('\n'))).be.rejected - expect(err.message).to.equal('tag a not found, line:2, col:2') + expect(err.message).to.equal('tag "a" not found, line:2, col:2') expect(err.stack).to.contain(message.join('\n')) }) @@ -261,12 +261,12 @@ describe('error', function () { it('should contain stack in err.stack', async function () { const err = await expect(engine.parseAndRender('{% -a %}')).be.rejected - expect(err.stack).to.contain('ParseError: tag -a not found') + expect(err.stack).to.contain('ParseError: tag "-a" not found') expect(err.stack).to.match(/at .*:\d+:\d+\)/) }) }) describe('sync support', function () { - let engine + let engine: Liquid beforeEach(function () { engine = new Liquid({ root: '/' diff --git a/test/unit/render/render.ts b/test/unit/render/render.ts index e49e91b9b..d88c36a25 100644 --- a/test/unit/render/render.ts +++ b/test/unit/render/render.ts @@ -1,8 +1,6 @@ import { expect } from 'chai' import { Context } from '../../../src/context/context' import { Token } from '../../../src/parser/token' -import { Tag } from '../../../src/template/tag/tag' -import { Filter } from '../../../src/template/filter/filter' import { Render } from '../../../src/render/render' import { HTML } from '../../../src/template/html' import { toThenable } from '../../../src/util/async' @@ -10,8 +8,6 @@ import { toThenable } from '../../../src/util/async' describe('render', function () { let render: Render before(function () { - Filter.clear() - Tag.clear() render = new Render() }) diff --git a/test/unit/template/filter/filter.ts b/test/unit/template/filter/filter.ts index 5ef24f241..385fffee5 100644 --- a/test/unit/template/filter/filter.ts +++ b/test/unit/template/filter/filter.ts @@ -1,69 +1,70 @@ import * as chai from 'chai' import * as sinon from 'sinon' import * as sinonChai from 'sinon-chai' -import { Filter } from '../../../../src/template/filter/filter' import { Context } from '../../../../src/context/context' import { toThenable } from '../../../../src/util/async' +import { FilterMap } from '../../../../src/template/filter/filter-map' chai.use(sinonChai) const expect = chai.expect describe('filter', function () { let ctx: Context + let filters: FilterMap beforeEach(function () { - Filter.clear() + filters = new FilterMap(false) ctx = new Context() }) it('should create default filter if not registered', async function () { - const result = new Filter('foo', [], false) as any + const result = filters.create('foo', []) as any expect(result.name).to.equal('foo') }) it('should render input if filter not registered', async function () { - expect(await toThenable(new Filter('undefined', [], false).render('foo', ctx))).to.equal('foo') + expect(await toThenable(filters.create('undefined', []).render('foo', ctx))).to.equal('foo') }) it('should call filter impl with correct arguments', async function () { const spy = sinon.spy() - Filter.register('foo', spy) - await toThenable(new Filter('foo', ['33'], false).render('foo', ctx)) + filters.set('foo', spy) + await toThenable(filters.create('foo', ['33']).render('foo', ctx)) expect(spy).to.have.been.calledWith('foo', 33) }) it('should call filter impl with correct this arg', async function () { const spy = sinon.spy() - Filter.register('foo', spy) - await toThenable(new Filter('foo', ['33'], false).render('foo', ctx)) + filters.set('foo', spy) + await toThenable(filters.create('foo', ['33']).render('foo', ctx)) expect(spy).to.have.been.calledOn(sinon.match.has('context', ctx)) }) it('should render a simple filter', async function () { - Filter.register('upcase', x => x.toUpperCase()) - expect(await toThenable(new Filter('upcase', [], false).render('foo', ctx))).to.equal('FOO') + filters.set('upcase', x => x.toUpperCase()) + expect(await toThenable(filters.create('upcase', []).render('foo', ctx))).to.equal('FOO') }) it('should render filters with argument', async function () { - Filter.register('add', (a, b) => a + b) - expect(await toThenable(new Filter('add', ['2'], false).render(3, ctx))).to.equal(5) + filters.set('add', (a, b) => a + b) + expect(await toThenable(filters.create('add', ['2']).render(3, ctx))).to.equal(5) }) it('should render filters with multiple arguments', async function () { - Filter.register('add', (a, b, c) => a + b + c) - expect(await toThenable(new Filter('add', ['2', '"c"'], false).render(3, ctx))).to.equal('5c') + filters.set('add', (a, b, c) => a + b + c) + expect(await toThenable(filters.create('add', ['2', '"c"']).render(3, ctx))).to.equal('5c') }) it('should pass Objects/Drops as it is', async function () { - Filter.register('name', a => a.constructor.name) + filters.set('name', a => a.constructor.name) class Foo {} - expect(await toThenable(new Filter('name', [], false).render(new Foo(), ctx))).to.equal('Foo') + expect(await toThenable(filters.create('name', []).render(new Foo(), ctx))).to.equal('Foo') }) it('should not throw when filter name illegal', function () { expect(function () { - new Filter('/', [], false) + filters.create('/', []) }).to.not.throw() }) it('should support key value pairs', async function () { - Filter.register('add', (a, b) => b[0] + ':' + (a + b[1])) - expect(await toThenable((new Filter('add', [['num', '2']], false).render(3, ctx)))).to.equal('num:5') + filters.set('add', (a, b) => b[0] + ':' + (a + b[1])) + expect(await toThenable((filters.create('add', [['num', '2']]).render(3, ctx)))).to.equal('num:5') }) }) diff --git a/test/unit/template/output.ts b/test/unit/template/output.ts index 61c0e56bb..5f06b62ea 100644 --- a/test/unit/template/output.ts +++ b/test/unit/template/output.ts @@ -3,14 +3,15 @@ import { toThenable } from '../../../src/util/async' import { Context } from '../../../src/context/context' import { Output } from '../../../src/template/output' import { OutputToken } from '../../../src/parser/output-token' -import { Filter } from '../../../src/template/filter/filter' +import { FilterMap } from '../../../src/template/filter/filter-map' const expect = chai.expect describe('Output', function () { const emitter: any = { write: (html: string) => (emitter.html += html), html: '' } + let filters: FilterMap beforeEach(function () { - Filter.clear() + filters = new FilterMap(false) emitter.html = '' }) @@ -18,25 +19,25 @@ describe('Output', function () { const scope = new Context({ foo: { obj: { arr: ['a', 2] } } }) - const output = new Output({ value: 'foo' } as OutputToken, false) + const output = new Output({ value: 'foo' } as OutputToken, filters) await toThenable(output.render(scope, emitter)) return expect(emitter.html).to.equal('[object Object]') }) it('should skip function property', async function () { const scope = new Context({ obj: { foo: 'foo', bar: (x: any) => x } }) - const output = new Output({ value: 'obj' } as OutputToken, false) + const output = new Output({ value: 'obj' } as OutputToken, filters) await toThenable(output.render(scope, emitter)) return expect(emitter.html).to.equal('[object Object]') }) it('should respect to .toString()', async () => { const scope = new Context({ obj: { toString: () => 'FOO' } }) - const output = new Output({ value: 'obj' } as OutputToken, false) + const output = new Output({ value: 'obj' } as OutputToken, filters) await toThenable(output.render(scope, emitter)) return expect(emitter.html).to.equal('FOO') }) it('should respect to .toString()', async () => { const scope = new Context({ obj: { toString: () => 'FOO' } }) - const output = new Output({ value: 'obj' } as OutputToken, false) + const output = new Output({ value: 'obj' } as OutputToken, filters) await toThenable(output.render(scope, emitter)) return expect(emitter.html).to.equal('FOO') }) diff --git a/test/unit/template/tag.ts b/test/unit/template/tag.ts index 4c6036c3a..3a500a7fb 100644 --- a/test/unit/template/tag.ts +++ b/test/unit/template/tag.ts @@ -9,10 +9,10 @@ import { toThenable } from '../../../src/util/async' chai.use(sinonChai) const expect = chai.expect -const liquid = new Liquid() describe('Tag', function () { let ctx: Context + let liquid: Liquid const emitter: any = { write: (html: string) => (emitter.html += html), html: '' } before(function () { ctx = new Context({ @@ -22,7 +22,7 @@ describe('Tag', function () { coo: 'uoo' } }) - Tag.clear() + liquid = new Liquid() }) beforeEach(function () { emitter.html = '' @@ -33,24 +33,20 @@ describe('Tag', function () { new Tag({ // eslint-disable-line type: 'tag', value: 'foo', - name: 'foo' + name: 'not-exist' } as TagToken, [], liquid) - }).to.throw(/tag foo not found/) + }).to.throw(/tag "not-exist" not found/) }) it('should register simple tag', function () { expect(function () { - Tag.register('foo', { - render: () => 'bar' - }) + liquid.registerTag('foo', { render: () => 'bar' }) }).not.throw() }) it('should call tag.render', async function () { const spy = sinon.spy() - Tag.register('foo', { - render: spy - }) + liquid.registerTag('foo', { render: spy }) const token = { type: 'tag', value: 'foo', @@ -64,9 +60,7 @@ describe('Tag', function () { let spy: sinon.SinonSpy, token: TagToken beforeEach(function () { spy = sinon.spy() - Tag.register('foo', { - render: spy - }) + liquid.registerTag('foo', { render: spy }) token = { type: 'tag', value: 'foo aa:foo bb: arr[0] cc: 2.3\ndd:bar.coo', diff --git a/test/unit/template/value.ts b/test/unit/template/value.ts index c03ccf42c..442b2b8c9 100644 --- a/test/unit/template/value.ts +++ b/test/unit/template/value.ts @@ -1,9 +1,9 @@ import * as chai from 'chai' import { toThenable } from '../../../src/util/async' +import { FilterMap } from '../../../src/template/filter/filter-map' import * as sinonChai from 'sinon-chai' import * as sinon from 'sinon' import { Context } from '../../../src/context/context' -import { Filter } from '../../../src/template/filter/filter' import { Value } from '../../../src/template/value' chai.use(sinonChai) @@ -11,74 +11,73 @@ chai.use(sinonChai) const expect = chai.expect describe('Value', function () { - beforeEach(() => Filter.clear()) - describe('#constructor()', function () { + const filterMap = new FilterMap(false) it('should parse "foo', function () { - const tpl = new Value('foo', false) + const tpl = new Value('foo', filterMap) expect(tpl.initial).to.equal('foo') expect(tpl.filters).to.deep.equal([]) }) it('should parse "foo | add"', function () { - const tpl = new Value('foo | add', false) + const tpl = new Value('foo | add', filterMap) expect(tpl.initial).to.equal('foo') expect(tpl.filters.length).to.equal(1) expect(tpl.filters[0].args).to.eql([]) }) it('should parse "foo,foo | add"', function () { - const tpl = new Value('foo,foo | add', false) + const tpl = new Value('foo,foo | add', filterMap) expect(tpl.initial).to.equal('foo') expect(tpl.filters.length).to.equal(1) expect(tpl.filters[0].args).to.eql([]) }) it('should parse "foo | add: 3, false"', function () { - const tpl = new Value('foo | add: 3, "foo"', false) + const tpl = new Value('foo | add: 3, "foo"', filterMap) expect(tpl.initial).to.equal('foo') expect(tpl.filters.length).to.equal(1) expect(tpl.filters[0].args).to.eql(['3', '"foo"']) }) it('should parse "foo | add: "foo" bar, 3"', function () { - const tpl = new Value('foo | add: "foo" bar, 3', false) + const tpl = new Value('foo | add: "foo" bar, 3', filterMap) expect(tpl.initial).to.equal('foo') expect(tpl.filters.length).to.equal(1) expect(tpl.filters[0].name).to.eql('add') expect(tpl.filters[0].args).to.eql(['"foo"', '3']) }) it('should parse "foo | add: "|", 3', function () { - const tpl = new Value('foo | add: "|", 3', false) + const tpl = new Value('foo | add: "|", 3', filterMap) expect(tpl.initial).to.equal('foo') expect(tpl.filters.length).to.equal(1) expect(tpl.filters[0].args).to.eql(['"|"', '3']) }) it('should parse "foo | add: "|", 3', function () { - const tpl = new Value('foo | add: "|", 3', false) + const tpl = new Value('foo | add: "|", 3', filterMap) expect(tpl.initial).to.equal('foo') expect(tpl.filters.length).to.equal(1) expect(tpl.filters[0].args).to.eql(['"|"', '3']) }) it('should support arguments as named key/values', function () { - const f = new Value('o | foo: key1: "literal1", key2: value2', false) + const f = new Value('o | foo: key1: "literal1", key2: value2', filterMap) expect(f.filters[0].name).to.equal('foo') expect(f.filters[0].args).to.eql([['key1', '"literal1"'], ['key2', 'value2']]) }) it('should support arguments as named key/values with inline literals', function () { - const f = new Value('o | foo: "test0", key1: "literal1", key2: value2', false) + const f = new Value('o | foo: "test0", key1: "literal1", key2: value2', filterMap) expect(f.filters[0].name).to.equal('foo') expect(f.filters[0].args).to.deep.equal(['"test0"', ['key1', '"literal1"'], ['key2', 'value2']]) }) it('should support arguments as named key/values with inline values', function () { - const f = new Value('o | foo: test0, key1: "literal1", key2: value2', false) + const f = new Value('o | foo: test0, key1: "literal1", key2: value2', filterMap) expect(f.filters[0].name).to.equal('foo') expect(f.filters[0].args).to.deep.equal(['test0', ['key1', '"literal1"'], ['key2', 'value2']]) }) it('should support argument values named same as keys', function () { - const f = new Value('o | foo: a: a', false) + const f = new Value('o | foo: a: a', filterMap) expect(f.filters[0].name).to.equal('foo') expect(f.filters[0].args).to.deep.equal([['a', 'a']]) }) it('should support argument literals named same as keys', function () { - const f = new Value('o | foo: a: "a"', false) + const f = new Value('o | foo: a: "a"', filterMap) expect(f.filters[0].name).to.equal('foo') expect(f.filters[0].args).to.deep.equal([['a', '"a"']]) }) @@ -115,9 +114,10 @@ describe('Value', function () { it('should call chained filters correctly', async function () { const date = sinon.stub().returns('y') const time = sinon.spy() - Filter.register('date', date) - Filter.register('time', time) - const tpl = new Value('foo.bar | date: "b" | time:2', false) + const filterMap = new FilterMap(false) + filterMap.set('date', date) + filterMap.set('time', time) + const tpl = new Value('foo.bar | date: "b" | time:2', filterMap) const scope = new Context({ foo: { bar: 'bar' } }) diff --git a/tsconfig.json b/tsconfig.json index 9fa576dd3..57d176f40 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,5 +13,5 @@ "suppressImplicitAnyIndexErrors": true }, "all": true, - "exclude": [ "node_modules", "dist" ] + "exclude": [ "node_modules", "dist", "demo" ] }