diff --git a/internal/docsite/checks_test.go b/internal/docsite/checks_test.go index 221173a..70efd4b 100644 --- a/internal/docsite/checks_test.go +++ b/internal/docsite/checks_test.go @@ -469,6 +469,140 @@ func TestCommandTokenForms(t *testing.T) { }) } +func TestNestedFenceChecks(t *testing.T) { + body := strings.Join([]string{ + "> [!NOTE]", + "> ```go", + "> x := 1", + "> ```", + "", + "- item", + "", + " ```go", + " x := 1", + " ```", + "", + "```GO", + "x := 1", + "```", + "", + "```main.go", + "x := 1", + "```", + "", + " ```go", + " x := 1", + " ```", + "", + "> [!NOTE]", + "> ```sh", + "> summer bogus:nested", + "> ```", + "", + "> ```yaml src=config/app.yaml", + "> app:", + "> ```", + "", + }, "\n") + readme := strings.Join([]string{ + "# fixture", + "", + "Fixture does one thing.", + "", + "## Usage", + "", + "```go", + "x := 1", + "```", + "", + "```go src=modules/fixture/fixture.go", + "package fixture", + "```", + "", + }, "\n") + assertProblems(t, checkFixture(t, fixtureCommands, map[string]string{ + "docs/index.md": page("Acme docs", "index", 0, "Text.\n"), + "docs/setup/start.md": page("Start", "setup", 10, body), + "modules/fixture/README.md": readme, + }), []string{ + "docs/setup/start.md:10: snippet: go code block has no src= reference", + "docs/setup/start.md:16: snippet: go code block has no src= reference", + "docs/setup/start.md:20: snippet: GO code block has no src= reference", + "docs/setup/start.md:24: snippet: main.go code block has no src= reference", + `docs/setup/start.md:34: command: "bogus:nested" is not a summer or application command`, + "docs/setup/start.md:37: snippet: config/app.yaml: " + nestedSrcMessage, + "modules/fixture/README.md:11: snippet: modules/fixture/fixture.go: " + readmeSrcMessage, + }) +} + +func TestIdentifierGoDocCaseSensitive(t *testing.T) { + root := writeTree(t, map[string]string{ + "go.mod": "module example.com/forms\n\ngo 1.27\n", + "modules/forms/forms.go": "package forms\n\n" + + "// OpenFromApp is a fixture.\nfunc OpenFromApp() {}\n\n" + + "// Outer holds a method.\ntype Outer struct{}\n\n" + + "// HelloWorld is a method.\nfunc (Outer) HelloWorld() {}\n", + }) + idx, problems, err := buildIdentIndex(root) + if err != nil || len(problems) > 0 { + t.Fatalf("buildIdentIndex: %v %q", err, problemLines(problems)) + } + for _, q := range []string{"OpenFromApp", "Outer.HelloWorld"} { + if !idx.goDoc("modules/forms", q) { + t.Errorf("goDoc(%q) = false, want true", q) + } + } + for _, q := range []string{"Openfromapp", "Outer.Helloworld"} { + if idx.goDoc("modules/forms", q) { + t.Errorf("goDoc(%q) = true, want false", q) + } + } + if got := idx.checkSpan("forms.Openfromapp"); got != "forms.Openfromapp does not exist in modules/forms" { + t.Fatalf("checkSpan = %q", got) + } +} + +func TestCommandWord(t *testing.T) { + tool, app := true, false + cases := []struct { + in string + name string + tool, ok bool + }{ + {"summer docs:build", "docs:build", tool, true}, + {"$ summer docs:build --out site", "docs:build", tool, true}, + {"FOO=1 summer no:such", "no:such", tool, true}, + {"FOO=1 BAR=x ./bin/acme serve", "serve", app, true}, + {"summer --root . no:such", "no:such", tool, true}, + {"summer --root=. docs:build", "docs:build", tool, true}, + {"summer -r . docs:build", "docs:build", tool, true}, + {"summer --help", "", tool, false}, + {"summer --help docs:build", "docs:build", tool, true}, + {"summer -h docs:build", "docs:build", tool, true}, + {"summer --root", "", tool, false}, + {"summer --root .", "", tool, false}, + {"summer -- docs:build", "", tool, false}, + {"go run ./cmd/summer no:such", "no:such", tool, true}, + {"go run ./cmd/summer --root . docs:build", "docs:build", tool, true}, + {"bin/acme migrate", "migrate", app, true}, + {"./bin/acme migrate", "migrate", app, true}, + {"echo summer x", "", false, false}, + {"# summer x", "", false, false}, + {"go test ./modules/demo", "", false, false}, + {"go install ./cmd/summer", "", false, false}, + {"summer", "", tool, false}, + {"FOO=1", "", false, false}, + {"summer --version", "", tool, false}, + } + for _, tc := range cases { + name, isTool, ok := commandWord(tc.in) + if name != tc.name || isTool != tc.tool || ok != tc.ok { + t.Errorf("commandWord(%q) = %q, %v, %v; want %q, %v, %v", + tc.in, name, isTool, ok, tc.name, tc.tool, tc.ok) + } + } +} + func TestExampleCommandNames(t *testing.T) { dir := writeTree(t, map[string]string{ "a/main.go": "package main\n\nimport \"x/bonfire\"\n\nvar a = bonfire.Command{Name: \"a:one\", Description: \"d\"}\n" + diff --git a/internal/docsite/fences_test.go b/internal/docsite/fences_test.go new file mode 100644 index 0000000..6f9ebf5 --- /dev/null +++ b/internal/docsite/fences_test.go @@ -0,0 +1,84 @@ +package docsite + +import ( + "strings" + "testing" +) + +func TestCollectFences(t *testing.T) { + body := strings.Join([]string{ + "```go", + "top", + "```", + "", + "> [!TIP]", + "> ```go", + "> callout line", + "> ```", + "", + "> ```yaml", + "> quote line", + "> ```", + "", + "- item", + "", + " ```go", + " list line", + " ```", + "", + "```", + "noinfo", + "```", + "", + " ```", + " four spaces", + " ```", + "", + "```sh", + "unterminated", + }, "\n") + doc := (&site{}).parseRaw(newMarkdown(), []byte(body)) + fences, err := collectFences(doc, []byte(body)) + if err != nil { + t.Fatal(err) + } + if len(fences) != 6 { + t.Fatalf("fences = %d, want 6 (four-space backticks are not a fence)", len(fences)) + } + want := []struct { + info, code string + line int + top bool + close int // -2 means top was not filled + }{ + {"go", "top", 0, true, 2}, + {"go", "callout line", 5, false, -2}, + {"yaml", "quote line", 9, false, -2}, + {"go", "list line", 15, false, -2}, + {"", "noinfo", 19, true, -2}, + {"sh", "unterminated", 27, true, -1}, + } + for i, w := range want { + f := fences[i] + if f.info != w.info || f.line != w.line || f.topLevel != w.top { + t.Errorf("fence %d = info %q line %d top %v, want info %q line %d top %v", + i, f.info, f.line, f.topLevel, w.info, w.line, w.top) + } + if len(f.code) != 1 || f.code[0].text != w.code { + got := "" + if len(f.code) > 0 { + got = f.code[0].text + } + t.Errorf("fence %d code = %q, want %q", i, got, w.code) + } + if w.close != -2 && f.top.close != w.close { + t.Errorf("fence %d top.close = %d, want %d", i, f.top.close, w.close) + } + } + if _, ok := openFence(" ```go"); !ok { + t.Error("openFence rejected three leading spaces") + } + if _, ok := openFence(" ```go"); ok { + t.Error("openFence accepted four leading spaces") + } +} diff --git a/internal/docsite/highlight_test.go b/internal/docsite/highlight_test.go index 65cc6b4..bf0873d 100644 --- a/internal/docsite/highlight_test.go +++ b/internal/docsite/highlight_test.go @@ -150,3 +150,16 @@ func TestHighlightFenceMarkup(t *testing.T) { t.Error("a fence without src= has a caption") } } + +func TestGoLang(t *testing.T) { + for _, lang := range []string{"go", "Go", "GO", "golang", "Golang", "main.go"} { + if !goLang(lang) { + t.Errorf("goLang(%q) = false, want true", lang) + } + } + for _, lang := range []string{"go-html-template", "go-text-template", "text", "yaml", "sh", ""} { + if goLang(lang) { + t.Errorf("goLang(%q) = true, want false", lang) + } + } +} diff --git a/internal/docsite/render_test.go b/internal/docsite/render_test.go index 15a8082..10aa648 100644 --- a/internal/docsite/render_test.go +++ b/internal/docsite/render_test.go @@ -71,6 +71,36 @@ func renderHTML(t *testing.T, s *site, p *Page) renderedPage { return r } +func TestFenceCaptionsOnlyVerified(t *testing.T) { + guide := page("Start", "setup", 10, strings.Join([]string{ + "```go src=pkg/lib.go#Greeting", + "func Greeting() {}", + "```", + "", + "> [!TIP]", + "> ```go src=pkg/lib.go#Greeting", + "> func stale() {}", + "> ```", + "", + }, "\n")) + readme := "# alpha\n\nAlpha does one thing.\n\n```go src=modules/alpha/alpha.go\npackage alpha\n```\n" + s, pages := renderFixture(t, map[string]string{ + "docs/setup/start.md": guide, + "modules/alpha/README.md": readme, + }) + guideHTML := string(renderHTML(t, s, pages["setup/start"]).html) + if got := strings.Count(guideHTML, "
"); got != 1 { + t.Fatalf("guide figcaptions = %d, want 1\n%s", got, guideHTML) + } + if !strings.Contains(guideHTML, "
pkg/lib.go#Greeting
") { + t.Fatalf("guide caption missing the top-level reference\n%s", guideHTML) + } + readmeHTML := string(renderHTML(t, s, pages["api/alpha"]).html) + if strings.Contains(readmeHTML, "
") { + t.Fatalf("module README has a caption\n%s", readmeHTML) + } +} + func TestLinkRewriting(t *testing.T) { body := strings.Join([]string{ "- [start](setup/start.md)", diff --git a/internal/docsite/snippet_test.go b/internal/docsite/snippet_test.go index b3a6fc0..471ef14 100644 --- a/internal/docsite/snippet_test.go +++ b/internal/docsite/snippet_test.go @@ -3,6 +3,7 @@ package docsite import ( "os" "path/filepath" + "runtime" "slices" "strings" "testing" @@ -320,3 +321,160 @@ func TestSnippetTestGraph(t *testing.T) { t.Errorf("Extract with an unparsable sibling test = %v", err) } } + +func TestSnippetRootsAndBuild(t *testing.T) { + other := "windows" + if runtime.GOOS == "windows" { + other = "linux" + } + root := writeTree(t, map[string]string{ + "p/p.go": "package p\n", + "p/p_test.go": `package p + +import "testing" + +func Test(t *testing.T) { fromTest() } +func Test_x(t *testing.T) { fromTestX() } +func TestMain(m *testing.M) { fromMain() } +func Testable() { fromTestable() } + +func fromTest() {} +func fromTestX() {} +func fromMain() {} +func fromTestable() {} +func onlyFromIgnored() {} + +func ExampleOut() { + fromOut() + // Output: out +} + +func ExampleEmpty() { + fromEmpty() + // Output: +} + +func ExampleNone() { + fromNone() +} + +func fromOut() {} +func fromEmpty() {} +func fromNone() {} +`, + "p/ignored_test.go": "//go:build ignore\n\npackage p\n\nimport \"testing\"\n\nfunc TestIgnored(t *testing.T) { onlyFromIgnored() }\n", + "p/broken.go": "//go:build ignore\n\npackage p\n\nfunc Broken() {}\n", + "p/broken_test.go": "//go:build ignore\n\npackage p\n\nfunc IgnoredTestTarget() {}\n", + "p/only_" + other + ".go": "package p\n\nfunc OnlyOS() {}\n", + "p/testdata/skip.go": "package p\n\nfunc Skip() {}\n", + "p/_old/old.go": "package p\n\nfunc Old() {}\n", + "p/testdata/app.yaml": "# docs:start db\nhost: localhost\n# docs:end db\n", + }) + g, err := loadTestGraph(filepath.Join(root, "p")) + if err != nil { + t.Fatal(err) + } + for _, name := range []string{"Test", "Test_x", "TestMain", "ExampleOut", "ExampleEmpty", "fromTest", "fromTestX", "fromMain", "fromOut", "fromEmpty"} { + if !g.reachable[name] { + t.Errorf("%s is not a root or reachable from one", name) + } + } + for _, name := range []string{"Testable", "ExampleNone", "fromTestable", "fromNone", "onlyFromIgnored", "TestIgnored"} { + if g.reachable[name] { + t.Errorf("%s is reachable, want it not run", name) + } + } + mustExtract := func(ref Ref, wantErr string) { + t.Helper() + _, err := Extract(root, ref) + if wantErr == "" { + if err != nil { + t.Errorf("Extract(%s) = %v, want nil", ref, err) + } + return + } + if err == nil || !strings.Contains(err.Error(), wantErr) { + t.Errorf("Extract(%s) = %v, want %q", ref, err, wantErr) + } + } + skipped := "file is in a directory go test ./... skips" + excluded := "file is excluded from the default build" + mustExtract(Ref{"p/p_test.go", "fromOut"}, "") + mustExtract(Ref{"p/p_test.go", "fromEmpty"}, "") + mustExtract(Ref{"p/p_test.go", "fromNone"}, notRunMessage) + mustExtract(Ref{"p/p_test.go", "fromTestable"}, notRunMessage) + mustExtract(Ref{"p/p_test.go", "onlyFromIgnored"}, notRunMessage) + mustExtract(Ref{"p/broken.go", "Broken"}, excluded) + mustExtract(Ref{"p/broken_test.go", "IgnoredTestTarget"}, excluded) + mustExtract(Ref{"p/only_" + other + ".go", "OnlyOS"}, excluded) + mustExtract(Ref{"p/testdata/skip.go", "Skip"}, skipped) + mustExtract(Ref{"p/_old/old.go", "Old"}, skipped) + got, err := Extract(root, Ref{"p/testdata/app.yaml", "db"}) + if err != nil || got != "host: localhost" { + t.Fatalf("Extract yaml = %q, %v", got, err) + } +} + +func TestSyncParsedFences(t *testing.T) { + callout := "> [!NOTE]\n> Keep this callout text.\n" + doc := page("Start", "setup", 10, "Intro.\n\n```go src=pkg/lib.go#Greeting\nfunc stale() {}\n```\n\n"+callout+"\nOutro.\n") + root := snippetTree(t, map[string]string{"docs/setup/start.md": doc}) + path := filepath.Join(root, "docs/setup/start.md") + res, problems, err := Sync(Options{Root: root}) + if err != nil || len(problems) > 0 { + t.Fatalf("Sync: %v %v", err, problems) + } + if res.Files != 1 || res.Snippets != 1 { + t.Fatalf("Sync = %+v, want 1 snippet in 1 file", res) + } + got, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + wantDoc := strings.Replace(doc, "func stale() {}\n", + "// Greeting returns a greeting.\nfunc Greeting(name string) string {\n\treturn \"Hello, \" + name\n}\n", 1) + if string(got) != wantDoc { + t.Fatalf("synced file =\n%s\nwant\n%s", got, wantDoc) + } + if !strings.HasPrefix(string(got), "---\ntitle: Start\n") || !strings.Contains(string(got), callout) { + t.Fatal("frontmatter or callout text changed") + } + + nested := page("Start", "setup", 10, "Intro.\n\n> [!NOTE]\n> ```go src=pkg/lib.go#Greeting\n> func stale() {}\n> ```\n\nOutro.\n") + root = snippetTree(t, map[string]string{"docs/setup/start.md": nested}) + before := map[string]string{} + err = filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() { + return err + } + b, err := os.ReadFile(p) + if err != nil { + return err + } + before[p] = string(b) + return nil + }) + if err != nil { + t.Fatal(err) + } + res, problems, err = Sync(Options{Root: root}) + if err != nil || res != (SyncResult{}) || len(problems) != 1 || !strings.Contains(problems[0].Message, nestedSrcMessage) { + t.Fatalf("nested Sync = %+v %q %v", res, problemLines(problems), err) + } + err = filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() { + return err + } + b, err := os.ReadFile(p) + if err != nil { + return err + } + if string(b) != before[p] { + t.Errorf("Sync changed %s", p) + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} diff --git a/internal/docsite/testdata/violations/command-bin-no-dot/docs/extras/faq.md b/internal/docsite/testdata/violations/command-bin-no-dot/docs/extras/faq.md new file mode 100644 index 0000000..270b867 --- /dev/null +++ b/internal/docsite/testdata/violations/command-bin-no-dot/docs/extras/faq.md @@ -0,0 +1,19 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +```sh +bin/demo no:such +``` diff --git a/internal/docsite/testdata/violations/command-bin-no-dot/want.txt b/internal/docsite/testdata/violations/command-bin-no-dot/want.txt new file mode 100644 index 0000000..d5f59c5 --- /dev/null +++ b/internal/docsite/testdata/violations/command-bin-no-dot/want.txt @@ -0,0 +1,3 @@ +rule: command +file: docs/extras/faq.md +message: "no:such" is not a summer or application command diff --git a/internal/docsite/testdata/violations/command-env-prefix/docs/extras/faq.md b/internal/docsite/testdata/violations/command-env-prefix/docs/extras/faq.md new file mode 100644 index 0000000..539fb50 --- /dev/null +++ b/internal/docsite/testdata/violations/command-env-prefix/docs/extras/faq.md @@ -0,0 +1,19 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +```sh +FOO=1 summer no:such +``` diff --git a/internal/docsite/testdata/violations/command-env-prefix/want.txt b/internal/docsite/testdata/violations/command-env-prefix/want.txt new file mode 100644 index 0000000..d5f59c5 --- /dev/null +++ b/internal/docsite/testdata/violations/command-env-prefix/want.txt @@ -0,0 +1,3 @@ +rule: command +file: docs/extras/faq.md +message: "no:such" is not a summer or application command diff --git a/internal/docsite/testdata/violations/command-flag-first/docs/extras/faq.md b/internal/docsite/testdata/violations/command-flag-first/docs/extras/faq.md new file mode 100644 index 0000000..629ae47 --- /dev/null +++ b/internal/docsite/testdata/violations/command-flag-first/docs/extras/faq.md @@ -0,0 +1,19 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +```sh +summer --root . no:such +``` diff --git a/internal/docsite/testdata/violations/command-flag-first/want.txt b/internal/docsite/testdata/violations/command-flag-first/want.txt new file mode 100644 index 0000000..d5f59c5 --- /dev/null +++ b/internal/docsite/testdata/violations/command-flag-first/want.txt @@ -0,0 +1,3 @@ +rule: command +file: docs/extras/faq.md +message: "no:such" is not a summer or application command diff --git a/internal/docsite/testdata/violations/command-go-run/docs/extras/faq.md b/internal/docsite/testdata/violations/command-go-run/docs/extras/faq.md new file mode 100644 index 0000000..c2d6cab --- /dev/null +++ b/internal/docsite/testdata/violations/command-go-run/docs/extras/faq.md @@ -0,0 +1,19 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +```sh +go run ./cmd/summer no:such +``` diff --git a/internal/docsite/testdata/violations/command-go-run/want.txt b/internal/docsite/testdata/violations/command-go-run/want.txt new file mode 100644 index 0000000..d5f59c5 --- /dev/null +++ b/internal/docsite/testdata/violations/command-go-run/want.txt @@ -0,0 +1,3 @@ +rule: command +file: docs/extras/faq.md +message: "no:such" is not a summer or application command diff --git a/internal/docsite/testdata/violations/command-in-callout/docs/extras/faq.md b/internal/docsite/testdata/violations/command-in-callout/docs/extras/faq.md new file mode 100644 index 0000000..17141ed --- /dev/null +++ b/internal/docsite/testdata/violations/command-in-callout/docs/extras/faq.md @@ -0,0 +1,20 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +> [!NOTE] +> ```sh +> summer no:such +> ``` diff --git a/internal/docsite/testdata/violations/command-in-callout/want.txt b/internal/docsite/testdata/violations/command-in-callout/want.txt new file mode 100644 index 0000000..d5f59c5 --- /dev/null +++ b/internal/docsite/testdata/violations/command-in-callout/want.txt @@ -0,0 +1,3 @@ +rule: command +file: docs/extras/faq.md +message: "no:such" is not a summer or application command diff --git a/internal/docsite/testdata/violations/go-fence-callout-no-src/docs/extras/faq.md b/internal/docsite/testdata/violations/go-fence-callout-no-src/docs/extras/faq.md new file mode 100644 index 0000000..e03f128 --- /dev/null +++ b/internal/docsite/testdata/violations/go-fence-callout-no-src/docs/extras/faq.md @@ -0,0 +1,20 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +> [!NOTE] +> ```go +> x := 1 +> ``` diff --git a/internal/docsite/testdata/violations/go-fence-callout-no-src/want.txt b/internal/docsite/testdata/violations/go-fence-callout-no-src/want.txt new file mode 100644 index 0000000..83d9670 --- /dev/null +++ b/internal/docsite/testdata/violations/go-fence-callout-no-src/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: docs/extras/faq.md +message: go code block has no src= reference diff --git a/internal/docsite/testdata/violations/go-fence-golang/docs/extras/faq.md b/internal/docsite/testdata/violations/go-fence-golang/docs/extras/faq.md new file mode 100644 index 0000000..2839343 --- /dev/null +++ b/internal/docsite/testdata/violations/go-fence-golang/docs/extras/faq.md @@ -0,0 +1,19 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +```golang +x := 1 +``` diff --git a/internal/docsite/testdata/violations/go-fence-golang/want.txt b/internal/docsite/testdata/violations/go-fence-golang/want.txt new file mode 100644 index 0000000..758b11b --- /dev/null +++ b/internal/docsite/testdata/violations/go-fence-golang/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: docs/extras/faq.md +message: golang code block has no src= reference diff --git a/internal/docsite/testdata/violations/identifier-wrong-case/docs/extras/faq.md b/internal/docsite/testdata/violations/identifier-wrong-case/docs/extras/faq.md new file mode 100644 index 0000000..797f427 --- /dev/null +++ b/internal/docsite/testdata/violations/identifier-wrong-case/docs/extras/faq.md @@ -0,0 +1,17 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +Call `demo.Openfromapp`. diff --git a/internal/docsite/testdata/violations/identifier-wrong-case/modules/demo/open.go b/internal/docsite/testdata/violations/identifier-wrong-case/modules/demo/open.go new file mode 100644 index 0000000..d56021d --- /dev/null +++ b/internal/docsite/testdata/violations/identifier-wrong-case/modules/demo/open.go @@ -0,0 +1,4 @@ +package demo + +// OpenFromApp is a fixture. +func OpenFromApp() {} diff --git a/internal/docsite/testdata/violations/identifier-wrong-case/want.txt b/internal/docsite/testdata/violations/identifier-wrong-case/want.txt new file mode 100644 index 0000000..82db385 --- /dev/null +++ b/internal/docsite/testdata/violations/identifier-wrong-case/want.txt @@ -0,0 +1,3 @@ +rule: identifier +file: docs/extras/faq.md +message: demo.Openfromapp does not exist in modules/demo diff --git a/internal/docsite/testdata/violations/snippet-blockquote/docs/extras/faq.md b/internal/docsite/testdata/violations/snippet-blockquote/docs/extras/faq.md new file mode 100644 index 0000000..10a3a1b --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-blockquote/docs/extras/faq.md @@ -0,0 +1,24 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +> ```yaml src=config/app.yaml +> app: +> # docs:start db +> db: +> host: localhost +> port: 5432 +> # docs:end db +> ``` diff --git a/internal/docsite/testdata/violations/snippet-blockquote/want.txt b/internal/docsite/testdata/violations/snippet-blockquote/want.txt new file mode 100644 index 0000000..8e591e0 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-blockquote/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: docs/extras/faq.md +message: config/app.yaml: src= code block must be a top-level block diff --git a/internal/docsite/testdata/violations/snippet-build-ignored-test-root/docs/extras/faq.md b/internal/docsite/testdata/violations/snippet-build-ignored-test-root/docs/extras/faq.md new file mode 100644 index 0000000..c7e2a0c --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-build-ignored-test-root/docs/extras/faq.md @@ -0,0 +1,19 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +```go src=modules/demo/demo_test.go#ignoredOnly +func ignoredOnly() string { return "x" } +``` diff --git a/internal/docsite/testdata/violations/snippet-build-ignored-test-root/modules/demo/demo_test.go b/internal/docsite/testdata/violations/snippet-build-ignored-test-root/modules/demo/demo_test.go new file mode 100644 index 0000000..89d405d --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-build-ignored-test-root/modules/demo/demo_test.go @@ -0,0 +1,15 @@ +package demo + +import "testing" + +func TestGreet(t *testing.T) { + // docs:start greet + g := Greeter{Name: "blog"} + got := g.Greet() + // docs:end greet + if got != "Hello, blog" { + t.Fatalf("Greet() = %q", got) + } +} + +func ignoredOnly() string { return "x" } diff --git a/internal/docsite/testdata/violations/snippet-build-ignored-test-root/modules/demo/ignored_test.go b/internal/docsite/testdata/violations/snippet-build-ignored-test-root/modules/demo/ignored_test.go new file mode 100644 index 0000000..b5492ef --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-build-ignored-test-root/modules/demo/ignored_test.go @@ -0,0 +1,7 @@ +//go:build ignore + +package demo + +import "testing" + +func TestIgnored(t *testing.T) { _ = ignoredOnly() } diff --git a/internal/docsite/testdata/violations/snippet-build-ignored-test-root/want.txt b/internal/docsite/testdata/violations/snippet-build-ignored-test-root/want.txt new file mode 100644 index 0000000..d8f2eaa --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-build-ignored-test-root/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: docs/extras/faq.md +message: fragment is not inside a Test or Example function diff --git a/internal/docsite/testdata/violations/snippet-build-ignored/docs/extras/faq.md b/internal/docsite/testdata/violations/snippet-build-ignored/docs/extras/faq.md new file mode 100644 index 0000000..2f5a93c --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-build-ignored/docs/extras/faq.md @@ -0,0 +1,20 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +```go src=modules/demo/broken.go#Broken +// Broken is never built. +func Broken() { undefinedCall() } +``` diff --git a/internal/docsite/testdata/violations/snippet-build-ignored/modules/demo/broken.go b/internal/docsite/testdata/violations/snippet-build-ignored/modules/demo/broken.go new file mode 100644 index 0000000..d025da9 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-build-ignored/modules/demo/broken.go @@ -0,0 +1,6 @@ +//go:build ignore + +package demo + +// Broken is never built. +func Broken() { undefinedCall() } diff --git a/internal/docsite/testdata/violations/snippet-build-ignored/want.txt b/internal/docsite/testdata/violations/snippet-build-ignored/want.txt new file mode 100644 index 0000000..a025457 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-build-ignored/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: docs/extras/faq.md +message: modules/demo/broken.go#Broken: file is excluded from the default build diff --git a/internal/docsite/testdata/violations/snippet-callout-drift/docs/extras/faq.md b/internal/docsite/testdata/violations/snippet-callout-drift/docs/extras/faq.md new file mode 100644 index 0000000..cbafc2b --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-callout-drift/docs/extras/faq.md @@ -0,0 +1,20 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +> [!TIP] +> ```go src=modules/demo/example_test.go#ExampleHello +> BOGUS +> ``` diff --git a/internal/docsite/testdata/violations/snippet-callout-drift/want.txt b/internal/docsite/testdata/violations/snippet-callout-drift/want.txt new file mode 100644 index 0000000..c633912 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-callout-drift/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: docs/extras/faq.md +message: modules/demo/example_test.go#ExampleHello: src= code block must be a top-level block diff --git a/internal/docsite/testdata/violations/snippet-callout-missing/docs/extras/faq.md b/internal/docsite/testdata/violations/snippet-callout-missing/docs/extras/faq.md new file mode 100644 index 0000000..7ce9876 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-callout-missing/docs/extras/faq.md @@ -0,0 +1,20 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +> [!NOTE] +> ```go src=modules/demo/missing_test.go#ExampleNope +> fmt.Println() +> ``` diff --git a/internal/docsite/testdata/violations/snippet-callout-missing/want.txt b/internal/docsite/testdata/violations/snippet-callout-missing/want.txt new file mode 100644 index 0000000..c1fbd7a --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-callout-missing/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: docs/extras/faq.md +message: modules/demo/missing_test.go#ExampleNope: src= code block must be a top-level block diff --git a/internal/docsite/testdata/violations/snippet-example-no-output-helper/docs/extras/faq.md b/internal/docsite/testdata/violations/snippet-example-no-output-helper/docs/extras/faq.md new file mode 100644 index 0000000..e51508e --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-example-no-output-helper/docs/extras/faq.md @@ -0,0 +1,19 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +```go src=modules/demo/never_test.go#neverRunHelper +func neverRunHelper() {} +``` diff --git a/internal/docsite/testdata/violations/snippet-example-no-output-helper/modules/demo/never_test.go b/internal/docsite/testdata/violations/snippet-example-no-output-helper/modules/demo/never_test.go new file mode 100644 index 0000000..bc70206 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-example-no-output-helper/modules/demo/never_test.go @@ -0,0 +1,7 @@ +package demo_test + +func Example_neverRun() { + neverRunHelper() +} + +func neverRunHelper() {} diff --git a/internal/docsite/testdata/violations/snippet-example-no-output-helper/want.txt b/internal/docsite/testdata/violations/snippet-example-no-output-helper/want.txt new file mode 100644 index 0000000..d8f2eaa --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-example-no-output-helper/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: docs/extras/faq.md +message: fragment is not inside a Test or Example function diff --git a/internal/docsite/testdata/violations/snippet-example-no-output-region/docs/extras/faq.md b/internal/docsite/testdata/violations/snippet-example-no-output-region/docs/extras/faq.md new file mode 100644 index 0000000..1312cd5 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-example-no-output-region/docs/extras/faq.md @@ -0,0 +1,19 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +```go src=modules/demo/never_test.go#never +_ = 1 +``` diff --git a/internal/docsite/testdata/violations/snippet-example-no-output-region/modules/demo/never_test.go b/internal/docsite/testdata/violations/snippet-example-no-output-region/modules/demo/never_test.go new file mode 100644 index 0000000..8aed2d2 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-example-no-output-region/modules/demo/never_test.go @@ -0,0 +1,11 @@ +package demo_test + +func Example_neverRun() { + neverRunHelper() +} + +func neverRunHelper() { + // docs:start never + _ = 1 + // docs:end never +} diff --git a/internal/docsite/testdata/violations/snippet-example-no-output-region/want.txt b/internal/docsite/testdata/violations/snippet-example-no-output-region/want.txt new file mode 100644 index 0000000..d8f2eaa --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-example-no-output-region/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: docs/extras/faq.md +message: fragment is not inside a Test or Example function diff --git a/internal/docsite/testdata/violations/snippet-list-item/docs/extras/faq.md b/internal/docsite/testdata/violations/snippet-list-item/docs/extras/faq.md new file mode 100644 index 0000000..0f440e1 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-list-item/docs/extras/faq.md @@ -0,0 +1,22 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +- Greet: + + ```go src=modules/demo/example_test.go#ExampleHello + fmt.Println(demo.Hello("blog")) + // Output: Hello, blog + ``` diff --git a/internal/docsite/testdata/violations/snippet-list-item/want.txt b/internal/docsite/testdata/violations/snippet-list-item/want.txt new file mode 100644 index 0000000..c633912 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-list-item/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: docs/extras/faq.md +message: modules/demo/example_test.go#ExampleHello: src= code block must be a top-level block diff --git a/internal/docsite/testdata/violations/snippet-readme-src/modules/demo/README.md b/internal/docsite/testdata/violations/snippet-readme-src/modules/demo/README.md new file mode 100644 index 0000000..9f374b4 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-readme-src/modules/demo/README.md @@ -0,0 +1,24 @@ +# demo + +Demo greets people by name. + +```go +import "example.com/docfixture/modules/demo" +``` + +## Usage + +Call `demo.Hello` or build a `demo.Greeter` and call `demo.Greeter.Greet`. + +See the [start guide](../../docs/guide/start.md#install). + +## Testing + +```sh +go test ./modules/demo +``` + +```go src=modules/demo/example_test.go#ExampleHello +fmt.Println(demo.Hello("blog")) +// Output: Hello, blog +``` diff --git a/internal/docsite/testdata/violations/snippet-readme-src/want.txt b/internal/docsite/testdata/violations/snippet-readme-src/want.txt new file mode 100644 index 0000000..715f409 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-readme-src/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: modules/demo/README.md +message: modules/demo/example_test.go#ExampleHello: module README code blocks are rendered as written diff --git a/internal/docsite/testdata/violations/snippet-testdata-go/docs/extras/faq.md b/internal/docsite/testdata/violations/snippet-testdata-go/docs/extras/faq.md new file mode 100644 index 0000000..c218872 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-testdata-go/docs/extras/faq.md @@ -0,0 +1,19 @@ +--- +title: FAQ +description: "Questions about the demo." +section: extras +order: 20 +--- +# FAQ + +## Does it migrate + +Yes, run `./bin/demo migrate`, then `summer docs:sync` after a source change. + +```text +summer not:checked in a text fence +``` + +```go src=modules/demo/testdata/fixture_test.go#td +_ = 1 +``` diff --git a/internal/docsite/testdata/violations/snippet-testdata-go/modules/demo/testdata/fixture_test.go b/internal/docsite/testdata/violations/snippet-testdata-go/modules/demo/testdata/fixture_test.go new file mode 100644 index 0000000..6e811d1 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-testdata-go/modules/demo/testdata/fixture_test.go @@ -0,0 +1,9 @@ +package demo + +import "testing" + +func TestFixture(t *testing.T) { + // docs:start td + _ = 1 + // docs:end td +} diff --git a/internal/docsite/testdata/violations/snippet-testdata-go/want.txt b/internal/docsite/testdata/violations/snippet-testdata-go/want.txt new file mode 100644 index 0000000..55d84d3 --- /dev/null +++ b/internal/docsite/testdata/violations/snippet-testdata-go/want.txt @@ -0,0 +1,3 @@ +rule: snippet +file: docs/extras/faq.md +message: file is in a directory go test ./... skips