Fix rename_to_camel_case to handle screaming caps

Change-Id: I04564db1c4f47be18949cf16dcf42230db0da6e5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/252562
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/rename_to_camel_case.dart b/pkg/analysis_server/lib/src/services/correction/dart/rename_to_camel_case.dart
index 3323ec6..83f4f0a 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/rename_to_camel_case.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/rename_to_camel_case.dart
@@ -5,7 +5,7 @@
 import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
 import 'package:analysis_server/src/services/correction/fix.dart';
 import 'package:analysis_server/src/services/correction/util.dart';
-import 'package:analysis_server/src/utilities/strings.dart';
+import 'package:analysis_server/src/utilities/extensions/string.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
@@ -39,11 +39,11 @@
     }
 
     // Prepare the new name.
-    var words = identifier.name.split('_');
-    if (words.length < 2) {
+    var newName = identifier.name.toLowerCamelCase;
+    if (newName == null) {
       return;
     }
-    _newName = words.first + words.skip(1).map((w) => capitalize(w)).join();
+    _newName = newName;
 
     // Find references to the identifier.
     List<SimpleIdentifier>? references;
diff --git a/pkg/analysis_server/lib/src/utilities/extensions/string.dart b/pkg/analysis_server/lib/src/utilities/extensions/string.dart
new file mode 100644
index 0000000..6e9d46f
--- /dev/null
+++ b/pkg/analysis_server/lib/src/utilities/extensions/string.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+extension StringExtension on String {
+  /// Assuming that the receiver is a valid identifier, return a lowerCamelCase
+  /// version of the identifier.
+  ///
+  /// No checks are made that the receiver is a valid identifier, but the
+  /// receiver must contain at least one underscore (but neither at the
+  /// beginning nor the end), and must not have two adjacent underscores.
+  ///
+  /// The resulting identifier is one in which only the letters following the
+  /// underscores are capitalized.
+  String? get toLowerCamelCase {
+    var words = split('_');
+    if (words.length < 2) {
+      return null;
+    }
+    var firstWord = words.first;
+    if (firstWord.isEmpty) {
+      return null;
+    }
+    var buffer = StringBuffer();
+    buffer.write(firstWord.toLowerCase());
+    for (var i = 1; i < words.length; i++) {
+      var word = words[i];
+      if (word.isEmpty) {
+        return null;
+      }
+      buffer.write(word._capitalized);
+    }
+    return buffer.toString();
+  }
+
+  /// Return a version of this string in which the first character is upper case
+  /// and all remaining characters are lower case.
+  String get _capitalized {
+    if (length <= 1) {
+      return toUpperCase();
+    }
+    return substring(0, 1).toUpperCase() + substring(1).toLowerCase();
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/rename_to_camel_case_test.dart b/pkg/analysis_server/test/src/services/correction/fix/rename_to_camel_case_test.dart
index a658d2d..a70faee 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/rename_to_camel_case_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/rename_to_camel_case_test.dart
@@ -104,6 +104,19 @@
 ''');
   }
 
+  Future<void> test_parameter_function_screamingCaps() async {
+    await resolveTestCode('''
+void f(int FIRST_PARAMETER) {
+  print(FIRST_PARAMETER);
+}
+''');
+    await assertHasFix('''
+void f(int firstParameter) {
+  print(firstParameter);
+}
+''');
+  }
+
   Future<void> test_parameter_method() async {
     await resolveTestCode('''
 class A {
diff --git a/pkg/analysis_server/test/src/utilities/extensions/string_test.dart b/pkg/analysis_server/test/src/utilities/extensions/string_test.dart
new file mode 100644
index 0000000..8f7c1ec
--- /dev/null
+++ b/pkg/analysis_server/test/src/utilities/extensions/string_test.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/utilities/extensions/string.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(ToLowerCamelCaseTest);
+  });
+}
+
+@reflectiveTest
+class ToLowerCamelCaseTest {
+  void test_adjacentUnderscores() {
+    expect('a__b'.toLowerCamelCase, null);
+  }
+
+  void test_empty() {
+    expect(''.toLowerCamelCase, null);
+  }
+
+  void test_leadingUnderscore() {
+    expect('_a'.toLowerCamelCase, null);
+  }
+
+  void test_screamingCaps() {
+    expect('AA_BB'.toLowerCamelCase, 'aaBb');
+  }
+
+  void test_snakeCase() {
+    expect('aa_bb'.toLowerCamelCase, 'aaBb');
+  }
+
+  void test_trailingUnderscore() {
+    expect('a_'.toLowerCamelCase, null);
+  }
+}
diff --git a/pkg/analysis_server/test/src/utilities/extensions/test_all.dart b/pkg/analysis_server/test/src/utilities/extensions/test_all.dart
index d4620d4..e3dbeea2 100644
--- a/pkg/analysis_server/test/src/utilities/extensions/test_all.dart
+++ b/pkg/analysis_server/test/src/utilities/extensions/test_all.dart
@@ -5,9 +5,11 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'range_factory_test.dart' as range_factory;
+import 'string_test.dart' as string;
 
 void main() {
   defineReflectiveSuite(() {
     range_factory.main();
+    string.main();
   });
 }