diff --git a/save-plugins/fix-and-warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPlugin.kt b/save-plugins/fix-and-warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPlugin.kt index 44143b341..dff2ce3cf 100644 --- a/save-plugins/fix-and-warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPlugin.kt +++ b/save-plugins/fix-and-warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPlugin.kt @@ -9,6 +9,7 @@ import org.cqfn.save.core.result.Pass import org.cqfn.save.core.result.TestResult import org.cqfn.save.plugin.warn.WarnPlugin import org.cqfn.save.plugin.warn.WarnPluginConfig +import org.cqfn.save.plugin.warn.utils.getLineNumber import org.cqfn.save.plugins.fix.FixPlugin import org.cqfn.save.plugins.fix.FixPluginConfig @@ -34,12 +35,12 @@ class FixAndWarnPlugin( fileSystem, useInternalRedirections, redirectTo) { - private val fixPluginConfig: FixPluginConfig = - testConfig.pluginConfigs.filterIsInstance().single().fix - private val warnPluginConfig: WarnPluginConfig = - testConfig.pluginConfigs.filterIsInstance().single().warn + private val fixAndWarnPluginConfig: FixAndWarnPluginConfig = testConfig.pluginConfigs.filterIsInstance().single() + private val fixPluginConfig: FixPluginConfig = fixAndWarnPluginConfig.fix + private val warnPluginConfig: WarnPluginConfig = fixAndWarnPluginConfig.warn private val generalConfig: GeneralConfig = testConfig.pluginConfigs.filterIsInstance().single() + private val inlineFixer = fixAndWarnPluginConfig.inlineFixer private lateinit var fixPlugin: FixPlugin private lateinit var warnPlugin: WarnPlugin @@ -69,12 +70,19 @@ class FixAndWarnPlugin( // Need to update private fields after validation initOrUpdateConfigs() val testFilePattern = warnPluginConfig.resourceNamePattern - val expectedFiles = files.filterTestResources(testFilePattern, match = false) + + val newFiles = if (inlineFixer == true) { + replaceFixLine(files) + } else { + files + } + + val expectedFiles = newFiles.filterTestResources(testFilePattern, match = false) // Remove (in place) warnings from test files before fix plugin execution val filesAndTheirWarningsMap = removeWarningsFromExpectedFiles(expectedFiles) - val fixTestResults = fixPlugin.handleFiles(files).toList() + val fixTestResults = fixPlugin.handleFiles(newFiles).toList() val (fixTestResultsPassed, fixTestResultsFailed) = fixTestResults.partition { it.status is Pass } @@ -168,6 +176,61 @@ class FixAndWarnPlugin( } } + @Suppress( + "TOO_LONG_FUNCTION", + "TOO_MANY_LINES_IN_LAMBDA" + ) + private fun replaceFixLine(files: Sequence>): Sequence> { + val newFiles: MutableList> = mutableListOf() + files.map { file -> file.single() }.map { + val fileCopy = createTestFile(it) + val fileCopyWarning = createTestFile(it) + val linesFile = fs.readLines(it) as MutableList + linesFile.mapIndexed { index, line -> + if (generalConfig.expectedWarningsPattern!!.matches(line)) { + val lineNumber = line.getLineNumber( + generalConfig.expectedWarningsPattern!!, + warnPluginConfig.lineCaptureGroup, + warnPluginConfig.linePlaceholder!!, + index + 1, + it, + linesFile, + ) + val newLine = fixAndWarnPluginConfig.checkFixesPattern!!.find(linesFile[index + 1])?.groups?.get(1) + ?.value + newLine?.let { + lineNumber?.let { + linesFile.removeAt(lineNumber - 1) + linesFile.add(lineNumber - 1, newLine) + } + } + } + } + linesFile.map { + if (!generalConfig.expectedWarningsPattern!!.matches(it) && !fixAndWarnPluginConfig.checkFixesPattern!!.matches(it)) { + fs.write(fileCopy) { + write((it + "\n").encodeToByteArray()) + } + } + } + linesFile.map { + if (!fixAndWarnPluginConfig.checkFixesPattern!!.matches(it)) { + fs.write(fileCopyWarning) { + write((it + "\n").encodeToByteArray()) + } + } + } + newFiles.add(listOf(fileCopy, fileCopyWarning)) + } + return newFiles.asSequence() + } + + private fun createTestFile(path: Path): Path { + val pathCopy: Path = constructPathForCopyOfTestFile(FixAndWarnPlugin::class.simpleName!!, path) + createTempDir(pathCopy.parent!!) + return pathCopy + } + override fun cleanupTempDir() { val tmpDir = (FileSystem.SYSTEM_TEMPORARY_DIRECTORY / FixAndWarnPlugin::class.simpleName!!) if (fs.exists(tmpDir)) { diff --git a/save-plugins/fix-and-warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPluginConfig.kt b/save-plugins/fix-and-warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPluginConfig.kt index 406696d9e..4e40b695b 100644 --- a/save-plugins/fix-and-warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPluginConfig.kt +++ b/save-plugins/fix-and-warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPluginConfig.kt @@ -1,7 +1,11 @@ +@file:Suppress("HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE") +@file:UseSerializers(RegexSerializer::class) + package org.cqfn.save.plugins.fixandwarn import org.cqfn.save.core.config.TestConfigSections import org.cqfn.save.core.plugin.PluginConfig +import org.cqfn.save.core.utils.RegexSerializer import org.cqfn.save.plugin.warn.WarnPluginConfig import org.cqfn.save.plugins.fix.FixPluginConfig @@ -10,15 +14,20 @@ import okio.Path.Companion.toPath import kotlinx.serialization.Serializable import kotlinx.serialization.Transient +import kotlinx.serialization.UseSerializers /** * @property fix config for nested [fix] section * @property warn config for nested [warn] section + * @property inlineFixer + * @property checkFixesPattern */ @Serializable data class FixAndWarnPluginConfig( val fix: FixPluginConfig, - val warn: WarnPluginConfig + val warn: WarnPluginConfig, + val inlineFixer: Boolean? = null, + val checkFixesPattern: Regex? = null, ) : PluginConfig { override val type = TestConfigSections.`FIX AND WARN` @@ -31,7 +40,9 @@ data class FixAndWarnPluginConfig( val mergedWarnPluginConfig = warn.mergeWith(other.warn) return FixAndWarnPluginConfig( mergedFixPluginConfig as FixPluginConfig, - mergedWarnPluginConfig as WarnPluginConfig + mergedWarnPluginConfig as WarnPluginConfig, + this.inlineFixer ?: otherConfig.inlineFixer, + this.checkFixesPattern ?: other.checkFixesPattern, ) } @@ -43,9 +54,21 @@ data class FixAndWarnPluginConfig( [warn]: {${warn.testName}, ${warn.batchSize}} """ } + val newInlineFixer = inlineFixer ?: false + val newCheckFixerPattern = if (newInlineFixer) (checkFixesPattern ?: defaultCheckFixesPattern) else null return FixAndWarnPluginConfig( fix.validateAndSetDefaults(), - warn.validateAndSetDefaults() + warn.validateAndSetDefaults(), + newInlineFixer, + newCheckFixerPattern, ) } + + companion object { + /** + * Default regex for check fixes + * ```CHECK-FIXES: val foo = 42``` + */ + internal val defaultCheckFixesPattern = Regex("(.+): (.+)") + } } diff --git a/save-plugins/fix-and-warn-plugin/src/commonNonJsTest/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPluginTest.kt b/save-plugins/fix-and-warn-plugin/src/commonNonJsTest/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPluginTest.kt index 7783618c9..cc8a211d9 100644 --- a/save-plugins/fix-and-warn-plugin/src/commonNonJsTest/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPluginTest.kt +++ b/save-plugins/fix-and-warn-plugin/src/commonNonJsTest/kotlin/org/cqfn/save/plugins/fixandwarn/FixAndWarnPluginTest.kt @@ -81,7 +81,8 @@ class FixAndWarnPluginTest { WarnPluginConfig(warnExecutionCmd, Regex("(.+):(\\d+):(\\d+): (.+)"), true, true, 1, ", ", 1, 2, 3, 1, 2, 3, 4 - ) + ), + false, ), GeneralConfig("", listOf(""), "", "", expectedWarningsPattern = Regex("// ;warn:(\\d+):(\\d+): (.*)")) ), diff --git a/save-plugins/warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugin/warn/utils/Warning.kt b/save-plugins/warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugin/warn/utils/Warning.kt index 08eda3c51..386410fc2 100644 --- a/save-plugins/warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugin/warn/utils/Warning.kt +++ b/save-plugins/warn-plugin/src/commonMain/kotlin/org/cqfn/save/plugin/warn/utils/Warning.kt @@ -25,6 +25,57 @@ data class Warning( val fileName: String, ) +/** + * @param warningRegex regular expression for warning + * @param lineGroupIdx index of capture group for line number + * @param placeholder placeholder for line + * @param lineNum number of line + * @param file path to test file + * @param linesFile lines of file + * @return a [Warning] or null if [this] string doesn't match [warningRegex] + * @throws ResourceFormatException when parsing a file + */ +@Suppress( + "TooGenericExceptionCaught", + "LongParameterList", + "NestedBlockDepth", + "ReturnCount", + // fixme: add `cause` parameter to `PluginException` + "SwallowedException", + "TOO_MANY_PARAMETERS", + "AVOID_NULL_CHECKS", +) +fun String.getLineNumber(warningRegex: Regex, + lineGroupIdx: Long?, + placeholder: String, + lineNum: Int?, + file: Path?, + linesFile: List?, +): Int? { + if (lineGroupIdx == null) { + // line capture group is not configured in save.toml + return null + } + + val groups = warningRegex.find(this)?.groups ?: return null + val lineValue = groups[lineGroupIdx.toInt()]!!.value + return if (lineValue.isEmpty() && lineNum != null && linesFile != null) { + nextLineNotMatchingRegex(file!!, warningRegex, linesFile, lineNum) + } else { + lineValue.toIntOrNull() ?: run { + if (lineValue[0] != placeholder[0]) { + throw ResourceFormatException("The group <$lineValue> is neither a number nor a placeholder.") + } + try { + val adjustment = lineValue.substringAfterLast(placeholder) + lineNum!! + adjustment.ifBlank { "0" }.toInt() + } catch (e: Exception) { + throw ResourceFormatException("Could not extract line number from line [$this], cause: ${e.describe()}") + } + } + } +} + /** * Extract warning from [this] string using provided parameters * @@ -80,57 +131,6 @@ internal fun String.extractWarning(warningRegex: Regex, return extractWarning(warningRegex, fileName, line, columnGroupIdx, messageGroupIdx) } -/** - * @param warningRegex regular expression for warning - * @param lineGroupIdx index of capture group for line number - * @param placeholder placeholder for line - * @param lineNum number of line - * @param file path to test file - * @param linesFile lines of file - * @return a [Warning] or null if [this] string doesn't match [warningRegex] - * @throws ResourceFormatException when parsing a file - */ -@Suppress( - "TooGenericExceptionCaught", - "LongParameterList", - "NestedBlockDepth", - "ReturnCount", - // fixme: add `cause` parameter to `PluginException` - "SwallowedException", - "TOO_MANY_PARAMETERS", - "AVOID_NULL_CHECKS", -) -internal fun String.getLineNumber(warningRegex: Regex, - lineGroupIdx: Long?, - placeholder: String, - lineNum: Int?, - file: Path?, - linesFile: List?, -): Int? { - if (lineGroupIdx == null) { - // line capture group is not configured in save.toml - return null - } - - val groups = warningRegex.find(this)?.groups ?: return null - val lineValue = groups[lineGroupIdx.toInt()]!!.value - return if (lineValue.isEmpty() && lineNum != null && linesFile != null) { - nextLineNotMatchingRegex(file!!, warningRegex, linesFile, lineNum) - } else { - lineValue.toIntOrNull() ?: run { - if (lineValue[0] != placeholder[0]) { - throw ResourceFormatException("The group <$lineValue> is neither a number nor a placeholder.") - } - try { - val adjustment = lineValue.substringAfterLast(placeholder) - lineNum!! + adjustment.ifBlank { "0" }.toInt() - } catch (e: Exception) { - throw ResourceFormatException("Could not extract line number from line [$this], cause: ${e.describe()}") - } - } - } -} - /** * @param warningRegex regular expression for warning * @param lineGroupIdx index of capture group for line number