Awesome-omni-skill moonbit-refactoring
Refactor MoonBit code to be idiomatic: shrink public APIs, convert functions to methods, use pattern matching with views, add loop invariants, and ensure test coverage without regressions.
git clone https://github.com/diegosouzapw/awesome-omni-skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/moonbit-refactoring-bobzhang" ~/.claude/skills/diegosouzapw-awesome-omni-skill-moonbit-refactoring && rm -rf "$T"
skills/development/moonbit-refactoring-bobzhang/SKILL.mdMoonBit Refactoring Skill
Intent
- Preserve behavior and public contracts unless explicitly changed.
- Minimize the public API to what callers require.
- Prefer declarative style and pattern matching over incidental mutation.
- Use view types (ArrayView/StringView/BytesView) to avoid copies.
- Add tests and docs alongside refactors.
Workflow
Start broad, then refine locally:
- Architecture first: Review package structure, dependencies, and API boundaries.
- Inventory public APIs and call sites (
,moon doc
).moon ide find-references - Pick one refactor theme (API minimization, package splits, pattern matching, loop style).
- Apply the smallest safe change.
- Update docs/tests in the same patch.
- Run
, thenmoon check
.moon test - Use coverage to target missing branches.
Avoid local cleanups (renaming, pattern matching) until the high-level structure is sound.
Improve Package Architecture
- Keep packages focused: aim for <10k lines per package.
- Keep files manageable: aim for <2k lines per file.
- Keep functions focused: aim for <200 lines per function.
Splitting Files
Files in MoonBit are just organizational—move code freely within a package as long as each file stays focused on one concept.
Splitting Packages
When spinning off package
A into A and B:
-
Create the new package and re-export temporarily:
// In package B using @A { ... } // re-export A's APIsEnsure
passes before proceeding.moon check -
Find and update all call sites:
moon ide find-references <symbol>Replace bare
withf
.@B.f -
Remove the
statement once all call sites are updated.use -
Audit and remove newly-unused
APIs from both packages.pub
Guidelines
- Prefer acyclic dependencies: lower-level packages should not import higher-level ones.
- Only expose what downstream packages actually need.
- Consider an
package for helpers that shouldn't leak.internal/
Minimize Public API and Modularize
- Remove
from helpers; keep only required exports.pub - Move helpers into
packages to block external imports.internal/ - Split large files by feature; files do not define modules in MoonBit.
Local refactoring
Convert Free Functions to Methods + Chaining
- Move behavior onto the owning type for discoverability.
- Use
for fluent, mutating chains when it reads clearly...
Example:
// Before fn reader_next(r : Reader) -> Char? { ... } let ch = reader_next(r) // After fn Reader::next(self : Reader) -> Char? { ... } let ch = r.next()
Example (chaining):
buf..write_string("#\\")..write_char(ch)
Prefer Explicit Qualification
- Use
instead of@pkg.fn
when clarity matters.using - Keep call sites explicit during wide refactors.
Example:
let n = @parser.parse_number(token)
Simplify Constructors When Type Is Known
- Drop
when the surrounding type is known.TypePath::Constr
Example:
match tree { // the type of tree is known to be Tree Leaf(x) => x // no need for Tree::Leaf Node(left~, x, right~) => left.sum() + x + right.sum() }
Pattern Matching and Views
- Pattern match arrays directly; the compiler inserts ArrayView implicitly.
- Use
in the middle to match prefix and suffix at once... - Pattern match strings directly; avoid converting to
.Array[Char]
/String
indexing yieldsStringView
code units. UseUInt16
for Unicode-aware iteration.for ch in s
Examples:
match items { [] => () [head, ..tail] => handle(head, tail) [..prefix, mid, ..suffix] => handle_mid(prefix, mid, suffix) }
match s { "" => () [.."let", ..rest] => handle_let(rest) _ => () }
Char literal matching
MoonBit allows char literal overloading for
Char, UInt16, and Int, so the examples below work. This is handy when matching String indexing results (UInt16) against a char range.
test { let a_int : Int = 'b' if (a_int is 'a'..<'z') { () } else { () } let a_u16 : UInt16 = 'b' if (a_u16 is 'a'..<'z') { () } else { () } let a_char : Char = 'b' if (a_char is 'a'..<'z') { () } else { () } }
Use Nested Patterns and is
is- Use
patterns insideis
/if
to keep branches concise.guard
Example:
match token { Some(Ident([.."@", ..rest])) if process(rest) is Some(x) => handle_at(rest) Some(Ident(name)) => handle_ident(name) None => () }
Prefer Range Loops for Simple Indexing
- Use
,for i in start..<end { ... }
,for i in start..<=end { ... }
, orfor i in large>..small
for simple index loops.for i in large>=..small - Keep functional-state
loops for algorithms that update state.for
Example:
// Before for i = 0; i < len; { items.push(fill) continue i + 1 } // After for i in 0..<len { items.push(fill) }
Loop Specs (Dafny-Style Comments)
- Add specs for functional-state loops.
- Skip invariants for simple
loops.for x in xs - Add TODO when a decreases clause is unclear (possible bug).
Example:
for i = 0, acc = 0; i < xs.length(); { acc = acc + xs[i] i = i + 1 } else { acc } where { invariant: 0 <= i <= xs.length(), reasoning: ( #| ... rigorous explanation ... #| ... ) }
Tests and Docs
- Prefer black-box tests in
or*_test.mbt
.*.mbt.md - Add docstring tests with
for public APIs.mbt check
Example:
///| /// Return the last element of a non-empty array. /// /// # Example /// ```mbt check /// test { /// inspect(last([1, 2, 3]), content="3") /// } /// ``` pub fn last(xs : Array[Int]) -> Int { ... }
Coverage-Driven Refactors
- Use coverage to target missing branches through public APIs.
- Prefer small, focused tests over white-box checks.
Commands:
moon coverage analyze -- -f summary moon coverage analyze -- -f caret -F path/to/file.mbt
Moon IDE Commands
moon doc "<query>" moon ide outline <dir|file> moon ide find-references <symbol> moon ide peek-def <symbol> moon check moon test moon info
These commands are useful for reliable refactoring.
Example: spinning off
package_b from package_a.
Temporary import in
package_b:
using @package_a { a, type B }
Steps:
- Use
to find all call sites ofmoon ide find-references <symbol>
anda
.B - Replace them with
and@package_a.a
.@package_a.B - Remove the
statement and runusing
.moon check