@@ -10,24 +10,42 @@ on Linux so it catches this, but only after a push. We want to catch it locally,
1010
1111Add a new check to the Go-based check runner (` scripts/check/ ` ) that:
1212
13- 1 . ** Parses ` Cargo.toml ` ** to auto-derive the list of macOS-only crate names from the
14- ` [target.'cfg(target_os = "macos")'.dependencies] ` section. Converts crate names to Rust module form (hyphens to
15- underscores, for example ` cmdr-fsevent-stream ` becomes ` cmdr_fsevent_stream ` ).
16- 2 . ** Scans all ` .rs ` files** under ` apps/desktop/src-tauri/src/ ` for ` use <crate>:: ` statements that reference any of
17- those crates.
18- 3 . ** Verifies each match is gated.** For each ` use ` of a macOS-only crate, walks backwards from that line to check that
19- either:
20- - The line itself or a preceding non-empty line has ` #[cfg(target_os = "macos")] ` , or
21- - The use is inside a block that's already gated (for example, a ` mod ` block or ` #[cfg(test)] ` + ` target_os ` combo).
22- For simplicity, the initial version only checks the "preceding ` #[cfg(...)] ` attribute" pattern, which covers 100% of
23- the current codebase's usage. The block-level gating can be added later if needed.
24- 4 . ** Reports violations** with file path and line number.
13+ 1 . ** Parses ` Cargo.toml ` ** using a TOML library (` github.com/BurntSushi/toml ` ) to extract macOS-only crate names from
14+ the ` [target.'cfg(target_os = "macos")'.dependencies] ` section. Converts crate names to Rust module form (hyphens to
15+ underscores, for example ` cmdr-fsevent-stream ` becomes ` cmdr_fsevent_stream ` ). A proper TOML parser handles
16+ multi-line inline tables (like ` objc2-foundation ` with its multi-line features array) without fragile regex hacks.
17+ 2 . ** Builds a set of "module-gated" files** by scanning ` lib.rs ` and ` mod.rs ` files for
18+ ` #[cfg(target_os = "macos")] mod <name>; ` patterns, resolving each to the corresponding ` .rs ` file (or
19+ ` <name>/mod.rs ` ). Files inside a cfg-gated module are inherently safe — everything in them is already gated by the
20+ parent's ` mod ` declaration. These files are skipped during the ` use ` scan.
21+ 3 . ** Scans remaining ` .rs ` files** under ` apps/desktop/src-tauri/src/ ` for ` use <crate>:: ` statements that reference any
22+ of those crates (including indented ` use ` inside function bodies and ` pub use ` re-exports).
23+ 4 . ** Verifies each match is gated.** For each ` use ` of a macOS-only crate, walks backwards from that line to check that
24+ a preceding ` #[...] ` attribute contains ` target_os = "macos" ` (including compound forms like
25+ ` cfg(all(test, target_os = "macos")) ` ).
26+ 5 . ** Reports violations** with file path and line number.
27+
28+ ## Why module-level gating matters
29+
30+ The codebase uses two gating patterns:
31+
32+ - ** Per-line gating:** ` #[cfg(target_os = "macos")] ` before each ` use ` statement (for example, ` watcher.rs ` , ` icons.rs ` ).
33+ - ** Module-level gating:** ` #[cfg(target_os = "macos")] mod foo; ` in ` lib.rs ` /` mod.rs ` , where everything inside ` foo.rs `
34+ is inherently gated (for example, ` drag_image_swap ` , ` accent_color ` , ` volumes ` , ` network ` , ` mtp ` , ` permissions ` ,
35+ ` macos_icons ` , ` file_system/macos_metadata ` , ` file_system/volume/mtp ` ).
36+
37+ Module-level gating is actually the more common pattern. Without handling it, the checker would produce dozens of false
38+ positives.
2539
2640## Scope
2741
2842Only the ` [target.'cfg(target_os = "macos")'.dependencies] ` section. We could extend this to ` cfg(unix) ` later, but
2943` libc ` is the only unix-only dep and it's common enough to not be worth the noise.
3044
45+ Note: some cross-platform crates (` chrono ` , ` bytes ` ) live in the macOS-only section because they're only needed for
46+ macOS features. The checker correctly flags ungated uses of these too — if someone adds ` use chrono:: ` in non-macOS
47+ code, it genuinely won't compile on Linux.
48+
3149## Check runner integration
3250
3351- ** File:** ` scripts/check/checks/desktop-rust-cfg-gate.go `
@@ -38,43 +56,57 @@ Only the `[target.'cfg(target_os = "macos")'.dependencies]` section. We could ex
3856- ** Slow:** No (pure text scanning, should run in milliseconds)
3957- ** Depends on:** Nothing (independent, can run in parallel with everything)
4058- ** Position in registry:** After ` desktop-rust-jscpd ` , before ` desktop-rust-tests `
59+ - ** New dependency:** ` github.com/BurntSushi/toml ` (MIT license — run ` cargo deny ` -equivalent check: it's fine)
4160
4261## Algorithm
4362
4463```
45- 1. Read Cargo.toml
46- 2. Find the [target.'cfg(target_os = "macos")'.dependencies] section
47- 3. Extract crate names, convert hyphens → underscores → build set
48- 4. Walk all .rs files in src-tauri/src/
49- 5. For each file, scan lines:
50- a. If line matches `use <macos_crate>::` (ignoring leading whitespace and `pub`):
51- - Walk backwards over blank lines and `#[...]` attribute lines
52- - Look for `cfg(target_os = "macos")` in any of those attributes (including compound forms
53- like `cfg(all(test, target_os = "macos"))`)
64+ 1. Read and parse Cargo.toml with BurntSushi/toml
65+ 2. Extract crate names from the [target.'cfg(target_os = "macos")'.dependencies] table
66+ 3. Convert hyphens to underscores, build set of macOS-only crate module names
67+ 4. Build set of module-gated files:
68+ a. Walk all lib.rs and mod.rs files in src-tauri/src/
69+ b. For each, find lines matching: #[cfg(target_os = "macos")] followed by mod <name>;
70+ (possibly with blank lines or other attributes in between)
71+ c. Resolve <name> to <dir>/<name>.rs or <dir>/<name>/mod.rs
72+ d. If the resolved file is a directory module (mod.rs), recursively add all .rs files under it
73+ e. Collect all resolved paths into a "skip set"
74+ 5. Walk all .rs files in src-tauri/src/, skipping files in the skip set
75+ 6. For each non-skipped file, scan lines:
76+ a. If line matches `use <macos_crate>::` (ignoring leading whitespace, optional `pub `):
77+ - Walk backwards over blank lines and #[...] attribute lines
78+ - Look for `target_os = "macos"` in any of those attributes
5479 - If not found, record a violation: {file, line number, crate name}
55- 6 . If any violations, return error listing them all
56- 7 . If none, return success with count of gated uses verified
80+ 7 . If any violations, return error listing them all
81+ 8 . If none, return success with count of gated uses verified + count of module-gated files skipped
5782```
5883
5984## Success message examples
6085
61- - ` 23 gated uses of 8 macOS-only crates verified ` (all good)
62- - Error: ` apps/desktop/src-tauri/src/indexing/watcher.rs:5: use of macOS-only crate 'cmdr_fsevent_stream' without #[cfg(target_os = "macos")] `
86+ - ` 23 gated uses of 8 macOS-only crates verified (12 files skipped via module-level gating) ` (all good)
87+ - Error: `apps/desktop/src-tauri/src/indexing/watcher.rs:5: use of macOS-only crate 'cmdr_fsevent_stream' without
88+ #[ cfg(target_os = "macos")] `
6389
6490## Testing
6591
6692Add a test in ` scripts/check/checks/ ` that:
67- - Constructs a minimal Cargo.toml snippet and a few ` .rs ` file contents (as strings/ temp files)
68- - Verifies that correctly gated uses pass
93+ - Constructs a minimal Cargo.toml and a few ` .rs ` files in a temp directory
94+ - Verifies that per-line gated uses pass
6995- Verifies that ungated uses are caught
70- - Verifies that crate name extraction handles hyphens, inline tables (` { version = "..." } ` ), and git deps
96+ - Verifies that uses inside module-gated files are skipped (not flagged)
97+ - Verifies that crate name extraction handles hyphens, inline tables (` { version = "..." } ` ), git deps, and multi-line
98+ feature arrays
99+ - Verifies the module-gated file resolver handles both ` <name>.rs ` and ` <name>/mod.rs ` layouts
71100
72101## Task list
73102
74- - [ ] Implement crate name extraction from Cargo.toml (parse the target section, convert hyphens to underscores)
75- - [ ] Implement ` .rs ` file scanner (find ` use <crate>:: ` lines, walk backwards for ` cfg ` attributes)
76- - [ ] Wire up as ` RunCfgGate ` in ` desktop-rust-cfg-gate.go `
77- - [ ] Register in ` registry.go ` (after jscpd, before tests)
78- - [ ] Add unit tests for extraction and scanning logic
79- - [ ] Run ` ./scripts/check.sh --check cfg-gate ` to verify it passes on the current codebase
80- - [ ] Run ` ./scripts/check.sh --go ` to verify Go checks pass (gofmt, vet, staticcheck, and the rest)
103+ - [x] Add ` github.com/BurntSushi/toml ` dependency to ` scripts/check/go.mod `
104+ - [x] Implement crate name extraction from Cargo.toml (TOML parser, convert hyphens to underscores)
105+ - [x] Implement module-gated file detection (scan ` lib.rs ` /` mod.rs ` for cfg-gated ` mod ` declarations, resolve to files)
106+ - [x] Implement ` .rs ` file scanner (find ` use <crate>:: ` lines, walk backwards for ` cfg ` attributes, skip gated files)
107+ - [x] Wire up as ` RunCfgGate ` in ` desktop-rust-cfg-gate.go `
108+ - [x] Register in ` registry.go ` (after jscpd, before tests)
109+ - [x] Add unit tests for crate extraction, module-gating detection, and use-line scanning
110+ - [x] Run ` ./scripts/check.sh --check cfg-gate ` to verify it passes on the current codebase
111+ - [x] Run ` ./scripts/check.sh --go ` to verify Go checks pass (gofmt, vet, staticcheck, and the rest)
112+ - [x] Add ` desktop-rust-cfg-gate ` to the ` --check ` list in ` AGENTS.md `
0 commit comments