diff --git a/rewrite-csharp/src/main/java/org/openrewrite/csharp/service/CSharpNamingService.java b/rewrite-csharp/src/main/java/org/openrewrite/csharp/service/CSharpNamingService.java new file mode 100644 index 0000000..0686d5f --- /dev/null +++ b/rewrite-csharp/src/main/java/org/openrewrite/csharp/service/CSharpNamingService.java @@ -0,0 +1,66 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.csharp.service; + +import org.openrewrite.internal.NameCaseConvention; +import org.openrewrite.internal.NamingService; + +import java.util.regex.Pattern; + +public class CSharpNamingService implements NamingService { + + private static final Pattern STANDARD_METHOD_NAME = Pattern.compile("^[A-Z][a-zA-Z0-9]*$"); + private static final Pattern SNAKE_CASE = Pattern.compile("^[a-zA-Z0-9]+_\\w+$"); + + @Override + public String standardizeMethodName(String oldMethodName) { + if (!STANDARD_METHOD_NAME.matcher(oldMethodName).matches()) { + StringBuilder result = new StringBuilder(); + if (SNAKE_CASE.matcher(oldMethodName).matches()) { + result.append(NameCaseConvention.format(NameCaseConvention.UPPER_CAMEL, oldMethodName)); + } else { + int nameLength = oldMethodName.length(); + for (int i = 0; i < nameLength; i++) { + char c = oldMethodName.charAt(i); + if (i == 0) { + // the java specification requires identifiers to start with [a-zA-Z$_] + if (c != '$' && c != '_') { + result.append(Character.toUpperCase(c)); + } + } else { + if (!Character.isLetterOrDigit(c)) { + while ((!Character.isLetterOrDigit(c) || c > 'z')) { + i++; + if (i < nameLength) { + c = oldMethodName.charAt(i); + } else { + break; + } + } + if (i < nameLength) { + result.append(Character.toUpperCase(c)); + } + } else { + result.append(c); + } + } + } + } + return result.toString(); + } + return oldMethodName; + } +} diff --git a/rewrite-csharp/src/main/java/org/openrewrite/csharp/service/package-info.java b/rewrite-csharp/src/main/java/org/openrewrite/csharp/service/package-info.java new file mode 100644 index 0000000..310d549 --- /dev/null +++ b/rewrite-csharp/src/main/java/org/openrewrite/csharp/service/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NullMarked +@NonNullFields +package org.openrewrite.csharp.service; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/rewrite-csharp/src/main/java/org/openrewrite/csharp/tree/Cs.java b/rewrite-csharp/src/main/java/org/openrewrite/csharp/tree/Cs.java index 712ba51..d3496f0 100644 --- a/rewrite-csharp/src/main/java/org/openrewrite/csharp/tree/Cs.java +++ b/rewrite-csharp/src/main/java/org/openrewrite/csharp/tree/Cs.java @@ -22,7 +22,9 @@ import org.openrewrite.*; import org.openrewrite.csharp.CSharpPrinter; import org.openrewrite.csharp.CSharpVisitor; +import org.openrewrite.csharp.service.CSharpNamingService; import org.openrewrite.internal.ListUtils; +import org.openrewrite.internal.NamingService; import org.openrewrite.java.JavaPrinter; import org.openrewrite.java.JavaTypeVisitor; import org.openrewrite.java.internal.TypesInUse; @@ -72,7 +74,7 @@ final class CompilationUnit implements Cs, JavaSourceFile { @Nullable @NonFinal - transient WeakReference padding; + transient WeakReference padding; @Getter @With @@ -264,8 +266,17 @@ public Padding getPadding() { return p; } + @Override + @Incubating(since = "8.2.0") + public T service(Class service) { + if (NamingService.class.getName().equals(service.getName())) { + return (T) new CSharpNamingService(); + } + return JavaSourceFile.super.service(service); + } + @RequiredArgsConstructor - public static class Padding implements JavaSourceFile.Padding { + public static class Padding implements JavaSourceFile.Padding { private final Cs.CompilationUnit t; @Override @@ -505,7 +516,9 @@ final class Argument implements Cs, Expression { @With Keyword refKindKeyword; - public @Nullable Identifier getNameColumn() { return nameColumn == null ? null : nameColumn.getElement(); } + public @Nullable Identifier getNameColumn() { + return nameColumn == null ? null : nameColumn.getElement(); + } public Argument withNameColumn(@Nullable Identifier nameColumn) { return getPadding().withNameColumn(JRightPadded.withElement(this.nameColumn, nameColumn)); @@ -1602,7 +1615,7 @@ public

J acceptCSharp(CSharpVisitor

v, P p) { return v.visitInterpolation(this, p); } - public Padding getPadding() { + public Padding getPadding() { Padding p; if (this.padding == null) { p = new Padding(this); @@ -1966,7 +1979,7 @@ public PropertyDeclaration withType(@Nullable JavaType type) { } public @Nullable NameTree getInterfaceSpecifier() { - return interfaceSpecifier!= null ? interfaceSpecifier.getElement() : null; + return interfaceSpecifier != null ? interfaceSpecifier.getElement() : null; } public PropertyDeclaration withInterfaceSpecifier(@Nullable NameTree interfaceSpecifier) { @@ -2058,14 +2071,11 @@ public PropertyDeclaration withInitializer(@Nullable JLeftPadded ini } } - - @Getter @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) @RequiredArgsConstructor - final class Keyword implements Cs - { + final class Keyword implements Cs { @With @Getter @EqualsAndHashCode.Include @@ -2088,8 +2098,7 @@ final class Keyword implements Cs return v.visitKeyword(this, p); } - public enum KeywordKind - { + public enum KeywordKind { Ref, Out, Await, @@ -2145,7 +2154,7 @@ public CoordinateBuilder.Statement getCoordinates() { @Override public Cs.Lambda withType(@Nullable JavaType type) { - return this.getType() == type ? this : new Cs.Lambda( + return this.getType() == type ? this : new Cs.Lambda( id, prefix, markers, @@ -2491,12 +2500,13 @@ public TypeParameterConstraintClause withTypeParameterConstraints(@Nullable JCon } } - interface TypeParameterConstraint extends J {} + interface TypeParameterConstraint extends J { + } /** * Represents a type constraint in a type parameter's constraint clause. * Example: where T : SomeClass - * where T : IInterface + * where T : IInterface */ @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true) @@ -2573,10 +2583,8 @@ public TypeConstraint withType(TypeTree type) { /* ------------------ */ - - - - interface AllowsConstraint extends J {} + interface AllowsConstraint extends J { + } /** * Represents an `allows` constraint in a where clause. @@ -2701,8 +2709,7 @@ final class ClassOrStructConstraint implements Cs, TypeParameterConstraint { @Getter TypeKind kind; - public enum TypeKind - { + public enum TypeKind { Class, Struct } @@ -2788,7 +2795,6 @@ public

J acceptCSharp(CSharpVisitor

v, P p) { * // use result * } * - * * Example 2: Deconstruction declaration: *

      * int (x, y) = point;
@@ -2852,9 +2858,11 @@ public CoordinateBuilder.Expression getCoordinates() {
     }
 
     //region VariableDesignation
+
     /**
      * Interface for variable designators in declaration expressions.
      * This can be either a single variable name or a parenthesized list of designators for deconstruction.
+     *
      * @see SingleVariableDesignation
      * @see ParenthesizedVariableDesignation
      */
@@ -2864,12 +2872,10 @@ interface VariableDesignation extends Expression, Cs {
     /**
      * Represents a single variable declaration within a declaration expression.
      * Used both for simple out variable declarations and as elements within deconstruction declarations.
-     *
      * Example in out variable:
      * 
      * int.TryParse(s, out int x)  // 'int x' is the SingleVariable
      * 
- * * Example in deconstruction: *
      * (int x, string y) = point;  // both 'int x' and 'string y' are SingleVariables
@@ -2908,7 +2914,7 @@ public 

J acceptCSharp(CSharpVisitor

v, P p) { @Override public SingleVariableDesignation withType(@Nullable JavaType type) { - return this.getType() == type ? this : new SingleVariableDesignation( + return this.getType() == type ? this : new SingleVariableDesignation( id, prefix, markers, @@ -2924,12 +2930,10 @@ public CoordinateBuilder.Expression getCoordinates() { /** * Represents a parenthesized list of variable declarations used in deconstruction patterns. - * * Example of simple deconstruction: *

      * int (x, y) = point;
      * 
- * * Example of nested deconstruction: *
      * (int count, var (string name, int age)) = GetPersonDetails();
@@ -3413,15 +3417,13 @@ public Cs.Unary withOperator(JLeftPadded operator) {
     /**
      * Represents a constructor initializer which is a call to another constructor, either in the same class (this)
      * or in the base class (base).
-     *
      * Examples:
-     *  
+     * 
      * class Person {
-     *     // Constructor with 'this' initializer
-     *     public Person(string name) : this(name, 0) { }
-     *
-     *     // Constructor with 'base' initializer
-     *     public Person(string name, int age) : base(name) { }
+     * // Constructor with 'this' initializer
+     * public Person(string name) : this(name, 0) { }
+     * // Constructor with 'base' initializer
+     * public Person(string name, int age) : base(name) { }
      * }
      * 
*/ @@ -3701,6 +3703,7 @@ public CoordinateBuilder.Statement getCoordinates() { } } + /** * Represents an initializer expression that consists of a list of expressions, typically used in array * or collection initialization contexts. The expressions are contained within delimiters like curly braces. @@ -4157,6 +4160,7 @@ public IsPattern withPattern(JLeftPadded pattern) { } //region Patterns + /** * Base interface for all C# pattern types that can appear on the right-hand side of an 'is' expression. * This includes type patterns, constant patterns, declaration patterns, property patterns, etc. @@ -4709,6 +4713,7 @@ public ListPattern withPatterns(JContainer patterns) { } } + /** * Represents a C# parenthesized pattern expression that groups a nested pattern. *

@@ -5274,8 +5279,6 @@ public CoordinateBuilder.Expression getCoordinates() { } - - /** * Represents a property pattern clause in C# pattern matching, which matches against object properties. *

@@ -5863,8 +5866,7 @@ public SwitchSection withStatements(List> statements) { } } - public interface SwitchLabel extends Expression - { + public interface SwitchLabel extends Expression { } @@ -6371,8 +6373,6 @@ final class FixedStatement implements Cs, Statement { @Getter J.Block block; - - @Override public

J acceptCSharp(CSharpVisitor

v, P p) { return v.visitFixedStatement(this, p); @@ -6385,7 +6385,6 @@ public CoordinateBuilder.Statement getCoordinates() { } } - /** * Represents a C# checked statement which enforces overflow checking for arithmetic operations * and conversions. Operations within a checked block will throw OverflowException if arithmetic diff --git a/rewrite-csharp/src/test/java/org/openrewrite/csharp/service/CSharpNamingServiceTest.java b/rewrite-csharp/src/test/java/org/openrewrite/csharp/service/CSharpNamingServiceTest.java new file mode 100644 index 0000000..78e8b8f --- /dev/null +++ b/rewrite-csharp/src/test/java/org/openrewrite/csharp/service/CSharpNamingServiceTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.csharp.service; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class CSharpNamingServiceTest { + + @ParameterizedTest + @CsvSource(textBlock = """ + foo_bar,FooBar + foo$bar,FooBar + foo_bar$,FooBar + foo$bar$,FooBar + """) + void changeMethodName(String before, String after) { + String actual = new CSharpNamingService().standardizeMethodName(before); + assertThat(actual).isEqualTo(after); + } + +}