Skip to content

Commit 431ad18

Browse files
ZviBaratzclaude
andauthored
fix(ego-lint): deduplicate prototype-override warnings (#32)
## Summary Field test #10 (Just Perfection) revealed that the prototype-override detection regex `\w+\.prototype\.\w+\s*=` matches both the override and the restore, causing each prototype to appear twice (5 warnings for 3 unique prototypes). - Deduplicate `prototype_overrides` by `(file, prototype_name)` before emitting warnings Closes #28 ## Test plan - [x] New fixture `proto-override-dedup@test` with override + restore pattern - [x] Verifies 2 unique warnings (not 4) for 2 prototypes - [x] 454 tests pass (452 baseline + 2 new) - [x] Verify no regressions on other field-tested extensions 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9f616ec commit 431ad18

File tree

7 files changed

+75
-2
lines changed

7 files changed

+75
-2
lines changed

skills/ego-lint/scripts/check-lifecycle.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,15 +374,23 @@ def check_injection_manager(ext_dir):
374374

375375
# WS1-D: Detect direct prototype overrides
376376
prototype_overrides = []
377+
seen_overrides = set()
377378
for filepath in js_files:
378379
content = strip_comments(read_file(filepath))
379380
rel = os.path.relpath(filepath, ext_dir)
380381
# SomeClass.prototype.methodName = ...
381382
for m in re.finditer(r'(\w+\.prototype\.\w+)\s*=', content):
382-
prototype_overrides.append((rel, m.group(1)))
383+
key = (rel, m.group(1))
384+
if key not in seen_overrides:
385+
seen_overrides.add(key)
386+
prototype_overrides.append(key)
383387
# Object.assign(SomeClass.prototype, ...)
384388
for m in re.finditer(r'Object\.assign\s*\(\s*(\w+\.prototype)', content):
385-
prototype_overrides.append((rel, f"Object.assign({m.group(1)}, ...)"))
389+
label = f"Object.assign({m.group(1)}, ...)"
390+
key = (rel, label)
391+
if key not in seen_overrides:
392+
seen_overrides.add(key)
393+
prototype_overrides.append(key)
386394

387395
if prototype_overrides:
388396
# Check if disable() restores prototypes
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Field test #10: prototype-override deduplication (closes #28)
2+
3+
# --- proto-override-dedup (each prototype warned only once) ---
4+
echo "=== proto-override-dedup ==="
5+
run_lint "proto-override-dedup@test"
6+
assert_output_contains "prototype override detected" "\[WARN\].*lifecycle/prototype-override.*BackgroundMenu.prototype.open"
7+
assert_output_contains "search prototype override detected" "\[WARN\].*lifecycle/prototype-override.*SearchController.prototype.startSearch"
8+
assert_output_count "exactly 2 prototype-override warnings (dedup works)" "\[WARN\].*lifecycle/prototype-override" 2
9+
echo ""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SPDX-License-Identifier: GPL-2.0-or-later
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
2+
import {API} from './lib/api.js';
3+
4+
export default class TestExtension extends Extension {
5+
enable() {
6+
this._api = new API();
7+
this._api.open();
8+
}
9+
10+
disable() {
11+
this._api.close();
12+
this._api = null;
13+
}
14+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as BackgroundMenu from 'resource:///org/gnome/shell/ui/backgroundMenu.js';
2+
import * as Search from 'resource:///org/gnome/shell/ui/search.js';
3+
4+
export class API {
5+
#originals = {};
6+
7+
open() {
8+
// Override prototype — saves original first
9+
this.#originals['bgOpen'] = BackgroundMenu.BackgroundMenu.prototype.open;
10+
BackgroundMenu.BackgroundMenu.prototype.open = () => {};
11+
12+
this.#originals['startSearch'] = Search.SearchController.prototype.startSearch;
13+
Search.SearchController.prototype.startSearch = () => {};
14+
}
15+
16+
close() {
17+
// Restore originals
18+
BackgroundMenu.BackgroundMenu.prototype.open = this.#originals['bgOpen'];
19+
Search.SearchController.prototype.startSearch = this.#originals['startSearch'];
20+
}
21+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"uuid": "proto-override-dedup@test",
3+
"name": "Test Proto Override Dedup",
4+
"description": "Tests that prototype override warnings are deduplicated",
5+
"shell-version": ["48"],
6+
"url": "https://example.com"
7+
}

tests/run-tests.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ assert_exit_code() {
4545
fi
4646
}
4747

48+
assert_output_count() {
49+
local label="$1" pattern="$2" expected="$3"
50+
local actual
51+
actual=$(echo "$output" | grep -cE "$pattern" || true)
52+
if [[ "$actual" -eq "$expected" ]]; then
53+
echo -e " ${GREEN}${NC} $label (count: $actual)"
54+
PASS_COUNT=$((PASS_COUNT + 1))
55+
else
56+
echo -e " ${RED}${NC} $label (expected $expected matches, got $actual)"
57+
FAIL_COUNT=$((FAIL_COUNT + 1))
58+
fi
59+
}
60+
4861
run_lint() {
4962
local fixture="$1"
5063
output=""

0 commit comments

Comments
 (0)