diff --git a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/ScriptRuleParser.kt b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/ScriptRuleParser.kt index e93f94b7d..a5386f31a 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/ScriptRuleParser.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/idea/plugin/rule/ScriptRuleParser.kt @@ -7,12 +7,14 @@ import com.itangcent.annotation.script.ScriptIgnore import com.itangcent.annotation.script.ScriptTypeName import com.itangcent.common.logger.traceError import com.itangcent.common.utils.asBool +import com.itangcent.common.utils.getSub import com.itangcent.common.utils.mapToTypedArray import com.itangcent.http.RequestUtils import com.itangcent.idea.plugin.api.MethodInferHelper import com.itangcent.idea.plugin.format.Json5Formatter import com.itangcent.intellij.config.rule.* import com.itangcent.intellij.context.ActionContext +import com.itangcent.intellij.extend.notReentrant import com.itangcent.intellij.extend.toPrettyString import com.itangcent.intellij.jvm.* import com.itangcent.intellij.jvm.duck.ArrayDuckType @@ -599,6 +601,14 @@ abstract class ScriptRuleParser : AbstractRuleParser() { return ScriptPsiTypeContext(classRuleConfig!!.tryConvert(psiField.type, psiField)) } + fun isEnumField(): Boolean { + return psiField is PsiEnumConstant + } + + fun asEnumField(): ScriptPsiEnumConstantContext? { + return (psiField as? PsiEnumConstant)?.let { ScriptPsiEnumConstantContext(it) } + } + override fun toString(): String { return containingClass().name() + "#" + psiField.name } @@ -626,6 +636,40 @@ abstract class ScriptRuleParser : AbstractRuleParser() { } } + @ScriptTypeName("enumField") + inner class ScriptPsiEnumConstantContext(private val psiEnumConstant: PsiEnumConstant) : + BaseScriptRuleContext(psiEnumConstant) { + + override fun getName(): String { + return psiEnumConstant.name + } + + fun ordinal(): Int { + return actionContext.callInReadUI { + psiEnumConstant.containingClass?.fields?.indexOf(psiEnumConstant) + } ?: 0 + } + + private fun getEnumFields(): Map { + return actionContext.notReentrant("enumFields") { + actionContext.callInReadUI { + actionContext.instance(PsiResolver::class).resolveEnumFields( + ordinal(), psiEnumConstant + ) + } + } ?: emptyMap() + + } + + fun getParams(): Map { + return getEnumFields().getSub("params") ?: emptyMap() + } + + fun getParam(name: String): Any? { + return getParams()[name] + } + } + /** * it.returnType:class * it.isVarArgs:Boolean diff --git a/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt b/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt index c3ee337b2..d0774148a 100644 --- a/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt +++ b/idea-plugin/src/main/kotlin/com/itangcent/intellij/extend/ActionContextKit.kt @@ -72,15 +72,16 @@ fun ActionContext.withBoundary(action: () -> Unit) { */ private val reentrantIdx = AtomicInteger() -fun ActionContext.notReentrant(flag: String, action: () -> Unit) { +fun ActionContext.notReentrant(flag: String, action: () -> T): T? { val idx = reentrantIdx.getAndIncrement() if (this.cacheOrCompute("reentrantFlag-${flag}") { idx } == idx) { try { - action() + return action() } finally { this.deleteCache("reentrantFlag-${flag}") } } + return null } /** diff --git a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/rule/ScriptClassContextBaseTest.kt b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/rule/ScriptClassContextBaseTest.kt index f6ca32999..108e3c498 100644 --- a/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/rule/ScriptClassContextBaseTest.kt +++ b/idea-plugin/src/test/kotlin/com/itangcent/idea/plugin/rule/ScriptClassContextBaseTest.kt @@ -33,6 +33,8 @@ abstract class ScriptClassContextBaseTest : PluginContextLightCodeInsightFixture internal lateinit var addPsiClass: PsiClass internal lateinit var updatePsiClass: PsiClass internal lateinit var validationGroupedDemoDtoPsiClass: PsiClass + internal lateinit var userTypePsiClass: PsiClass + internal lateinit var userInfoPsiClass: PsiClass override fun beforeBind() { super.beforeBind() @@ -64,6 +66,8 @@ abstract class ScriptClassContextBaseTest : PluginContextLightCodeInsightFixture addPsiClass = loadClass("constant/Add.java")!! updatePsiClass = loadClass("constant/Update.java")!! validationGroupedDemoDtoPsiClass = loadClass("model/ValidationGroupedDemoDto.java")!! + userTypePsiClass = loadClass("constant/UserType.java")!! + userInfoPsiClass = loadClass("model/UserInfo.java")!! } override fun customConfig(): String? { @@ -90,6 +94,12 @@ abstract class ScriptClassContextBaseTest : PluginContextLightCodeInsightFixture " }else{\n" + " return -1\n" + " }\n" + + "```\n" + + "field.doc=groovy:```\n" + + "if(!it.isEnumField()){\n" + + " return\n" + + "}\n" + + "return it.asEnumField().getParam(\"desc\")\n" + "```" } @@ -532,6 +542,18 @@ abstract class ScriptClassContextBaseTest : PluginContextLightCodeInsightFixture fun testToJson() { assertEquals("{}", objectPsiClass.asClassContext().toJson(true, true)) + assertEquals( + "{\n" + + " \"id\": 0,\n" + + " \"type\": 0,\n" + + " \"name\": \"\",\n" + + " \"age\": 0,\n" + + " \"sex\": 0,\n" + + " \"birthDay\": \"\",\n" + + " \"regtime\": \"\"\n" + + "}", userInfoPsiClass.asClassContext().toJson(true, true) + ) + assertEquals( "{\n" + " \"shouldBeFirst\": \"\",\n" + @@ -695,6 +717,26 @@ abstract class ScriptClassContextBaseTest : PluginContextLightCodeInsightFixture fun testToJson5() { assertEquals("{}", objectPsiClass.asClassContext().toJson5(true, true)) + assertEquals( + "{\n" + + " \"id\": 0, //user id\n" + + " /**\n" + + " * user type\n" + + " * 1 :administration\n" + + " * ADMINISTRATION\n" + + " * 2 :a person, an animal or a plant\n" + + " * MEMBER\n" + + " * 3 :Anonymous visitor\n" + + " * ANONYMOUS\n" + + " */\n" + + " \"type\": 0,\n" + + " \"name\": \"\", //user name\n" + + " \"age\": 0, //user age\n" + + " \"sex\": 0,\n" + + " \"birthDay\": \"\", //user birthDay\n" + + " \"regtime\": \"\" //user regtime\n" + + "}", userInfoPsiClass.asClassContext().toJson5(true, true) + ) assertEquals( "{\n" + " \"shouldBeFirst\": \"\",\n" + @@ -1145,5 +1187,34 @@ abstract class ScriptClassContextBaseTest : PluginContextLightCodeInsightFixture } //endregion + + //region tests of ScriptFieldContext + + fun testIsEnumField() { + assertFalse(userInfoPsiClass.asClassContext().fields()[0].isEnumField()) + assertTrue(userTypePsiClass.asClassContext().fields()[0].isEnumField()) + } + + fun testAsEnumField() { + assertNull(userInfoPsiClass.asClassContext().fields()[0].asEnumField()) + run { + val enumField = userTypePsiClass.asClassContext().fields()[0].asEnumField() + assertNotNull(enumField) + enumField!! + assertEquals(0, enumField.ordinal()) + assertEquals(mapOf("type" to 1, "desc" to "ADMINISTRATION"), enumField.getParams()) + assertEquals("ADMINISTRATION", enumField.getParam("desc")) + } + run { + val enumField = userTypePsiClass.asClassContext().fields()[1].asEnumField() + assertNotNull(enumField) + enumField!! + assertEquals(1, enumField.ordinal()) + assertEquals(mapOf("type" to 2, "desc" to "MEMBER"), enumField.getParams()) + assertEquals("MEMBER", enumField.getParam("desc")) + } + } + + //endregion }