Skip to content

Commit 21a4a44

Browse files
authored
Merge branch 'main' into fix/media-controls-false-positives
2 parents 8c47409 + 431ad18 commit 21a4a44

File tree

14 files changed

+222
-2
lines changed

14 files changed

+222
-2
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Field Test #10: Just Perfection
2+
3+
## Pre-flight
4+
5+
- **Extension**: Just Perfection (`just-perfection-desktop@just-perfection`)
6+
- **Source**: https://github.com/jrahmatzadeh/just-perfection
7+
- **GNOME versions**: 45, 46, 47, 48, 49, 50
8+
- **File count**: 7 JS files, 2 CSS files; ~5,300 JS lines (Manager.js alone is 1,636 non-blank)
9+
- **License**: GPL-3.0-only (in repo root, not in `src/`)
10+
- **Why this extension**: Most popular GNOME extension; exercises connectObject/disconnectObject signal management (67 signals), prototype backup/restore via `#originals` map, `src/` directory layout, GNOME 50 support (widest version range), CSS-heavy (23.7%), private field usage (`#timeoutIds`, `#originals`, `#shellVersion`), 20 Shell.ui imports
11+
12+
## Step 1: ego-lint
13+
14+
```bash
15+
./ego-lint --verbose /tmp/just-perfection/src
16+
```
17+
18+
### Results (before fixes)
19+
20+
| Status | Count |
21+
|--------|-------|
22+
| PASS | 202 |
23+
| FAIL | 3 |
24+
| WARN | 414 |
25+
| SKIP | 12 |
26+
| Exit | 1 |
27+
28+
### Classification of Each FAIL
29+
30+
| Rule | File:Line | TP/FP | Root Cause (if FP) |
31+
|------|-----------|-------|-------------------|
32+
| `license` || **FP** | LICENSE in repo root, not in `src/`; no parent-dir fallback for src/ layout |
33+
| `R-VER48-07` | stylesheet.css:167 | TP | `.quick-menu-toggle` renamed to `.quick-toggle-has-menu` in GNOME 48 |
34+
| `metadata/future-shell-version` || TP | GNOME 50 > CURRENT_STABLE (49) |
35+
36+
### Classification of Key WARNs
37+
38+
| Rule | Count | Classification | Notes |
39+
|------|-------|---------------|-------|
40+
| R-SLOP-01 (JSDoc @param) | 129 | **FP** | Provenance score=3 but threshold was >=4; PR #23 fixes threshold to >=3 |
41+
| R-SLOP-02 (JSDoc @returns) | 271 | **FP** | Same root cause as R-SLOP-01 |
42+
| `lifecycle/prototype-override` | 5 (3 unique) | TP + **bug** | Advisory is valid (prototypes restored via close() chain). Regex catches both override AND restore → duplicates |
43+
| `metadata/uuid-matches-dir` | 1 | **FP** | Dir is `src`, not UUID; meaningless for src/ layout |
44+
| `metadata/session-modes-consistency` | 1 | **FP** | `sessionMode.isLocked` used as guard — not lock screen functionality |
45+
| `quality/impossible-state` | 1 | **FP** | Same isLocked guard pattern |
46+
| `quality/comment-density` | 1 | **FP** | SupportNotifier.js has 40% comments from thorough hand-written JSDoc |
47+
| `metadata/deprecated-version` | 1 | TP | `version` field ignored by EGO for GNOME 45+ |
48+
| `quality/file-complexity` | 1 | TP | Manager.js 1,636 non-blank lines |
49+
| `quality/private-api` | 1 | TP | statusArea access (legitimate for a UI tweaker) |
50+
| `disclosure/private-api` | 1 | TP | Private API not disclosed in metadata description |
51+
52+
## Fixes Implemented
53+
54+
Split across 3 PRs per one-concern-per-PR convention:
55+
56+
### PR: fix/src-layout-license (closes #26)
57+
58+
| Rule/Check | Root Cause | Fix |
59+
|------------|-----------|-----|
60+
| `license` (FAIL) | No parent-dir fallback for src/ layout | `ego-lint.sh`: check parent dir when `basename == "src"` |
61+
| `metadata/uuid-matches-dir` | Dir is `src` for src/ layout | `check-metadata.py`: skip when `dir_name == "src"` |
62+
63+
### PR: fix/islocked-guard (closes #27)
64+
65+
| Rule/Check | Root Cause | Fix |
66+
|------------|-----------|-----|
67+
| `session-modes-consistency` | `isLocked` is always a guard | `check-metadata.py`: narrow regex to `sessionMode.currentMode` only |
68+
| `quality/impossible-state` | `isLocked` is always a guard | `check-quality.py`: remove `isLocked` from check, keep `currentMode` comparisons |
69+
| `quality/comment-density` | 40% threshold too aggressive | `check-quality.py`: raise threshold from 40% to 50% |
70+
71+
### PR: fix/proto-override-dedup (closes #28)
72+
73+
| Rule/Check | Root Cause | Fix |
74+
|------------|-----------|-----|
75+
| `lifecycle/prototype-override` (dupes) | Regex matches override + restore | `check-lifecycle.py`: deduplicate by `(file, prototype)` key |
76+
77+
### Test Fixtures Added
78+
79+
| Fixture | What It Tests |
80+
|---------|--------------|
81+
| `src-license-fallback@test` | License found in parent dir for src/ layout; UUID-dir skip |
82+
| `islocked-guard@test` | `sessionMode.isLocked` guard not flagged as impossible-state or session-modes inconsistency |
83+
| `proto-override-dedup@test` | Each prototype warned only once despite override + restore pattern |
84+
85+
### Existing Fixture Updated
86+
87+
| Fixture | Change | Reason |
88+
|---------|--------|--------|
89+
| `ai-slop@test` | `sessionMode.isLocked``sessionMode.currentMode !== 'unlock-dialog'` | `isLocked` no longer triggers impossible-state (by design) |
90+
91+
## Results (after all fixes)
92+
93+
| Status | Before | After | Change |
94+
|--------|--------|-------|--------|
95+
| FAIL | 3 | 2 | -1 (license FP fixed) |
96+
| WARN | 414 | 408 | -6 (5 FPs + 2 dupes fixed) |
97+
98+
**With PR #23 merged** (provenance threshold >=3): WARNs would drop to ~8, all true positives.
99+
100+
## Calibration Lessons Learned
101+
102+
1. **`sessionMode.isLocked` is always a guard**: Reading lock state to decide behavior is defensive coding, not lock screen functionality. Only `currentMode === 'unlock-dialog'` comparisons suggest active lock screen mode usage.
103+
104+
2. **`src/` layout needs holistic support**: License check and UUID-dir match didn't handle src/ layout. Multiple checks already had src/ fallbacks (CSS, prefs, metadata), but these two were missed.
105+
106+
3. **Prototype override + restore creates duplicates**: When an extension stores originals and restores them (Just Perfection's `#originals` map pattern), the same regex catches both the override and the restore. Deduplication by `(file, prototype_name)` is essential.
107+
108+
4. **Comment-density threshold interacts with provenance**: Well-documented hand-written code (provenance=3) naturally has higher comment density (40%+) from thorough JSDoc. The 40% threshold was too aggressive; 50% avoids FPs while still catching AI slop (typically 55-70%).
109+
110+
5. **Just Perfection validates PR #23**: Provenance score=3 with 400 JSDoc warnings confirms the threshold change from >=4 to >=3 is needed.

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

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,11 @@ def main():
173173
result("FAIL", "metadata/uuid-format", f"UUID contains invalid characters: {uuid}")
174174

175175
# UUID matches directory name (advisory — cloned repos often differ)
176+
# Skip for src/ layout — dirname is "src", not the UUID
176177
if uuid == dir_name:
177178
result("PASS", "metadata/uuid-matches-dir", f"UUID matches directory name")
179+
elif dir_name == "src":
180+
result("PASS", "metadata/uuid-matches-dir", f"UUID check skipped for src/ layout")
178181
else:
179182
result("WARN", "metadata/uuid-matches-dir", f"UUID '{uuid}' does not match directory '{dir_name}' — must match when installed")
180183

skills/ego-lint/scripts/ego-lint.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,17 @@ for candidate in LICENSE COPYING LICENSE.rst LICENSE.md LICENSE.txt COPYING.rst
205205
fi
206206
done
207207

208+
# src/ layout fallback: check parent directory for license
209+
if [[ -z "$license_file" && "$(basename "$EXT_DIR")" == "src" ]]; then
210+
parent_dir="$(dirname "$EXT_DIR")"
211+
for candidate in LICENSE COPYING LICENSE.rst LICENSE.md LICENSE.txt COPYING.rst COPYING.md COPYING.txt; do
212+
if [[ -f "$parent_dir/$candidate" ]]; then
213+
license_file="$parent_dir/$candidate"
214+
break
215+
fi
216+
done
217+
fi
218+
208219
if [[ -n "$license_file" ]]; then
209220
head_content=$(head -5 "$license_file" 2>/dev/null || true)
210221
if echo "$head_content" | grep -qiE '(GPL|LGPL|MIT|BSD|Apache|MPL|ISC|Artistic)'; then
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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Field test #10: src/ layout license fallback and UUID-dir skip (closes #26)
2+
3+
# --- src-license-fallback (LICENSE in parent dir of src/) ---
4+
echo "=== src-license-fallback ==="
5+
run_lint "src-license-fallback@test/src"
6+
assert_exit_code "exits with 0 (license found in parent dir)" 0
7+
assert_output_contains "license found via parent fallback" "\[PASS\].*license"
8+
assert_output_contains "UUID-dir skipped for src/ layout" "\[PASS\].*metadata/uuid-matches-dir.*src/ layout"
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+
}

0 commit comments

Comments
 (0)