Repository: microsoft/vscode (TypeScript) — Bug Fix

Issue:
Snippet transform `camelcase` not working well.
<!-- ⚠️⚠️ Do Not Delete This! bug_report_template ⚠️⚠️ -->
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
<!-- 🕮 Read our guide about submitting issues: https://github.com/microsoft/vscode/wiki/Submitting-Bugs-and-Suggestions -->
<!-- 🔎 Search existing issues to avoid creating duplicates. -->
<!-- 🧪 Test using the latest Insiders build to see if your issue has already been fixed: https://code.visualstudio.com/insiders/ -->
<!-- 💡 Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. -->
<!-- 🔧 Launch with `code --disable-extensions` to check. -->
Does this issue occur when all extensions are disabled?: Yes/No

<!-- 🪓 If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. -->
<!-- 📣 Issues caused by an extension need to be reported directly to the extension publisher. The 'Help > Report Issue' dialog can assist with this. -->
- VS Code Version: 1.65.2
- OS Version: Darwin x64 19.6.0

Steps to Reproduce:

1. Create a code snippet like this.

```json
{
	"TS -> Inject Service": {
		"scope": "typescript",
		"description": "Constructor Injection Pattern",
		"prefix": "@inject",
		"body": "@inject(${1/(.*)/I${1}Token/}) private readonly ${1/(.*)/${1:/camelcase}/}: I${1},$0"
	}
}
```

2. Using the snippet. 

