Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/__tests__/templates/bindingSourcesAndTokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ describe('containsTokens', () => {
expect(containsTokens('Hello {site.name}')).toBe(true)
})

it('returns true even for escaped { (the parser will resolve it correctly)', () => {
// Cheap check is conservative — false positives are fine because the
// parser short-circuits on no real tokens.
expect(containsTokens('Literal \\{not-a-token}')).toBe(false)
it('returns true even for escaped { (the parser resolves it to a literal)', () => {
// Cheap check is conservative — an escaped `\{` still needs a parse so
// the backslash is stripped; a false positive only costs one parse.
expect(containsTokens('Literal \\{not-a-token}')).toBe(true)
})
})

Expand Down Expand Up @@ -303,6 +303,14 @@ describe('interpolateTokens', () => {
const c = ctx({ entryStack: [] })
expect(interpolateTokens('Welcome, {currentEntry.title}!', c)).toBe('Welcome, !')
})

it('strips the backslash of an escaped `\\{` and emits a literal `{`', () => {
expect(interpolateTokens('Literal \\{not-a-token}', ctx())).toBe('Literal {not-a-token}')
})

it('strips the escape even when the string carries no real token', () => {
expect(interpolateTokens('\\{just-braces}', ctx())).toBe('{just-braces}')
})
})

// ---------------------------------------------------------------------------
Expand Down
11 changes: 4 additions & 7 deletions src/core/templates/tokenInterpolation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,12 @@ export function bindingToToken(source: DynamicPropBinding['source'], fieldPath:
* string-typed props in a published page contain no token syntax at all
* (button labels, html tags, etc.). Detecting that case in O(n) without
* allocations short-circuits a full parse + concat per render.
*
* Any `{` warrants a parse — including an escaped `\{`, which the parser
* still processes to strip the backslash and emit a literal `{`.
*/
export function containsTokens(value: string): boolean {
// Need at least `{x.y}` — 5 chars minimum. Looking for an unescaped `{`.
if (value.length < 5) return false
for (let i = 0; i < value.length; i++) {
const c = value[i]
if (c === '{' && (i === 0 || value[i - 1] !== '\\')) return true
}
return false
return value.includes('{')
}

// ---------------------------------------------------------------------------
Expand Down