fix: resolve TS errors without deprecated tsconfig options

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Yang Jun
2026-06-07 01:35:03 +08:00
co-authored by Cursor
parent 9b0b9be849
commit dd6bb0fb60
19 changed files with 92 additions and 85 deletions
+2 -1
View File
@@ -23,7 +23,8 @@ const tsconfig = (target) => ({
compilerOptions: {
target,
module: 'ES2015',
rootDir: 'src'
rootDir: 'src',
downlevelIteration: true
}
}
})
+1 -1
View File
@@ -13,7 +13,7 @@ export class Context {
* for tags like `{% capture %}` `{% assign %}` to operate
*/
private scopes: Scope[] = [createScope()]
private registers = {}
private registers: Record<string, any> = {}
/**
* user passed in scope
* `{% increment %}`, `{% decrement %}` changes this scope,
+1
View File
@@ -1,6 +1,7 @@
import { Context } from '../context'
export abstract class Drop {
[key: string]: any
public liquidMethodMissing (key: string | number, context: Context): Promise<any> | any {
return undefined
}
+2 -2
View File
@@ -1,14 +1,14 @@
import { FilterImpl } from '../template'
import { stringify } from '../util/underscore'
const escapeMap = {
const escapeMap: Record<string, string> = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&#34;',
"'": '&#39;'
}
const unescapeMap = {
const unescapeMap: Record<string, string> = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
+5 -5
View File
@@ -1,6 +1,6 @@
import { FilteredValueToken, TagToken, HTMLToken, HashToken, QuotedToken, LiquidTagToken, OutputToken, ValueToken, Token, RangeToken, FilterToken, TopLevelToken, PropertyAccessToken, OperatorToken, LiteralToken, IdentifierToken, NumberToken } from '../tokens'
import { OperatorHandler } from '../render/operator'
import { TrieNode, LiteralValue, Trie, createTrie, ellipsis, literalValues, TokenizationError, TYPES, QUOTE, BLANK, NUMBER, SIGN, isWord, isString } from '../util'
import { LiteralValue, Trie, createTrie, ellipsis, literalValues, TokenizationError, TYPES, QUOTE, BLANK, NUMBER, SIGN, isWord, isString } from '../util'
import { Operators, Expression } from '../render'
import { NormalizedFullOptions, defaultOptions } from '../liquid-options'
import { FilterArg } from './filter-arg'
@@ -51,11 +51,11 @@ export class Tokenizer {
return new OperatorToken(this.input, this.p, (this.p = end), this.file)
}
matchTrie<T> (trie: Trie<T>) {
let node: TrieNode<T> = trie
let node: Trie<T> = trie
let i = this.p
let info
while (node[this.input[i]] && i < this.N) {
node = node[this.input[i++]]
let info: Trie<T> | undefined
while ((node as Trie<T>)[this.input[i]] && i < this.N) {
node = (node as Trie<T>)[this.input[i++]] as Trie<T>
if (node['end']) info = node
}
if (!info) return -1
+1 -1
View File
@@ -1,6 +1,6 @@
const rHex = /[\da-fA-F]/
const rOct = /[0-7]/
const escapeChar = {
const escapeChar: Record<string, string> = {
b: '\b',
f: '\f',
n: '\n',
+1 -1
View File
@@ -52,7 +52,7 @@ export default class extends Tag {
const continueKey = 'continue-' + this.variable + '-' + this.collection.getText()
ctx.push(createScope({ continue: ctx.getRegister(continueKey, {}) }))
const hash = yield this.hash.render(ctx)
const hash = (yield this.hash.render(ctx)) as Record<string, any>
ctx.pop()
const modifiers = this.liquid.options.orderedFilterParameters
+14 -12
View File
@@ -3,16 +3,18 @@ import { BlockMode, createScope, Scope } from '../context'
import { Parser } from '../parser'
import { Argument, Arguments, PartialScope } from '../template'
import { isString, isValueToken } from '../util'
import { parseFilePath, renderFilePath } from './render'
import { parseFilePath, renderFilePath, ParsedFileName } from './render'
export default class extends Tag {
private file: ParsedFileName
private currentFile?: string
private withVar?: ValueToken
private hash: Hash
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid, parser: Parser) {
super(token, remainTokens, liquid)
const { tokenizer } = token
this['file'] = parseFilePath(tokenizer, this.liquid, parser)
this['currentFile'] = token.file
this.file = parseFilePath(tokenizer, this.liquid, parser)
this.currentFile = token.file
const begin = tokenizer.p
const withStr = tokenizer.readIdentifier()
@@ -28,7 +30,7 @@ export default class extends Tag {
* render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
const { liquid, hash, withVar } = this
const { renderer } = liquid
const filepath = (yield renderFilePath(this['file'], ctx, liquid)) as string
const filepath = (yield renderFilePath(this.file, ctx, liquid)) as string
assert(filepath, () => `illegal file path "${filepath}"`)
const saved = ctx.saveRegister('blocks', 'blockMode')
@@ -36,7 +38,7 @@ export default class extends Tag {
ctx.setRegister('blockMode', BlockMode.OUTPUT)
const scope = createScope((yield hash.render(ctx)) as Scope)
if (withVar) scope[filepath] = yield evalToken(withVar, ctx)
const templates = (yield liquid._parsePartialFile(filepath, ctx.sync, this['currentFile'])) as Template[]
const templates = (yield liquid._parsePartialFile(filepath, ctx.sync, this.currentFile)) as Template[]
ctx.push(ctx.opts.jekyllInclude ? createScope({ include: scope }) : scope)
yield renderer.renderTemplates(templates, ctx, emitter)
ctx.pop()
@@ -44,14 +46,14 @@ export default class extends Tag {
}
public * children (partials: boolean, sync: boolean): Generator<unknown, Template[]> {
if (partials && isString(this['file'])) {
return (yield this.liquid._parsePartialFile(this['file'], sync, this['currentFile'])) as Template[]
if (partials && isString(this.file)) {
return (yield this.liquid._parsePartialFile(this.file, sync, this.currentFile)) as Template[]
}
return []
}
public partialScope (): PartialScope | undefined {
if (isString(this['file'])) {
if (isString(this.file)) {
let names: Array<string | [string, Argument]>
if (this.liquid.options.jekyllInclude) {
@@ -59,19 +61,19 @@ export default class extends Tag {
} else {
names = Object.keys(this.hash.hash)
if (this.withVar) {
names.push([this['file'], this.withVar])
names.push([this.file, this.withVar])
}
}
return { name: this['file'], isolated: false, scope: names }
return { name: this.file, isolated: false, scope: names }
}
}
public * arguments (): Arguments {
yield * Object.values(this.hash.hash).filter(isValueToken)
if (isValueToken(this['file'])) {
yield this['file']
if (isValueToken(this.file)) {
yield this.file
}
if (isValueToken(this.withVar)) {
+4 -3
View File
@@ -10,10 +10,11 @@ export default class extends Tag {
args: Hash
templates: Template[]
file?: ParsedFileName
private currentFile?: string
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid, parser: Parser) {
super(token, remainTokens, liquid)
this.file = parseFilePath(this.tokenizer, this.liquid, parser)
this['currentFile'] = token.file
this.currentFile = token.file
this.args = new Hash(this.tokenizer, liquid.options.keyValueSeparator)
this.templates = parser.parseTokens(remainTokens)
}
@@ -27,7 +28,7 @@ export default class extends Tag {
}
const filepath = (yield renderFilePath(this.file, ctx, liquid)) as string
assert(filepath, () => `illegal file path "${filepath}"`)
const templates = (yield liquid._parseLayoutFile(filepath, ctx.sync, this['currentFile'])) as Template[]
const templates = (yield liquid._parseLayoutFile(filepath, ctx.sync, this.currentFile)) as Template[]
// render remaining contents and store rendered results
ctx.setRegister('blockMode', BlockMode.STORE)
@@ -48,7 +49,7 @@ export default class extends Tag {
const templates = this.templates.slice()
if (partials && isString(this.file)) {
templates.push(...(yield this.liquid._parsePartialFile(this.file, true, this['currentFile'])) as Template[])
templates.push(...(yield this.liquid._parsePartialFile(this.file, true, this.currentFile)) as Template[])
}
return templates
+29 -23
View File
@@ -1,16 +1,20 @@
import { __assign } from 'tslib'
import { ForloopDrop } from '../drop'
import { isString, isValueToken, toEnumerable } from '../util'
import { TopLevelToken, assert, Liquid, Token, Template, evalQuotedToken, TypeGuards, Tokenizer, evalToken, Hash, Emitter, TagToken, Context, Tag } from '..'
import { TopLevelToken, assert, Liquid, Token, ValueToken, Template, evalQuotedToken, TypeGuards, Tokenizer, evalToken, Hash, Emitter, TagToken, Context, Tag } from '..'
import { Parser } from '../parser'
import { Argument, Arguments, PartialScope } from '../template'
export type ParsedFileName = Template[] | Token | string | undefined
type RenderBinding = { value: ValueToken; alias?: string }
export default class extends Tag {
private file: ParsedFileName
private currentFile?: string
private hash: Hash
private with?: RenderBinding
private forBinding?: RenderBinding
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid, parser: Parser) {
super(token, remainTokens, liquid)
const tokenizer = this.tokenizer
@@ -33,7 +37,9 @@ export default class extends Tag {
if (asStr.content === 'as') alias = tokenizer.readIdentifier()
else tokenizer.p = beforeAs
this[keyword.content] = { value, alias: alias && alias.content }
const binding: RenderBinding = { value, alias: alias && alias.content }
if (keyword.content === 'with') this.with = binding
else this.forBinding = binding
tokenizer.skipBlank()
if (tokenizer.peek() === ',') tokenizer.advance()
continue // matched!
@@ -50,46 +56,46 @@ export default class extends Tag {
}
* render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
const { liquid, hash } = this
const filepath = (yield renderFilePath(this['file'], ctx, liquid)) as string
const filepath = (yield renderFilePath(this.file, ctx, liquid)) as string
assert(filepath, () => `illegal file path "${filepath}"`)
const childCtx = ctx.spawn()
const scope = childCtx.bottom()
__assign(scope, yield hash.render(ctx))
if (this['with']) {
const { value, alias } = this['with']
if (this.with) {
const { value, alias } = this.with
scope[alias || filepath] = yield evalToken(value, ctx)
}
if (this['for']) {
const { value, alias } = this['for']
if (this.forBinding) {
const { value, alias } = this.forBinding
const collection = toEnumerable(yield evalToken(value, ctx))
scope['forloop'] = new ForloopDrop(collection.length, value.getText(), alias)
scope['forloop'] = new ForloopDrop(collection.length, value.getText(), alias as string)
for (const item of collection) {
scope[alias] = item
const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile'])) as Template[]
scope[alias as string] = item
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'])) as Template[]
const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this.currentFile)) as Template[]
yield liquid.renderer.renderTemplates(templates, childCtx, emitter)
}
}
public * children (partials: boolean, sync: boolean): Generator<unknown, Template[]> {
if (partials && isString(this['file'])) {
return (yield this.liquid._parsePartialFile(this['file'], sync, this['currentFile'])) as Template[]
if (partials && isString(this.file)) {
return (yield this.liquid._parsePartialFile(this.file, sync, this.currentFile)) as Template[]
}
return []
}
public partialScope (): PartialScope | undefined {
if (isString(this['file'])) {
if (isString(this.file)) {
const names: Array<string | [string, Argument]> = Object.keys(this.hash.hash)
if (this['with']) {
const { value, alias } = this['with']
if (this.with) {
const { value, alias } = this.with
if (isString(alias)) {
names.push([alias, value])
} else if (isString(this.file)) {
@@ -97,8 +103,8 @@ export default class extends Tag {
}
}
if (this['for']) {
const { value, alias } = this['for']
if (this.forBinding) {
const { value, alias } = this.forBinding
if (isString(alias)) {
names.push([alias, value])
} else if (isString(this.file)) {
@@ -106,7 +112,7 @@ export default class extends Tag {
}
}
return { name: this['file'], isolated: true, scope: names }
return { name: this.file, isolated: true, scope: names }
}
}
@@ -117,15 +123,15 @@ export default class extends Tag {
}
}
if (this['with']) {
const { value } = this['with']
if (this.with) {
const { value } = this.with
if (isValueToken(value)) {
yield value
}
}
if (this['for']) {
const { value } = this['for']
if (this.forBinding) {
const { value } = this.forBinding
if (isValueToken(value)) {
yield value
}
+1 -1
View File
@@ -24,7 +24,7 @@ export class Hash {
}
* render (ctx: Context): Generator<unknown, Record<string, any>, unknown> {
const hash = {}
const hash: Record<string, any> = {}
for (const key of Object.keys(this.hash)) {
hash[key] = this.hash[key] === undefined ? true : yield evalToken(this.hash[key], ctx)
}
+3 -3
View File
@@ -1,10 +1,10 @@
import { Token } from './token'
import { TokenKind } from '../parser'
import { literalValues, LiteralValue } from '../util'
import { literalValues, LiteralValue, LiteralKey } from '../util'
export class LiteralToken extends Token {
public content: LiteralValue
public literal: string
public literal: LiteralKey
public constructor (
public input: string,
public begin: number,
@@ -12,7 +12,7 @@ export class LiteralToken extends Token {
public file?: string
) {
super(TokenKind.Literal, input, begin, end, file)
this.literal = this.getText()
this.literal = this.getText() as LiteralKey
this.content = literalValues[this.literal]
}
}
+5 -4
View File
@@ -32,8 +32,10 @@ export const operatorTypes = {
'or': OperatorType.Binary
}
export type OperatorKey = keyof typeof operatorPrecedences
export class OperatorToken extends Token {
public operator: string
public operator: OperatorKey
public constructor (
public input: string,
public begin: number,
@@ -41,10 +43,9 @@ export class OperatorToken extends Token {
public file?: string
) {
super(TokenKind.Operator, input, begin, end, file)
this.operator = this.getText()
this.operator = this.getText() as OperatorKey
}
getPrecedence () {
const key = this.getText()
return key in operatorPrecedences ? operatorPrecedences[key] : 1
return this.operator in operatorPrecedences ? operatorPrecedences[this.operator] : 1
}
}
+4 -4
View File
@@ -18,10 +18,10 @@ export async function toPromise<T> (val: Generator<unknown, T, unknown> | Promis
if (!isIterator(val)) return val
let value: unknown
let done = false
let next = 'next'
let next: 'next' | 'throw' = 'next'
do {
const state = val[next](value)
done = state.done
done = !!state.done
value = state.value
next = 'next'
try {
@@ -40,10 +40,10 @@ export function toValueSync<T> (val: Generator<unknown, T, unknown> | T): T {
if (!isIterator(val)) return val
let value: any
let done = false
let next = 'next'
let next: 'next' | 'throw' = 'next'
do {
const state = val[next](value)
done = state.done
done = !!state.done
value = state.value
next = 'next'
if (isIterator(value)) {
+1 -1
View File
@@ -29,7 +29,7 @@ export abstract class LiquidError extends Error {
if (this.originalError) this.stack += '\nFrom ' + this.originalError.stack
}
static is (obj: unknown): obj is LiquidError {
return obj?.[TRAIT] === 'LiquidError'
return (obj as Record<string, unknown> | null | undefined)?.[TRAIT] === 'LiquidError'
}
}
+6 -12
View File
@@ -4,22 +4,16 @@ interface TrieInput<T> {
[key: string]: T
}
interface TrieLeafNode<T> {
data: T;
end: true;
needBoundary?: true;
}
export interface Trie<T> {
[key: string]: Trie<T> | TrieLeafNode<T>;
}
export type TrieNode<T> = Trie<T> | TrieLeafNode<T>
export type Trie<T> = {
data?: T
end?: true
needBoundary?: true
} & Record<string, any>
export function createTrie<T = any> (input: TrieInput<T>): Trie<T> {
const trie: Trie<T> = {}
for (const [name, data] of Object.entries(input)) {
let node: Trie<T> | TrieLeafNode<T> = trie
let node = trie
for (let i = 0; i < name.length; i++) {
const c = name[i]
+9 -7
View File
@@ -4,7 +4,7 @@ import type { Limiter } from './limiter'
const rFormat = /%([-_0^#:]+)?(\d+)?([EO])?(.)/
interface FormatOptions {
flags: object;
flags: Record<string, boolean>;
width?: string;
modifier?: string;
memoryLimit?: Pick<Limiter, 'use'>;
@@ -50,7 +50,7 @@ function century (d: LiquidDate) {
}
// default to 0
const padWidths = {
const padWidths: Record<string, number> = {
d: 2,
e: 2,
H: 2,
@@ -77,7 +77,9 @@ function getTimezoneOffset (d: LiquidDate, opts: FormatOptions) {
(opts.flags[':'] ? ':' : '') +
padStart(m, 2, '0')
}
const formatCodes = {
type FormatCodeHandler = (d: LiquidDate, opts: FormatOptions) => unknown
const formatCodes: Record<string, FormatCodeHandler> = {
a: (d: LiquidDate) => d.getShortWeekdayName(),
A: (d: LiquidDate) => d.getLongWeekdayName(),
b: (d: LiquidDate) => d.getShortMonthName(),
@@ -118,8 +120,8 @@ const formatCodes = {
't': () => '\t',
'n': () => '\n',
'%': () => '%'
};
(formatCodes as any).h = formatCodes.b
}
formatCodes.h = formatCodes.b
export function strftime (d: LiquidDate, formatStr: string, memoryLimit?: Pick<Limiter, 'use'>) {
let output = ''
@@ -137,11 +139,11 @@ function format (d: LiquidDate, match: RegExpExecArray, memoryLimit?: Pick<Limit
const [input, flagStr = '', width, modifier, conversion] = match
const convert = formatCodes[conversion]
if (!convert) return input
const flags = {}
const flags: Record<string, boolean> = {}
for (const flag of flagStr) flags[flag] = true
let ret = String(convert(d, { flags, width, modifier, memoryLimit }))
let padChar = padSpaceChars.has(conversion) ? ' ' : '0'
let padWidth = width || padWidths[conversion] || 0
let padWidth = Number(width) || padWidths[conversion] || 0
if (flags['^']) ret = ret.toUpperCase()
else if (flags['#']) ret = changeCase(ret)
if (flags['_']) padChar = ' '
+2 -1
View File
@@ -1,6 +1,7 @@
import { Liquid, Context, isFalsy } from '../../../src'
import { mock, restore } from '../../stub/mockfs'
import { drainStream } from '../../stub/stream'
import { resolve } from 'path'
describe('Liquid', function () {
describe('#plugin()', function () {
@@ -144,7 +145,7 @@ describe('Liquid', function () {
})
it('should fallback to require.resolve in Node.js', async function () {
const engine = new Liquid({
root: [process.cwd()],
root: [resolve(__dirname, '../../..')],
extname: '.html'
})
const tpls = await engine.parseFileSync('jest')
+1 -3
View File
@@ -9,9 +9,7 @@
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"downlevelIteration": true,
"strict": true,
"suppressImplicitAnyIndexErrors": true
"strict": true
},
"all": true,
"exclude": [ "node_modules", "dist", "demo", "test" ]