fix: allow string literals contain delimiters, fixes #288

This commit is contained in:
harttle
2021-01-24 12:49:19 +08:00
parent 5e7598ce45
commit 9c40da7369
3 changed files with 157 additions and 117 deletions
+16 -5
View File
@@ -126,11 +126,10 @@ export class Tokenizer {
return new HTMLToken(this.input, begin, this.p, this.file) return new HTMLToken(this.input, begin, this.p, this.file)
} }
readTagToken (options: NormalizedFullOptions): TagToken { readTagToken (options: NormalizedFullOptions = defaultOptions): TagToken {
const { file, input } = this const { file, input } = this
const { tagDelimiterRight } = options
const begin = this.p const begin = this.p
if (this.readTo(tagDelimiterRight) === -1) { if (this.readToDelimiter(options.tagDelimiterRight) === -1) {
throw this.mkError(`tag ${this.snapshot(begin)} not closed`, begin) throw this.mkError(`tag ${this.snapshot(begin)} not closed`, begin)
} }
const token = new TagToken(input, begin, this.p, options, file) const token = new TagToken(input, begin, this.p, options, file)
@@ -138,11 +137,23 @@ export class Tokenizer {
return token return token
} }
readOutputToken (options: NormalizedFullOptions): OutputToken { readToDelimiter (delimiter: string) {
while (this.p < this.N) {
if ((this.peekType() & QUOTE)) {
this.readQuoted()
continue
}
++this.p
if (this.rmatch(delimiter)) return this.p
}
return -1
}
readOutputToken (options: NormalizedFullOptions = defaultOptions): OutputToken {
const { file, input } = this const { file, input } = this
const { outputDelimiterRight } = options const { outputDelimiterRight } = options
const begin = this.p const begin = this.p
if (this.readTo(outputDelimiterRight) === -1) { if (this.readToDelimiter(outputDelimiterRight) === -1) {
throw this.mkError(`output ${this.snapshot(begin)} not closed`, begin) throw this.mkError(`output ${this.snapshot(begin)} not closed`, begin)
} }
return new OutputToken(input, begin, this.p, options, file) return new OutputToken(input, begin, this.p, options, file)
+6 -1
View File
@@ -56,7 +56,7 @@ describe('Issues', function () {
}) })
it('#277 Passing liquid in FilterImpl', () => { it('#277 Passing liquid in FilterImpl', () => {
const engine = new Liquid() const engine = new Liquid()
engine.registerFilter('render', function (template: string, name: string) { engine.registerFilter('render', function (this: any, template: string, name: string) {
return this.liquid.parseAndRenderSync(decodeURIComponent(template), { name }) return this.liquid.parseAndRenderSync(decodeURIComponent(template), { name })
}) })
const html = engine.parseAndRenderSync( const html = engine.parseAndRenderSync(
@@ -65,4 +65,9 @@ describe('Issues', function () {
) )
expect(html).to.equal('hello foo') expect(html).to.equal('hello foo')
}) })
it('#288 Unexpected behavior when string literals contain }}', async () => {
const engine = new Liquid()
const html = await engine.parseAndRender(`{{ '{{' }}{{ '}}' }}`)
expect(html).to.equal('{{}}')
})
}) })
+25 -1
View File
@@ -10,7 +10,7 @@ import { QuotedToken } from '../../../src/tokens/quoted-token'
import { OutputToken } from '../../../src/tokens/output-token' import { OutputToken } from '../../../src/tokens/output-token'
import { HTMLToken } from '../../../src/tokens/html-token' import { HTMLToken } from '../../../src/tokens/html-token'
describe('Tokenize', function () { describe('Tokenizer', function () {
it('should read quoted', () => { it('should read quoted', () => {
expect(new Tokenizer('"foo" ff').readQuoted()!.getText()).to.equal('"foo"') expect(new Tokenizer('"foo" ff').readQuoted()!.getText()).to.equal('"foo"')
expect(new Tokenizer(' "foo"ff').readQuoted()!.getText()).to.equal('"foo"') expect(new Tokenizer(' "foo"ff').readQuoted()!.getText()).to.equal('"foo"')
@@ -84,6 +84,7 @@ describe('Tokenize', function () {
expect(rols.name.content).to.equal('rows') expect(rols.name.content).to.equal('rows')
expect(rols.value!.getText()).to.equal('data["rows"]') expect(rols.value!.getText()).to.equal('data["rows"]')
}) })
describe('#readTopLevelTokens()', () => {
it('should read HTML token', function () { it('should read HTML token', function () {
const html = '<html><body><p>Lorem Ipsum</p></body></html>' const html = '<html><body><p>Lorem Ipsum</p></body></html>'
const tokenizer = new Tokenizer(html) const tokenizer = new Tokenizer(html)
@@ -204,6 +205,29 @@ describe('Tokenize', function () {
const tokenizer = new Tokenizer('{{name}') const tokenizer = new Tokenizer('{{name}')
expect(() => tokenizer.readTopLevelTokens()).to.throw(/output "{{name}" not closed/) expect(() => tokenizer.readTopLevelTokens()).to.throw(/output "{{name}" not closed/)
}) })
})
describe('#readTagToken()', () => {
it('should skip quoted delimiters', function () {
const html = '{% assign a = "%} {% }} {{" %}'
const tokenizer = new Tokenizer(html)
const token = tokenizer.readTagToken()
expect(token).instanceOf(TagToken)
expect(token.name).to.equal('assign')
expect(token.args).to.equal('a = "%} {% }} {{"')
})
})
describe('#readOutputToken()', () => {
it('should skip quoted delimiters', function () {
const html = '{{ "%} {%" | append: "}} {{" }}'
const tokenizer = new Tokenizer(html)
const token = tokenizer.readOutputToken()
console.log(token)
expect(token).instanceOf(OutputToken)
expect(token.content).to.equal('"%} {%" | append: "}} {{"')
})
})
describe('#readRange()', () => { describe('#readRange()', () => {
it('should read `(1..3)`', () => { it('should read `(1..3)`', () => {
const range = new Tokenizer('(1..3)').readRange() const range = new Tokenizer('(1..3)').readRange()