mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 04:10:40 -07:00
* feat: static variable analysis * Accept any iterable from `children`, `arguments`, etc. * Test analysis of standard tags * Use `TagToken.tokenizer` instead of creating a new one * Test analysis of netsted tags * Group variables by their root value * Test analysis of nested globals and locals * Analyze included and rendered templates WIP * Use existing tokenizer when constructing `Hash` * Improve test coverage * Analyze variables from `layout` and `block` tags * Test analysis of Jekyll style includes * Handle variables that start with a nested variable * Async analysis * Test non-standard tag end to end * Implement convenience analysis methods on the `Liquid` class * More analysis convenience methods * Accept string or template array * Draft static analysis docs * Deduplicate variables names * Fix isolated scope global variable map * Coerce variables to strings instead of extending String * Private map instead of extending Map * Fix e2e test * Tentatively implement analysis of aliased variables * Fix nested variable segments array * Update docs sidebar
71 lines
2.5 KiB
TypeScript
71 lines
2.5 KiB
TypeScript
import { Context, Emitter, Hash, Liquid, Scope, Tag, TagToken, Template, TopLevelToken, ParseStream, Parser, Arguments, analyzeSync, Variable, StaticAnalysisOptions, StaticAnalysis } from '../..'
|
|
|
|
class MockTag extends Tag {
|
|
private args: Hash
|
|
private templates: Template[]
|
|
|
|
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid, parser: Parser) {
|
|
super(token, remainTokens, liquid)
|
|
this.args = new Hash(token.tokenizer)
|
|
this.templates = []
|
|
|
|
const stream: ParseStream = parser.parseStream(remainTokens)
|
|
.on<TagToken>('tag:endmock', () => { stream.stop() })
|
|
.on('template', (tpl: Template) => this.templates.push(tpl))
|
|
.on('end', () => { throw new Error(`tag ${token.getText()} not closed`) })
|
|
|
|
stream.start()
|
|
}
|
|
|
|
public * render (ctx: Context, emitter: Emitter): Generator<unknown, void, string> {
|
|
const scope = (yield this.args.render(ctx)) as unknown as Scope
|
|
ctx.push(scope)
|
|
yield this.liquid.renderer.renderTemplates(this.templates, ctx, emitter)
|
|
ctx.pop()
|
|
}
|
|
|
|
public * children (): Generator<unknown, Template[]> {
|
|
return this.templates
|
|
}
|
|
|
|
public * arguments (): Arguments {
|
|
// XXX: tokens and type guards are not exported
|
|
yield * Object.values(this.args.hash).filter((el) => el !== undefined) as Arguments
|
|
}
|
|
|
|
public blockScope (): Iterable<string> {
|
|
return Object.keys(this.args.hash)
|
|
}
|
|
}
|
|
|
|
describe('Static analysis', () => {
|
|
it('should report variables from non-standard tags', () => {
|
|
const engine = new Liquid()
|
|
engine.registerTag('mock', MockTag)
|
|
|
|
const template = engine.parse('{% mock a:b x:y %}{{ x }}{{ z }}{% endmock %}')
|
|
const analysis = analyzeSync(template)
|
|
|
|
const b = [new Variable(['b'], { row: 1, col: 11, file: undefined })]
|
|
const x = [new Variable(['x'], { row: 1, col: 22, file: undefined })]
|
|
const y = [new Variable(['y'], { row: 1, col: 15, file: undefined })]
|
|
const z = [new Variable(['z'], { row: 1, col: 29, file: undefined })]
|
|
|
|
expect(analysis).toStrictEqual({
|
|
variables: { b, x, y, z },
|
|
globals: { b, y, z },
|
|
locals: { }
|
|
})
|
|
})
|
|
|
|
it('should export analysis interfaces', () => {
|
|
const engine = new Liquid()
|
|
const template = engine.parse('{% include nothing %}')
|
|
const options: StaticAnalysisOptions = { partials: false }
|
|
const analysis: StaticAnalysis = analyzeSync(template, options)
|
|
const vars: Variable[] = analysis.variables['nothing'] || []
|
|
const v: Variable = vars[0]
|
|
expect(String(v)).toBe('nothing')
|
|
})
|
|
})
|