Merge pull request #287150 from lucas-gomes-santana/fix/snippet-unicode-support

Improve snippet case transforms suport for non-Latin scripts (fix: #286165)
This commit is contained in:
Johannes Rieken
2026-01-26 18:10:15 +01:00
committed by GitHub
2 changed files with 54 additions and 10 deletions

View File

@@ -400,13 +400,15 @@ export class FormatString extends Marker {
}
}
// Note: word-based case transforms rely on uppercase/lowercase distinctions.
// For scripts without case, transforms are effectively no-ops.
private _toKebabCase(value: string): string {
const match = value.match(/[a-z0-9]+/gi);
const match = value.match(/[\p{L}0-9]+/gu);
if (!match) {
return value;
}
if (!value.match(/[a-z0-9]/)) {
if (!value.match(/[\p{L}0-9]/u)) {
return value
.trim()
.toLowerCase()
@@ -414,12 +416,16 @@ export class FormatString extends Marker {
.replace(/[\s_]+/g, '-');
}
const match2 = value
.trim()
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g);
const cleaned = value.trim().replace(/^_+|_+$/g, '');
const match2 = cleaned.match(/\p{Lu}{2,}(?=\p{Lu}\p{Ll}+[0-9]*|[\s_-]|$)|\p{Lu}?\p{Ll}+[0-9]*|\p{Lu}(?=\p{Lu}\p{Ll})|\p{Lu}(?=[\s_-]|$)|[0-9]+/gu);
if (!match2) {
return value;
return cleaned
.split(/[\s_-]+/)
.filter(word => word.length > 0)
.map(word => word.toLowerCase())
.join('-');
}
return match2
@@ -428,7 +434,7 @@ export class FormatString extends Marker {
}
private _toPascalCase(value: string): string {
const match = value.match(/[a-z0-9]+/gi);
const match = value.match(/[\p{L}0-9]+/gu);
if (!match) {
return value;
}
@@ -439,7 +445,7 @@ export class FormatString extends Marker {
}
private _toCamelCase(value: string): string {
const match = value.match(/[a-z0-9]+/gi);
const match = value.match(/[\p{L}0-9]+/gu);
if (!match) {
return value;
}
@@ -453,7 +459,7 @@ export class FormatString extends Marker {
}
private _toSnakeCase(value: string): string {
return value.replace(/([a-z])([A-Z])/g, '$1_$2')
return value.replace(/(\p{Ll})(\p{Lu})/gu, '$1_$2')
.replace(/[\s\-]+/g, '_')
.toLowerCase();
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import assert from 'assert';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
import { Choice, FormatString, Marker, Placeholder, Scanner, SnippetParser, Text, TextmateSnippet, TokenType, Transform, Variable } from '../../browser/snippetParser.js';
import { Choice, FormatString, Marker, Placeholder, Scanner, SnippetParser, Text, TextmateSnippet, TokenType, Transform, Variable, VariableResolver } from '../../browser/snippetParser.js';
suite('SnippetParser', () => {
@@ -700,6 +700,44 @@ suite('SnippetParser', () => {
assert.strictEqual(new FormatString(1, undefined, 'bar', 'foo').resolve('baz'), 'bar');
});
test('Unicode Variable Transformations', () => {
const resolver = new class implements VariableResolver {
resolve(variable: Variable): string | undefined {
const values: { [key: string]: string } = {
'RUSSIAN': 'одинДва',
'GREEK': 'έναςΔύο',
'TURKISH': 'istanbulLı',
'JAPANESE': 'こんにちは'
};
return values[variable.name];
}
};
function assertTransform(transformName: string, varName: string, expected: string) {
const p = new SnippetParser();
const snippet = p.parse(`\${${varName}/(.*)/\${1:/${transformName}}/}`);
const variable = snippet.children[0] as Variable;
variable.resolve(resolver);
const resolved = variable.toString();
assert.strictEqual(resolved, expected, `${transformName} failed for ${varName}`);
}
assertTransform('kebabcase', 'RUSSIAN', 'один-два');
assertTransform('kebabcase', 'GREEK', 'ένας-δύο');
assertTransform('snakecase', 'RUSSIAN', 'один_два');
assertTransform('snakecase', 'GREEK', νας_δύο');
assertTransform('camelcase', 'RUSSIAN', 'одинДва');
assertTransform('camelcase', 'GREEK', 'έναςΔύο');
assertTransform('pascalcase', 'RUSSIAN', 'ОдинДва');
assertTransform('pascalcase', 'GREEK', 'ΈναςΔύο');
assertTransform('upcase', 'RUSSIAN', 'ОДИНДВА');
assertTransform('downcase', 'RUSSIAN', 'одиндва');
assertTransform('kebabcase', 'TURKISH', 'istanbul-lı');
assertTransform('pascalcase', 'TURKISH', 'IstanbulLı');
assertTransform('upcase', 'JAPANESE', 'こんにちは');
assertTransform('kebabcase', 'JAPANESE', 'こんにちは');
});
test('Snippet variable transformation doesn\'t work if regex is complicated and snippet body contains \'$$\' #55627', function () {
const snippet = new SnippetParser().parse('const fileName = "${TM_FILENAME/(.*)\\..+$/$1/}"');
assert.strictEqual(snippet.toTextmateString(), 'const fileName = "${TM_FILENAME/(.*)\\..+$/${1}/}"');