t: fix Lexer line count for $() inside double-quoted strings

scan_dqstring's post-loop newline counter re-counts newlines that were already counted during recursive parsing of $() bodies. This happens because scan_dollar returns text containing newlines (from multi-line command substitutions), and the catch-all counter at the end of scan_dqstring counts all of them again. Fix this by counting newlines inline as non-special characters are consumed, and removing the post-loop catch-all. Each newline is now counted exactly once: literal newlines at the inline match, line splices at the backslash handler, and $() newlines by scan_token during the recursive parse. This is a latent bug: any consumer that relies on token line numbers rather than byte offsets would get incorrect results for tokens following a multi-line $() inside a double-quoted string. chainlint is not affected because it annotates the original body text using byte offsets, not token line numbers. Signed-off-by: Michael Montalbo <mmontalbo@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Michael Montalbo committed Jul 6, 2026 at 05:01 UTC 4f8f121effc5dc7fcc4fcc98c60c5b92ec6f87f4
1 file changed +6 -3
t/lib-shell-parser.pl
+6 -3
@@ -93,8 +93,12 @@ sub scan_dqstring {
93 my $b = $self->{buff};
94 my $s = '"';
95 while (1) {
96 - # slurp up non-special characters
97 - $s .= $1 if $$b =~ /\G([^"\$\\]+)/gc;
96 + # Slurp non-special characters; count newlines here because
97 + # newlines inside $() are already counted by the recursive parse.
98 + if ($$b =~ /\G([^"\$\\]+)/gc) {
99 + $s .= $1;
100 + $self->{lineno} += $1 =~ tr/\n//;
101 + }
102 # handle special characters
103 last unless $$b =~ /\G(.)/sgc;
104 my $c = $1;
@@ -111,7 +115,6 @@ sub scan_dqstring {
115 }
116 die("internal error scanning dq-string '$c'\n");
117 }
114 - $self->{lineno} += () = $s =~ /\n/sg;
118 return $s;
119 }
120