Skip to content

Commit fc6c771

Browse files
Merge pull request #1846 from AxiomCodeAI/apps/java/changed-maps-hunks-to-wrong-declaration
changed: a declaration longer or gone in the base than in the graph's text keeps its own lines
2 parents 0df85ac + 8dfdb4d commit fc6c771

11 files changed

Lines changed: 195 additions & 5 deletions

File tree

‎plugins/axiomcode/skills/axiomcode/scripts/axiomcode-changed‎

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,27 @@ class Changed:
365365
elif tag == 'replace': near[j1 + k_ + 1] = i1 + min(k_ + 1, i2 - i1) # a rewritten line: the line in its position
366366
else: near[j1 + k_ + 1] = max(1, i1) # a line this text lacks: the one before it
367367
taken = {exact[a] for a, b, i, k, d, n in spans if a in exact}
368+
# A DECLARATION CAN BE LONGER IN THIS TEXT THAN IN THE GRAPH'S. Its end is carried to the line the graph's end
369+
# line became, so the lines this text has after it and the graph's text does not (statements a later text
370+
# deleted) fell outside it: removed from a method, they were charged to the declaration below or to nothing.
371+
# The span runs on over lines only this text has while they are still inside it: its braces not yet closed, or
372+
# (Python) indented deeper than its header
373+
mine = set(exact.values())
374+
def grow(a2, b2, k):
375+
if k not in ('method', 'function', 'constructor', 'class', 'interface', 'enum', 'type') or b2 >= len(OL): return b2
376+
if is_py:
377+
ind = len(OL[a2 - 1]) - len(OL[a2 - 1].lstrip()); x = b2
378+
while x < len(OL) and x + 1 not in mine and (not OL[x].strip() or len(OL[x]) - len(OL[x].lstrip()) > ind):
379+
x += 1
380+
if OL[x - 1].strip(): b2 = x
381+
return b2
382+
depth = sum(OS[x - 1].count('{') - OS[x - 1].count('}') for x in range(a2, b2 + 1))
383+
if depth <= 0 or '{' not in ''.join(OS[a2 - 1:b2]): return b2
384+
x = b2
385+
while x < len(OL) and x + 1 not in mine:
386+
x += 1; depth += OS[x - 1].count('{') - OS[x - 1].count('}')
387+
if depth <= 0: return x
388+
return b2
368389
def renamed_line(a, d, n, k):
369390
"""A HEADER THE GRAPH HOLDS UNDER A NEW NAME is the same declaration when this text's line in its place declares
370391
another name with the same parameters (a rename the refresh already indexed). Dropped as "not declared here",
@@ -388,7 +409,7 @@ class Changed:
388409
b2 = a2 + (b - a) if exact.get(b) is None else exact[b]
389410
else:
390411
b2 = exact.get(b) or near.get(b) or a2 + (b - a)
391-
out.append((a2, max(a2, b2), i, k, d, n))
412+
out.append((a2, grow(a2, max(a2, b2), k), i, k, d, n))
392413
out.sort(); return out
393414
def rel(self, f):
394415
f = os.path.realpath(f) if os.path.exists(f) else f
@@ -797,7 +818,7 @@ class Changed:
797818
for a, b, i, k, d, n in decls:
798819
if ln < a <= ln + 3 and all(not L[x - 1].strip() or L[x - 1].strip().startswith('@') for x in range(ln + 1, a)): return (a, b, i, k, d, n)
799820
return None
800-
decorated = {}; stmt_pair = {}
821+
decorated = {}; stmt_pair = {}; gone_fields = []
801822
OK = strip_code(old, strings=False, hash_comments=is_py).split('\n'); NK = strip_code(new, strings=False, hash_comments=is_py).split('\n')
802823
# A LINE INSERTED INTO A HEADER CHANGES THE SIGNATURE. A parameter list written one parameter a line gains a
803824
# parameter as an inserted line, which no old line was changed for; charged to the callable as an insertion it
@@ -866,12 +887,20 @@ class Changed:
866887
elif m and m[3] != 'module' and m[0] <= ln <= self.header_end(OL, m[0]): key = ('signature', m)
867888
elif m: key = ('body', m)
868889
elif t and t[0] <= ln <= self.header_end(OL, t[0]): key = ('type', t)
890+
# A FIELD THE GRAPH NEVER HELD is still named by its line. Read against a graph from a later text (a range, a
891+
# refresh), a field that text no longer declares has no span, and its deleted line came back as an anonymous
892+
# "inside <type>": the mirror of a new field, named from its inserted line (added)
893+
elif t and not is_py and (fp := self.field_parts(OS[ln - 1]))[0] and OS[ln - 1].rstrip().endswith(';') \
894+
and '(' not in OS[ln - 1].split('=')[0] and self.declares(OS[ln - 1], fp[1], 'field', False) \
895+
and not still_declared(fp[1], 'field', new_of.get(ln) or ln):
896+
gone_fields.append((ln, t, fp[1]))
869897
elif t: key = ('inside', t) # between members: a field the graph did not record, a comment
870898
if key: hits.setdefault(key, set()).add(ln)
871899
nd = [0] * (len(NL) + 2)
872900
for ii, text in enumerate(strip_code(new).split('\n'), 1): nd[ii] = nd[ii - 1] + text.count('{') - text.count('}')
873901
ndepth_ok = lambda j: nd[j - 1] <= 2
874-
out = []
902+
out = [dict(kind='field', symbol=f"{t[4]}.{nm}", id=None, file=rel, line=ln, end=ln, old_lines=[ln], target_kind='field',
903+
detail='removed or renamed', target=None) for ln, t, nm in gone_fields]
875904
KIND_OF = lambda k: 'field' if k in ('field', 'const', 'enum_member', 'variable') else 'type' if k in ('class', 'interface', 'enum', 'type', 'namespace') else 'signature'
876905
KIND = lambda k: 'field' if k in ('field', 'const', 'enum_member', 'variable') else 'type' if k in ('class', 'interface', 'enum', 'type', 'namespace') else 'method'
877906
for (a, b, i, k, d, n), decs in decorated.items():
@@ -1568,6 +1597,9 @@ def main(argv):
15681597
for e in r:
15691598
if e.get('new_file'): e['kind'] = 'removed'; e['detail'] = 'the file is gone at the newer side'; continue
15701599
if e['kind'] == 'removed': e['kind'] = 'added (declaration)'; e['target'] = None
1600+
# and one the newer text lacks, read as new in the swapped pair, is one the range removed
1601+
elif e['kind'] == 'added':
1602+
e.update(kind='removed', detail=re.sub(r'^new ', '', e.get('detail') or '') + ' gone at the newer side', target=None); continue
15711603
if e.get('detail'):
15721604
e['detail'] = ' → '.join(reversed(e['detail'].split(' → '))) if ' → ' in e['detail'] and ':' not in e['detail'] else re.sub(r'(\w+): (.+?) → (.+?)(,|$)', r'\1: \3 → \2\4', e['detail'])
15731605
e['detail'] = re.sub(r'(^|, )([+-])(\w+)', lambda m: m.group(1) + ('-' if m.group(2) == '+' else '+') + m.group(3), e['detail'])
@@ -1626,7 +1658,7 @@ def main(argv):
16261658
+ ', '.join(e['symbol'] for e in es[:10]) + (f" … +{len(es) - 10}" if len(es) > 10 else ''))
16271659
# a note with a file is an addition in that file; one without is a caveat about the whole answer, and printing it
16281660
# under `added` made "the graph is built at the newer commit …" read as a new declaration
1629-
for f, n, _ in notes: print(f" {'added':<10} {n}" if f else f"note: {n}")
1661+
for f, n, _ in notes: print(f" {'removed' if ' line(s) gone at ' in n else 'added':<10} {n}" if f else f"note: {n}")
16301662
# CASE DATA IS NOT A TEST: a changed file in a case directory or a fixture tree is an input its runner reads
16311663
cd = case_data(C.repo, {e['file'] for e in results if e.get('file') and not e.get('new_file')} | set(outside))
16321664
if cd:
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{"lang": "java", "src": "src",
2+
"checks": [
3+
{"why": "a graph from a later text than both sides: lines removed from a method that the later text has shorter are that method's body, and a field and a constructor parameter removed above it are the field and the constructor's signature; the unchanged method below, whose lines shifted, is not listed",
4+
"run": ["changed", "{repo}", "--old", "{repo}/old.java", "--new", "{repo}/new.java", "--file", "src/pkg/Writer.java"],
5+
"want": ["field Writer.codec", "signature Writer.Writer", "body Writer.write"],
6+
"avoid": ["Writer.priority", "inside Writer", "added", "signature Writer.write"]},
7+
{"why": "control: an edit only inside the method below is that method's body and nothing else",
8+
"run": ["changed", "{repo}", "--old", "{repo}/new.java", "--new", "{repo}/later.java", "--file", "src/pkg/Writer.java"],
9+
"want": ["body Writer.priority"],
10+
"avoid": ["Writer.write", "Writer.Writer", "Writer.codec"]}]}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package pkg;
2+
3+
class Writer {
4+
private final Store store;
5+
6+
Writer(Store store) {
7+
this.store = store;
8+
}
9+
10+
void write(Event e) {
11+
store.append(e);
12+
}
13+
14+
static String priority(Event e) {
15+
return "high-" + e.id();
16+
}
17+
}
18+
19+
class Store { void save(Record r) {} void append(Event e) {} }
20+
class Codec { String encode(Event e) { return ""; } }
21+
class Record { Record(String id, String body) {} }
22+
class Event { String id() { return ""; } }
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package pkg;
2+
3+
class Writer {
4+
private final Store store;
5+
6+
Writer(Store store) {
7+
this.store = store;
8+
}
9+
10+
void write(Event e) {
11+
store.append(e);
12+
}
13+
14+
static String priority(Event e) {
15+
return "normal";
16+
}
17+
}
18+
19+
class Store { void save(Record r) {} void append(Event e) {} }
20+
class Codec { String encode(Event e) { return ""; } }
21+
class Record { Record(String id, String body) {} }
22+
class Event { String id() { return ""; } }
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package pkg;
2+
3+
class Writer {
4+
private final Store store;
5+
private final Codec codec;
6+
7+
Writer(Store store, Codec codec) {
8+
this.store = store;
9+
this.codec = codec;
10+
}
11+
12+
void write(Event e) {
13+
store.append(e);
14+
String body = codec.encode(e);
15+
store.save(new Record(e.id(), body));
16+
}
17+
18+
static String priority(Event e) {
19+
return "normal";
20+
}
21+
}
22+
23+
class Store { void save(Record r) {} void append(Event e) {} }
24+
class Codec { String encode(Event e) { return ""; } }
25+
class Record { Record(String id, String body) {} }
26+
class Event { String id() { return ""; } }
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package pkg;
2+
3+
class Writer {
4+
private final Store store;
5+
6+
Writer(Store store) {
7+
this.store = store;
8+
}
9+
10+
void write(Event e) {
11+
store.append(e);
12+
}
13+
14+
static String priority(Event e) {
15+
return "high-" + e.id();
16+
}
17+
}
18+
19+
class Store { void save(Record r) {} void append(Event e) {} }
20+
class Codec { String encode(Event e) { return ""; } }
21+
class Record { Record(String id, String body) {} }
22+
class Event { String id() { return ""; } }

‎tests/cases/python/grown-file-read-against-its-base/case.json‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@
4747
"body Service.get",
4848
"inside Service"
4949
]
50-
}
50+
},
51+
{"why": "a graph from a later text than both sides: lines removed from a method that the later text has shorter are that method's body, and a parameter removed above it is the signature of `__init__`; the unchanged method below, whose lines shifted, is not listed",
52+
"run": ["changed", "{repo}", "--old", "{repo}/old-writer.py", "--new", "{repo}/new-writer.py", "--file", "src/writer.py"],
53+
"want": ["signature Writer.__init__", "body Writer.write"],
54+
"avoid": ["Writer.priority", "added"]},
55+
{"why": "control: an edit only inside the method below is that method's body and nothing else",
56+
"run": ["changed", "{repo}", "--old", "{repo}/new-writer.py", "--new", "{repo}/later-writer.py", "--file", "src/writer.py"],
57+
"want": ["body Writer.priority"],
58+
"avoid": ["Writer.write", "Writer.__init__"]}
5159
]
5260
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class Writer:
2+
3+
def __init__(self, store):
4+
self.store = store
5+
6+
def write(self, e):
7+
self.store.append(e)
8+
9+
@staticmethod
10+
def priority(e):
11+
return "high-" + e.id
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class Writer:
2+
3+
def __init__(self, store):
4+
self.store = store
5+
6+
def write(self, e):
7+
self.store.append(e)
8+
9+
@staticmethod
10+
def priority(e):
11+
return "normal"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class Writer:
2+
codec = None
3+
4+
def __init__(self, store, codec):
5+
self.store = store
6+
self.codec = codec
7+
8+
def write(self, e):
9+
self.store.append(e)
10+
body = self.codec.encode(e)
11+
self.store.save((e.id, body))
12+
13+
@staticmethod
14+
def priority(e):
15+
return "normal"

0 commit comments

Comments
 (0)