Merge pull request #1853 from DeusData/fix/issue-1746-dockerfile-eof-whitespace-resume

fix: ignore trailing Dockerfile whitespace at EOF
This commit is contained in:
Martin Vogel
2026-08-29 01:07:05 +02:00
committed by GitHub
2 changed files with 81 additions and 8 deletions
+28 -8
View File
@@ -814,17 +814,37 @@ static void cbm_error_regions_push(cbm_error_regions_t *acc, TSNode n) {
* Deliberately narrow — ZERO-WIDTH AT EOF ONLY. A MISSING or ERROR node with
* WIDTH still counts even at EOF (a Makefile whose last recipe line is
* unterminated really does lose the recipe), and anything before EOF is
* untouched. */
static bool cbm_is_eof_terminator_miss(TSNode n, int source_len) {
* untouched.
*
* #1746: the Dockerfile grammar places that zero-width missing newline before
* trailing horizontal whitespace rather than at raw EOF. Preserve the broad
* exact-EOF rule above; only extend it past spaces/tabs when the missing token
* is specifically a newline. */
static bool cbm_is_eof_terminator_miss(TSNode n, const char *source, int source_len) {
if (!ts_node_is_missing(n) || source_len < 0) {
return false;
}
uint32_t start = ts_node_start_byte(n);
uint32_t end = ts_node_end_byte(n);
return start == end && end == (uint32_t)source_len;
if (start != end || end > (uint32_t)source_len) {
return false;
}
if (end == (uint32_t)source_len) {
return true;
}
if (!source || strcmp(ts_node_type(n), "\n") != 0) {
return false;
}
for (uint32_t i = end; i < (uint32_t)source_len; i++) {
if (source[i] != ' ' && source[i] != '\t') {
return false;
}
}
return true;
}
static void cbm_collect_error_regions(TSNode n, cbm_error_regions_t *acc, int source_len) {
static void cbm_collect_error_regions(TSNode n, cbm_error_regions_t *acc, const char *source,
int source_len) {
if (acc->count >= CBM_MAX_ERROR_REGIONS) {
return;
}
@@ -832,12 +852,12 @@ static void cbm_collect_error_regions(TSNode n, cbm_error_regions_t *acc, int so
for (uint32_t i = 0; i < k && acc->count < CBM_MAX_ERROR_REGIONS; i++) {
TSNode c = ts_node_child(n, i);
if (ts_node_is_missing(c) || strcmp(ts_node_type(c), "ERROR") == 0) {
if (cbm_is_eof_terminator_miss(c, source_len)) {
if (cbm_is_eof_terminator_miss(c, source, source_len)) {
continue; /* absent final newline only — nothing was dropped */
}
cbm_error_regions_push(acc, c); /* top-most region; do not descend */
} else if (ts_node_has_error(c)) {
cbm_collect_error_regions(c, acc, source_len);
cbm_collect_error_regions(c, acc, source, source_len);
}
}
}
@@ -1460,7 +1480,7 @@ CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLangua
* already extract. */
if (ts_node_has_error(root)) {
cbm_error_regions_t raw_regs = {{0}, {0}, 0};
cbm_collect_error_regions(root, &raw_regs, source_len);
cbm_collect_error_regions(root, &raw_regs, source, source_len);
if (raw_regs.count > 0) {
int defs_before = result->defs.count;
cbm_extract_definitions(&pp_ctx);
@@ -1618,7 +1638,7 @@ CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLangua
if (strcmp(ts_node_type(root), "ERROR") == 0) {
cbm_error_regions_push(&regs, root); /* whole file unparseable */
} else {
cbm_collect_error_regions(root, &regs, source_len);
cbm_collect_error_regions(root, &regs, source, source_len);
}
cbm_subtract_recovered_regions(&regs, &result->defs);
/* #1071: don't flag a benign function-like-macro call (defined in-file)
+53
View File
@@ -295,6 +295,55 @@ TEST(dockerfile_with_final_newline_still_clean_issue1610) {
PASS();
}
/* #1746: on Windows, a Dockerfile whose final instruction is followed by one
* ASCII space and then EOF was reported as parse_partial. */
TEST(dockerfile_trailing_space_at_eof_not_flagged_issue1746) {
const char *src = "FROM scratch\nENTRYPOINT [\"a\"] ";
CBMFileResult *r = do_extract(src, CBM_LANG_DOCKERFILE, "Dockerfile");
ASSERT_NOT_NULL(r);
bool flagged = r->parse_incomplete;
if (flagged) {
fprintf(stderr, " exact issue #1746 fixture flagged: ranges=%s\n",
r->error_ranges ? r->error_ranges : "(none)");
}
cbm_free_result(r);
if (flagged) {
FAIL("trailing horizontal whitespace at Dockerfile EOF must not be parse_partial");
}
PASS();
}
/* The same bytes WITH the LF must stay clean. This is a separate test so the
* control executes even while the exact affected fixture is RED. */
TEST(dockerfile_trailing_space_with_final_newline_clean_issue1746) {
const char *src = "FROM scratch\nENTRYPOINT [\"a\"] \n";
CBMFileResult *r = do_extract(src, CBM_LANG_DOCKERFILE, "Dockerfile");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->parse_incomplete);
cbm_free_result(r);
PASS();
}
/* Removing the trailing space while retaining EOF must also stay clean. */
TEST(dockerfile_without_trailing_space_at_eof_clean_issue1746) {
const char *src = "FROM scratch\nENTRYPOINT [\"a\"]";
CBMFileResult *r = do_extract(src, CBM_LANG_DOCKERFILE, "Dockerfile");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->parse_incomplete);
cbm_free_result(r);
PASS();
}
/* The reporter also observed the same failure when the first line uses CRLF. */
TEST(dockerfile_crlf_trailing_space_at_eof_not_flagged_issue1746) {
const char *src = "FROM scratch\r\nENTRYPOINT [\"a\"] ";
CBMFileResult *r = do_extract(src, CBM_LANG_DOCKERFILE, "Dockerfile");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->parse_incomplete);
cbm_free_result(r);
PASS();
}
/* Language-general, not a Dockerfile patch: these four were each proven to flag
* on a stripped trailing newline. */
TEST(missing_final_newline_not_flagged_across_grammars_issue1610) {
@@ -381,6 +430,10 @@ SUITE(parse_coverage) {
RUN_TEST(c_trailing_recovered_defs_keep_flag);
RUN_TEST(dockerfile_missing_final_newline_not_flagged_issue1610);
RUN_TEST(dockerfile_with_final_newline_still_clean_issue1610);
RUN_TEST(dockerfile_trailing_space_at_eof_not_flagged_issue1746);
RUN_TEST(dockerfile_trailing_space_with_final_newline_clean_issue1746);
RUN_TEST(dockerfile_without_trailing_space_at_eof_clean_issue1746);
RUN_TEST(dockerfile_crlf_trailing_space_at_eof_not_flagged_issue1746);
RUN_TEST(missing_final_newline_not_flagged_across_grammars_issue1610);
RUN_TEST(real_error_before_eof_still_flagged_without_final_newline_issue1610);
RUN_TEST(width_bearing_error_at_eof_still_flagged_issue1610);