From 2adc3a45fbd60126c0eab66b9cdd177a63bd4704 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Tiago?=
 <114936010+jmlt2002@users.noreply.github.com>
Date: Tue, 2 Apr 2024 19:15:40 +0100
Subject: [PATCH] Fixes #27605: inline math blocks can't be preceeded/followed
 by alphanumerical characters (#30175)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Inline math blocks couldn't be preceeded or succeeded by
alphanumerical characters due to changes introduced in PR #21171.
Removed the condition that caused this (precedingCharacter condition)
and added a new exit condition of the for-loop that checks if a specific
'$' was escaped using '\' so that the math expression can be rendered as
intended.
- Additionally this PR fixes another bug where math blocks of the type
'$xyz$abc$' where the dollar sign was not escaped by the user, generated
an error (shown in the screenshots below)
- Altered the tests to accomodate for the changes

Former behaviour (from try.gitea.io):

![image](https://github.com/go-gitea/gitea/assets/114936010/8f0cbb21-321d-451c-b871-c67a8e1e9235)

Fixed behaviour (from my local build):

![image](https://github.com/go-gitea/gitea/assets/114936010/5c22687c-6f11-4407-b5e7-c14b838bc20d)

(Edit) Source code for the README.md file:
```
$x$ -$x$ $x$-

a$xa$ $xa$a 1$xb$ $xb$1

$a a$b b$

a$b $a a$b b$

$a a\$b b$
```

---------

Signed-off-by: João Tiago <joao.leal.tintas@tecnico.ulisboa.pt>
Co-authored-by: Giteabot <teabot@gitea.io>
(cherry picked from commit e006451ab1509f8d6d43c5974387c05b26517392)
---
 modules/markup/markdown/markdown_test.go      | 20 +++++++++++++++++--
 modules/markup/markdown/math/inline_parser.go | 18 ++++++++++++-----
 2 files changed, 31 insertions(+), 7 deletions(-)

diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index ddb6814cd9..3e1d75b291 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -505,9 +505,17 @@ func TestMathBlock(t *testing.T) {
 			`\(a\) \(b\)`,
 			`<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
 		},
+		{
+			`$a$.`,
+			`<p><code class="language-math is-loading">a</code>.</p>` + nl,
+		},
+		{
+			`.$a$`,
+			`<p>.$a$</p>` + nl,
+		},
 		{
 			`$a a$b b$`,
-			`<p><code class="language-math is-loading">a a$b b</code></p>` + nl,
+			`<p>$a a$b b$</p>` + nl,
 		},
 		{
 			`a a$b b`,
@@ -515,7 +523,15 @@ func TestMathBlock(t *testing.T) {
 		},
 		{
 			`a$b $a a$b b$`,
-			`<p>a$b <code class="language-math is-loading">a a$b b</code></p>` + nl,
+			`<p>a$b $a a$b b$</p>` + nl,
+		},
+		{
+			"a$x$",
+			`<p>a$x$</p>` + nl,
+		},
+		{
+			"$x$a",
+			`<p>$x$a</p>` + nl,
 		},
 		{
 			"$$a$$",
diff --git a/modules/markup/markdown/math/inline_parser.go b/modules/markup/markdown/math/inline_parser.go
index 0ac25c2b2a..862234e69b 100644
--- a/modules/markup/markdown/math/inline_parser.go
+++ b/modules/markup/markdown/math/inline_parser.go
@@ -41,9 +41,12 @@ func (parser *inlineParser) Trigger() []byte {
 	return parser.start[0:1]
 }
 
+func isPunctuation(b byte) bool {
+	return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':'
+}
+
 func isAlphanumeric(b byte) bool {
-	// Github only cares about 0-9A-Za-z
-	return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z')
+	return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
 }
 
 // Parse parses the current line and returns a result of parsing.
@@ -56,7 +59,7 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
 	}
 
 	precedingCharacter := block.PrecendingCharacter()
-	if precedingCharacter < 256 && isAlphanumeric(byte(precedingCharacter)) {
+	if precedingCharacter < 256 && (isAlphanumeric(byte(precedingCharacter)) || isPunctuation(byte(precedingCharacter))) {
 		// need to exclude things like `a$` from being considered a start
 		return nil
 	}
@@ -75,14 +78,19 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser.
 		ender += pos
 
 		// Now we want to check the character at the end of our parser section
-		// that is ender + len(parser.end)
+		// that is ender + len(parser.end) and check if char before ender is '\'
 		pos = ender + len(parser.end)
 		if len(line) <= pos {
 			break
 		}
-		if !isAlphanumeric(line[pos]) {
+		suceedingCharacter := line[pos]
+		if !isPunctuation(suceedingCharacter) && !(suceedingCharacter == ' ') {
+			return nil
+		}
+		if line[ender-1] != '\\' {
 			break
 		}
+
 		// move the pointer onwards
 		ender += len(parser.end)
 	}