diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 806575354..9b1c150d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,7 +116,7 @@ jobs: DEST_DIR="arkanalyzer" MAX_RETRIES=10 RETRY_DELAY=3 # Delay between retries in seconds - BRANCH="neo/2025-08-12" + BRANCH="neo/2025-09-03" for ((i=1; i<=MAX_RETRIES; i++)); do git clone --depth=1 --branch $BRANCH $REPO_URL $DEST_DIR && break diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt index 75fb62326..d071751f7 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Convert.kt @@ -529,8 +529,8 @@ fun TypeDto.toEtsType(): EtsType = when (this) { is ClassTypeDto -> toEtsClassType() is EnumValueTypeDto -> EtsEnumValueType( - signature = signature.toEtsFieldSignature(), - constant = constant?.toEtsConstant(), + signature = signature.toEtsClassSignature(), + name = name, ) is FunctionTypeDto -> EtsFunctionType( diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt index 5e9fb8fd8..511db7ac3 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/dto/Types.kt @@ -71,8 +71,8 @@ data class LexicalEnvTypeDto( @Serializable @SerialName("EnumValueType") data class EnumValueTypeDto( - val signature: FieldSignatureDto, - val constant: ConstantDto? = null, + val signature: ClassSignatureDto, + val name: String? = null, ) : TypeDto @Serializable diff --git a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt index d6dda2ae2..d78bfdbd7 100644 --- a/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt +++ b/jacodb-ets/src/main/kotlin/org/jacodb/ets/model/Type.kt @@ -428,11 +428,15 @@ data class EtsLexicalEnvType( } data class EtsEnumValueType( - val signature: EtsFieldSignature, - val constant: EtsConstant? = null, + val signature: EtsClassSignature, + val name: String? = null, ) : EtsType { override val typeName: String - get() = signature.name + get() = if (name != null) { + "${signature.name}.$name" + } else { + signature.name + } override fun toString(): String = typeName diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EnumTest.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EnumTest.kt new file mode 100644 index 000000000..921cf8c57 --- /dev/null +++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EnumTest.kt @@ -0,0 +1,148 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *
+ * 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 + *
+ * http://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.jacodb.ets.test
+
+import org.jacodb.ets.model.EtsClassCategory
+import org.jacodb.ets.test.utils.getResourcePath
+import org.jacodb.ets.utils.loadEtsFileAutoConvert
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+class EnumTest {
+
+ @Test
+ fun testEnum() {
+ val path = "/samples/source/lang/enum.ts"
+ val res = getResourcePath(path)
+ val file = loadEtsFileAutoConvert(res)
+
+ // Count enum classes
+ val enumClasses = file.allClasses.filter { it.category == EtsClassCategory.ENUM }
+ assertTrue(enumClasses.size >= 5, "File should have multiple enums")
+
+ // Check for specific enum types
+ val basicEnum = enumClasses.find { it.name == "BasicEnum" }
+ val stringEnum = enumClasses.find { it.name == "StringEnum" }
+ val mixedEnum = enumClasses.find { it.name == "MixedEnum" }
+
+ assertNotNull(basicEnum, "Should find BasicEnum")
+ assertNotNull(stringEnum, "Should find StringEnum")
+ assertNotNull(mixedEnum, "Should find MixedEnum")
+
+ // Verify field counts
+ assertEquals(3, basicEnum.fields.size, "BasicEnum should have 3 fields")
+ assertEquals(3, stringEnum.fields.size, "StringEnum should have 3 fields")
+ assertEquals(3, mixedEnum.fields.size, "MixedEnum should have 3 fields")
+ }
+
+ @Test
+ fun testEnumEdgeCases() {
+ val path = "/samples/source/lang/enum-edge-cases.ts"
+ val res = getResourcePath(path)
+ val file = loadEtsFileAutoConvert(res)
+
+ val enumClasses = file.allClasses.filter { it.category == EtsClassCategory.ENUM }
+ assertTrue(enumClasses.isNotEmpty(), "Edge cases file should have enums")
+
+ // Check for specific edge case enums
+ val emptyEnum = enumClasses.find { it.name == "EmptyEnum" }
+ val singleEnum = enumClasses.find { it.name == "SingleEnum" }
+ val floatEnum = enumClasses.find { it.name == "FloatEnum" }
+
+ assertNotNull(emptyEnum, "Should find EmptyEnum")
+ assertNotNull(singleEnum, "Should find SingleEnum")
+ assertNotNull(floatEnum, "Should find FloatEnum")
+
+ // Verify edge case properties
+ assertEquals(0, emptyEnum.fields.size, "EmptyEnum should have no fields")
+ assertEquals(1, singleEnum.fields.size, "SingleEnum should have 1 field")
+ assertEquals(3, floatEnum.fields.size, "FloatEnum should have 3 fields")
+ }
+
+ @Test
+ fun testEnumModules() {
+ val path = "/samples/source/lang/enum-modules.ts"
+ val res = getResourcePath(path)
+ val file = loadEtsFileAutoConvert(res)
+
+ val enumClasses = file.allClasses.filter { it.category == EtsClassCategory.ENUM }
+ assertTrue(enumClasses.isNotEmpty(), "Modules file should have enums")
+
+ // Check for exported enums
+ val publicEnum = enumClasses.find { it.name == "PublicEnum" }
+ val constExportEnum = enumClasses.find { it.name == "ConstExportEnum" }
+
+ assertNotNull(publicEnum, "Should find PublicEnum")
+ assertNotNull(constExportEnum, "Should find ConstExportEnum")
+
+ // Verify exported enum properties
+ assertEquals(3, publicEnum.fields.size, "PublicEnum should have 3 fields")
+ assertEquals(3, constExportEnum.fields.size, "ConstExportEnum should have 3 fields")
+ }
+
+ @Test
+ fun testEnumUsageInClasses() {
+ val path = "/samples/source/lang/enum.ts"
+ val res = getResourcePath(path)
+ val file = loadEtsFileAutoConvert(res)
+
+ // Find class that uses enums
+ val usageClass = file.allClasses.find { it.name == "EnumUsageExamples" }
+ assertNotNull(usageClass, "Should find EnumUsageExamples class")
+
+ // Verify the class has methods that work with enums
+ val methods = usageClass.methods
+ assertTrue(methods.any { it.name == "setColor" }, "Should have setColor method")
+ assertTrue(methods.any { it.name == "getDirection" }, "Should have getDirection method")
+ assertTrue(methods.any { it.name == "handleDirection" }, "Should have handleDirection method")
+
+ // Check method parameter counts
+ val setColorMethod = methods.find { it.name == "setColor" }
+ assertNotNull(setColorMethod, "setColor method should exist")
+ assertEquals(1, setColorMethod.parameters.size, "setColor should have 1 parameter")
+
+ val getDirectionMethod = methods.find { it.name == "getDirection" }
+ assertNotNull(getDirectionMethod, "getDirection method should exist")
+ assertEquals(0, getDirectionMethod.parameters.size, "getDirection should have no parameters")
+ }
+
+ @Test
+ fun testEnumVsClassDistinction() {
+ val path = "/samples/source/lang/enum.ts"
+ val res = getResourcePath(path)
+ val file = loadEtsFileAutoConvert(res)
+
+ // Count different class categories
+ val enumCount = file.allClasses.count { it.category == EtsClassCategory.ENUM }
+ val classCount = file.allClasses.count { it.category == EtsClassCategory.CLASS }
+
+ assertTrue(enumCount > 0, "Should have enum classes")
+ assertTrue(classCount > 0, "Should have regular classes")
+
+ // Verify proper categorization
+ val basicEnum = file.allClasses.find { it.name == "BasicEnum" }
+ val usageClass = file.allClasses.find { it.name == "EnumUsageExamples" }
+
+ assertNotNull(basicEnum, "Should find BasicEnum")
+ assertNotNull(usageClass, "Should find EnumUsageExamples")
+
+ assertEquals(EtsClassCategory.ENUM, basicEnum.category, "BasicEnum should be categorized as ENUM")
+ assertEquals(EtsClassCategory.CLASS, usageClass.category, "EnumUsageExamples should be categorized as CLASS")
+ }
+}
diff --git a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt
index 7c920d504..990a6a8c7 100644
--- a/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt
+++ b/jacodb-ets/src/test/kotlin/org/jacodb/ets/test/EtsFromJsonTest.kt
@@ -609,11 +609,12 @@ class EtsFromJsonTest {
fun testClassCategory() {
val path = "/samples/etsir/ast/lang/enum.ts.json"
val file = loadEtsFileFromResource(path)
- val cls = file.classes.first { it.name == "Animal" }
+ val cls = file.classes.first { it.name == "BasicEnum" }
assertEquals(EtsClassCategory.ENUM, cls.category)
- assertEquals(2, cls.fields.size)
- assertEquals("Cat", cls.fields[0].name)
- assertEquals("Dog", cls.fields[1].name)
+ assertEquals(3, cls.fields.size)
+ assertEquals("First", cls.fields[0].name)
+ assertEquals("Second", cls.fields[1].name)
+ assertEquals("Third", cls.fields[2].name)
}
@Test
diff --git a/jacodb-ets/src/test/resources/samples/source/lang/enum-edge-cases.ts b/jacodb-ets/src/test/resources/samples/source/lang/enum-edge-cases.ts
new file mode 100644
index 000000000..02e85d9fa
--- /dev/null
+++ b/jacodb-ets/src/test/resources/samples/source/lang/enum-edge-cases.ts
@@ -0,0 +1,193 @@
+// noinspection JSUnusedGlobalSymbols,JSUnusedLocalSymbols
+
+// Empty enum (edge case)
+enum EmptyEnum {
+ // No members
+}
+
+// Single member enum
+enum SingleEnum {
+ OnlyOne
+}
+
+// Enum with float values
+enum FloatEnum {
+ Pi = 3.14,
+ E = 2.71,
+ Phi = 1.618
+}
+
+// Enum with very long names
+enum VeryLongNameEnum {
+ ThisIsAVeryLongEnumMemberNameThatTestsTheParsingCapability,
+ AnotherExtremelyLongEnumMemberNameForTestingPurposes,
+ ShortName
+}
+
+// Enum with special characters in string values
+enum SpecialCharEnum {
+ NewLine = "line1\nline2",
+ Tab = "col1\tcol2",
+ Quote = "He said \"Hello\"",
+ Backslash = "path\\to\\file",
+ Unicode = "café 🚀 ñoño"
+}
+
+// Enum with duplicate values (different keys, same values)
+enum DuplicateValueEnum {
+ Primary = 1,
+ Secondary = 1, // Same value as Primary
+ Tertiary = 2
+}
+
+// Enum members that look like reserved words
+enum ReservedWordLikeEnum {
+ class = "class_value",
+ function = "function_value",
+ var = "var_value",
+ let = "let_value",
+ const = "const_value"
+}
+
+// Enum with number-like string values
+enum NumberStringEnum {
+ Zero = "0",
+ One = "1",
+ Two = "2",
+ NotANumber = "NaN"
+}
+
+// Nested enum access patterns
+namespace NestedAccess {
+ export enum InnerEnum {
+ Value1 = "inner1",
+ Value2 = "inner2"
+ }
+
+ export function useInnerEnum(): InnerEnum {
+ return InnerEnum.Value1;
+ }
+}
+
+// Enum used in generic constraints
+interface EnumConstraint