aboutsummaryrefslogtreecommitdiff
path: root/Scripts/normalize-zig-issues.pl
blob: b6ff328ac0d4d14d0603af5b2f07b403d1029753 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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 = <STDIN>) {
  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;
}