Repository: naturalcrit/homebrewery (js)

Issue:
Markdown: Functions break if a variable name is a substring of the function names
### Your idea:

During development of #3587, I discovered that `abs(a)` did not work, but `abs(c)` did.

Exhaustive testing for `abs()`:
![image](https://github.com/user-attachments/assets/cb4593b2-5c46-4947-b31b-13a088e29240)

Initial testing for `floor()`:
![image](https://github.com/user-attachments/assets/b470d596-7b85-4ddc-ba89-06b9d58a9adb)

As can be seen, in every case where a variable name used in that calculation is a substring of the function name, the function stops working.

My suspicion is that the issue lies here, `shared/naturalcrit/markdown.js:line 465`:
![image](https://github.com/user-attachments/assets/098bcb91-5a8a-467b-b585-497fb215692f)

By replacing _every_ instance of the variable name, we are catching unintended characters. Instead, we need to only replace the instances identified in the original regex match.

Pull request:
Fix var name substring clash #3613
This PR resolves #3613.

This PR changes the regex of the variable name replacement, to ensure that names that are substrings of math functions or other variables are not unintentionally replaced.

CURRENT:
```
[a]:-4

$[abs(a)]
```
**OUTPUT:** `$[abs(a)]`

![image](https://github.com/user-attachments/assets/5217170e-f0a2-4589-9eef-212deb3a22e3)

PR:
```
[a]:-4

$[abs(a)]
```
**OUTPUT:** `4`

![image](https://github.com/user-attachments/assets/61bf4370-6e7d-42f1-98d3-a342fa079012)

Patch:
diff --git a/shared/naturalcrit/markdown.js b/shared/naturalcrit/markdown.js
index 518b4870..f7f1763e 100644
--- a/shared/naturalcrit/markdown.js
+++ b/shared/naturalcrit/markdown.js
@@ -462,7 +462,7 @@ const replaceVar = function(input, hoist=false, allowUnresolved=false) {
 		mathVars?.forEach((variable)=>{
 			const foundVar = lookupVar(variable, globalPageNumber, hoist);
 			if(foundVar && foundVar.resolved && foundVar.content && !isNaN(foundVar.content)) // Only subsitute math values if fully resolved, not empty strings, and numbers
-				replacedLabel = replacedLabel.replaceAll(variable, foundVar.content);
+				replacedLabel = replacedLabel.replaceAll(new RegExp(`(?<!\\w)(${variable})(?!\\w)`, 'g'), foundVar.content);
 		});
 
 		try {


Test patch:
diff --git a/tests/markdown/variables.test.js b/tests/markdown/variables.test.js
index e6018e19..3a03d64f 100644
--- a/tests/markdown/variables.test.js
+++ b/tests/markdown/variables.test.js
@@ -370,4 +370,18 @@ describe('Cross-page variables', ()=>{
 		const rendered = renderAllPages([source0, source1]).join('\n\\page\n').trimReturns();
 		expect(rendered, `Input:\n${[source0, source1].join('\n\\page\n')}`, { showPrefix: false }).toBe('<p>two</p><p>one</p>\\page<p>two</p>');
 	});
+});
+
+describe('Variable name as a subset of a function or other variable name', ()=>{
+	it('Variable name as a subset of a function', function() {
+		const source = `[a]: -1\n\n$[abs(a)]`;
+		const rendered = Markdown.render(source).trimReturns();
+		expect(rendered).toBe('<p>1</p>');
+	});
+
+	it('Variable name as a subset of another variable name', function() {
+		const source = `[ab]: 2\n\n[aba]: 8\n\n[ba]: 4\n\n$[ab + aba + ba]`;
+		const rendered = Markdown.render(source).trimReturns();
+		expect(rendered).toBe('<p>14</p>');
+	});
 });
\ No newline at end of file