Skip to content

Commit 7203293

Browse files
ZviBaratzclaude
andauthored
fix(ego-lint): suppress fix text for multi-version extensions (#8)
fix(ego-lint): suppress fix text for multi-version extensions (#7) Add fix-min-version field to pattern rules that gates the fix suggestion based on the extension's minimum declared shell-version. R-VER48-04/04b still warn about the deprecated vertical property, but omit the "use orientation" fix when the extension targets GNOME <=46 where orientation is unavailable. Closes #7 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c00efee commit 7203293

File tree

9 files changed

+92
-10
lines changed

9 files changed

+92
-10
lines changed

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ bash skills/ego-lint/scripts/ego-lint.sh tests/fixtures/<fixture-name>
4444
`ego-lint.sh` is the main orchestrator. It uses a three-tier rule system (pattern → structural → semantic) and delegates to sub-scripts via `run_subscript`:
4545

4646
- `rules/patterns.yaml` — Tier 1 pattern rules (124 regex-based, declarative rules). Note: at project root, not under `skills/ego-lint/`
47-
- `apply-patterns.py` — Tier 1 pattern engine (inline YAML parser, no PyYAML dependency). Supports `guard-pattern` + `guard-window` (sliding `deque` lookback), `replacement-pattern`, `exclude-dirs`, version-gating
47+
- `apply-patterns.py` — Tier 1 pattern engine (inline YAML parser, no PyYAML dependency). Supports `guard-pattern` + `guard-window` (sliding `deque` lookback), `replacement-pattern`, `exclude-dirs`, version-gating, `fix-min-version` (suppresses fix text when extension min shell-version is below threshold)
4848
- `check-quality.py` — Tier 2 heuristic AI slop detection (try-catch density, impossible states, pendulum patterns, empty catches, _destroyed density, mock detection, constructor resources, run_dispose comment, clipboard disclosure, network disclosure, excessive null checks, repeated getSettings, obfuscated names, mixed indentation, excessive logging, code provenance)
4949
- `check-metadata.py` — JSON validity, required fields, UUID format/match, shell-version (with VALID_GNOME_VERSIONS allowlist), session-modes, settings-schema, version-name, donations, gettext-domain consistency
5050
- `check-init.py` — Init-time Shell modification, GObject constructor detection (extension.js only; all GI namespaces, GObject.registerClass exempt, GLib value types exempt), Gio._promisify placement

rules/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,28 @@ if (global.compositor) {
176176
- Only use when the default 1-line lookback is insufficient — most guards are on the same or previous line
177177
- Keep the window as small as practical to avoid false suppressions
178178

179+
### Fix Version Gating (`fix-min-version`)
180+
181+
When a deprecation rule suggests a fix that only works on newer GNOME versions, use `fix-min-version` to suppress the fix text for extensions whose minimum shell-version is below the threshold. The warning still fires (developers should know about deprecations), but the fix suggestion is omitted when applying it would break backward compatibility.
182+
183+
```yaml
184+
- id: R-VER48-04
185+
pattern: "\\.vertical\\s*="
186+
scope: ["*.js"]
187+
severity: advisory
188+
message: "St.Widget.vertical deprecated in GNOME 48; use orientation (available since GNOME 47; keep vertical for GNOME <=46 compat)"
189+
fix: "Use {orientation: Clutter.Orientation.VERTICAL} instead of {vertical: true}"
190+
min-version: 48
191+
fix-min-version: 47
192+
```
193+
194+
For an extension with `shell-version: ["46", "47", "48"]` (min_shell=46), the fix text is suppressed because 46 < 47. For an extension targeting only `["48"]` (min_shell=48), the fix text is shown because 48 >= 47.
195+
196+
**Guidelines:**
197+
- Set `fix-min-version` to the GNOME version where the replacement API was introduced
198+
- Only use when the fix would break older versions in the extension's declared range
199+
- The warning message should include the compat note inline (e.g., "keep vertical for GNOME <=46 compat")
200+
179201
## Inline Suppression
180202

181203
Add `ego-lint-ignore` comments to suppress specific findings on a per-line basis.

rules/patterns.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -932,19 +932,21 @@
932932
pattern: "\\.vertical\\s*="
933933
scope: ["*.js"]
934934
severity: advisory
935-
message: "St.Widget.vertical property deprecated in GNOME 48; use orientation: Clutter.Orientation.VERTICAL"
935+
message: "St.Widget.vertical property deprecated in GNOME 48; use orientation: Clutter.Orientation.VERTICAL (available since GNOME 47; keep vertical for GNOME <=46 compat)"
936936
category: version-compat
937937
fix: "Use {orientation: Clutter.Orientation.VERTICAL} instead of {vertical: true}"
938938
min-version: 48
939+
fix-min-version: 47
939940

940941
- id: R-VER48-04b
941942
pattern: "vertical:\\s*(true|false)"
942943
scope: ["*.js"]
943944
severity: advisory
944-
message: "vertical property in constructor config removed in GNOME 48; use orientation: Clutter.Orientation.VERTICAL"
945+
message: "vertical property in constructor config deprecated in GNOME 48; use orientation: Clutter.Orientation.VERTICAL (available since GNOME 47; keep vertical for GNOME <=46 compat)"
945946
category: version-compat
946947
fix: "Replace {vertical: true} with {orientation: Clutter.Orientation.VERTICAL}"
947948
min-version: 48
949+
fix-min-version: 47
948950

949951
- id: R-VER48-05
950952
pattern: "\\bShell\\.SnippetHook\\b"

skills/ego-lint/references/rules-reference.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,10 +1811,10 @@ Rules for APIs removed or changed in specific GNOME Shell versions. These rules
18111811

18121812
### R-VER48-04: St.Widget.vertical deprecated
18131813
- **Severity**: advisory
1814-
- **Checked by**: apply-patterns.py (min-version: 48)
1814+
- **Checked by**: apply-patterns.py (min-version: 48, fix-min-version: 47)
18151815
- **Rule**: The `.vertical` property on St widgets is deprecated in GNOME 48.
1816-
- **Rationale**: Will be removed around GNOME 50. Use the orientation property instead.
1817-
- **Fix**: Use `{orientation: Clutter.Orientation.VERTICAL}` instead of `{vertical: true}`.
1816+
- **Rationale**: Will be removed around GNOME 50. The `orientation` property replacement is available since GNOME 47. For multi-version extensions targeting GNOME <=46, the fix cannot be applied unconditionally.
1817+
- **Fix**: Use `{orientation: Clutter.Orientation.VERTICAL}` instead of `{vertical: true}`. Fix text is suppressed when the extension's minimum shell-version is below 47.
18181818
18191819
### R-VER48-05: Shell.SnippetHook removed
18201820
- **Severity**: blocking
@@ -2460,10 +2460,10 @@ Rules for APIs removed or changed in specific GNOME Shell versions. These rules
24602460

24612461
### R-VER48-04b: vertical property in constructor config
24622462
- **Severity**: advisory
2463-
- **Checked by**: apply-patterns.py (min-version: 48)
2464-
- **Rule**: `vertical: true/false` in constructor configuration objects was removed in GNOME 48.
2465-
- **Rationale**: GNOME 48 removed the `vertical` shorthand property from St.BoxLayout and other container constructors. Extensions targeting GNOME 48+ must use the `orientation` property with `Clutter.Orientation.VERTICAL` or `Clutter.Orientation.HORIZONTAL`.
2466-
- **Fix**: Replace `{vertical: true}` with `{orientation: Clutter.Orientation.VERTICAL}`.
2463+
- **Checked by**: apply-patterns.py (min-version: 48, fix-min-version: 47)
2464+
- **Rule**: `vertical: true/false` in constructor configuration objects is deprecated in GNOME 48.
2465+
- **Rationale**: GNOME 48 deprecated the `vertical` shorthand property from St.BoxLayout and other container constructors. The `orientation` property replacement is available since GNOME 47. For multi-version extensions targeting GNOME <=46, the fix cannot be applied unconditionally.
2466+
- **Fix**: Replace `{vertical: true}` with `{orientation: Clutter.Orientation.VERTICAL}`. Fix text is suppressed when the extension's minimum shell-version is below 47.
24672467
24682468
---
24692469

skills/ego-lint/scripts/apply-patterns.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,15 @@ def validate_rules(rules_file):
225225
print(f"ERROR: {rid}: guard-window must be an integer, got '{gw}'")
226226
errors += 1
227227

228+
# Check fix-min-version validity
229+
fix_min = rule.get('fix-min-version')
230+
if fix_min is not None:
231+
try:
232+
int(fix_min)
233+
except (ValueError, TypeError):
234+
print(f"ERROR: {rid}: fix-min-version must be an integer, got '{fix_min}'")
235+
errors += 1
236+
228237
if errors:
229238
print(f"\n{errors} error(s) found in {len(rules)} rules")
230239
return 1
@@ -251,6 +260,7 @@ def main():
251260

252261
rules = parse_rules(rules_file)
253262
shell_versions = _get_shell_versions(ext_dir)
263+
min_shell = min(shell_versions) if shell_versions else None
254264

255265
for rule in rules:
256266
rid = rule.get('id', '?')
@@ -339,6 +349,13 @@ def main():
339349
found = True
340350
else:
341351
fix = rule.get('fix', '')
352+
fix_min_ver = rule.get('fix-min-version')
353+
if fix and fix_min_ver is not None and min_shell is not None:
354+
try:
355+
if min_shell < int(fix_min_ver):
356+
fix = ''
357+
except (ValueError, TypeError):
358+
pass
342359
if fix:
343360
print(f"{status}|{rid}|{rel}:{lineno}: {message}|fix: {fix}")
344361
else:
@@ -351,6 +368,13 @@ def main():
351368
if deduplicate and dedup_files:
352369
files_list = ', '.join(sorted(dedup_files))
353370
fix = rule.get('fix', '')
371+
fix_min_ver = rule.get('fix-min-version')
372+
if fix and fix_min_ver is not None and min_shell is not None:
373+
try:
374+
if min_shell < int(fix_min_ver):
375+
fix = ''
376+
except (ValueError, TypeError):
377+
pass
354378
summary = f"{message} in {len(dedup_files)} file(s): {files_list}"
355379
if fix:
356380
print(f"{status}|{rid}|{summary}|fix: {fix}")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# fix-min-version gating tests (R-VER48-04/04b with multi-version extensions)
2+
# Sourced by run-tests.sh
3+
4+
echo "=== multiversion-vertical ==="
5+
run_lint "multiversion-vertical@test"
6+
assert_exit_code "exits with 0 (advisory only)" 0
7+
assert_output_contains "warns on vertical assignment" "\[WARN\].*R-VER48-04 "
8+
assert_output_contains "warns on vertical constructor" "\[WARN\].*R-VER48-04b"
9+
assert_output_contains "message mentions compat" "keep vertical for GNOME"
10+
assert_output_not_contains "no fix text when min-shell below fix-min-version" "fix:.*orientation"
11+
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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import St from 'gi://St';
2+
import Clutter from 'gi://Clutter';
3+
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
4+
5+
export default class MultiVersionExtension extends Extension {
6+
enable() {
7+
this._box = new St.BoxLayout({vertical: true});
8+
this._box.vertical = false;
9+
}
10+
11+
disable() {
12+
this._box.destroy();
13+
this._box = null;
14+
}
15+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"uuid": "multiversion-vertical@test",
3+
"name": "Multiversion Vertical Test",
4+
"description": "Tests fix-min-version gating for R-VER48-04/04b",
5+
"shell-version": ["46", "47", "48"],
6+
"url": "https://example.com"
7+
}

0 commit comments

Comments
 (0)