[analyzer_utilities] Add String.toSnakeCase extension method.

This extension method complements the existing `toCamelCase` and
`toPascalCase` extension methods, allowing a string to be converted
from `camelCase` or `PascalCase` to `snake_case`.

This extension method will be useful in the analyzer diagnostic
message generation logic, which still uses a mix of case styles.

Note that there's no need for a separate method to convert to
`UPPER_SNAKE_CASE`; that can be easily done by calling `toUpperCase`
after `toSnakeCase`.

Change-Id: I6a6a69647f4ee176ecdbaefc3de0906a88c69079
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/460206
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
Paul Berry
2025-11-07 05:26:20 -08:00
committed by Commit Queue
parent a6faf24bee
commit 790d844b51
2 changed files with 40 additions and 0 deletions
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
extension StringExtension on String {
static final _toSnakeCaseRegExp = RegExp('_?[A-Z]');
/// Converts `SCREAMING_SNAKE_CASE` or `snake_case` to `camelCase`.
String toCamelCase() {
var parts = toLowerCase().split('_');
@@ -51,4 +53,26 @@ extension StringExtension on String {
}
return buffer.toString();
}
/// Converts `camelCase` or `PascalCase` to `snake_case`
String toSnakeCase() {
var parts = <String>[];
var i = 0;
var wordStarts = _toSnakeCaseRegExp.allMatches(this);
for (var RegExpMatch(:start) in wordStarts) {
if (i < start) {
parts.add(substring(i, start).toLowerCase());
i = start;
}
if (this[i] == '_' && parts.isNotEmpty) {
// Avoid doubling up the `_`. This handles strings that are already in
// snake case like `foo_Bar` (which translates to `foo_bar`).
i++;
}
}
if (i < length) {
parts.add(substring(i).toLowerCase());
}
return parts.join('_');
}
}
@@ -41,4 +41,20 @@ class StringExtensionTest {
expect('FOO__BAR'.toPascalCase(), 'FooBar');
expect('FOO_BAR_'.toPascalCase(), 'FooBar');
}
void test_toSnakeCase() {
expect('camelCase'.toSnakeCase(), 'camel_case');
expect('PascalCase'.toSnakeCase(), 'pascal_case');
expect('already_snake_case'.toSnakeCase(), 'already_snake_case');
expect(
'mixedCamel_AndPascal_and_snake'.toSnakeCase(),
'mixed_camel_and_pascal_and_snake',
);
expect('with123Numbers'.toSnakeCase(), 'with123_numbers');
expect(''.toSnakeCase(), '');
expect(
'CONSECUTIVE_UPCASE'.toSnakeCase(),
'c_o_n_s_e_c_u_t_i_v_e_u_p_c_a_s_e',
);
}
}