1#!/usr/bin/perl
2
3# How to use:
4#
5# Step 1: run release-notes diff old-jsapi.h new-jsapi.h > diff.txt
6#
7# Step 2: edit diff.txt
8#  - when a function has been renamed, get the - and + lines adjacent and mark the - line with [renamed] at the end
9#  - when a function has been replaced, do the same (replacements behave differently)
10#  - for anything that isn't a simple addition, deletion, rename, or replace, tag with [other]
11#    (things tagged [other] will be put in a separate section for manual fixup)
12#
13# Step 3: run release-notes < diff.txt > changes.txt
14#  - this will group changes into sections and annotate them with bug numbers
15#  - the bugs chosen are just the bug that last touched each line, and are unlikely to be entirely accurate
16#
17# Step 4: run release-notes mdn < changes.txt > final.txt
18#  - this will add an MDN link to every list item, first checking whether such a link is valid
19#
20# Step 5: paste into the MDN page, eg https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Releases/45
21
22# Upcoming: basing everything off of jsapi.h is probably not going to work for
23# much longer, given that more stuff is moving into js/public. Scan
24# js/public/*.h too and record where everything comes from (to automate header
25# changes in the notes)?
26#
27# This is only looking at C style APIs. Dump out all methods too?
28#
29# The enbuggification should be split out into a separate phase because it is
30# wrong a fair amount of the time (whitespace changes, parameter changes,
31# etc.), and should have a way of running repeatedly so you can incrementally
32# fix stuff up.
33#
34# It would be very nice to have an example program that links against mozjs,
35# tested in CI, so we can diff that for release notes.
36
37use strict;
38use warnings;
39
40if (@ARGV && $ARGV[0] eq 'diff') {
41    my ($orig_file, $new_file) = @ARGV[1..2];
42    my $orig_api = grab_api($orig_file);
43    my $new_api = grab_api($new_file);
44    diff_apis($orig_api, $new_api);
45    exit 0;
46}
47
48my $path = "/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_Reference";
49my $url_prefix = "https://developer.mozilla.org$path";
50
51if (@ARGV && $ARGV[0] eq 'mdn') {
52    shift(@ARGV);
53    while(<>) {
54        if (/<li>([\w:]+)/) {
55            print STDERR "Checking $1...\n";
56            system("wget", "-q", "$url_prefix/$1");
57            if ($? == 0) {
58                s!<li>([\w:]+)!<li><a href="$path/$1">$1</a>!;
59            }
60        }
61        print;
62    }
63    exit 0;
64}
65
66sub grab_api {
67    my ($file) = @_;
68    open(my $fh, "<", $file) or die "open $file: $!";
69    my $grabbing;
70    my @api;
71    while(<$fh>) {
72        if ($grabbing && /^(\w+)/) {
73            push @api, $1;
74        }
75        $grabbing = /JS_PUBLIC_API/;
76    }
77    return \@api;
78}
79
80sub diff_apis {
81    my ($old, $new) = @_;
82    my %old;
83    @old{@$old} = ();
84    my %new;
85    @new{@$new} = ();
86
87    open(my $ofh, ">", "/tmp/r-c.diff.1");
88    print $ofh "$_\n" foreach (@$old);
89    close $ofh;
90    open(my $nfh, ">", "/tmp/r-c.diff.2");
91    print $nfh "$_\n" foreach (@$new);
92    close $nfh;
93    open(my $diff, "diff -u /tmp/r-c.diff.1 /tmp/r-c.diff.2 |");
94    while(<$diff>) {
95        if (/^-(\w+)/) {
96            next if exists $new{$1}; # Still exists, so skip it
97        } elsif (/^\+(\w+)/) {
98            next if exists $old{$1}; # It was already there, skip it
99        }
100        print;
101    }
102}
103
104my @added;
105my @renamed;
106my @replaced;
107my @deleted;
108my @other;
109
110my %N;
111
112my $renaming;
113my $replacing;
114while (<>) {
115    my $name;
116    if (/^[ +-](\w+)/) {
117        $name = $1;
118        $N{$name} = $name =~ /^JS_/ ? $name : "JS::$name";
119    }
120
121    if (/^-/) {
122        die if ! $name;
123        if (/\[rename\]/) {
124            $renaming = $name;
125        } elsif (/\[replace\]/) {
126            $replacing = $name;
127        } elsif (/\[other\]/) {
128            push @other, $name;
129        } else {
130            push @deleted, $name;
131        }
132    } elsif (/^\+/) {
133        die if ! $name;
134        if ($renaming) {
135            push @renamed, [ $renaming, $name ];
136            undef $renaming;
137        } elsif ($replacing) {
138            push @replaced, [ $replacing, $name ];
139            undef $replacing;
140        } elsif (/\[other\]/) {
141            push @other, $name;
142        } else {
143            push @added, $name;
144        }
145    }
146}
147
148open(my $fh, "<", "jsapi.blame") or die "open jsapi.blame: $!";
149my $grabbing;
150my %changerev;
151my %revs;
152while(<$fh>) {
153    if ($grabbing && /^\s*(\d+): (\w+)/ ) {
154        $changerev{$2} = $1;
155        $revs{$1} = 1;
156    }
157    $grabbing = /JS_PUBLIC_API/;
158}
159
160my %bug;
161for my $rev (keys %revs) {
162    open(my $fh, "hg log -r $rev -T '{desc}' |");
163    while(<$fh>) {
164        if (/[bB]ug (\d+)/) {
165            $bug{$rev} = $1;
166        }
167    }
168}
169
170sub get_bug_suffix {
171    my ($api) = @_;
172    $DB::single = 1 if ! $changerev{$api};
173    my $bug = $bug{$changerev{$api}};
174    return $bug ? " {{{bug($bug)}}}" : "";
175}
176
177print "(new apis)\n";
178print "<ul>\n";
179print "  <li>$N{$_}" . get_bug_suffix($_) . "</li>\n" foreach @added;
180print "  <li>$N{$_->[0]} renamed to $N{$_->[1]}" . get_bug_suffix($_->[1]) . "</li>\n" foreach @renamed;
181print "  <li>$N{$_->[0]} replaced with $N{$_->[1]}" . get_bug_suffix($_->[1]) . "</li>\n" foreach @replaced;
182print "</ul>\n";
183print "\n";
184
185print qq(<h2 id="Deleted_APIs">Deleted APIs</h2>\n);
186print "<ul>\n";
187print "  <li>$N{$_}</li>\n" foreach @deleted;
188print "</ul>\n";
189print "\n";
190
191print qq(<h2 id="Changed_APIs">Changed APIs</h2>\n);
192print "<ul>\n";
193print "  <li>$N{$_}" . get_bug_suffix($_) . "</li>\n" foreach @other;
194print "</ul>\n";
195print "\n";
196