diff --git a/src/main/kotlin/creator/custom/BuiltinValidations.kt b/src/main/kotlin/creator/custom/BuiltinValidations.kt index 8fd1a8401..f34e51d21 100644 --- a/src/main/kotlin/creator/custom/BuiltinValidations.kt +++ b/src/main/kotlin/creator/custom/BuiltinValidations.kt @@ -23,11 +23,13 @@ package com.demonwav.mcdev.creator.custom import com.demonwav.mcdev.asset.MCDevBundle import com.demonwav.mcdev.platform.fabric.util.FabricVersions import com.demonwav.mcdev.util.SemanticVersion +import com.intellij.lang.java.lexer.JavaLexer import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.ValidationInfo import com.intellij.openapi.ui.validation.DialogValidation import com.intellij.openapi.ui.validation.validationErrorIf import com.intellij.openapi.util.text.StringUtil +import com.intellij.pom.java.LanguageLevel import javax.swing.JComponent object BuiltinValidations { @@ -58,7 +60,9 @@ object BuiltinValidations { } val validClassFqn = validationErrorIf(MCDevBundle("creator.validation.class_fqn")) { - it.isBlank() || it.split('.').any { part -> !StringUtil.isJavaIdentifier(part) } + it.isBlank() || it.split('.').any { part -> + !StringUtil.isJavaIdentifier(part) || JavaLexer.isKeyword(part, LanguageLevel.HIGHEST) + } } fun byRegex(regex: Regex): DialogValidation.WithParameter<() -> String> = diff --git a/src/main/kotlin/creator/custom/derivation/SuggestClassNamePropertyDerivation.kt b/src/main/kotlin/creator/custom/derivation/SuggestClassNamePropertyDerivation.kt index 8362a7b4a..bd04aad44 100644 --- a/src/main/kotlin/creator/custom/derivation/SuggestClassNamePropertyDerivation.kt +++ b/src/main/kotlin/creator/custom/derivation/SuggestClassNamePropertyDerivation.kt @@ -33,11 +33,14 @@ class SuggestClassNamePropertyDerivation : PreparedDerivation { override fun derive(parentValues: List): Any? { val coords = parentValues[0] as BuildSystemCoordinates val name = parentValues[1] as String - return ClassFqn("${coords.groupId}.${name.decapitalize()}.${name.capitalize()}") + val sanitizedName = name.split(NOT_JAVA_IDENTIFIER).joinToString("", transform = String::capitalize) + return ClassFqn("${coords.groupId}.${sanitizedName.decapitalize()}.$sanitizedName") } companion object : PropertyDerivationFactory { + private val NOT_JAVA_IDENTIFIER = Regex("\\P{javaJavaIdentifierPart}+") + override fun create( reporter: TemplateValidationReporter, parents: List?>?, diff --git a/src/test/kotlin/creator/ClassFqnCreatorPropertyTest.kt b/src/test/kotlin/creator/ClassFqnCreatorPropertyTest.kt new file mode 100644 index 000000000..a150e788d --- /dev/null +++ b/src/test/kotlin/creator/ClassFqnCreatorPropertyTest.kt @@ -0,0 +1,101 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2024 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.demonwav.mcdev.creator + +import com.demonwav.mcdev.creator.custom.BuiltinValidations +import com.demonwav.mcdev.creator.custom.TemplateDescriptor +import com.demonwav.mcdev.creator.custom.model.BuildSystemCoordinates +import com.demonwav.mcdev.creator.custom.model.ClassFqn +import com.intellij.openapi.ui.validation.invoke +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +@DisplayName("Class FQN Creator Property Tests") +class ClassFqnCreatorPropertyTest : CreatorTemplateProcessorTestBase() { + + private fun checkValidation(input: String, expectedMessage: String?) { + val validation = BuiltinValidations.validClassFqn { input } + val validationInfo = validation.validate() + if (expectedMessage == null) { + assertNull(validationInfo?.message) { "Expected input to be valid: '$input'" } + } else { + assertNotNull(validationInfo, "Expected input to be invalid: '$input'") + assertEquals(expectedMessage, validationInfo!!.message) + } + } + + @Test + @DisplayName("Validation") + fun validation() { + checkValidation("test.TestName", null) + val invalidFqns = listOf( + "test.0InvalidName", + "test.Invalid-Name", + "test.package.InvalidPackage", + "test..InvalidPackage", + "test." + ) + for (fqn in invalidFqns) { + checkValidation(fqn, "Must be a valid class fully qualified name") + } + } + + @Test + @DisplayName("Class Name Suggestion") + fun classNameSuggestion() { + makeTemplate( + """ + { + "version": ${TemplateDescriptor.FORMAT_VERSION}, + "properties": [ + { + "name": "BUILD_COORDS", + "type": "build_system_coordinates" + }, + { + "name": "NAME", + "type": "string" + }, + { + "name": "FQN", + "type": "class_fqn", + "derives": { + "method": "suggestClassName", + "parents": ["BUILD_COORDS", "NAME"] + } + } + ] + } + """.trimIndent() + ) + + val buildCoordsProperty = processor.context.property("BUILD_COORDS") + val nameProperty = processor.context.property("NAME") + val fqnProperty = processor.context.property("FQN") + + buildCoordsProperty.graphProperty.set(BuildSystemCoordinates("com.example.project", "example-project", "1.0")) + nameProperty.graphProperty.set("My Project") + assertEquals(ClassFqn("com.example.project.myProject.MyProject"), fqnProperty.get()) + } +}