Skip to content

Commit 2b9ed3c

Browse files
fix(coverage): stop reporting a duplicate range and a line past EOF (#963)
scripts/setup-windows.ps1 has 326 lines and its parse-coverage report read "113-113,113-113,245-327" — the same line named twice, and an end line that does not exist. Two separate faults, both in cbm_error_regions_push. Past-EOF end line. A tree-sitter node that ends at column 0 stopped right after the previous line's newline, so it holds no text on the row it points at. Adding 1 to that row named a line past the end of the file whenever the region ran to EOF. The node here is start=(244,2) end=(326,0). Clamp the end to the row above when the end column is 0 and the node spans more than one row. Duplicate range. Line 113 carries two separate ERROR nodes, at columns 25-29 and 31-32, and each pushed its own range. A line range says nothing new the second time. Merge a region that overlaps the one already open instead of repeating it. The walk visits children in source order, so a region starting at or before the open range's end really does overlap it. The merge runs BEFORE the cap check, so it is never miscounted as a dropped range. The real file now reports "113-113,245-326". Two tests, both proved RED first with the exact expected text: coverage_repeated_error_line_reports_one_range_issue963 "2-2,2-2" != "2-2" coverage_range_never_ends_past_the_last_line_issue963 "1-5" != "1-4" Full suite: 7798 passed, 2 failed — both pre-existing agent-client install assertions in tests/test_cli.c, unrelated to this change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
1 parent 5ef8131 commit 2b9ed3c

2 files changed

Lines changed: 75 additions & 2 deletions

File tree

‎internal/cbm/cbm.c‎

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -790,12 +790,38 @@ typedef struct {
790790
} cbm_error_regions_t;
791791

792792
static void cbm_error_regions_push(cbm_error_regions_t *acc, TSNode n) {
793+
TSPoint start = ts_node_start_point(n);
794+
TSPoint end = ts_node_end_point(n);
795+
uint32_t start_line = start.row + 1;
796+
uint32_t end_line = end.row + 1;
797+
798+
/* A node that ends at column 0 stopped right after the previous line's
799+
* newline, so it holds no text on the row it points at. Counting that row
800+
* named a line past the end of the file whenever the region ran to EOF:
801+
* scripts/setup-windows.ps1 has 326 lines and reported "245-327". */
802+
if (end.column == 0 && end.row > start.row) {
803+
end_line = end.row;
804+
}
805+
806+
/* One line can carry several error nodes, and a line range says nothing
807+
* new the second time. Line 113 of scripts/setup-windows.ps1 has two, and
808+
* the report read "113-113,113-113". Extend the range already open instead
809+
* of repeating it. The walk visits children in source order, so a region
810+
* that starts at or before the open range's end really does overlap it.
811+
* This runs BEFORE the cap check, so a merge never counts as a drop. */
812+
if (acc->count > 0 && start_line <= acc->ends[acc->count - 1]) {
813+
if (end_line > acc->ends[acc->count - 1]) {
814+
acc->ends[acc->count - 1] = end_line;
815+
}
816+
return;
817+
}
818+
793819
if (acc->count >= CBM_MAX_ERROR_REGIONS) {
794820
acc->dropped++;
795821
return;
796822
}
797-
acc->starts[acc->count] = ts_node_start_point(n).row + 1;
798-
acc->ends[acc->count] = ts_node_end_point(n).row + 1;
823+
acc->starts[acc->count] = start_line;
824+
acc->ends[acc->count] = end_line;
799825
acc->count++;
800826
}
801827

‎tests/test_parse_coverage.c‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,51 @@ TEST(c_thread_local_grammar_limit_is_pinned_issue963) {
711711
PASS();
712712
}
713713

714+
/* Two error nodes can sit on ONE line. Line 113 of scripts/setup-windows.ps1
715+
* does exactly that, and the report used to read "113-113,113-113" — the same
716+
* line named twice. A line range says nothing new the second time, so repeated
717+
* or overlapping regions must collapse into one. */
718+
static const char *PS_TWO_ERRORS_ONE_LINE = "Write-Host \"start\"\n" /* 1 */
719+
"wsl.exe -- bash -c $Command 2>&1\n" /* 2 */
720+
"Write-Host \"end\"\n"; /* 3 */
721+
722+
/* An error region that runs to the end of the file stops just after the last
723+
* newline. Tree-sitter calls that position row N, column 0 — a row that holds
724+
* no text. Reading it as a line number named a line past the end of the file:
725+
* scripts/setup-windows.ps1 has 326 lines and the report said "245-327". */
726+
static const char *PS_ERROR_TO_EOF = "} else {\n" /* 1 */
727+
" if ($a) {\n" /* 2 */
728+
" Write-Host x\n" /* 3 */
729+
"}\n"; /* 4 */
730+
731+
TEST(coverage_repeated_error_line_reports_one_range_issue963) {
732+
CBMFileResult *r =
733+
cbm_extract_file(PS_TWO_ERRORS_ONE_LINE, (int)strlen(PS_TWO_ERRORS_ONE_LINE),
734+
CBM_LANG_POWERSHELL, "covproj", "two_errors.ps1", 0, NULL, NULL);
735+
ASSERT_NOT_NULL(r);
736+
ASSERT_TRUE(r->parse_incomplete);
737+
ASSERT_NOT_NULL(r->error_ranges);
738+
/* Line 2 carries two separate error nodes. It must be named once. */
739+
ASSERT_STR_EQ(r->error_ranges, "2-2");
740+
ASSERT_EQ(r->error_region_count, 1);
741+
cbm_free_result(r);
742+
PASS();
743+
}
744+
745+
TEST(coverage_range_never_ends_past_the_last_line_issue963) {
746+
int len = (int)strlen(PS_ERROR_TO_EOF);
747+
CBMFileResult *r = cbm_extract_file(PS_ERROR_TO_EOF, len, CBM_LANG_POWERSHELL, "covproj",
748+
"error_to_eof.ps1", 0, NULL, NULL);
749+
ASSERT_NOT_NULL(r);
750+
ASSERT_TRUE(r->parse_incomplete);
751+
ASSERT_NOT_NULL(r->error_ranges);
752+
/* The file has four lines and ends with a newline. Line 5 does not exist. */
753+
ASSERT_STR_EQ(r->error_ranges, "1-4");
754+
ASSERT_NULL(strstr(r->error_ranges, "5"));
755+
cbm_free_result(r);
756+
PASS();
757+
}
758+
714759
SUITE(parse_coverage) {
715760
RUN_TEST(c_ifdef_split_brace_sets_parse_incomplete);
716761
RUN_TEST(c_ifdef_split_brace_neighbors_still_extracted);
@@ -742,4 +787,6 @@ SUITE(parse_coverage) {
742787
RUN_TEST(c_refinement_does_not_suppress_real_garbage);
743788
RUN_TEST(c_clean_file_stays_unflagged_after_refinement);
744789
RUN_TEST(c_thread_local_grammar_limit_is_pinned_issue963);
790+
RUN_TEST(coverage_repeated_error_line_reports_one_range_issue963);
791+
RUN_TEST(coverage_range_never_ends_past_the_last_line_issue963);
745792
}

0 commit comments

Comments
 (0)