From d1179f12ece440734f3e943c439ed9657b2b691b Mon Sep 17 00:00:00 2001 From: Spencer Brower <6729162+sbrow@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:01:31 -0400 Subject: [PATCH] feat: Mis-spelled extension names now show up as hints. --- AGENTS.md | 2 +- TODOS.md | 5 +++-- markdown/markdown.odin | 28 +++++++++++++++++++++++++--- site/thor.json | 3 ++- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cb41f00..8bdf15d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,7 +73,7 @@ thor/ | Package | Files | Responsibility | |---|---|---| | `treesitter/` | `treesitter.odin` | FFI types (`Parser`, `Node`, `Query`, etc.), `@(link_prefix="ts_")` foreign bindings, grammar management (`Grammar_Store` with persistent allocator, `load_language`/`compile_query` building blocks, `ensure_parser`/`load_grammar` lazy loading, `preload_grammar`/`preload_grammars` for parallel loading with `sync.Mutex` cache protection), statically-linked HTML/CSS grammars | -| `markdown/` | `markdown.odin` | `Extension` enum, `DEFAULT_EXTENSIONS`, `process(body, ext, file_path, allocator)` — full pipeline (clones cmark output, frees original), `parse_extension_list`, `apply_extension_config` | +| `markdown/` | `markdown.odin` | `Extension` enum, `DEFAULT_EXTENSIONS`, `EXTENSION_NAMES`, `process(body, ext, file_path, allocator)` — full pipeline (clones cmark output, frees original), `parse_extension_list` (with "did you mean?" hints via `diagnostics.suggest_correction`), `apply_extension_config` (same), `resolve_extension_conflicts` (Footnotes wins over Sidenotes) | | | `footnotes.odin` | `strip_definitions` (pre-cmark, shared by `.Sidenotes` + `.Footnotes`), `inject_notes` (post-cmark sidenote rendering), `inject_footnotes` (post-cmark standard footnote rendering — numbered `` links + `
    ` at bottom). `.Sidenotes` and `.Footnotes` are mutually exclusive; `resolve_extension_conflicts` in `markdown.odin` picks `.Footnotes` if both are set. | | | `alerts.odin` | `inject_alerts` — GitHub alert blocks (`> [!NOTE]`) → styled blockquotes with semantic class names (`alert-note` etc.) | | | `emoji.odin` | `expand_emoji` — `:shortcode:` → unicode emoji | diff --git a/TODOS.md b/TODOS.md index 5ff8a72..653c023 100644 --- a/TODOS.md +++ b/TODOS.md @@ -13,8 +13,9 @@ - [ ] [aliases](https://gohugo.io/methods/page/aliases/#redirects)? - [ ] Improve diagnostics - [x] keep track of every error and don't report them more than once. - - [ ] show parsed arg when -extension is unrecognized - - [ ] also do typo detection? + - [x] show parsed arg when -extension is unrecognized + - [x] also do typo detection? + - [ ] show where the typo occurred (config file path for `apply_extension_config`, full `-ext:value` for `parse_extension_list`) - [ ] `*` make sure the frontmatter parser has good diagnostics. - [ ] fix the diagnostics in [DIAGNOSTIC TODOS](./DIAGNOSTIC_TODOS.yaml) - [ ] only report format errors once. diff --git a/markdown/markdown.odin b/markdown/markdown.odin index 0b210f8..fa1127c 100644 --- a/markdown/markdown.odin +++ b/markdown/markdown.odin @@ -6,6 +6,8 @@ import "core:encoding/json" import "core:log" import "core:strings" +import diags "../diagnostics" + Extension :: enum { Emoji, Sidenotes, @@ -19,6 +21,11 @@ Extension :: enum { DEFAULT_EXTENSIONS :: bit_set[Extension]{.Emoji, .Sidenotes, .Alerts, .HeadingIDs, .DefLists} +EXTENSION_NAMES :: []string{ + "emoji", "sidenotes", "alerts", "highlight", "sections", + "heading_ids", "deflists", "footnotes", +} + // Caller is responsible for freeing string process :: proc( body: string, @@ -69,7 +76,12 @@ parse_extension_list :: proc(s: string) -> (result: bit_set[Extension]) { e, ok := extension_from_name(name) if !ok { if name != "" { - log.warnf("unknown extension '%s'", name) + suggestion := diags.suggest_correction(EXTENSION_NAMES, name) + if suggestion != "" { + log.warnf("unknown extension '%s' (did you mean '%s'?)", name, suggestion) + } else { + log.warnf("unknown extension '%s'", name) + } } continue } @@ -81,9 +93,19 @@ parse_extension_list :: proc(s: string) -> (result: bit_set[Extension]) { // Given a map[Extension]bool, apply it to ext. apply_extension_config :: proc(ext: ^bit_set[Extension], config: json.Object) { for name, val in config { - // TODO: Silently discards invalid values. enabled := val.(json.Boolean) or_continue - e := extension_from_name(name) or_continue + + e, ok := extension_from_name(name) + if !ok { + lower := strings.to_lower(name, context.temp_allocator) + suggestion := diags.suggest_correction(EXTENSION_NAMES, lower) + if suggestion != "" { + log.warnf("unknown extension '%s' (did you mean '%s'?)", name, suggestion) + } else { + log.warnf("unknown extension '%s'", name) + } + continue + } if enabled { ext^ += {e} diff --git a/site/thor.json b/site/thor.json index dc2f8ee..32708cb 100644 --- a/site/thor.json +++ b/site/thor.json @@ -1,6 +1,7 @@ { "markdown_extensions": { - "footnotes": true + // "footnotes": true + "foonotes": true }, "params": { "author": {