File size: 2,252 Bytes
4951e76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Find the functions whose bodies changed between two unpacked crate versions (pre-side names), by line-range
overlap of difflib opcodes with a regex-located enclosing `fn`. Heuristic (no parser), documented as such."""
import difflib, os, re

FN = re.compile(r"^\s*(?:pub(?:\([^)]*\))?\s+)?(?:const\s+)?(?:unsafe\s+)?(?:async\s+)?(?:extern\s+\"[^\"]*\"\s+)?fn\s+([A-Za-z_]\w*)")


def modpath(rel):
    p = rel[len("src/"):-3].split("/")
    if p[-1] in ("mod", "lib"): p = p[:-1]
    return p


def changed_functions(pre, post):
    out = []
    for dp, _, fs in os.walk(os.path.join(pre, "src")):
        for f in fs:
            if not f.endswith(".rs"): continue
            rel = os.path.relpath(os.path.join(dp, f), pre)
            if rel.endswith("main.rs") and rel == "src/main.rs": continue
            a = open(os.path.join(pre, rel), encoding="utf8", errors="replace").read().splitlines()
            pp = os.path.join(post, rel)
            b = open(pp, encoding="utf8", errors="replace").read().splitlines() if os.path.exists(pp) else []
            if a == b: continue
            # skip test code: everything from a `#[cfg(test)]` line on, and fns directly under `#[test]`
            cut = next((i for i, l in enumerate(a) if re.match(r"\s*#\[cfg\(test\)\]", l)), len(a))
            starts = [(i, FN.match(l).group(1)) for i, l in enumerate(a[:cut]) if FN.match(l) and not any("#[test]" in a[k] for k in range(max(0, i - 3), i))]
            if cut < len(a):
                a, b = a[:cut], b[: next((i for i, l in enumerate(b) if re.match(r"\s*#\[cfg\(test\)\]", l)), len(b))]
            for tag, i1, i2, j1, j2 in difflib.SequenceMatcher(None, a, b, autojunk=False).get_opcodes():
                if tag == "equal": continue
                for ln in sorted({i1, max(i2 - 1, i1)}):
                    cands = [s for s in starts if s[0] <= ln]
                    if cands:
                        name = cands[-1][1]
                        # a `fn` header preceding the changed line only counts if the change is within a plausible body (<= 400 lines)
                        if ln - cands[-1][0] <= 400:
                            out.append((rel, ".".join(modpath(rel)).replace(".", "::"), name))
    return sorted(set(out))