name: moonbit-code-review description: Use when reviewing MoonBit code changes. Scores 0-100 with deductions per category.
MoonBit Code Review
Overview
Score every code review from 100 → 0. Start at 100 and apply deductions.
Issues detectable by the MoonBit compiler or moon check are NOT scored here.
Scoring Summary
| Category | Deduction | Per |
|---|---|---|
| Bidirectional dependency | -20 | per occurrence |
| Duplicated logic | -10 | per occurrence |
| Violates project design (AGENTS.md) | -10 | per occurrence |
| Non-refactoring-tolerant test | -3 | per test case |
| WhiteBox test | -3 | per test case |
Using _wbtest.mbt file |
-3 | per file |
| Uncovered code | -1 | per line |
| Security HIGH | -10 | per finding |
| Security MEDIUM | -5 | per finding |
| Security LOW | -1 | per finding |
| Spec not met | -100 | — |
| MoonBit best practice violation | varies | per occurrence |
Architecture
Bidirectional Dependency (-20 per occurrence)
Packages must depend in one direction only. Types-only packages are allowed as shared dependencies.
// ❌ BAD: bidirectional — order imports payment, payment imports order
// order/order.mbt
fn create_order(p : @payment.Processor) -> Order { ... }
// payment/payment.mbt
fn charge(o : @order.Order) -> Unit { ... }
// ✅ GOOD: unidirectional with shared types
// types/types.mbt
pub struct Order {
id : String
amount : Int
}
// payment/payment.mbt — depends on types only
pub fn charge(o : @types.Order) -> Result[Unit, PaymentError] { ... }
// order/order.mbt — depends on types and payment
pub fn place(o : @types.Order) -> Result[Unit, Error] {
@payment.charge(o)
}
Duplicated Logic (-10 per occurrence)
Extract shared behavior into a function.
// ❌ BAD: duplicated validation
pub fn create_user(name : String) -> Result[Unit, AppError] {
if name.length() == 0 || name.length() > 100 {
return Err(AppError::InvalidName)
}
...
}
pub fn update_user(name : String) -> Result[Unit, AppError] {
if name.length() == 0 || name.length() > 100 {
return Err(AppError::InvalidName)
}
...
}
// ✅ GOOD: shared validation
fn validate_name(name : String) -> Result[Unit, AppError] {
if name.length() == 0 || name.length() > 100 {
return Err(AppError::InvalidName)
}
Ok(())
}
pub fn create_user(name : String) -> Result[Unit, AppError] {
validate_name(name)!
...
}
pub fn update_user(name : String) -> Result[Unit, AppError] {
validate_name(name)!
...
}
Project Design Violation (-10 per occurrence)
Code must follow the architecture described in the project's AGENTS.md. Check package boundaries, naming conventions, and responsibilities.
Unit Testing
Non-Refactoring-Tolerant Test (-3 per test case)
Tests must assert results, not how the code works internally.
// ❌ BAD: asserts internal call behavior
test "transfer calls debit and credit" {
let mock = MockRepo::new()
let svc = Service::new(mock)
svc.transfer("alice", "bob", 100)
// Asserting internal calls = breaks on refactor
assert_eq!(mock.debit_called_with, ("alice", 100))
assert_eq!(mock.credit_called_with, ("bob", 100))
}
// ✅ GOOD: asserts observable outcome
test "transfer moves balance between accounts" {
let repo = InMemoryRepo::new()
repo.set_balance("alice", 200)
repo.set_balance("bob", 50)
let svc = Service::new(repo)
svc.transfer("alice", "bob", 100) |> ignore
assert_eq!(repo.get_balance("alice"), 100)
assert_eq!(repo.get_balance("bob"), 150)
}
WhiteBox vs BlackBox Test File (-3 per test case / per file)
MoonBit has explicit file conventions:
*_test.mbt→ BlackBox test (only accessespubmembers) ✅*_wbtest.mbt→ WhiteBox test (accesses all members)
Always use BlackBox tests (_test.mbt). WhiteBox tests are a deduction.
// ❌ BAD: WhiteBox test accessing private internals
// wallet_wbtest.mbt
test "internal ledger" {
let w = { ledger: [] } // accessing private field
add_entry(w, 100) // calling private function
assert_eq!(w.ledger.length(), 1)
}
// ✅ GOOD: BlackBox test using public API
// wallet_test.mbt
test "deposit increases balance" {
let w = @wallet.new()
@wallet.deposit(w, 100) |> ignore
assert_eq!(@wallet.balance(w), 100)
}
Uncovered Code (-1 per line)
Every line of production code should be exercised by tests. Run:
moon test --coverage
Security
Review security against the project context (README.md / AGENTS.md).
| Severity | Deduction | Example |
|---|---|---|
| HIGH (-10) | Injection, hardcoded secrets, missing auth | String-concatenated queries, embedded API keys |
| MEDIUM (-5) | Insufficient input validation, weak crypto | Missing boundary checks on user input |
| LOW (-1) | Missing rate limiting, verbose error exposure | Returning internal error details to client |
// ❌ HIGH: string-concatenated query
pub fn get_user(db : Database, id : String) -> Result[User, DbError] {
db.query("SELECT * FROM users WHERE id = '" + id + "'")
}
// ✅ GOOD: parameterized query
pub fn get_user(db : Database, id : String) -> Result[User, DbError] {
db.query_with_params("SELECT * FROM users WHERE id = $1", [id])
}
Specification
Spec Not Met (-100)
If the code does not satisfy the specification, the review score is effectively 0. Verify behavior against the requirements before anything else.
MoonBit Best Practices
Ignoring Result / Error (-5 per occurrence)
// ❌ BAD: silently ignoring error
fn process(data : String) -> Unit {
let _ = parse(data) // error discarded
}
// ✅ GOOD: propagate or handle
fn process(data : String) -> Result[Unit, ParseError] {
let parsed = parse(data)!
do_something(parsed)
Ok(())
}
Not Using Pattern Matching (-3 per occurrence)
// ❌ BAD: manual if-else chain on enum
fn describe(shape : Shape) -> String {
if shape.is_circle() {
"circle"
} else if shape.is_rect() {
"rectangle"
} else {
"unknown"
}
}
// ✅ GOOD: exhaustive pattern matching
fn describe(shape : Shape) -> String {
match shape {
Circle(_) => "circle"
Rect(_) => "rectangle"
}
}
Not Using Pipeline Operator (-2 per occurrence)
// ❌ BAD: deeply nested function calls
fn process(input : String) -> String {
trim(to_lowercase(remove_spaces(input)))
}
// ✅ GOOD: pipeline for readability
fn process(input : String) -> String {
input
|> remove_spaces
|> to_lowercase
|> trim
}
Stringly-Typed API (-3 per occurrence)
// ❌ BAD: status as string
pub fn set_status(order : Order, status : String) -> Unit {
order.status = status
}
// ✅ GOOD: enum for known variants
pub enum OrderStatus {
Pending
Shipped
Delivered
}
pub fn set_status(order : Order, status : OrderStatus) -> Unit {
order.status = status
}
Not Using Snapshot Testing (-2 per occurrence)
When verifying complex output, prefer inspect over manual assertions.
// ❌ BAD: fragile manual assertion on complex structure
test "parse config" {
let config = parse("key=value\nfoo=bar")
assert_eq!(config.entries.length(), 2)
assert_eq!(config.entries[0].key, "key")
assert_eq!(config.entries[0].value, "value")
assert_eq!(config.entries[1].key, "foo")
assert_eq!(config.entries[1].value, "bar")
}
// ✅ GOOD: snapshot testing — auto-updated with `moon test --update`
test "parse config" {
let config = parse("key=value\nfoo=bar")
inspect!(config, content="{entries: [{key: \"key\", value: \"value\"}, {key: \"foo\", value: \"bar\"}]}")
}
Trait Interface Design (-5 per occurrence)
Keep traits small and focused. Define them where they are consumed.
// ❌ BAD: large trait with unrelated methods
pub trait Storage {
get(Self, String) -> Option[Bytes]
set(Self, String, Bytes) -> Unit
delete(Self, String) -> Unit
list(Self, String) -> Array[String]
backup(Self) -> Unit
restore(Self, String) -> Unit
}
// ✅ GOOD: small, focused traits
pub trait ItemReader {
get(Self, String) -> Option[Bytes]
}
pub trait ItemWriter {
set(Self, String, Bytes) -> Unit
}
Mutable When Immutable Suffices (-2 per occurrence)
// ❌ BAD: using mutable ref when not needed
fn total(items : Array[Item]) -> Int {
let mut sum = 0
for item in items {
sum = sum + item.price
}
sum
}
// ✅ GOOD: functional approach
fn total(items : Array[Item]) -> Int {
items.fold(init=0, fn(acc, item) { acc + item.price })
}
Review Procedure
- Spec — Does the code meet the requirements? If not, stop (-100).
- Architecture — Check dependency direction, duplication, project design.
- Unit Tests — BlackBox (
_test.mbt)? Refactoring-tolerant? Snapshot tests? Coverage? - Security — Review against project context for vulnerabilities.
- MoonBit Best Practices — Error handling, pattern matching, pipelines, traits, immutability.
- Calculate Score — Start at 100, apply all deductions. Minimum is 0.
Output Format
## Code Review: [component/file]
**Score: XX/100**
### Deductions
| # | Category | Detail | Deduction |
|---|----------|--------|-----------|
| 1 | Architecture | Bidirectional dep: order ↔ payment | -20 |
| 2 | Unit Test | WhiteBox test: wallet_wbtest.mbt | -3 |
| ... | ... | ... | ... |
**Total Deductions: -XX**
### Summary
[brief summary of key issues and recommendations]