Skip to content

Commit 893bcec

Browse files
author
merge-queue-bot
committed
Merge PR #686: test(inline_scan): add dedicated unit tests for 12 unexported helpers
2 parents 10d2692 + 865919b commit 893bcec

5 files changed

Lines changed: 421 additions & 20 deletions

File tree

PLAN.md

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,7 @@ footer: |
1616

1717
| ID | Status | Model | Title |
1818
| ---------- | ------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------- |
19-
| 52 || | [Archetype / Template Library for Agentic Patterns](plan/52_archetype-template-library.md) |
20-
| 61 || | [Required Structure Rule Hardening](plan/61_required-structure-hardening.md) |
21-
| 65 || | [Spike WASM-Embedded Weasel Inference](plan/65_spike-wasm-embedded-inference.md) |
22-
| 78 || | [Query subcommand for front-matter filtering](plan/78_query-command.md) |
23-
| 83 || | [Security hardening batch](plan/83_security-hardening-batch.md) |
24-
| 84 || | [Symlink default-deny for file discovery](plan/84_symlink-default-deny.md) |
25-
| 85 || | [Increase test coverage to 95% by extracting shared rule helpers](plan/85_coverage-to-95-percent.md) |
26-
| 86 || | [Markdown flavor validation](plan/86_markdown-flavor-validation.md) |
27-
| 89 || | [TOC generator directive and MDS035 auto-fix](plan/89_toc-generator-directive.md) |
28-
| 90 || | [Isolate corpus test git config from host signing](plan/90_corpus-test-git-config-isolation.md) |
29-
| 91 || | [MDS037 skips paragraphs inside generated sections](plan/91_mds037-skip-generated-sections.md) |
30-
| 92 || sonnet | [File kinds — config schema, assignment, merge](plan/92_file-kinds.md) |
31-
| 93 || sonnet | [Placeholder grammar — opt-in token vocabulary](plan/93_placeholder-grammar.md) |
32-
| 94 || sonnet | [Lint-once for `<?include?>` and `<?catalog?>` embeds](plan/94_lint-once-for-embeds.md) |
33-
| 95 || opus | [Kind/rule resolution observability via `kinds` subcommand](plan/95_kind-rule-resolution-cli.md) |
34-
| 96 || sonnet | [Adopt kinds in mdsmith repo and ship the docs](plan/96_kinds-adoption-and-docs.md) |
35-
| 97 || opus | [Deep-merge for kinds and overrides](plan/97_deep-merge-config.md) |
36-
| 98 || sonnet | [Replace `archetypes` with `kinds`](plan/98_replace-archetypes-with-kinds.md) |
19+
| | | | [](plan/2606231014_arch-fix-samefileanchor-helper-tests.md) |
3720
| 100 || sonnet | [build config block and MDS040 recipe-safety rule](plan/100_build-config-and-mds040.md) |
3821
| 101 || sonnet | [build directive and MDS039 lint rule](plan/101_build-directive-mds039.md) |
3922
| 102 || opus | [Multi-output `<?build?>` directive](plan/102_build-subcommand.md) |
@@ -222,5 +205,23 @@ footer: |
222205
| 2606211908 || | [arch-fix: split internal/lint/layer0.go](plan/2606211908_arch-fix-layer0-split.md) |
223206
| 2606211909 || | [arch-fix: split internal/lsp/server.go](plan/2606211909_arch-fix-lsp-server-split.md) |
224207
| 2606211910 || | [arch-fix: add trivial-accessor exemption comments in workspace.go](plan/2606211910_arch-fix-workspace-exemptions.md) |
225-
| 2606231014 || sonnet | [Add dedicated unit tests for samefileanchor helper functions](plan/2606231014_arch-fix-samefileanchor-helper-tests.md) |
208+
| 2606231013 || sonnet | [Add dedicated unit tests for inline_scan.go helpers](plan/2606231013_arch-fix-inline-scan-helper-tests.md) |
209+
| 52 || | [Archetype / Template Library for Agentic Patterns](plan/52_archetype-template-library.md) |
210+
| 61 || | [Required Structure Rule Hardening](plan/61_required-structure-hardening.md) |
211+
| 65 || | [Spike WASM-Embedded Weasel Inference](plan/65_spike-wasm-embedded-inference.md) |
212+
| 78 || | [Query subcommand for front-matter filtering](plan/78_query-command.md) |
213+
| 83 || | [Security hardening batch](plan/83_security-hardening-batch.md) |
214+
| 84 || | [Symlink default-deny for file discovery](plan/84_symlink-default-deny.md) |
215+
| 85 || | [Increase test coverage to 95% by extracting shared rule helpers](plan/85_coverage-to-95-percent.md) |
216+
| 86 || | [Markdown flavor validation](plan/86_markdown-flavor-validation.md) |
217+
| 89 || | [TOC generator directive and MDS035 auto-fix](plan/89_toc-generator-directive.md) |
218+
| 90 || | [Isolate corpus test git config from host signing](plan/90_corpus-test-git-config-isolation.md) |
219+
| 91 || | [MDS037 skips paragraphs inside generated sections](plan/91_mds037-skip-generated-sections.md) |
220+
| 92 || sonnet | [File kinds — config schema, assignment, merge](plan/92_file-kinds.md) |
221+
| 93 || sonnet | [Placeholder grammar — opt-in token vocabulary](plan/93_placeholder-grammar.md) |
222+
| 94 || sonnet | [Lint-once for `<?include?>` and `<?catalog?>` embeds](plan/94_lint-once-for-embeds.md) |
223+
| 95 || opus | [Kind/rule resolution observability via `kinds` subcommand](plan/95_kind-rule-resolution-cli.md) |
224+
| 96 || sonnet | [Adopt kinds in mdsmith repo and ship the docs](plan/96_kinds-adoption-and-docs.md) |
225+
| 97 || opus | [Deep-merge for kinds and overrides](plan/97_deep-merge-config.md) |
226+
| 98 || sonnet | [Replace `archetypes` with `kinds`](plan/98_replace-archetypes-with-kinds.md) |
226227
<?/catalog?>

