-
Notifications
You must be signed in to change notification settings - Fork 506
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add API for retrieving the list of files that will be accessed (#1659)
* Add API for retrieving the list of files that will be accessed when linting or formatting a given path Closes #1446
- Loading branch information
1 parent
09ea546
commit 8e78581
Showing
6 changed files
with
295 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/internal/EditorConfigFinder.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package com.pinterest.ktlint.core.internal | ||
|
||
import com.pinterest.ktlint.core.initKtLintKLogger | ||
import java.nio.charset.StandardCharsets | ||
import java.nio.file.FileVisitResult | ||
import java.nio.file.Files | ||
import java.nio.file.Path | ||
import java.nio.file.SimpleFileVisitor | ||
import java.nio.file.attribute.BasicFileAttributes | ||
import kotlin.io.path.isDirectory | ||
import mu.KotlinLogging | ||
import org.ec4j.core.Resource | ||
import org.ec4j.core.ResourcePropertiesService | ||
import org.ec4j.core.model.Version | ||
import org.jetbrains.kotlin.konan.file.File | ||
|
||
private val logger = KotlinLogging.logger {}.initKtLintKLogger() | ||
|
||
internal class EditorConfigFinder { | ||
// Do not reuse the generic threadSafeEditorConfigCache to prevent that results are incorrect due to other calls to | ||
// KtLint that result in changing the cache | ||
private val editorConfigCache = ThreadSafeEditorConfigCache() | ||
|
||
/** | ||
* Finds all relevant ".editorconfig" files for the given path. | ||
*/ | ||
fun findEditorConfigs(path: Path): List<Path> { | ||
val result = mutableListOf<Path>() | ||
val normalizedPath = path.normalize().toAbsolutePath() | ||
if (path.isDirectory()) { | ||
result += findEditorConfigsInSubDirectories(normalizedPath) | ||
} | ||
result += findEditorConfigsInParentDirectories(normalizedPath) | ||
return result | ||
.map { | ||
// Resolve against original path as the drive letter seems to get lost on WindowsOs | ||
path.resolve(it) | ||
}.toList() | ||
} | ||
|
||
private fun findEditorConfigsInSubDirectories(path: Path): List<Path> { | ||
val result = mutableListOf<Path>() | ||
|
||
Files.walkFileTree( | ||
path, | ||
object : SimpleFileVisitor<Path>() { | ||
override fun visitFile( | ||
filePath: Path, | ||
fileAttrs: BasicFileAttributes, | ||
): FileVisitResult { | ||
if (filePath.File().name == ".editorconfig") { | ||
logger.trace { "- File: $filePath: add to list of accessed files" } | ||
result.add(filePath) | ||
} | ||
return FileVisitResult.CONTINUE | ||
} | ||
|
||
override fun preVisitDirectory( | ||
dirPath: Path, | ||
dirAttr: BasicFileAttributes, | ||
): FileVisitResult { | ||
return if (Files.isHidden(dirPath)) { | ||
logger.trace { "- Dir: $dirPath: Ignore" } | ||
FileVisitResult.SKIP_SUBTREE | ||
} else { | ||
logger.trace { "- Dir: $dirPath: Traverse" } | ||
FileVisitResult.CONTINUE | ||
} | ||
} | ||
}, | ||
) | ||
|
||
return result.toList() | ||
} | ||
|
||
private fun findEditorConfigsInParentDirectories(path: Path): List<Path> { | ||
// The logic to load parental ".editorconfig" files resides in the ec4j library. This library however uses a | ||
// cache provided by KtLint. As of this the list of parental ".editorconfig" files can be extracted from the | ||
// cache. | ||
createLoaderService().queryProperties(path.resource()) | ||
return editorConfigCache.getPaths() | ||
} | ||
|
||
private fun Path?.resource() = | ||
Resource.Resources.ofPath(this, StandardCharsets.UTF_8) | ||
|
||
private fun createLoaderService() = | ||
ResourcePropertiesService.builder() | ||
.cache(editorConfigCache) | ||
.loader(org.ec4j.core.EditorConfigLoader.of(Version.CURRENT)) | ||
.build() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
163 changes: 163 additions & 0 deletions
163
ktlint-core/src/test/kotlin/com/pinterest/ktlint/core/internal/EditorConfigFinderTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package com.pinterest.ktlint.core.internal | ||
|
||
import java.nio.file.Files | ||
import java.nio.file.Path | ||
import java.nio.file.Paths | ||
import kotlin.io.path.absolutePathString | ||
import kotlin.io.path.writeText | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Nested | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.io.TempDir | ||
|
||
class EditorConfigFinderTest { | ||
@Nested | ||
inner class FindByFile { | ||
@Test | ||
fun `Given a kotlin file in a subdirectory and a root-editorconfig file in the same directory then get the path of that editorconfig file`( | ||
@TempDir | ||
tempDir: Path, | ||
) { | ||
val someSubDir = "some-project/src/main/kotlin" | ||
tempDir.createFile("$someSubDir/.editorconfig", "root=true") | ||
val kotlinFilePath = tempDir.createFile("$someSubDir/test.kt", "val foo = 42") | ||
|
||
val actual = EditorConfigFinder().findEditorConfigs(kotlinFilePath) | ||
|
||
assertThat(actual).contains( | ||
tempDir.plus("$someSubDir/.editorconfig"), | ||
) | ||
} | ||
|
||
@Test | ||
fun `Given a kotlin file in a subdirectory and a root-editorconfig file in a parent directory then get the path of that parent editorconfig file`( | ||
@TempDir | ||
tempDir: Path, | ||
) { | ||
val someProjectDirectory = "some-project" | ||
tempDir.createFile("$someProjectDirectory/.editorconfig", "root=true") | ||
val kotlinFilePath = tempDir.createFile("$someProjectDirectory/src/main/kotlin/test.kt", "val foo = 42") | ||
|
||
val actual = EditorConfigFinder().findEditorConfigs(kotlinFilePath) | ||
|
||
assertThat(actual).contains( | ||
tempDir.plus("$someProjectDirectory/.editorconfig"), | ||
) | ||
} | ||
|
||
@Test | ||
fun `Given a kotlin file in a subdirectory and a non-root-editorconfig file in that same directory and a root-editorconfig file in a parent directory then get the paths of both editorconfig files`( | ||
@TempDir | ||
tempDir: Path, | ||
) { | ||
val someProjectDirectory = "some-project" | ||
tempDir.createFile("$someProjectDirectory/.editorconfig", "root=true") | ||
tempDir.createFile("$someProjectDirectory/src/main/.editorconfig", "root=false") | ||
val kotlinFilePath = tempDir.createFile("$someProjectDirectory/src/main/kotlin/test.kt", "val foo = 42") | ||
|
||
val actual = EditorConfigFinder().findEditorConfigs(kotlinFilePath) | ||
|
||
assertThat(actual).contains( | ||
tempDir.plus("$someProjectDirectory/.editorconfig"), | ||
tempDir.plus("$someProjectDirectory/src/main/.editorconfig"), | ||
) | ||
} | ||
|
||
@Test | ||
fun `Given a kotlin file in a subdirectory and a root-editorconfig file in the parent directory and another root-editorconfig file in a great-parent directory then get the paths of editorconfig files excluding root-editorconfig once the first one is found`( | ||
@TempDir | ||
tempDir: Path, | ||
) { | ||
val someProjectDirectory = "some-project" | ||
tempDir.createFile("$someProjectDirectory/src/main/.editorconfig", "root=false") | ||
tempDir.createFile("$someProjectDirectory/src/.editorconfig", "root=true") | ||
tempDir.createFile("$someProjectDirectory/.editorconfig", "root=true") | ||
val kotlinFilePath = tempDir.createFile("$someProjectDirectory/src/main/kotlin/test.kt", "val foo = 42") | ||
|
||
val actual = EditorConfigFinder().findEditorConfigs(kotlinFilePath) | ||
|
||
assertThat(actual) | ||
.contains( | ||
tempDir.plus("$someProjectDirectory/src/main/.editorconfig"), | ||
tempDir.plus("$someProjectDirectory/src/.editorconfig"), | ||
).doesNotContain( | ||
tempDir.plus("$someProjectDirectory/.editorconfig"), | ||
) | ||
} | ||
} | ||
|
||
@Nested | ||
inner class FindByDirectory { | ||
@Test | ||
fun `Given a directory containing a root-editorconfig file and a subdirectory containing a editorconfig file then get the paths of both editorconfig files`( | ||
@TempDir | ||
tempDir: Path, | ||
) { | ||
val someDirectory = "some-project" | ||
tempDir.createFile("$someDirectory/.editorconfig", "root=true") | ||
tempDir.createFile("$someDirectory/src/main/kotlin/.editorconfig", "some-property=some-value") | ||
|
||
val actual = EditorConfigFinder().findEditorConfigs(tempDir.plus(someDirectory)) | ||
|
||
assertThat(actual).contains( | ||
tempDir.plus("$someDirectory/.editorconfig"), | ||
tempDir.plus("$someDirectory/src/main/kotlin/.editorconfig"), | ||
) | ||
} | ||
|
||
@Test | ||
fun `Given a subdirectory containing an editorconfig file and a sibling subdirectory contain a editorconfig file in a parent directory then get the path of all editorconfig file except of the sibling subdirectory`( | ||
@TempDir | ||
tempDir: Path, | ||
) { | ||
val someProjectDirectory = "some-project" | ||
tempDir.createFile("$someProjectDirectory/.editorconfig", "root=true") | ||
tempDir.createFile("$someProjectDirectory/src/main/kotlin/.editorconfig", "some-property=some-value") | ||
tempDir.createFile("$someProjectDirectory/src/test/kotlin/.editorconfig", "some-property=some-value") | ||
|
||
val actual = EditorConfigFinder().findEditorConfigs(tempDir.plus("$someProjectDirectory/src/main/kotlin")) | ||
|
||
assertThat(actual) | ||
.contains( | ||
tempDir.plus("$someProjectDirectory/.editorconfig"), | ||
tempDir.plus("$someProjectDirectory/src/main/kotlin/.editorconfig"), | ||
).doesNotContain( | ||
tempDir.plus("$someProjectDirectory/src/test/kotlin/.editorconfig"), | ||
) | ||
} | ||
|
||
@Test | ||
fun `Given a directory containing an editorconfig file and multiple subdirectores containing a editorconfig file then get the path of all editorconfig files`( | ||
@TempDir | ||
tempDir: Path, | ||
) { | ||
val someProjectDirectory = "some-project" | ||
tempDir.createFile("$someProjectDirectory/.editorconfig", "root=true") | ||
tempDir.createFile("$someProjectDirectory/src/main/kotlin/.editorconfig", "some-property=some-value") | ||
tempDir.createFile("$someProjectDirectory/src/test/kotlin/.editorconfig", "some-property=some-value") | ||
|
||
val actual = EditorConfigFinder().findEditorConfigs(tempDir.plus(someProjectDirectory)) | ||
|
||
assertThat(actual).contains( | ||
tempDir.plus("$someProjectDirectory/.editorconfig"), | ||
tempDir.plus("$someProjectDirectory/src/main/kotlin/.editorconfig"), | ||
tempDir.plus("$someProjectDirectory/src/test/kotlin/.editorconfig"), | ||
) | ||
} | ||
} | ||
|
||
private fun Path.createFile(fileName: String, content: String): Path { | ||
val dirPath = fileName.substringBeforeLast("/", "") | ||
Files.createDirectories(this.plus(dirPath)) | ||
return Files | ||
.createFile(this.plus(fileName)) | ||
.also { it.writeText(content) } | ||
} | ||
|
||
private fun Path.plus(subPath: String): Path = | ||
this | ||
.absolutePathString() | ||
.plus(this.fileSystem.separator) | ||
.plus(subPath) | ||
.let { Paths.get(it) } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters