Merge branch 'es/chainlint-lineno'

Teach chainlint.pl to show corresponding line numbers when printing
the source of a test.

* es/chainlint-lineno:
  chainlint: prefix annotated test definition with line numbers
  chainlint: latch line numbers at which each token starts and ends
  chainlint: sidestep impoverished macOS "terminfo"
This commit is contained in:
Junio C Hamano 2022-11-23 11:22:24 +09:00
commit cf9721cc46
2 changed files with 49 additions and 23 deletions

View file

@ -94,7 +94,7 @@ check-chainlint:
done \
} >'$(CHAINLINTTMP_SQ)'/expect && \
$(CHAINLINT) --emit-all '$(CHAINLINTTMP_SQ)'/tests | \
grep -v '^[ ]*$$' >'$(CHAINLINTTMP_SQ)'/actual && \
sed -e 's/^[1-9][0-9]* //;/^[ ]*$$/d' >'$(CHAINLINTTMP_SQ)'/actual && \
if test -f ../GIT-BUILD-OPTIONS; then \
. ../GIT-BUILD-OPTIONS; \
fi && \

View file

@ -67,6 +67,7 @@ sub new {
bless {
parser => $parser,
buff => $s,
lineno => 1,
heretags => []
} => $class;
}
@ -97,7 +98,9 @@ sub scan_op {
sub scan_sqstring {
my $self = shift @_;
${$self->{buff}} =~ /\G([^']*'|.*\z)/sgc;
return "'" . $1;
my $s = $1;
$self->{lineno} += () = $s =~ /\n/sg;
return "'" . $s;
}
sub scan_dqstring {
@ -115,7 +118,7 @@ sub scan_dqstring {
if ($c eq '\\') {
$s .= '\\', last unless $$b =~ /\G(.)/sgc;
$c = $1;
next if $c eq "\n"; # line splice
$self->{lineno}++, next if $c eq "\n"; # line splice
# backslash escapes only $, `, ", \ in dq-string
$s .= '\\' unless $c =~ /^[\$`"\\]$/;
$s .= $c;
@ -123,6 +126,7 @@ sub scan_dqstring {
}
die("internal error scanning dq-string '$c'\n");
}
$self->{lineno} += () = $s =~ /\n/sg;
return $s;
}
@ -137,6 +141,7 @@ sub scan_balanced {
$depth--;
last if $depth == 0;
}
$self->{lineno} += () = $s =~ /\n/sg;
return $s;
}
@ -163,8 +168,11 @@ sub swallow_heredocs {
my $b = $self->{buff};
my $tags = $self->{heretags};
while (my $tag = shift @$tags) {
my $start = pos($$b);
my $indent = $tag =~ s/^\t// ? '\\s*' : '';
$$b =~ /(?:\G|\n)$indent\Q$tag\E(?:\n|\z)/gc;
my $body = substr($$b, $start, pos($$b) - $start);
$self->{lineno} += () = $body =~ /\n/sg;
}
}
@ -172,11 +180,12 @@ sub scan_token {
my $self = shift @_;
my $b = $self->{buff};
my $token = '';
my $start;
my ($start, $startln);
RESTART:
$startln = $self->{lineno};
$$b =~ /\G[ \t]+/gc; # skip whitespace (but not newline)
$start = pos($$b) || 0;
return ["\n", $start, pos($$b)] if $$b =~ /\G#[^\n]*(?:\n|\z)/gc; # comment
$self->{lineno}++, return ["\n", $start, pos($$b), $startln, $startln] if $$b =~ /\G#[^\n]*(?:\n|\z)/gc; # comment
while (1) {
# slurp up non-special characters
$token .= $1 if $$b =~ /\G([^\\;&|<>(){}'"\$\s]+)/gc;
@ -188,20 +197,20 @@ sub scan_token {
$token .= $self->scan_sqstring(), next if $c eq "'";
$token .= $self->scan_dqstring(), next if $c eq '"';
$token .= $c . $self->scan_dollar(), next if $c eq '$';
$self->swallow_heredocs(), $token = $c, last if $c eq "\n";
$self->{lineno}++, $self->swallow_heredocs(), $token = $c, last if $c eq "\n";
$token = $self->scan_op($c), last if $c =~ /^[;&|<>]$/;
$token = $c, last if $c =~ /^[(){}]$/;
if ($c eq '\\') {
$token .= '\\', last unless $$b =~ /\G(.)/sgc;
$c = $1;
next if $c eq "\n" && length($token); # line splice
goto RESTART if $c eq "\n"; # line splice
$self->{lineno}++, next if $c eq "\n" && length($token); # line splice
$self->{lineno}++, goto RESTART if $c eq "\n"; # line splice
$token .= '\\' . $c;
next;
}
die("internal error scanning character '$c'\n");
}
return length($token) ? [$token, $start, pos($$b)] : undef;
return length($token) ? [$token, $start, pos($$b), $startln, $self->{lineno}] : undef;
}
# ShellParser parses POSIX shell scripts (with minor extensions for Bash). It
@ -605,6 +614,7 @@ sub check_test {
my $problems = $parser->{problems};
return unless $emit_all || @$problems;
my $c = main::fd_colors(1);
my $lineno = $_[1]->[3];
my $start = 0;
my $checked = '';
for (sort {$a->[1]->[2] <=> $b->[1]->[2]} @$problems) {
@ -614,10 +624,12 @@ sub check_test {
$start = $pos;
}
$checked .= substr($body, $start);
$checked =~ s/^\n//;
$checked =~ s/^/$lineno++ . ' '/mge;
$checked =~ s/^\d+ \n//;
$checked =~ s/(\s) \?!/$1?!/mg;
$checked =~ s/\?! (\s)/?!$1/mg;
$checked =~ s/(\?![^?]+\?!)/$c->{rev}$c->{red}$1$c->{reset}/mg;
$checked =~ s/^\d+/$c->{dim}$&$c->{reset}/mg;
$checked .= "\n" unless $checked =~ /\n$/;
push(@{$self->{output}}, "$c->{blue}# chainlint: $title$c->{reset}\n$checked");
}
@ -649,25 +661,39 @@ package main;
# thread and ignore %ENV changes in subthreads.
$ENV{TERM} = $ENV{USER_TERM} if $ENV{USER_TERM};
my @NOCOLORS = (bold => '', rev => '', reset => '', blue => '', green => '', red => '');
my @NOCOLORS = (bold => '', rev => '', dim => '', reset => '', blue => '', green => '', red => '');
my %COLORS = ();
sub get_colors {
return \%COLORS if %COLORS;
if (exists($ENV{NO_COLOR}) ||
system("tput sgr0 >/dev/null 2>&1") != 0 ||
system("tput bold >/dev/null 2>&1") != 0 ||
system("tput rev >/dev/null 2>&1") != 0 ||
system("tput setaf 1 >/dev/null 2>&1") != 0) {
if (exists($ENV{NO_COLOR})) {
%COLORS = @NOCOLORS;
return \%COLORS;
}
%COLORS = (bold => `tput bold`,
rev => `tput rev`,
reset => `tput sgr0`,
blue => `tput setaf 4`,
green => `tput setaf 2`,
red => `tput setaf 1`);
chomp(%COLORS);
if ($ENV{TERM} =~ /xterm|xterm-\d+color|xterm-new|xterm-direct|nsterm|nsterm-\d+color|nsterm-direct/) {
%COLORS = (bold => "\e[1m",
rev => "\e[7m",
dim => "\e[2m",
reset => "\e[0m",
blue => "\e[34m",
green => "\e[32m",
red => "\e[31m");
return \%COLORS;
}
if (system("tput sgr0 >/dev/null 2>&1") == 0 &&
system("tput bold >/dev/null 2>&1") == 0 &&
system("tput rev >/dev/null 2>&1") == 0 &&
system("tput dim >/dev/null 2>&1") == 0 &&
system("tput setaf 1 >/dev/null 2>&1") == 0) {
%COLORS = (bold => `tput bold`,
rev => `tput rev`,
dim => `tput dim`,
reset => `tput sgr0`,
blue => `tput setaf 4`,
green => `tput setaf 2`,
red => `tput setaf 1`);
return \%COLORS;
}
%COLORS = @NOCOLORS;
return \%COLORS;
}