@@ -470,6 +470,77 @@ TEST(perl_malformed_source_remains_partial_issue1838) {
470470 PASS ();
471471}
472472
473+ /* ── #1746: trailing blanks before EOF are still just an absent newline ───────
474+ *
475+ * #1610 suppressed the zero-width MISSING terminator but tested for it with
476+ * `end == source_len`. Trailing blanks are extras owned by no node, so
477+ * `ENTRYPOINT ["a"] ` + EOF parks it at [29,29) while source_len is 30.
478+ *
479+ * The reporter's byte-exact controls pin the trigger to the PAIR: `] ` + EOF
480+ * flags, `] ` + newline is clean, `]` + EOF is clean. */
481+ TEST (dockerfile_trailing_blank_at_eof_not_flagged_issue1746 ) {
482+ const char * cases [] = {
483+ "FROM scratch\nENTRYPOINT [\"a\"] " , /* space + EOF — the report */
484+ "FROM scratch\nENTRYPOINT [\"a\"]\t" , /* tab + EOF */
485+ "FROM scratch\nENTRYPOINT [\"a\"]\v" , /* vertical tab + EOF */
486+ "FROM scratch\nENTRYPOINT [\"a\"]\f" , /* form feed + EOF */
487+ "FROM scratch\nENTRYPOINT [\"a\"] \t " , /* run of blanks + EOF */
488+ "FROM scratch\nENTRYPOINT [\"a\"] \r" , /* CRLF file truncated to CR */
489+ };
490+ for (size_t i = 0 ; i < sizeof (cases ) / sizeof (cases [0 ]); i ++ ) {
491+ CBMFileResult * r = do_extract (cases [i ], CBM_LANG_DOCKERFILE , "Dockerfile" );
492+ ASSERT_NOT_NULL (r );
493+ bool flagged = r -> parse_incomplete ;
494+ if (flagged ) {
495+ fprintf (stderr , " case %zu flagged: ranges=%s\n" , i ,
496+ r -> error_ranges ? r -> error_ranges : "(none)" );
497+ }
498+ cbm_free_result (r );
499+ if (flagged ) {
500+ FAIL ("trailing blanks must not turn an absent final newline into parse_partial" );
501+ }
502+ }
503+ PASS ();
504+ }
505+
506+ /* GUARD: widening the tail must not swallow a genuine mid-file failure just
507+ * because the file happens to end in blanks. */
508+ TEST (real_error_before_eof_still_flagged_with_trailing_blank_issue1746 ) {
509+ size_t n = strlen (C_IFDEF_SPLIT );
510+ char * buf = (char * )malloc (n + 1 );
511+ ASSERT_NOT_NULL (buf );
512+ memcpy (buf , C_IFDEF_SPLIT , n + 1 );
513+ buf [n - 1 ] = ' ' ; /* final newline becomes a blank */
514+
515+ CBMFileResult * r = do_extract (buf , CBM_LANG_C , "split.c" );
516+ free (buf );
517+ ASSERT_NOT_NULL (r );
518+ bool flagged = r -> parse_incomplete ;
519+ bool has_ranges = r -> error_ranges != NULL ;
520+ cbm_free_result (r );
521+ if (!flagged ) {
522+ FAIL ("a real mid-file parse failure must still be reported when the file ends in blanks" );
523+ }
524+ if (!has_ranges ) {
525+ FAIL ("a reported failure must still name its line range" );
526+ }
527+ PASS ();
528+ }
529+
530+ /* GUARD: a WIDTH-BEARING loss at EOF stays honest with a blank tail too — the
531+ * Makefile recipe really is dropped, and only zero-width nodes are excused. */
532+ TEST (width_bearing_error_at_eof_still_flagged_with_trailing_blank_issue1746 ) {
533+ const char * src = "all:\n\techo hi " ;
534+ CBMFileResult * r = do_extract (src , CBM_LANG_MAKEFILE , "Makefile" );
535+ ASSERT_NOT_NULL (r );
536+ bool flagged = r -> parse_incomplete ;
537+ cbm_free_result (r );
538+ if (!flagged ) {
539+ FAIL ("a width-bearing parse failure at EOF must still be reported when the file ends in blanks" );
540+ }
541+ PASS ();
542+ }
543+
473544SUITE (parse_coverage ) {
474545 RUN_TEST (c_ifdef_split_brace_sets_parse_incomplete );
475546 RUN_TEST (c_ifdef_split_brace_neighbors_still_extracted );
@@ -491,4 +562,7 @@ SUITE(parse_coverage) {
491562 RUN_TEST (width_bearing_error_at_eof_still_flagged_issue1610 );
492563 RUN_TEST (perl_format_followed_by_named_sub_is_complete_issue1838 );
493564 RUN_TEST (perl_malformed_source_remains_partial_issue1838 );
565+ RUN_TEST (dockerfile_trailing_blank_at_eof_not_flagged_issue1746 );
566+ RUN_TEST (real_error_before_eof_still_flagged_with_trailing_blank_issue1746 );
567+ RUN_TEST (width_bearing_error_at_eof_still_flagged_with_trailing_blank_issue1746 );
494568}
0 commit comments