From 14af16997f7f2c9580fe54948be61bdd50a162b5 Mon Sep 17 00:00:00 2001 From: Daniel Sagenschneider Date: Wed, 23 Sep 2026 07:14:19 +0800 Subject: [PATCH] Improve the cognitive estimator: count ternaries and lambda-body nesting The parser-free estimator was materially under-counting idiomatic code. On a real Java method (a seed fixture loader that is a run of for-loops each with several `x == null ? a : b` ternaries) it read 14 while PMD read 29. The whole gap was ternaries, which the estimator dropped, plus lambda bodies which did not add nesting. - Ternaries (`a ? b : c`) now count as +1 plus nesting. The tokenizer separates TS optional chaining (`?.`), nullish (`??`) and optional-member (`?:`) so they never false-fire. - A lambda body (after `->` in Java or `=>` in JS/TS) now adds a nesting level, so control flow inside a stream/callback is charged for its depth. An expression lambda with no block adds nothing. The estimator now equals PMD on that method (29 == 29). Prior calibration is unchanged (flat sequential 0, deep nesting 21, boolean runs, switch, Python). New tests pin ternaries, the TS non-ternary `?` forms, and lambda nesting. Full suite green (109). The estimator's own methods stay under the dogfood cognitive line. Co-Authored-By: Claude Opus 4.8 --- impact_gate/core/cognitive.py | 48 +++++++++++++++++++++++------------ tests/test_cognitive.py | 21 +++++++++++++++ 2 files changed, 53 insertions(+), 16 deletions(-) diff --git a/impact_gate/core/cognitive.py b/impact_gate/core/cognitive.py index 22a183b..269d283 100644 --- a/impact_gate/core/cognitive.py +++ b/impact_gate/core/cognitive.py @@ -16,10 +16,11 @@ * NESTING increases inside every one of those blocks. Nesting is tracked by braces for the C family and by indentation for Python-like languages. -Two documented simplifications keep it parser-free and cross-language robust: the ternary -``?:`` is not counted (its token is ambiguous with TS optional/nullable syntax), and nested -lambdas/functions do not add a nesting level. Both under-count slightly and uniformly, which -a gate threshold absorbs; the calibration tests pin the residual. +Ternaries (``a ? b : c``) count as +1 plus nesting; the tokenizer separates TS optional +chaining (``?.``), nullish (``??``) and optional-member (``?:``) so they do not false-fire. +A lambda body (after ``->`` in Java or ``=>`` in JS/TS) adds a nesting level, so control flow +inside a stream/callback is charged for its depth. The calibration tests pin the numbers +against PMD (Java) and hand-verified values. """ from __future__ import annotations @@ -38,7 +39,10 @@ # Control-flow keywords shared across the C family (and most curly-brace languages). _NEST_KW = {"if", "for", "foreach", "while", "switch", "catch"} # +1 + nesting, opens a block -_C_TOKEN = re.compile(r"&&|\|\||[{}();]|[A-Za-z_$][A-Za-z0-9_$]*") +# Order matters: the multi-char '?' forms come before the bare '?' so TS optional chaining (?.), +# nullish (??) and optional-member (?:) are tokenised out and do NOT count as ternaries. Lambda +# arrows (-> for Java, => for JS/TS) are tokens too, so a lambda body adds a nesting level. +_C_TOKEN = re.compile(r"\?\?|\?\.|\?:|->|=>|&&|\|\||[?{}();]|[A-Za-z_$][A-Za-z0-9_$]*") class _CScan: @@ -52,6 +56,7 @@ def __init__(self) -> None: self._pending = 0 # nesting to attach to the next '{' self._last_bool: str | None = None self.prev: str | None = None # previous token (for do-while tails) + self._arrow = False # previous token was a lambda arrow (-> / =>) def boolean(self, op: str) -> None: if op != self._last_bool: # a run of like operators counts once @@ -61,9 +66,13 @@ def boolean(self, op: str) -> None: def end_bool_run(self) -> None: self._last_bool = None - def open_brace(self) -> None: - self._brace.append(self._pending) - self.nesting += self._pending + def ternary(self) -> None: # a ? b : c — +1 + depth, no block + self.score += 1 + self.nesting + + def open_brace(self, lambda_block: bool = False) -> None: + add = 1 if (self._pending or lambda_block) else 0 # control block or lambda body nests + self._brace.append(add) + self.nesting += add self._pending = 0 def close_brace(self) -> None: @@ -83,24 +92,31 @@ def _cognitive_c(source: str) -> int: """Brace-nested cognitive complexity for the C family (Java, JS/TS, C/C++, C#, Go, …).""" toks = _C_TOKEN.findall(_STRIP.sub(" ", source)) s = _CScan() - # Tokens that reset the boolean run and take a fixed action (order: reset, then act). - fixed = {"{": s.open_brace, "}": s.close_brace, "do": s.nest, ";": lambda: None} i, n = 0, len(toks) while i < n: t = toks[i] + arrow = s._arrow # did a lambda arrow immediately precede this token? + s._arrow = False if t in ("&&", "||"): # boolean run — operands between operators don't break it s.boolean(t) - elif t in fixed: - s.end_bool_run() - fixed[t]() + elif t == "?": # ternary (bare '?'; ?. ?? ?: are separate tokens, ignored) + s.end_bool_run(); s.ternary() + elif t in ("->", "=>"): # lambda arrow: the block it opens adds a nesting level + s._arrow = True + elif t == "{": + s.end_bool_run(); s.open_brace(lambda_block=arrow) + elif t == "}": + s.end_bool_run(); s.close_brace() + elif t == "do": + s.end_bool_run(); s.nest() elif t == "else": - s.end_bool_run() - s.cont() + s.end_bool_run(); s.cont() if i + 1 < n and toks[i + 1] == "if": i += 1 # consume the `if` of `else if` elif t in _NEST_KW and not (t == "while" and s.prev == "}"): # skip do{}while tail + s.end_bool_run(); s.nest() + elif t == ";": s.end_bool_run() - s.nest() s.prev = t i += 1 return max(s.score, 0) diff --git a/tests/test_cognitive.py b/tests/test_cognitive.py index 64fe2fc..c5dcee7 100644 --- a/tests/test_cognitive.py +++ b/tests/test_cognitive.py @@ -59,6 +59,27 @@ def test_strings_and_comments_do_not_count(): assert cc(src, "java") == 0 +def test_ternary_counts(): + assert cc("int f(){ return a ? b : c; }", "java") == 1 + # ternary inside a loop is charged the nesting depth too: for(1) + ternary(1+1) = 3 + assert cc("void f(){ for(x : xs){ y = a == null ? 1 : 2; } }", "java") == 3 + + +def test_ts_optional_syntax_is_not_a_ternary(): + # These all contain '?' but are NOT ternaries; they must not count. + assert cc("function f(){ return a?.b?.c; }", "ts") == 0 # optional chaining + assert cc("function f(x?: number){ return x; }", "ts") == 0 # optional parameter + assert cc("function f(){ return a ?? b; }", "ts") == 0 # nullish coalescing + + +def test_lambda_body_adds_nesting(): + # A control structure inside a lambda/callback is charged for its depth (Java -> and JS =>). + assert cc("void f(){ xs.forEach(x -> { if(a){ y(); } }); }", "java") == 2 + assert cc("function f(){ xs.forEach(x => { if(a){ y(); } }); }", "ts") == 2 + # An expression lambda with no block and no control flow costs nothing. + assert cc("const f = () => xs.map(x => x.id);", "ts") == 0 + + # --- the plugin field --------------------------------------------------------- def test_plugin_sets_unit_cognitive():