Skip to content

typescript func_start: brace-less/expression-bodied function definitions (curried FP-style consts) are silently dropped entirely #1629

Description

@squid-protocol

Summary

tests/tools/tree_sitter_accuracy_audit.py --lang typescript currently measures func_recall=92.1%, extra_functions=309 (precision 85.6%) against the language-crucible/data/typescript corpus (see docs/self_scan/tree_sitter_accuracy_history.csv). Diagnosed the 159 found_functions misses directly (full diff dumped via a one-off script reusing tree_sitter_accuracy_audit.py's own measure() internals, not just the tool's 8-file/3-name sample). This is the single largest contributor: at least 88 of the 159 missing functions (>55%) are brace-less, expression-bodied arrow function definitions — real, func_start-regex-matched function declarations that detector.py's _slice_by_braces (Mode B) silently drops because their body has no { at all.

Root cause

_slice_by_braces (gitgalaxy/core/detector.py:2133) has language-gated special handling for brace-less/expression bodies in csharp (#789, => arrow-bodied members), scala (#1266, bare = expr bodies), rust/zig/solidity (bodyless ;-terminated trait signatures), kotlin, objective-c, and dart — but not typescript (or javascript, which shares the same fallback). Every language not explicitly gated into one of those branches falls through to the generic default:

else:
    brace_idx = safe_code.find(opener, start_idx, search_limit)
    if brace_idx == -1:
        continue
    end_idx = self._find_balanced_end(safe_code, brace_idx, opener, closer)

If no { appears within search_limit (min of 2000 chars or the next func_start match), the match is dropped entirely via continue — the function is never recorded, not even with a degraded/zero-magnitude entry.

TypeScript/JavaScript idiomatically use brace-less arrow function bodies extremely often — single-expression arrow functions (const swap = (x) => x + 1) and, especially in functional-style code, curried multi-arrow chains with no { anywhere in the whole definition. This is not a rare style; it's the primary export shape throughout fp-ts (a top-20 npm functional-programming library, part of the pinned corpus).

Evidence

Confirmed with the actual func_start regex (gitgalaxy/standards/language_standards.py) — these DO match at the regex level (proving the drop happens in _slice_by_braces, not the regex):

>>> rule.search("export const swap = <E, A>(ma: Either<E, A>): Either<A, E> => (isLeft(ma) ? right(ma.left) : left(ma.right))")
<re.Match object; span=(13, 17), match='swap'>   # matches, but absent from function_data

language-crucible/data/typescript/fp-ts/Either.ts alone accounts for dozens of these, e.g.:

// line 1325 -- matches func_start, never reaches function_data
export const swap = <E, A>(ma: Either<E, A>): Either<A, E> => (isLeft(ma) ? right(ma.left) : left(ma.right))

// line 688 -- curried chain, no `{` anywhere in the whole definition
export const traverse: PipeableTraverse2<URI> =
  <F>(F: ApplicativeHKT<F>) =>
  <A, B>(f: (a: A) => HKT<F, B>) =>
  <E>(ta: Either<E, A>): HKT<F, Either<E, B>> =>
    isLeft(ta) ? F.of(left(ta.left)) : F.map<B, Either<E, B>>(f(ta.right), right)

// line 170 -- top-level, unexported, still a real function
const _map: Monad2<URI>['map'] = (fa, f) => pipe(fa, map(f))

Confirmed via galaxyscope --db-only against a single-file scan of fp-ts/Either.ts: function_data has zero rows named traverse, swap, _map, _reduce, _ap, _reduceRight, reduce, foldMap, mapLeft, filter, ap, bimap, altW, concat, equals — all real, tree-sitter-confirmed functions.

Other affected files in the same corpus: fp-ts/TaskEither.ts, fp-ts/pipeable.ts (same style throughout).

Suggested fix direction

Add a typescript/javascript branch to _slice_by_braces mirroring csharp's (#789) or scala's (#1266) approach: if no { is found before search_limit, look for the nearest un-nested => after the parameter list and bound the body by either the next top-level statement terminator or the next func_start match (scala's approach — end_idx = next_match_start — is probably the closer analogy here, since TS/JS arrow bodies have no reliable ; terminator either, same as scala's def).

Scope note

This audit only measures typescript; javascript shares the exact same _slice_by_braces fallback path and near-certainly has the same gap (unverified here — out of scope for this issue, worth a quick follow-up check once this is fixed).

Filed under epic #1261.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugUnintended behavior or logic failure in the enginecore-engineModifications to the central physics and parsing enginepriority: highCore feature broken, but workarounds existtestingUnit, integration, and E2E pipeline verificationthreat: false-negativeAccuracy tuning: Engine missed a bypass or vulnerability

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions