Skip to content

Lex Lucee's backtick template block - #54

Merged
ghedwards merged 1 commit into
masterfrom
claude/fix-backtick-template-48
Aug 13, 2026
Merged

Lex Lucee's backtick template block#54
ghedwards merged 1 commit into
masterfrom
claude/fix-backtick-template-48

Conversation

@ghedwards

Copy link
Copy Markdown

Closes #48.

name = "World";

Hello #name#

``` ```

The backtick had no lexer rule, so the fences vanished and the HTML between them was parsed as cfscript.

A lexer mode, built like the string modes

OPEN_TEMPLATE pushes InTemplate, CLOSE_TEMPLATE pops, and # pushes HashMode + DefaultMode — the same machinery InDoubleQuotes already uses, so interpolation works inside a template block exactly as it does inside a quoted string.

The visitor is the part that mattered

Grammar alone got a clean parse. It was also completely wrong, and silently:

name = 'World';
name;              <- markup gone, the interpolation leaked out as a bare statement

That's the trap CLAUDE.md describes, which is why the check here is decompiled output rather than the error count.

CFTemplateBlockStatement keeps the body as its source text and writes it back unchanged — it's markup, not cfscript, so modelling it would be inventing structure the parser can't validate. What a consumer actually needs is the interpolations, and those are parsed:

CFTemplateBlockStatement
  content=[\n<h1>Hello #name#</h1>\n<p>You are #user.age# years old</p>\n]
  interpolation: name
  interpolation: user.age

Both reachable via getExpressions() and decomposeExpression(), so a variable used only inside a block still reads as a use — the thing a linter would otherwise get wrong.

Verification

  • 313 cfparser tests, ./gradlew build
  • Differential harness 8 → 7, nothing newly broken
  • CFLint 675 against a clean build
  • Fixture fails with cfml.parsing/src/main stashed

Scope

Lucee-specific, same bucket as the extensions adopted in #34. The issue called it low priority — one corpus case, rare in real code — and that's still true; it's here because the remaining list was short enough to finish.


Generated by Claude Code

``` drops out of cfscript into template markup, the mirror of <cfscript>
dropping the other way. The backtick had no lexer rule at all, so the fences
vanished and the HTML between them was parsed as cfscript.

A lexer mode, built the way the string modes already are: OPEN_TEMPLATE pushes
InTemplate, CLOSE_TEMPLATE pops, and '#' pushes HashMode plus DefaultMode so
interpolation works exactly as it does inside a quoted string.

The body is markup, not cfscript, so CFTemplateBlockStatement keeps it as its
source text and writes it back unchanged. What a consumer needs is the
interpolations, and those are parsed: `#name#` and `#user.age#` come back
through getExpressions() and decomposeExpression(), so a variable used only
inside a block still reads as a use.

Without the visitor the block parsed with zero errors and then disappeared --
the markup gone from the tree, the interpolations leaking out as bare
statements. That is the trap CLAUDE.md describes, and the reason the check here
is the decompiled output rather than the error count.

Differential harness 8 disagreements to 7, nothing newly broken. Verified end to
end -- 313 cfparser tests, ./gradlew build, CFLint's 675.

Closes #48

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GzpZFd4rnE1Yi2sVHAji35
@ghedwards
ghedwards merged commit 7485910 into master Aug 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Backtick-delimited template blocks are not lexed

2 participants