Add SIR builder and printer for classes #KT-65905 fixed

Merge-request: KT-MR-14478
Merged-by: Artem Olkov <artem.olkov@jetbrains.com>
diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/TypeCheckSwiftExportGoldenData.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/TypeCheckSwiftExportGoldenData.java
index d334606..9882a42 100644
--- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/TypeCheckSwiftExportGoldenData.java
+++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/test/blackbox/TypeCheckSwiftExportGoldenData.java
@@ -25,6 +25,42 @@
   }
 
   @Nested
+  @TestMetadata("native/swift/swift-export-standalone/testData/classes")
+  @TestDataPath("$PROJECT_ROOT")
+  public class Classes {
+    @Test
+    public void testAllFilesPresentInClasses() {
+      KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/swift/swift-export-standalone/testData/classes"), Pattern.compile("^(.+)\\.swift$"), null, true);
+    }
+
+    @Nested
+    @TestMetadata("native/swift/swift-export-standalone/testData/classes/golden_result")
+    @TestDataPath("$PROJECT_ROOT")
+    public class Golden_result {
+      @Test
+      public void testAllFilesPresentInGolden_result() {
+        KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/swift/swift-export-standalone/testData/classes/golden_result"), Pattern.compile("^(.+)\\.swift$"), null, true);
+      }
+
+      @Test
+      @TestMetadata("result.swift")
+      public void testResult() {
+        runTest("native/swift/swift-export-standalone/testData/classes/golden_result/result.swift");
+      }
+    }
+
+    @Nested
+    @TestMetadata("native/swift/swift-export-standalone/testData/classes/input_root")
+    @TestDataPath("$PROJECT_ROOT")
+    public class Input_root {
+      @Test
+      public void testAllFilesPresentInInput_root() {
+        KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("native/swift/swift-export-standalone/testData/classes/input_root"), Pattern.compile("^(.+)\\.swift$"), null, true);
+      }
+    }
+  }
+
+  @Nested
   @TestMetadata("native/swift/swift-export-standalone/testData/documentation")
   @TestDataPath("$PROJECT_ROOT")
   public class Documentation {
diff --git a/native/swift/sir-passes/src/org/jetbrains/sir/passes/builder/SirFromPsiBuilder.kt b/native/swift/sir-passes/src/org/jetbrains/sir/passes/builder/SirFromPsiBuilder.kt
index a6b8100..883bd64 100644
--- a/native/swift/sir-passes/src/org/jetbrains/sir/passes/builder/SirFromPsiBuilder.kt
+++ b/native/swift/sir-passes/src/org/jetbrains/sir/passes/builder/SirFromPsiBuilder.kt
@@ -6,15 +6,15 @@
 package org.jetbrains.sir.passes.builder
 
 import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
+import org.jetbrains.kotlin.analysis.api.symbols.KtClassKind
+import org.jetbrains.kotlin.analysis.api.symbols.KtNamedClassOrObjectSymbol
 import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
 import org.jetbrains.kotlin.analysis.api.types.KtType
+import org.jetbrains.kotlin.descriptors.Modality
 import org.jetbrains.kotlin.psi.*
 import org.jetbrains.kotlin.psi.psiUtil.isPublic
 import org.jetbrains.kotlin.sir.*
-import org.jetbrains.kotlin.sir.builder.buildFunction
-import org.jetbrains.kotlin.sir.builder.buildGetter
-import org.jetbrains.kotlin.sir.builder.buildSetter
-import org.jetbrains.kotlin.sir.builder.buildVariable
+import org.jetbrains.kotlin.sir.builder.*
 import org.jetbrains.kotlin.sir.util.SirSwiftModule
 
 
@@ -28,6 +28,17 @@
     private val res: MutableList<SirDeclaration>,
     private val analysisSession: KtAnalysisSession
 ) : KtTreeVisitorVoid() {
+
+    override fun visitClassOrObject(classOrObject: KtClassOrObject) {
+        // we do not handle inner declarations of class currently. No need to go deeper.
+        // super.visitClassOrObject(classOrObject)
+        with(analysisSession) {
+            classOrObject.process {
+                buildSirClassFromPsi(classOrObject)
+            }
+        }
+    }
+
     override fun visitNamedFunction(function: KtNamedFunction) {
         super.visitNamedFunction(function)
         with(analysisSession) {
@@ -46,7 +57,7 @@
         }
     }
 
-    private inline fun <T : KtDeclaration> T.process(converter: T.() -> SirDeclaration) {
+    private inline fun <T : KtDeclaration> T.process(converter: T.() -> SirDeclaration?) {
         this.takeIf { it.isPublic }
             ?.let(converter)
             ?.let { res.add(it) }
@@ -54,6 +65,18 @@
 }
 
 context(KtAnalysisSession)
+internal fun buildSirClassFromPsi(classOrObject: KtClassOrObject): SirNamedDeclaration? {
+    val symbol = classOrObject
+        .getNamedClassOrObjectSymbol()
+        ?.takeIf { it.isConsumableBySirBuilder() }
+        ?: return null // todo: error handling strategy: KT-65980
+    return buildClass {
+        name = classOrObject.name ?: "UNKNOWN_CLASS" // todo: error handling strategy: KT-65980
+        origin = KotlinSource(symbol)
+    }
+}
+
+context(KtAnalysisSession)
 internal fun buildSirFunctionFromPsi(function: KtNamedFunction): SirFunction = buildFunction {
     val symbol = function.getFunctionLikeSymbol()
     val callableId = symbol.callableIdIfNonLocal
@@ -134,3 +157,12 @@
             throw IllegalArgumentException("Swift Export does not support argument type: ${it.asStringForDebugging()}")
     }
 )
+
+context(KtAnalysisSession)
+private fun KtNamedClassOrObjectSymbol.isConsumableBySirBuilder(): Boolean =
+    classKind == KtClassKind.CLASS
+            && (superTypes.count() == 1 && superTypes.first().isAny) // Every class has Any as a superclass
+            && classIdIfNonLocal?.packageFqName?.isRoot != false
+            && !isData
+            && !isInline
+            && modality == Modality.FINAL
diff --git a/native/swift/sir-printer/src/org/jetbrains/sir/printer/SirAsSwiftSourcesPrinter.kt b/native/swift/sir-printer/src/org/jetbrains/sir/printer/SirAsSwiftSourcesPrinter.kt
index 2ffccd6..7562d66 100644
--- a/native/swift/sir-printer/src/org/jetbrains/sir/printer/SirAsSwiftSourcesPrinter.kt
+++ b/native/swift/sir-printer/src/org/jetbrains/sir/printer/SirAsSwiftSourcesPrinter.kt
@@ -41,9 +41,22 @@
         println("import ${import.moduleName}")
     }
 
