From 2ea3648ff6e36585eb8ba20c223ba8407ee5f9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Gonz=C3=A1lez=20Di=20Antonio?= Date: Sat, 24 Oct 2026 20:29:42 +0200 Subject: [PATCH] docs: the dialect diagrams render on GitHub GitHub decodes HTML entities before it hands a diagram to Mermaid, so the " in docs/dialects.md's checking diagram reached Mermaid as a quote that ended its label: a parse error on GitHub, and a diagram in every renderer that leaves the entity alone, which is how it was checked. The labels use Mermaid's own codes (#quot;, #gt;), which GitHub leaves alone, in dialects.md, error-handling.md and filtering.md. All 21 diagrams were rendered after GitHub's decoding, which reproduced the failure on main. TestNoDiagramUsesAnHTMLEntity refuses an entity in a diagram. Co-Authored-By: Claude Opus 5.5 --- docs/dialects.md | 6 ++--- docs/error-handling.md | 2 +- docs/filtering.md | 2 +- docs_mermaid_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 docs_mermaid_test.go diff --git a/docs/dialects.md b/docs/dialects.md index 36a1579..71c6264 100644 --- a/docs/dialects.md +++ b/docs/dialects.md @@ -114,11 +114,11 @@ flowchart TD IN["filter text"] --> LEX["Lexer: tokens
(strings read as the dialect reads them)"] LEX --> OP["an operator, as written
(e.g. !=, ILIKE, ~~, ISNULL)"] OP --> D1{"does the dialect have
the operator?"} - D1 -->|"no: ILIKE, ~ in SQL:2023 and MySQL 8.4;
SIMILAR TO, IS DISTINCT FROM in MySQL 8.4"| E1["operator "ILIKE" is not part of
the SQL:2023 dialect"] + D1 -->|"no: ILIKE, ~ in SQL:2023 and MySQL 8.4;
SIMILAR TO, IS DISTINCT FROM in MySQL 8.4"| E1["operator #quot;ILIKE#quot; is not part of
the SQL:2023 dialect"] D1 -->|"yes"| D2{"does it have
this spelling of it?"} - D2 -->|"no: != in SQL:2023; ~~ !~~ ISNULL NOTNULL
in both; BETWEEN SYMMETRIC in MySQL 8.4"| E2["!= is not part of the SQL:2023
dialect: write <>"] + D2 -->|"no: != in SQL:2023; ~~ !~~ ISNULL NOTNULL
in both; BETWEEN SYMMETRIC in MySQL 8.4"| E2["!= is not part of the SQL:2023
dialect: write #lt;#gt;"] D2 -->|"yes"| A{"allowed by
WithAllowedOperators?"} - A -->|"no"| E3["operator "LIKE" is not allowed"] + A -->|"no"| E3["operator #quot;LIKE#quot; is not allowed"] A -->|"yes, or no allow-list"| OK["into the AST"] E1 --> J["errors.Join: every problem"] E2 --> J diff --git a/docs/error-handling.md b/docs/error-handling.md index b605142..ac27558 100644 --- a/docs/error-handling.md +++ b/docs/error-handling.md @@ -60,7 +60,7 @@ flowchart TD P --> CONS{"entire input
consumed?"} CONS -->|"no"| ADD["add 'unexpected token' error"] CONS -->|"yes"| Q - ADD --> Q{"len(errors) > 0?"} + ADD --> Q{"len(errors) #gt; 0?"} Q -->|"yes"| J["return errors.Join(…)
of *QFVFilterError"] Q -->|"no"| OK["return AST, nil"] ``` diff --git a/docs/filtering.md b/docs/filtering.md index b3f95da..447f2b6 100644 --- a/docs/filtering.md +++ b/docs/filtering.md @@ -273,7 +273,7 @@ parses into this tree (precedence makes `AND` the root, with the parenthesized graph TD AND["BinaryOperatorNode
AND"] AND --> GRP["GroupNode
( … )"] - AND --> GT["BinaryOperatorNode
>"] + AND --> GT["BinaryOperatorNode
#gt;"] GRP --> OR["BinaryOperatorNode
OR"] OR --> EQ1["BinaryOperatorNode
="] diff --git a/docs_mermaid_test.go b/docs_mermaid_test.go new file mode 100644 index 0000000..27c21c3 --- /dev/null +++ b/docs_mermaid_test.go @@ -0,0 +1,50 @@ +package qfv + +import ( + "os" + "path/filepath" + "regexp" + "testing" +) + +// GitHub decodes HTML entities before it hands a diagram to Mermaid, so a +// label written with " reaches Mermaid as a quote that ends the label: +// docs/dialects.md's checking diagram rendered as a parse error on GitHub, +// and as a diagram everywhere else. Mermaid's own entity codes (#quot;, +// #lt;, #gt;, #amp;) are left alone by GitHub and decoded by Mermaid. +func TestNoDiagramUsesAnHTMLEntity(t *testing.T) { + t.Parallel() + + files, err := filepath.Glob("*.md") + if err != nil { + t.Fatal(err) + } + + docs, err := filepath.Glob("docs/*.md") + if err != nil { + t.Fatal(err) + } + + block := regexp.MustCompile("(?s)```mermaid\n(.*?)```") + entity := regexp.MustCompile(`&(quot|lt|gt|amp|#[0-9]+);`) + diagrams := 0 + + for _, f := range append(files, docs...) { + raw, err := os.ReadFile(f) + if err != nil { + t.Fatal(err) + } + + for _, m := range block.FindAllStringSubmatch(string(raw), -1) { + diagrams++ + + if e := entity.FindString(m[1]); e != "" { + t.Errorf("%s: a diagram uses the HTML entity %s, which GitHub decodes before Mermaid parses it: write #%s", f, e, e[1:]) + } + } + } + + if diagrams < 10 { + t.Fatalf("read %d diagrams, want at least 10: the docs moved and this test reads nothing", diagrams) + } +}