![camelcase](https://user-images.githubusercontent.com/24731539/160342428-b0d6e71e-f472-4c34-8ce3-48b28ffc7e51.gif)

As I as concerned, the camelcase of `PathService` should be `pathService`. However, the result is `pathservice`. 

https://github.com/microsoft/vscode/blob/049b0e564b6bc1fcc07e9a39b727bca9836274f4/src/vs/editor/contrib/snippet/browser/snippetParser.ts#L404-L418

The implementation doesn't seems to tackle it well. 

I'd like to help if you could be so nice to give me a hint to fix the problem. 😃 

Snippet transform `camelcase` not working well.
<!-- ⚠️⚠️ Do Not Delete This! bug_report_template ⚠️⚠️ -->
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
<!-- 🕮 Read our guide about submitting issues: https://github.com/microsoft/vscode/wiki/Submitting-Bugs-and-Suggestions -->
<!-- 🔎 Search existing issues to avoid creating duplicates. -->
<!-- 🧪 Test using the latest Insiders build to see if your issue has already been fixed: https://code.visualstudio.com/insiders/ -->
<!-- 💡 Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. -->
<!-- 🔧 Launch with `code --disable-extensions` to check. -->
Does this issue occur when all extensions are disabled?: Yes/No

<!-- 🪓 If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. -->
<!-- 📣 Issues caused by an extension need to be reported directly to the extension publisher. The 'Help > Report Issue' dialog can assist with this. -->
- VS Code Version: 1.65.2
- OS Version: Darwin x64 19.6.0

Steps to Reproduce:

1. Create a code snippet like this.

```json
{
	"TS -> Inject Service": {
		"scope": "typescript",
		"description": "Constructor Injection Pattern",
		"prefix": "@inject",
		"body": "@inject(${1/(.*)/I${1}Token/}) private readonly ${1/(.*)/${1:/camelcase}/}: I${1},$0"
	}
}
```

2. Using the snippet. 

![camelcase](https://user-images.githubusercontent.com/24731539/160342428-b0d6e71e-f472-4c34-8ce3-48b28ffc7e51.gif)

As I as concerned, the camelcase of `PathService` should be `pathService`. However, the result is `pathservice`. 

https://github.com/microsoft/vscode/blob/049b0e564b6bc1fcc07e9a39b727bca9836274f4/src/vs/editor/contrib/snippet/browser/snippetParser.ts#L404-L418

The implementation doesn't seems to tackle it well. 

I'd like to help if you could be so nice to give me a hint to fix the problem. 😃 



Discussion:
It is supposed to convert `kebab-case` and `snake_case` to `camelCase`.

In your case it's basically matching first letter and converting to lowercase and then matching everything else to use it as is.
You can check examples in https://stackoverflow.com/questions/48104851/snippet-regex-match-arbitrary-number-of-groups-and-transform-to-camelcase/51228186#51228186 to adopt them to your use case.
However, I am curious about how to deal with the word `stub_PathServiceToken`.

In my opinion, the camelized expectation should be `stubPathServiceToken` rather `stubPathservicetoken`.
I'd like to help if you help to define the actual expectation of the `camelcase` transform function.
Thanks. Your definition (https://github.com/microsoft/vscode/issues/146166#issuecomment-1081287990) seems reasonable, add camel case but don't remove them
It is supposed to convert `kebab-case` and `snake_case` to `camelCase`.

In your case it's basically matching first letter and converting to lowercase and then matching everything else to use it as is.
You can check examples in https://stackoverflow.com/questions/48104851/snippet-regex-match-arbitrary-number-of-groups-and-transform-to-camelcase/51228186#51228186 to adopt them to your use case.
However, I am curious about how to deal with the word `stub_PathServiceToken`.

In my opinion, the camelized expectation should be `stubPathServiceToken` rather `stubPathservicetoken`.
I'd like to help if you help to define the actual expectation of the `camelcase` transform function.
Thanks. Your definition (https://github.com/microsoft/vscode/issues/146166#issuecomment-1081287990) seems reasonable, add camel case but don't remove them

Resolving patch:
diff --git a/src/vs/editor/contrib/snippet/browser/snippetParser.ts b/src/vs/editor/contrib/snippet/browser/snippetParser.ts
--- a/src/vs/editor/contrib/snippet/browser/snippetParser.ts
+++ b/src/vs/editor/contrib/snippet/browser/snippetParser.ts
@@ -395,8 +395,7 @@ export class FormatString extends Marker {
 			return value;
 		}
 		return match.map(word => {
-			return word.charAt(0).toUpperCase()
-				+ word.substr(1).toLowerCase();
+			return word.charAt(0).toUpperCase() + word.substr(1);
 		})
 			.join('');
 	}
@@ -408,11 +407,9 @@ export class FormatString extends Marker {
 		}
 		return match.map((word, index) => {
 			if (index === 0) {
-				return word.toLowerCase();
-			} else {
-				return word.charAt(0).toUpperCase()
-					+ word.substr(1).toLowerCase();
+				return word.charAt(0).toLowerCase() + word.substr(1);
 			}
+			return word.charAt(0).toUpperCase() + word.substr(1);
 		})
 			.join('');
 	}


Test patch:
diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts
--- a/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts
+++ b/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts
@@ -656,8 +656,14 @@ suite('SnippetParser', () => {
 		assert.strictEqual(new FormatString(1, 'capitalize').resolve('bar no repeat'), 'Bar no repeat');
 		assert.strictEqual(new FormatString(1, 'pascalcase').resolve('bar-foo'), 'BarFoo');
 		assert.strictEqual(new FormatString(1, 'pascalcase').resolve('bar-42-foo'), 'Bar42Foo');
+		assert.strictEqual(new FormatString(1, 'pascalcase').resolve('snake_AndPascalCase'), 'SnakeAndPascalCase');
+		assert.strictEqual(new FormatString(1, 'pascalcase').resolve('kebab-AndPascalCase'), 'KebabAndPascalCase');
+		assert.strictEqual(new FormatString(1, 'pascalcase').resolve('_justPascalCase'), 'JustPascalCase');
 		assert.strictEqual(new FormatString(1, 'camelcase').resolve('bar-foo'), 'barFoo');
 		assert.strictEqual(new FormatString(1, 'camelcase').resolve('bar-42-foo'), 'bar42Foo');
+		assert.strictEqual(new FormatString(1, 'camelcase').resolve('snake_AndCamelCase'), 'snakeAndCamelCase');
+		assert.strictEqual(new FormatString(1, 'camelcase').resolve('kebab-AndCamelCase'), 'kebabAndCamelCase');
+		assert.strictEqual(new FormatString(1, 'camelcase').resolve('_JustCamelCase'), 'justCamelCase');
 		assert.strictEqual(new FormatString(1, 'notKnown').resolve('input'), 'input');
 
 		// if