diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeExtBlackBoxTestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeExtBlackBoxTestGenerated.java index 4606e983152..cdc05dacaad 100644 --- a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeExtBlackBoxTestGenerated.java +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeExtBlackBoxTestGenerated.java @@ -7,7 +7,7 @@ package org.jetbrains.kotlin.konan.blackboxtest; import com.intellij.testFramework.TestDataPath; import org.jetbrains.kotlin.test.util.KtTestUtil; -import org.jetbrains.kotlin.konan.blackboxtest.support.NativeBlackBoxTestCaseGroupProvider; +import org.jetbrains.kotlin.konan.blackboxtest.support.group.NativeBlackBoxTestCaseGroupProvider; import org.jetbrains.kotlin.konan.blackboxtest.support.group.ExtTestCaseGroupProvider; import org.jetbrains.kotlin.test.TestMetadata; import org.junit.jupiter.api.Nested; diff --git a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeBlackboxTests.kt b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeBlackboxTests.kt index 3036bac11f7..a49c5dde152 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeBlackboxTests.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeBlackboxTests.kt @@ -8,7 +8,7 @@ package org.jetbrains.kotlin.generators.tests import org.jetbrains.kotlin.generators.generateTestGroupSuiteWithJUnit5 import org.jetbrains.kotlin.generators.model.annotation import org.jetbrains.kotlin.konan.blackboxtest.AbstractNativeBlackBoxTest -import org.jetbrains.kotlin.konan.blackboxtest.support.NativeBlackBoxTestCaseGroupProvider +import org.jetbrains.kotlin.konan.blackboxtest.support.group.NativeBlackBoxTestCaseGroupProvider import org.jetbrains.kotlin.konan.blackboxtest.support.group.ExtTestCaseGroupProvider fun main() { diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeBlackBoxTestSupport.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeBlackBoxTestSupport.kt index db7e23b73dc..b622b4940a2 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeBlackBoxTestSupport.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeBlackBoxTestSupport.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.konan.blackboxtest.support import org.jetbrains.kotlin.konan.blackboxtest.AbstractNativeBlackBoxTest +import org.jetbrains.kotlin.konan.blackboxtest.support.group.NativeBlackBoxTestCaseGroupProvider import org.jetbrains.kotlin.konan.blackboxtest.support.group.StandardTestCaseGroupProvider import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider import org.jetbrains.kotlin.konan.blackboxtest.support.util.* diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeBlackBoxTestCaseGroupProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/NativeBlackBoxTestCaseGroupProvider.kt similarity index 84% rename from native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeBlackBoxTestCaseGroupProvider.kt rename to native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/NativeBlackBoxTestCaseGroupProvider.kt index 7d720dc766b..5ed605b2f83 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeBlackBoxTestCaseGroupProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/NativeBlackBoxTestCaseGroupProvider.kt @@ -3,7 +3,7 @@ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -package org.jetbrains.kotlin.konan.blackboxtest.support +package org.jetbrains.kotlin.konan.blackboxtest.support.group import kotlin.reflect.KClass diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/StandardTestCaseGroupProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/StandardTestCaseGroupProvider.kt new file mode 100644 index 00000000000..09d1784a0ee --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/StandardTestCaseGroupProvider.kt @@ -0,0 +1,264 @@ +/* + * Copyright 2010-2021 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. + */ + +package org.jetbrains.kotlin.konan.blackboxtest.support.group + +import org.jetbrains.kotlin.konan.blackboxtest.support.* +import org.jetbrains.kotlin.konan.blackboxtest.support.util.DEFAULT_FILE_NAME +import org.jetbrains.kotlin.konan.blackboxtest.support.util.ThreadSafeFactory +import org.jetbrains.kotlin.konan.blackboxtest.support.util.computeGeneratedSourcesDir +import org.jetbrains.kotlin.konan.blackboxtest.support.util.computePackageName +import org.jetbrains.kotlin.test.directives.model.Directive +import org.jetbrains.kotlin.test.services.JUnit5Assertions +import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertEquals +import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertFalse +import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertNotEquals +import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue +import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail +import org.jetbrains.kotlin.test.services.impl.RegisteredDirectivesParser +import java.io.File + +internal class StandardTestCaseGroupProvider(private val environment: TestEnvironment) : TestCaseGroupProvider { + // Load test cases in groups on demand. + private val lazyTestCaseGroups = ThreadSafeFactory { testDataDir -> + val testDataFiles = testDataDir.listFiles() + ?: return@ThreadSafeFactory null // `null` means that there is no such testDataDir. + + val testCases = testDataFiles.mapNotNull { testDataFile -> + if (!testDataFile.isFile || testDataFile.extension != "kt") + return@mapNotNull null + + createTestCase(testDataFile) + } + + TestCaseGroup.Default(disabledTestDataFileNames = emptySet(), testCases = testCases) + } + + override fun getTestCaseGroup(testDataDir: File) = lazyTestCaseGroups[testDataDir] + + private fun createTestCase(testDataFile: File): TestCase { + val generatedSourcesDir = computeGeneratedSourcesDir( + testDataBaseDir = environment.testRoots.baseDir, + testDataFile = testDataFile, + generatedSourcesBaseDir = environment.testSourcesDir + ) + + val effectivePackageName = computePackageName( + testDataBaseDir = environment.testRoots.baseDir, + testDataFile = testDataFile + ) + + val testModules = hashMapOf() + var currentTestModule: TestModule.Exclusive? = null + + var currentTestFileName: String? = null + val currentTestFileText = StringBuilder() + + val directivesParser = RegisteredDirectivesParser(TestDirectives, JUnit5Assertions) + var lastParsedDirective: Directive? = null + + fun switchTestModule(newTestModule: TestModule.Exclusive, location: Location): TestModule.Exclusive { + // Don't register new test module if there is another one with the same name. + val testModule = testModules.getOrPut(newTestModule.name) { newTestModule } + assertTrue(testModule === newTestModule || testModule.haveSameSymbols(newTestModule)) { + """ + $location: Two declarations of the same module with different dependencies or friends found: + $testModule + $newTestModule + """.trimIndent() + } + + currentTestModule = testModule + return testModule + } + + fun beginTestFile(fileName: String) { + assertEquals(null, currentTestFileName) + currentTestFileName = fileName + } + + fun finishTestFile(forceFinish: Boolean, location: Location) { + val needToFinish = forceFinish + || currentTestFileName != null + || (currentTestFileName == null /*&& testFiles.isEmpty()*/ && currentTestFileText.hasAnythingButComments()) + + if (needToFinish) { + val fileName = currentTestFileName ?: DEFAULT_FILE_NAME + val testModule = currentTestModule ?: switchTestModule(TestModule.newDefaultModule(), location) + + testModule.files += TestFile.createUncommitted( + location = generatedSourcesDir.resolve(testModule.name).resolve(fileName), + module = testModule, + text = currentTestFileText + ) + + currentTestFileText.clear() + repeat(location.lineNumber ?: 0) { currentTestFileText.appendLine() } + currentTestFileName = null + } + } + + testDataFile.readLines().forEachIndexed { lineNumber, line -> + val location = Location(testDataFile, lineNumber) + val expectFileDirectiveAfterModuleDirective = + lastParsedDirective == TestDirectives.MODULE // Only FILE directive may follow MODULE directive. + + val rawDirective = RegisteredDirectivesParser.parseDirective(line) + if (rawDirective != null) { + val parsedDirective = try { + directivesParser.convertToRegisteredDirective(rawDirective) + } catch (e: AssertionError) { + // Enhance error message with concrete test data file and line number where the error has happened. + throw AssertionError( + """ + $location: Error while parsing directive in test data file. + Cause: ${e.message} + """.trimIndent(), + e + ) + } + + if (parsedDirective != null) { + when (val directive = parsedDirective.directive) { + TestDirectives.FILE -> { + val newFileName = parseFileName(parsedDirective, location) + finishTestFile(forceFinish = false, location) + beginTestFile(newFileName) + } + else -> { + assertFalse(expectFileDirectiveAfterModuleDirective) { + "$location: Directive $directive encountered after ${TestDirectives.MODULE} directive but was expecting ${TestDirectives.FILE}" + } + + when (directive) { + TestDirectives.MODULE -> { + finishTestFile(forceFinish = false, location) + switchTestModule(parseModule(parsedDirective, location), location) + } + else -> { + assertNotEquals(TestDirectives.FILE, lastParsedDirective) { + "$location: Global directive $directive encountered after ${TestDirectives.FILE} directive" + } + assertNotEquals(TestDirectives.MODULE, lastParsedDirective) { + "$location: Global directive $directive encountered after ${TestDirectives.MODULE} directive" + } + + directivesParser.addParsedDirective(parsedDirective) + } + } + } + } + + currentTestFileText.appendLine() + lastParsedDirective = parsedDirective.directive + return@forEachIndexed + } + } + + if (expectFileDirectiveAfterModuleDirective) { + // Was expecting a line with the FILE directive as this is the only possible continuation of a line with + // the MODULE directive, but failed. + fail { "$location: ${TestDirectives.FILE} directive expected after ${TestDirectives.MODULE} directive" } + } + + currentTestFileText.appendLine(line) + } + + val location = Location(testDataFile) + finishTestFile(forceFinish = true, location) + + val registeredDirectives = directivesParser.build() + + val freeCompilerArgs = parseFreeCompilerArgs(registeredDirectives, location) + val expectedOutputDataFile = parseOutputDataFile(baseDir = testDataFile.parentFile, registeredDirectives, location) + val testKind = parseTestKind(registeredDirectives, location) + + if (testKind == TestKind.REGULAR) { + // Fix package declarations to avoid unintended conflicts between symbols with the same name in different test cases. + testModules.values.forEach { testModule -> + testModule.files.forEach { testFile -> fixPackageDeclaration(testFile, effectivePackageName, testDataFile) } + } + } + + val testCase = TestCase( + kind = testKind, + modules = testModules.values.toSet(), + freeCompilerArgs = freeCompilerArgs, + origin = TestOrigin.SingleTestDataFile(testDataFile), + nominalPackageName = effectivePackageName, + expectedOutputDataFile = expectedOutputDataFile, + extras = if (testKind == TestKind.STANDALONE_NO_TR) { + TestCase.StandaloneNoTestRunnerExtras( + entryPoint = parseEntryPoint(registeredDirectives, location), + inputDataFile = parseInputDataFile(baseDir = testDataFile.parentFile, registeredDirectives, location) + ) + } else + null + ) + testCase.initialize(findSharedModule = null) + + return testCase + } + + private fun CharSequence.hasAnythingButComments(): Boolean { + var result = false + runForFirstMeaningfulStatement { _, _ -> result = true } + return result + } + + private fun fixPackageDeclaration( + testFile: TestFile, + packageName: PackageName, + testDataFile: File + ) = testFile.update { text -> + var existingPackageDeclarationLine: String? = null + var existingPackageDeclarationLineNumber: Int? = null + + text.runForFirstMeaningfulStatement { lineNumber, line -> + // First meaningful line. + val trimmedLine = line.trim() + if (trimmedLine.startsWith("package ")) { + existingPackageDeclarationLine = trimmedLine + existingPackageDeclarationLineNumber = lineNumber + } + } + + if (existingPackageDeclarationLine != null) { + val existingPackageName = existingPackageDeclarationLine!!.substringAfter("package ").trimStart() + assertTrue( + existingPackageName == packageName + || (existingPackageName.length > packageName.length + && existingPackageName.startsWith(packageName) + && existingPackageName[packageName.length] == '.') + ) { + val location = Location(testDataFile, existingPackageDeclarationLineNumber) + """ + $location: Invalid package name declaration found: $existingPackageDeclarationLine + Expected: package $packageName + """.trimIndent() + } + text + } else + "package $packageName $text" + } + + private inline fun CharSequence.runForFirstMeaningfulStatement(action: (lineNumber: Int, line: String) -> Unit) { + var inMultilineComment = false + + for ((lineNumber, line) in lines().withIndex()) { + val trimmedLine = line.trim() + when { + inMultilineComment -> inMultilineComment = !trimmedLine.endsWith("*/") + trimmedLine.startsWith("/*") -> inMultilineComment = true + trimmedLine.isMeaningfulLine() -> { + action(lineNumber, line) + break + } + } + } + } + + private fun String.isMeaningfulLine() = isNotEmpty() && !startsWith("//") && !startsWith("@file:") +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/TestCaseGroupProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/TestCaseGroupProvider.kt index 38a0b8595b9..89a0829e387 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/TestCaseGroupProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/TestCaseGroupProvider.kt @@ -5,264 +5,9 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.group -import org.jetbrains.kotlin.konan.blackboxtest.support.* -import org.jetbrains.kotlin.konan.blackboxtest.support.util.DEFAULT_FILE_NAME -import org.jetbrains.kotlin.konan.blackboxtest.support.util.ThreadSafeFactory -import org.jetbrains.kotlin.konan.blackboxtest.support.util.computeGeneratedSourcesDir -import org.jetbrains.kotlin.konan.blackboxtest.support.util.computePackageName -import org.jetbrains.kotlin.test.directives.model.Directive -import org.jetbrains.kotlin.test.services.JUnit5Assertions -import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertEquals -import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertFalse -import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertNotEquals -import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue -import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail -import org.jetbrains.kotlin.test.services.impl.RegisteredDirectivesParser +import org.jetbrains.kotlin.konan.blackboxtest.support.TestCaseGroup import java.io.File internal interface TestCaseGroupProvider { fun getTestCaseGroup(testDataDir: File): TestCaseGroup? } - -internal class StandardTestCaseGroupProvider(private val environment: TestEnvironment) : TestCaseGroupProvider { - // Load test cases in groups on demand. - private val lazyTestCaseGroups = ThreadSafeFactory { testDataDir -> - val testDataFiles = testDataDir.listFiles() - ?: return@ThreadSafeFactory null // `null` means that there is no such testDataDir. - - val testCases = testDataFiles.mapNotNull { testDataFile -> - if (!testDataFile.isFile || testDataFile.extension != "kt") - return@mapNotNull null - - createTestCase(testDataFile) - } - - TestCaseGroup.Default(disabledTestDataFileNames = emptySet(), testCases = testCases) - } - - override fun getTestCaseGroup(testDataDir: File) = lazyTestCaseGroups[testDataDir] - - private fun createTestCase(testDataFile: File): TestCase { - val generatedSourcesDir = computeGeneratedSourcesDir( - testDataBaseDir = environment.testRoots.baseDir, - testDataFile = testDataFile, - generatedSourcesBaseDir = environment.testSourcesDir - ) - - val effectivePackageName = computePackageName( - testDataBaseDir = environment.testRoots.baseDir, - testDataFile = testDataFile - ) - - val testModules = hashMapOf() - var currentTestModule: TestModule.Exclusive? = null - - var currentTestFileName: String? = null - val currentTestFileText = StringBuilder() - - val directivesParser = RegisteredDirectivesParser(TestDirectives, JUnit5Assertions) - var lastParsedDirective: Directive? = null - - fun switchTestModule(newTestModule: TestModule.Exclusive, location: Location): TestModule.Exclusive { - // Don't register new test module if there is another one with the same name. - val testModule = testModules.getOrPut(newTestModule.name) { newTestModule } - assertTrue(testModule === newTestModule || testModule.haveSameSymbols(newTestModule)) { - """ - $location: Two declarations of the same module with different dependencies or friends found: - $testModule - $newTestModule - """.trimIndent() - } - - currentTestModule = testModule - return testModule - } - - fun beginTestFile(fileName: String) { - assertEquals(null, currentTestFileName) - currentTestFileName = fileName - } - - fun finishTestFile(forceFinish: Boolean, location: Location) { - val needToFinish = forceFinish - || currentTestFileName != null - || (currentTestFileName == null /*&& testFiles.isEmpty()*/ && currentTestFileText.hasAnythingButComments()) - - if (needToFinish) { - val fileName = currentTestFileName ?: DEFAULT_FILE_NAME - val testModule = currentTestModule ?: switchTestModule(TestModule.newDefaultModule(), location) - - testModule.files += TestFile.createUncommitted( - location = generatedSourcesDir.resolve(testModule.name).resolve(fileName), - module = testModule, - text = currentTestFileText - ) - - currentTestFileText.clear() - repeat(location.lineNumber ?: 0) { currentTestFileText.appendLine() } - currentTestFileName = null - } - } - - testDataFile.readLines().forEachIndexed { lineNumber, line -> - val location = Location(testDataFile, lineNumber) - val expectFileDirectiveAfterModuleDirective = - lastParsedDirective == TestDirectives.MODULE // Only FILE directive may follow MODULE directive. - - val rawDirective = RegisteredDirectivesParser.parseDirective(line) - if (rawDirective != null) { - val parsedDirective = try { - directivesParser.convertToRegisteredDirective(rawDirective) - } catch (e: AssertionError) { - // Enhance error message with concrete test data file and line number where the error has happened. - throw AssertionError( - """ - $location: Error while parsing directive in test data file. - Cause: ${e.message} - """.trimIndent(), - e - ) - } - - if (parsedDirective != null) { - when (val directive = parsedDirective.directive) { - TestDirectives.FILE -> { - val newFileName = parseFileName(parsedDirective, location) - finishTestFile(forceFinish = false, location) - beginTestFile(newFileName) - } - else -> { - assertFalse(expectFileDirectiveAfterModuleDirective) { - "$location: Directive $directive encountered after ${TestDirectives.MODULE} directive but was expecting ${TestDirectives.FILE}" - } - - when (directive) { - TestDirectives.MODULE -> { - finishTestFile(forceFinish = false, location) - switchTestModule(parseModule(parsedDirective, location), location) - } - else -> { - assertNotEquals(TestDirectives.FILE, lastParsedDirective) { - "$location: Global directive $directive encountered after ${TestDirectives.FILE} directive" - } - assertNotEquals(TestDirectives.MODULE, lastParsedDirective) { - "$location: Global directive $directive encountered after ${TestDirectives.MODULE} directive" - } - - directivesParser.addParsedDirective(parsedDirective) - } - } - } - } - - currentTestFileText.appendLine() - lastParsedDirective = parsedDirective.directive - return@forEachIndexed - } - } - - if (expectFileDirectiveAfterModuleDirective) { - // Was expecting a line with the FILE directive as this is the only possible continuation of a line with - // the MODULE directive, but failed. - fail { "$location: ${TestDirectives.FILE} directive expected after ${TestDirectives.MODULE} directive" } - } - - currentTestFileText.appendLine(line) - } - - val location = Location(testDataFile) - finishTestFile(forceFinish = true, location) - - val registeredDirectives = directivesParser.build() - - val freeCompilerArgs = parseFreeCompilerArgs(registeredDirectives, location) - val expectedOutputDataFile = parseOutputDataFile(baseDir = testDataFile.parentFile, registeredDirectives, location) - val testKind = parseTestKind(registeredDirectives, location) - - if (testKind == TestKind.REGULAR) { - // Fix package declarations to avoid unintended conflicts between symbols with the same name in different test cases. - testModules.values.forEach { testModule -> - testModule.files.forEach { testFile -> fixPackageDeclaration(testFile, effectivePackageName, testDataFile) } - } - } - - val testCase = TestCase( - kind = testKind, - modules = testModules.values.toSet(), - freeCompilerArgs = freeCompilerArgs, - origin = TestOrigin.SingleTestDataFile(testDataFile), - nominalPackageName = effectivePackageName, - expectedOutputDataFile = expectedOutputDataFile, - extras = if (testKind == TestKind.STANDALONE_NO_TR) { - TestCase.StandaloneNoTestRunnerExtras( - entryPoint = parseEntryPoint(registeredDirectives, location), - inputDataFile = parseInputDataFile(baseDir = testDataFile.parentFile, registeredDirectives, location) - ) - } else - null - ) - testCase.initialize(findSharedModule = null) - - return testCase - } - - private fun CharSequence.hasAnythingButComments(): Boolean { - var result = false - runForFirstMeaningfulStatement { _, _ -> result = true } - return result - } - - private fun fixPackageDeclaration( - testFile: TestFile, - packageName: PackageName, - testDataFile: File - ) = testFile.update { text -> - var existingPackageDeclarationLine: String? = null - var existingPackageDeclarationLineNumber: Int? = null - - text.runForFirstMeaningfulStatement { lineNumber, line -> - // First meaningful line. - val trimmedLine = line.trim() - if (trimmedLine.startsWith("package ")) { - existingPackageDeclarationLine = trimmedLine - existingPackageDeclarationLineNumber = lineNumber - } - } - - if (existingPackageDeclarationLine != null) { - val existingPackageName = existingPackageDeclarationLine!!.substringAfter("package ").trimStart() - assertTrue( - existingPackageName == packageName - || (existingPackageName.length > packageName.length - && existingPackageName.startsWith(packageName) - && existingPackageName[packageName.length] == '.') - ) { - val location = Location(testDataFile, existingPackageDeclarationLineNumber) - """ - $location: Invalid package name declaration found: $existingPackageDeclarationLine - Expected: package $packageName - """.trimIndent() - } - text - } else - "package $packageName $text" - } - - private inline fun CharSequence.runForFirstMeaningfulStatement(action: (lineNumber: Int, line: String) -> Unit) { - var inMultilineComment = false - - for ((lineNumber, line) in lines().withIndex()) { - val trimmedLine = line.trim() - when { - inMultilineComment -> inMultilineComment = !trimmedLine.endsWith("*/") - trimmedLine.startsWith("/*") -> inMultilineComment = true - trimmedLine.isMeaningfulLine() -> { - action(lineNumber, line) - break - } - } - } - } - - private fun String.isMeaningfulLine() = isNotEmpty() && !startsWith("//") && !startsWith("@file:") -}