Skip to content

monorepo/cargo-workspace@v1

Workspace-aware overlay for Cargo workspaces. Layered on top of monorepo@v1 (which covers polyglot package-dir conventions) and rust@v1 (which covers Rust source hygiene). Adopt with:

extends:
- alint://bundled/monorepo@v1
- alint://bundled/rust@v1
- alint://bundled/monorepo/cargo-workspace@v1

Gated by facts.is_cargo_workspace — the root Cargo.toml must declare a [workspace] table. Outside an actual workspace (e.g. a single-crate repo, or a polyglot tree whose Rust portion isn’t a workspace), the rules silently no-op so the ruleset is safe to extend unconditionally.

v0.9.18 SCOPE NOTE — non-canonical workspace layouts:

The per-member rules below use select: "crates/*" — the canonical Cargo workspace convention used by rust-lang/rust, alint itself, and most published workspaces. Workspaces with non-canonical layouts (deno’s ext/* + libs/* + runtime/

  • cli/; clap’s clap_builder, clap_derive, etc.) will see the bundled per-member checks silently no-op against their tree.

Non-canonical adopters should redefine the per-member rules in their .alint.yml with the correct select: glob (rule IDs reused at the user level OVERRIDE the bundled definition while keeping the rule active). For example, deno’s config carries:

- id: cargo-workspace-member-has-readme
kind: for_each_dir
select: "{ext/*,libs/*,runtime,cli}"
when_iter: 'iter.has_file("Cargo.toml")'
require: [{ kind: file_exists, paths: "{path}/README.md" }]

(for_each_dir.select: accepts a single glob; brace expansion handles multi-pattern intent.)

The principled fix — a select_from: "$.workspace.members" refinement that derives the iteration set from the root Cargo.toml’s [workspace] members array — ships in v0.10 alongside the toml_path_array_iter rule-kind design. Tracking: v0.10 design candidate ”*_path_array_iter”.

Cargo workspace must declare members = [...] under [workspace]. Without it, cargo build fails to resolve the package graph.

  • kind: for_each_dir
  • level: warning
  • when: facts.is_cargo_workspace

Cargo workspace members should have a README.md so the crate’s purpose is discoverable from the directory tree.

  • kind: for_each_dir
  • level: warning
  • when: facts.is_cargo_workspace

Workspace member’s Cargo.toml must declare [package] with a name field.

The full ruleset definition is committed at crates/alint-dsl/rulesets/v1/monorepo/cargo-workspace.yml in the alint repo (the snapshot below is generated verbatim from that file).

# alint://bundled/monorepo/cargo-workspace@v1
#
# Workspace-aware overlay for Cargo workspaces. Layered on
# top of `monorepo@v1` (which covers polyglot package-dir
# conventions) and `rust@v1` (which covers Rust source
# hygiene). Adopt with:
#
# extends:
# - alint://bundled/monorepo@v1
# - alint://bundled/rust@v1
# - alint://bundled/monorepo/cargo-workspace@v1
#
# Gated by `facts.is_cargo_workspace` — the root Cargo.toml
# must declare a `[workspace]` table. Outside an actual
# workspace (e.g. a single-crate repo, or a polyglot tree
# whose Rust portion isn't a workspace), the rules silently
# no-op so the ruleset is safe to extend unconditionally.
#
# v0.9.18 SCOPE NOTE — non-canonical workspace layouts:
#
# The per-member rules below use `select: "crates/*"` — the
# canonical Cargo workspace convention used by rust-lang/rust,
# alint itself, and most published workspaces. Workspaces with
# non-canonical layouts (deno's `ext/*` + `libs/*` + `runtime/`
# + `cli/`; clap's `clap_builder`, `clap_derive`, etc.) will
# see the bundled per-member checks silently no-op against
# their tree.
#
# Non-canonical adopters should redefine the per-member rules
# in their `.alint.yml` with the correct `select:` glob (rule
# IDs reused at the user level OVERRIDE the bundled definition
# while keeping the rule active). For example, deno's config
# carries:
#
# - id: cargo-workspace-member-has-readme
# kind: for_each_dir
# select: "{ext/*,libs/*,runtime,cli}"
# when_iter: 'iter.has_file("Cargo.toml")'
# require: [{ kind: file_exists, paths: "{path}/README.md" }]
#
# (`for_each_dir.select:` accepts a single glob; brace expansion
# handles multi-pattern intent.)
#
# The principled fix — a `select_from: "$.workspace.members"`
# refinement that derives the iteration set from the root
# Cargo.toml's `[workspace] members` array — ships in v0.10
# alongside the `toml_path_array_iter` rule-kind design.
# Tracking: v0.10 design candidate "*_path_array_iter".
version: 1
facts:
- id: is_cargo_workspace
file_content_matches:
paths: Cargo.toml
pattern: '(?m)^\[workspace\]'
rules:
# The workspace root must declare its members. A `[workspace]`
# table without `members = [...]` is broken — Cargo rejects it.
# Catching this here keeps the failure local to alint output.
- id: cargo-workspace-members-declared
when: facts.is_cargo_workspace
kind: toml_path_matches
paths: Cargo.toml
path: "$.workspace.members[*]"
matches: ".+"
level: error
message: >-
Cargo workspace must declare `members = [...]` under
`[workspace]`. Without it, `cargo build` fails to resolve
the package graph.
policy_url: "https://doc.rust-lang.org/cargo/reference/workspaces.html"
# Every actual workspace member (a `crates/*` directory that
# has a Cargo.toml of its own) needs a README. Pre-v0.5.2,
# this would have fired on `crates/notes/` even if it wasn't a
# real package — `when_iter:` scopes the iteration to just
# the package dirs.
- id: cargo-workspace-member-has-readme
when: facts.is_cargo_workspace
kind: for_each_dir
select: "crates/*"
when_iter: 'iter.has_file("Cargo.toml")'
require:
- kind: file_exists
paths: "{path}/README.md"
level: warning
message: >-
Cargo workspace members should have a README.md so the
crate's purpose is discoverable from the directory tree.
# Each member's Cargo.toml should declare its package name and
# edition — the two fields most commonly missing from
# hand-rolled members.
- id: cargo-workspace-member-declares-name
when: facts.is_cargo_workspace
kind: for_each_dir
select: "crates/*"
when_iter: 'iter.has_file("Cargo.toml")'
require:
- kind: toml_path_matches
paths: "{path}/Cargo.toml"
path: "$.package.name"
matches: '^[A-Za-z][A-Za-z0-9_-]*$'
level: warning
message: >-
Workspace member's Cargo.toml must declare
`[package]` with a `name` field.