fix(typescript/javascript): stop dropping bodyless overload signatures - #2284
Merged
Conversation
TypeScript allows multiple bodyless function-overload SIGNATURES sharing a name, followed by one real body-bearing implementation. func_start's regex correctly matched every overload (confirmed via struct_func_start's raw signal count), but detector.py's terminator- scanning loop (the #1629 brace-less/expression-bodied arrow scanner, gated to typescript/javascript only) silently dropped any match that never found a real terminator in its search window -- exactly the shape of a bodyless overload signature, whose own return-type expression can contain literal `{}`/`=>` tokens that aren't a real body or terminator at all (fp-ts's pipeable.ts: `: {}) &` empty- object-type intersection members). Three changes, all scoped to the typescript/javascript branch: 1. When the scan reaches a `{`, check whether it's preceded (skipping whitespace) by a type-level operator (`&|:?=`) -- if so, it's a type literal's own brace, not a real body opener; skip past its matching close and keep scanning for the real terminator. 2. When "no terminator was ever found" (previously: drop the match entirely), give it a real function_data row bounded by the next match instead -- but ONLY when a genuine parameter list (`(`, optionally after `<generics>`) immediately follows the name. This guard is required: without it, a parameter's own function-type annotation or a type-annotated variable declaration (Flow's `callback: A => T,`, `const task: Task = ...`) also lacks a findable terminator and would otherwise be misattributed a bogus function_data row instead of correctly finding nothing -- confirmed via a real regression in react/ReactFlightServer.js during verification of an earlier draft of this fix (Component/Task/ callback/getAsyncIterator, all real Flow type-level fields, briefly became phantom matches without this guard). 3. Loosen the min-2-lines-of-body filter to also allow single-line bodies for typescript/javascript, since a recovered bodyless overload's "body" (bounded by the next match) can be very short. Verified via the full 23-file typescript + javascript corpus diff against tree-sitter: - fp-ts/pipeable.ts: struct_func_start and function_count now match exactly (224/224, was 224/217) -- all 8 `pipeable` overloads recovered. - typescript missing-function count: 131 -> 123 (this fix's own isolated contribution; the sibling #2276/#2277/#2279 PRs cover the rest of the corpus's gaps). - javascript: zero missing, zero new false-positive/phantom matches -- every new "extra" entry (vs. tree-sitter) traced to a real function in an already Flow-broken file tree-sitter simply can't parse (same documented class as the pre-existing 198-entry baseline), not a regression. - Full typescript/javascript/detector pytest suite (463 tests) plus a cross-language sanity spot-check (java/csharp/python/rust, 262 tests) all pass. - ReDoS pathological-input timing probe -- no slowdown. - ruff/mypy -- no new findings beyond baseline. Fixes #2278. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Contributor
Two follow-up fixes to the same-day #2278 diff, found during its own golden-master/tree-sitter-accuracy verification pass (not shipped in the first commit): 1. Reverted the second `break` -> `pos += 2; continue` change (the "belongs to an annotation, not an assignment" case) back to its original `break`. Keeping it let a curried arrow function's anonymous returned closure (fp-ts/TaskEither.ts's `tryCatch = <E, A>(...) => async () => { ... }`) scan past its own `=>` and reach the closure's REAL closing brace, misattributing "async" (the modifier keyword, mistaken for the method's own name since nothing else follows it here) a genuine function_data row with a real body -- a phantom this exact regex+detector combination already produced on main too, just previously always dropped via the "no terminator found" fallback this same PR's first commit only just started giving matches a home. This specific `pos+=2` variant wasn't actually needed by any of pipeable's/createInstance's overloads (confirmed: both still fully recovered after reverting it), so reverting is a pure fix with no lost coverage. The first `pos += 2` change (the `outer_container in (paren,angle,bracket)` case) is unaffected and stays as shipped. 2. Added a defense-in-depth guard to the "give it a home" fallback: never accept a captured name that's one of TS/JS's own reserved modifier keywords (async/static/public/private/protected/abstract/ readonly/override/get/set) immediately followed by a real parameter list -- none of these can ever legitimately be a real function's own bare name in this position, only a modifier the regex mistook for one. 3. Golden masters + the typescript tree-sitter-accuracy baseline re-blessed against the corrected code (the first bless, run before this fix, had baked the "async" phantom in). Verified: fp-ts/pipeable.ts still fully recovered (struct_func_start/ function_count 224/224). tree_sitter_accuracy_audit --lang typescript: extra_functions back to the 12-baseline (was transiently 13 with the async phantom). --all --ci: all 31 languages OK. Full pytest (463), cross-language spot-check (262), ReDoS probe, ruff/mypy/audit_check: all clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
squid-protocol
enabled auto-merge (squash)
August 26, 2026 20:07
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.
Summary
Fixes #2278. TypeScript allows multiple bodyless function-overload signatures sharing a name, followed by one real body-bearing implementation.
func_start's regex correctly matched every overload (confirmed viastruct_func_start's raw signal count), butdetector.py's terminator-scanning loop (the#1629brace-less/expression-bodied arrow scanner, gated totypescript/javascriptonly) silently dropped any match that never found a real terminator in its search window -- exactly the shape of a bodyless overload signature, whose own return-type expression can contain literal{}/=>tokens that aren't a real body or terminator at all.Fix
Three changes, all scoped to the
typescript/javascriptbranch of_slice_by_braces:{, check whether it's preceded (skipping whitespace) by a type-level operator (&|:?=) -- if so it's a type literal's own brace (e.g. fp-ts's: {}) &empty-object-type intersection members), not a real body opener; skip past its matching close and keep scanning.function_datarow bounded by the next match -- but only when a genuine parameter list immediately follows the name (optionally after<generics>). This guard is required: an earlier draft without it turned real Flow type-level fields (callback: A => T,,const task: Task = ...) into phantom function matches inreact/ReactFlightServer.js-- caught and fixed during this PR's own verification.typescript/javascript, since a recovered overload's body (bounded by the next match) can be very short.Verification
struct_func_start/function_countnow match exactly at 224/224 (was 224/217) -- all 8pipeableoverloads recovered.pyteston typescript/javascript/detector suites: 463 passed. Cross-language sanity spot-check (java/csharp/python/rust, since this touches a nominally shared file even though the changed branch itself is language-gated): 262 passed.tests/ruff_audit.py --ci/tests/mypy_audit.py --ci: no new findings beyond baseline.This is the highest-risk of a batch of 4 TypeScript fixes (the only one touching shared
detector.pyrather than an isolated per-language regex). A first draft was dispatched to a Gemini/agy subagent per this repo'stree-sitter-accuracy-sweep-style workflow; independent verification in the main session caught a real precision regression that draft's own narrow testing missed, root-caused it, and landed a scoped, evidenced fix directly.Test plan
struct_func_start/function_countbefore/after (pipeable.ts)test_typescript.py/test_typescript_strict.py/test_javascript.py/test_javascript_strict.py/test_detector.py