Something went wrong. Try again.
A human-friendly DSL for ATProto Lexicons mlf.lol
atproto lexicons cli dns
Something went wrong. Try again.
Diagnostics guidelines #
How MLF words, structures, and renders errors and warnings. Adapted
from rustc's diagnostics philosophy (rustc dev guide: Errors and
lints) for our
stack: mlf-lang produces structured errors, mlf-diagnostics renders
them through miette for the CLI, and mlf-wasm/mlf-lsp render them
for the playground and editors.
Principles #
- Plain English, matter-of-fact. If a normal programmer can't understand the message, it's too complex. No blame, no jargon from our internals ("workspace resolution", "module symbol table"), no the-user-did-something-"illegal" — prefer "invalid" or something more specific.
- Diagnose the mistake, not the symptom. The resolver knows more
at failure time than any renderer does later. Compute targeted help
where the context lives (e.g.
Workspace::suggest_for_undefined) and carry it on the error value, rather than reverse-engineering intent from a message string downstream. - State facts, don't ask questions. rustc moved away from "did
you mean
Foo?" to "there is a struct with a similar name:Foo". Same here:a def with a similar name is in scope: `external`. - Smallest useful span. Point at the reference, not the whole field; the primary label should still make sense if it were the only thing displayed.
- One error per root cause. Don't cascade: an unresolved import shouldn't also error every reference that used its binding, when the import error alone explains them.
- Every message names the thing. Reference identifiers, paths,
and syntax in backticks:
`record`,`use x as y;`.
Anatomy (miette mapping) #
| rustc concept | ours |
|---|---|
| level | miette severity |
error code (E0308) |
#[diagnostic(code(mlf::…))] — stable, greppable |
| message | the error's Display/message() — standalone summary |
| span labels | LabeledSpans from mlf-diagnostics |
| help | get_error_help / the error's suggestion field |
| note | additional LabeledSpans or trailing context |
help is for a concrete change the user can make. note is for context and facts. If you can phrase an actionable edit, it's help.
Wording style #
- lowercase, no trailing period, unless multiple sentences are needed
- succinct: users see the same message many times
- name the language "MLF" and the tool by its command (
mlf check), not "the compiler" - suggestions must be conservative: only offer an edit you'd be comfortable auto-applying or that is obviously checkable by the user
Current targeted suggestions (wave 1) #
UndefinedReference.suggestioncarries pre-phrased help:- a non-final segment of an import path matches the name →
`app.bsky.embed.external` is imported as `external` — reference it as `external`, or write `use … as embed;`(the exact confusion observed twice in the wild). - an in-scope name within edit distance
max(1, len/3)→a def with a similar name is in scope: `external`. - a typo'd item in
use ns.{…}→ nearest def in the target module.
- a non-final segment of an import path matches the name →
Wave 2 (done) #
- Single source of wording:
ValidationError::message()/::help()andParseError::message()/::span()/::help()live inmlf-lang;mlf-diagnostics(CLI) andmlf-wasm(playground) delegate to them. No miette needed on wasm32 — the shared part is plain strings. - Keyword-as-name hints:
ParseError::Syntaxcarrieshelp, attached where the parser still knows the offending token — a keyword in identifier or type position points at the backtick escape. use-target typos: unresolvable use targets suggest the nearest known lexicon namespace.
Wave 3 (done) #
- LSP unified: editor diagnostics use
message()/primary_span()and appendhelp()to the diagnostic text, so all three surfaces (CLI, playground, editor) say the same thing. - Structured constraint errors:
InvalidConstraintcarries aConstraintProblemclass andConstraintTooPermissivecarries the loosened constraint key —help()matches on structure, never on message substrings.
Wave 4 (done) #
- One report per error in
mlf check— each error keeps its own code, caret, and help line instead of hiding behind a "Found N validation error(s)" batch headline. - Wording pass: every message and span label follows this doc —
lowercase, matter-of-fact, backticked identifiers. Parse errors name
tokens honestly via
Token::describe()("found keywordrecord", "found{", "found end of file"). - Playground locations + squiggles: the wasm surface returns
structured diagnostics (message, help, span, line/col); errors
display with
line:colprefixes and the editor draws wavy underlines under error spans (a third scroll-synced layer). - Vague fallback help removed ("check for missing commas…") — help is only ever site-attached guidance.
mlf explain <code>: long-form explanations for everymlf::…code, rustc--explainstyle.- LSP: diagnostics carry the
mlf::…code and surface help asrelatedInformation(expandable note with a jump link) instead of a run-on message.
Wave 5 (done) #
- Machine-applicable fixes:
SuggestedFix { label, span, replacement }attached at the error site — typo replacements, theuse … as …;rebinding, keyword backtick escapes. - LSP quick-fix code actions: fixes are serialized onto
Diagnostic.dataat publish time and served fromcodeActionwithout recomputation. The pre-existing organize-imports actions were silently dead (they matched old message prefixes — the exact anti-pattern this doc bans); they now trigger on the stablemlf::…code, and the missing-import action reads the name from the diagnostic's range instead of the message. - Warnings channel:
resolve()keeps non-blocking findings (unused imports) onWorkspace::warnings()instead of dropping them. The CLI renders them as ⚠ reports without failing the check; the LSP publishes them as hints with the removal quick-fix.
Wave 6 (done) #
- Playground "apply fix" buttons: the wasm diagnostics carry the
same
SuggestedFixdata the LSP uses; the playground renders each as a button that applies the edit to the editor (guarded against stale offsets — clicks are ignored if the source changed since the check). - Typecheck no longer gated behind name resolution: reference errors, constraint errors, and cycles all surface in one resolve. Safe because the later phases skip unresolved references — findings stay accurate, and users get every actionable error at once.
All planned waves are complete. New diagnostics work should follow the
principles above; the UI test suite (tests/diagnostics/) is the
regression net — add a fixture for every new message, hint, or fix.