- Rust 97.9%
- Nix 1.6%
- Shell 0.5%
Nixpkgs style guide recommends using finalAttrs pattern instead of rec attrset in derivation functions for better maintainability and to avoid issues with attribute evaluation order. |
||
|---|---|---|
| blocks | ||
| crates | ||
| docs | ||
| scripts | ||
| timbers | ||
| wit | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| LICENSE-APACHE | ||
| LICENSE-MIT | ||
| README.md | ||
Yosegi
yosegi (寄木): A Japanese woodwork technique of assembling small pieces of wood into intricate patterns.
A tree-sitter based linter with WASM plugin support, written in Rust.
Features
- WASM Plugin Architecture: Linting rules are distributed as WebAssembly modules (Blocks), enabling safe, sandboxed execution and easy distribution via OCI registries
- Composable Rules: Each Block contains multiple Pieces (individual lint rules) that can be independently enabled/disabled
- Multi-language Support: Built on tree-sitter for accurate AST parsing. Currently supports Nix (first-class) and Rust
- LSP Integration: Real-time linting feedback in your editor with diagnostics, hover information, and quick fixes
- Extensibility: Write custom linting rules in Rust using the Piece Development Kit (PDK)
Quick Start
Installation
# Build from source
cargo build --release
# The binary will be at ./target/release/yosegi
Basic Usage
# Lint files or directories
yosegi check path/to/code
# Lint with specific blocks
yosegi check --blocks ./path/to/block.wasm path/to/code
# Use JSON output format
yosegi check -f json path/to/code
# Filter by severity
yosegi check --severity error path/to/code
Configuration
Create a yosegi.toml in your project root:
[settings]
severity-threshold = "warning"
[blocks.nixpkgs]
source = "ghcr.io/natsukium/yosegi-nixpkgs:latest"
enabled = true
# Disable specific pieces
[blocks.nixpkgs.pieces.no-with]
enabled = false
# Override severity for a piece
[blocks.nixpkgs.pieces.description-no-period]
severity = "error"
Core Concepts
Blocks
A Block is a WASM module containing one or more linting rules. Blocks are distributed as OCI artifacts and can be fetched from container registries.
# List configured blocks
yosegi blocks list
# Show block details
yosegi blocks info nixpkgs
# Pre-fetch blocks for offline use
yosegi blocks fetch
# Manage cache
yosegi blocks cache info
yosegi blocks cache clear
Pieces
A Piece is an individual linting rule within a Block. Each piece can be independently:
- Enabled or disabled
- Configured with custom severity (error, warning, info, hint)
- Customized with piece-specific options
Timbers
A Timber is a language support crate that provides:
- Tree-sitter grammar integration
- AST traversal and query helpers
- Language-specific utilities
Timbers are embedded into Blocks at compile time, enabling fast native testing during development.
Available Blocks
nixpkgs
Linting rules for Nix/nixpkgs packages (21 pieces):
| Category | Pieces |
|---|---|
| Style | no-with, description-no-period, description-no-article, description-capitalization, pname-normalization, flags-not-list, optional-with-list |
| Correctness | build-tool-in-build-inputs, redundant-stdenv-package |
| Python | versioned-dependency, redundant-native-build-input, deprecated-test-tool, deprecated-format, non-functional-test-tool, package-in-native-build-inputs, pytest-cov, pytest-benchmark, bare-pytest, unnecessary-wheel, missing-pyproject |
rust
Linting rules for Rust code (2 pieces).
LSP Server
Start the LSP server for editor integration:
yosegi lsp
Editor Configuration
Neovim (with nvim-lspconfig):
vim.api.nvim_create_autocmd("FileType", {
pattern = "nix",
callback = function()
vim.lsp.start({
name = "yosegi",
cmd = { "yosegi", "lsp" },
root_dir = vim.fs.dirname(vim.fs.find({ "yosegi.toml", "flake.nix" }, { upward = true })[1]),
})
end,
})
VS Code: Configure in settings.json or use a custom language server client.
Developing Custom Blocks
Create custom linting rules using the Piece Development Kit (PDK):
use yosegi_pdk::prelude::*;
use yosegi_timber_nix as timber;
struct MyPiece;
impl DynPiece for MyPiece {
fn info(&self) -> PieceInfo {
PieceInfo::new("my-rule", "My Rule", "Description of the rule")
.category("style")
.language("nix")
.severity(Severity::Warning)
}
fn check(&self, source: &str) -> Vec<Diagnostic> {
// Use timber for AST queries
let matches = timber::query("(with_expression) @target").unwrap_or_default();
matches.into_iter().map(|m| {
Diagnostic {
rule_id: "my-rule".into(),
message: "Found a with expression".into(),
severity: Severity::Warning,
span: m.span,
fix: None,
}
}).collect()
}
}
struct MyBlock;
impl Block for MyBlock {
fn info() -> BlockInfo {
BlockInfo::new("my-block", "My Block", "My custom linting rules")
}
fn pieces() -> Vec<&'static dyn DynPiece> {
vec![&MyPiece]
}
}
Build the block as a WASM component:
cargo build --target wasm32-wasip1 --profile release-wasm
Project Structure
yosegi/
├── crates/
│ ├── yosegi/ # CLI binary
│ ├── yosegi-core/ # Core types (Diagnostic, Span, Severity)
│ ├── yosegi-config/ # TOML configuration parsing
│ ├── yosegi-block/ # WASM block loader (Wasmtime)
│ ├── yosegi-pdk/ # Piece Development Kit
│ ├── yosegi-engine/ # Linting engine orchestration
│ ├── yosegi-registry/ # OCI registry client
│ └── yosegi-lsp/ # Language Server Protocol
├── timbers/
│ ├── nix/ # Nix language support
│ └── rust/ # Rust language support
├── blocks/
│ ├── nixpkgs/ # Nixpkgs linting rules
│ └── rust/ # Rust linting rules
└── wit/
└── world.wit # WebAssembly Interface Types definition
Documentation
- Architecture Overview
- Plugin Architecture
- Block/Piece Architecture (RFC-001)
- Timber Architecture (RFC-002)
- LSP Integration
- Performance Guide
License
This project uses a dual-license structure:
- Core components (
crates/*exceptyosegi-pdk): AGPL-3.0-or-later - Plugin SDK and plugins (
yosegi-pdk,timbers/*,blocks/*): MIT OR Apache-2.0
Using yosegi as a linting tool does not affect your code's license. The AGPL obligations apply only if you modify and distribute yosegi itself, or provide it as a network service.