diff --git a/docs/source/tutorials/register-filters-tags.md b/docs/source/tutorials/register-filters-tags.md index 961ca1ada6..3ceb12d0d2 100644 --- a/docs/source/tutorials/register-filters-tags.md +++ b/docs/source/tutorials/register-filters-tags.md @@ -12,8 +12,8 @@ engine.registerTag('upper', { parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) { this.str = tagToken.args; // name }, - render: async function(ctx: Context) { - var str = await this.liquid.evalValue(this.str, ctx); // 'alice' + render: function*(ctx: Context) { + const str = yield this.liquid.evalValue(this.str, ctx); // 'alice' return str.toUpperCase() // 'ALICE' } }); @@ -22,6 +22,25 @@ engine.registerTag('upper', { * `parse`: Read tokens from `remainTokens` until your end token. * `render`: Combine scope data with your parsed tokens into HTML string. +For complex tag implementation, you can also provide a tag class: + +```typescript +// Usage: {% upper name:"alice" %} +import { Hash, Tag, TagToken, Context, Emitter, TopLevelToken, Liquid } from 'liquidjs' + +engine.registerTag('upper', class UpperTag extends Tag { + private hash: Hash + constructor(tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(tagToken, remainTokens, liquid) + this.hash = new Hash(tagToken.args) + } + * render(ctx: Context) { + const hash = yield this.hash.render(); + return hash.name.toUpperCase() // 'ALICE' + } +}); +``` + See existing tag implementations here: See demo example here: https://github.com/harttle/liquidjs/blob/master/demo/typescript/index.ts diff --git a/docs/source/zh-cn/tutorials/register-filters-tags.md b/docs/source/zh-cn/tutorials/register-filters-tags.md index 9890848dda..6ff7121444 100644 --- a/docs/source/zh-cn/tutorials/register-filters-tags.md +++ b/docs/source/zh-cn/tutorials/register-filters-tags.md @@ -36,7 +36,24 @@ engine.registerFilter('upper', v => v.toUpperCase()) engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2) ``` -查看已有的过滤器实现: +查看已有的过滤器实现:。对于复杂的标签,也可以用一个类来实现: + +```typescript +// Usage: {% upper name:"alice" %} +import { Hash, Tag, TagToken, Context, Emitter, TopLevelToken, Liquid } from 'liquidjs' + +engine.registerTag('upper', class UpperTag extends Tag { + private hash: Hash + constructor(tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(tagToken, remainTokens, liquid) + this.hash = new Hash(tagToken.args) + } + * render(ctx: Context) { + const hash = yield this.hash.render(); + return hash.name.toUpperCase() // 'ALICE' + } +}); +``` ## 反注册标签/过滤器 diff --git a/package.json b/package.json index f0970db5a4..e4f3a6a55a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "./dist/liquid.node.cjs.js": "./dist/liquid.browser.umd.js", "./dist/liquid.node.esm.js": "./dist/liquid.browser.esm.js" }, - "types": "dist/liquid.d.ts", + "types": "dist/index.d.ts", "engines": { "node": ">=14" }, @@ -44,9 +44,6 @@ "LICENSE", "README.md" ], - "engines": { - "node": ">=4.8.7" - }, "keywords": [ "liquid", "template engine", diff --git a/rollup.config.mjs b/rollup.config.mjs index a382accb0b..bf0225c410 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -38,16 +38,16 @@ const versionInjection = versionInjector({ logger: console, exclude: [] }) -const input = './src/liquid.ts' +const input = './src/index.ts' const browserFS = { include: './src/liquid-options.ts', delimiters: ['', ''], './fs/node': './fs/browser' } const browserStream = { - include: './src/render/render.ts', + include: './src/emitters/index.ts', delimiters: ['', ''], - '../emitters/streamed-emitter': '../emitters/streamed-emitter-browser' + './streamed-emitter': './streamed-emitter-browser' } const esmRequire = { include: './src/fs/node.ts', diff --git a/src/cache/index.ts b/src/cache/index.ts new file mode 100644 index 0000000000..fc1e352ef3 --- /dev/null +++ b/src/cache/index.ts @@ -0,0 +1,2 @@ +export * from './cache' +export * from './lru' diff --git a/src/cache/lru.ts b/src/cache/lru.ts index ec14d3aa10..caf13f9603 100644 --- a/src/cache/lru.ts +++ b/src/cache/lru.ts @@ -10,7 +10,7 @@ class Node { } export class LRU implements Cache { - private cache: { [key: string]: Node } = {} + private cache: Record> = {} private head: Node private tail: Node diff --git a/src/context/block-mode.ts b/src/context/block-mode.ts index 78fd321446..dc61cac263 100644 --- a/src/context/block-mode.ts +++ b/src/context/block-mode.ts @@ -1,8 +1,6 @@ -enum BlockMode { +export enum BlockMode { /* store rendered html into blocks */ OUTPUT, /* output rendered html directly */ STORE } - -export default BlockMode diff --git a/src/context/context.ts b/src/context/context.ts index 2b75496c70..7339870bea 100644 --- a/src/context/context.ts +++ b/src/context/context.ts @@ -2,9 +2,7 @@ import { Drop } from '../drop/drop' import { __assign } from 'tslib' import { NormalizedFullOptions, defaultOptions, RenderOptions } from '../liquid-options' import { Scope } from './scope' -import { isArray, isNil, isString, isFunction, toLiquid } from '../util/underscore' -import { InternalUndefinedVariableError } from '../util/error' -import { toValueSync } from '../util/async' +import { isArray, isNil, isString, isFunction, toLiquid, InternalUndefinedVariableError, toValueSync } from '../util' type PropertyKey = string | number; diff --git a/src/context/index.ts b/src/context/index.ts new file mode 100644 index 0000000000..d5c7d6a194 --- /dev/null +++ b/src/context/index.ts @@ -0,0 +1,3 @@ +export * from './context' +export * from './scope' +export * from './block-mode' diff --git a/src/context/scope.ts b/src/context/scope.ts index 748e316a9e..f8f0ab057c 100644 --- a/src/context/scope.ts +++ b/src/context/scope.ts @@ -1,8 +1,7 @@ import { Drop } from '../drop/drop' -export interface PlainObject { - [key: string]: any; +interface ScopeObject extends Record { toLiquid?: () => any; } -export type Scope = PlainObject | Drop +export type Scope = ScopeObject | Drop diff --git a/src/drop/blank-drop.ts b/src/drop/blank-drop.ts index 652dcf8b1a..79335392fd 100644 --- a/src/drop/blank-drop.ts +++ b/src/drop/blank-drop.ts @@ -1,5 +1,5 @@ -import { isNil, isString, toValue } from '../util/underscore' -import { EmptyDrop } from '../drop/empty-drop' +import { isNil, isString, toValue } from '../util' +import { EmptyDrop } from '../drop' export class BlankDrop extends EmptyDrop { public equals (value: any) { diff --git a/src/drop/comparable.ts b/src/drop/comparable.ts index 16aaef3a98..9e7cd69a9c 100644 --- a/src/drop/comparable.ts +++ b/src/drop/comparable.ts @@ -1,4 +1,4 @@ -import { isFunction } from '../util/underscore' +import { isFunction } from '../util' export interface Comparable { equals: (rhs: any) => boolean; diff --git a/src/drop/empty-drop.ts b/src/drop/empty-drop.ts index cf9664bda4..16d8970ebc 100644 --- a/src/drop/empty-drop.ts +++ b/src/drop/empty-drop.ts @@ -1,6 +1,6 @@ import { Drop } from './drop' import { Comparable } from './comparable' -import { isObject, isString, isArray, toValue } from '../util/underscore' +import { isObject, isString, isArray, toValue } from '../util' export class EmptyDrop extends Drop implements Comparable { public equals (value: any) { diff --git a/src/drop/index.ts b/src/drop/index.ts new file mode 100644 index 0000000000..6edaf8e794 --- /dev/null +++ b/src/drop/index.ts @@ -0,0 +1,7 @@ +export * from './drop' +export * from './null-drop' +export * from './empty-drop' +export * from './blank-drop' +export * from './forloop-drop' +export * from './block-drop' +export * from './comparable' diff --git a/src/drop/null-drop.ts b/src/drop/null-drop.ts index ea7e322c3b..4908fd40ca 100644 --- a/src/drop/null-drop.ts +++ b/src/drop/null-drop.ts @@ -1,6 +1,6 @@ import { Drop } from './drop' import { Comparable } from './comparable' -import { isNil, toValue } from '../util/underscore' +import { isNil, toValue } from '../util' export class NullDrop extends Drop implements Comparable { public equals (value: any) { diff --git a/src/emitters/index.ts b/src/emitters/index.ts new file mode 100644 index 0000000000..0e27dd0e56 --- /dev/null +++ b/src/emitters/index.ts @@ -0,0 +1,4 @@ +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 index 7565f264f2..91d406286c 100644 --- a/src/emitters/keeping-type-emitter.ts +++ b/src/emitters/keeping-type-emitter.ts @@ -1,5 +1,5 @@ -import { stringify, toValue } from '../util/underscore' -import { Emitter } from '../types' +import { stringify, toValue } from '../util' +import { Emitter } from './emitter' export class KeepingTypeEmitter implements Emitter { public buffer: any = ''; diff --git a/src/emitters/simple-emitter.ts b/src/emitters/simple-emitter.ts index 64a7f5b20d..f1d0482068 100644 --- a/src/emitters/simple-emitter.ts +++ b/src/emitters/simple-emitter.ts @@ -1,4 +1,4 @@ -import { stringify } from '../util/underscore' +import { stringify } from '../util' import { Emitter } from './emitter' export class SimpleEmitter implements Emitter { diff --git a/src/emitters/streamed-emitter.ts b/src/emitters/streamed-emitter.ts index 83a9ca25e7..6750ea3b4e 100644 --- a/src/emitters/streamed-emitter.ts +++ b/src/emitters/streamed-emitter.ts @@ -1,4 +1,4 @@ -import { stringify } from '../util/underscore' +import { stringify } from '../util' import { Emitter } from './emitter' import { PassThrough } from 'stream' diff --git a/src/filters/array.ts b/src/filters/array.ts index a7ae807b0f..26ad9d0e20 100644 --- a/src/filters/array.ts +++ b/src/filters/array.ts @@ -1,9 +1,8 @@ -import { argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast, hasOwnProperty } from '../util/underscore' -import { toArray } from '../util/collection' -import { isTruthy } from '../render/boolean' -import { FilterImpl } from '../template/filter/filter-impl' -import { Scope } from '../context/scope' -import { isComparable } from '../drop/comparable' +import { toArray, argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast, hasOwnProperty } from '../util' +import { isTruthy } from '../render' +import { FilterImpl } from '../template' +import { Scope } from '../context' +import { isComparable } from '../drop' export const join = argumentsToValue((v: any[], arg: string) => toArray(v).join(arg === undefined ? ' ' : arg)) export const last = argumentsToValue((v: any) => isArray(v) ? arrayLast(v) : '') diff --git a/src/filters/date.ts b/src/filters/date.ts index 6c1a9af9b8..e4ca440203 100644 --- a/src/filters/date.ts +++ b/src/filters/date.ts @@ -1,8 +1,5 @@ -import strftime from '../util/strftime' -import { LiquidDate } from '../util/liquid-date' -import { toValue, stringify, isString, isNumber } from '../util/underscore' -import { FilterImpl } from '../template/filter/filter-impl' -import { TimezoneDate } from '../util/timezone-date' +import { toValue, stringify, isString, isNumber, TimezoneDate, LiquidDate, strftime } from '../util' +import { FilterImpl } from '../template' export function date (this: FilterImpl, v: string | Date, format: string, timeZoneOffset?: number) { const opts = this.context.opts diff --git a/src/filters/index.ts b/src/filters/index.ts index 8fa56176e2..7347035265 100644 --- a/src/filters/index.ts +++ b/src/filters/index.ts @@ -5,9 +5,9 @@ import * as arrayFilters from './array' import * as dateFilters from './date' import * as stringFilters from './string' import { Default, json } from './misc' -import { FilterImplOptions } from '../template/filter/filter-impl-options' +import { FilterImplOptions } from '../template' -export const filters: { [key: string]: FilterImplOptions } = { +export const filters: Record = { ...htmlFilters, ...mathFilters, ...urlFilters, diff --git a/src/filters/misc.ts b/src/filters/misc.ts index a6b9619f8c..71968bbbe4 100644 --- a/src/filters/misc.ts +++ b/src/filters/misc.ts @@ -1,6 +1,6 @@ import { isFalsy } from '../render/boolean' import { identify, isArray, isString, toValue } from '../util/underscore' -import { FilterImpl } from '../template/filter/filter-impl' +import { FilterImpl } from '../template' export function Default (this: FilterImpl, value: T1, defaultValue: T2, ...args: Array<[string, any]>): T1 | T2 { value = toValue(value) diff --git a/src/filters/string.ts b/src/filters/string.ts index 49ff3cb6dc..bf739db8bb 100644 --- a/src/filters/string.ts +++ b/src/filters/string.ts @@ -3,8 +3,7 @@ * * * prefer stringify() to String() since `undefined`, `null` should eval '' */ -import { escapeRegExp, stringify } from '../util/underscore' -import { assert } from '../util/assert' +import { assert, escapeRegExp, stringify } from '../util' export function append (v: string, arg: string) { assert(arguments.length === 2, 'append expect 2 arguments') diff --git a/src/fs/browser.ts b/src/fs/browser.ts index fe0dd94446..fef1586382 100644 --- a/src/fs/browser.ts +++ b/src/fs/browser.ts @@ -1,4 +1,4 @@ -import { last } from '../util/underscore' +import { last } from '../util' function domResolve (root: string, path: string) { const base = document.createElement('base') diff --git a/src/fs/index.ts b/src/fs/index.ts new file mode 100644 index 0000000000..3c6ec813ec --- /dev/null +++ b/src/fs/index.ts @@ -0,0 +1,2 @@ +export * from './loader' +export * from './fs' diff --git a/src/fs/loader.ts b/src/fs/loader.ts index 76fa1f5505..86decd36b9 100644 --- a/src/fs/loader.ts +++ b/src/fs/loader.ts @@ -1,6 +1,5 @@ import { FS } from './fs' -import { escapeRegex } from '../util/underscore' -import { assert } from '../util/assert' +import { assert, escapeRegex } from '../util' export interface LoaderOptions { fs: FS; diff --git a/src/fs/node.ts b/src/fs/node.ts index 5f15d196b7..af414ee2f8 100644 --- a/src/fs/node.ts +++ b/src/fs/node.ts @@ -1,10 +1,10 @@ -import * as _ from '../util/underscore' +import { promisify } from '../util' import { sep, resolve as nodeResolve, extname, dirname as nodeDirname } from 'path' import { stat, statSync, readFile as nodeReadFile, readFileSync as nodeReadFileSync } from 'fs' import { requireResolve } from './node-require' -const statAsync = _.promisify(stat) -const readFileAsync = _.promisify(nodeReadFile) +const statAsync = promisify(stat) +const readFileAsync = promisify(nodeReadFile) export async function exists (filepath: string) { try { diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000000..0ee3daf4f0 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,16 @@ +/* istanbul ignore file */ +export const version = '[VI]{version}[/VI]' +export * as TypeGuards from './util/type-guards' +export { toValue, TimezoneDate, createTrie, Trie, toPromise, toValueSync, assert, ParseError, TokenizationError, AssertionError } from './util' +export { Drop } from './drop' +export { Emitter } from './emitters' +// TODO change to _evalToken +export { defaultOperators, Operators, _evalToken, evalQuotedToken, Expression, isFalsy, isTruthy } from './render' +export { Context, Scope } from './context' +export { Value, Hash, Template, FilterImplOptions, Tag, Filter } from './template' +export { Token, TopLevelToken, TagToken, ValueToken } from './tokens' +export { TokenKind, Tokenizer, ParseStream } from './parser' +export { filters } from './filters' +export { tags } from './tags' +export { defaultOptions } from './liquid-options' +export { Liquid } from './liquid' diff --git a/src/liquid-options.ts b/src/liquid-options.ts index a45ca5ec27..1bc261584b 100644 --- a/src/liquid-options.ts +++ b/src/liquid-options.ts @@ -1,11 +1,9 @@ -import { isArray, isString, isFunction } from './util/underscore' -import { LiquidCache } from './cache/cache' -import { LRU } from './cache/lru' +import { assert, isArray, isString, isFunction } from './util' +import { LRU, LiquidCache } from './cache' import { FS } from './fs/fs' import * as fs from './fs/node' -import { defaultOperators, Operators } from './render/operator' +import { defaultOperators, Operators } from './render' import { filters } from './filters' -import { assert } from './util/assert' type OutputEscape = (value: any) => string type OutputEscapeOption = 'escape' | 'json' | OutputEscape diff --git a/src/liquid.ts b/src/liquid.ts index bae6164543..2b21b5f5f9 100644 --- a/src/liquid.ts +++ b/src/liquid.ts @@ -1,38 +1,24 @@ -import { Context } from './context/context' -import { forOwn } from './util/underscore' -import { Template } from './template/template' +import { Context } from './context' +import { toPromise, toValueSync, isFunction, forOwn } from './util' +import { TagClass, createTagClass, TagImplOptions, FilterImplOptions, Template, Value } from './template' import { LookupType } from './fs/loader' -import { Render } from './render/render' -import Parser from './parser/parser' -import { TagImplOptions } from './template/tag/tag-impl-options' -import { Value } from './template/value' +import { Render } from './render' +import { Parser } from './parser' import { tags } from './tags' import { filters } from './filters' -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, toValueSync } from './util/async' - -export * from './util/error' -export * from './types' -export const version = '[VI]{version}[/VI]' export class Liquid { public readonly options: NormalizedFullOptions - public readonly renderer: Render + public readonly renderer = new Render() public readonly parser: Parser - public readonly filters: FilterMap - public readonly tags: TagMap + public readonly filters: Record = {} + public readonly tags: Record = {} public constructor (opts: LiquidOptions = {}) { this.options = normalize(opts) this.parser = new Parser(this) - this.renderer = new Render() - this.filters = new FilterMap(this.options.strictFilters, this) - this.tags = new TagMap() - - forOwn(tags, (conf: TagImplOptions, name: string) => this.registerTag(name, conf)) + forOwn(tags, (conf: TagClass, name: string) => this.registerTag(name, conf)) forOwn(filters, (handler: FilterImplOptions, name: string) => this.registerFilter(name, handler)) } public parse (html: string, filepath?: string): Template[] { @@ -103,10 +89,10 @@ export class Liquid { } public registerFilter (name: string, filter: FilterImplOptions) { - this.filters.set(name, filter) + this.filters[name] = filter } - public registerTag (name: string, tag: TagImplOptions) { - this.tags.set(name, tag) + public registerTag (name: string, tag: TagClass | TagImplOptions) { + this.tags[name] = isFunction(tag) ? tag : createTagClass(tag) } public plugin (plugin: (this: Liquid, L: typeof Liquid) => void) { return plugin.call(this, Liquid) diff --git a/src/parser/filter-arg.ts b/src/parser/filter-arg.ts index b3e4990681..d1b49b0a6c 100644 --- a/src/parser/filter-arg.ts +++ b/src/parser/filter-arg.ts @@ -5,6 +5,6 @@ type KeyValuePair = [string?, ValueToken?] export type FilterArg = ValueToken | KeyValuePair -export function isKeyValuePair (arr: FilterArg): arr is KeyValuePair { // TODO check +export function isKeyValuePair (arr: FilterArg): arr is KeyValuePair { return isArray(arr) } diff --git a/src/parser/index.ts b/src/parser/index.ts new file mode 100644 index 0000000000..c593e190ab --- /dev/null +++ b/src/parser/index.ts @@ -0,0 +1,5 @@ +export * from './tokenizer' +export * from './parser' +export * from './parse-stream' +export * from './parse-string-literal' +export * from './token-kind' diff --git a/src/parser/match-operator.ts b/src/parser/match-operator.ts index 37ccde58c2..9ea80216e7 100644 --- a/src/parser/match-operator.ts +++ b/src/parser/match-operator.ts @@ -1,8 +1,7 @@ -import { IDENTIFIER, TYPES } from '../util/character' -import { Trie } from '../util/operator-trie' +import { Trie, TrieNode, IDENTIFIER, TYPES } from '../util' export function matchOperator (str: string, begin: number, trie: Trie, end = str.length) { - let node = trie + let node: TrieNode = trie let i = begin let info while (node[str[i]] && i < end) { diff --git a/src/parser/parse-stream.ts b/src/parser/parse-stream.ts index 7543f21259..7173267fa5 100644 --- a/src/parser/parse-stream.ts +++ b/src/parser/parse-stream.ts @@ -1,13 +1,12 @@ -import { Token } from '../tokens/token' -import { Template } from '../template/template' -import { isTagToken } from '../util/type-guards' -import { TopLevelToken } from '../tokens/toplevel-token' +import { Token, TopLevelToken } from '../tokens' +import { Template } from '../template' +import { isTagToken } from '../util' type ParseToken = ((token: T, remainTokens: T[]) => Template) export class ParseStream { private tokens: T[] - private handlers: {[key: string]: (arg: any) => void} = {} + private handlers: Record void> = {} private stopRequested = false private parseToken: ParseToken diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 3cae7a9c77..ff9d215712 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -1,19 +1,13 @@ -import { ParseError } from '../util/error' -import { Liquid, Tokenizer } from '../liquid' +import { toPromise, assert, isTagToken, isOutputToken, ParseError } from '../util' +import { Tokenizer } from './tokenizer' import { ParseStream } from './parse-stream' -import { isTagToken, isOutputToken } from '../util/type-guards' -import { OutputToken } from '../tokens/output-token' -import { Tag } from '../template/tag/tag' -import { Output } from '../template/output' -import { HTML } from '../template/html' -import { Template } from '../template/template' -import { TopLevelToken } from '../tokens/toplevel-token' -import { LiquidCache } from '../cache/cache' -import { Loader, LookupType } from '../fs/loader' -import { toPromise } from '../util/async' -import { FS } from '../fs/fs' +import { TopLevelToken, OutputToken } from '../tokens' +import { Template, Output, HTML } from '../template' +import { LiquidCache } from '../cache' +import { FS, Loader, LookupType } from '../fs' +import type { Liquid } from '../liquid' -export default class Parser { +export class Parser { public parseFile: (file: string, sync?: boolean, type?: LookupType, currentFile?: string) => Generator private liquid: Liquid @@ -44,7 +38,9 @@ export default class Parser { public parseToken (token: TopLevelToken, remainTokens: TopLevelToken[]) { try { if (isTagToken(token)) { - return new Tag(token, remainTokens, this.liquid) + const TagClass = this.liquid.tags[token.name] + assert(TagClass, `tag "${token.name}" not found`) + return new TagClass(token, remainTokens, this.liquid) } if (isOutputToken(token)) { return new Output(token as OutputToken, this.liquid) diff --git a/src/parser/tokenizer.ts b/src/parser/tokenizer.ts index 324c911b7e..335f8197c0 100644 --- a/src/parser/tokenizer.ts +++ b/src/parser/tokenizer.ts @@ -1,31 +1,10 @@ -import { whiteSpaceCtrl } from './whitespace-ctrl' -import { NumberToken } from '../tokens/number-token' -import { IdentifierToken } from '../tokens/identifier-token' -import { literalValues } from '../util/literal' -import { LiteralToken } from '../tokens/literal-token' -import { OperatorToken } from '../tokens/operator-token' -import { PropertyAccessToken } from '../tokens/property-access-token' -import { assert } from '../util/assert' -import { TopLevelToken } from '../tokens/toplevel-token' -import { FilterArg } from './filter-arg' -import { FilterToken } from '../tokens/filter-token' -import { HashToken } from '../tokens/hash-token' -import { QuotedToken } from '../tokens/quoted-token' -import { ellipsis } from '../util/underscore' -import { HTMLToken } from '../tokens/html-token' -import { TagToken } from '../tokens/tag-token' -import { Token } from '../tokens/token' -import { RangeToken } from '../tokens/range-token' -import { ValueToken } from '../tokens/value-token' -import { OutputToken } from '../tokens/output-token' -import { TokenizationError } from '../util/error' +import { TagToken, HTMLToken, HashToken, QuotedToken, LiquidTagToken, OutputToken, ValueToken, Token, RangeToken, FilterToken, TopLevelToken, PropertyAccessToken, OperatorToken, LiteralToken, IdentifierToken, NumberToken } from '../tokens' +import { Trie, createTrie, ellipsis, literalValues, assert, TokenizationError, TYPES, QUOTE, BLANK, IDENTIFIER } from '../util' +import { Operators, Expression } from '../render' import { NormalizedFullOptions, defaultOptions } from '../liquid-options' -import { TYPES, QUOTE, BLANK, IDENTIFIER } from '../util/character' +import { FilterArg } from './filter-arg' import { matchOperator } from './match-operator' -import { Trie, createTrie } from '../util/operator-trie' -import { Expression } from '../render/expression' -import { Operators } from '../render/operator' -import { LiquidTagToken } from '../tokens/liquid-tag-token' +import { whiteSpaceCtrl } from './whitespace-ctrl' export class Tokenizer { p = 0 @@ -36,7 +15,7 @@ export class Tokenizer { constructor ( public input: string, operators: Operators = defaultOptions.operators, - public file: string = '' + public file?: string ) { this.N = input.length this.opTrie = createTrie(operators) diff --git a/src/parser/whitespace-ctrl.ts b/src/parser/whitespace-ctrl.ts index 49a621febd..00d7397b20 100644 --- a/src/parser/whitespace-ctrl.ts +++ b/src/parser/whitespace-ctrl.ts @@ -1,7 +1,6 @@ -import { Token } from '../tokens/token' -import { isTagToken, isHTMLToken, isDelimitedToken } from '../util/type-guards' +import { Token } from '../tokens' import { NormalizedFullOptions } from '../liquid-options' -import { TYPES, INLINE_BLANK, BLANK } from '../util/character' +import { isTagToken, isHTMLToken, isDelimitedToken, TYPES, INLINE_BLANK, BLANK } from '../util' export function whiteSpaceCtrl (tokens: Token[], options: NormalizedFullOptions) { let inRaw = false diff --git a/src/render/expression.ts b/src/render/expression.ts index aad2e01dd8..0bb388122a 100644 --- a/src/render/expression.ts +++ b/src/render/expression.ts @@ -1,19 +1,8 @@ -import { QuotedToken } from '../tokens/quoted-token' -import { PropertyAccessToken } from '../tokens/property-access-token' -import { NumberToken } from '../tokens/number-token' -import { assert } from '../util/assert' -import { literalValues } from '../util/literal' -import { LiteralToken } from '../tokens/literal-token' -import * as TypeGuards from '../util/type-guards' -import { Token } from '../tokens/token' -import { OperatorToken } from '../tokens/operator-token' -import { RangeToken } from '../tokens/range-token' -import { parseStringLiteral } from '../parser/parse-string-literal' -import { Context } from '../context/context' -import { range } from '../util/underscore' -import { Operators } from '../render/operator' -import { UndefinedVariableError } from '../util/error' -import { toValueSync } from '../util/async' +import { RangeToken, OperatorToken, Token, LiteralToken, NumberToken, PropertyAccessToken, QuotedToken } from '../tokens' +import { isQuotedToken, isWordToken, isNumberToken, isLiteralToken, isRangeToken, isPropertyAccessToken, UndefinedVariableError, range, isOperatorToken, literalValues, assert } from '../util' +import { parseStringLiteral } from '../parser' +import { Context } from '../context' +import { Operators } from '../render' export class Expression { private postfix: Token[] @@ -25,7 +14,7 @@ export class Expression { assert(ctx, 'unable to evaluate: context not defined') const operands: any[] = [] for (const token of this.postfix) { - if (TypeGuards.isOperatorToken(token)) { + if (isOperatorToken(token)) { const r = operands.pop() const l = operands.pop() const result = yield evalOperatorToken(ctx.opts.operators, token, l, r, ctx) @@ -38,20 +27,13 @@ export class Expression { } } -/** - * @deprecated use `_evalToken` instead - */ -export function * evalToken (token: Token | undefined, ctx: Context, lenient = false) { - return toValueSync(_evalToken(token, ctx, lenient)) -} - export function * _evalToken (token: Token | undefined, ctx: Context, lenient = false): IterableIterator { - if (TypeGuards.isPropertyAccessToken(token)) return yield evalPropertyAccessToken(token, ctx, lenient) - if (TypeGuards.isRangeToken(token)) return yield evalRangeToken(token, ctx) - if (TypeGuards.isLiteralToken(token)) return evalLiteralToken(token) - if (TypeGuards.isNumberToken(token)) return evalNumberToken(token) - if (TypeGuards.isWordToken(token)) return token.getText() - if (TypeGuards.isQuotedToken(token)) return evalQuotedToken(token) + if (isPropertyAccessToken(token)) return yield evalPropertyAccessToken(token, ctx, lenient) + if (isRangeToken(token)) return yield evalRangeToken(token, ctx) + if (isLiteralToken(token)) return evalLiteralToken(token) + if (isNumberToken(token)) return evalNumberToken(token) + if (isWordToken(token)) return token.getText() + if (isQuotedToken(token)) return evalQuotedToken(token) } function * evalPropertyAccessToken (token: PropertyAccessToken, ctx: Context, lenient: boolean): IterableIterator { @@ -94,7 +76,7 @@ function * evalRangeToken (token: RangeToken, ctx: Context) { function * toPostfix (tokens: IterableIterator): IterableIterator { const ops: OperatorToken[] = [] for (const token of tokens) { - if (TypeGuards.isOperatorToken(token)) { + if (isOperatorToken(token)) { while (ops.length && ops[ops.length - 1].getPrecedence() > token.getPrecedence()) { yield ops.pop()! } diff --git a/src/render/index.ts b/src/render/index.ts new file mode 100644 index 0000000000..a3a48c99c4 --- /dev/null +++ b/src/render/index.ts @@ -0,0 +1,4 @@ +export * from './render' +export * from './expression' +export * from './operator' +export * from './boolean' diff --git a/src/render/operator.ts b/src/render/operator.ts index 4f0f0dde98..2391a1ee47 100644 --- a/src/render/operator.ts +++ b/src/render/operator.ts @@ -1,11 +1,10 @@ import { isComparable } from '../drop/comparable' -import { Context } from '../context/context' -import { isFunction, toValue } from '../util/underscore' +import { Context } from '../context' +import { isFunction, toValue } from '../util' import { isTruthy } from '../render/boolean' -export interface Operators { - [key: string]: (lhs: any, rhs: any, ctx: Context) => boolean; -} +export type OperatorHandler = (lhs: any, rhs: any, ctx: Context) => boolean; +export type Operators = Record export const defaultOperators: Operators = { '==': (l: any, r: any) => { diff --git a/src/render/render.ts b/src/render/render.ts index 10aaac928e..bb64bee7fd 100644 --- a/src/render/render.ts +++ b/src/render/render.ts @@ -1,11 +1,7 @@ -import { RenderError } from '../util/error' -import { Context } from '../context/context' -import { Template } from '../template/template' -import { Emitter } from '../emitters/emitter' -import { SimpleEmitter } from '../emitters/simple-emitter' -import { StreamedEmitter } from '../emitters/streamed-emitter' -import { toPromise } from '../util/async' -import { KeepingTypeEmitter } from '../emitters/keeping-type-emitter' +import { toPromise, RenderError } from '../util' +import { Context } from '../context' +import { Template } from '../template' +import { Emitter, KeepingTypeEmitter, StreamedEmitter, SimpleEmitter } from '../emitters' export class Render { public renderTemplatesToNodeStream (templates: Template[], ctx: Context): NodeJS.ReadableStream { diff --git a/src/tags/assign.ts b/src/tags/assign.ts index 38ed5436fc..c3416e538d 100644 --- a/src/tags/assign.ts +++ b/src/tags/assign.ts @@ -1,15 +1,18 @@ -import { Value, Tokenizer, assert, TagImplOptions, TagToken, Context } from '../types' +import { Value, assert, Tokenizer, Liquid, TopLevelToken, TagToken, Context, Tag } from '..' +export default class extends Tag { + private key: string + private value: Value -export default { - parse: function (token: TagToken) { - const tokenizer = new Tokenizer(token.args, this.liquid.options.operators) + constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(token, remainTokens, liquid) + const tokenizer = new Tokenizer(token.args, liquid.options.operators) this.key = tokenizer.readIdentifier().content tokenizer.skipBlank() assert(tokenizer.peek() === '=', () => `illegal token ${token.getText()}`) tokenizer.advance() this.value = new Value(tokenizer.remaining(), this.liquid) - }, - render: function * (ctx: Context): Generator { + } + * render (ctx: Context): Generator { ctx.bottom()[this.key] = yield this.value.value(ctx, this.liquid.options.lenientIf) } -} as TagImplOptions +} diff --git a/src/tags/block.ts b/src/tags/block.ts index ec3ba67040..b59c600bdf 100644 --- a/src/tags/block.ts +++ b/src/tags/block.ts @@ -1,29 +1,34 @@ -import BlockMode from '../context/block-mode' -import { BlockDrop } from '../drop/block-drop' -import { TagToken, TopLevelToken, Template, Context, TagImpl, Emitter } from '../types' +import { BlockMode } from '../context' +import { isTagToken } from '../util' +import { BlockDrop } from '../drop' +import { Liquid, TagToken, TopLevelToken, Template, Context, Emitter, Tag } from '..' -export default { - parse (this: TagImpl, token: TagToken, remainTokens: TopLevelToken[]) { +export default class extends Tag { + private block: string + private tpls: Template[] = [] + constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(token, remainTokens, liquid) const match = /\w+/.exec(token.args) this.block = match ? match[0] : '' - this.tpls = [] as Template[] - this.liquid.parser.parseStream(remainTokens) - .on('tag:endblock', function () { this.stop() }) - .on('template', (tpl: Template) => this.tpls.push(tpl)) - .on('end', () => { throw new Error(`tag ${token.getText()} not closed`) }) - .start() - }, + while (remainTokens.length) { + const token = remainTokens.shift()! + if (isTagToken(token) && token.name === 'endblock') return + const template = liquid.parser.parseToken(token, remainTokens) + this.tpls.push(template) + } + throw new Error(`tag ${token.getText()} not closed`) + } - * render (this: TagImpl, ctx: Context, emitter: Emitter) { + * render (ctx: Context, emitter: Emitter) { const blockRender = this.getBlockRender(ctx) if (ctx.getRegister('blockMode') === BlockMode.STORE) { ctx.getRegister('blocks')[this.block] = blockRender } else { yield blockRender(new BlockDrop(), emitter) } - }, + } - getBlockRender (this: TagImpl, ctx: Context) { + private getBlockRender (ctx: Context) { const { liquid, tpls } = this const renderChild = ctx.getRegister('blocks')[this.block] const renderCurrent = function * (superBlock: BlockDrop, emitter: Emitter) { diff --git a/src/tags/break.ts b/src/tags/break.ts index fc73ce7ff8..0ed79388c4 100644 --- a/src/tags/break.ts +++ b/src/tags/break.ts @@ -1,7 +1,7 @@ -import { Emitter, Context } from '../types' +import { Context, Emitter, Tag } from '..' -export default { - render: function (ctx: Context, emitter: Emitter) { +export default class extends Tag { + render (ctx: Context, emitter: Emitter) { emitter['break'] = true } } diff --git a/src/tags/capture.ts b/src/tags/capture.ts index 82339da15e..95a7c40c52 100644 --- a/src/tags/capture.ts +++ b/src/tags/capture.ts @@ -1,28 +1,29 @@ -import { Tokenizer, assert, Template, Context, TagImplOptions, TagToken, TopLevelToken } from '../types' -import { evalQuotedToken } from '../render/expression' +import { Liquid, Tag, Tokenizer, assert, Template, Context, TagToken, TopLevelToken } from '..' +import { evalQuotedToken } from '../render' +import { isTagToken } from '../util' -export default { - parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) { +export default class extends Tag { + private variable: string + private templates: Template[] = [] + constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(tagToken, remainTokens, liquid) const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operators) - this.variable = readVariableName(tokenizer) + this.variable = readVariableName(tokenizer)! assert(this.variable, () => `${tagToken.args} not valid identifier`) - this.templates = [] - - const stream = this.liquid.parser.parseStream(remainTokens) - stream.on('tag:endcapture', () => stream.stop()) - .on('template', (tpl: Template) => this.templates.push(tpl)) - .on('end', () => { - throw new Error(`tag ${tagToken.getText()} not closed`) - }) - stream.start() - }, - render: function * (ctx: Context): Generator { + while (remainTokens.length) { + const token = remainTokens.shift()! + if (isTagToken(token) && token.name === 'endcapture') return + this.templates.push(liquid.parser.parseToken(token, remainTokens)) + } + throw new Error(`tag ${tagToken.getText()} not closed`) + } + * render (ctx: Context): Generator { const r = this.liquid.renderer const html = yield r.renderTemplates(this.templates, ctx) ctx.bottom()[this.variable] = html } -} as TagImplOptions +} function readVariableName (tokenizer: Tokenizer) { const word = tokenizer.readIdentifier().content diff --git a/src/tags/case.ts b/src/tags/case.ts index 0dec1e3e9c..703e94df82 100644 --- a/src/tags/case.ts +++ b/src/tags/case.ts @@ -1,10 +1,12 @@ -import { toValue, _evalToken, Value, Emitter, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../types' -import { Tokenizer } from '../parser/tokenizer' +import { ValueToken, Liquid, Tokenizer, toValue, _evalToken, Value, Emitter, TagToken, TopLevelToken, Context, Template, Tag, ParseStream } from '..' -export default { - parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) { +export default class extends Tag { + private cond: Value + private cases: { val?: ValueToken, templates: Template[] }[] = [] + private elseTemplates: Template[] = [] + constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(tagToken, remainTokens, liquid) this.cond = new Value(tagToken.args, this.liquid) - this.cases = [] this.elseTemplates = [] let p: Template[] = [] @@ -31,9 +33,9 @@ export default { }) stream.start() - }, + } - render: function * (ctx: Context, emitter: Emitter) { + * render (ctx: Context, emitter: Emitter): Generator { const r = this.liquid.renderer const cond = toValue(yield this.cond.value(ctx, ctx.opts.lenientIf)) for (const branch of this.cases) { @@ -45,4 +47,4 @@ export default { } yield r.renderTemplates(this.elseTemplates, ctx, emitter) } -} as TagImplOptions +} diff --git a/src/tags/comment.ts b/src/tags/comment.ts index 0c07446739..d43a209f3c 100644 --- a/src/tags/comment.ts +++ b/src/tags/comment.ts @@ -1,17 +1,14 @@ -import { TagToken } from '../tokens/tag-token' -import { TopLevelToken } from '../tokens/toplevel-token' -import { TagImplOptions } from '../template/tag/tag-impl-options' +import { Liquid, TopLevelToken, TagToken, Tag } from '..' +import { isTagToken } from '../util' -export default { - parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) { - const stream = this.liquid.parser.parseStream(remainTokens) - stream - .on('token', (token: TagToken) => { - if (token.name === 'endcomment') stream.stop() - }) - .on('end', () => { - throw new Error(`tag ${tagToken.getText()} not closed`) - }) - stream.start() +export default class extends Tag { + constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(tagToken, remainTokens, liquid) + while (remainTokens.length) { + const token = remainTokens.shift()! + if (isTagToken(token) && token.name === 'endcomment') return + } + throw new Error(`tag ${tagToken.getText()} not closed`) } -} as TagImplOptions + render () {} +} diff --git a/src/tags/continue.ts b/src/tags/continue.ts index ce92d96701..914b8e3a00 100644 --- a/src/tags/continue.ts +++ b/src/tags/continue.ts @@ -1,7 +1,7 @@ -import { Emitter, Context } from '../types' +import { Tag, Emitter, Context } from '..' -export default { - render: function (ctx: Context, emitter: Emitter) { +export default class extends Tag { + render (ctx: Context, emitter: Emitter) { emitter['continue'] = true } } diff --git a/src/tags/cycle.ts b/src/tags/cycle.ts index 87a322941e..06d7074595 100644 --- a/src/tags/cycle.ts +++ b/src/tags/cycle.ts @@ -1,15 +1,14 @@ -import { assert } from '../util/assert' -import { _evalToken, Emitter, TagToken, Context, TagImplOptions } from '../types' -import { Tokenizer } from '../parser/tokenizer' +import { Tokenizer, assert, TopLevelToken, Liquid, ValueToken, _evalToken, Emitter, TagToken, Context, Tag } from '..' -export default { - parse: function (tagToken: TagToken) { +export default class extends Tag { + private candidates: ValueToken[] = [] + private group?: ValueToken + constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(tagToken, remainTokens, liquid) const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operators) const group = tokenizer.readValue() tokenizer.skipBlank() - this.candidates = [] - if (group) { if (tokenizer.peek() === ':') { this.group = group @@ -23,10 +22,10 @@ export default { tokenizer.readTo(',') } assert(this.candidates.length, () => `empty candidates: ${tagToken.getText()}`) - }, + } - render: function * (ctx: Context, emitter: Emitter) { - const group = yield _evalToken(this.group, ctx) + * render (ctx: Context, emitter: Emitter): Generator { + const group = (yield _evalToken(this.group, ctx)) as ValueToken const fingerprint = `cycle:${group}:` + this.candidates.join(',') const groups = ctx.getRegister('cycle') let idx = groups[fingerprint] @@ -38,7 +37,6 @@ export default { const candidate = this.candidates[idx] idx = (idx + 1) % this.candidates.length groups[fingerprint] = idx - const html = yield _evalToken(candidate, ctx) - emitter.write(html) + return yield _evalToken(candidate, ctx) } -} as TagImplOptions +} diff --git a/src/tags/decrement.ts b/src/tags/decrement.ts index 932196335c..b889273cba 100644 --- a/src/tags/decrement.ts +++ b/src/tags/decrement.ts @@ -1,16 +1,18 @@ -import { Tokenizer, Emitter, TagToken, Context, TagImplOptions } from '../types' -import { isNumber, stringify } from '../util/underscore' +import { Tag, Liquid, TopLevelToken, Tokenizer, Emitter, TagToken, Context } from '..' +import { isNumber, stringify } from '../util' -export default { - parse: function (token: TagToken) { +export default class extends Tag { + private variable: string + constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(token, remainTokens, liquid) const tokenizer = new Tokenizer(token.args, this.liquid.options.operators) this.variable = tokenizer.readIdentifier().content - }, - render: function (context: Context, emitter: Emitter) { + } + render (context: Context, emitter: Emitter) { const scope = context.environments if (!isNumber(scope[this.variable])) { scope[this.variable] = 0 } emitter.write(stringify(--scope[this.variable])) } -} as TagImplOptions +} diff --git a/src/tags/echo.ts b/src/tags/echo.ts index 4ed8805c2c..844014ba03 100644 --- a/src/tags/echo.ts +++ b/src/tags/echo.ts @@ -1,13 +1,13 @@ -import { Value } from '../template/value' -import { Emitter } from '../emitters/emitter' -import { TagImplOptions, TagToken, Context } from '../types' +import { Liquid, TopLevelToken, Emitter, Value, TagToken, Context, Tag } from '..' -export default { - parse: function (token: TagToken) { +export default class extends Tag { + private value: Value + constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(token, remainTokens, liquid) this.value = new Value(token.args, this.liquid) - }, - render: function * (ctx: Context, emitter: Emitter): Generator { + } + * render (ctx: Context, emitter: Emitter): Generator { const val = yield this.value.value(ctx, false) emitter.write(val) } -} as TagImplOptions +} diff --git a/src/tags/for.ts b/src/tags/for.ts index 2d74268429..b10a6ddb96 100644 --- a/src/tags/for.ts +++ b/src/tags/for.ts @@ -1,24 +1,27 @@ -import { assert, Tokenizer, _evalToken, Emitter, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../types' +import { Hash, ValueToken, Liquid, Tag, Tokenizer, _evalToken, Emitter, TagToken, TopLevelToken, Context, Template, ParseStream } from '..' import { toEnumerable } from '../util/collection' import { ForloopDrop } from '../drop/forloop-drop' -import { Hash, HashValue } from '../template/tag/hash' const MODIFIERS = ['offset', 'limit', 'reversed'] type valueof = T[keyof T] -export default { - type: 'block', - parse: function (token: TagToken, remainTokens: TopLevelToken[]) { - const tokenizer = new Tokenizer(token.args, this.liquid.options.operators) +export default class extends Tag { + private variable: string + private collection: ValueToken + private hash: Hash + private templates: Template[] + private elseTemplates: Template[] + constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(token, remainTokens, liquid) + const tokenizer = new Tokenizer(token.args, this.liquid.options.operators) const variable = tokenizer.readIdentifier() const inStr = tokenizer.readIdentifier() const collection = tokenizer.readValue() - assert( - variable.size() && inStr.content === 'in' && collection, - () => `illegal tag: ${token.getText()}` - ) + if (!variable.size() || inStr.content !== 'in' || !collection) { + throw new Error(`illegal tag: ${token.getText()}`) + } this.variable = variable.content this.collection = collection @@ -37,8 +40,8 @@ export default { }) stream.start() - }, - render: function * (ctx: Context, emitter: Emitter): Generator { + } + * render (ctx: Context, emitter: Emitter): Generator { const r = this.liquid.renderer let collection = toEnumerable(yield _evalToken(this.collection, ctx)) @@ -77,7 +80,7 @@ export default { } ctx.pop() } -} as TagImplOptions +} function reversed (arr: Array) { return [...arr].reverse() diff --git a/src/tags/if.ts b/src/tags/if.ts index 4aba47b510..c619c9142d 100644 --- a/src/tags/if.ts +++ b/src/tags/if.ts @@ -1,12 +1,13 @@ -import { Value, Emitter, isTruthy, TagToken, TopLevelToken, Context, Template, TagImplOptions } from '../types' +import { Liquid, Tag, Value, Emitter, isTruthy, TagToken, TopLevelToken, Context, Template } from '..' -export default { - parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) { - this.branches = [] - this.elseTemplates = [] +export default class extends Tag { + private branches: { predicate: Value, templates: Template[] }[] = [] + private elseTemplates: Template[] = [] + constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(tagToken, remainTokens, liquid) let p - this.liquid.parser.parseStream(remainTokens) + liquid.parser.parseStream(remainTokens) .on('start', () => this.branches.push({ predicate: new Value(tagToken.args, this.liquid), templates: (p = []) @@ -20,9 +21,9 @@ export default { .on('template', (tpl: Template) => p.push(tpl)) .on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) }) .start() - }, + } - render: function * (ctx: Context, emitter: Emitter): Generator { + * render (ctx: Context, emitter: Emitter): Generator { const r = this.liquid.renderer for (const { predicate, templates } of this.branches) { @@ -34,4 +35,4 @@ export default { } yield r.renderTemplates(this.elseTemplates, ctx, emitter) } -} as TagImplOptions +} diff --git a/src/tags/include.ts b/src/tags/include.ts index 9cffe14a9d..199c35f34e 100644 --- a/src/tags/include.ts +++ b/src/tags/include.ts @@ -1,14 +1,15 @@ -import { assert, Tokenizer, _evalToken, Hash, Emitter, TagToken, Context, TagImplOptions } from '../types' -import BlockMode from '../context/block-mode' +import { Template, ValueToken, TopLevelToken, Liquid, Tag, assert, Tokenizer, _evalToken, Hash, Emitter, TagToken, Context } from '..' +import { BlockMode, Scope } from '../context' import { parseFilePath, renderFilePath } from './render' -export default { - parseFilePath, - renderFilePath, - parse: function (token: TagToken) { +export default class extends Tag { + private withVar?: ValueToken + private hash: Hash + constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(token, remainTokens, liquid) const args = token.args const tokenizer = new Tokenizer(args, this.liquid.options.operators) - this['file'] = this.parseFilePath(tokenizer, this.liquid) + this['file'] = parseFilePath(tokenizer, this.liquid) this['currentFile'] = token.file const begin = tokenizer.p @@ -21,22 +22,22 @@ export default { } else tokenizer.p = begin this.hash = new Hash(tokenizer.remaining(), this.liquid.options.jekyllInclude) - }, - render: function * (ctx: Context, emitter: Emitter) { + } + * render (ctx: Context, emitter: Emitter): Generator { const { liquid, hash, withVar } = this const { renderer } = liquid - const filepath = yield this.renderFilePath(this['file'], ctx, liquid) + const filepath = (yield renderFilePath(this['file'], ctx, liquid)) as string assert(filepath, () => `illegal filename "${filepath}"`) const saved = ctx.saveRegister('blocks', 'blockMode') ctx.setRegister('blocks', {}) ctx.setRegister('blockMode', BlockMode.OUTPUT) - const scope = yield hash.render(ctx) + const scope = (yield hash.render(ctx)) as Scope if (withVar) scope[filepath] = yield _evalToken(withVar, ctx) - const templates = yield liquid._parsePartialFile(filepath, ctx.sync, this['currentFile']) + const templates = (yield liquid._parsePartialFile(filepath, ctx.sync, this['currentFile'])) as Template[] ctx.push(ctx.opts.jekyllInclude ? { include: scope } : scope) yield renderer.renderTemplates(templates, ctx, emitter) ctx.pop() ctx.restoreRegister(saved) } -} as TagImplOptions +} diff --git a/src/tags/increment.ts b/src/tags/increment.ts index 6b63999939..1748db2f7c 100644 --- a/src/tags/increment.ts +++ b/src/tags/increment.ts @@ -1,12 +1,14 @@ -import { isNumber, stringify } from '../util/underscore' -import { Tokenizer, Emitter, TagToken, Context, TagImplOptions } from '../types' +import { isNumber, stringify } from '../util' +import { Tag, Liquid, TopLevelToken, Tokenizer, Emitter, TagToken, Context } from '..' -export default { - parse: function (token: TagToken) { +export default class extends Tag { + private variable: string + constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(token, remainTokens, liquid) const tokenizer = new Tokenizer(token.args, this.liquid.options.operators) this.variable = tokenizer.readIdentifier().content - }, - render: function (context: Context, emitter: Emitter) { + } + render (context: Context, emitter: Emitter) { const scope = context.environments if (!isNumber(scope[this.variable])) { scope[this.variable] = 0 @@ -15,4 +17,4 @@ export default { scope[this.variable]++ emitter.write(stringify(val)) } -} as TagImplOptions +} diff --git a/src/tags/index.ts b/src/tags/index.ts index 19e6e3ac4f..b9763a67ce 100644 --- a/src/tags/index.ts +++ b/src/tags/index.ts @@ -19,8 +19,8 @@ import Continue from './continue' import echo from './echo' import liquid from './liquid' import inlineComment from './inline-comment' -import { TagImplOptions } from '../template/tag/tag-impl-options' +import type { TagClass } from '../template/tag' -export const tags: { [key: string]: TagImplOptions } = { +export const tags: Record = { assign, 'for': For, capture, 'case': Case, comment, include, render, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue, echo, liquid, '#': inlineComment } diff --git a/src/tags/inline-comment.ts b/src/tags/inline-comment.ts index 2a1d740d20..0bf4e71097 100644 --- a/src/tags/inline-comment.ts +++ b/src/tags/inline-comment.ts @@ -1,11 +1,11 @@ -import { TagToken } from '../tokens/tag-token' -import { TopLevelToken } from '../tokens/toplevel-token' -import { TagImplOptions } from '../template/tag/tag-impl-options' +import { TagToken, Liquid, TopLevelToken, Tag } from '..' -export default { - parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) { +export default class extends Tag { + constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(tagToken, remainTokens, liquid) if (tagToken.args.search(/\n\s*[^#\s]/g) !== -1) { throw new Error('every line of an inline comment must start with a \'#\' character') } } -} as TagImplOptions + render () { } +} diff --git a/src/tags/layout.ts b/src/tags/layout.ts index e2e3230807..b1700feb02 100644 --- a/src/tags/layout.ts +++ b/src/tags/layout.ts @@ -1,29 +1,31 @@ -import { assert, Tokenizer, Emitter, Hash, TagToken, TopLevelToken, Context, TagImplOptions } from '../types' -import BlockMode from '../context/block-mode' -import { parseFilePath, renderFilePath } from './render' -import { BlankDrop } from '../drop/blank-drop' +import { Scope, Template, Liquid, Tag, assert, Tokenizer, Emitter, Hash, TagToken, TopLevelToken, Context } from '..' +import { BlockMode } from '../context' +import { parseFilePath, renderFilePath, ParsedFileName } from './render' +import { BlankDrop } from '../drop' -export default { - parseFilePath, - renderFilePath, - parse: function (token: TagToken, remainTokens: TopLevelToken[]) { +export default class extends Tag { + private hash: Hash + private tpls: Template[] + private file?: ParsedFileName + constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(token, remainTokens, liquid) const tokenizer = new Tokenizer(token.args, this.liquid.options.operators) - this['file'] = this.parseFilePath(tokenizer, this.liquid) + this.file = parseFilePath(tokenizer, this.liquid) this['currentFile'] = token.file this.hash = new Hash(tokenizer.remaining()) this.tpls = this.liquid.parser.parseTokens(remainTokens) - }, - render: function * (ctx: Context, emitter: Emitter) { + } + * render (ctx: Context, emitter: Emitter): Generator { const { liquid, hash, file } = this const { renderer } = liquid - if (file === null) { + if (file === undefined) { ctx.setRegister('blockMode', BlockMode.OUTPUT) yield renderer.renderTemplates(this.tpls, ctx, emitter) return } - const filepath = yield this.renderFilePath(this['file'], ctx, liquid) + const filepath = (yield renderFilePath(this.file, ctx, liquid)) as string assert(filepath, () => `illegal filename "${filepath}"`) - const templates = yield liquid._parseLayoutFile(filepath, ctx.sync, this['currentFile']) + const templates = (yield liquid._parseLayoutFile(filepath, ctx.sync, this['currentFile'])) as Template[] // render remaining contents and store rendered results ctx.setRegister('blockMode', BlockMode.STORE) @@ -35,8 +37,8 @@ export default { ctx.setRegister('blockMode', BlockMode.OUTPUT) // render the layout file use stored blocks - ctx.push(yield hash.render(ctx)) + ctx.push((yield hash.render(ctx)) as Scope) yield renderer.renderTemplates(templates, ctx, emitter) ctx.pop() } -} as TagImplOptions +} diff --git a/src/tags/liquid.ts b/src/tags/liquid.ts index cd33733840..d733529b85 100644 --- a/src/tags/liquid.ts +++ b/src/tags/liquid.ts @@ -1,14 +1,14 @@ -import { Emitter } from '../emitters/emitter' -import { TagImplOptions, TagToken, Context } from '../types' -import { Tokenizer } from '../parser/tokenizer' +import { Template, Tokenizer, Emitter, Liquid, TopLevelToken, TagToken, Context, Tag } from '..' -export default { - parse: function (token: TagToken) { +export default class extends Tag { + private tpls: Template[] + constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(token, remainTokens, liquid) const tokenizer = new Tokenizer(token.args, this.liquid.options.operators) const tokens = tokenizer.readLiquidTagTokens(this.liquid.options) this.tpls = this.liquid.parser.parseTokens(tokens) - }, - render: function * (ctx: Context, emitter: Emitter): Generator { + } + * render (ctx: Context, emitter: Emitter): Generator { yield this.liquid.renderer.renderTemplates(this.tpls, ctx, emitter) } -} as TagImplOptions +} diff --git a/src/tags/raw.ts b/src/tags/raw.ts index 41cbeda52b..00e8e291f1 100644 --- a/src/tags/raw.ts +++ b/src/tags/raw.ts @@ -1,21 +1,18 @@ -import { TagToken, TopLevelToken, TagImplOptions } from '../types' +import { Liquid, TagToken, TopLevelToken, Tag } from '..' +import { isTagToken } from '../util' -export default { - parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) { - this.tokens = [] - - const stream = this.liquid.parser.parseStream(remainTokens) - stream - .on('token', (token: TagToken) => { - if (token.name === 'endraw') stream.stop() - else this.tokens.push(token) - }) - .on('end', () => { - throw new Error(`tag ${tagToken.getText()} not closed`) - }) - stream.start() - }, - render: function () { +export default class extends Tag { + private tokens: TopLevelToken[] = [] + constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(tagToken, remainTokens, liquid) + while (remainTokens.length) { + const token = remainTokens.shift()! + if (isTagToken(token) && token.name === 'endraw') return + this.tokens.push(token) + } + throw new Error(`tag ${tagToken.getText()} not closed`) + } + render () { return this.tokens.map((token: TopLevelToken) => token.getText()).join('') } -} as TagImplOptions +} diff --git a/src/tags/render.ts b/src/tags/render.ts index 62a6c83a03..12b202b777 100644 --- a/src/tags/render.ts +++ b/src/tags/render.ts @@ -1,18 +1,20 @@ import { __assign } from 'tslib' -import { assert } from '../util/assert' -import { ForloopDrop } from '../drop/forloop-drop' -import { toEnumerable } from '../util/collection' -import { Liquid } from '../liquid' -import { Token, Template, evalQuotedToken, TypeGuards, Tokenizer, _evalToken, Hash, Emitter, TagToken, Context, TagImplOptions } from '../types' +import { ForloopDrop } from '../drop' +import { toEnumerable } from '../util' +import { TopLevelToken, assert, Liquid, Token, Template, evalQuotedToken, TypeGuards, Tokenizer, _evalToken, Hash, Emitter, TagToken, Context, Tag } from '..' -export default { - parseFilePath, - renderFilePath, - parse: function (token: TagToken) { +export type ParsedFileName = Template[] | Token | string | undefined + +export default class extends Tag { + private file: ParsedFileName + private currentFile?: string + private hash: Hash + constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(token, remainTokens, liquid) const args = token.args const tokenizer = new Tokenizer(args, this.liquid.options.operators) - this['file'] = this.parseFilePath(tokenizer, this.liquid) - this['currentFile'] = token.file + this.file = parseFilePath(tokenizer, this.liquid) + this.currentFile = token.file while (!tokenizer.end()) { tokenizer.skipBlank() const begin = tokenizer.p @@ -33,8 +35,7 @@ export default { this[keyword.content] = { value, alias: alias && alias.content } tokenizer.skipBlank() if (tokenizer.peek() === ',') tokenizer.advance() - // matched! - continue + continue // matched! } } } @@ -45,10 +46,10 @@ export default { break } this.hash = new Hash(tokenizer.remaining()) - }, - render: function * (ctx: Context, emitter: Emitter) { + } + * render (ctx: Context, emitter: Emitter): Generator { const { liquid, hash } = this - const filepath = yield this.renderFilePath(this['file'], ctx, liquid) + const filepath = (yield renderFilePath(this['file'], ctx, liquid)) as string assert(filepath, () => `illegal filename "${filepath}"`) const childCtx = new Context({}, ctx.opts, { sync: ctx.sync, globals: ctx.globals, strictVariables: ctx.strictVariables }) @@ -61,23 +62,20 @@ export default { if (this['for']) { const { value, alias } = this['for'] - let collection = yield _evalToken(value, ctx) - collection = toEnumerable(collection) + const collection = toEnumerable(yield _evalToken(value, ctx)) scope['forloop'] = new ForloopDrop(collection.length, value.getText(), alias) for (const item of collection) { scope[alias] = item - const templates = yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile']) + const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile'])) as Template[] yield liquid.renderer.renderTemplates(templates, childCtx, emitter) scope['forloop'].next() } } else { - const templates = yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile']) + const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile'])) as Template[] yield liquid.renderer.renderTemplates(templates, childCtx, emitter) } } -} as TagImplOptions - -type ParsedFileName = Template[] | Token | string | undefined +} /** * @return null for "none", @@ -85,11 +83,11 @@ type ParsedFileName = Template[] | Token | string | undefined * @return Token for expression (not quoted) * @throws TypeError if cannot read next token */ -export function parseFilePath (tokenizer: Tokenizer, liquid: Liquid): ParsedFileName | null { +export function parseFilePath (tokenizer: Tokenizer, liquid: Liquid): ParsedFileName { if (liquid.options.dynamicPartials) { const file = tokenizer.readValue() if (file === undefined) throw new TypeError(`illegal argument "${tokenizer.input}"`) - if (file.getText() === 'none') return null + if (file.getText() === 'none') return if (TypeGuards.isQuotedToken(file)) { // for filenames like "files/{{file}}", eval as liquid template const templates = liquid.parse(evalQuotedToken(file)) @@ -99,7 +97,7 @@ export function parseFilePath (tokenizer: Tokenizer, liquid: Liquid): ParsedFile } const tokens = [...tokenizer.readFileNameTemplate(liquid.options)] const templates = optimize(liquid.parser.parseTokens(tokens)) - return templates === 'none' ? null : templates + return templates === 'none' ? undefined : templates } function optimize (templates: Template[]): string | Template[] { diff --git a/src/tags/tablerow.ts b/src/tags/tablerow.ts index f4a25a1794..82b63270e5 100644 --- a/src/tags/tablerow.ts +++ b/src/tags/tablerow.ts @@ -1,20 +1,28 @@ import { toEnumerable } from '../util/collection' -import { assert, _evalToken, Emitter, Hash, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../types' +import { ValueToken, Liquid, Tag, _evalToken, Emitter, Hash, TagToken, TopLevelToken, Context, Template, ParseStream } from '..' import { TablerowloopDrop } from '../drop/tablerowloop-drop' import { Tokenizer } from '../parser/tokenizer' -export default { - parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) { +export default class extends Tag { + private variable: string + private hash: Hash + private templates: Template[] + private collection: ValueToken + constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(tagToken, remainTokens, liquid) const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operators) const variable = tokenizer.readIdentifier() tokenizer.skipBlank() - const tmp = tokenizer.readIdentifier() - assert(tmp && tmp.content === 'in', () => `illegal tag: ${tagToken.getText()}`) + const predicate = tokenizer.readIdentifier() + const collectionToken = tokenizer.readValue() + if (predicate.content !== 'in' || !collectionToken) { + throw new Error(`illegal tag: ${tagToken.getText()}`) + } this.variable = variable.content - this.collection = tokenizer.readValue() + this.collection = collectionToken this.hash = new Hash(tokenizer.remaining()) this.templates = [] @@ -28,11 +36,11 @@ export default { }) stream.start() - }, + } - render: function * (ctx: Context, emitter: Emitter) { + * render (ctx: Context, emitter: Emitter): Generator { let collection = toEnumerable(yield _evalToken(this.collection, ctx)) - const hash = yield this.hash.render(ctx) + const hash = (yield this.hash.render(ctx)) as Record const offset = hash.offset || 0 const limit = (hash.limit === undefined) ? collection.length : hash.limit @@ -57,4 +65,4 @@ export default { if (collection.length) emitter.write('') ctx.pop() } -} as TagImplOptions +} diff --git a/src/tags/unless.ts b/src/tags/unless.ts index 8c0465921e..15f03cc068 100644 --- a/src/tags/unless.ts +++ b/src/tags/unless.ts @@ -1,9 +1,10 @@ -import { Value, TopLevelToken, Template, Emitter, isTruthy, isFalsy, Context, TagImplOptions, TagToken } from '../types' +import { Liquid, Tag, Value, TopLevelToken, Template, Emitter, isTruthy, isFalsy, Context, TagToken } from '..' -export default { - parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) { - this.branches = [] - this.elseTemplates = [] +export default class extends Tag { + private branches: { predicate: Value, test: (val: any, ctx: Context) => boolean, templates: Template[] }[] = [] + private elseTemplates: Template[] = [] + constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(tagToken, remainTokens, liquid) let p this.liquid.parser.parseStream(remainTokens) .on('start', () => this.branches.push({ @@ -21,9 +22,9 @@ export default { .on('template', (tpl: Template) => p.push(tpl)) .on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) }) .start() - }, + } - render: function * (ctx: Context, emitter: Emitter) { + * render (ctx: Context, emitter: Emitter): Generator { const r = this.liquid.renderer for (const { predicate, test, templates } of this.branches) { @@ -36,4 +37,4 @@ export default { yield r.renderTemplates(this.elseTemplates, ctx, emitter) } -} as TagImplOptions +} diff --git a/src/template/filter-impl-options.ts b/src/template/filter-impl-options.ts new file mode 100644 index 0000000000..94c8e37c2c --- /dev/null +++ b/src/template/filter-impl-options.ts @@ -0,0 +1,11 @@ +import type { Context } from '../context' +import type { Liquid } from '../liquid' + +export interface FilterImpl { + context: Context; + liquid: Liquid; +} + +export interface FilterImplOptions { + (this: FilterImpl, value: any, ...args: any[]): any; +} diff --git a/src/template/filter/filter.ts b/src/template/filter.ts similarity index 65% rename from src/template/filter/filter.ts rename to src/template/filter.ts index 6b3f99539f..b6797b1771 100644 --- a/src/template/filter/filter.ts +++ b/src/template/filter.ts @@ -1,9 +1,9 @@ -import { _evalToken } from '../../render/expression' -import { Context } from '../../context/context' -import { identify } from '../../util/underscore' +import { _evalToken } from '../render' +import { Context } from '../context' +import { identify } from '../util/underscore' import { FilterImplOptions } from './filter-impl-options' -import { FilterArg, isKeyValuePair } from '../../parser/filter-arg' -import { Liquid } from '../../liquid' +import { FilterArg, isKeyValuePair } from '../parser/filter-arg' +import { Liquid } from '../liquid' export class Filter { public name: string @@ -11,7 +11,7 @@ export class Filter { private impl: FilterImplOptions private liquid: Liquid - public constructor (name: string, impl: FilterImplOptions, args: FilterArg[], liquid: Liquid) { + public constructor (name: string, impl: FilterImplOptions | undefined, args: FilterArg[], liquid: Liquid) { this.name = name this.impl = impl || identify this.args = args diff --git a/src/template/filter/filter-impl-options.ts b/src/template/filter/filter-impl-options.ts deleted file mode 100644 index d6f800d677..0000000000 --- a/src/template/filter/filter-impl-options.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { FilterImpl } from './filter-impl' - -export interface FilterImplOptions { - (this: FilterImpl, value: any, ...args: any[]): any; -} diff --git a/src/template/filter/filter-impl.ts b/src/template/filter/filter-impl.ts deleted file mode 100644 index 6602b86f44..0000000000 --- a/src/template/filter/filter-impl.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Context } from '../../context/context' -import { Liquid } from '../../liquid' - -export interface FilterImpl { - context: Context; - liquid: Liquid; -} diff --git a/src/template/filter/filter-map.ts b/src/template/filter/filter-map.ts deleted file mode 100644 index 8ad41498d2..0000000000 --- a/src/template/filter/filter-map.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { FilterImplOptions } from './filter-impl-options' -import { Filter } from './filter' -import { FilterArg } from '../../parser/filter-arg' -import { assert } from '../../util/assert' -import { Liquid } from '../../liquid' - -export class FilterMap { - private impls: {[key: string]: FilterImplOptions} = {} - - constructor ( - private readonly strictFilters: boolean, - private readonly liquid: Liquid - ) {} - - 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: FilterArg[]) { - return new Filter(name, this.get(name), args, this.liquid) - } -} diff --git a/src/template/tag/hash.ts b/src/template/hash.ts similarity index 64% rename from src/template/tag/hash.ts rename to src/template/hash.ts index e435a31011..e9479a3715 100644 --- a/src/template/tag/hash.ts +++ b/src/template/hash.ts @@ -1,10 +1,9 @@ -import { _evalToken } from '../../render/expression' -import { Context } from '../../context/context' -import { Tokenizer } from '../../parser/tokenizer' +import { _evalToken } from '../render/expression' +import { Context } from '../context/context' +import { Tokenizer } from '../parser/tokenizer' +import { Token } from '../tokens/token' -export interface HashValue { - [key: string]: any; -} +type HashValueTokens = Record /** * Key-Value Pairs Representing Tag Arguments @@ -15,14 +14,14 @@ export interface HashValue { * hash['reversed'] === undefined */ export class Hash { - hash: HashValue = {} + hash: HashValueTokens = {} constructor (markup: string, jekyllStyle?: boolean) { const tokenizer = new Tokenizer(markup, {}) for (const hash of tokenizer.readHashes(jekyllStyle)) { this.hash[hash.name.content] = hash.value } } - * render (ctx: Context): Generator { + * render (ctx: Context): Generator, unknown> { const hash = {} for (const key of Object.keys(this.hash)) { hash[key] = this.hash[key] === undefined ? true : yield _evalToken(this.hash[key], ctx) diff --git a/src/template/html.ts b/src/template/html.ts index 50cfdb8b12..6685367259 100644 --- a/src/template/html.ts +++ b/src/template/html.ts @@ -1,8 +1,7 @@ -import { TemplateImpl } from '../template/template-impl' -import { Template } from '../template/template' -import { HTMLToken } from '../tokens/html-token' -import { Context } from '../context/context' -import { Emitter } from '../emitters/emitter' +import { TemplateImpl, Template } from '../template' +import { HTMLToken } from '../tokens' +import { Context } from '../context' +import { Emitter } from '../emitters' export class HTML extends TemplateImpl implements Template { private str: string diff --git a/src/template/index.ts b/src/template/index.ts new file mode 100644 index 0000000000..e5a055b6e1 --- /dev/null +++ b/src/template/index.ts @@ -0,0 +1,10 @@ +export * from './template' +export * from './template-impl' +export * from './tag' +export * from './tag-options-adapter' +export * from './filter' +export * from './filter-impl-options' +export * from './hash' +export * from './value' +export * from './output' +export * from './html' diff --git a/src/template/output.ts b/src/template/output.ts index 5cabdc63c1..9c20bfff84 100644 --- a/src/template/output.ts +++ b/src/template/output.ts @@ -1,11 +1,10 @@ import { Value } from './value' -import { TemplateImpl } from '../template/template-impl' -import { Template } from '../template/template' +import { Template, TemplateImpl } from '../template' import { Context } from '../context/context' import { Emitter } from '../emitters/emitter' import { OutputToken } from '../tokens/output-token' import { Liquid } from '../liquid' -import { Filter } from './filter/filter' +import { Filter } from './filter' export class Output extends TemplateImpl implements Template { private value: Value diff --git a/src/template/tag-options-adapter.ts b/src/template/tag-options-adapter.ts new file mode 100644 index 0000000000..4bb23043ed --- /dev/null +++ b/src/template/tag-options-adapter.ts @@ -0,0 +1,27 @@ +import { isFunction } from '../util' +import { Hash } from './hash' +import { Tag, TagClass, TagRenderReturn } from './tag' +import { TagToken, TopLevelToken } from '../tokens' +import { Emitter } from '../emitters' +import { Context } from '../context' +import type { Liquid } from '../liquid' + +export interface TagImplOptions { + parse?: (this: Tag, token: TagToken, remainingTokens: TopLevelToken[]) => void; + render: (this: Tag, ctx: Context, emitter: Emitter, hash: Record) => TagRenderReturn; +} + +export function createTagClass (options: TagImplOptions): TagClass { + return class extends Tag { + constructor (token: TagToken, tokens: TopLevelToken[], liquid: Liquid) { + super(token, tokens, liquid) + if (isFunction(options.parse)) { + options.parse.call(this, token, tokens) + } + } + * render (ctx: Context, emitter: Emitter): TagRenderReturn { + const hash = (yield new Hash(this.token.args).render(ctx)) as Record + return yield options.render.call(this, ctx, emitter, hash) + } + } +} diff --git a/src/template/tag.ts b/src/template/tag.ts new file mode 100644 index 0000000000..1515b2a2c5 --- /dev/null +++ b/src/template/tag.ts @@ -0,0 +1,24 @@ +import { TemplateImpl } from './template-impl' +import type { Emitter } from '../emitters/emitter' +import type { Context } from '../context/context' +import type { TopLevelToken, TagToken } from '../tokens' +import type { Template } from './template' +import type { Liquid } from '../liquid' + +export type TagRenderReturn = Generator | Promise | unknown + +export abstract class Tag extends TemplateImpl implements Template { + public name: string + protected liquid: Liquid + + public constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) { + super(token) + this.name = token.name + this.liquid = liquid + } + public abstract render (ctx: Context, emitter: Emitter): TagRenderReturn; +} + +export interface TagClass { + new(token: TagToken, tokens: TopLevelToken[], liquid: Liquid): Tag +} diff --git a/src/template/tag/tag-impl-options.ts b/src/template/tag/tag-impl-options.ts deleted file mode 100644 index 56a2d29f3a..0000000000 --- a/src/template/tag/tag-impl-options.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Context } from '../../context/context' -import { TagToken } from '../../tokens/tag-token' -import { TopLevelToken } from '../../tokens/toplevel-token' -import { TagImpl } from './tag-impl' -import { HashValue } from '../../template/tag/hash' -import { Emitter } from '../../emitters/emitter' - -export interface TagImplOptions { - parse?: (this: TagImpl, token: TagToken, remainingTokens: TopLevelToken[]) => void; - render: (this: TagImpl, ctx: Context, emitter: Emitter, hash: HashValue) => void | string | Promise | Generator; -} diff --git a/src/template/tag/tag-impl.ts b/src/template/tag/tag-impl.ts deleted file mode 100644 index 904b833f7a..0000000000 --- a/src/template/tag/tag-impl.ts +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index f23ed2ba1d..0000000000 --- a/src/template/tag/tag-map.ts +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 8bfb7c278a..0000000000 --- a/src/template/tag/tag.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { isFunction } from '../../util/underscore' -import { Liquid } from '../../liquid' -import { TemplateImpl } from '../../template/template-impl' -import { Emitter, Hash, Context, TagToken, Template, TopLevelToken } from '../../types' -import { TagImpl } from './tag-impl' -import { HashValue } from './hash' - -export class Tag extends TemplateImpl implements Template { - public name: string - private impl: TagImpl - - public constructor (token: TagToken, tokens: TopLevelToken[], liquid: Liquid) { - super(token) - this.name = token.name - - const impl = liquid.tags.get(token.name) - - this.impl = Object.create(impl) - this.impl.liquid = liquid - if (this.impl.parse) { - this.impl.parse(token, tokens) - } - } - public * render (ctx: Context, emitter: Emitter): Generator { - const hash = (yield new Hash(this.token.args).render(ctx)) as HashValue - const impl = this.impl - if (isFunction(impl.render)) return yield impl.render(ctx, emitter, hash) - } -} diff --git a/src/template/value.ts b/src/template/value.ts index b02157adf4..902ffb2a49 100644 --- a/src/template/value.ts +++ b/src/template/value.ts @@ -1,8 +1,9 @@ -import { Expression } from '../render/expression' -import { Tokenizer } from '../parser/tokenizer' -import { Filter } from './filter/filter' -import { Context } from '../context/context' -import { Liquid } from '../liquid' +import { Filter } from './filter' +import { Expression } from '../render' +import { Tokenizer } from '../parser' +import { assert } from '../util' +import type { Liquid } from '../liquid' +import type { Context } from '../context' export class Value { public readonly filters: Filter[] = [] @@ -14,7 +15,7 @@ export class Value { public constructor (str: string, liquid: Liquid) { const tokenizer = new Tokenizer(str, liquid.options.operators) this.initial = tokenizer.readExpression() - this.filters = tokenizer.readFilters().map(({ name, args }) => new Filter(name, liquid.filters.get(name), args, liquid)) + this.filters = tokenizer.readFilters().map(({ name, args }) => new Filter(name, this.getFilter(liquid, name), args, liquid)) } public * value (ctx: Context, lenient: boolean): Generator { lenient = lenient || (ctx.opts.lenientIf && this.filters.length > 0 && this.filters[0].name === 'default') @@ -25,4 +26,9 @@ export class Value { } return val } + private getFilter (liquid: Liquid, name: string) { + const impl = liquid.filters[name] + assert(impl || !liquid.options.strictFilters, () => `undefined filter: ${name}`) + return impl + } } diff --git a/src/tokens/delimited-token.ts b/src/tokens/delimited-token.ts index 1a9f2948f6..d960c64283 100644 --- a/src/tokens/delimited-token.ts +++ b/src/tokens/delimited-token.ts @@ -1,6 +1,6 @@ import { Token } from './token' -import { TokenKind } from '../parser/token-kind' -import { last } from '../util/underscore' +import { TokenKind } from '../parser' +import { last } from '../util' export abstract class DelimitedToken extends Token { public trimLeft = false diff --git a/src/tokens/filter-token.ts b/src/tokens/filter-token.ts index dbd8b32626..5b593b9aac 100644 --- a/src/tokens/filter-token.ts +++ b/src/tokens/filter-token.ts @@ -1,6 +1,6 @@ import { Token } from './token' import { FilterArg } from '../parser/filter-arg' -import { TokenKind } from '../parser/token-kind' +import { TokenKind } from '../parser' export class FilterToken extends Token { public constructor ( diff --git a/src/tokens/hash-token.ts b/src/tokens/hash-token.ts index 6f691939cc..1aa35b0c5c 100644 --- a/src/tokens/hash-token.ts +++ b/src/tokens/hash-token.ts @@ -1,7 +1,7 @@ import { Token } from './token' import { ValueToken } from './value-token' import { IdentifierToken } from './identifier-token' -import { TokenKind } from '../parser/token-kind' +import { TokenKind } from '../parser' export class HashToken extends Token { constructor ( diff --git a/src/tokens/html-token.ts b/src/tokens/html-token.ts index 015467fdc8..cb8e420315 100644 --- a/src/tokens/html-token.ts +++ b/src/tokens/html-token.ts @@ -1,5 +1,5 @@ import { Token } from './token' -import { TokenKind } from '../parser/token-kind' +import { TokenKind } from '../parser' export class HTMLToken extends Token { trimLeft = 0 diff --git a/src/tokens/identifier-token.ts b/src/tokens/identifier-token.ts index 056a85de47..0c44d3b460 100644 --- a/src/tokens/identifier-token.ts +++ b/src/tokens/identifier-token.ts @@ -1,6 +1,6 @@ import { Token } from './token' -import { NUMBER, TYPES, SIGN } from '../util/character' -import { TokenKind } from '../parser/token-kind' +import { NUMBER, TYPES, SIGN } from '../util' +import { TokenKind } from '../parser' export class IdentifierToken extends Token { public content: string diff --git a/src/tokens/index.ts b/src/tokens/index.ts new file mode 100644 index 0000000000..d117dcf8a7 --- /dev/null +++ b/src/tokens/index.ts @@ -0,0 +1,17 @@ +export * from './top-level-token' +export * from './tag-token' +export * from './output-token' +export * from './html-token' +export * from './number-token' +export * from './identifier-token' +export * from './literal-token' +export * from './operator-token' +export * from './property-access-token' +export * from './filter-token' +export * from './hash-token' +export * from './quoted-token' +export * from './token' +export * from './range-token' +export * from './value-token' +export * from './liquid-tag-token' +export * from './delimited-token' diff --git a/src/tokens/liquid-tag-token.ts b/src/tokens/liquid-tag-token.ts index d7bc2cc509..d8dc7c2d71 100644 --- a/src/tokens/liquid-tag-token.ts +++ b/src/tokens/liquid-tag-token.ts @@ -1,8 +1,7 @@ import { DelimitedToken } from './delimited-token' -import { TokenizationError } from '../util/error' +import { TokenizationError } from '../util' import { NormalizedFullOptions } from '../liquid-options' -import { TokenKind } from '../parser/token-kind' -import { Tokenizer } from '../parser/tokenizer' +import { Tokenizer, TokenKind } from '../parser' export class LiquidTagToken extends DelimitedToken { public name: string diff --git a/src/tokens/literal-token.ts b/src/tokens/literal-token.ts index 4e6a56be2b..e93065c481 100644 --- a/src/tokens/literal-token.ts +++ b/src/tokens/literal-token.ts @@ -1,5 +1,5 @@ import { Token } from './token' -import { TokenKind } from '../parser/token-kind' +import { TokenKind } from '../parser' export class LiteralToken extends Token { public literal: string diff --git a/src/tokens/number-token.ts b/src/tokens/number-token.ts index e8ea5da238..aa2b854db2 100644 --- a/src/tokens/number-token.ts +++ b/src/tokens/number-token.ts @@ -1,6 +1,6 @@ import { Token } from './token' import { IdentifierToken } from './identifier-token' -import { TokenKind } from '../parser/token-kind' +import { TokenKind } from '../parser' export class NumberToken extends Token { constructor ( diff --git a/src/tokens/operator-token.ts b/src/tokens/operator-token.ts index fd1f93a1c9..2efa3d9383 100644 --- a/src/tokens/operator-token.ts +++ b/src/tokens/operator-token.ts @@ -1,5 +1,5 @@ import { Token } from './token' -import { TokenKind } from '../parser/token-kind' +import { TokenKind } from '../parser' export const precedence = { '==': 1, diff --git a/src/tokens/output-token.ts b/src/tokens/output-token.ts index d6f8b748f2..da2d7eef96 100644 --- a/src/tokens/output-token.ts +++ b/src/tokens/output-token.ts @@ -1,6 +1,6 @@ import { DelimitedToken } from './delimited-token' import { NormalizedFullOptions } from '../liquid-options' -import { TokenKind } from '../parser/token-kind' +import { TokenKind } from '../parser' export class OutputToken extends DelimitedToken { public constructor ( diff --git a/src/tokens/property-access-token.ts b/src/tokens/property-access-token.ts index 44262bbb59..c2ee6f003f 100644 --- a/src/tokens/property-access-token.ts +++ b/src/tokens/property-access-token.ts @@ -1,8 +1,7 @@ import { Token } from './token' import { IdentifierToken } from './identifier-token' import { QuotedToken } from './quoted-token' -import { TokenKind } from '../parser/token-kind' -import { parseStringLiteral } from '../parser/parse-string-literal' +import { TokenKind, parseStringLiteral } from '../parser' export class PropertyAccessToken extends Token { public propertyName: string diff --git a/src/tokens/quoted-token.ts b/src/tokens/quoted-token.ts index 803023a386..55174461f6 100644 --- a/src/tokens/quoted-token.ts +++ b/src/tokens/quoted-token.ts @@ -1,5 +1,5 @@ import { Token } from './token' -import { TokenKind } from '../parser/token-kind' +import { TokenKind } from '../parser' export class QuotedToken extends Token { constructor ( diff --git a/src/tokens/range-token.ts b/src/tokens/range-token.ts index 62ffa87278..877cfd50e9 100644 --- a/src/tokens/range-token.ts +++ b/src/tokens/range-token.ts @@ -1,6 +1,6 @@ import { Token } from './token' import { ValueToken } from './value-token' -import { TokenKind } from '../parser/token-kind' +import { TokenKind } from '../parser' export class RangeToken extends Token { constructor ( diff --git a/src/tokens/tag-token.ts b/src/tokens/tag-token.ts index 746d69a7ca..6b23728805 100644 --- a/src/tokens/tag-token.ts +++ b/src/tokens/tag-token.ts @@ -1,8 +1,7 @@ import { DelimitedToken } from './delimited-token' import { TokenizationError } from '../util/error' -import { NormalizedFullOptions } from '../liquid-options' -import { TokenKind } from '../parser/token-kind' -import { Tokenizer } from '../parser/tokenizer' +import { Tokenizer, TokenKind } from '../parser' +import type { NormalizedFullOptions } from '../liquid-options' export class TagToken extends DelimitedToken { public name: string diff --git a/src/tokens/token.ts b/src/tokens/token.ts index 9cae30a6cb..8d41800e12 100644 --- a/src/tokens/token.ts +++ b/src/tokens/token.ts @@ -1,4 +1,4 @@ -import { TokenKind } from '../parser/token-kind' +import { TokenKind } from '../parser' export abstract class Token { public constructor ( diff --git a/src/tokens/top-level-token.ts b/src/tokens/top-level-token.ts new file mode 100644 index 0000000000..2591c8c94b --- /dev/null +++ b/src/tokens/top-level-token.ts @@ -0,0 +1,5 @@ +import type { TagToken } from './tag-token' +import type { HTMLToken } from './html-token' +import type { OutputToken } from './output-token' + +export type TopLevelToken = TagToken | OutputToken | HTMLToken diff --git a/src/tokens/toplevel-token.ts b/src/tokens/toplevel-token.ts deleted file mode 100644 index 320d2df5f3..0000000000 --- a/src/tokens/toplevel-token.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { TagToken } from './tag-token' -import { HTMLToken } from './html-token' -import { OutputToken } from './output-token' - -export type TopLevelToken = TagToken | OutputToken | HTMLToken diff --git a/src/types.ts b/src/types.ts deleted file mode 100644 index be5185eb20..0000000000 --- a/src/types.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* istanbul ignore file */ -import * as TypeGuards from './util/type-guards' -export { TypeGuards } -export { ParseError, TokenizationError, AssertionError } from './util/error' -export { assert } from './util/assert' -export { Drop } from './drop/drop' -export { Emitter } from './emitters/emitter' -export { Expression } from './render/expression' -export { isFalsy, isTruthy } from './render/boolean' -export { TagToken } from './tokens/tag-token' -export { Context } from './context/context' -export { Template } from './template/template' -export { FilterImplOptions } from './template/filter/filter-impl-options' -export { TagImplOptions } from './template/tag/tag-impl-options' -export { TagImpl } from './template/tag/tag-impl' -export { ParseStream } from './parser/parse-stream' -export { Token } from './tokens/token' -export { TokenKind } from './parser/token-kind' -export { TopLevelToken } from './tokens/toplevel-token' -export { Tokenizer } from './parser/tokenizer' -export { Hash } from './template/tag/hash' -export { Value } from './template/value' -// eslint-disable-next-line deprecation/deprecation -export { _evalToken, evalToken, evalQuotedToken } from './render/expression' -export { toPromise, toValueSync } from './util/async' -export { defaultOperators, Operators } from './render/operator' -export { createTrie, Trie } from './util/operator-trie' -export { toValue } from './util/underscore' -export { TimezoneDate } from './util/timezone-date' -export { filters } from './filters' -export { tags } from './tags' -export { defaultOptions } from './liquid-options' diff --git a/src/util/collection.ts b/src/util/collection.ts index b43b7ad369..43b7bfb346 100644 --- a/src/util/collection.ts +++ b/src/util/collection.ts @@ -1,11 +1,11 @@ import { isNil, isString, isObject, isArray, isIterable, toValue } from './underscore' -export function toEnumerable (val: any) { +export function toEnumerable (val: any): T[] { val = toValue(val) if (isArray(val)) return val - if (isString(val) && val.length > 0) return [val] + if (isString(val) && val.length > 0) return [val] as unknown as T[] if (isIterable(val)) return Array.from(val) - if (isObject(val)) return Object.keys(val).map((key) => [key, val[key]]) + if (isObject(val)) return Object.keys(val).map((key) => [key, val[key]]) as unknown as T[] return [] } diff --git a/src/util/index.ts b/src/util/index.ts new file mode 100644 index 0000000000..5a6152c3d9 --- /dev/null +++ b/src/util/index.ts @@ -0,0 +1,12 @@ +export * from './error' +export * from './character' +export * from './assert' +export * from './literal' +export * from './underscore' +export * from './operator-trie' +export * from './type-guards' +export * from './async' +export * from './collection' +export * from './strftime' +export * from './liquid-date' +export * from './timezone-date' diff --git a/src/util/literal.ts b/src/util/literal.ts index 212d0424f4..7a071f1f69 100644 --- a/src/util/literal.ts +++ b/src/util/literal.ts @@ -1,6 +1,4 @@ -import { NullDrop } from '../drop/null-drop' -import { EmptyDrop } from '../drop/empty-drop' -import { BlankDrop } from '../drop/blank-drop' +import { BlankDrop, EmptyDrop, NullDrop } from '../drop' const nil = new NullDrop() export const literalValues = { diff --git a/src/util/operator-trie.ts b/src/util/operator-trie.ts index 94d3acb80d..94bd7b80b3 100644 --- a/src/util/operator-trie.ts +++ b/src/util/operator-trie.ts @@ -1,14 +1,22 @@ -import { Operators } from '../render/operator' +import { Operators, OperatorHandler } from '../render/operator' import { IDENTIFIER, TYPES } from '../util/character' +interface TrieLeafNode { + handler: OperatorHandler; + end: true; + needBoundary?: true; +} + export interface Trie { - [key: string]: any; + [key: string]: Trie | TrieLeafNode; } +export type TrieNode = Trie | TrieLeafNode + export function createTrie (operators: Operators): Trie { const trie: Trie = {} for (const [name, handler] of Object.entries(operators)) { - let node = trie + let node: Trie | TrieLeafNode = trie for (let i = 0; i < name.length; i++) { const c = name[i] diff --git a/src/util/strftime.ts b/src/util/strftime.ts index 761227d26f..007fa1c3d9 100644 --- a/src/util/strftime.ts +++ b/src/util/strftime.ts @@ -140,7 +140,7 @@ const formatCodes = { }; (formatCodes as any).h = formatCodes.b -export default function (d: LiquidDate, formatStr: string) { +export function strftime (d: LiquidDate, formatStr: string) { let output = '' let remaining = formatStr let match diff --git a/src/util/type-guards.ts b/src/util/type-guards.ts index f2e02bf171..bd585098aa 100644 --- a/src/util/type-guards.ts +++ b/src/util/type-guards.ts @@ -1,15 +1,5 @@ -import { OperatorToken } from '../tokens/operator-token' -import { DelimitedToken } from '../tokens/delimited-token' -import { IdentifierToken } from '../tokens/identifier-token' -import { TagToken } from '../tokens/tag-token' -import { HTMLToken } from '../tokens/html-token' -import { OutputToken } from '../tokens/output-token' -import { PropertyAccessToken } from '../tokens/property-access-token' -import { LiteralToken } from '../tokens/literal-token' -import { QuotedToken } from '../tokens/quoted-token' -import { NumberToken } from '../tokens/number-token' -import { RangeToken } from '../tokens/range-token' -import { TokenKind } from '../parser/token-kind' +import { RangeToken, NumberToken, QuotedToken, LiteralToken, PropertyAccessToken, OutputToken, HTMLToken, TagToken, IdentifierToken, DelimitedToken, OperatorToken } from '../tokens' +import { TokenKind } from '../parser' export function isDelimitedToken (val: any): val is DelimitedToken { return !!(getKind(val) & TokenKind.Delimited) diff --git a/src/util/underscore.ts b/src/util/underscore.ts index fd679fc4e1..fbe4e49833 100644 --- a/src/util/underscore.ts +++ b/src/util/underscore.ts @@ -81,7 +81,7 @@ export function isIterable (value: any): value is Iterable { * @return {Object} Returns object. */ export function forOwn ( - obj: {[key: string]: T} | undefined, + obj: Record | undefined, iteratee: ((val: T, key: string, obj: {[key: string]: T}) => boolean | void) ) { obj = obj || {} diff --git a/test/e2e/drop.ts b/test/e2e/drop.ts index 3edd0db06a..69e869f1e4 100644 --- a/test/e2e/drop.ts +++ b/test/e2e/drop.ts @@ -1,4 +1,5 @@ -import { Liquid, Drop } from '../..' +import { Liquid } from '../../src/liquid' +import { Drop } from '../../src/drop/drop' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/e2e/issues.ts b/test/e2e/issues.ts index 9d2dbe7c32..bc5a7421c1 100644 --- a/test/e2e/issues.ts +++ b/test/e2e/issues.ts @@ -1,4 +1,4 @@ -import { Tokenizer, Context, Liquid, Drop, defaultOptions, toValueSync } from '../..' +import { Tokenizer, Context, Liquid, Drop, toValueSync } from '../..' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' import * as sinon from 'sinon' @@ -261,7 +261,7 @@ describe('Issues', function () { expect(() => engine.parse('{% assign headshot = https://testurl.com/not_enclosed_in_quotes.jpg %}')).to.throw(/unexpected token at ":/) }) it('#527 export Liquid Expression', () => { - const tokenizer = new Tokenizer('a > b', defaultOptions.operatorsTrie) + const tokenizer = new Tokenizer('a > b') const expression = tokenizer.readExpression() const result = toValueSync(expression.evaluate(new Context({ a: 1, b: 2 }))) expect(result).to.equal(false) diff --git a/test/integration/drop/drop.ts b/test/integration/drop/drop.ts index f32ebfabe2..fa52300007 100644 --- a/test/integration/drop/drop.ts +++ b/test/integration/drop/drop.ts @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { Liquid, Drop } from '../../../src/liquid' +import { Liquid, Drop } from '../../../src' describe('drop/drop', function () { let liquid: Liquid diff --git a/test/integration/builtin/filters/array.ts b/test/integration/filters/array.ts similarity index 99% rename from test/integration/builtin/filters/array.ts rename to test/integration/filters/array.ts index ecd303701e..39cc9675de 100644 --- a/test/integration/builtin/filters/array.ts +++ b/test/integration/filters/array.ts @@ -1,4 +1,4 @@ -import { test, render } from '../../../stub/render' +import { test, render } from '../../stub/render' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' use(chaiAsPromised) diff --git a/test/integration/builtin/filters/date.ts b/test/integration/filters/date.ts similarity index 97% rename from test/integration/builtin/filters/date.ts rename to test/integration/filters/date.ts index 4b7ac2be56..55783469fa 100644 --- a/test/integration/builtin/filters/date.ts +++ b/test/integration/filters/date.ts @@ -1,5 +1,5 @@ -import { LiquidOptions } from '../../../../src/liquid-options' -import { test } from '../../../stub/render' +import { LiquidOptions } from '../../../src/liquid-options' +import { test } from '../../stub/render' describe('filters/date', function () { it('should support date: %a %b %d %Y', function () { diff --git a/test/integration/builtin/filters/html.ts b/test/integration/filters/html.ts similarity index 98% rename from test/integration/builtin/filters/html.ts rename to test/integration/filters/html.ts index 4cb2c1cd0a..afe45b980f 100644 --- a/test/integration/builtin/filters/html.ts +++ b/test/integration/filters/html.ts @@ -1,4 +1,4 @@ -import { test } from '../../../stub/render' +import { test } from '../../stub/render' describe('filters/html', function () { describe('escape', function () { diff --git a/test/integration/builtin/filters/math.ts b/test/integration/filters/math.ts similarity index 98% rename from test/integration/builtin/filters/math.ts rename to test/integration/filters/math.ts index 0220609eaf..98d5c724a3 100644 --- a/test/integration/builtin/filters/math.ts +++ b/test/integration/filters/math.ts @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { test, liquid } from '../../../stub/render' +import { test, liquid } from '../../stub/render' describe('filters/math', function () { describe('abs', function () { diff --git a/test/integration/builtin/filters/misc.ts b/test/integration/filters/misc.ts similarity index 98% rename from test/integration/builtin/filters/misc.ts rename to test/integration/filters/misc.ts index e9030a66e9..4f4a2898f9 100644 --- a/test/integration/builtin/filters/misc.ts +++ b/test/integration/filters/misc.ts @@ -1,4 +1,4 @@ -import { Liquid } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/filters/string.ts b/test/integration/filters/string.ts similarity index 99% rename from test/integration/builtin/filters/string.ts rename to test/integration/filters/string.ts index d530191ff7..44d4b1c2c5 100644 --- a/test/integration/builtin/filters/string.ts +++ b/test/integration/filters/string.ts @@ -1,4 +1,4 @@ -import { test } from '../../../stub/render' +import { test } from '../../stub/render' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/filters/url.ts b/test/integration/filters/url.ts similarity index 91% rename from test/integration/builtin/filters/url.ts rename to test/integration/filters/url.ts index c09a81f2e9..6c21d6acca 100644 --- a/test/integration/builtin/filters/url.ts +++ b/test/integration/filters/url.ts @@ -1,4 +1,4 @@ -import { test } from '../../../stub/render' +import { test } from '../../stub/render' describe('filters/url', function () { describe('url_decode', function () { diff --git a/test/integration/liquid/liquid.ts b/test/integration/liquid/liquid.ts index 766ad5d63d..8a50b4fb8f 100644 --- a/test/integration/liquid/liquid.ts +++ b/test/integration/liquid/liquid.ts @@ -1,7 +1,7 @@ -import { Liquid, Context, isFalsy } from '../../../src/liquid' import * as chai from 'chai' -import { mock, restore } from '../../stub/mockfs' import * as chaiAsPromised from 'chai-as-promised' +import { Liquid, Context, isFalsy } from '../../../src' +import { mock, restore } from '../../stub/mockfs' import { drainStream } from '../../stub/stream' const expect = chai.expect diff --git a/test/integration/liquid/operators-option.ts b/test/integration/liquid/operators-option.ts index 61093343a0..7850cb663b 100644 --- a/test/integration/liquid/operators-option.ts +++ b/test/integration/liquid/operators-option.ts @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { Liquid, defaultOperators } from '../../../src/liquid' +import { Liquid, defaultOperators } from '../../../src' describe('LiquidOptions#operators', function () { let engine: Liquid diff --git a/test/integration/builtin/tags/assign.ts b/test/integration/tags/assign.ts similarity index 97% rename from test/integration/builtin/tags/assign.ts rename to test/integration/tags/assign.ts index 201562a42d..30abc12224 100644 --- a/test/integration/builtin/tags/assign.ts +++ b/test/integration/tags/assign.ts @@ -1,4 +1,5 @@ -import { ParseError, Liquid } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' +import { ParseError } from '../../../src' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/tags/capture.ts b/test/integration/tags/capture.ts similarity index 96% rename from test/integration/builtin/tags/capture.ts rename to test/integration/tags/capture.ts index 16ae08082c..8b65198aec 100644 --- a/test/integration/builtin/tags/capture.ts +++ b/test/integration/tags/capture.ts @@ -1,4 +1,4 @@ -import { Liquid } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/tags/case.ts b/test/integration/tags/case.ts similarity index 98% rename from test/integration/builtin/tags/case.ts rename to test/integration/tags/case.ts index 23b89bb25b..b085468255 100644 --- a/test/integration/builtin/tags/case.ts +++ b/test/integration/tags/case.ts @@ -1,4 +1,4 @@ -import { Liquid } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/tags/comment.ts b/test/integration/tags/comment.ts similarity index 97% rename from test/integration/builtin/tags/comment.ts rename to test/integration/tags/comment.ts index db652ecc71..f27f2f6b6f 100644 --- a/test/integration/builtin/tags/comment.ts +++ b/test/integration/tags/comment.ts @@ -1,4 +1,4 @@ -import { Liquid } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/tags/cycle.ts b/test/integration/tags/cycle.ts similarity index 97% rename from test/integration/builtin/tags/cycle.ts rename to test/integration/tags/cycle.ts index 4d42d87908..6843256292 100644 --- a/test/integration/builtin/tags/cycle.ts +++ b/test/integration/tags/cycle.ts @@ -1,4 +1,4 @@ -import { Liquid } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/tags/decrement.ts b/test/integration/tags/decrement.ts similarity index 97% rename from test/integration/builtin/tags/decrement.ts rename to test/integration/tags/decrement.ts index e5eab09235..cc055156ea 100644 --- a/test/integration/builtin/tags/decrement.ts +++ b/test/integration/tags/decrement.ts @@ -1,4 +1,4 @@ -import { Liquid } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/tags/echo.ts b/test/integration/tags/echo.ts similarity index 96% rename from test/integration/builtin/tags/echo.ts rename to test/integration/tags/echo.ts index a10b31638a..e174372587 100644 --- a/test/integration/builtin/tags/echo.ts +++ b/test/integration/tags/echo.ts @@ -1,4 +1,4 @@ -import { Liquid } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/tags/for.ts b/test/integration/tags/for.ts similarity index 99% rename from test/integration/builtin/tags/for.ts rename to test/integration/tags/for.ts index 632d5c98e0..b681a90c55 100644 --- a/test/integration/builtin/tags/for.ts +++ b/test/integration/tags/for.ts @@ -1,7 +1,8 @@ -import { Drop, Liquid } from '../../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' -import { Scope } from '../../../../src/context/scope' +import { Liquid } from '../../../src/liquid' +import { Drop } from '../../../src/drop/drop' +import { Scope } from '../../../src/context/scope' use(chaiAsPromised) diff --git a/test/integration/builtin/tags/if.ts b/test/integration/tags/if.ts similarity index 99% rename from test/integration/builtin/tags/if.ts rename to test/integration/tags/if.ts index 396aec3cfe..f25cf11373 100644 --- a/test/integration/builtin/tags/if.ts +++ b/test/integration/tags/if.ts @@ -1,4 +1,4 @@ -import { Liquid } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' import * as chai from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/tags/include.ts b/test/integration/tags/include.ts similarity index 98% rename from test/integration/builtin/tags/include.ts rename to test/integration/tags/include.ts index 800bb0905f..68bd528eb8 100644 --- a/test/integration/builtin/tags/include.ts +++ b/test/integration/tags/include.ts @@ -1,6 +1,7 @@ -import { Liquid, Drop } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' +import { Drop } from '../../../src/drop/drop' import { expect } from 'chai' -import { mock, restore } from '../../../stub/mockfs' +import { mock, restore } from '../../stub/mockfs' describe('tags/include', function () { let liquid: Liquid diff --git a/test/integration/builtin/tags/increment.ts b/test/integration/tags/increment.ts similarity index 97% rename from test/integration/builtin/tags/increment.ts rename to test/integration/tags/increment.ts index f40ec0c525..ff27f8bd48 100644 --- a/test/integration/builtin/tags/increment.ts +++ b/test/integration/tags/increment.ts @@ -1,4 +1,4 @@ -import { Liquid } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/tags/inline-comment.ts b/test/integration/tags/inline-comment.ts similarity index 98% rename from test/integration/builtin/tags/inline-comment.ts rename to test/integration/tags/inline-comment.ts index 08d7ef4478..cef1f49131 100644 --- a/test/integration/builtin/tags/inline-comment.ts +++ b/test/integration/tags/inline-comment.ts @@ -1,4 +1,4 @@ -import { Liquid } from '../../../../src/liquid' +import { Liquid } from '../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' diff --git a/test/integration/builtin/tags/layout.ts b/test/integration/tags/layout.ts similarity index 99% rename from test/integration/builtin/tags/layout.ts rename to test/integration/tags/layout.ts index c8a8f4e35c..1c9320fc52 100644 --- a/test/integration/builtin/tags/layout.ts +++ b/test/integration/tags/layout.ts @@ -1,7 +1,7 @@ -import { Liquid } from '../../../../src/liquid' import { expect, use } from 'chai' -import { mock, restore } from '../../../stub/mockfs' import * as chaiAsPromised from 'chai-as-promised' +import { Liquid } from '../../../src/liquid' +import { mock, restore } from '../../stub/mockfs' use(chaiAsPromised) diff --git a/test/integration/builtin/tags/liquid.ts b/test/integration/tags/liquid.ts similarity index 97% rename from test/integration/builtin/tags/liquid.ts rename to test/integration/tags/liquid.ts index fbc859cf48..312adc28be 100644 --- a/test/integration/builtin/tags/liquid.ts +++ b/test/integration/tags/liquid.ts @@ -1,6 +1,6 @@ -import { Liquid } from '../../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' +import { Liquid } from '../../../src/liquid' use(chaiAsPromised) diff --git a/test/integration/builtin/tags/raw.ts b/test/integration/tags/raw.ts similarity index 95% rename from test/integration/builtin/tags/raw.ts rename to test/integration/tags/raw.ts index a9f0d64a37..bd84471843 100644 --- a/test/integration/builtin/tags/raw.ts +++ b/test/integration/tags/raw.ts @@ -1,6 +1,6 @@ -import { Liquid } from '../../../../src/liquid' import * as chai from 'chai' import * as chaiAsPromised from 'chai-as-promised' +import { Liquid } from '../../../src/liquid' const expect = chai.expect chai.use(chaiAsPromised) diff --git a/test/integration/builtin/tags/render.ts b/test/integration/tags/render.ts similarity index 98% rename from test/integration/builtin/tags/render.ts rename to test/integration/tags/render.ts index 3125b26886..2d389e20fc 100644 --- a/test/integration/builtin/tags/render.ts +++ b/test/integration/tags/render.ts @@ -1,7 +1,8 @@ -import { Liquid, Drop } from '../../../../src/liquid' import { expect, use } from 'chai' -import { mock, restore } from '../../../stub/mockfs' import * as chaiAsPromised from 'chai-as-promised' +import { Liquid } from '../../../src/liquid' +import { Drop } from '../../../src/drop/drop' +import { mock, restore } from '../../stub/mockfs' use(chaiAsPromised) describe('tags/render', function () { diff --git a/test/integration/builtin/tags/tablerow.ts b/test/integration/tags/tablerow.ts similarity index 99% rename from test/integration/builtin/tags/tablerow.ts rename to test/integration/tags/tablerow.ts index 78ae4697a7..97309a0d11 100644 --- a/test/integration/builtin/tags/tablerow.ts +++ b/test/integration/tags/tablerow.ts @@ -1,6 +1,6 @@ -import { Liquid } from '../../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' +import { Liquid } from '../../../src/liquid' use(chaiAsPromised) diff --git a/test/integration/builtin/tags/unless.ts b/test/integration/tags/unless.ts similarity index 98% rename from test/integration/builtin/tags/unless.ts rename to test/integration/tags/unless.ts index 7d5fc6fe71..ce69559b78 100644 --- a/test/integration/builtin/tags/unless.ts +++ b/test/integration/tags/unless.ts @@ -1,6 +1,6 @@ -import { Liquid } from '../../../../src/liquid' import { expect, use } from 'chai' import * as chaiAsPromised from 'chai-as-promised' +import { Liquid } from '../../../src/liquid' use(chaiAsPromised) diff --git a/test/unit/parser/match-operator.ts b/test/unit/parser/match-operator.ts index 8c55f43e80..920945d861 100644 --- a/test/unit/parser/match-operator.ts +++ b/test/unit/parser/match-operator.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import { matchOperator } from '../../../src/parser/match-operator' -import { defaultOperators } from '../../../src/types' +import { defaultOperators } from '../../../src' import { createTrie } from '../../../src/util/operator-trie' describe('parser/matchOperator()', function () { diff --git a/test/unit/parser/parse-stream.ts b/test/unit/parser/parse-stream.ts new file mode 100644 index 0000000000..ae1545f150 --- /dev/null +++ b/test/unit/parser/parse-stream.ts @@ -0,0 +1,15 @@ +import * as chai from 'chai' +import { ParseStream } from '../../../src/parser' +import { Token } from '../../../src/tokens' + +const expect = chai.expect + +describe('parseStream', () => { + it('should trigger "token" event', () => { + const token = { kind: 4 } as Token + const ps = new ParseStream([token], (token) => ({ token } as any)) + let got + ps.on('token', token => { got = token }).start() + expect(got).to.equal(token) + }) +}) diff --git a/test/unit/render/render.ts b/test/unit/render/render.ts index 3c1f14812b..640d5aeec0 100644 --- a/test/unit/render/render.ts +++ b/test/unit/render/render.ts @@ -1,12 +1,11 @@ import { expect } from 'chai' import { Context } from '../../../src/context/context' -import { HTMLToken } from '../../../src/tokens/html-token' +import { HTMLToken, TagToken } from '../../../src/tokens' import { Render } from '../../../src/render/render' import { HTML } from '../../../src/template/html' import { SimpleEmitter } from '../../../src/emitters/simple-emitter' import { toPromise } from '../../../src/util/async' -import { Tag } from '../../../src/template/tag/tag' -import { TagToken } from '../../../src/types' +import { Tag } from '../../../src/template/tag' describe('render', function () { let render: Render @@ -42,17 +41,16 @@ describe('render', function () { }) it('should render to html stream asyncly', function (done) { const scope = new Context() + class CustomTag extends Tag { + render () { + return new Promise( + resolve => setTimeout(() => resolve('async tag'), 10) + ) + } + } const tpls = [ new HTML({ getContent: () => '

' } as HTMLToken), - new Tag({ content: 'foo', args: '', name: 'foo' } as TagToken, [], { - tags: { - get: () => ({ - render: () => new Promise( - resolve => setTimeout(() => resolve('async tag'), 10) - ) - }) - } - } as any), + new CustomTag({ content: 'foo', args: '', name: 'foo' } as TagToken, [], {} as any), new HTML({ getContent: () => '

' } as HTMLToken) ] const stream = render.renderTemplatesToNodeStream(tpls, scope) diff --git a/test/unit/template/filter/filter.ts b/test/unit/template/filter/filter.ts index 4385876475..cf596268e3 100644 --- a/test/unit/template/filter/filter.ts +++ b/test/unit/template/filter/filter.ts @@ -1,81 +1,58 @@ import * as chai from 'chai' import * as sinon from 'sinon' import * as sinonChai from 'sinon-chai' -import { Context } from '../../../../src/context/context' -import { toPromise } from '../../../../src/util/async' -import { NumberToken } from '../../../../src/tokens/number-token' -import { QuotedToken } from '../../../../src/tokens/quoted-token' -import { IdentifierToken } from '../../../../src/tokens/identifier-token' -import { FilterMap } from '../../../../src/template/filter/filter-map' +import { Context } from '../../../../src/context' +import { toPromise } from '../../../../src/util' +import { IdentifierToken, NumberToken, QuotedToken } from '../../../../src/tokens' +import { Filter } from '../../../../src/template' chai.use(sinonChai) const expect = chai.expect describe('filter', function () { - let ctx: Context - let filters: FilterMap + const ctx = new Context() const liquid = {} as any - beforeEach(function () { - filters = new FilterMap(false, liquid) - ctx = new Context() - }) - it('should create default filter if not registered', async function () { - 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 toPromise(filters.create('undefined', []).render('foo', ctx))).to.equal('foo') + it('should not change input if filter not registered', async function () { + const filter = new Filter('foo', undefined as any, [], liquid) + expect(await toPromise(filter.render('value', ctx))).to.equal('value') }) it('should call filter impl with correct arguments', async function () { const spy = sinon.spy() - filters.set('foo', spy) const thirty = new NumberToken(new IdentifierToken('30', 0, 2), undefined) - await toPromise(filters.create('foo', [thirty]).render('foo', ctx)) + const filter = new Filter('foo', spy, [thirty], liquid) + await toPromise(filter.render('foo', ctx)) expect(spy).to.have.been.calledWith('foo', 30) }) it('should call filter impl with correct this', async function () { const spy = sinon.spy() - filters.set('foo', spy) const thirty = new NumberToken(new IdentifierToken('33', 0, 2), undefined) - await toPromise(filters.create('foo', [thirty]).render('foo', ctx)) + await toPromise(new Filter('foo', spy, [thirty], liquid).render('foo', ctx)) expect(spy).to.have.been.calledOn(sinon.match.has('context', ctx)) expect(spy).to.have.been.calledOn(sinon.match.has('liquid', liquid)) }) it('should render a simple filter', async function () { - filters.set('upcase', x => x.toUpperCase()) - expect(await toPromise(filters.create('upcase', []).render('foo', ctx))).to.equal('FOO') + expect(await toPromise(new Filter('upcase', (x: string) => x.toUpperCase(), [], liquid).render('foo', ctx))).to.equal('FOO') }) it('should render filters with argument', async function () { - filters.set('add', (a, b) => a + b) const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined) - expect(await toPromise(filters.create('add', [two]).render(3, ctx))).to.equal(5) + expect(await toPromise(new Filter('add', (a: number, b: number) => a + b, [two], liquid).render(3, ctx))).to.equal(5) }) it('should render filters with multiple arguments', async function () { - filters.set('add', (a, b, c) => a + b + c) const two = new NumberToken(new IdentifierToken('2', 0, 1), undefined) const c = new QuotedToken('"c"', 0, 3) - expect(await toPromise(filters.create('add', [two, c]).render(3, ctx))).to.equal('5c') + expect(await toPromise(new Filter('add', (a: number, b: number, c: number) => a + b + c, [two, c], liquid).render(3, ctx))).to.equal('5c') }) it('should pass Objects/Drops as it is', async function () { - filters.set('name', a => a.constructor.name) class Foo {} - expect(await toPromise(filters.create('name', []).render(new Foo(), ctx))).to.equal('Foo') - }) - - it('should not throw when filter name illegal', function () { - expect(function () { - filters.create('/', []) - }).to.not.throw() + expect(await toPromise(new Filter('name', (a: any) => a.constructor.name, [], liquid).render(new Foo(), ctx))).to.equal('Foo') }) 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(new Filter('add', (a: number, b: number[]) => b[0] + ':' + (a + b[1]), [['num', two]], liquid).render(3, ctx))).to.equal('num:5') }) }) diff --git a/test/unit/template/hash.ts b/test/unit/template/hash.ts index 0a89779973..2f191ccb11 100644 --- a/test/unit/template/hash.ts +++ b/test/unit/template/hash.ts @@ -1,6 +1,6 @@ import * as chai from 'chai' import { toPromise } from '../../../src/util/async' -import { Hash } from '../../../src/template/tag/hash' +import { Hash } from '../../../src/template/hash' import { Context } from '../../../src/context/context' const expect = chai.expect diff --git a/test/unit/template/output.ts b/test/unit/template/output.ts index 270b76f5f2..d0be69f9c4 100644 --- a/test/unit/template/output.ts +++ b/test/unit/template/output.ts @@ -4,16 +4,12 @@ import { Context } from '../../../src/context/context' import { Output } from '../../../src/template/output' import { OutputToken } from '../../../src/tokens/output-token' import { defaultOptions } from '../../../src/liquid-options' -import { createTrie } from '../../../src/util/operator-trie' -import { defaultOperators } from '../../../src/types' const expect = chai.expect describe('Output', function () { const emitter: any = { write: (html: string) => (emitter.html += html), html: '' } - const liquid = { - options: { operatorsTrie: createTrie(defaultOperators) } - } as any + const liquid = { options: {} } as any beforeEach(() => { emitter.html = '' }) it('should stringify objects', async function () { diff --git a/test/unit/template/tag-map.ts b/test/unit/template/tag-map.ts deleted file mode 100644 index d2b631bacd..0000000000 --- a/test/unit/template/tag-map.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { expect } from 'chai' -import { TagMap } from '../../../src/template/tag/tag-map' - -describe('TagMap', function () { - it('should throw when not exist', function () { - const map = new TagMap() - expect(() => map.get('not-exist')) - .to.throw(/tag "not-exist" not found/) - }) - it('should get previously set value', function () { - const map = new TagMap() - const impl = { render: () => 'foo' } - map.set('foo', impl) - expect(map.get('foo')).to.equal(impl) - }) -}) diff --git a/test/unit/template/tag.ts b/test/unit/template/tag.ts deleted file mode 100644 index 23cbadbfda..0000000000 --- a/test/unit/template/tag.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as chai from 'chai' -import { Tag } from '../../../src/template/tag/tag' -import { Context } from '../../../src/context/context' -import * as sinon from 'sinon' -import * as sinonChai from 'sinon-chai' -import { TagToken } from '../../../src/tokens/tag-token' -import { toPromise } from '../../../src/util/async' - -chai.use(sinonChai) -const expect = chai.expect - -describe('Tag', function () { - const ctx = new Context() - const emitter: any = { write: (html: string) => (emitter.html += html), html: '' } - - it('should call tag.render', async function () { - const spy = sinon.spy() - const token = { - content: 'foo', - args: '', - name: 'foo' - } as TagToken - await toPromise(new Tag(token, [], { - tags: { - get: () => ({ render: spy }) - } - } as any).render(ctx, emitter)) - expect(spy).to.have.been.called - }) -}) diff --git a/test/unit/util/strftime.ts b/test/unit/util/strftime.ts index 959b5e9253..0b46c2db63 100644 --- a/test/unit/util/strftime.ts +++ b/test/unit/util/strftime.ts @@ -1,5 +1,5 @@ import * as chai from 'chai' -import t from '../../../src/util/strftime' +import { strftime as t } from '../../../src/util/strftime' import { DateWithTimezone } from '../../stub/date-with-timezone' const expect = chai.expect