WIT syntax tooling for MoonBit
import {
"moonrockz/wit/parser",
"moonrockz/wit/scanner",
"moonrockz/wit/span",
"moonrockz/wit/diagnostic",
"moonrockz/wit/ast",
"moonrockz/wit/cst",
"moonrockz/wit/traversal",
"moonrockz/wit/printer",
"moonrockz/wit/formatter",
} for "test"test "Guide: Parse a WIT document" {
let source = "package example:greeter; interface greeter { hello: func(name: string) -> string; }"
let scanned = @scanner.scan(source)
assert_true(scanned.diagnostics.is_empty())
let parsed = @parser.parse(source)
assert_true(parsed.is_valid())
let syntax : @ast.Tree = parsed.ast
let concrete : @cst.Tree = parsed.cst
assert_true(syntax.validate() && concrete.validate())
assert_eq(concrete.to_source(), source)
let canonical = @printer.print(syntax).unwrap()
assert_true(syntax.equivalent(@parser.parse(canonical).ast))
}test "Guide: Inspect a resource-based API and its world" {
let source =
#|package example:files;
#|interface files {
#| record metadata { size: u64 }
#| resource file {
#| constructor(path: string);
#| stat: func() -> metadata;
#| }
#| open: func(path: string) -> result<own<file>, string>;
#|}
#|world app { export files; }
let parsed = @parser.parse(source)
assert_true(parsed.is_valid())
assert_eq(parsed.cst.to_source(), source)
let resources = parsed.ast.nodes.filter(node => node.data.kind() == Resource)
assert_eq(resources.length(), 1)
guard resources[0].data is Resource(name~, methods~, has_body~, ..) else {
fail("expected typed resource data")
}
assert_eq(name, "file")
assert_true(has_body)
assert_eq(methods.length(), 2)
assert_true(parsed.ast.nodes.any(node => node.data.kind() == World))
assert_true(parsed.ast.nodes.any(node => node.data.kind() == Record))
}test "Guide: Render diagnostics as editor line and column values" {
let source = "interface broken { type missing = ; }"
let parsed = @parser.parse(source)
let index = @span.Source::new(source)
let errors : ReadOnlyArray[@diagnostic.Diagnostic] = parsed.diagnostics
assert_false(errors.is_empty())
let locations = errors.map(diagnostic => {
let position = index.position(diagnostic.primary.span.start).unwrap()
"example.wit:\{position.line + 1}:\{position.column + 1}: " + diagnostic.code
})
assert_true(locations[0].has_prefix("example.wit:1:"))
assert_false(parsed.is_valid())
assert_eq(parsed.cst.to_source(), source)
}test "Guide: Index WIT declarations" {
let parsed = @parser.parse("interface api { read: func() -> u8; write: func(value: u8); }")
let names = []
let report = @traversal.walk(Ast(parsed.ast), event => {
if event.phase == Enter && event.view.kind == Function {
if parsed.ast.nodes[event.view.index].data is Function(name~, ..) {
names.push(name)
}
// Names are enough for this index; skip signature descendants.
Prune
} else { Continue }
}).unwrap()
assert_eq(names, ["read", "write"])
assert_false(report.stopped)
}test "Guide: Change a type without mutating the parsed tree" {
let parsed = @parser.parse("interface api { type byte = u8; }")
let changed = @traversal.rewrite(parsed.ast, (_view, node) => {
match node.data {
PrimitiveType(name="u8") => Replace(PrimitiveType(name="u64"))
_ => Keep
}
}).unwrap()
assert_true(changed.tree.validate())
assert_true(parsed.ast.nodes.any(node => node.data == PrimitiveType(name="u8")))
assert_false(changed.tree.nodes.any(node => node.data == PrimitiveType(name="u8")))
assert_true(changed.tree.nodes.any(node => node.data == PrimitiveType(name="u64")))
let printed = @printer.print(changed.tree).unwrap()
assert_true(printed.contains("type byte = u64;"))
assert_true(changed.tree.equivalent(@parser.parse(printed).ast))
}test "Guide: Edit a token and format the result" {
let source = "// keep this comment\ninterface api { type byte = u8; }"
let parsed = @parser.parse(source)
let token = parsed.cst.tokens.iter().find_first(token => token.text == "u8").unwrap()
let changed = @traversal.rewrite_cst(parsed.cst, [{ span: token.span, text: "u64" }]).unwrap()
assert_true(changed.is_valid())
assert_eq(changed.cst.to_source(), source.replace(old="u8", new="u64"))
assert_eq(parsed.cst.to_source(), source)
let formatted = @formatter.format_cst(changed.cst).unwrap()
assert_true(formatted.has_prefix("// keep this comment\n"))
assert_eq(@formatter.format(formatted), Ok(formatted))
}test "Guide: Enable only the grammar extension your source needs" {
let source = "interface api { type bytes = list<u8, 16>; }"
assert_false(@parser.parse(source).is_valid())
let features = { ..@parser.Features::default(), fixed_lists: true }
let parsed = @parser.parse(source, features~)
assert_true(parsed.is_valid())
assert_eq(parsed.cst.to_source(), source)
let formatted = @formatter.format_cst(parsed.cst, features~).unwrap()
assert_true(@parser.parse(formatted, features~).is_valid())
}test "Guide: Keep invalid input unchanged and report formatting errors" {
let source = "interface broken { type missing = ; } interface later {}"
match @formatter.format(source) {
Err(InvalidSyntax(diagnostics)) => assert_false(diagnostics.is_empty())
_ => fail("expected syntax diagnostics")
}
assert_eq(@parser.parse(source).cst.to_source(), source)
assert_eq(@formatter.format(source, indent_width=-1), Err(InvalidOptions))
}| Task | Guide |
|---|---|
| Editor offsets, Unicode and safe slices | span recipes |
| Diagnostic severity, recovery and related labels | diagnostic recipes |
| Highlighting, comments and malformed tokens | scanner recipes |
| Typed node fields, arenas and structural equality | ast recipes |
| Exact source and concrete subtree ranges | cst recipes |
| Recovery, feature profiles and source diagnostics | parser recipes |
| Readers, prune/stop, cursors, folds, reductions and rewrites | traversal recipes |
| Canonical source and printer errors | printer recipes |
| Comment retention, indentation and formatting errors | formatter recipes |
| Package | Intended responsibility |
|---|---|
| span | Implemented: source text, UTF-16 positions, checked slices and span containment |
| diagnostic | Implemented: codes, severity, immutable source labels and recovery details |
| scanner | Implemented: lossless tokens, comments, whitespace and lexical diagnostics |
| ast | Implemented: typed immutable nodes, named child fields and structural validation |
| cst | Implemented: lossless grammar hierarchy, token ranges and subtree reconstruction |
| parser | Implemented: recovering grammar parser and explicit proposal switches |
| traversal | Implemented: AST/CST walks, folds, reductions, events, immutable cursors and rewrites |
| printer | Implemented: stack-safe canonical AST emission with structural round-trip checks |
| formatter | Implemented: CST-aware layout preserving comment text, token order and meaning |
fn version() -> String