docs/development/architecture-audit.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ summary: >-
66
solid-architecture skill (audit mode)
77
appends here; blockers are also filed as
88
plans.
9-
audit-from: e701b94b5640dfb4f5d07d1fc38d49e0dba23e75
9+
audit-from: 1599c9f17336b36d4d06d10677b2510bfe33665b
1010
---
1111
# Architecture audit log
1212

@@ -193,3 +193,30 @@ in the new `internal/rules/listscan` helper
193193
[2606211908]: ../../plan/2606211908_arch-fix-layer0-split.md
194194
[2606211909]: ../../plan/2606211909_arch-fix-lsp-server-split.md
195195
[2606211910]: ../../plan/2606211910_arch-fix-workspace-exemptions.md
196+
197+
## Audit 2026-06-23 (range: e701b94..1599c9f)
198+
199+
Performance + struct-alignment series;
200+
inline scanner refinements; benchmark
201+
additions. No TypeScript changes. 273 Go
202+
sources outside fixtures.
203+
204+
No blockers. No rule-to-rule imports added.
205+
No DIP violations. New files are under 800
206+
lines. Struct alignment and `map[string]struct{}`
207+
changes are mechanical rewrites with no
208+
layering impact.
209+
210+
### tax (2026-06-23)
211+
212+
- `internal/lint/inline_scan.go` — 13
213+
unexported helpers lack dedicated unit
214+
tests. Tests doc §"every function by
215+
name" — [plan/2606231013][2606231013].
216+
217+
- `internal/rules/samefileanchor/rule.go`
218+
— 12 unexported helpers lack dedicated
219+
unit tests — [plan/2606231014][2606231014].
220+
221+
[2606231013]: ../../plan/2606231013_arch-fix-inline-scan-helper-tests.md
222+
[2606231014]: ../../plan/2606231014_arch-fix-samefileanchor-helper-tests.md

