use strict; use warnings; $| = 1; my $base = $ENV{NOVA_ZIG_TASK_CWD} || $ENV{PWD} || ""; my %file_cache = (); sub normalize_path { my ($path) = @_; return $path if $path =~ m{^/} || $path =~ m{^[A-Za-z]:[\/\\]}; # The Zig compiler occasionally emits paths without a leading slash (e.g. # "Users/..." instead of "/Users/...") when traversing relative directories. # Detect known top-level directory names and restore the leading slash. if ($path =~ m{^(?:Users|private|opt|Library|System|usr|var|tmp|etc|home)/}) { return "/$path"; } return $base eq "" ? $path : "$base/$path"; } sub source_line_for_path { my ($path, $line_number) = @_; return undef if $line_number < 1; if (!exists $file_cache{$path}) { if (open my $fh, "<", $path) { my @lines = <$fh>; close $fh; $file_cache{$path} = \@lines; } else { $file_cache{$path} = undef; } } my $lines = $file_cache{$path}; return undef if !defined $lines; return undef if $line_number > scalar(@$lines); my $line = $lines->[$line_number - 1]; $line =~ s/\r?\n$//; return $line; } # Zig reports argument-count errors at the column of the opening parenthesis, # which causes Nova to underline the entire argument list. For method-call # expressions (receiver.method(...)), shift the column back to the start of # the callee identifier so only the call site itself is highlighted. sub adjusted_call_column { my ($source_line, $column) = @_; return $column if !defined $source_line || $column < 1; my $column_index = $column - 1; my $open_paren = index($source_line, "(", $column_index); return $column if $open_paren < 0; my $prefix = substr($source_line, 0, $open_paren); return $column if $prefix !~ /([A-Za-z_][A-Za-z0-9_]*)\s*$/; my $callee = $1; my $callee_index = rindex($prefix, $callee); return $column if $callee_index < 0; my $target_column = $callee_index + 1; return $column if $target_column <= $column; my $between = substr($source_line, $column_index, $target_column - $column); return $column if $between !~ /\./; return $target_column; } while (my $line = ) { my $newline = $line =~ s/\r?\n$// ? "\n" : ""; if ($line =~ m{^([^:\n]+\.zig):(\d+):(\d+):\s*(error|warning|note):\s*(.*)$}) { my ($path, $line_number, $column, $severity, $message) = ($1, $2, $3, $4, $5); my $normalized = normalize_path($path); if ($message =~ /\bexpected\b.*argument\(s\).*\bfound\b/i) { my $source_line = source_line_for_path($normalized, $line_number); $column = adjusted_call_column($source_line, $column); } $line = "$normalized:$line_number:$column: $severity: $message"; } elsif ($line =~ m{^([^:\n]+\.zig):(\d+):(\d+):}) { my ($path, $line_number, $column) = ($1, $2, $3); my $normalized = normalize_path($path); $line =~ s{^[^:\n]+\.zig:\d+:\d+:}{$normalized:$line_number:$column:}; } print $line, $newline; }