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.

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
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"
manifest: skills/development/moonbit-refactoring-bobzhang/SKILL.md
source content

MoonBit 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:

  1. Architecture first: Review package structure, dependencies, and API boundaries.
  2. Inventory public APIs and call sites (
    moon doc
    ,
    moon ide find-references
    ).
  3. Pick one refactor theme (API minimization, package splits, pattern matching, loop style).
  4. Apply the smallest safe change.
  5. Update docs/tests in the same patch.
  6. Run
    moon check
    , then
    moon test
    .
  7. 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
:

  1. Create the new package and re-export temporarily:

    // In package B
    using @A { ... }  // re-export A's APIs
    

    Ensure

    moon check
    passes before proceeding.

  2. Find and update all call sites:

    moon ide find-references <symbol>
    

    Replace bare

    f
    with
    @B.f
    .

  3. Remove the

    use
    statement once all call sites are updated.

  4. Audit and remove newly-unused

    pub
    APIs from both packages.

Guidelines

  • Prefer acyclic dependencies: lower-level packages should not import higher-level ones.
  • Only expose what downstream packages actually need.
  • Consider an
    internal/
    package for helpers that shouldn't leak.

Minimize Public API and Modularize

  • Remove
    pub
    from helpers; keep only required exports.
  • Move helpers into
    internal/
    packages to block external imports.
  • 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
    @pkg.fn
    instead of
    using
    when clarity matters.
  • Keep call sites explicit during wide refactors.

Example:

let n = @parser.parse_number(token)

Simplify Constructors When Type Is Known

  • Drop
    TypePath::Constr
    when the surrounding type is known.

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
    /
    StringView
    indexing yields
    UInt16
    code units. Use
    for ch in s
    for Unicode-aware iteration.

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

  • Use
    is
    patterns inside
    if
    /
    guard
    to keep branches concise.

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 { ... }
    ,
    for i in large>..small
    , or
    for i in large>=..small
    for simple index loops.
  • Keep functional-state
    for
    loops for algorithms that update state.

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
    for x in xs
    loops.
  • 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
    *_test.mbt
    or
    *.mbt.md
    .
  • Add docstring tests with
    mbt check
    for public APIs.

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:

  1. Use
    moon ide find-references <symbol>
    to find all call sites of
    a
    and
    B
    .
  2. Replace them with
    @package_a.a
    and
    @package_a.B
    .
  3. Remove the
    using
    statement and run
    moon check
    .