internal/lint/inline_scan_test.go

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,3 +449,268 @@ func TestInlineBlocks_ScannerHandlesSimpleFile(t *testing.T) {
449449
assert.Nil(t, first.NextSibling(), "scanner run has a single paragraph")
450450
}
451451
}
452+
453+
// --- Dedicated helper tests (plan 2606231013) ---
454+
455+
func TestScanRunEligible(t *testing.T) {
456+
assert.False(t, scanRunEligible(nil), "nil is not eligible")
457+
assert.False(t, scanRunEligible([]byte("")), "empty is not eligible")
458+
assert.False(t, scanRunEligible([]byte("line one\nline two")), "multiline not eligible")
459+
assert.False(t, scanRunEligible([]byte("# heading")), "ATX heading not eligible")
460+
assert.False(t, scanRunEligible([]byte("- list")), "list item not eligible")
461+
assert.False(t, scanRunEligible([]byte("> quote")), "block quote not eligible")
462+
assert.False(t, scanRunEligible([]byte("*em*")), "emphasis asterisk not eligible")
463+
assert.False(t, scanRunEligible([]byte("_em_")), "emphasis underscore not eligible")
464+
assert.False(t, scanRunEligible([]byte(`a\b`)), "backslash not eligible")
465+
assert.False(t, scanRunEligible([]byte("a&amp;b")), "entity not eligible")
466+
assert.True(t, scanRunEligible([]byte("plain text")), "plain text eligible")
467+
assert.True(t, scanRunEligible([]byte("[link](url)")), "link chars eligible")
468+
assert.True(t, scanRunEligible([]byte("`code`")), "backtick eligible")
469+
assert.True(t, scanRunEligible([]byte("<autolink>")), "angle bracket eligible")
470+
}
471+
472+
func TestMergeAppendText(t *testing.T) {
473+
a := arena.New()
474+
run := []byte("hello world")
475+
para := a.Paragraph()
476+
477+
// end <= start: no child appended.
478+
mergeAppendText(run, 3, 3, para, a)
479+
assert.Nil(t, para.FirstChild(), "end==start emits nothing")
480+
481+
// Normal range: appends a Text node.
482+
mergeAppendText(run, 0, 5, para, a)
483+
child := para.FirstChild()
484+
require.NotNil(t, child, "Text node appended")
485+
txt, ok := child.(*ast.Text)
486+
require.True(t, ok, "child is *ast.Text")
487+
assert.Equal(t, "hello", string(txt.Segment.Value(run)))
488+
489+
// Adjacent range [5:11): merges into the existing Text node.
490+
mergeAppendText(run, 5, 11, para, a)
491+
assert.Nil(t, child.NextSibling(), "adjacent text merged, no new sibling")
492+
assert.Equal(t, "hello world", string(txt.Segment.Value(run)), "merged bounds")
493+
}
494+
495+
func TestFinalAppendText(t *testing.T) {
496+
a := arena.New()
497+
run := []byte("hello ")
498+
para := a.Paragraph()
499+
500+
// Trailing spaces trimmed: appends "hello".
501+
finalAppendText(run, 0, len(run), para, a)
502+
child := para.FirstChild()
503+
require.NotNil(t, child)
504+
txt, ok := child.(*ast.Text)
505+
require.True(t, ok)
506+
assert.Equal(t, "hello", string(txt.Segment.Value(run)))
507+
508+
// All whitespace: nothing appended.
509+
para2 := a.Paragraph()
510+
finalAppendText([]byte(" "), 0, 3, para2, a)
511+
assert.Nil(t, para2.FirstChild(), "all-space run appends nothing")
512+
513+
// end <= start: nothing appended.
514+
para3 := a.Paragraph()
515+
finalAppendText(run, 5, 5, para3, a)
516+
assert.Nil(t, para3.FirstChild(), "empty range appends nothing")
517+
}
518+
519+
func TestScanParagraphInlines(t *testing.T) {
520+
a := arena.New()
521+
522+
// Plain text: returns true, one Text child.
523+
para := a.Paragraph()
524+
ok := scanParagraphInlines([]byte("hello"), para, a)
525+
require.True(t, ok)
526+
require.NotNil(t, para.FirstChild(), "Text child added")
527+
528+
// Unclosed backtick: scanCodeSpan declines, returns false.
529+
para2 := a.Paragraph()
530+
ok2 := scanParagraphInlines([]byte("`unclosed"), para2, a)
531+
assert.False(t, ok2)
532+
}
533+
534+
func TestApplyCodeSpan(t *testing.T) {
535+
a := arena.New()
536+
// Run starts at the backtick so no prior text is flushed first.
537+
run := []byte("`code` after")
538+
para := a.Paragraph()
539+
540+
// i=0 points at the opening backtick; textStart=0 so nothing is flushed.
541+
ni, ns, ok := applyCodeSpan(run, 0, 0, para, a)
542+
require.True(t, ok)
543+
assert.Equal(t, 6, ni, "ni just past closing backtick")
544+
assert.Equal(t, 6, ns, "textStart reset to ni")
545+
child := para.FirstChild()
546+
require.NotNil(t, child)
547+
_, isCode := child.(*ast.CodeSpan)
548+
assert.True(t, isCode, "CodeSpan appended")
549+
550+
// Unclosed backtick: returns false.
551+
para2 := a.Paragraph()
552+
_, _, ok2 := applyCodeSpan([]byte("`unclosed"), 0, 0, para2, a)
553+
assert.False(t, ok2)
554+
}
555+
556+
func TestApplyAutolink(t *testing.T) {
557+
a := arena.New()
558+
run := []byte("<https://example.com>")
559+
para := a.Paragraph()
560+
561+
ni, ns, ok := applyAutolink(run, 0, 0, para, a)
562+
require.True(t, ok)
563+
assert.Equal(t, len(run), ni)
564+
assert.Equal(t, len(run), ns)
565+
child := para.FirstChild()
566+
require.NotNil(t, child)
567+
_, isAL := child.(*ast.AutoLink)
568+
assert.True(t, isAL, "AutoLink appended")
569+
570+
// Raw HTML angle: returns false.
571+
para2 := a.Paragraph()
572+
_, _, ok2 := applyAutolink([]byte("<div>"), 0, 0, para2, a)
573+
assert.False(t, ok2)
574+
}
575+
576+
func TestApplyBang(t *testing.T) {
577+
a := arena.New()
578+
579+
// `!` without `[`: flushes pending text, returns i+1 with textStart at i.
580+
run := []byte("a! text")
581+
para := a.Paragraph()
582+
ni, ns, ok := applyBang(run, 1, 0, para, a)
583+
require.True(t, ok)
584+
assert.Equal(t, 2, ni, "i advanced past `!`")
585+
assert.Equal(t, 1, ns, "textStart set to position of `!`")
586+
require.NotNil(t, para.FirstChild(), "pending text flushed as child")
587+
588+
// `![alt](url)`: appends Image node.
589+
run2 := []byte("![alt](url)")
590+
para2 := a.Paragraph()
591+
ni2, _, ok2 := applyBang(run2, 0, 0, para2, a)
592+
require.True(t, ok2)
593+
assert.Equal(t, len(run2), ni2)
594+
child := para2.FirstChild()
595+
require.NotNil(t, child)
596+
_, isImg := child.(*ast.Image)
597+
assert.True(t, isImg, "Image appended")
598+
599+
// `![` with unclosed label: returns false.
600+
para3 := a.Paragraph()
601+
_, _, ok3 := applyBang([]byte("![alt"), 0, 0, para3, a)
602+
assert.False(t, ok3)
603+
}
604+
605+
func TestApplyLink(t *testing.T) {
606+
a := arena.New()
607+
run := []byte("[text](url)")
608+
para := a.Paragraph()
609+
610+
ni, ns, ok := applyLink(run, 0, 0, para, a)
611+
require.True(t, ok)
612+
assert.Equal(t, len(run), ni)
613+
assert.Equal(t, len(run), ns)
614+
child := para.FirstChild()
615+
require.NotNil(t, child)
616+
_, isLink := child.(*ast.Link)
617+
assert.True(t, isLink, "Link appended")
618+
619+
// Reference link `[text][ref]`: returns false.
620+
para2 := a.Paragraph()
621+
_, _, ok2 := applyLink([]byte("[text][ref]"), 0, 0, para2, a)
622+
assert.False(t, ok2)
623+
}
624+
625+
func TestScanCodeSpan(t *testing.T) {
626+
a := arena.New()
627+
628+
// Single-backtick span.
629+
node, after, ok := scanCodeSpan([]byte("`code`"), 0, a)
630+
require.True(t, ok)
631+
assert.Equal(t, 6, after)
632+
require.NotNil(t, node)
633+
_, isCode := node.(*ast.CodeSpan)
634+
assert.True(t, isCode)
635+
636+
// Double-backtick span containing a single backtick.
637+
node2, after2, ok2 := scanCodeSpan([]byte("`` a`b ``"), 0, a)
638+
require.True(t, ok2)
639+
assert.Equal(t, 9, after2)
640+
require.NotNil(t, node2)
641+
_, isCode2 := node2.(*ast.CodeSpan)
642+
assert.True(t, isCode2, "double-backtick produces CodeSpan")
643+
644+
// No closing backtick: returns false.
645+
_, _, ok3 := scanCodeSpan([]byte("`unclosed"), 0, a)
646+
assert.False(t, ok3)
647+
}
648+
649+
func TestScanLinkOrImage(t *testing.T) {
650+
a := arena.New()
651+
652+
// Inline link.
653+
node, after, ok := scanLinkOrImage([]byte("[text](url)"), 0, false, a)
654+
require.True(t, ok)
655+
assert.Equal(t, 11, after)
656+
_, isLink := node.(*ast.Link)
657+
assert.True(t, isLink, "Link node")
658+
659+
// Inline image.
660+
node2, after2, ok2 := scanLinkOrImage([]byte("![alt](url)"), 0, true, a)
661+
require.True(t, ok2)
662+
assert.Equal(t, 11, after2, "image after matches run length")
663+
_, isImg := node2.(*ast.Image)
664+
assert.True(t, isImg, "Image node")
665+
666+
// Reference link `[text][ref]`: returns false.
667+
_, _, ok3 := scanLinkOrImage([]byte("[text][ref]"), 0, false, a)
668+
assert.False(t, ok3)
669+
670+
// Nested brackets in label: returns false.
671+
_, _, ok4 := scanLinkOrImage([]byte("[[a]](url)"), 0, false, a)
672+
assert.False(t, ok4)
673+
}
674+
675+
func TestScanLinkParens(t *testing.T) {
676+
// Empty parens.
677+
dest, title, after, ok := scanLinkParens([]byte("()"), 0)
678+
require.True(t, ok)
679+
assert.Nil(t, dest)
680+
assert.Nil(t, title)
681+
assert.Equal(t, 2, after)
682+
683+
// Destination only.
684+
dest2, title2, after2, ok2 := scanLinkParens([]byte("(url)"), 0)
685+
require.True(t, ok2)
686+
assert.Equal(t, "url", string(dest2))
687+
assert.Nil(t, title2)
688+
assert.Equal(t, 5, after2)
689+
690+
// Destination and title.
691+
dest3, title3, after3, ok3 := scanLinkParens([]byte(`(url "ttl")`), 0)
692+
require.True(t, ok3)
693+
assert.Equal(t, "url", string(dest3))
694+
assert.Equal(t, "ttl", string(title3))
695+
assert.Equal(t, 11, after3)
696+
697+
// Missing closing paren.
698+
_, _, _, ok4 := scanLinkParens([]byte("(url"), 0)
699+
assert.False(t, ok4)
700+
}
701+
702+
func TestSkipSpacesAt(t *testing.T) {
703+
run := []byte(" hello")
704+
assert.Equal(t, 3, skipSpacesAt(run, 0), "leading spaces skipped")
705+
assert.Equal(t, 3, skipSpacesAt(run, 3), "no spaces from non-space pos")
706+
assert.Equal(t, len(run), skipSpacesAt(run, len(run)), "past end stays at end")
707+
708+
allSpace := []byte(" ")
709+
assert.Equal(t, 3, skipSpacesAt(allSpace, 0), "all spaces → past end")
710+
}
711+
712+
func TestIsSpaceOrNewlineByte(t *testing.T) {
713+
assert.True(t, isSpaceOrNewlineByte(' '))
714+
assert.True(t, isSpaceOrNewlineByte('\n'))
715+
assert.False(t, isSpaceOrNewlineByte('\t'), "tab is not a space-or-newline byte")
716+
}

0 commit comments

Comments
 (0)