+    override fun visitClass(klass: SirClass): Unit = with(printer) {
+        printVisibility(klass)
+        println(
+            "class ",
+            klass.name.swiftIdentifier,
+            " {"
+        )
+        withIndent {
+            klass.acceptChildren(this@SirAsSwiftSourcesPrinter)
+        }
+        println("}")
+    }
+
     override fun visitVariable(variable: SirVariable): Unit = with(printer) {
+        printVisibility(variable)
         print(
-            variable.visibility.takeIf { it != SirVisibility.INTERNAL }?.let { "${it.swift} " } ?: "",
             if (variable.isStatic) "static " else "",
             "var ",
             variable.name.swiftIdentifier,
@@ -82,8 +95,8 @@
 
     override fun visitFunction(function: SirFunction): Unit = with(printer) {
         function.documentation?.let { println(it) }
+        printVisibility(function)
         print(
-            function.visibility.takeIf { it != SirVisibility.INTERNAL }?.let { "${it.swift} " } ?: "",
             if (function.isStatic) {
                 "static "
             } else {
@@ -121,8 +134,8 @@
     }
 
     override fun visitEnum(enum: SirEnum): Unit = with(printer) {
+        printVisibility(enum)
         println(
-            enum.visibility.takeIf { it != SirVisibility.INTERNAL }?.let { "${it.swift} " } ?: "",
             "enum ",
             enum.name.swiftIdentifier,
             " {"
@@ -167,4 +180,10 @@
 
 private val simpleIdentifierRegex = Regex("[_a-zA-Z][_a-zA-Z0-9]*")
 
-private val String.swiftIdentifier get() = if (simpleIdentifierRegex.matches(this)) this else "`$this`"
\ No newline at end of file
+private val String.swiftIdentifier get() = if (simpleIdentifierRegex.matches(this)) this else "`$this`"
+
+internal fun SmartPrinter.printVisibility(decl: SirDeclaration) {
+    print(
+        decl.visibility.takeIf { it != SirVisibility.INTERNAL }?.let { "${it.swift} " } ?: ""
+    )
+}
\ No newline at end of file
diff --git a/native/swift/sir-printer/testData/empty_class.golden.swift b/native/swift/sir-printer/testData/empty_class.golden.swift
new file mode 100644
index 0000000..4ce430d
--- /dev/null
+++ b/native/swift/sir-printer/testData/empty_class.golden.swift
@@ -0,0 +1,2 @@
+public class Foo {
+}
diff --git a/native/swift/sir-printer/testData/empty_class_inside_class.golden.swift b/native/swift/sir-printer/testData/empty_class_inside_class.golden.swift
new file mode 100644
index 0000000..0d06495
--- /dev/null
+++ b/native/swift/sir-printer/testData/empty_class_inside_class.golden.swift
@@ -0,0 +1,4 @@
+public class OUTER_CLASS {
+    public class INNER_CLASS {
+    }
+}
diff --git a/native/swift/sir-printer/testData/empty_class_inside_enum.golden.swift b/native/swift/sir-printer/testData/empty_class_inside_enum.golden.swift
new file mode 100644
index 0000000..e957c2b
--- /dev/null
+++ b/native/swift/sir-printer/testData/empty_class_inside_enum.golden.swift
@@ -0,0 +1,4 @@
+public enum MyEnum {
+    public class Foo {
+    }
+}
diff --git a/native/swift/sir-printer/tests/org/jetbrains/kotlin/sir/printer/SirAsSwiftSourcesPrinterTests.kt b/native/swift/sir-printer/tests/org/jetbrains/kotlin/sir/printer/SirAsSwiftSourcesPrinterTests.kt
index accc381..40bc213 100644
--- a/native/swift/sir-printer/tests/org/jetbrains/kotlin/sir/printer/SirAsSwiftSourcesPrinterTests.kt
+++ b/native/swift/sir-printer/tests/org/jetbrains/kotlin/sir/printer/SirAsSwiftSourcesPrinterTests.kt
@@ -6,6 +6,8 @@
 package org.jetbrains.kotlin.sir.printer
 
 import org.jetbrains.kotlin.sir.*
+import org.jetbrains.kotlin.sir.builder.buildClass
+import org.jetbrains.kotlin.sir.builder.buildEnum
 import org.jetbrains.kotlin.sir.builder.buildFunction
 import org.jetbrains.kotlin.sir.builder.buildModule
 import org.jetbrains.kotlin.sir.util.SirSwiftModule
@@ -275,6 +277,72 @@
         )
     }
 
+    @Test
+    fun `should print empty class`() {
+
+        val module = buildModule {
+            name = "Test"
+            declarations.add(
+                buildClass {
+                    origin = SirOrigin.Unknown
+                    visibility = SirVisibility.PUBLIC
+                    name = "Foo"
+                }
+            )
+        }
+
+        runTest(
+            module,
+            "testData/empty_class"
+        )
+    }
+
+    @Test
+    fun `should print empty class inside enum`() {
+
+        val module = buildModule {
+            name = "Test"
+            declarations.add(
+                buildEnum {
+                    origin = SirOrigin.Unknown
+                    name = "MyEnum"
+
+                    declarations.add(
+                        buildClass {
+                            origin = SirOrigin.Unknown
+                            visibility = SirVisibility.PUBLIC
+                            name = "Foo"
+                        })})}
+
+        runTest(
+            module,
+            "testData/empty_class_inside_enum"
+        )
+    }
+
+    @Test
+    fun `should print empty class inside class`() {
+
+        val module = buildModule {
+            name = "Test"
+            declarations.add(
+                buildClass {
+                    origin = SirOrigin.Unknown
+                    name = "OUTER_CLASS"
+
+                    declarations.add(
+                        buildClass {
+                            origin = SirOrigin.Unknown
+                            visibility = SirVisibility.PUBLIC
+                            name = "INNER_CLASS"
+                        })})}
+
+        runTest(
+            module,
+            "testData/empty_class_inside_class"
+        )
+    }
+
     private fun runTest(module: SirModule, goldenDataFile: String) {
         val expectedSwiftSrc = File(KtTestUtil.getHomeDirectory()).resolve("$goldenDataFile.golden.swift")
 
diff --git a/native/swift/sir/gen/org/jetbrains/kotlin/sir/SirClass.kt b/native/swift/sir/gen/org/jetbrains/kotlin/sir/SirClass.kt
new file mode 100644
index 0000000..6e3813f
--- /dev/null
+++ b/native/swift/sir/gen/org/jetbrains/kotlin/sir/SirClass.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+// This file was generated automatically. See native/swift/sir/tree-generator/Readme.md.
+// DO NOT MODIFY IT MANUALLY.
+
+package org.jetbrains.kotlin.sir
+
+import org.jetbrains.kotlin.sir.visitors.SirTransformer
+import org.jetbrains.kotlin.sir.visitors.SirVisitor
+
+/**
+ * Generated from: [org.jetbrains.kotlin.sir.tree.generator.SwiftIrTree.class]
+ */
+abstract class SirClass : SirDeclarationContainer(), SirNamedDeclaration {
+    abstract override val origin: SirOrigin
+    abstract override val visibility: SirVisibility
+    abstract override var parent: SirDeclarationParent
+    abstract override val name: String
+    abstract override val declarations: List<SirDeclaration>
+
+    override fun <R, D> accept(visitor: SirVisitor<R, D>, data: D): R =
+        visitor.visitClass(this, data)
+
+    @Suppress("UNCHECKED_CAST")
+    override fun <E : SirElement, D> transform(transformer: SirTransformer<D>, data: D): E =
+        transformer.transformClass(this, data) as E
+}
diff --git a/native/swift/sir/gen/org/jetbrains/kotlin/sir/builder/SirClassBuilder.kt b/native/swift/sir/gen/org/jetbrains/kotlin/sir/builder/SirClassBuilder.kt
new file mode 100644
index 0000000..9b19332
--- /dev/null
+++ b/native/swift/sir/gen/org/jetbrains/kotlin/sir/builder/SirClassBuilder.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+// This file was generated automatically. See native/swift/sir/tree-generator/Readme.md.
+// DO NOT MODIFY IT MANUALLY.
+
+@file:Suppress("DuplicatedCode", "unused")
+
+package org.jetbrains.kotlin.sir.builder
+
+import kotlin.contracts.*
+import org.jetbrains.kotlin.sir.*
+import org.jetbrains.kotlin.sir.impl.SirClassImpl
+
+@SirBuilderDsl
+class SirClassBuilder {
+    var origin: SirOrigin = SirOrigin.Unknown
+    var visibility: SirVisibility = SirVisibility.PUBLIC
+    lateinit var name: String
+    val declarations: MutableList<SirDeclaration> = mutableListOf()
+
+    fun build(): SirClass {
+        return SirClassImpl(
+            origin,
+            visibility,
+            name,
+            declarations,
+        )
+    }
+}
+
+@OptIn(ExperimentalContracts::class)
+inline fun buildClass(init: SirClassBuilder.() -> Unit): SirClass {
+    contract {
+        callsInPlace(init, InvocationKind.EXACTLY_ONCE)
+    }
+    return SirClassBuilder().apply(init).build()
+}
+
+@OptIn(ExperimentalContracts::class)
+inline fun buildClassCopy(original: SirClass, init: SirClassBuilder.() -> Unit): SirClass {
+    contract {
+        callsInPlace(init, InvocationKind.EXACTLY_ONCE)
+    }
+    val copyBuilder = SirClassBuilder()
+    copyBuilder.origin = original.origin
+    copyBuilder.visibility = original.visibility
+    copyBuilder.name = original.name
+    copyBuilder.declarations.addAll(original.declarations)
+    return copyBuilder.apply(init).build()
+}
diff --git a/native/swift/sir/gen/org/jetbrains/kotlin/sir/impl/SirClassImpl.kt b/native/swift/sir/gen/org/jetbrains/kotlin/sir/impl/SirClassImpl.kt
new file mode 100644
index 0000000..da3b46b
--- /dev/null
+++ b/native/swift/sir/gen/org/jetbrains/kotlin/sir/impl/SirClassImpl.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+// This file was generated automatically. See native/swift/sir/tree-generator/Readme.md.
+// DO NOT MODIFY IT MANUALLY.
+
+@file:Suppress("DuplicatedCode", "unused")
+
+package org.jetbrains.kotlin.sir.impl
+
+import org.jetbrains.kotlin.sir.*
+import org.jetbrains.kotlin.sir.util.transformInPlace
+import org.jetbrains.kotlin.sir.visitors.SirTransformer
+import org.jetbrains.kotlin.sir.visitors.SirVisitor
+
+internal class SirClassImpl(
+    override val origin: SirOrigin,
+    override val visibility: SirVisibility,
+    override val name: String,
+    override val declarations: MutableList<SirDeclaration>,
+) : SirClass() {
+    override lateinit var parent: SirDeclarationParent
+
+    override fun <R, D> acceptChildren(visitor: SirVisitor<R, D>, data: D) {
+        declarations.forEach { it.accept(visitor, data) }
+    }
+
+    override fun <D> transformChildren(transformer: SirTransformer<D>, data: D) {
+        declarations.transformInPlace(transformer, data)
+    }
+}
diff --git a/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirTransformer.kt b/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirTransformer.kt
index 31074249..d48bdb7 100644
--- a/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirTransformer.kt
+++ b/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirTransformer.kt
@@ -71,6 +71,14 @@
         return transformStruct(struct, data)
     }
 
+    open fun transformClass(klass: SirClass, data: D): SirDeclaration {
+        return transformNamedDeclaration(klass, data)
+    }
+
+    final override fun visitClass(klass: SirClass, data: D): SirDeclaration {
+        return transformClass(klass, data)
+    }
+
     open fun transformCallable(callable: SirCallable, data: D): SirDeclaration {
         return transformDeclaration(callable, data)
     }
diff --git a/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirTransformerVoid.kt b/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirTransformerVoid.kt
index 1da2838..f6c7a74 100644
--- a/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirTransformerVoid.kt
+++ b/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirTransformerVoid.kt
@@ -56,6 +56,12 @@
     final override fun transformStruct(struct: SirStruct, data: Nothing?): SirDeclaration =
         transformStruct(struct)
 
+    open fun transformClass(klass: SirClass): SirDeclaration =
+        transformNamedDeclaration(klass)
+
+    final override fun transformClass(klass: SirClass, data: Nothing?): SirDeclaration =
+        transformClass(klass)
+
     open fun transformCallable(callable: SirCallable): SirDeclaration =
         transformDeclaration(callable)
 
diff --git a/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirVisitor.kt b/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirVisitor.kt
index 75c38a9..f32935c 100644
--- a/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirVisitor.kt
+++ b/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirVisitor.kt
@@ -35,6 +35,9 @@
     open fun visitStruct(struct: SirStruct, data: D): R =
         visitNamedDeclaration(struct, data)
 
+    open fun visitClass(klass: SirClass, data: D): R =
+        visitNamedDeclaration(klass, data)
+
     open fun visitCallable(callable: SirCallable, data: D): R =
         visitDeclaration(callable, data)
 
diff --git a/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirVisitorVoid.kt b/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirVisitorVoid.kt
index 3f70fd3..bf23d17 100644
--- a/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirVisitorVoid.kt
+++ b/native/swift/sir/gen/org/jetbrains/kotlin/sir/visitors/SirVisitorVoid.kt
@@ -70,6 +70,14 @@
         visitNamedDeclaration(struct)
     }
 
+    final override fun visitClass(klass: SirClass, data: Nothing?) {
+        visitClass(klass)
+    }
+
+    open fun visitClass(klass: SirClass) {
+        visitNamedDeclaration(klass)
+    }
+
     final override fun visitCallable(callable: SirCallable, data: Nothing?) {
         visitCallable(callable)
     }
diff --git a/native/swift/sir/tree-generator/src/org/jetbrains/kotlin/sir/tree/generator/SwiftIrTree.kt b/native/swift/sir/tree-generator/src/org/jetbrains/kotlin/sir/tree/generator/SwiftIrTree.kt
index 3d44fb6..17a688e 100644
--- a/native/swift/sir/tree-generator/src/org/jetbrains/kotlin/sir/tree/generator/SwiftIrTree.kt
+++ b/native/swift/sir/tree-generator/src/org/jetbrains/kotlin/sir/tree/generator/SwiftIrTree.kt
@@ -68,6 +68,12 @@
         parent(declarationContainer)
     }
 
+    val `class`: Element by element {
+        customParentInVisitor = namedDeclaration
+        parent(namedDeclaration)
+        parent(declarationContainer)
+    }
+
     val callable by sealedElement {
         parent(declaration)
     }
diff --git a/native/swift/swift-export-standalone/testData/classes/golden_result/result.h b/native/swift/swift-export-standalone/testData/classes/golden_result/result.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/native/swift/swift-export-standalone/testData/classes/golden_result/result.h
diff --git a/native/swift/swift-export-standalone/testData/classes/golden_result/result.kt b/native/swift/swift-export-standalone/testData/classes/golden_result/result.kt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/native/swift/swift-export-standalone/testData/classes/golden_result/result.kt
diff --git a/native/swift/swift-export-standalone/testData/classes/golden_result/result.swift b/native/swift/swift-export-standalone/testData/classes/golden_result/result.swift
new file mode 100644
index 0000000..0942b6a
--- /dev/null
+++ b/native/swift/swift-export-standalone/testData/classes/golden_result/result.swift
@@ -0,0 +1,4 @@
+import KotlinBridges
+
+public class Foo {
+}
diff --git a/native/swift/swift-export-standalone/testData/classes/input_root/classes.kt b/native/swift/swift-export-standalone/testData/classes/input_root/classes.kt
new file mode 100644
index 0000000..ee51ea2
--- /dev/null
+++ b/native/swift/swift-export-standalone/testData/classes/input_root/classes.kt
@@ -0,0 +1,4 @@
+
+class Foo {
+    class INSIDE_CLASS // this should be ignored currently
+}
\ No newline at end of file
diff --git a/native/swift/swift-export-standalone/testData/classes/input_root/should_be_ignored.kt b/native/swift/swift-export-standalone/testData/classes/input_root/should_be_ignored.kt
new file mode 100644
index 0000000..358bb02
--- /dev/null
+++ b/native/swift/swift-export-standalone/testData/classes/input_root/should_be_ignored.kt
@@ -0,0 +1,28 @@
+public annotation class OptIn
+
+enum ENUM {
+    class INSIDE_ENUM
+}
+
+interface OUTSIDE_PROTO {
+    class INSIDE_PROTO
+}
+
+class INHERITANCE_COUPLE : OUTSIDE_PROTO.INSIDE_PROTO, OUTSIDE_PROTO
+class INHERITANCE_SINGLE_PROTO : OUTSIDE_PROTO.INSIDE_PROTO
+
+open class OPEN_CLASS
+
+class INHERITANCE_SINGLE_CLASS : OPEN_CLASS
+
+object OBJECT
+
+data class DATA_CLASS(val a: Int)
+
+inline class INLINE_CLASS(val a: Int)
+
+abstract class ABSTRACT_CLASS
+
+sealed class SEALED {
+    object O : SEALED()
+}
\ No newline at end of file
diff --git a/native/swift/swift-export-standalone/testData/classes/input_root/should_be_ignored_packaged.kt b/native/swift/swift-export-standalone/testData/classes/input_root/should_be_ignored_packaged.kt
new file mode 100644
index 0000000..f94b8a0
--- /dev/null
+++ b/native/swift/swift-export-standalone/testData/classes/input_root/should_be_ignored_packaged.kt
@@ -0,0 +1,3 @@
+package namespace
+
+class NAMESPACED_CLASS
diff --git a/native/swift/swift-export-standalone/tests-gen/org/jetbrains/kotlin/swiftexport/standalone/SwiftExportRunnerTest.java b/native/swift/swift-export-standalone/tests-gen/org/jetbrains/kotlin/swiftexport/standalone/SwiftExportRunnerTest.java
index e63f864..760e46f 100644
--- a/native/swift/swift-export-standalone/tests-gen/org/jetbrains/kotlin/swiftexport/standalone/SwiftExportRunnerTest.java
+++ b/native/swift/swift-export-standalone/tests-gen/org/jetbrains/kotlin/swiftexport/standalone/SwiftExportRunnerTest.java
@@ -25,6 +25,12 @@
   }
 
   @Test
+  @TestMetadata("classes")
+  public void testClasses() {
+    runTest("native/swift/swift-export-standalone/testData/classes/");
+  }
+
+  @Test
   @TestMetadata("documentation")
   public void testDocumentation() {
     runTest("native/swift/swift-export-standalone/testData/documentation/");