User: rednesto Date: 23 Aug 24 13:23 Revision: 8443d3b31e640cdec2bda6fdd21ca4f4d88a9b4a Summary: Fix creator ClassFqn suggestion and validation This catches more invalid FQNs parts TeamCity URL: https://ci.mcdev.io/viewModification.html?tab=vcsModificationFiles&modId=9625&personal=false Index: src/main/kotlin/creator/custom/BuiltinValidations.kt =================================================================== --- src/main/kotlin/creator/custom/BuiltinValidations.kt (revision c27988001896e64244de43ac74dbb7fc5edc5b86) +++ src/main/kotlin/creator/custom/BuiltinValidations.kt (revision 8443d3b31e640cdec2bda6fdd21ca4f4d88a9b4a) @@ -23,11 +23,13 @@ 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,8 +60,10 @@ } 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> = validationErrorIf(MCDevBundle("creator.validation.regex", regex)) { !it.matches(regex) } Index: src/main/kotlin/creator/custom/derivation/SuggestClassNamePropertyDerivation.kt =================================================================== --- src/main/kotlin/creator/custom/derivation/SuggestClassNamePropertyDerivation.kt (revision c27988001896e64244de43ac74dbb7fc5edc5b86) +++ src/main/kotlin/creator/custom/derivation/SuggestClassNamePropertyDerivation.kt (revision 8443d3b31e640cdec2bda6fdd21ca4f4d88a9b4a) @@ -33,11 +33,14 @@ 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?>?, Index: src/test/kotlin/creator/ClassFqnCreatorPropertyTest.kt =================================================================== --- src/test/kotlin/creator/ClassFqnCreatorPropertyTest.kt (revision 8443d3b31e640cdec2bda6fdd21ca4f4d88a9b4a) +++ src/test/kotlin/creator/ClassFqnCreatorPropertyTest.kt (revision 8443d3b31e640cdec2bda6fdd21ca4f4d88a9b4a) @@ -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()) + } +}