Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions src/main/kotlin/com/florexlabs/docscribe/runner/DocscribeDaemon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import kotlin.concurrent.Volatile
* - `update_types` — refresh YARD docs from RBS signatures.
* - `shutdown` — graceful server stop.
*/
@Suppress("TooManyFunctions")
@Suppress("TooManyFunctions", "LargeClass")
@Service(Service.Level.PROJECT)
class DocscribeDaemon(
private val project: Project,
Expand Down Expand Up @@ -166,7 +166,16 @@ class DocscribeDaemon(
formatJson: Boolean = false,
): RunResult {
val handle = synchronized(lock) { ensureRunning(projectDir) } ?: return fallback(command, file, projectDir, formatJson)
val params = if (command == "update_types") buildUpdateTypesParams(projectDir, file) else buildExecuteParams(file, projectDir)
// Daemons before 1.6.2 crash on ANY cli_overrides (their ConfigBuilder
// calls options[:include].any? on the partial hash -> NoMethodError).
// Omit overrides there; the daemon falls back to its docscribe.yml.
val includeCliOverrides = supportsCliOverrides(capabilities?.version)
val params =
if (command == "update_types") {
buildUpdateTypesParams(projectDir, file, includeCliOverrides)
} else {
buildExecuteParams(file, projectDir, includeCliOverrides)
}
val response = performRpcCall(handle, command, params)
// Fallback for older daemons that don't support update_types (< 1.6.2)
if (command == "update_types" && isUnknownMethodError(response)) {
Expand All @@ -183,9 +192,10 @@ class DocscribeDaemon(
internal fun buildUpdateTypesParams(
projectDir: String?,
file: String? = null,
includeCliOverrides: Boolean = true,
): Map<String, Any?> {
val dir = projectDir ?: project.basePath ?: "."
return Companion.buildUpdateTypesParams(dir, file)
return Companion.buildUpdateTypesParams(dir, file, includeCliOverrides)
}

/**
Expand All @@ -209,7 +219,7 @@ class DocscribeDaemon(
return fallback("check", file = null, projectDir = projectDir, formatJson = true)
}
val effectiveDir = projectDir ?: project.basePath ?: ""
val params = buildBatchParams(files, effectiveDir)
val params = buildBatchParams(files, effectiveDir, includeCliOverrides = supportsCliOverrides(capabilities?.version))
val response = rpcCall(handle, "check_batch", params)
return processBatchResponse(response, projectDir)
}
Expand Down Expand Up @@ -274,6 +284,7 @@ class DocscribeDaemon(
private fun buildExecuteParams(
file: String?,
projectDir: String?,
includeCliOverrides: Boolean = true,
): Map<String, Any?> {
val dir = projectDir ?: project.basePath ?: ""
val map =
Expand All @@ -282,6 +293,7 @@ class DocscribeDaemon(
"project_dir" to dir,
"no_boilerplate" to true,
)
if (!includeCliOverrides) return map
val cliOverrides = buildRbsCliOverrides(dir)
if (cliOverrides != null) map["cli_overrides"] = cliOverrides
return map
Expand Down Expand Up @@ -929,6 +941,27 @@ class DocscribeDaemon(
private const val BATCH_PER_FILE_TIMEOUT_SECONDS = 120L
private const val SERVER_MODE_MIN_VERSION = "1.5.1"
private const val BATCH_MODE_MIN_VERSION = "1.5.2"
private const val CLI_OVERRIDES_MIN_VERSION = "1.6.2"
private const val VERSION_PARTS_COUNT = 3

/**
* Whether the daemon understands the `cli_overrides` RPC parameter.
*
* Daemons before 1.6.2 merge overrides raw into `ConfigBuilder`, whose
* `filter_overrides?` calls `options[:include].any?` on the partial hash
* and crashes with `NoMethodError: undefined method 'any?' for nil`.
* Unknown versions are assumed new (preserves current behavior).
*
* @param version Full version string (e.g. `"1.6.2"`), or `null` if unknown.
* @return `true` unless the version is known to be older than 1.6.2.
*/
@JvmStatic
fun supportsCliOverrides(version: String?): Boolean {
val v = version?.trim()?.takeIf { it.isNotEmpty() } ?: return true
val parts = v.split(".").map { it.toIntOrNull() ?: return true }
return parts.size == VERSION_PARTS_COUNT && atLeast(parts, CLI_OVERRIDES_MIN_VERSION)
}

private val sharedGson by lazy { GsonBuilder().create() }

/**
Expand All @@ -944,6 +977,7 @@ class DocscribeDaemon(
files: List<String>,
projectDir: String,
timeoutSeconds: Long = BATCH_PER_FILE_TIMEOUT_SECONDS,
includeCliOverrides: Boolean = true,
): Map<String, Any?> {
val map =
mutableMapOf<String, Any?>(
Expand All @@ -952,6 +986,7 @@ class DocscribeDaemon(
"no_boilerplate" to true,
"timeout" to timeoutSeconds,
)
if (!includeCliOverrides) return map
val cliOverrides = buildRbsCliOverridesStatic(projectDir)
if (cliOverrides != null) map["cli_overrides"] = cliOverrides
return map
Expand Down Expand Up @@ -992,9 +1027,11 @@ class DocscribeDaemon(
internal fun buildUpdateTypesParams(
projectDir: String,
file: String? = null,
includeCliOverrides: Boolean = true,
): Map<String, Any?> {
val map = mutableMapOf<String, Any?>("dir" to projectDir)
if (file != null) map["file"] = file
if (!includeCliOverrides) return map
val cliOverrides = buildRbsCliOverridesStatic(projectDir)
if (cliOverrides != null) map["cli_overrides"] = cliOverrides
return map
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.florexlabs.docscribe.runner

import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.File
import java.nio.file.Files

class DocscribeDaemonCliOverridesTest {
@Test
fun `supportsCliOverrides false for gems before 1_6_2`() {
assertFalse(DocscribeDaemon.supportsCliOverrides("1.6.1"))
assertFalse(DocscribeDaemon.supportsCliOverrides("1.6.0"))
assertFalse(DocscribeDaemon.supportsCliOverrides("1.5.2"))
assertFalse(DocscribeDaemon.supportsCliOverrides("1.5.1"))
}

@Test
fun `supportsCliOverrides true for 1_6_2 and newer`() {
assertTrue(DocscribeDaemon.supportsCliOverrides("1.6.2"))
assertTrue(DocscribeDaemon.supportsCliOverrides("1.7.0"))
assertTrue(DocscribeDaemon.supportsCliOverrides("2.0.0"))
}

@Test
fun `supportsCliOverrides true for unknown versions`() {
assertTrue(DocscribeDaemon.supportsCliOverrides(null))
assertTrue(DocscribeDaemon.supportsCliOverrides(""))
assertTrue(DocscribeDaemon.supportsCliOverrides("dev"))
}

@Test
fun `buildUpdateTypesParams omits cli_overrides when gated off`() {
val dir = Files.createTempDirectory("rbs-gate-update").toFile()
try {
val sig = File(dir, "sig")
sig.mkdir()
File(sig, "a.rbs").writeText("class A; end")
val params = DocscribeDaemon.buildUpdateTypesParams(dir.absolutePath, includeCliOverrides = false)
assertFalse(params.containsKey("cli_overrides"))
} finally {
dir.deleteRecursively()
}
}

@Test
fun `buildBatchParams omits cli_overrides when gated off`() {
val dir = Files.createTempDirectory("rbs-gate-batch").toFile()
try {
val sig = File(dir, "sig")
sig.mkdir()
File(sig, "b.rbs").writeText("class B; end")
val params =
DocscribeDaemon.buildBatchParams(
listOf("/tmp/a.rb"),
dir.absolutePath,
includeCliOverrides = false,
)
assertFalse(params.containsKey("cli_overrides"))
} finally {
dir.deleteRecursively()
}
}
}
Loading