Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Default else block of with causes stray quotes to be injected in <script> tags #14711

@asdofindia

Description

@asdofindia

Hi, I was debugging an issue with geekblog theme thegeeklab/hugo-geekblog#827 and came across what's potentially an issue with hugo itself? The following bug report is generated by Gemini, but after I made it discover the exact issue in this conversation. I've verified the content of this bug report and I stand by it except of course I do not know if this is intended behavior. I'll create a PR with a suggested fix in case this is unintended behavior.

What version of Hugo are you using (hugo version)?

$ hugo version
hugo v0.159.2 linux/amd64 BuildDate=unknown

Does this issue reproduce with the latest release?

Yes. Reproduced with CGO_ENABLED=0 go install github.com/gohugoio/hugo@latest.

Description

When using {{ with partial "name" . }} inside a <script> tag (such as when building JSON-LD schema), if the partial evaluates to an empty string, Hugo renders a literal pair of double-quotes ("") instead of rendering nothing.

This happens because Hugo's internal AST transformer (tplimpl) secretly injects an else block into with partial statements, which collides with Go's html/template context-aware JavaScript escaper.

The Bug Mechanism

In tplimpl/template.go, the function handleWithPartial intercepts with partial calls to manage the partial decorator stack. It automatically appends a fallback else branch that calls _popPartialDecorator:

// From tplimpl/template.go -> handleWithPartial()
// When the partial returns a falsy value, the with block is skipped,
// but we still need to pop the decorator ID from the stack.
// Add a pop call to the else branch.
popElse := popPartialDecoratorElse.CopyList()
// ...
if withNode.ElseList == nil {
    withNode.ElseList = popElse // <-- INJECTED ELSE BLOCK
}

The function that this else block executes is defined in the init() function, and it returns a primitive Go string:

"_popPartialDecorator": func(string) string { return "" }

The Collision:

  1. We call {{ with partial "empty" . }} inside a <script> tag.
  2. The partial returns "". The with condition evaluates as falsy.
  3. The engine falls back to the injected else block, executing _popPartialDecorator, which yields the primitive string "".
  4. Because the engine is inside a <script> tag (stateJS), Go's html/template engine aggressively escapes the output to ensure it forms a valid JS token. It passes the "" through jsValEscaper, which explicitly wraps it in byte quotes, yielding the literal output "".

Note: This bug is bypassed entirely if the partial is evaluated into a variable first ({{ $var := partial ... }}) because Hugo's AST parser only injects the else block if the condition is a direct partial call.

Minimal Reproduction

Create a completely barebones site with just these two files:

layouts/partials/empty.html

{{ return "" }}

layouts/index.html

<script>
  {{- with partial "empty" . }}
  "this-should-be-skipped": "{{ . }}"
  {{- end }}
</script>

Expected Output:
The block evaluates to empty, so nothing should be printed inside the script tag.

<script></script>

Actual Output (The Bug):
The literal string quotes from the injected else block are forced into the template by the JS escaper.

<script>""</script>

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions