Repository: gohugoio/hugo (language: go)

Issue:
Cannot get attributes in no language identifier in CodeBlock
<!-- Please answer these questions before submitting your issue. Thanks! -->

### What version of Hugo are you using (`hugo version`)?

<pre>
$ hugo version
hugo v0.101.0+extended darwin/amd64 BuildDate=unknown
</pre>

### Does this issue reproduce with the latest release?

Yes

### The issue
Sometimes, I don't specify a language identifier in a Code Block as follows:

````markdown
```{foo=true}
bar
```
````

In the above case, the Render Hooks for Code Block do not receive the attributes.
Is this behavior expected?

I guess this cause is that `attrStartIdx` equals 0  if no language identifier in`getAttributes()`.
Ref. https://github.com/gohugoio/hugo/blob/master/markup/goldmark/codeblocks/render.go#L184-L191

If this behavior is a bug, I guess that be fixed by the below diff. 

```diff
-		if attrStartIdx > 0 {
+		if attrStartIdx != -1 {
```
Ref. https://github.com/gohugoio/hugo/commit/b6b15e57602186dcbef0a61c422bdaa9eb295dca

Thanks!


Discussion / hints:

Fix patch:
diff --git a/markup/goldmark/codeblocks/render.go b/markup/goldmark/codeblocks/render.go
index e245688e7e6..3daad0af6d2 100644
--- a/markup/goldmark/codeblocks/render.go
+++ b/markup/goldmark/codeblocks/render.go
@@ -16,6 +16,7 @@ package codeblocks
 import (
 	"bytes"
 	"fmt"
+	"strings"
 	"sync"
 
 	"github.com/alecthomas/chroma/v2/lexers"
@@ -69,7 +70,7 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
 	}
 
 	n := node.(*codeBlock)
-	lang := string(n.b.Language(src))
+	lang := getLang(n.b, src)
 	renderer := ctx.RenderContext().GetRenderer(hooks.CodeBlockRendererType, lang)
 	if renderer == nil {
 		return ast.WalkStop, fmt.Errorf("no code renderer found for %q", lang)
@@ -174,6 +175,12 @@ func (c *codeBlockContext) Position() htext.Position {
 	return c.pos
 }
 
+func getLang(node *ast.FencedCodeBlock, src []byte) string {
+	langWithAttributes := string(node.Language(src))
+	lang, _, _ := strings.Cut(langWithAttributes, "{")
+	return lang
+}
+
 func getAttributes(node *ast.FencedCodeBlock, infostr []byte) []ast.Attribute {
 	if node.Attributes() != nil {
 		return node.Attributes()
@@ -188,7 +195,7 @@ func getAttributes(node *ast.FencedCodeBlock, infostr []byte) []ast.Attribute {
 			}
 		}
 
-		if attrStartIdx > 0 {
+		if attrStartIdx != -1 {
 			n := ast.NewTextBlock() // dummy node for storing attributes
 			attrStr := infostr[attrStartIdx:]
 			if attrs, hasAttr := parser.ParseAttributes(text.NewReader(attrStr)); hasAttr {


Test patch:
diff --git a/markup/goldmark/codeblocks/integration_test.go b/markup/goldmark/codeblocks/integration_test.go
index 1990497892d..d0206957a1d 100644
--- a/markup/goldmark/codeblocks/integration_test.go
+++ b/markup/goldmark/codeblocks/integration_test.go
@@ -265,6 +265,39 @@ Position: {{ .Position | safeHTML }}
 	b.AssertFileContent("public/p1/index.html", filepath.FromSlash("Position: \"/content/p1.md:7:1\""))
 }
 
+// Issue 10118
+func TestAttributes(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+-- content/p1.md --
+---
+title: "p1"
+---
+
+## Issue 10118
+
+§§§ {foo="bar"}
+Hello, World!
+§§§
+
+-- layouts/_default/single.html --
+{{ .Content }}
+-- layouts/_default/_markup/render-codeblock.html --
+Attributes: {{ .Attributes }}|Type: {{ .Type }}|
+`
+
+	b := hugolib.NewIntegrationTestBuilder(
+		hugolib.IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+		},
+	).Build()
+
+	b.AssertFileContent("public/p1/index.html", "<h2 id=\"issue-10118\">Issue 10118</h2>\nAttributes: map[foo:bar]|Type: |")
+}
+
 // Issue 9571
 func TestAttributesChroma(t *testing.T) {
 	t.Parallel()