for_each_dir
For every matching directory / file, evaluate a nested require: block with the entry as context. Template tokens ({dir}, {stem}, {ext}, {basename}, {path}, {parent_name}) expand against each match. select: is a single glob or a list with !-prefixed excludes (e.g. ["src/*", "!src/internal"]).
when_iter: — per-iteration filter. Optional expression in the when: grammar, with one extra namespace: iter.* references the entry currently being iterated. Iterations whose verdict is false are skipped before any nested rule is built — the canonical use case for monorepos shaped like Cargo / pnpm / Bazel workspaces.
The iter namespace exposes:
| Reference | Type | Notes |
|---|---|---|
iter.path | string | Relative path of the iterated entry. |
iter.basename | string | Basename. |
iter.parent_name | string | Parent dir name. |
iter.stem | string | Basename minus the final extension (mainly useful for files). |
iter.ext | string | Final extension without the dot. |
iter.is_dir | bool | True for for_each_dir, false for for_each_file; always available. |
iter.has_file(pattern) | bool | Glob match relative to the iterated dir. iter.has_file("Cargo.toml"), iter.has_file("**/*.bzl"). Always false for file iteration. |
when_iter: composes with the rule's outer when: (whole-rule gate, evaluated once) and with each nested rule's when: (which now also sees the same iter.* context). Same field is available on for_each_file and every_matching_has.
Token values are literal paths. When a token expands into a nested option that alint compiles as a glob (paths:, and per kind options such as dir_contains's select / require, dir_only_contains's select / allow, unique_by's select, pair's primary, or a nested for_each_*'s own select), its value is glob-escaped, so a directory named app/[slug] or pkgs/* matches only itself rather than acting as a character class or wildcard. The template text around the token keeps its glob meaning ("{path}/*.tsx"). Options that are regexes (for_each_match's select, pattern) or literal paths (pair's partner) receive the value unescaped. Messages show the real path, not its escaped form.
Options
Section titled "Options"| Option | Type | Required | Default | Description |
|---|---|---|---|---|
require | list of nested rule | yes | Nested rules evaluated against each matched directory. | |
select | select spec | yes | Glob(s) selecting the directories to iterate - a single glob, or a list with !-prefixed excludes (e.g. ["src/*", "!src/internal"]). | |
when_iter | string | null | Per-iteration when: filter - evaluated against iter.* in the iterated entry's context. Iterations whose verdict is false are skipped before any nested rule is built. Examples: iter.has_file("Cargo.toml"), iter.basename matches "^pkg-". |
Plus the common level, id, and when fields. This rule analyses the whole repository, so it takes no paths or expect_matches. This table is generated from the JSON Schema; option types and defaults are authoritative.
Example
Section titled "Example"A module directory missing its mod.rs
Section titled "A module directory missing its mod.rs"The rule fires on this repository:
- src/
- src/alpha/
- src/alpha/mod.rs✓
- src/beta/
✗ error every-module-has-mod
expected a file matching [src/beta/mod.rs]
- src/beta/lib.rs✓
- src/gamma/
- src/gamma/mod.rs✓
pub fn a() {}pub fn b() {}pub fn c() {}With this .alint.yml:
version: 1rules: - id: every-module-has-mod kind: for_each_dir select: "src/*" require: - kind: file_exists paths: "{path}/mod.rs" level: erroralint check reports:
--- src/beta ------------------------------------------------------------------- x error every-module-has-mod expected a file matching [src/beta/mod.rs]
Summary (1 violation): x 1 error 0 passing * 1 failingFilter Cargo workspace members with when_iter
Section titled "Filter Cargo workspace members with when_iter"The rule fires on this repository:
- Cargo.toml✓
- crates/
- crates/alint-core/
- crates/alint-core/Cargo.toml✓
- crates/alint-core/README.md✓
- crates/alint-rules/
✗ error cargo-pkg-needs-readme
expected a file matching [crates/alint-rules/README.md]
- crates/alint-rules/Cargo.toml✓
- crates/notes/
- crates/notes/scratch.md✓
[workspace]members = ["crates/*" ][package]name = "alint-core"# alint-core[package]name = "alint-rules"WIP notes.With this .alint.yml:
version: 1rules: - id: cargo-pkg-needs-readme kind: for_each_dir select: "crates/*" when_iter: 'iter.has_file("Cargo.toml")' require: - kind: file_exists paths: "{path}/README.md" level: erroralint check reports:
--- crates/alint-rules --------------------------------------------------------- x error cargo-pkg-needs-readme expected a file matching [crates/alint-rules/README.md]
Summary (1 violation): x 1 error 0 passing * 1 failingEvery module directory has a mod.rs
Section titled "Every module directory has a mod.rs"This repository is compliant:
- src/
- src/alpha/
- src/alpha/mod.rs✓
- src/beta/
- src/beta/mod.rs✓
pub fn a() {}pub fn b() {}With this .alint.yml:
version: 1rules: - id: every-module-has-mod kind: for_each_dir select: "src/*" require: - kind: file_exists paths: "{path}/mod.rs" level: erroralint check reports:
v All 1 rule(s) passed.