diff --git a/doccov/main.go b/doccov/main.go index 4673478..0416c4e 100644 --- a/doccov/main.go +++ b/doccov/main.go @@ -255,19 +255,30 @@ func shortName(name string) string { } var ( + fencedBlock = regexp.MustCompile("(?s)```.*?```") backtickSpan = regexp.MustCompile("`[^`]+`") wordToken = regexp.MustCompile(`[A-Za-z_][A-Za-z0-9_]*`) ) -// backtickWords collects every identifier word appearing inside a backtick span -// of the document. +// backtickWords collects every identifier word that appears inside code in the +// document — both ```fenced``` blocks and `inline` spans. Fenced blocks are +// processed and removed first so their backticks don't throw off the +// single-backtick span matcher (which otherwise mis-pairs across a fence, so a +// `code` reference in a table *after* a fenced example could be missed). func backtickWords(doc string) map[string]bool { out := map[string]bool{} - for _, span := range backtickSpan.FindAllString(doc, -1) { - for _, w := range wordToken.FindAllString(span, -1) { + add := func(s string) { + for _, w := range wordToken.FindAllString(s, -1) { out[w] = true } } + rest := fencedBlock.ReplaceAllStringFunc(doc, func(block string) string { + add(block) + return "\n" + }) + for _, span := range backtickSpan.FindAllString(rest, -1) { + add(span) + } return out } diff --git a/doccov/main_test.go b/doccov/main_test.go index d6cfe29..cee628e 100644 --- a/doccov/main_test.go +++ b/doccov/main_test.go @@ -64,6 +64,18 @@ func TestBacktickWords(t *testing.T) { } } +func TestBacktickWordsAcrossFences(t *testing.T) { + // Regression: a `code` reference in a table AFTER a ```fenced``` block must + // still be detected (the fenced block must not corrupt inline-span pairing). + doc := "Intro `inline_one`.\n\n```go\ncode := example()\n```\n\n| opt | accessors |\n|---|---|\n| width | `get_width` / `set_width` |\n" + words := backtickWords(doc) + for _, w := range []string{"inline_one", "example", "get_width", "set_width"} { + if !words[w] { + t.Errorf("expected %q documented across the fence, got %v", w, words) + } + } +} + func TestRunGood(t *testing.T) { if err := run("testdata/good", "README.md", nil, false); err != nil { t.Fatalf("good fixture should pass, got: %v", err)