Skip to content

2.0.0-rc.25

Pre-release
Pre-release

Choose a tag to compare

@reid-spencer reid-spencer released this 26 Aug 01:34
· 30 commits to main since this release

2.0.0-rc.25

Delta since 2.0.0-rc.24.

This is the diagnostics release. Every message riddlc emits now names the rule that
produced it, and those names are stable API. Everything else here follows from that.

Diagnostics are data

Every diagnostic carries a stable rule id — kebab-case, subject-prefixed, 370 of them:
[error] [stmt-morph-single-state] Order.riddl(759:7). The rendering is rustc's shape and
the id sits where a rustc reader already looks for it.

riddlc validate --json emits one record per diagnostic on stdout, with the rule id,
severity, class, file, line and column. [] when the model is clean — never empty output,
because a consumer must be able to tell "validated clean" from "the command never ran".

Severity and class are separate fields. One kind conflated them, so a consumer handed
"MissingWarning" had to know riddl's taxonomy to learn it was a warning.

--no-msg-ids restores the previous output exactly, for a script that parses it.

Why this matters more than it sounds: riddl-models drives corpus-wide migrations from
scripts that had to match message TEXT, which breaks silently the first time a message is
reworded. An id does not.

Rules ship with their fixes

riddlc validate --fix applies the codemod a rule carries, then re-validates and
restores every file if the model stopped parsing or gained errors — the same gate
find -replace uses, now shared rather than duplicated. --fix-rule <id> takes one
rule at a time; --fix-dry-run shows the diff and writes nothing.

It reports what it did not fix and why, grouped by reason with the rules named. A
codemod that silently leaves sites behind is worse than one that fixes none.

Two rules carry a mechanical fix today, and the boundary is deliberate: a fix qualifies
only if it is a pure span replacement. Reordering a declaration or inserting a keyword
elsewhere would corrupt source, so those are absent rather than approximated.

Language

Multi-line do and prompt, braced like described as:

do {
  "first line of a longer instruction"
  "second line, which a generator joins with a newline"
}

The single-line form is unchanged and byte-identical on output, so no existing model needs
reformatting. Generators read the joined prose from DoStatement.text.

system.now and system.random — runtime values with known types. system.now is a
TimeStamp, so assigning it to a Duration is an error: an instant is not an interval.
system.bogus says what system actually provides rather than failing as a path lookup.

BAST FORMAT_REVISION is 23. Regenerate .bast files; riddl-models already has.

Seven new rules riddlc knows enough to enforce

Each names both sides and carries an id:

  • stmt-morph-single-state / stmt-become-single-handler — an entity with one state has
    nowhere to morph to
  • state-record-out-of-scope — a carry-forward naming a different state's record
  • state-read-before-creation — reading entity state in the clause that creates it
  • msg-no-instance-address — an entity-handled message carrying no field that names an
    instance
  • repo-inlet-carries-event — a repository is changed by commands and read by queries,
    never by events
  • saga-no-timeout — an unstated bound is the executor's choice, and that choice decides
    when compensation fires
  • value-argument-type-mismatch — constructor arguments are now type-checked, which they
    never were

Both corpora have already migrated: riddl-models is 190/190 clean, riddl-examples clean but
for its deliberately-ambiguous fixture.

Tooling

  • riddlc <command> --corpus <dir> — one process over many models. 190 models in ~10
    seconds against ~154 for the equivalent shell loop.
  • validate always prints a summary, naming which warning classes were enabled — worth
    knowing, because a .conf that switches style and usage off is quietly the lenient gate.
    --fail-on <severity> exits non-zero at or above a level.
  • prettify --check lists non-canonical files and writes nothing. unbastify now
    requires -o
    — its old default was the input's own directory, which silently overwrote
    the sources the .bast was made from.
  • find gains statement-content selectors: -operand-kind, -reads-state,
    -source-regex.
  • dump --json emits every statement operand as a node with its resolved target.

Fixes

  • Prettify emits one space after is, everywhere. This has the widest byte-level reach
    of anything in the release — all 188 corpus models had drifted — so re-prettify before
    diffing anything.
  • Message was being exported to JavaScript under the wrong symbol: an
    @JSExportTopLevel("Message") annotation had been separated from its case class and was
    binding to the object below it.

API

PromptStatement is now DoStatement, and the JSON discriminator is "do". prompt
remains a deprecated synonym of the do statement; prompt(...) with parentheses is a
different thing — a typed hole — and is unaffected.

Trying it

brew install ossuminc/tap/riddlc-rc

Feedback most wanted on the rule ids: whether the names read as guessable, and whether the
--json shape carries what your tooling needs. They are API from here, so this is the
moment to say.