From 58c87bb771c38b075b8167e5ee4092b5285700d5 Mon Sep 17 00:00:00 2001 From: Joseph Barratt Date: Thu, 30 Jan 2025 12:13:46 -0600 Subject: [PATCH 1/4] Read excluded patterns from configuration --- .../main/kotlin/org/javacs/kt/CompilerClassPath.kt | 3 ++- .../src/main/kotlin/org/javacs/kt/Configuration.kt | 1 + .../kotlin/org/javacs/kt/KotlinLanguageServer.kt | 4 ++-- .../kotlin/org/javacs/kt/KotlinWorkspaceService.kt | 7 +++++++ server/src/main/kotlin/org/javacs/kt/SourceFiles.kt | 9 +++++---- .../test/kotlin/org/javacs/kt/CompiledFileTest.kt | 2 +- .../kotlin/org/javacs/kt/ExclusionsConfiguration.kt | 5 +++++ .../main/kotlin/org/javacs/kt/SourceExclusions.kt | 12 ++++++------ 8 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt diff --git a/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt b/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt index 794377454..12ccd3fbe 100644 --- a/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt +++ b/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt @@ -18,6 +18,7 @@ import java.nio.file.Path class CompilerClassPath( private val config: CompilerConfiguration, private val scriptsConfig: ScriptsConfiguration, + private val exclusionsConfig: ExclusionsConfiguration, private val codegenConfig: CodegenConfiguration, private val databaseService: DatabaseService ) : Closeable { @@ -162,7 +163,7 @@ class CompilerClassPath( private fun findJavaSourceFiles(root: Path): Set { val sourceMatcher = FileSystems.getDefault().getPathMatcher("glob:*.java") - return SourceExclusions(listOf(root), scriptsConfig) + return SourceExclusions(listOf(root), scriptsConfig, exclusionsConfig) .walkIncluded() .filter { sourceMatcher.matches(it.fileName) } .toSet() diff --git a/server/src/main/kotlin/org/javacs/kt/Configuration.kt b/server/src/main/kotlin/org/javacs/kt/Configuration.kt index 58d615ab2..2746eeaa3 100644 --- a/server/src/main/kotlin/org/javacs/kt/Configuration.kt +++ b/server/src/main/kotlin/org/javacs/kt/Configuration.kt @@ -114,4 +114,5 @@ public data class Configuration( val externalSources: ExternalSourcesConfiguration = ExternalSourcesConfiguration(), val inlayHints: InlayHintsConfiguration = InlayHintsConfiguration(), val formatting: FormattingConfiguration = FormattingConfiguration(), + val exclusions: ExclusionsConfiguration = ExclusionsConfiguration(), ) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt index e8da0ff99..13d4d38e2 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt @@ -26,12 +26,12 @@ class KotlinLanguageServer( val config: Configuration = Configuration() ) : LanguageServer, LanguageClientAware, Closeable { val databaseService = DatabaseService() - val classPath = CompilerClassPath(config.compiler, config.scripts, config.codegen, databaseService) + val classPath = CompilerClassPath(config.compiler, config.scripts, config.exclusions, config.codegen, databaseService) private val tempDirectory = TemporaryDirectory() private val uriContentProvider = URIContentProvider(ClassContentProvider(config.externalSources, classPath, tempDirectory, CompositeSourceArchiveProvider(JdkSourceArchiveProvider(classPath), ClassPathSourceArchiveProvider(classPath)))) val sourcePath = SourcePath(classPath, uriContentProvider, config.indexing, databaseService) - val sourceFiles = SourceFiles(sourcePath, uriContentProvider, config.scripts) + val sourceFiles = SourceFiles(sourcePath, uriContentProvider, config.scripts, config.exclusions) private val textDocuments = KotlinTextDocumentService(sourceFiles, sourcePath, config, tempDirectory, uriContentProvider, classPath) private val workspaces = KotlinWorkspaceService(sourceFiles, sourcePath, classPath, textDocuments, config) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt index de5afdfa0..1cee7c05f 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt @@ -188,6 +188,13 @@ class KotlinWorkspaceService( get("useKlsScheme")?.asBoolean?.let { externalSources.useKlsScheme = it } get("autoConvertToKotlin")?.asBoolean?.let { externalSources.autoConvertToKotlin = it } } + + // Update source file exclusions + get("exclusions")?.asJsonObject?.apply { + val exclusions = config.exclusions + get("excludePatterns")?.asString?.let { exclusions.excludePatterns = it } + sf.updateExclusions() + } } LOG.info("Updated configuration: {}", settings) diff --git a/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt b/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt index d7bb84968..e13e96604 100644 --- a/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt +++ b/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt @@ -66,10 +66,11 @@ private class NotifySourcePath(private val sp: SourcePath) { class SourceFiles( private val sp: SourcePath, private val contentProvider: URIContentProvider, - private val scriptsConfig: ScriptsConfiguration + private val scriptsConfig: ScriptsConfiguration, + private val exclusionsConfig: ExclusionsConfiguration, ) { private val workspaceRoots = mutableSetOf() - private var exclusions = SourceExclusions(workspaceRoots, scriptsConfig) + private var exclusions = SourceExclusions(workspaceRoots, scriptsConfig, exclusionsConfig) private val files = NotifySourcePath(sp) private val open = mutableSetOf() @@ -181,7 +182,7 @@ class SourceFiles( private fun findSourceFiles(root: Path): Set { val sourceMatcher = FileSystems.getDefault().getPathMatcher("glob:*.{kt,kts}") - return SourceExclusions(listOf(root), scriptsConfig) + return SourceExclusions(listOf(root), scriptsConfig, exclusionsConfig) .walkIncluded() .filter { sourceMatcher.matches(it.fileName) } .map(Path::toUri) @@ -189,7 +190,7 @@ class SourceFiles( } fun updateExclusions() { - exclusions = SourceExclusions(workspaceRoots, scriptsConfig) + exclusions = SourceExclusions(workspaceRoots, scriptsConfig, exclusionsConfig) LOG.info("Updated exclusions: ${exclusions.excludedPatterns}") } diff --git a/server/src/test/kotlin/org/javacs/kt/CompiledFileTest.kt b/server/src/test/kotlin/org/javacs/kt/CompiledFileTest.kt index d0db2f597..aceabd35b 100644 --- a/server/src/test/kotlin/org/javacs/kt/CompiledFileTest.kt +++ b/server/src/test/kotlin/org/javacs/kt/CompiledFileTest.kt @@ -36,7 +36,7 @@ class CompiledFileTest { val file = testResourcesRoot().resolve("compiledFile/CompiledFileExample.kt") val content = Files.readAllLines(file).joinToString("\n") val parse = compiler.createKtFile(content, file) - val classPath = CompilerClassPath(CompilerConfiguration(), ScriptsConfiguration(), CodegenConfiguration(), DatabaseService()) + val classPath = CompilerClassPath(CompilerConfiguration(), ScriptsConfiguration(), ExclusionsConfiguration(), CodegenConfiguration(), DatabaseService()) val sourcePath = listOf(parse) val (context, container) = compiler.compileKtFiles(sourcePath, sourcePath) CompiledFile(content, parse, context, container, sourcePath, classPath) diff --git a/shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt b/shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt new file mode 100644 index 000000000..a0534e3f3 --- /dev/null +++ b/shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt @@ -0,0 +1,5 @@ +package org.javacs.kt + +data class ExclusionsConfiguration( + var excludePatterns: String = "", // Semicolon-separated list of glob patterns +) diff --git a/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt b/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt index de4a512c2..dd6d16a15 100644 --- a/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt +++ b/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt @@ -7,21 +7,21 @@ import java.nio.file.FileSystems import java.nio.file.Path import java.nio.file.Paths -// TODO: Read exclusions from gitignore/settings.json/... instead of -// hardcoding them class SourceExclusions( private val workspaceRoots: Collection, - private val scriptsConfig: ScriptsConfiguration + private val scriptsConfig: ScriptsConfiguration, + private val exclusionsConfig: ExclusionsConfiguration, ) { - val excludedPatterns = (listOf( + val configuredExclusions = exclusionsConfig.excludePatterns.split(";").map { it.trim() }.filter { it.isNotEmpty() } + val excludedPatterns = listOf( ".git", ".hg", ".svn", // Version control systems ".idea", ".idea_modules", ".vs", ".vscode", ".code-workspace", ".settings", // IDEs - "bazel-*", "bin", "build", "node_modules", "target", // Build systems + "bazel-*", "bin", "node_modules", // Build systems ) + when { !scriptsConfig.enabled -> listOf("*.kts") !scriptsConfig.buildScriptsEnabled -> listOf("*.gradle.kts") else -> emptyList() - }) + } + configuredExclusions private val exclusionMatchers = excludedPatterns .map { FileSystems.getDefault().getPathMatcher("glob:$it") } From addcef3fdae4ab65eb34bd16ddf50ca0656f6562 Mon Sep 17 00:00:00 2001 From: Joseph Barratt Date: Thu, 30 Jan 2025 16:21:40 -0600 Subject: [PATCH 2/4] Handle directory patterns in source exclusions --- .../kotlin/org/javacs/kt/SourceExclusions.kt | 27 ++++++++- .../org/javacs/kt/SourceExclusionsTest.kt | 59 +++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 shared/src/test/kotlin/org/javacs/kt/SourceExclusionsTest.kt diff --git a/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt b/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt index dd6d16a15..f167a3bfd 100644 --- a/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt +++ b/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt @@ -3,7 +3,9 @@ package org.javacs.kt import org.javacs.kt.util.filePath import java.io.File import java.net.URI +import java.util.regex.PatternSyntaxException import java.nio.file.FileSystems +import java.nio.file.PathMatcher import java.nio.file.Path import java.nio.file.Paths @@ -24,7 +26,29 @@ class SourceExclusions( } + configuredExclusions private val exclusionMatchers = excludedPatterns - .map { FileSystems.getDefault().getPathMatcher("glob:$it") } + .map(::parseExcludePattern) + .filterNotNull() + + private fun parseExcludePattern(pattern: String): PathMatcher? { + try { + val normalizedPattern = pattern.removeSuffix("/").trim() + val pathMatcher = + // Takes inspiration from https://git-scm.com/docs/gitignore + if (normalizedPattern.contains("/")) { + FileSystems.getDefault().getPathMatcher("glob:$normalizedPattern") + } else { + PathMatcher { path -> path.any { FileSystems.getDefault().getPathMatcher("glob:$normalizedPattern").matches(it) } } + } + return pathMatcher + } catch (e: IllegalArgumentException) { + LOG.warn("Did not recognize exclude pattern: '{}' ({})", pattern, e.message) + } catch (e: PatternSyntaxException) { + LOG.warn("Did not recognize exclude pattern: '{}' ({})", pattern, e.message) + } catch (e: UnsupportedOperationException) { + LOG.warn("Did not recognize exclude pattern: '{}' ({})", pattern, e.message) + } + return null + } /** Finds all non-excluded files recursively. */ fun walkIncluded(): Sequence = workspaceRoots.asSequence().flatMap { root -> @@ -42,7 +66,6 @@ class SourceExclusions( && exclusionMatchers.none { matcher -> workspaceRoots .mapNotNull { if (file.startsWith(it)) it.relativize(file) else null } - .flatMap { it } // Extract path segments .any(matcher::matches) } } diff --git a/shared/src/test/kotlin/org/javacs/kt/SourceExclusionsTest.kt b/shared/src/test/kotlin/org/javacs/kt/SourceExclusionsTest.kt new file mode 100644 index 000000000..60d8e2ac4 --- /dev/null +++ b/shared/src/test/kotlin/org/javacs/kt/SourceExclusionsTest.kt @@ -0,0 +1,59 @@ +package org.javacs.kt + +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.attribute.PosixFilePermissions +import org.hamcrest.Matchers.equalTo +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Test + +class SourceExclusionsTest { + private val workspaceRoots = listOf(File("/test/workspace1").toPath(), File("/test/workspace2").toPath()) + private var excludePatterns = "" + + @Test + fun `test only default exclusions`() { + assertIncluded("/test/workspace1/src/main/kotlin/MyClass.kt") + assertIncluded("/test/workspace1/build/generated/blah.kt") + assertExcluded("/test/workspace1/.git") + assertExcluded("/test/workspace1/.git/HEAD") + } + + @Test + fun `test configured exclusions`() { + excludePatterns = "build;junk" + assertIncluded("/test/workspace1/src/main/kotlin/MyClass.kt") + assertExcluded("/test/workspace1/build/generated/blah.kt") + assertExcluded("/test/workspace1/src/main/kotlin/junk/blah.kt") + } + + @Test + fun `test configured directory exclusions`() { + excludePatterns = "build/dist/**" + assertIncluded("/test/workspace1/build/generated/blah.kt") + assertIncluded("/test/workspace1/blah/build/dist/blah.kt") + assertExcluded("/test/workspace1/build/dist/blah.kt") + } + + @Test + fun `test configured wildcard directory exclusions`() { + excludePatterns = "**/build/dist/**;build/dist/**" + assertIncluded("/test/workspace1/build/generated/blah.kt") + assertExcluded("/test/workspace1/blah/build/dist/blah.kt") + assertExcluded("/test/workspace1/build/dist/blah.kt") + } + + fun assertExcluded(path: String) { + val exclusions = SourceExclusions(workspaceRoots, ScriptsConfiguration(enabled = true, buildScriptsEnabled = true), ExclusionsConfiguration(excludePatterns=excludePatterns)) + assertFalse("Expected $path to be excluded by $excludePatterns", exclusions.isPathIncluded(File(path).toPath())) + } + + fun assertIncluded(path: String) { + val exclusions = SourceExclusions(workspaceRoots, ScriptsConfiguration(enabled = true, buildScriptsEnabled = true), ExclusionsConfiguration(excludePatterns=excludePatterns)) + assertTrue("Expected $path to be included by $excludePatterns", exclusions.isPathIncluded(File(path).toPath())) + } +} From 50325f6b333cb179aa47946dc3e6b6871a5149c7 Mon Sep 17 00:00:00 2001 From: Joseph Barratt Date: Tue, 4 Feb 2025 13:24:47 -0600 Subject: [PATCH 3/4] Improvements to configurable source exclusions following review --- .../main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt | 2 +- .../main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt | 2 +- shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt | 9 +++++---- .../test/kotlin/org/javacs/kt/SourceExclusionsTest.kt | 8 ++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt index 1cee7c05f..6176bc7cf 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt @@ -192,7 +192,7 @@ class KotlinWorkspaceService( // Update source file exclusions get("exclusions")?.asJsonObject?.apply { val exclusions = config.exclusions - get("excludePatterns")?.asString?.let { exclusions.excludePatterns = it } + get("excludePatterns")?.asJsonArray?.let { exclusions.excludePatterns = it.asList().map { it.asString } } sf.updateExclusions() } } diff --git a/shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt b/shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt index a0534e3f3..183a12d9f 100644 --- a/shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt +++ b/shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt @@ -1,5 +1,5 @@ package org.javacs.kt data class ExclusionsConfiguration( - var excludePatterns: String = "", // Semicolon-separated list of glob patterns + var excludePatterns: List = listOf(), // Semicolon-separated list of glob patterns ) diff --git a/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt b/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt index f167a3bfd..71f443f4b 100644 --- a/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt +++ b/shared/src/main/kotlin/org/javacs/kt/SourceExclusions.kt @@ -14,7 +14,7 @@ class SourceExclusions( private val scriptsConfig: ScriptsConfiguration, private val exclusionsConfig: ExclusionsConfiguration, ) { - val configuredExclusions = exclusionsConfig.excludePatterns.split(";").map { it.trim() }.filter { it.isNotEmpty() } + val configuredExclusions = exclusionsConfig.excludePatterns.map { it.trim() }.filter { it.isNotEmpty() } val excludedPatterns = listOf( ".git", ".hg", ".svn", // Version control systems ".idea", ".idea_modules", ".vs", ".vscode", ".code-workspace", ".settings", // IDEs @@ -30,6 +30,7 @@ class SourceExclusions( .filterNotNull() private fun parseExcludePattern(pattern: String): PathMatcher? { + fun warning(e: Exception) = LOG.warn("Did not recognize exclude pattern: '$pattern' (${e.message})") try { val normalizedPattern = pattern.removeSuffix("/").trim() val pathMatcher = @@ -41,11 +42,11 @@ class SourceExclusions( } return pathMatcher } catch (e: IllegalArgumentException) { - LOG.warn("Did not recognize exclude pattern: '{}' ({})", pattern, e.message) + warning(e) } catch (e: PatternSyntaxException) { - LOG.warn("Did not recognize exclude pattern: '{}' ({})", pattern, e.message) + warning(e) } catch (e: UnsupportedOperationException) { - LOG.warn("Did not recognize exclude pattern: '{}' ({})", pattern, e.message) + warning(e) } return null } diff --git a/shared/src/test/kotlin/org/javacs/kt/SourceExclusionsTest.kt b/shared/src/test/kotlin/org/javacs/kt/SourceExclusionsTest.kt index 60d8e2ac4..934cb9eb5 100644 --- a/shared/src/test/kotlin/org/javacs/kt/SourceExclusionsTest.kt +++ b/shared/src/test/kotlin/org/javacs/kt/SourceExclusionsTest.kt @@ -13,7 +13,7 @@ import org.junit.Test class SourceExclusionsTest { private val workspaceRoots = listOf(File("/test/workspace1").toPath(), File("/test/workspace2").toPath()) - private var excludePatterns = "" + private var excludePatterns = listOf() @Test fun `test only default exclusions`() { @@ -25,7 +25,7 @@ class SourceExclusionsTest { @Test fun `test configured exclusions`() { - excludePatterns = "build;junk" + excludePatterns = listOf("build","junk") assertIncluded("/test/workspace1/src/main/kotlin/MyClass.kt") assertExcluded("/test/workspace1/build/generated/blah.kt") assertExcluded("/test/workspace1/src/main/kotlin/junk/blah.kt") @@ -33,7 +33,7 @@ class SourceExclusionsTest { @Test fun `test configured directory exclusions`() { - excludePatterns = "build/dist/**" + excludePatterns = listOf("build/dist/**") assertIncluded("/test/workspace1/build/generated/blah.kt") assertIncluded("/test/workspace1/blah/build/dist/blah.kt") assertExcluded("/test/workspace1/build/dist/blah.kt") @@ -41,7 +41,7 @@ class SourceExclusionsTest { @Test fun `test configured wildcard directory exclusions`() { - excludePatterns = "**/build/dist/**;build/dist/**" + excludePatterns = listOf("**/build/dist/**", "build/dist/**") assertIncluded("/test/workspace1/build/generated/blah.kt") assertExcluded("/test/workspace1/blah/build/dist/blah.kt") assertExcluded("/test/workspace1/build/dist/blah.kt") From 1953984846a099ff9e9f62828a89392f7e161656 Mon Sep 17 00:00:00 2001 From: Joseph Barratt Date: Tue, 4 Feb 2025 13:25:56 -0600 Subject: [PATCH 4/4] Fix comment text to match code --- shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt b/shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt index 183a12d9f..df8680eea 100644 --- a/shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt +++ b/shared/src/main/kotlin/org/javacs/kt/ExclusionsConfiguration.kt @@ -1,5 +1,5 @@ package org.javacs.kt data class ExclusionsConfiguration( - var excludePatterns: List = listOf(), // Semicolon-separated list of glob patterns + var excludePatterns: List = listOf(), // List of glob patterns )