Repository: gohugoio/hugo (language: go)

Issue:
Set .Inner and .InnerDeindent to nil when shortcode is not closed
markdown

```
{{< sc >}}

Bar
```

layouts/shortcodes/sc.html

```
{{ .Inner | len }} --> 6
{{ .InnerDeindent | len }} --> 6  (\n+ \n+ 3 + \n)
```

When a shortcode is not closed, with either a closing tag or self-closing, set .Inner and .InnerDeindent to nil.

Reference <https://discourse.gohugo.io/t/42190/2>

Fix shortcode error message
When a shortcode is closed, with either a closing tag or self-closing, and neither `.Inner` nor `.InnerDeindent` is evaluated in the shortcode, we currently emit this:

> failed to extract shortcode: shortcode “>}}” has no .Inner, yet a closing tag was provided

Suggest this instead:

> failed to extract shortcode: shortcode “{{ name-of-shortcode }}” does not evaluate .Inner or .InnerDeindent, yet a closing tag was provided

Reference <https://discourse.gohugo.io/t/42190/2>


Discussion / hints:
I suggest we set it to an empty string. `nil` prints differently in many situations, and they're for most practical applications the same.
> I suggest we set it to an empty string

Works for me.


Fix patch:
diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
index a82caff43cf..13fe913a686 100644
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -224,6 +224,10 @@ func (s shortcode) insertPlaceholder() bool {
 	return !s.doMarkup || s.configVersion() == 1
 }
 
+func (s shortcode) needsInner() bool {
+	return s.info != nil && s.info.ParseInfo().IsInner
+}
+
 func (s shortcode) configVersion() int {
 	if s.info == nil {
 		// Not set for inline shortcodes.
@@ -557,12 +561,9 @@ func (s *shortcodeHandler) extractShortcode(ordinal, level int, source []byte, p
 	cnt := 0
 	nestedOrdinal := 0
 	nextLevel := level + 1
+	closed := false
 	const errorPrefix = "failed to extract shortcode"
 
-	fail := func(err error, i pageparser.Item) error {
-		return s.parseError(fmt.Errorf("%s: %w", errorPrefix, err), source, i.Pos())
-	}
-
 Loop:
 	for {
 		currItem := pt.Next()
@@ -602,24 +603,21 @@ Loop:
 			// we trust the template on this:
 			// if there's no inner, we're done
 			if !sc.isInline {
-				if sc.info == nil {
-					// This should not happen.
-					return sc, fail(errors.New("BUG: template info not set"), currItem)
-				}
 				if !sc.info.ParseInfo().IsInner {
 					return sc, nil
 				}
 			}
 
 		case currItem.IsShortcodeClose():
+			closed = true
 			next := pt.Peek()
 			if !sc.isInline {
-				if sc.info == nil || !sc.info.ParseInfo().IsInner {
+				if !sc.needsInner() {
 					if next.IsError() {
 						// return that error, more specific
 						continue
 					}
-					return sc, fail(fmt.Errorf("shortcode %q has no .Inner, yet a closing tag was provided", next.ValStr(source)), next)
+					return nil, fmt.Errorf("%s: shortcode %q does not evaluate .Inner or .InnerDeindent, yet a closing tag was provided", errorPrefix, next.ValStr(source))
 				}
 			}
 			if next.IsRightShortcodeDelim() {
@@ -689,6 +687,11 @@ Loop:
 				}
 			}
 		case currItem.IsDone():
+			if !currItem.IsError() {
+				if !closed && sc.needsInner() {
+					return sc, fmt.Errorf("%s: unclosed shortcode %q", errorPrefix, sc.name)
+				}
+			}
 			// handled by caller
 			pt.Backup()
 			break Loop


Test patch:
diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go
index 2f285d0da4d..eac3de2ce2c 100644
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -875,7 +875,7 @@ title: "No Inner!"
 		"layouts/shortcodes/noinner.html", `No inner here.`)
 
 	err := b.BuildE(BuildCfg{})
-	b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`"content/mypage.md:4:21": failed to extract shortcode: shortcode "noinner" has no .Inner, yet a closing tag was provided`))
+	b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`"content/mypage.md:4:16": failed to extract shortcode: shortcode "noinner" does not evaluate .Inner or .InnerDeindent, yet a closing tag was provided`))
 }
 
 func TestShortcodeStableOutputFormatTemplates(t *testing.T) {
@@ -1198,3 +1198,81 @@ C'est un test
 	b.AssertFileContent("public/fr/p2/index.html", `plus-dinformations`)
 
 }
+
+// Issue 10671.
+func TestShortcodeInnerShouldBeEmptyWhenNotClosed(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+disableKinds = ["home", "taxonomy", "term"]
+-- content/p1.md --
+---
+title: "p1"
+---
+
+{{< sc "self-closing" />}}
+
+Text.
+
+{{< sc "closing-no-newline" >}}{{< /sc >}}
+
+-- layouts/shortcodes/sc.html --
+Inner: {{ .Get 0 }}: {{ len .Inner }}
+InnerDeindent: {{ .Get 0 }}: {{ len .InnerDeindent }}
+-- layouts/_default/single.html --
+{{ .Content }}
+`
+
+	b := NewIntegrationTestBuilder(
+		IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+			Running:     true,
+			Verbose:     true,
+		},
+	).Build()
+
+	b.AssertFileContent("public/p1/index.html", `
+Inner: self-closing: 0
+InnerDeindent: self-closing: 0
+Inner: closing-no-newline: 0
+InnerDeindent: closing-no-newline: 0
+
+`)
+}
+
+// Issue 10675.
+func TestShortcodeErrorWhenItShouldBeClosed(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+disableKinds = ["home", "taxonomy", "term"]
+-- content/p1.md --
+---
+title: "p1"
+---
+
+{{< sc >}}
+
+Text.
+
+-- layouts/shortcodes/sc.html --
+Inner: {{ .Get 0 }}: {{ len .Inner }}
+-- layouts/_default/single.html --
+{{ .Content }}
+`
+
+	b, err := NewIntegrationTestBuilder(
+		IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+			Running:     true,
+			Verbose:     true,
+		},
+	).BuildE()
+
+	b.Assert(err, qt.Not(qt.IsNil))
+	b.Assert(err.Error(), qt.Contains, `p1.md:5:1": failed to extract shortcode: unclosed shortcode "sc"`)
+}