fix(groovy): func_start no longer mistakes MarkupBuilder/DSL calls for declarations - #2597
Merged
squid-protocol merged 3 commits intoAug 31, 2026
Merged
Conversation
…r declarations Closes #2530. func_start's zero-prefix branch (needed to match real bare constructors, `MyClass(String arg) {`) was syntactically indistinguishable from Groovy's MarkupBuilder/NodeBuilder DSL idiom -- a method call with a named-argument map plus a trailing closure (`div(class: "x") { ... }`, `button(name: "clear", type: "submit") { ... }`). Pervasive in Jenkins's own `.groovy` view templates and Jenkins Pipeline DSL steps. Fix: a real Groovy declaration's parameter list can never contain a `key:` token (that syntax is call-site named-argument sugar only), so branch 2 now rejects a candidate whose parenthesized argument list contains an identifier immediately followed by `:` with no intervening whitespace -- distinguishing it from a ternary's `cond ? a : b`, which conventionally has a space before the colon. Verified via a full corpus differential scan (galaxyscope --db-only, 201 files): 44 of the 57 misdetections in jenkins_view_groovy/ resolved, zero removals anywhere else in the corpus -- no legitimate bare constructor lost recall, including edge cases like a Map-literal default (`= [:]`) or a ternary default value. ReDoS-scaling probed (linear to n=16000). 13 residual FPs remain, out of scope per the issue: single-positional-arg DSL calls (`stage('Build') { }`, `node('x') { }`, `dir(x) { }`) have no `key:` token to key off of -- same class the issue explicitly deferred (a curated builder-tag-name denylist is fragile/incomplete by construction). Fixing the button(...) false positive also unmasked one new, unrelated func_start defect in a different branch (a paren-less call, `raw _("Dismiss")`, previously silently absorbed as "nested inside button") -- filed separately as #2558 rather than bundled in here. Golden masters re-blessed (both precision modes): 43 net diff (44 direct removals - 1 new #2558 surfacing), rest is the standard topological/ aggregate ripple class already documented in CLAUDE.md's Differential Scan protocol. Verified with a full uncapped diff categorization: zero unexplained/off-target changes anywhere in the 201-file corpus. Also updates docs/language_status/groovy.md, manual_verification.json, and regenerates the tri-comparison chart/ledger to reflect the corrected groovy function precision (661/718 -> 661/675, 92.1% -> 97.9%). Verification: full groovy extraction gauntlet + strict suite (150 passed, new regression test added), cross-language strict registry (21 passed), full pytest suite (7154 passed), audit_check.py clean, tree_sitter_accuracy_audit.py --all --ci clean (groovy correctly excluded as gg_only per #2532), crucible_check.py both modes PASS after re-bless. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…start-dsl-builder-calls # Conflicts: # docs/self_scan/tri_comparison_ledger.json
Resolves the merge conflict in tri_comparison_ledger.json from main's own auto-update-tri-comparison-data bot commit (#2553) landing while this branch's crucible verification was running. Took main's version as the base and regenerated via tri_comparison_chart.py --all --write on top of the merged code state, so the output reflects both main's other-language refreshes and this branch's groovy fix together. Re-verified clean: audit_check.py, full pytest suite (7154 passed), crucible_check.py both modes PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Contributor
squid-protocol
deleted the
fix/2530-groovy-func-start-dsl-builder-calls
branch
August 31, 2026 17:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2530.
Root cause
func_start's zero-prefix branch (needed to match real bare constructors,MyClass(String arg) {) is syntactically indistinguishable from Groovy'sMarkupBuilder/NodeBuilder DSL idiom — a method call with a named-argument map plus
a trailing closure (
div(class: "x") { ... },button(name: "clear", type: "submit") { ... }). Pervasive in Jenkins's own.groovyview templates (whichreplaced Jelly) and Jenkins Pipeline DSL steps.
Fix
A real Groovy declaration's parameter list can never contain a
key:token (thatsyntax is call-site named-argument sugar only), so branch 2 now rejects a
candidate whose parenthesized argument list contains an identifier immediately
followed by
:with no intervening whitespace — distinguishing it from aternary's
cond ? a : b, which conventionally has a space before the colon.Scope (per the issue's own framing)
The issue explicitly asked to investigate "whether branch 2 can be tightened to
exclude the named-argument-map-first-parameter shape... without also rejecting
legitimate bare constructors" and to "re-run the recall audit against the full
crucible corpus." Did both:
galaxyscope --db-only, 201 files, beforevs. after): 44 of the 57 misdetections in
jenkins_view_groovy/resolved, zeroremovals anywhere else in the corpus — no legitimate bare constructor lost
recall, including edge cases I specifically probed for (a Map-literal default
= [:], a ternary default value).plain paren and a colon-heavy pathological input).
calls (
stage('Build') { },node('x') { },dir(x) { }) have nokey:tokento key off of — the issue itself flagged this as needing "a curated
builder-tag-name denylist" that's "fragile and incomplete by construction," so I
didn't try to force it into this fix.
A second, unrelated defect this unmasked
Fixing the
button(...)false positive in one file also unmasked a second,independent
func_startbug:raw _("Dismiss")(a paren-less call, Groovy's_()i18n-helper convention) was already being misdetected by branch 1 (a completely
different mechanism — treating the bare identifier
rawas a modifier/typeprefix), but was silently absorbed as "nested inside
button" untilbuttonstopped being wrongly detected as an enclosing function. Confirmed via
git stashthat this raw-regex match pre-dates this PR. Filed separately as
#2558 rather than
bundled in here — different branch, different root cause, same reasoning the
original issue used to defer itself from #2532.
Docs / data updated
docs/language_status/groovy.md§5/§9 — corrected FP counts, cross-referencedgroovy func_start: paren-less builder call (
raw _("x")) misdetected as function when no longer masked by a nested FP #2558, noted the residual.docs/self_scan/manual_verification.json— groovyfunction.total718 → 675(
verifiedstays 661, all previously-correct matches unaffected).docs/self_scan/tri_comparison_chart.svg/tri_comparison_ledger.json—regenerated per repo convention (
--all --write), groovy precision badge nowreads
661/675**instead of661/718**.Verification
tests/extraction/languages/test_groovy.py(invalid-match cases,plus a positive case for the Map-literal-default edge case) and
test_groovy_strict.py(dedicated regression test covering the DSL-callrejections and the ternary/map-literal preservation).
test_groovy.py+test_groovy_strict.py: 150 passed.tests/core_engine/test_language_standards_strict.py: 21 passed.pytest tests/: 7154 passed, 2 skipped, 9 xfailed, 3 xpassed (unrelatedpre-existing).
tests/tools/audit_check.py: clean (ruff/mypy/dead-key/ast-accuracy).tests/tools/tree_sitter_accuracy_audit.py --all --ci: clean (groovy correctlystays excluded as
gg_only, per fix(groovy): tri-comparison manual verification -- 3 real engine defects fixed #2532).tests/tools/crucible_check.py(bothfull_precisionandzero_dependencymodes, full corpus): re-blessed after confirming every one of the 631 diffs
traces to either the direct fix (43 net, isolated to
jenkins_view_groovy/) orthe standard topological/aggregate ripple class CLAUDE.md's Differential Scan
protocol pre-approves (588) — zero unexplained/off-target diffs, verified via a
full uncapped
golden_diff.deep_compare()categorization, not just thepytest-truncated summary. Both modes
PASSclean after re-bless.🤖 Generated with Claude Code