10 files changed,
354 insertions(+),
213 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-08-13 13:40:52 +0300
Authored at:
2026-08-13 12:01:44 +0300
Change ID:
lkysvlpsntuvvqnqotnmoyukulpotynv
Parent:
4b5c1da
jump to
A
internal/lsp/testdata/completion-contexts.txtar
··· 1 +-- in.journal -- 2 +account expenses:food 3 +account expenses:taxi 4 +account assets:cash 5 + 6 +payee Grocery Store 7 +payee Coffee Shop 8 + 9 +commodity USD 10 + 11 +tag client 12 +tag project 13 + 14 +2024-01-01 Grocery Store 15 + ; client:acme 16 + ; project:alpha 17 + expenses:food $50 18 + expenses:taxi $30 19 + assets:cash 20 + 21 +^ 22 +2024-01-02 * 23 + ^ 24 + expenses:^food $50 25 + expenses:food $^50 26 + expenses:food ^ 27 + expenses:food U^SD 28 + expenses:food $5^0 29 + expenses:food $50 @^ 1.5 30 + * expenses:f^ood $50 31 + (expenses:f^ood) $50 32 + expenses:food ; client:^x 33 + expenses:food ; client: ac^me 34 + ; a:b, client:ac^ 35 + ; url:https://exa^mple 36 + ; a:b:c, cli^ent:x 37 + ; groc^eries 38 + 39 +2024-01-03 * (123) gro^cer 40 +2024-01-03=2024-01-04 cof^fee 41 +2024-01-03 "gro^cery" 42 +2024-01-03 acme | no^te 43 +2024-01-03 acme|note x^y 44 +2024-01-03 ; cli^ 45 +2024-01-03^ 46 + 47 +account expenses:food 48 + no^te ignore 49 + 50 +account expenses:food 51 + note: x 52 + 53 + no^te foo 54 + 55 +account expenses:taxi 56 + note: x 57 + 58 + no^te foo 59 + 60 +~ monthly^ budget 61 + 62 + 63 +2024-01-15 Grocery 64 + expenses:food $50 65 + expenses:taxi $30 66 + assets:cash 67 + 68 +2024-01-16 ^ 69 + expenses:f^ood $5 70 + assets:cash 71 + 72 +-- expect -- 73 +19:0 directive "" 74 + C 75 + D 76 + N 77 + P 78 + Y 79 + account 80 + alias 81 + apply 82 + commodity 83 + decimal-mark 84 + end 85 + include 86 + payee 87 + tag 88 + year 89 +21:4 account "" 90 + expenses:food 91 + assets:cash 92 + expenses:taxi 93 +22:13 account "expenses:" 94 + expenses:food 95 + expenses:taxi 96 +23:20 commodity "$" 97 + $ 98 +24:19 commodity "" 99 + $ 100 + USD 101 +25:20 commodity "U" 102 + USD 103 +26:21 none "" 104 +27:24 none "" 105 +28:16 account "expenses:f" 106 + expenses:food 107 +29:15 account "expenses:f" 108 + expenses:food 109 +30:28 tag-value "" 110 + ac 111 + acme 112 + x 113 +31:31 tag-value "ac" 114 + ac 115 + acme 116 +32:20 tag-value "ac" 117 + ac 118 + acme 119 +33:21 tag-value "https://exa" 120 + https://example 121 +34:16 tag "cli" 122 + client 123 +35:10 tag "groc" 124 +37:22 payee "gro" 125 + grocer 126 + grocery 127 + Grocery 128 + Grocery Store 129 +38:25 payee "cof" 130 + coffee 131 + Coffee Shop 132 +39:15 payee "gro" 133 + grocer 134 + grocery 135 + Grocery 136 + Grocery Store 137 +40:20 none "" 138 +41:22 none "" 139 +42:16 tag "cli" 140 + client 141 +43:10 payee "" 142 + Grocery 143 + Grocery Store 144 + acme 145 + acme|note xy 146 + coffee 147 + grocer 148 + grocery 149 + Coffee Shop 150 +46:6 none "" 151 +51:6 account "no" 152 + expenses:food 153 +56:6 none "" 154 +58:9 none "" 155 +66:11 payee "" 156 + Grocery 157 + Grocery Store 158 + acme 159 + acme|note xy 160 + coffee 161 + grocer 162 + grocery 163 + Coffee Shop 164 +67:14 account "expenses:f" 165 + expenses:food
D
internal/lsp/testdata/completion-crlf.txtar
··· 1 --- in.journal -- 2 -2024-01-15 Grocery 3 - expenses:food $50 4 - expenses:taxi $30 5 - assets:cash 6 - 7 -2024-01-16 ^ 8 - expenses:f^ood $5 9 - assets:cash 10 --- expect -- 11 -5:11 payee "" 12 - Grocery 13 -6:14 account "expenses:f" 14 - expenses:food
M
internal/lsp/testdata/completion-journal.txtar
··· 40 40 41 41 acc^ount 42 42 43 + 44 +2024-01-15 Супермаркет 45 + Витрати:Продукти ¥50 46 + Витрати:Транспорт ¥30 47 + Активи:Готівка 48 + 49 +2024-01-15 Продукти 50 + Витрати:Продукти €10 51 + Активи:Готівка 52 + 53 +2024-01-16 Супер^маркет 54 + Витрати:Про^дукти ¥20 55 + Активи:Готівка 56 + 57 +2024-01-16 Прод^укти 58 + Витрати:Тр^анспорт €5 59 + Активи:Готівка 60 + 61 +commodity ¥^元 62 + 63 +tag 客^户 64 + 65 + 43 66 -- expect -- 44 67 19:10 tag-value "a" 45 68 acme ··· 47 70 alpha 48 71 22:11 payee "" 49 72 Grocery 73 + Продукти 74 + Супермаркет 50 75 Store 51 76 23:12 account "expenses:f" 52 77 expenses:food ··· 64 89 project 65 90 39:3 directive "acc" 66 91 account 92 +51:16 payee "Супер" 93 + Супермаркет 94 +52:13 account "Витрати:Про" 95 + Витрати:Продукти 96 +55:15 payee "Прод" 97 + Продукти 98 +56:12 account "Витрати:Тр" 99 + Витрати:Транспорт 100 +59:11 commodity "¥" 101 + ¥ 102 +61:5 tag "客" 103 + 客户
D
internal/lsp/testdata/completion-unicode.txtar
··· 1 --- in.journal -- 2 -2024-01-15 Супермаркет 3 - Витрати:Продукти ¥50 4 - Витрати:Транспорт ¥30 5 - Активи:Готівка 6 - 7 -2024-01-15 Продукти 8 - Витрати:Продукти €10 9 - Активи:Готівка 10 - 11 -2024-01-16 Супер^маркет 12 - Витрати:Про^дукти ¥20 13 - Активи:Готівка 14 - 15 -2024-01-16 Прод^укти 16 - Витрати:Тр^анспорт €5 17 - Активи:Готівка 18 - 19 -commodity ¥^元 20 - 21 -tag 客^户 22 - 23 --- expect -- 24 -9:16 payee "Супер" 25 - Супермаркет 26 -10:13 account "Витрати:Про" 27 - Витрати:Продукти 28 -13:15 payee "Прод" 29 - Продукти 30 -14:12 account "Витрати:Тр" 31 - Витрати:Транспорт 32 -17:11 commodity "¥" 33 - ¥ 34 -19:5 tag "客" 35 - 客户
M
internal/lsp/textdocument_completion_test.go
··· 15 15 "olexsmir.xyz/clerk/journal" 16 16 ) 17 17 18 -func TestDetectCompletionCtx(t *testing.T) { 19 - tests := map[string]struct { 20 - ctx cmplCtx 21 - in, want string 22 - }{ 23 - "posting account": {cmplAccount, " expenses:f^ood $50\n", "expenses:f"}, 24 - "posting account empty": {cmplAccount, " ^\n", ""}, 25 - "posting account after colon": {cmplAccount, " expenses:^food $50\n", "expenses:"}, 26 - "posting amount commodity": {cmplCommodity, " expenses:food $^50\n", "$"}, 27 - "posting empty amount region": {cmplCommodity, " expenses:food ^\n", ""}, 28 - "posting commodity word": {cmplCommodity, " expenses:food U^SD\n", "U"}, 29 - "posting amount number": {cmplNone, " expenses:food $5^0\n", ""}, 30 - "posting cost quantity": {cmplNone, " expenses:food $50 @^ 1.5\n", ""}, 31 - "posting status": {cmplAccount, " * expenses:f^ood $50\n", "expenses:f"}, 32 - "posting virtual": {cmplAccount, " (expenses:f^ood) $50\n", "expenses:f"}, 33 - "posting comment tag": {cmplTagName, " expenses:food ; clie^nt:x\n", "clie"}, 34 - "posting comment tag value": {cmplTagValue, " expenses:food ; client:^x\n", ""}, 35 - "header payee": {cmplPayee, "2024-01-15 acm^e\n assets:cash\n", "acm"}, 36 - "header payee empty": {cmplPayee, "2024-01-15 ^\n", ""}, 37 - "header payee right after date": {cmplPayee, "2024-01-15^\n", ""}, 38 - "header status and code": {cmplPayee, "2024-01-15 * (123) gro^cer\n", "gro"}, 39 - "header second date": {cmplPayee, "2024-01-15=2024-01-16 acm^e\n", "acm"}, 40 - "header quoted payee": {cmplPayee, "2024-01-15 \"ac^me\"\n", "ac"}, 41 - "header pipe note": {cmplNone, "2024-01-15 acme | no^te\n", ""}, 42 - "header pipe inline": {cmplNone, "2024-01-15 acme|note x^y\n", ""}, 43 - "header inline comment": {cmplTagName, "2024-01-15 ; foo^", "foo"}, 44 - "directive keyword partial": {cmplDirective, "acc^ount expenses\n", "acc"}, 45 - "directive keyword empty line": {cmplDirective, "\n^", ""}, 46 - "account directive value": {cmplAccount, "account exp^enses\n", "exp"}, 47 - "commodity directive value": {cmplCommodity, "commodity U^SD\n", "U"}, 48 - "payee directive value": {cmplPayee, "payee ac^me\n", "ac"}, 49 - "tag directive value": {cmplTagName, "tag pro^ject\n", "pro"}, 50 - "comment tag": {cmplTagName, "; clie^nt:x\n", "clie"}, 51 - "comment value": {cmplTagValue, "; client:x^yz\n", "x"}, 52 - "comment value empty": {cmplTagValue, "; client:^\n", ""}, 53 - "comment value after space": {cmplTagValue, "; client: ac^me\n", "ac"}, 54 - "comment value second tag": {cmplTagValue, "; a:b, client:x^\n", "x"}, 55 - "comment value with colon": {cmplTagValue, "; url:https://example^\n", "https://example"}, 56 - "comment key after previous value": {cmplTagName, "; a:b:c, cli^ent:x\n", "cli"}, 57 - "comment plain text": {cmplTagName, "; groc^eries\n", "groc"}, 58 - "subdirective ignored": {cmplNone, "account expenses\n no^te ignore\n", ""}, 59 - "periodic header": {cmplNone, "~ monthly^ budget\n", ""}, 60 - "cjk posting": {cmplAccount, " 支出:食^物 50\n", "支出:食"}, 61 - "ukrainian payee": {cmplPayee, "2024-01-15 прод^укти\n", "прод"}, 62 - "crlf posting": {cmplAccount, "2024-01-15 x\r\n expenses:f^ood $50\r\n", "expenses:f"}, 63 - "crlf header": {cmplPayee, "2024-01-15 acm^e\r\n assets:cash\r\n", "acm"}, 64 - } 65 - for tname, tt := range tests { 66 - t.Run(tname, func(t *testing.T) { 67 - cont := tt.in 68 - i := strings.Index(cont, "^") 69 - if i < 0 { 70 - t.Fatal("no cursor marker '^' in content") 71 - } 72 - 73 - cont = cont[:i] + cont[i+1:] 74 - ctx, start := detectCompletionCtx(cont, i) 75 - prefix := cont[start:i] 76 - 77 - if ctx != tt.ctx { 78 - t.Errorf("ctx = %v, want %v", ctx, tt.ctx) 79 - } 80 - if prefix != tt.want { 81 - t.Errorf("prefix = %q, want %q", prefix, tt.want) 82 - } 83 - }) 84 - } 85 -} 86 - 87 -func (c cmplCtx) String() string { 88 - switch c { 89 - case cmplNone: 90 - return "none" 91 - case cmplAccount: 92 - return "account" 93 - case cmplPayee: 94 - return "payee" 95 - case cmplCommodity: 96 - return "commodity" 97 - case cmplTagName: 98 - return "tag" 99 - case cmplTagValue: 100 - return "tag-value" 101 - case cmplDirective: 102 - return "directive" 103 - default: 104 - return "?" 105 - } 106 -} 107 - 108 18 func TestDetectCompletion_NoJournal(t *testing.T) { 109 19 // detection must not depend on a parse 110 20 i := strings.Index(" no^te foo\n", "^") ··· 115 25 } 116 26 } 117 27 118 -func TestDetectCompletion_Subdirective(t *testing.T) { 119 - // the lexical rule suppresses completion on subdirective lines without a parse 120 - i := strings.Index("account expenses\n no^te ignore\n", "^") 121 - content := "account expenses\n note ignore\n" 122 - if ctx, _ := detectCompletionCtx(content, i); ctx != cmplNone { 123 - t.Errorf("subdirective: ctx = %v, want none", ctx) 124 - } 125 - // a posting line after a transaction header is still an account context 126 - i = strings.Index("2024-01-15 acme\n expe^nses:food $50\n", "^") 127 - content = "2024-01-15 acme\n expenses:food $50\n" 128 - if ctx, _ := detectCompletionCtx(content, i); ctx != cmplAccount { 129 - t.Errorf("posting: ctx = %v, want account", ctx) 130 - } 131 - // a blank line ends the directive body; a whitespace-only line does not 132 - i = strings.Index("account expenses\n note: x\n\n no^te\n", "^") 133 - content = "account expenses\n note: x\n\n note\n" 134 - if ctx, _ := detectCompletionCtx(content, i); ctx != cmplAccount { 135 - t.Errorf("after blank line: ctx = %v, want account", ctx) 136 - } 137 - i = strings.Index("account expenses\n note: x\n \n no^te\n", "^") 138 - content = "account expenses\n note: x\n \n note\n" 139 - if ctx, _ := detectCompletionCtx(content, i); ctx != cmplNone { 140 - t.Errorf("whitespace-only line keeps body: ctx = %v, want none", ctx) 141 - } 142 -} 143 - 144 -func TestCompleteItems_NoTransactions(t *testing.T) { 145 - // directives-only doc: empty a.Dates must not panic the ranking 146 - content := "account expenses:food\n\n^" 147 - i := strings.Index(content, "^") 148 - content = content[:i] 149 - a := analyzer.Build(journal.NewLoader().ResolveBytes("", []byte(content))) 150 - if len(a.Dates) != 0 { 151 - t.Fatalf("setup: want 0 dates, got %d", len(a.Dates)) 152 - } 153 - items := cmplItems(a, cmplAccount, content, len(content), len(content)) 154 - if len(items) == 0 { 155 - t.Fatal("expected the directive-defined account to complete") 156 - } 157 - if items[0].Label != "expenses:food" { 158 - t.Errorf("label = %q, want expenses:food", items[0].Label) 159 - } 160 -} 161 - 162 -// Golden 163 - 164 -func TestCompletionTxtar(t *testing.T) { 165 - for _, tt := range []string{"completion-journal", "completion-unicode", "completion-crlf"} { 28 +func TestGolden_Completion(t *testing.T) { 29 + for _, tt := range []string{"completion-contexts", "completion-journal", "completion-no-transactions"} { 166 30 ar := golden.Read(t, tt) 167 31 168 32 t.Run(tt, func(t *testing.T) { ··· 192 56 } 193 57 } 194 58 195 -// Benchmark 59 +func (c cmplCtx) String() string { 60 + switch c { 61 + case cmplNone: 62 + return "none" 63 + case cmplAccount: 64 + return "account" 65 + case cmplPayee: 66 + return "payee" 67 + case cmplCommodity: 68 + return "commodity" 69 + case cmplTagName: 70 + return "tag" 71 + case cmplTagValue: 72 + return "tag-value" 73 + case cmplDirective: 74 + return "directive" 75 + default: 76 + return "?" 77 + } 78 +} 196 79 197 80 func BenchmarkCompletion(b *testing.B) { 198 81 path := "../../journal/testdata/journals/actual-1ktxns-100accts.journal" ··· 207 90 srv.server.openDoc(uri.URI("file:///test.journal"), content, 1, "journal") 208 91 srv.server.current = a 209 92 210 - for _, tc := range []struct { 211 - name string 212 - pos int 213 - }{ 214 - {"1k txns, account", strings.Index(content, "\n 1:2:3 ") + len("\n ") + 2}, 215 - {"1k txns, empty payee", strings.Index(content, "transaction 1") + len("transaction ")}, 216 - {"1k txns, commodity", strings.Index(content, "2 B @@") + len("2 B")}, 93 + for tname, tt := range map[string]int{ 94 + "1k txns, account": strings.Index(content, "\n 1:2:3 ") + len("\n ") + 2, 95 + "1k txns, empty payee": strings.Index(content, "transaction 1") + len("transaction "), 96 + "1k txns, commodity": strings.Index(content, "2 B @@") + len("2 B"), 217 97 } { 218 - b.Run(tc.name, func(b *testing.B) { 219 - line, col := lsputil.LineCol(content, tc.pos) 98 + b.Run(tname, func(b *testing.B) { 99 + line, col := lsputil.LineCol(content, tt) 220 100 params := &protocol.CompletionParams{ 221 101 TextDocumentPositionParams: protocol.TextDocumentPositionParams{ 222 102 TextDocument: protocol.TextDocumentIdentifier{URI: uri.URI("file:///test.journal")}, 223 103 Position: protocol.Position{Line: uint32(line), Character: uint32(col)}, 224 104 }, 225 105 } 226 - // warm up: first request parses the journal lazily 106 + 107 + // warm up 227 108 if _, err := srv.server.Completion(b.Context(), params); err != nil { 228 109 b.Fatal(err) 229 110 }
M
internal/lsp/textdocument_definition_test.go
··· 9 9 "go.lsp.dev/protocol" 10 10 "go.lsp.dev/uri" 11 11 12 + "olexsmir.xyz/clerk/internal/analyzer" 13 + "olexsmir.xyz/clerk/internal/lsp/lsputil" 12 14 "olexsmir.xyz/clerk/internal/testutil/golden" 15 + "olexsmir.xyz/clerk/journal" 13 16 ) 14 17 15 18 func TestDefinition_DocumentNotFound(t *testing.T) { ··· 28 31 } 29 32 } 30 33 31 -// Golden 32 - 33 -func TestDefinitionTxtar(t *testing.T) { 34 +func TestGolden_Definition(t *testing.T) { 34 35 for _, tt := range []string{"definition-journal", "definition-include"} { 35 36 ar := golden.Read(t, tt) 36 37 t.Run(tt, func(t *testing.T) { ··· 59 60 }) 60 61 } 61 62 } 63 + 64 +func BenchmarkDefinition(b *testing.B) { 65 + path := "../../journal/testdata/journals/actual-1ktxns-100accts.journal" 66 + abs, err := filepath.Abs(path) 67 + if err != nil { 68 + b.Fatal(err) 69 + } 70 + rj, err := journal.NewLoader().Resolve(abs) 71 + if err != nil { 72 + b.Fatal(err) 73 + } 74 + a := analyzer.Build(rj) 75 + content := string(rj.Occurrences[0].Src) 76 + 77 + srv := NewServer("test") 78 + u := uri.File(abs) 79 + srv.server.openDoc(u, content, 1, "journal") 80 + srv.server.current = a 81 + 82 + for tname, tt := range map[string]int{ 83 + "1k txns, account": strings.Index(content, "\n 1:2:3 ") + len("\n ") + 2, 84 + "1k txns, payee":strings.Index(content, "transaction 1") + len("transaction "), 85 + "1k txns, commodity":strings.Index(content, "2 B @@") + len("2 B"), 86 + } { 87 + b.Run(tname, func(b *testing.B) { 88 + line, col := lsputil.LineCol(content, tt) 89 + params := &protocol.DefinitionParams{ 90 + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ 91 + TextDocument: protocol.TextDocumentIdentifier{URI: u}, 92 + Position: protocol.Position{Line: uint32(line), Character: uint32(col)}, 93 + }, 94 + } 95 + // warm up: first request resolves the symbol lazily; assert it found one 96 + res, err := srv.server.Definition(b.Context(), params) 97 + if err != nil { 98 + b.Fatal(err) 99 + } 100 + locs, ok := res.(protocol.LocationSlice) 101 + if !ok || len(locs) == 0 { 102 + b.Fatalf("%s: no definition", tname) 103 + } 104 + b.ReportAllocs() 105 + b.ResetTimer() 106 + for b.Loop() { 107 + if _, err := srv.server.Definition(b.Context(), params); err != nil { 108 + b.Fatal(err) 109 + } 110 + } 111 + }) 112 + } 113 +}
M
internal/lsp/textdocument_rename_test.go
··· 11 11 "go.lsp.dev/protocol" 12 12 "go.lsp.dev/uri" 13 13 14 + "olexsmir.xyz/clerk/internal/analyzer" 14 15 "olexsmir.xyz/clerk/internal/lsp/lsputil" 15 16 "olexsmir.xyz/clerk/internal/testutil/golden" 17 + "olexsmir.xyz/clerk/journal" 16 18 ) 17 19 18 20 func TestValidateRenameName(t *testing.T) { ··· 69 71 70 72 // Golden 71 73 72 -func TestRenameTxtar(t *testing.T) { 74 +func TestGolden_Rename(t *testing.T) { 73 75 for _, tt := range []string{"rename-account", "rename-commodity", "rename-payee", "rename-tag"} { 74 76 ar := golden.Read(t, tt) 75 77 t.Run(tt, func(t *testing.T) { ··· 121 123 } 122 124 } 123 125 golden.Assert(t, ar, b.String()) 126 + }) 127 + } 128 +} 129 + 130 +func BenchmarkRename(b *testing.B) { 131 + path := "../../journal/testdata/journals/actual-1ktxns-100accts.journal" 132 + abs, err := filepath.Abs(path) 133 + if err != nil { 134 + b.Fatal(err) 135 + } 136 + rj, err := journal.NewLoader().Resolve(abs) 137 + if err != nil { 138 + b.Fatal(err) 139 + } 140 + a := analyzer.Build(rj) 141 + content := string(rj.Occurrences[0].Src) 142 + 143 + srv := NewServer("test") 144 + u := uri.File(abs) 145 + srv.server.openDoc(u, content, 1, "journal") 146 + srv.server.current = a 147 + 148 + for tname, tt := range map[string]struct { 149 + newName string 150 + pos int 151 + }{ 152 + "1k txns, account": {"1:2:3x", strings.Index(content, "\n 1:2:3 ") + len("\n ") + 2}, 153 + "1k txns, payee": {"transaction 1x", strings.Index(content, "transaction 1") + len("transaction ")}, 154 + "1k txns, commodity": {"Bx", strings.Index(content, "2 B @@") + len("2 B")}, 155 + } { 156 + b.Run(tname, func(b *testing.B) { 157 + line, col := lsputil.LineCol(content, tt.pos) 158 + params := &protocol.RenameParams{ 159 + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ 160 + TextDocument: protocol.TextDocumentIdentifier{URI: u}, 161 + Position: protocol.Position{Line: uint32(line), Character: uint32(col)}, 162 + }, 163 + NewName: tt.newName, 164 + } 165 + // warm up: first request generates the workspace edits; assert it found one 166 + edit, err := srv.server.Rename(b.Context(), params) 167 + if err != nil { 168 + b.Fatal(err) 169 + } 170 + if edit == nil || len(edit.Changes) == 0 { 171 + b.Fatalf("%s: no edits", tname) 172 + } 173 + b.ReportAllocs() 174 + b.ResetTimer() 175 + for b.Loop() { 176 + if _, err := srv.server.Rename(b.Context(), params); err != nil { 177 + b.Fatal(err) 178 + } 179 + } 124 180 }) 125 181 } 126 182 }
M
internal/lsp/textdocument_semantic_test.go
··· 160 160 161 161 // Golden 162 162 163 -func TestSemanticTokensTxtar(t *testing.T) { 164 - tests := []string{ 165 - "semantic-empty", 166 - "semantic-journal", 167 - "semantic-directives", 168 - "semantic-unparseable", 169 - "semantic-with-errors", 170 - } 171 - 172 - for _, tt := range tests { 163 +func TestGolden_SemanticTokens(t *testing.T) { 164 + for _, tt := range []string{"semantic-empty", "semantic-journal", "semantic-directives", "semantic-unparseable", "semantic-with-errors"} { 173 165 ar := golden.Read(t, tt) 174 166 175 167 t.Run(tt+"_golden", func(t *testing.T) {