Skip to content

fix(twig): count {# #} comments - #798

Merged
boyter merged 1 commit into
boyter:masterfrom
MaxFreedomPollard:fix/twig-comments
Oct 25, 2026
Merged

boyter merged 1 commit into
boyter:masterfrom
MaxFreedomPollard:fix/twig-comments

Conversation

@MaxFreedomPollard

Copy link
Copy Markdown
Contributor

What's broken

Twig writes a comment as {# ... #} (Templates, Comments), and the default tag_comment in Twig's src/Lexer.php is ['{#', '#}']. A comment runs from {# to the first #} after it, across lines, and comments do not nest.

The Twig Template entry in languages.json (line 8234) declares no comment at all:

"line_comment": [],
"multi_line": [],

so scc never reports a comment in a Twig file. Every line of a {# #} comment counts as code, including the docblock that opens 495 of Drupal core's 707 templates, and a tag commented out inside {# #} still adds to complexity.

Repro

{#
/**
 * @file
 * Displays the title of a page.
 */
#}
{# The heading is left out when there is no title. #}
{% if title %}
  <h1>{{ title }}</h1>
{% endif %}
{# {% if subtitle %}<h2>{{ subtitle }}</h2>{% endif %} #}
Lines Code Comments Complexity
master 11 11 0 2
this branch 11 3 8 1

Across the 707 .twig files in Drupal core (cf863a1), 10,231 of the 22,518 lines move from code to comments, and code drops from 22,061 to 11,836.

The fix

"multi_line": [["{#", "#}"]] for Twig Template, the pair the Jinja entry already uses, plus the matching entry in processor/constants.go. The #} closer also ends the -#} and ~#} whitespace control forms, and leaving nestedmultiline off matches Twig. LANGUAGES.md lists names and extensions only, so it does not change.

One limit: scc can't tell {% verbatim %} text or a string literal inside a tag apart, so a {# in one of those (a Handlebars {{#each}} kept verbatim, say) opens a comment. Everywhere else Twig's own lexer reads {# as a comment opener too, so the two agree.

Verification

processor/workers_twig_comment_test.go adds four CountStats tests: comments on their own line (including the {#- -#} and {#~ ~#} forms), a multi-line docblock, a commented-out {% if %} that no longer adds complexity, and a guard where a comment shares a line with markup and the line stays code. The guard passes on master too.

There is no Go toolchain on the machine I made this on, so I haven't run go test or go generate myself. CI will. I worked out the expected counts with a line-for-line Python port of countLoopGeneric and the state functions it calls. That port reproduces the counts asserted by 69 existing CountStats tests in processor/. By the port, the three behaviour tests fail on master (Expected 4 code got 7, Expected 3 code got 12, Expected 1 code got 6) and all four pass on this branch. The Drupal figures above come from the same port. The processor/constants.go hunk is byte for byte what a Python port of scripts/include.go produces, and that port reproduces master's constants.go exactly.

I explicitly licence this contribution under the MIT licence.

🤖 Generated with Claude Code

Twig writes a comment as {# ... #}, and its lexer drops everything from
the {# up to the first #} after it, newlines included, without nesting.
The Twig Template entry in languages.json declared no comment at all, so
every line of a Twig comment counted as code, including the docblock
that opens most Drupal templates, and a tag commented out inside one
still added to complexity.

Declare {# #} as Twig's block comment, the same pair the Jinja entry
uses, and update processor/constants.go to match. The #} closer also
ends the -#} and ~#} whitespace control forms. LANGUAGES.md lists names
and extensions only, so it does not change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@pr-insights pr-insights Bot added M/complexity Normal or medium complexity M/size Normal or medium sized change labels Oct 22, 2026

@by-ae by-ae left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mentioned you couldn't run Go locally, so I ran it on this branch (69b8aea, Go 1.27.3):

  • go test ./... passes (root, cmd/badges, processor).
  • go generate ./... leaves the tree clean, so the processor/constants.go hunk matches what scripts/include.go generates from languages.json.
  • With the parent commit's constants.go swapped back in, the new tests fail exactly as your port predicted: Expected 4 code got 7, Expected 3 code got 12, Expected 1 code got 6 (and complexity 0 got 1). TestCountStatsTwigCommentNextToMarkupIsCode passes both ways, as you said.

On the limitation you called out (no quotes for Twig, so a {# inside a string opens a comment), here is a concrete example of what it costs:

{{ "{#" ~ id }}
<p>{{ a }}</p>
{% if b %}x{% endif %}
{# c #}
Twig Template   1   4   0   3   1   0

Line 1 stays code, but lines 2 and 3 are counted as comment, and the {% if complexity is lost. Going by how Twig's own lexer reads the file, it should be 3 code, 1 comment, complexity 1. I agree with leaving quotes empty: adding "/' would make every apostrophe in the HTML text open a string, which is far worse. Two optional suggestions: keep this case as a short known-limitation test, so a later change to quote handling shows up as a diff, and add a one-line note in the PR or commit message saying Twig strings are deliberately not modelled.

@boyter
boyter merged commit 491e280 into boyter:master Oct 25, 2026
4 checks passed
@boyter

boyter commented Oct 25, 2026

Copy link
Copy Markdown
Owner

Cheers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

M/complexity Normal or medium complexity M/size Normal or medium sized change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants