perf: introduce AST to avoid reparse

This commit is contained in:
harttle
2020-03-15 02:51:25 +08:00
committed by Jun Yang
parent 3b58f1c3f6
commit d2d6a38235
96 changed files with 1553 additions and 1168 deletions
@@ -6,11 +6,6 @@ use(chaiAsPromised)
describe('tags/decrement', function () {
const liquid = new Liquid()
it('should throw when variable expression illegal', function () {
const src = '{% decrement / %}{{var}}'
const ctx = {}
return expect(liquid.parseAndRender(src, ctx)).to.be.rejectedWith(/illegal/)
})
it('should decrement undefined variable', async function () {
const src = '{% decrement var %}{% decrement var %}{% decrement var %}'
+6
View File
@@ -46,6 +46,12 @@ describe('tags/for', function () {
.to.be.rejectedWith(/tag .* not closed/)
})
it('should reject when for in not found', function () {
const src = '{%for c alpha%}{{c}}'
return expect(liquid.parseAndRender(src, scope))
.to.be.rejectedWith('illegal tag: {%for c alpha%}, line:1, col:1')
})
it('should reject when inner templates rejected', function () {
const src = '{%for c in alpha%}{%throwingTag%}{%endfor%}'
return expect(liquid.parseAndRender(src, scope))
+16
View File
@@ -83,6 +83,22 @@ describe('tags/include', function () {
const html = await liquid.renderFile('with.html')
return expect(html).to.equal('color:red, shape:rect')
})
it('should ignore if with value not specified', async function () {
mock({
'/with.html': '{% include "color" with, shape: "rect" %}',
'/color.html': 'color:{{color}}, shape:{{shape}}'
})
const html = await liquid.renderFile('with.html')
return expect(html).to.equal('color:, shape:rect')
})
it('should treat with as a valid key', async function () {
mock({
'/with.html': '{% include "color" with: "foo" %}',
'/color.html': 'with:{{with}}'
})
const html = await liquid.renderFile('with.html')
return expect(html).to.equal('with:foo')
})
it('should support include: with as Drop', async function () {
class ColorDrop extends Drop {
public valueOf (): string {
+13 -1
View File
@@ -1,6 +1,9 @@
import { Liquid } from '../../../../src/liquid'
import { expect } from 'chai'
import { expect, use } from 'chai'
import { mock, restore } from '../../../stub/mockfs'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/layout', function () {
let liquid: Liquid
@@ -29,6 +32,15 @@ describe('tags/layout', function () {
expect(e.message).to.match(/illegal argument ""/)
})
})
it('should throw when filename resolved to falsy', function () {
mock({
'/parent.html': '{%layout foo%}'
})
return liquid.renderFile('/parent.html').catch(function (e) {
expect(e.name).to.equal('RenderError')
expect(e.message).to.match(/illegal filename "foo":"undefined"/)
})
})
describe('anonymous block', function () {
it('should handle anonymous block', async function () {
mock({
+25 -1
View File
@@ -121,9 +121,33 @@ describe('tags/render', function () {
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] })
expect(html).to.equal('1: red\n2: green\n')
})
it('should support for <non-array> as', async function () {
mock({
'/index.html': '{% render "item" for "green" as color %}',
'/item.html': '{{forloop.index}}: {{color}}\n'
})
const html = await liquid.renderFile('index.html')
expect(html).to.equal('1: green\n')
})
it('should support for without as', async function () {
mock({
'/index.html': '{% render "item" for colors %}',
'/item.html': '{{forloop.index}}: {{color}}\n'
})
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] })
expect(html).to.equal('1: \n2: \n')
})
it('should support for...as with other parameters', async function () {
mock({
'/index.html': '{% render "item" for colors as color with ".\n" as tail, sep: ". "%}',
'/index.html': '{% render "item" for colors as color with ".\n" as tail sep: ". "%}',
'/item.html': '{{forloop.index}}{{sep}}{{color}}{{tail}}'
})
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] })
expect(html).to.equal('1. red.\n2. green.\n')
})
it('should support for...as with other parameters (comma separated)', async function () {
mock({
'/index.html': '{% render "item" for colors as color, with ".\n" as tail, sep: ". "%}',
'/item.html': '{{forloop.index}}{{sep}}{{color}}{{tail}}'
})
const html = await liquid.renderFile('index.html', { colors: ['red', 'green'] })
+10 -1
View File
@@ -1,5 +1,8 @@
import { Liquid } from '../../../../src/liquid'
import { expect } from 'chai'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised)
describe('tags/tablerow', function () {
const liquid = new Liquid()
@@ -50,6 +53,12 @@ describe('tags/tablerow', function () {
.to.be.rejectedWith(/tag .* not closed/)
})
it('should throw when x in y not found', function () {
const src = '{% tablerow i (1..3) %}{{ i }}'
return expect(liquid.parseAndRender(src))
.to.be.rejectedWith('illegal tag: {% tablerow i (1..3) %}, line:1, col:1')
})
it('should support tablerow with range', async function () {
const src = '{% tablerow i in (1..5) cols:2 %}{{ i }}{% endtablerow %}'
const dst =
+1 -1
View File
@@ -93,7 +93,7 @@ describe('Liquid', function () {
})
const tpls = await engine.getTemplate('mocha')
expect(tpls.length).to.gte(1)
expect(tpls[0].token.raw).to.contain('module.exports')
expect(tpls[0].token.getText()).to.contain('module.exports')
})
})
describe('#evalValue', function () {
+1 -1
View File
@@ -21,7 +21,7 @@ describe('liquid#registerTag()', function () {
it('should have access to ctx in render()', async () => {
const liquid = new Liquid()
liquid.registerTag('dynamic-string', {
render: async (ctx) => ctx.get('c')
render: async (ctx) => ctx.get(['c'])
})
const html = await liquid.parseAndRender(`A{% dynamic-string %}C`, {
c: 'B'
@@ -1,7 +1,7 @@
import { normalize } from '../../../src/liquid-options'
import { expect } from 'chai'
describe('LiquidOptions', function () {
describe('LiquidOptions#root', function () {
describe('#normalize ()', function () {
it('should normalize string typed root array', function () {
const options = normalize({ root: 'foo' })
+1 -1
View File
@@ -45,7 +45,7 @@ describe('LiquidOptions#trimming', function () {
const html = await engine.parseAndRender(src, ctx)
return expect(html).to.equal('aharttle')
})
it('should respect to greedy:false by default', async function () {
it('should allow greedy:false', async function () {
const engine = new Liquid({ greedy: false } as any)
const html = await engine.parseAndRender(src, ctx)
return expect(html).to.equal('\n a \nharttle ')
+450
View File
@@ -0,0 +1,450 @@
import { expect } from 'chai'
import { Liquid } from '../../../src/liquid'
const liquid = new Liquid()
const cases = [
{
text: `
<div>
<p>
{{ 'John' }}
</p>
</div>
`,
expected: `
<div>
<p>
John
</p>
</div>
`
}, {
text: `
<div>
<p>
{{- 'John' -}}
</p>
</div>
`,
expected: `
<div>
<p>John</p>
</div>
`
}, {
text: `
<div>
<p>
{%- if true -%}
yes
{%- endif -%}
</p>
</div>
`,
expected: `
<div>
<p>yes</p>
</div>
`
}, {
text: `
<div>
<p>
{% if true %}
yes
{% endif %}
</p>
</div>
`,
expected: `
<div>
<p>
yes
</p>
</div>
`
}, {
text: `
<div>
<p>
{% if false %}
no
{% endif %}
</p>
</div>
`,
expected: `
<div>
<p>
</p>
</div>
`
}, {
text: '<p>{{- \'John\' -}}</p>',
expected: '<p>John</p>'
}, {
text: '<p>{%- if true -%}yes{%- endif -%}</p>',
expected: '<p>yes</p>'
}, {
text: '<p>{%- if false -%}no{%- endif -%}</p>',
expected: '<p></p>'
}, {
text: '<p> {%- if true %} yes {% endif -%} </p>',
expected: '<p> yes </p>'
}, {
text: '<p> {%- if false %} no {% endif -%} </p>',
expected: '<p></p>'
}, {
text: '<p> {% if true -%} yes {%- endif %} </p>',
expected: '<p> yes </p>'
}, {
text: '<p> {% if false -%} no {%- endif %} </p>',
expected: '<p> </p>'
}, {
text: '<p> {% if true -%} yes {% endif -%} </p>',
expected: '<p> yes </p>'
}, {
text: '<p> {% if false -%} no {% endif -%} </p>',
expected: '<p> </p>'
}, {
text: '<p> {%- if true %} yes {%- endif %} </p>',
expected: '<p> yes </p>'
}, {
text: '<p> {%- if false %} no {%- endif %} </p>',
expected: '<p> </p>'
}, {
text: `
<div>
<p>
{{- 'John' }}
</p>
</div>
`,
expected: `
<div>
<p>John
</p>
</div>
`
}, {
text: `
<div>
<p>
{%- if true %}
yes
{%- endif %}
</p>
</div>
`,
expected: `
<div>
<p>
yes
</p>
</div>
`
}, {
text: `
<div>
<p>
{%- if false %}
no
{%- endif %}
</p>
</div>
`,
expected: `
<div>
<p>
</p>
</div>
`
}, {
text: `
<div>
<p>
{{ 'John' -}}
</p>
</div>
`,
expected: `
<div>
<p>
John</p>
</div>
`
}, {
text: `
<div>
<p>
{% if true -%}
yes
{% endif -%}
</p>
</div>
`,
expected: `
<div>
<p>
yes
</p>
</div>
`
}, {
text: `
<div>
<p>
{% if false -%}
no
{% endif -%}
</p>
</div>
`,
expected: `
<div>
<p>
</p>
</div>
`
}, {
text: `
<div>
<p>
{%- if true %}
yes
{% endif -%}
</p>
</div>
`,
expected: `
<div>
<p>
yes
</p>
</div>
`
}, {
text: `
<div>
<p>
{%- if false %}
no
{% endif -%}
</p>
</div>
`,
expected: `
<div>
<p></p>
</div>
`
}, {
text: `
<div>
<p>
{% if true -%}
yes
{%- endif %}
</p>
</div>
`,
expected: `
<div>
<p>
yes
</p>
</div>
`
}, {
text: `
<div>
<p>
{% if false -%}
no
{%- endif %}
</p>
</div>
`,
expected: `
<div>
<p>
</p>
</div>
`
}, {
text: `
<div>
<p>
{{- 'John' -}}
</p>
</div>
`,
expected: `
<div>
<p>John</p>
</div>
`
}, {
text: `
<div>
<p>
{%- if true -%}
yes
{%- endif -%}
</p>
</div>
`,
expected: `
<div>
<p>yes</p>
</div>
`
}, {
text: `
<div>
<p>
{%- if false -%}
no
{%- endif -%}
</p>
</div>
`,
expected: `
<div>
<p></p>
</div>
`
}, {
text: `
<div>
<p>
{{- 'John' -}},
{{- '30' -}}
</p>
</div>
`,
expected: `
<div>
<p>John,30</p>
</div>
`
}, {
text: `
<div>
<p>
{%- if true -%}
yes
{%- endif -%}
</p>
</div>
`,
expected: `
<div>
<p>yes</p>
</div>
`
}, {
text: `
<div>
<p>
{%- if false -%}
no
{%- endif -%}
</p>
</div>
`,
expected: `
<div>
<p></p>
</div>
`
}, {
text: `
<div>
<p>
{{- 'John' -}}
{{- '30' -}}
</p>
<b>
{{ 'John' -}}
{{- '30' }}
</b>
<i>
{{- 'John' }}
{{ '30' -}}
</i>
</div>
`,
expected: `
<div>
<p>John30</p>
<b>
John30
</b>
<i>John
30</i>
</div>
`
}, {
text: `
<div>
{%- if true -%}
{%- if true -%}
<p>
{{- 'John' -}}
</p>
{%- endif -%}
{%- endif -%}
</div>
`,
expected: `
<div><p>John</p></div>
`
}, {
text: `
<div>
{% raw %}
{%- if true -%}
<p>
{{- 'John' -}}
</p>
{%- endif -%}
{% endraw %}
</div>
`,
expected: `
<div>
{%- if true -%}
<p>
{{- 'John' -}}
</p>
{%- endif -%}
</div>
`
}
]
describe('Whitespace Control', function () {
cases.forEach(item => it(
item.text,
async () => {
const html = await liquid.parseAndRender(item.text)
expect(html).to.equal(item.expected)
}
))
})
+3 -20
View File
@@ -41,11 +41,6 @@ describe('error', function () {
const err = await expect(engine.parseAndRender(html)).be.rejected
expect(err.token.input).to.equal(html)
})
it('should contain line number in err.token.line', async function () {
const err = await expect(engine.parseAndRender('1\n2\n{% . a %}\n4')).be.rejected
expect(err.name).to.equal('TokenizationError')
expect(err.token.line).to.equal(3)
})
it('should contain stack in err.stack', async function () {
const err = await expect(engine.parseAndRender('{% . a %}')).be.rejected
expect(err.message).to.contain('illegal tag syntax')
@@ -58,11 +53,11 @@ describe('error', function () {
expect(err.stack).to.not.contain('at Object.parse')
})
})
it('should throw error with line and pos if tag unmatched', async function () {
it('should throw error with [line, col] if tag unmatched', async function () {
const err = await expect(engine.parseAndRender('1\n2\nfoo{% assign a = 4 }\n4')).be.rejected
console.log(err.stack)
expect(err.name).to.equal('TokenizationError')
expect(err.token.line).to.equal(3)
expect(err.token.col).to.equal(4)
expect(err.message).to.equal('tag "{% assign a =..." not closed, line:3, col:4')
})
})
@@ -175,12 +170,6 @@ describe('error', function () {
expect(err.stack).to.contain(message.join('\n'))
expect(err.name).to.equal('RenderError')
})
it('should contain line number in err.token.line', async function () {
const src = '1\n2\n{{1|throwingFilter}}\n4'
const err = await expect(engine.parseAndRender(src)).be.rejected
expect(err.token.line).to.equal(3)
expect(err.name).to.equal('RenderError')
})
it('should contain stack in err.stack', async function () {
const err = await expect(engine.parseAndRender('{%rejectingTag%}')).be.rejected
expect(err.message).to.contain('intended render reject')
@@ -256,12 +245,6 @@ describe('error', function () {
expect(err.stack).to.contain(message.join('\n'))
})
it('should contain line number in err.token.line', async function () {
const html = '<html>\n<head>\n\n{% raw %}\n\n'
const err = await expect(engine.parseAndRender(html)).be.rejected
expect(err.token.line).to.equal(4)
})
it('should contain stack in err.stack', async function () {
const err = await expect(engine.parseAndRender('{% -a %}')).be.rejected
expect(err.stack).to.contain('ParseError: tag "-a" not found')