Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import dev.sorn.fmp4j.CoveragePlugin
import dev.sorn.fmp4j.SnakeCaseMethodFormatter

plugins {
id 'java-library'
id 'java-test-fixtures'
id 'checkstyle'
id 'jacoco'
id 'com.vanniktech.maven.publish' version '0.34.0'
id 'com.diffplug.spotless' version '7.2.0'
id 'com.diffplug.spotless'
}

apply plugin: dev.sorn.fmp4j.CoveragePlugin
apply plugin: CoveragePlugin

group = 'dev.sorn.fmp4j'
version = projectVersion
Expand All @@ -18,6 +21,12 @@ java {
}
}

spotless {
java {
addStep(new SnakeCaseMethodFormatter())
}
}

repositories {
mavenCentral()
}
Expand All @@ -28,6 +37,8 @@ dependencies {
implementation("com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}")
implementation("org.apache.httpcomponents.client5:httpclient5:${apacheHttpComponentsVersion}")
implementation("org.apache.commons:commons-lang3:${apacheCommonsVersion}")
implementation("com.diffplug.spotless:spotless-plugin-gradle:${spotlessVersion}")

testImplementation("org.mockito:mockito-core:${mockitoCoreVersion}")
testImplementation("org.mockito:mockito-junit-jupiter:${mockitoJunitVersion}")
testImplementation(platform("org.junit:junit-bom:${junitVersion}"))
Expand Down
2 changes: 2 additions & 0 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ repositories {
dependencies {
implementation gradleApi()
implementation localGroovy()
implementation("com.diffplug.spotless:spotless-plugin-gradle:${spotlessVersion}")
implementation("com.github.javaparser:javaparser-core:${javaparserVersion}")
}
2 changes: 2 additions & 0 deletions buildSrc/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
spotlessVersion=7.2.0
javaparserVersion=3.27.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package dev.sorn.fmp4j;

import com.diffplug.spotless.FormatterStep;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ParserConfiguration.LanguageLevel;
import java.io.File;
import java.io.Serial;
import java.util.List;
import java.util.regex.Pattern;


public class SnakeCaseMethodFormatter implements FormatterStep {
@Serial
private static final long serialVersionUID = 1L;

// Set of annotations that identify a method as a unit test
private static final List<String> TEST_ANNOTATIONS = List.of(
"Test", // JUnit 4 & 5
"ParameterizedTest", // JUnit 5
"RepeatedTest", // JUnit 5
"TestFactory" // JUnit 5
);

private static final Pattern SNAKE_CASE_PATTERN = Pattern.compile("^[a-z][a-z0-9_]*$");

private static final ParserConfiguration parserConfig = new ParserConfiguration()
.setLanguageLevel(LanguageLevel.JAVA_17);;

@Override
public String getName() {
return "Unit test Naming Enforcement (snake_case)";
}

@Override
public String format(String rawUnix, File file) throws Exception {
// 1. Parse the source code into an AST
StaticJavaParser.setConfiguration(parserConfig);
CompilationUnit cu = StaticJavaParser.parse(rawUnix);

// 2. Walk through all method declarations in the file
boolean changed = false;
for (MethodDeclaration method : cu.findAll(MethodDeclaration.class)) {
if (isUnitTest(method)) {
String oldName = method.getNameAsString();

// 3. Check if the name complies with snake_case
if (!SNAKE_CASE_PATTERN.matcher(oldName).matches()) {
String newName = toSnakeCase(oldName);
method.setName(newName);
changed = true;
}
}
}

// 4. Return the modified source only if changes were made, else return original
// (Parsing/printing can sometimes shift whitespace, so we prefer returning original if untouched)
return changed ? cu.toString() : rawUnix;
}

/**
* Checks if the method is annotated with one of the known test annotations.
*/
private boolean isUnitTest(MethodDeclaration method) {
for (AnnotationExpr annotation : method.getAnnotations()) {
if (TEST_ANNOTATIONS.contains(annotation.getNameAsString())) {
return true;
}
}
return false;
}

/**
* Converts a camelCase string to snake_case.
* Example: "testUserProfile" -> "test_user_profile"
*/
private String toSnakeCase(String input) {
// Regex to look for instances of LowerUpper (e.g. "tU") and insert underscore
String regex = "([a-z])([A-Z]+)";
String replacement = "$1_$2";

// Apply replacement and convert to lowercase
return input.replaceAll(regex, replacement).toLowerCase();

Check warning on line 86 in buildSrc/src/main/java/dev/sorn/fmp4j/SnakeCaseMethodFormatter.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

buildSrc/src/main/java/dev/sorn/fmp4j/SnakeCaseMethodFormatter.java#L86

When doing a String.toLowerCase()/toUpperCase() call, use a Locale
}

@Override
public void close() throws Exception {

}
}

1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ apacheCommonsVersion=3.18.0
apacheHttpComponentsVersion=5.5
jacksonVersion=2.19.2
jacksonCsvVersion=2.17.2
spotlessVersion=7.2.0

# Publishing
mavenCentralPublishing=true
Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
5 changes: 3 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#Fri Aug 15 20:38:05 GST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
43 changes: 30 additions & 13 deletions gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 21 additions & 16 deletions gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading