diff --git a/go.mod b/go.mod index bf8b033fe..f95c8a7c2 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 + github.com/yuin/goldmark v1.7.8 github.com/zalando/go-keyring v0.2.5 golang.org/x/crypto v0.37.0 golang.org/x/sync v0.13.0 @@ -171,7 +172,6 @@ require ( github.com/transparency-dev/merkle v0.0.2 // indirect github.com/vbatts/tar-split v0.11.6 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/yuin/goldmark v1.7.8 // indirect github.com/yuin/goldmark-emoji v1.0.5 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect diff --git a/pkg/cmd/config/config.go b/pkg/cmd/config/config.go index 2661c3369..5f8242d2a 100644 --- a/pkg/cmd/config/config.go +++ b/pkg/cmd/config/config.go @@ -20,10 +20,10 @@ func NewCmdConfig(f *cmdutil.Factory) *cobra.Command { for _, co := range config.Options { longDoc.WriteString(fmt.Sprintf("- `%s`: %s", co.Key, co.Description)) if len(co.AllowedValues) > 0 { - longDoc.WriteString(fmt.Sprintf(" {%s}", strings.Join(co.AllowedValues, "|"))) + longDoc.WriteString(fmt.Sprintf(" `{%s}`", strings.Join(co.AllowedValues, " | "))) } if co.DefaultValue != "" { - longDoc.WriteString(fmt.Sprintf(" (default %s)", co.DefaultValue)) + longDoc.WriteString(fmt.Sprintf(" (default `%s`)", co.DefaultValue)) } longDoc.WriteRune('\n') } diff --git a/pkg/cmd/root/help_test.go b/pkg/cmd/root/help_test.go index d24a84a0d..40f333159 100644 --- a/pkg/cmd/root/help_test.go +++ b/pkg/cmd/root/help_test.go @@ -1,7 +1,20 @@ package root import ( + "fmt" "testing" + + "github.com/cli/cli/v2/internal/browser" + "github.com/cli/cli/v2/internal/config" + "github.com/cli/cli/v2/internal/gh" + "github.com/cli/cli/v2/pkg/cmdutil" + "github.com/cli/cli/v2/pkg/extensions" + "github.com/cli/cli/v2/pkg/iostreams" + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/text" ) func TestDedent(t *testing.T) { @@ -44,3 +57,68 @@ func TestDedent(t *testing.T) { } } } + +// Since our online docs website renders pages by using the kramdown (a superset +// of Markdown) engine, we have to check against some known quirks of the +// syntax. +func TestKramdownCompatibleDocs(t *testing.T) { + ios, _, _, _ := iostreams.Test() + f := &cmdutil.Factory{ + IOStreams: ios, + Config: func() (gh.Config, error) { return config.NewBlankConfig(), nil }, + Browser: &browser.Stub{}, + ExtensionManager: &extensions.ExtensionManagerMock{ + ListFunc: func() []extensions.Extension { + return nil + }, + }, + } + + cmd, err := NewCmdRoot(f, "N/A", "") + require.NoError(t, err) + + var walk func(*cobra.Command) + walk = func(cmd *cobra.Command) { + name := fmt.Sprintf("%q: test pipes are in code blocks", cmd.UseLine()) + t.Run(name, func(t *testing.T) { + assertPipesAreInCodeBlocks(t, cmd) + }) + for _, child := range cmd.Commands() { + walk(child) + } + } + + walk(cmd) +} + +// If not in a code block or a code span, kramdown treats pipes ("|") as table +// column separators, even if there's no table header, or left/right table row +// borders (i.e. lines starting and ending with a pipe). +// +// We need to assert there's no pipe in the text unless it's in a code-block or +// code-span. +// +// (See https://github.com/cli/cli/issues/10348) +func assertPipesAreInCodeBlocks(t *testing.T, cmd *cobra.Command) { + md := goldmark.New() + reader := text.NewReader([]byte(cmd.Long)) + doc := md.Parser().Parse(reader) + + var checkNode func(node ast.Node) + checkNode = func(node ast.Node) { + if node.Kind() == ast.KindCodeSpan || node.Kind() == ast.KindCodeBlock { + return + } + + if node.Kind() == ast.KindText { + text := string(node.(*ast.Text).Segment.Value(reader.Source())) + require.NotContains(t, text, "|", `found pipe ("|") in plain text in %q docs`, cmd.CommandPath()) + } + + for child := node.FirstChild(); child != nil; child = child.NextSibling() { + checkNode(child) + } + } + + checkNode(doc) +}