diff --git a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-changed b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-changed index 0055d86c0..7648ee21e 100755 --- a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-changed +++ b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-changed @@ -241,6 +241,10 @@ class Changed: m = narrowest(ln, {'method', 'function', 'constructor', 'module'}) t = narrowest(ln, {'class', 'interface', 'enum', 'type', 'namespace'}) key = None + # a callable that starts on the type's own header line and ends within the type is one the front end + # synthesised for it (an implicit constructor, a Python class body): the header is the TYPE's, or a new class + # is named by it (#1377). A file's module declaration also starts on line 1 but spans past the type + if m and t and m[0] == t[0] and m[1] <= t[1] and t[0] <= ln <= self.header_end(OL, t[0]): m = None if f: key = ('field', f) elif m and m[0] <= ln <= self.header_end(OL, m[0]): key = ('signature', m) elif m: key = ('body', m) @@ -271,7 +275,10 @@ class Changed: span_lines = [x for x in range(a, min(b, len(OL)) + 1) if not noise(OL[x - 1])] gone = a in removed_lines and span_lines and sum(1 for x in span_lines if x in removed_lines) >= 0.6 * len(span_lines) and \ not any(re.search(rf'\b{re.escape(n)}\s*\(', NL[j - 1]) and ndepth_ok(j) for j in range(max(1, (new_of.get(a) or a) - 3), min(len(NL), (new_of.get(a) or a) + 3) + 1)) - if gone: entry['kind'] = 'removed'; entry['target'] = self.target(kind, d, k, n); out.append(entry); continue + if gone: + # a header line and a body line of one declaration are two keys; it is removed once + if not any(e['kind'] == 'removed' and e['id'] == i for e in out): entry['kind'] = 'removed'; entry['target'] = self.target(kind, d, k, n); out.append(entry) + continue if kind == 'signature': # compared with comments removed: `f( /* note */ a)` is `f(a)`. Strings stay, so an edited route in a # decoration is still a header change diff --git a/tests/cases/java/removed-class-listed-once/case.json b/tests/cases/java/removed-class-listed-once/case.json new file mode 100644 index 000000000..aa96f6822 --- /dev/null +++ b/tests/cases/java/removed-class-listed-once/case.json @@ -0,0 +1,6 @@ +{"lang": "java", "src": "src", + "checks": [ + {"why": "a removed class and its one method are two declarations, each listed once: the method's header line and its body lines used to give it two rows, and the class was named by its implicit constructor instead of by its own name", + "run": ["changed", "{repo}", "--old", "{repo}/old.java", "--new", "{repo}/new.java", "--file", "src/Meter.java"], + "want": ["changed declarations (2)", "removed Meter ", "Meter.read"], + "avoid": ["Meter.Meter"]}]} diff --git a/tests/cases/java/removed-class-listed-once/new.java b/tests/cases/java/removed-class-listed-once/new.java new file mode 100644 index 000000000..e69de29bb diff --git a/tests/cases/java/removed-class-listed-once/old.java b/tests/cases/java/removed-class-listed-once/old.java new file mode 100644 index 000000000..7fcc4f668 --- /dev/null +++ b/tests/cases/java/removed-class-listed-once/old.java @@ -0,0 +1,6 @@ +class Meter { + int read(int raw) { + int value = raw * 2; + return value; + } +} diff --git a/tests/cases/java/removed-class-listed-once/src/Meter.java b/tests/cases/java/removed-class-listed-once/src/Meter.java new file mode 100644 index 000000000..7fcc4f668 --- /dev/null +++ b/tests/cases/java/removed-class-listed-once/src/Meter.java @@ -0,0 +1,6 @@ +class Meter { + int read(int raw) { + int value = raw * 2; + return value; + } +} diff --git a/tests/cases/python/removed-class-listed-once/case.json b/tests/cases/python/removed-class-listed-once/case.json new file mode 100644 index 000000000..d7841ce48 --- /dev/null +++ b/tests/cases/python/removed-class-listed-once/case.json @@ -0,0 +1,6 @@ +{"lang": "python", "src": ".", + "checks": [ + {"why": "a removed class and its one method are two declarations, each listed once: the method's header line and its body lines used to give it two rows, and the class was named by its class-body initializer instead of by its own name", + "run": ["changed", "{repo}", "--old", "{repo}/old.txt", "--new", "{repo}/new.txt", "--file", "lib.py"], + "want": ["changed declarations (2)", "removed Meter ", "Meter.read"], + "avoid": [""]}]} diff --git a/tests/cases/python/removed-class-listed-once/lib.py b/tests/cases/python/removed-class-listed-once/lib.py new file mode 100644 index 000000000..e77e26305 --- /dev/null +++ b/tests/cases/python/removed-class-listed-once/lib.py @@ -0,0 +1,4 @@ +class Meter: + def read(self, raw): + value = raw * 2 + return value diff --git a/tests/cases/python/removed-class-listed-once/new.txt b/tests/cases/python/removed-class-listed-once/new.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/cases/python/removed-class-listed-once/old.txt b/tests/cases/python/removed-class-listed-once/old.txt new file mode 100644 index 000000000..e77e26305 --- /dev/null +++ b/tests/cases/python/removed-class-listed-once/old.txt @@ -0,0 +1,4 @@ +class Meter: + def read(self, raw): + value = raw * 2 + return value