1616
1717 <path> <total> +<added> -<removed>
1818 New:
19- <func>() NEW +<added>
19+ <func>() <total> +<added>
2020 ...
2121 Existing:
22- <func>() <total> +<added> -<removed>
22+ <func>() <total> +<added> -<removed>
23+ ...
24+ Deleted:
25+ <func>() <total> -<removed>
2326 ...
2427
25- Within each file, functions are split into a ``New:`` group (added by this PR) and an
26- ``Existing:`` group (modified or deleted by this PR), and within each group sorted by added
27- lines descending, then removed lines descending. Function-name and numeric columns are
28- padded with spaces so the columns line up within a file.
28+ The ``<total>`` column at every level is the BASE (pre-PR) code-line count: file size before
29+ the PR (or 0 for newly-added files); function body size before the PR (or 0 for new functions;
30+ the original body size for deleted functions). Within each file, functions are split into a
31+ ``New:`` group (added by this PR), an ``Existing:`` group (modified by this PR), and a
32+ ``Deleted:`` group (removed by this PR), and within each group sorted by added lines descending,
33+ then removed lines descending. Function-name and numeric columns are padded with spaces so the
34+ columns line up within a file.
2935"""
3036
3137from __future__ import annotations
@@ -63,6 +69,7 @@ class _FileBookkeeping:
6369 base_codes : set [int ]
6470 added_line_nos : list [int ]
6571 removed_line_nos : list [int ]
72+ is_deleted : bool = False
6673
6774
6875def _load_function_entries (jsonl_path : Path ) -> dict [str , list [_FunctionEntry ]]:
@@ -143,26 +150,29 @@ def _build_file_bookkeeping(summary: dict, output_dir: Path) -> _FileBookkeeping
143150 base_codes = base_codes ,
144151 added_line_nos = added_line_nos ,
145152 removed_line_nos = removed_line_nos ,
153+ is_deleted = bool (summary .get ("is_deleted" , False )),
146154 )
147155
148156
149157def _attribute_function (entry : _FunctionEntry , book : _FileBookkeeping ) -> tuple [int , int , int , bool , bool ]:
150158 """Return ``(total, added, removed, is_new, is_deleted)`` for a single function entry.
151159
152- ``total`` is the number of code lines in the function's HEAD body. ``added`` / ``removed``
153- are the number of ``+`` / ``-`` diff lines that fall inside the function's HEAD / base range
154- AND are themselves code lines (excluding comments / blank lines / Python multi-line strings).
160+ ``total`` is the number of code lines in the function's BASE (pre-PR) body, i.e. how big
161+ this function was before this PR. For NEW functions (no base body) this is 0. ``added`` /
162+ ``removed`` are the number of ``+`` / ``-`` diff lines that fall inside the function's
163+ HEAD / base range AND are themselves code lines (excluding comments / blank lines / Python
164+ multi-line strings).
155165 """
156166 total = 0
157167 added = 0
158168 removed = 0
159- if entry .head_range is not None :
160- a , b = entry .head_range
161- total = sum (1 for ln in book .head_codes if a <= ln <= b )
162- added = sum (1 for ln in book .added_line_nos if a <= ln <= b and ln in book .head_codes )
163169 if entry .base_range is not None :
164170 c , d = entry .base_range
171+ total = sum (1 for ln in book .base_codes if c <= ln <= d )
165172 removed = sum (1 for ln in book .removed_line_nos if c <= ln <= d and ln in book .base_codes )
173+ if entry .head_range is not None :
174+ a , b = entry .head_range
175+ added = sum (1 for ln in book .added_line_nos if a <= ln <= b and ln in book .head_codes )
166176 is_new = entry .head_range is not None and entry .base_range is None
167177 is_deleted = entry .head_range is None and entry .base_range is not None
168178 return total , added , removed , is_new , is_deleted
@@ -223,20 +233,16 @@ def _impact_sort_key(a: _AttributedEntry) -> tuple[int, int, str]:
223233
224234
225235# Column widths chosen to fit the longest expected token in each column without crowding adjacent
226- # columns. ``DELETED`` is 7 chars (the longest total token); five -digit ``+``/``-`` counts are
227- # the longest expected numeric tokens .
228- _TOTAL_WIDTH = 8
236+ # columns. Five -digit ``+``/``-`` counts are the longest expected numeric tokens; the total
237+ # column holds a base (pre-PR) line count so it never exceeds a few digits in practice .
238+ _TOTAL_WIDTH = 6
229239_ADDED_WIDTH = 7
230240_REMOVED_WIDTH = 7
231241_FUNC_INDENT = " "
232242_GROUP_INDENT = " "
233243
234244
235245def _format_total (a : _AttributedEntry ) -> str :
236- if a .is_deleted :
237- return "DELETED"
238- if a .is_new :
239- return "NEW"
240246 return str (a .total )
241247
242248
@@ -269,17 +275,21 @@ def _emit_file_section(book: _FileBookkeeping, attributed: list[_AttributedEntry
269275 appended by the caller, not here.
270276 """
271277 lines : list [str ] = [format_header (_HeaderProxy (book ))]
278+ if book .is_deleted :
279+ lines .append (f"{ _GROUP_INDENT } # entire file deleted (per-function breakdown skipped)" )
280+ return lines , 0 , 0
272281 if not attributed :
273282 if book .added or book .removed :
274283 lines .append (f"{ _GROUP_INDENT } # note: no per-function attribution available" )
275284 return lines , 0 , 0
276285
277286 new_group = sorted ([a for a in attributed if a .is_new ], key = _impact_sort_key )
278- existing_group = sorted ([a for a in attributed if not a .is_new ], key = _impact_sort_key )
287+ deleted_group = sorted ([a for a in attributed if a .is_deleted ], key = _impact_sort_key )
288+ existing_group = sorted ([a for a in attributed if not a .is_new and not a .is_deleted ], key = _impact_sort_key )
279289
280- # Pad function names to the longest name across BOTH groups so the New: and Existing:
281- # subsections line up with each other.
282- all_names = [_function_label (a .entry .name ) for a in new_group + existing_group ]
290+ # Pad function names to the longest name across ALL groups so the New: / Existing: /
291+ # Deleted: subsections line up with each other.
292+ all_names = [_function_label (a .entry .name ) for a in new_group + existing_group + deleted_group ]
283293 name_width = max ((len (n ) for n in all_names ), default = 0 )
284294
285295 if new_group :
@@ -290,6 +300,10 @@ def _emit_file_section(book: _FileBookkeeping, attributed: list[_AttributedEntry
290300 lines .append (f"{ _GROUP_INDENT } Existing:" )
291301 for a in existing_group :
292302 lines .append (_format_aligned_function (a , name_width ))
303+ if deleted_group :
304+ lines .append (f"{ _GROUP_INDENT } Deleted:" )
305+ for a in deleted_group :
306+ lines .append (_format_aligned_function (a , name_width ))
293307
294308 per_added = sum (a .added for a in attributed )
295309 per_removed = sum (a .removed for a in attributed )
@@ -303,6 +317,18 @@ def _emit_file_section(book: _FileBookkeeping, attributed: list[_AttributedEntry
303317 return lines , per_added , per_removed
304318
305319
320+ _FOOTER = (
321+ "Notes:\n "
322+ " * The number columns (without a + or - sign) are code-line counts in the "
323+ "BASE (pre-PR) version: file size before this PR (0 for newly-added files), "
324+ "function body size before this PR (0 for new functions; original body size "
325+ "for deleted functions).\n "
326+ " * +<n> / -<n> are code lines added / removed by this PR.\n "
327+ " * Code lines exclude blank lines, comment-only lines, and Python "
328+ "multi-line strings."
329+ )
330+
331+
306332def render (summary_json : Path , jsonl_path : Path , output_dir : Path ) -> str :
307333 summaries : list [dict ] = json .loads (summary_json .read_text ())
308334 by_path = _load_function_entries (jsonl_path )
@@ -316,6 +342,9 @@ def render(summary_json: Path, jsonl_path: Path, output_dir: Path) -> str:
316342 out_lines .append ("" )
317343 while out_lines and out_lines [- 1 ] == "" :
318344 out_lines .pop ()
345+ if out_lines :
346+ out_lines .append ("" )
347+ out_lines .append (_FOOTER )
319348 return "\n " .join (out_lines ) + ("\n " if out_lines else "" )
320349
321350
0 commit comments