From 7ecdedb15e49ed948e07472b3cff86677ed65f68 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Mon, 11 Dec 2023 16:22:05 +0300 Subject: [PATCH 01/10] Support ANT-like glob patterns ### What's done: - supported folders as input pattern - supported ../ in glob pattern - supported `!` as exclude pattern --- .../diktat/cli/DiktatProperties.kt | 20 +++++++++-- .../com/saveourtool/diktat/util/CliUtils.kt | 35 +++++++++++-------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt index f3756e6cbb..48f3ca19c1 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt @@ -20,11 +20,14 @@ import org.slf4j.event.Level import java.io.OutputStream import java.nio.file.Path import java.nio.file.Paths +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.PathWalkOption import kotlin.io.path.createDirectories import kotlin.io.path.exists import kotlin.io.path.inputStream import kotlin.io.path.outputStream +import kotlin.io.path.walk import kotlin.system.exitProcess import kotlinx.cli.ArgParser import kotlinx.cli.ArgType @@ -96,17 +99,28 @@ data class DiktatProperties( ) } - private fun getFiles(sourceRootDir: Path): Collection = patterns + private fun getFiles(sourceRootDir: Path): Collection { + val (includePatterns, excludePatterns) = patterns.partition { !it.startsWith("!") } + val exclude by lazy { + getFiles(sourceRootDir, excludePatterns.map { it.removePrefix("!") }) + .toSet() + } + return getFiles(sourceRootDir, includePatterns).filterNot { exclude.contains(it) }.toList() + } + + @OptIn(ExperimentalPathApi::class) + private fun getFiles(sourceRootDir: Path, patterns: List): Sequence = patterns .asSequence() .flatMap { pattern -> - pattern.tryToPathIfExists()?.let { sequenceOf(it) } + pattern.tryToPathIfExists()?.walk(PathWalkOption.INCLUDE_DIRECTORIES) ?: sourceRootDir.walkByGlob(pattern) } .filter { file -> file.isKotlinCodeOrScript() } .map { it.normalize() } .map { it.toAbsolutePath() } .distinct() - .toList() + + private fun getReporterOutput(): OutputStream? = output ?.let { Paths.get(it) } diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt index 632df32475..c6e5c3cb90 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt @@ -24,6 +24,10 @@ private val roots: Set = FileSystems.getDefault() .map { it.absolutePathString() } .toSet() +private const val parentDirectoryPrefix = 3 +private const val parentDirectoryUnix = "../" +private const val parentDirectoryWindows = "..\\" + /** * Create a matcher and return a filter that uses it. * @@ -31,11 +35,22 @@ private val roots: Set = FileSystems.getDefault() * @return a sequence of files which matches to [glob] */ @OptIn(ExperimentalPathApi::class) -fun Path.walkByGlob(glob: String): Sequence = fileSystem.globMatcher(glob) - .let { matcher -> - this.walk(PathWalkOption.INCLUDE_DIRECTORIES) - .filter { matcher.matches(it) } - } +fun Path.walkByGlob(glob: String): Sequence = if (glob.startsWith(parentDirectoryUnix) || glob.startsWith(parentDirectoryWindows)) { + parent?.walkByGlob(glob.substring(parentDirectoryPrefix)) ?: emptySequence() +} else { + fileSystem.globMatcher(glob) + .let { matcher -> + walk(PathWalkOption.INCLUDE_DIRECTORIES).filter { matcher.matches(it) } + } +} + +private fun FileSystem.globMatcher(glob: String): PathMatcher = if (isAbsoluteGlob(glob)) { + getPathMatcher("glob:$glob") +} else { + getPathMatcher("glob:**${File.separatorChar}$glob") +} + +private fun isAbsoluteGlob(glob: String): Boolean = glob.startsWith("**") || roots.any { glob.startsWith(it, true) } /** * @return path or null if path is invalid or doesn't exist @@ -45,13 +60,3 @@ fun String.tryToPathIfExists(): Path? = try { } catch (e: InvalidPathException) { null } - -private fun FileSystem.globMatcher(glob: String): PathMatcher = if (isAbsoluteGlob(glob)) { - getPathMatcher("glob:${glob.toUnixSeparator()}") -} else { - getPathMatcher("glob:**/${glob.toUnixSeparator()}") -} - -private fun String.toUnixSeparator(): String = replace(File.separatorChar, '/') - -private fun isAbsoluteGlob(glob: String): Boolean = glob.startsWith("**") || roots.any { glob.startsWith(it, true) } From 1b02430fa794d0f07ad85969805b9bc5be863d66 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Mon, 11 Dec 2023 16:30:34 +0300 Subject: [PATCH 02/10] diktatFix --- .../diktat/cli/DiktatProperties.kt | 2 -- .../com/saveourtool/diktat/util/CliUtils.kt | 28 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt index 48f3ca19c1..bfca98d5cd 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt @@ -120,8 +120,6 @@ data class DiktatProperties( .map { it.toAbsolutePath() } .distinct() - - private fun getReporterOutput(): OutputStream? = output ?.let { Paths.get(it) } ?.also { it.parent.createDirectories() } diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt index c6e5c3cb90..100d5b4d0b 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt @@ -17,6 +17,10 @@ import kotlin.io.path.absolutePathString import kotlin.io.path.exists import kotlin.io.path.walk +private const val PARENT_DIRECTORY_PREFIX = 3 +private const val PARENT_DIRECTORY_UNIX = "../" +private const val PARENT_DIRECTORY_WINDOWS = "..\\" + // all roots private val roots: Set = FileSystems.getDefault() .rootDirectories @@ -24,10 +28,6 @@ private val roots: Set = FileSystems.getDefault() .map { it.absolutePathString() } .toSet() -private const val parentDirectoryPrefix = 3 -private const val parentDirectoryUnix = "../" -private const val parentDirectoryWindows = "..\\" - /** * Create a matcher and return a filter that uses it. * @@ -35,8 +35,8 @@ private const val parentDirectoryWindows = "..\\" * @return a sequence of files which matches to [glob] */ @OptIn(ExperimentalPathApi::class) -fun Path.walkByGlob(glob: String): Sequence = if (glob.startsWith(parentDirectoryUnix) || glob.startsWith(parentDirectoryWindows)) { - parent?.walkByGlob(glob.substring(parentDirectoryPrefix)) ?: emptySequence() +fun Path.walkByGlob(glob: String): Sequence = if (glob.startsWith(PARENT_DIRECTORY_UNIX) || glob.startsWith(PARENT_DIRECTORY_WINDOWS)) { + parent?.walkByGlob(glob.substring(PARENT_DIRECTORY_PREFIX)) ?: emptySequence() } else { fileSystem.globMatcher(glob) .let { matcher -> @@ -44,14 +44,6 @@ fun Path.walkByGlob(glob: String): Sequence = if (glob.startsWith(parentDi } } -private fun FileSystem.globMatcher(glob: String): PathMatcher = if (isAbsoluteGlob(glob)) { - getPathMatcher("glob:$glob") -} else { - getPathMatcher("glob:**${File.separatorChar}$glob") -} - -private fun isAbsoluteGlob(glob: String): Boolean = glob.startsWith("**") || roots.any { glob.startsWith(it, true) } - /** * @return path or null if path is invalid or doesn't exist */ @@ -60,3 +52,11 @@ fun String.tryToPathIfExists(): Path? = try { } catch (e: InvalidPathException) { null } + +private fun FileSystem.globMatcher(glob: String): PathMatcher = if (isAbsoluteGlob(glob)) { + getPathMatcher("glob:$glob") +} else { + getPathMatcher("glob:**${File.separatorChar}$glob") +} + +private fun isAbsoluteGlob(glob: String): Boolean = glob.startsWith("**") || roots.any { glob.startsWith(it, true) } From 1a98cb2cecb4b1f7ca2e0dcb120f4aaae8158e87 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Mon, 11 Dec 2023 18:01:30 +0300 Subject: [PATCH 03/10] WIP --- .../kotlin/com/saveourtool/diktat/util/CliUtils.kt | 12 ++++++++---- .../com/saveourtool/diktat/util/CliUtilsKtTest.kt | 11 +++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt index 100d5b4d0b..0d3b71243b 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt @@ -53,10 +53,14 @@ fun String.tryToPathIfExists(): Path? = try { null } -private fun FileSystem.globMatcher(glob: String): PathMatcher = if (isAbsoluteGlob(glob)) { - getPathMatcher("glob:$glob") +private fun FileSystem.globMatcher(glob: String): PathMatcher = (if (isAbsoluteGlob(glob)) { + glob } else { - getPathMatcher("glob:**${File.separatorChar}$glob") -} + "**${File.separatorChar}$glob" +}) + .replace("([^\\\\])(\\\\)([^\\\\])".toRegex(), "$1\\\\\\\\$3") + .let { + getPathMatcher("glob:$it") + } private fun isAbsoluteGlob(glob: String): Boolean = glob.startsWith("**") || roots.any { glob.startsWith(it, true) } diff --git a/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt b/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt index 1eb1151b59..503b33b1cd 100644 --- a/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt +++ b/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt @@ -60,11 +60,14 @@ class CliUtilsKtTest { fun walkByGlobWithRelativePath(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) + val expectedResult = arrayOf( + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), + ) Assertions.assertThat(tmpDir.walkByGlob("folder1/subFolder11/*.kt").toList()) - .containsExactlyInAnyOrder( - tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), - tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), - ) + .containsExactlyInAnyOrder(*expectedResult) + Assertions.assertThat(tmpDir.walkByGlob("folder1\\subFolder11\\*.kt").toList()) + .containsExactlyInAnyOrder(*expectedResult) } @Test From ec158bd2a30f23618b8d69ed5c296a6d9dcebc60 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Tue, 12 Dec 2023 12:28:59 +0300 Subject: [PATCH 04/10] refactored listFiles method and added tests --- .../diktat/cli/DiktatProperties.kt | 27 +------- .../com/saveourtool/diktat/util/CliUtils.kt | 61 ++++++++++++++----- .../saveourtool/diktat/util/CliUtilsKtTest.kt | 42 +++++++++++-- 3 files changed, 85 insertions(+), 45 deletions(-) diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt index bfca98d5cd..3700ff283c 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt @@ -9,8 +9,7 @@ import com.saveourtool.diktat.api.DiktatReporterCreationArguments import com.saveourtool.diktat.api.DiktatReporterFactory import com.saveourtool.diktat.api.DiktatReporterType import com.saveourtool.diktat.util.isKotlinCodeOrScript -import com.saveourtool.diktat.util.tryToPathIfExists -import com.saveourtool.diktat.util.walkByGlob +import com.saveourtool.diktat.util.listFiles import generated.DIKTAT_VERSION import org.apache.logging.log4j.LogManager @@ -20,14 +19,11 @@ import org.slf4j.event.Level import java.io.OutputStream import java.nio.file.Path import java.nio.file.Paths -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.PathWalkOption import kotlin.io.path.createDirectories import kotlin.io.path.exists import kotlin.io.path.inputStream import kotlin.io.path.outputStream -import kotlin.io.path.walk import kotlin.system.exitProcess import kotlinx.cli.ArgParser import kotlinx.cli.ArgType @@ -99,26 +95,9 @@ data class DiktatProperties( ) } - private fun getFiles(sourceRootDir: Path): Collection { - val (includePatterns, excludePatterns) = patterns.partition { !it.startsWith("!") } - val exclude by lazy { - getFiles(sourceRootDir, excludePatterns.map { it.removePrefix("!") }) - .toSet() - } - return getFiles(sourceRootDir, includePatterns).filterNot { exclude.contains(it) }.toList() - } - - @OptIn(ExperimentalPathApi::class) - private fun getFiles(sourceRootDir: Path, patterns: List): Sequence = patterns - .asSequence() - .flatMap { pattern -> - pattern.tryToPathIfExists()?.walk(PathWalkOption.INCLUDE_DIRECTORIES) - ?: sourceRootDir.walkByGlob(pattern) - } + private fun getFiles(sourceRootDir: Path): Collection = sourceRootDir.listFiles(patterns = patterns.toTypedArray()) .filter { file -> file.isKotlinCodeOrScript() } - .map { it.normalize() } - .map { it.toAbsolutePath() } - .distinct() + .toList() private fun getReporterOutput(): OutputStream? = output ?.let { Paths.get(it) } diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt index 0d3b71243b..3e78f0588f 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt @@ -5,7 +5,6 @@ package com.saveourtool.diktat.util import java.io.File -import java.nio.file.FileSystem import java.nio.file.FileSystems import java.nio.file.InvalidPathException import java.nio.file.Path @@ -15,8 +14,10 @@ import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.PathWalkOption import kotlin.io.path.absolutePathString import kotlin.io.path.exists +import kotlin.io.path.isRegularFile import kotlin.io.path.walk +private const val NEGATIVE_PREFIX_PATTERN = "!" private const val PARENT_DIRECTORY_PREFIX = 3 private const val PARENT_DIRECTORY_UNIX = "../" private const val PARENT_DIRECTORY_WINDOWS = "..\\" @@ -28,6 +29,34 @@ private val roots: Set = FileSystems.getDefault() .map { it.absolutePathString() } .toSet() +/** + * Lists all files in [this] directory based on [patterns] + * + * @param patterns a path to a file or a directory (all files from this directory will be returned) or an [Ant-style path pattern](https://ant.apache.org/manual/dirtasks.html#patterns) + * @return [Sequence] of files as [Path] matched to provided [patterns] + */ +fun Path.listFiles( + vararg patterns: String, +): Sequence { + val (includePatterns, excludePatterns) = patterns.partition { !it.startsWith(NEGATIVE_PREFIX_PATTERN) } + val exclude by lazy { + doListFiles(excludePatterns.map { it.removePrefix(NEGATIVE_PREFIX_PATTERN) }) + .toSet() + } + return doListFiles(includePatterns).filterNot { exclude.contains(it) } +} + +@OptIn(ExperimentalPathApi::class) +private fun Path.doListFiles(patterns: List): Sequence = patterns + .asSequence() + .flatMap { pattern -> + tryToResolveIfExists(pattern)?.walk() ?: walkByGlob(pattern) + } +// .filter { it.isRegularFile() } + .map { it.normalize() } + .map { it.toAbsolutePath() } + .distinct() + /** * Create a matcher and return a filter that uses it. * @@ -35,32 +64,32 @@ private val roots: Set = FileSystems.getDefault() * @return a sequence of files which matches to [glob] */ @OptIn(ExperimentalPathApi::class) -fun Path.walkByGlob(glob: String): Sequence = if (glob.startsWith(PARENT_DIRECTORY_UNIX) || glob.startsWith(PARENT_DIRECTORY_WINDOWS)) { +private fun Path.walkByGlob(glob: String): Sequence = if (glob.startsWith(PARENT_DIRECTORY_UNIX) || glob.startsWith(PARENT_DIRECTORY_WINDOWS)) { parent?.walkByGlob(glob.substring(PARENT_DIRECTORY_PREFIX)) ?: emptySequence() } else { - fileSystem.globMatcher(glob) + globMatcher(glob) .let { matcher -> - walk(PathWalkOption.INCLUDE_DIRECTORIES).filter { matcher.matches(it) } + walk().filter { matcher.matches(it) } } } /** * @return path or null if path is invalid or doesn't exist */ -fun String.tryToPathIfExists(): Path? = try { - Paths.get(this).takeIf { it.exists() } +private fun Path.tryToResolveIfExists(pattern: String): Path? = try { + Paths.get(pattern).takeIf { it.exists() } + ?: resolve(pattern).takeIf { it.exists() } } catch (e: InvalidPathException) { null } -private fun FileSystem.globMatcher(glob: String): PathMatcher = (if (isAbsoluteGlob(glob)) { - glob -} else { - "**${File.separatorChar}$glob" -}) - .replace("([^\\\\])(\\\\)([^\\\\])".toRegex(), "$1\\\\\\\\$3") - .let { - getPathMatcher("glob:$it") - } +private fun Path.globMatcher(glob: String): PathMatcher = glob.toAbsoluteGlob(this) + .replace("([^\\\\])(\\\\)([^\\\\])".toRegex(), "$1\\\\\\\\$3") // encode Windows separators + .let { fileSystem.getPathMatcher("glob:$it") } + +private fun String.toAbsoluteGlob(from: Path): String = when { + startsWith("**") -> this + roots.any { startsWith(it, true) } -> this + else -> "${from.absolutePathString()}${File.separatorChar}$this" +} -private fun isAbsoluteGlob(glob: String): Boolean = glob.startsWith("**") || roots.any { glob.startsWith(it, true) } diff --git a/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt b/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt index 503b33b1cd..caf5269274 100644 --- a/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt +++ b/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt @@ -36,7 +36,7 @@ class CliUtilsKtTest { fun walkByGlobWithLeadingAsterisks(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) - Assertions.assertThat(tmpDir.walkByGlob("**/Test1.kt").toList()) + Assertions.assertThat(tmpDir.listFiles("**/Test1.kt").toList()) .containsExactlyInAnyOrder( tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), tmpDir.resolve("folder1").resolve("subFolder12").resolve("Test1.kt"), @@ -49,7 +49,7 @@ class CliUtilsKtTest { fun walkByGlobWithGlobalPath(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) - Assertions.assertThat(tmpDir.walkByGlob("${tmpDir.absolutePathString()}${File.separator}**${File.separator}Test2.kt").toList()) + Assertions.assertThat(tmpDir.listFiles("${tmpDir.absolutePathString()}${File.separator}**${File.separator}Test2.kt").toList()) .containsExactlyInAnyOrder( tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), tmpDir.resolve("folder2").resolve("Test2.kt"), @@ -64,9 +64,9 @@ class CliUtilsKtTest { tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), ) - Assertions.assertThat(tmpDir.walkByGlob("folder1/subFolder11/*.kt").toList()) + Assertions.assertThat(tmpDir.listFiles("folder1/subFolder11/*.kt").toList()) .containsExactlyInAnyOrder(*expectedResult) - Assertions.assertThat(tmpDir.walkByGlob("folder1\\subFolder11\\*.kt").toList()) + Assertions.assertThat(tmpDir.listFiles("folder1\\subFolder11\\*.kt").toList()) .containsExactlyInAnyOrder(*expectedResult) } @@ -74,10 +74,42 @@ class CliUtilsKtTest { fun walkByGlobWithEmptyResult(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) - Assertions.assertThat(tmpDir.walkByGlob("**/*.kts").toList()) + Assertions.assertThat(tmpDir.listFiles("**/*.kts").toList()) .isEmpty() } + @Test + fun walkByGlobWithParentFolder(@TempDir tmpDir: Path) { + setupHierarchy(tmpDir) + + Assertions.assertThat(tmpDir.resolve("folder1").listFiles("../*/*.kt").toList()) + .containsExactlyInAnyOrder( + tmpDir.resolve("folder2").resolve("Test1.kt"), + tmpDir.resolve("folder2").resolve("Test2.kt"), + tmpDir.resolve("folder2").resolve("Test3.kt"), + ) + } + + @Test + fun walkByGlobWithFolder(@TempDir tmpDir: Path) { + setupHierarchy(tmpDir) + + Assertions.assertThat(tmpDir.listFiles("folder2").toList()) + .containsExactlyInAnyOrder( + tmpDir.resolve("folder2").resolve("Test1.kt"), + tmpDir.resolve("folder2").resolve("Test2.kt"), + tmpDir.resolve("folder2").resolve("Test3.kt"), + ) + + + Assertions.assertThat(tmpDir.listFiles("folder1").toList()) + .containsExactlyInAnyOrder( + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), + tmpDir.resolve("folder1").resolve("subFolder12").resolve("Test1.kt"), + ) + } + companion object { private fun Path.resolveAndCreateDirectory(name: String): Path = resolve(name).also { it.createDirectory() From 8857d918e92e3442f1703189ca57f68135cefd98 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Tue, 12 Dec 2023 12:37:01 +0300 Subject: [PATCH 05/10] added test for negative patterns --- .../saveourtool/diktat/util/CliUtilsKtTest.kt | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt b/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt index caf5269274..611006d0b7 100644 --- a/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt +++ b/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt @@ -33,7 +33,7 @@ class CliUtilsKtTest { } @Test - fun walkByGlobWithLeadingAsterisks(@TempDir tmpDir: Path) { + fun listByFilesWithLeadingAsterisks(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) Assertions.assertThat(tmpDir.listFiles("**/Test1.kt").toList()) @@ -46,7 +46,7 @@ class CliUtilsKtTest { @Test - fun walkByGlobWithGlobalPath(@TempDir tmpDir: Path) { + fun listByFilesWithGlobalPath(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) Assertions.assertThat(tmpDir.listFiles("${tmpDir.absolutePathString()}${File.separator}**${File.separator}Test2.kt").toList()) @@ -57,7 +57,7 @@ class CliUtilsKtTest { } @Test - fun walkByGlobWithRelativePath(@TempDir tmpDir: Path) { + fun listByFilesWithRelativePath(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) val expectedResult = arrayOf( @@ -71,7 +71,7 @@ class CliUtilsKtTest { } @Test - fun walkByGlobWithEmptyResult(@TempDir tmpDir: Path) { + fun listByFilesWithEmptyResult(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) Assertions.assertThat(tmpDir.listFiles("**/*.kts").toList()) @@ -79,7 +79,7 @@ class CliUtilsKtTest { } @Test - fun walkByGlobWithParentFolder(@TempDir tmpDir: Path) { + fun listByFilesWithParentFolder(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) Assertions.assertThat(tmpDir.resolve("folder1").listFiles("../*/*.kt").toList()) @@ -91,7 +91,7 @@ class CliUtilsKtTest { } @Test - fun walkByGlobWithFolder(@TempDir tmpDir: Path) { + fun listByFilesWithFolder(@TempDir tmpDir: Path) { setupHierarchy(tmpDir) Assertions.assertThat(tmpDir.listFiles("folder2").toList()) @@ -110,6 +110,18 @@ class CliUtilsKtTest { ) } + @Test + fun listByFilesWithNegative(@TempDir tmpDir: Path) { + setupHierarchy(tmpDir) + + Assertions.assertThat(tmpDir.listFiles("**/*.kt", "!**/subFolder11/*.kt", "!**/Test3.kt").toList()) + .containsExactlyInAnyOrder( + tmpDir.resolve("folder1").resolve("subFolder12").resolve("Test1.kt"), + tmpDir.resolve("folder2").resolve("Test1.kt"), + tmpDir.resolve("folder2").resolve("Test2.kt"), + ) + } + companion object { private fun Path.resolveAndCreateDirectory(name: String): Path = resolve(name).also { it.createDirectory() From 0878c51be9c6fdc28380c9f7e05572fa935d049b Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Tue, 12 Dec 2023 12:42:32 +0300 Subject: [PATCH 06/10] diktatFix --- .../src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt index 3e78f0588f..e5836993b3 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt @@ -11,10 +11,8 @@ import java.nio.file.Path import java.nio.file.PathMatcher import java.nio.file.Paths import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.PathWalkOption import kotlin.io.path.absolutePathString import kotlin.io.path.exists -import kotlin.io.path.isRegularFile import kotlin.io.path.walk private const val NEGATIVE_PREFIX_PATTERN = "!" @@ -52,7 +50,6 @@ private fun Path.doListFiles(patterns: List): Sequence = patterns .flatMap { pattern -> tryToResolveIfExists(pattern)?.walk() ?: walkByGlob(pattern) } -// .filter { it.isRegularFile() } .map { it.normalize() } .map { it.toAbsolutePath() } .distinct() @@ -84,7 +81,7 @@ private fun Path.tryToResolveIfExists(pattern: String): Path? = try { } private fun Path.globMatcher(glob: String): PathMatcher = glob.toAbsoluteGlob(this) - .replace("([^\\\\])(\\\\)([^\\\\])".toRegex(), "$1\\\\\\\\$3") // encode Windows separators + .replace("([^\\\\])(\\\\)([^\\\\])".toRegex(), "$1\\\\\\\\$3") // encode Windows separators .let { fileSystem.getPathMatcher("glob:$it") } private fun String.toAbsoluteGlob(from: Path): String = when { @@ -92,4 +89,3 @@ private fun String.toAbsoluteGlob(from: Path): String = when { roots.any { startsWith(it, true) } -> this else -> "${from.absolutePathString()}${File.separatorChar}$this" } - From 292189a658b6a2f0e9a9bffe46c01bee185684b8 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Tue, 12 Dec 2023 12:57:33 +0300 Subject: [PATCH 07/10] move test with windows separator to windows only --- .../saveourtool/diktat/util/CliUtilsKtTest.kt | 99 ++++++++++--------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt b/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt index 611006d0b7..841537f857 100644 --- a/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt +++ b/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt @@ -1,41 +1,22 @@ package com.saveourtool.diktat.util import org.assertj.core.api.Assertions +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test +import org.junit.jupiter.api.condition.EnabledOnOs +import org.junit.jupiter.api.condition.OS import org.junit.jupiter.api.io.TempDir import java.io.File import java.nio.file.Path +import java.nio.file.Paths import kotlin.io.path.absolutePathString import kotlin.io.path.createDirectory import kotlin.io.path.createFile import kotlin.io.path.writeText class CliUtilsKtTest { - private fun setupHierarchy(dir: Path) { - dir.resolveAndCreateDirectory("folder1") - .also { folder1 -> - folder1.resolveAndCreateDirectory("subFolder11") - .also { subFolder11 -> - subFolder11.resolveAndCreateFile("Test1.kt") - subFolder11.resolveAndCreateFile("Test2.kt") - } - folder1.resolveAndCreateDirectory("subFolder12") - .also { subFolder12 -> - subFolder12.resolveAndCreateFile("Test1.kt") - } - } - dir.resolveAndCreateDirectory("folder2") - .also { folder2 -> - folder2.resolveAndCreateFile("Test1.kt") - folder2.resolveAndCreateFile("Test2.kt") - folder2.resolveAndCreateFile("Test3.kt") - } - } - @Test - fun listByFilesWithLeadingAsterisks(@TempDir tmpDir: Path) { - setupHierarchy(tmpDir) - + fun listByFilesWithLeadingAsterisks() { Assertions.assertThat(tmpDir.listFiles("**/Test1.kt").toList()) .containsExactlyInAnyOrder( tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), @@ -46,9 +27,7 @@ class CliUtilsKtTest { @Test - fun listByFilesWithGlobalPath(@TempDir tmpDir: Path) { - setupHierarchy(tmpDir) - + fun listByFilesWithGlobalPath() { Assertions.assertThat(tmpDir.listFiles("${tmpDir.absolutePathString()}${File.separator}**${File.separator}Test2.kt").toList()) .containsExactlyInAnyOrder( tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), @@ -57,31 +36,32 @@ class CliUtilsKtTest { } @Test - fun listByFilesWithRelativePath(@TempDir tmpDir: Path) { - setupHierarchy(tmpDir) - - val expectedResult = arrayOf( - tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), - tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), - ) + fun listByFilesWithRelativePath() { Assertions.assertThat(tmpDir.listFiles("folder1/subFolder11/*.kt").toList()) - .containsExactlyInAnyOrder(*expectedResult) - Assertions.assertThat(tmpDir.listFiles("folder1\\subFolder11\\*.kt").toList()) - .containsExactlyInAnyOrder(*expectedResult) + .containsExactlyInAnyOrder( + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), + ) } @Test - fun listByFilesWithEmptyResult(@TempDir tmpDir: Path) { - setupHierarchy(tmpDir) + @EnabledOnOs(OS.WINDOWS) + fun listByFilesWithRelativePathWindows() { + Assertions.assertThat(tmpDir.listFiles("folder1\\subFolder11\\*.kt").toList()) + .containsExactlyInAnyOrder( + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test1.kt"), + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), + ) + } + @Test + fun listByFilesWithEmptyResult() { Assertions.assertThat(tmpDir.listFiles("**/*.kts").toList()) .isEmpty() } @Test - fun listByFilesWithParentFolder(@TempDir tmpDir: Path) { - setupHierarchy(tmpDir) - + fun listByFilesWithParentFolder() { Assertions.assertThat(tmpDir.resolve("folder1").listFiles("../*/*.kt").toList()) .containsExactlyInAnyOrder( tmpDir.resolve("folder2").resolve("Test1.kt"), @@ -91,9 +71,7 @@ class CliUtilsKtTest { } @Test - fun listByFilesWithFolder(@TempDir tmpDir: Path) { - setupHierarchy(tmpDir) - + fun listByFilesWithFolder() { Assertions.assertThat(tmpDir.listFiles("folder2").toList()) .containsExactlyInAnyOrder( tmpDir.resolve("folder2").resolve("Test1.kt"), @@ -111,9 +89,7 @@ class CliUtilsKtTest { } @Test - fun listByFilesWithNegative(@TempDir tmpDir: Path) { - setupHierarchy(tmpDir) - + fun listByFilesWithNegative() { Assertions.assertThat(tmpDir.listFiles("**/*.kt", "!**/subFolder11/*.kt", "!**/Test3.kt").toList()) .containsExactlyInAnyOrder( tmpDir.resolve("folder1").resolve("subFolder12").resolve("Test1.kt"), @@ -123,6 +99,33 @@ class CliUtilsKtTest { } companion object { + @JvmStatic + @TempDir + internal var tmpDir: Path = Paths.get("/invalid") + + @BeforeAll + @JvmStatic + internal fun setupHierarchy() { + tmpDir.resolveAndCreateDirectory("folder1") + .also { folder1 -> + folder1.resolveAndCreateDirectory("subFolder11") + .also { subFolder11 -> + subFolder11.resolveAndCreateFile("Test1.kt") + subFolder11.resolveAndCreateFile("Test2.kt") + } + folder1.resolveAndCreateDirectory("subFolder12") + .also { subFolder12 -> + subFolder12.resolveAndCreateFile("Test1.kt") + } + } + tmpDir.resolveAndCreateDirectory("folder2") + .also { folder2 -> + folder2.resolveAndCreateFile("Test1.kt") + folder2.resolveAndCreateFile("Test2.kt") + folder2.resolveAndCreateFile("Test3.kt") + } + } + private fun Path.resolveAndCreateDirectory(name: String): Path = resolve(name).also { it.createDirectory() } From f3e4dde75550aaa02f781741d040fa4ec895f8c5 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Tue, 12 Dec 2023 16:14:19 +0300 Subject: [PATCH 08/10] supported global glob --- .../com/saveourtool/diktat/util/CliUtils.kt | 41 +++++++++++-------- .../saveourtool/diktat/util/CliUtilsKtTest.kt | 10 ++++- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt index e5836993b3..2dab3bf3ce 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt @@ -8,9 +8,9 @@ import java.io.File import java.nio.file.FileSystems import java.nio.file.InvalidPathException import java.nio.file.Path -import java.nio.file.PathMatcher import java.nio.file.Paths import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.Path import kotlin.io.path.absolutePathString import kotlin.io.path.exists import kotlin.io.path.walk @@ -48,7 +48,7 @@ fun Path.listFiles( private fun Path.doListFiles(patterns: List): Sequence = patterns .asSequence() .flatMap { pattern -> - tryToResolveIfExists(pattern)?.walk() ?: walkByGlob(pattern) + tryToResolveIfExists(pattern, this)?.walk() ?: walkByGlob(pattern) } .map { it.normalize() } .map { it.toAbsolutePath() } @@ -64,28 +64,37 @@ private fun Path.doListFiles(patterns: List): Sequence = patterns private fun Path.walkByGlob(glob: String): Sequence = if (glob.startsWith(PARENT_DIRECTORY_UNIX) || glob.startsWith(PARENT_DIRECTORY_WINDOWS)) { parent?.walkByGlob(glob.substring(PARENT_DIRECTORY_PREFIX)) ?: emptySequence() } else { - globMatcher(glob) - .let { matcher -> - walk().filter { matcher.matches(it) } + getAbsoluteGlobAndRoot(glob, this) + .let { (absoluteGlob, root) -> + absoluteGlob + .replace("([^\\\\])(\\\\)([^\\\\])".toRegex(), "$1\\\\\\\\$3") // encode Windows separators + .let { root.fileSystem.getPathMatcher("glob:$it") } + .let { matcher -> + root.walk().filter { matcher.matches(it) } + } } } /** + * @param candidate + * @param currentDirectory * @return path or null if path is invalid or doesn't exist */ -private fun Path.tryToResolveIfExists(pattern: String): Path? = try { - Paths.get(pattern).takeIf { it.exists() } - ?: resolve(pattern).takeIf { it.exists() } +private fun tryToResolveIfExists(candidate: String, currentDirectory: Path): Path? = try { + Paths.get(candidate).takeIf { it.exists() } + ?: currentDirectory.resolve(candidate).takeIf { it.exists() } } catch (e: InvalidPathException) { null } -private fun Path.globMatcher(glob: String): PathMatcher = glob.toAbsoluteGlob(this) - .replace("([^\\\\])(\\\\)([^\\\\])".toRegex(), "$1\\\\\\\\$3") // encode Windows separators - .let { fileSystem.getPathMatcher("glob:$it") } - -private fun String.toAbsoluteGlob(from: Path): String = when { - startsWith("**") -> this - roots.any { startsWith(it, true) } -> this - else -> "${from.absolutePathString()}${File.separatorChar}$this" +private fun getAbsoluteGlobAndRoot(glob: String, currentFolder: Path): Pair = when { + glob.startsWith("**") -> glob to currentFolder + roots.any { glob.startsWith(it, true) } -> glob to glob.findRoot() + else -> "${currentFolder.absolutePathString()}${File.separatorChar}$glob" to currentFolder } + +private fun String.findRoot(): Path = substring(0, indexOf('*')) + .let { withoutAsterisks -> + withoutAsterisks.substring(0, withoutAsterisks.lastIndexOfAny(charArrayOf('\\', '/'))) + } + .let { Path(it) } diff --git a/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt b/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt index 841537f857..77c12eee11 100644 --- a/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt +++ b/diktat-cli/src/test/kotlin/com/saveourtool/diktat/util/CliUtilsKtTest.kt @@ -25,7 +25,6 @@ class CliUtilsKtTest { ) } - @Test fun listByFilesWithGlobalPath() { Assertions.assertThat(tmpDir.listFiles("${tmpDir.absolutePathString()}${File.separator}**${File.separator}Test2.kt").toList()) @@ -35,6 +34,15 @@ class CliUtilsKtTest { ) } + @Test + fun listByFilesWithGlobalPattern() { + Assertions.assertThat(tmpDir.resolve("folder2").listFiles("${tmpDir.absolutePathString()}${File.separator}**${File.separator}Test2.kt").toList()) + .containsExactlyInAnyOrder( + tmpDir.resolve("folder1").resolve("subFolder11").resolve("Test2.kt"), + tmpDir.resolve("folder2").resolve("Test2.kt"), + ) + } + @Test fun listByFilesWithRelativePath() { Assertions.assertThat(tmpDir.listFiles("folder1/subFolder11/*.kt").toList()) From f1c89ba52d420f5c7a3abb0e38bd0beb9582d8a8 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Tue, 12 Dec 2023 16:18:58 +0300 Subject: [PATCH 09/10] diktatFix + fixed relativePath when different roots --- .../kotlin/com/saveourtool/diktat/util/CliUtils.kt | 12 ++++++------ .../com/saveourtool/diktat/ktlint/KtLintUtils.kt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt index 2dab3bf3ce..e7c8be027a 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt @@ -75,6 +75,12 @@ private fun Path.walkByGlob(glob: String): Sequence = if (glob.startsWith( } } +private fun String.findRoot(): Path = substring(0, indexOf('*')) + .let { withoutAsterisks -> + withoutAsterisks.substring(0, withoutAsterisks.lastIndexOfAny(charArrayOf('\\', '/'))) + } + .let { Path(it) } + /** * @param candidate * @param currentDirectory @@ -92,9 +98,3 @@ private fun getAbsoluteGlobAndRoot(glob: String, currentFolder: Path): Pair glob to glob.findRoot() else -> "${currentFolder.absolutePathString()}${File.separatorChar}$glob" to currentFolder } - -private fun String.findRoot(): Path = substring(0, indexOf('*')) - .let { withoutAsterisks -> - withoutAsterisks.substring(0, withoutAsterisks.lastIndexOfAny(charArrayOf('\\', '/'))) - } - .let { Path(it) } diff --git a/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/KtLintUtils.kt b/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/KtLintUtils.kt index 72bd186c77..8ea8d04a71 100644 --- a/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/KtLintUtils.kt +++ b/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/KtLintUtils.kt @@ -22,7 +22,7 @@ import java.io.PrintStream import java.nio.file.Path import kotlin.io.path.invariantSeparatorsPathString -import kotlin.io.path.relativeTo +import kotlin.io.path.relativeToOrSelf private const val CANNOT_BE_AUTOCORRECTED_SUFFIX = " (cannot be auto-corrected)" @@ -92,7 +92,7 @@ fun String.correctErrorDetail(canBeAutoCorrected: Boolean): String = if (canBeAu * @param sourceRootDir * @return relative path to [sourceRootDir] as [String] */ -fun Path.relativePathStringTo(sourceRootDir: Path?): String = (sourceRootDir?.let { relativeTo(it) } ?: this).invariantSeparatorsPathString +fun Path.relativePathStringTo(sourceRootDir: Path?): String = (sourceRootDir?.let { relativeToOrSelf(it) } ?: this).invariantSeparatorsPathString /** * @param out [OutputStream] for [ReporterV2] From ac5ced85a1288fc6a15b498958fb73239b73111e Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Wed, 13 Dec 2023 14:54:51 +0300 Subject: [PATCH 10/10] review note about regex --- .../src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt index e7c8be027a..1cd816758d 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/util/CliUtils.kt @@ -67,7 +67,7 @@ private fun Path.walkByGlob(glob: String): Sequence = if (glob.startsWith( getAbsoluteGlobAndRoot(glob, this) .let { (absoluteGlob, root) -> absoluteGlob - .replace("([^\\\\])(\\\\)([^\\\\])".toRegex(), "$1\\\\\\\\$3") // encode Windows separators + .replace("([^\\\\])\\\\([^\\\\])".toRegex(), "$1\\\\\\\\$2") // encode Windows separators .let { root.fileSystem.getPathMatcher("glob:$it") } .let { matcher -> root.walk().filter { matcher.matches(it) }