Skip to content

ADFA-3588: Introduce plugin API binary compatibility tracking and version contract #1265

Open
Daniel-ADFA wants to merge 5 commits into
stagefrom
feat/ADFA-3588
Open

ADFA-3588: Introduce plugin API binary compatibility tracking and version contract #1265
Daniel-ADFA wants to merge 5 commits into
stagefrom
feat/ADFA-3588

Conversation

@Daniel-ADFA
Copy link
Copy Markdown
Contributor

Replaces the meaningless min_ide_version plugin compatibility check with a real plugin API versioning scheme backed by binary-compatibility-validator. The previous field could never enforce anything:

IDE version strings are timestamps (C-r-MMDD-HHMM), not comparable to the SemVer values plugins were declaring, and the field was only checked for non-blankness anyway.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Release Notes

New Features

  • Plugin API Versioning Scheme: Introduced PluginApiVersion - a semantic versioning (SemVer) based system for tracking plugin API compatibility, with current version set to 1.0.0
  • Binary Compatibility Tracking: Integrated binary-compatibility-validator (v0.18.1) to enforce API contracts and detect binary-incompatible changes to the public plugin API surface
  • Plugin API Compatibility Validation: Added PluginApiVersionChecker that validates plugin API compatibility at load time by requiring identical major versions and accepting backward-compatible minor/patch updates
  • Comprehensive Public Plugin API: Defined complete public API surface covering:
    • Core plugin lifecycle (IPlugin, PluginContext, PluginLogger)
    • Plugin metadata and permissions (PluginMetadata, PluginPermission)
    • IDE service integrations (command execution, editor manipulation, project discovery, etc.)
    • Extension points (build actions, editor features, code completion, documentation, snippets, project templates, etc.)
    • Plugin UI helpers (PluginFragmentHelper)
  • Internal API Annotation: Introduced @InternalPluginApi annotation to explicitly mark non-stable APIs requiring opt-in at compile time

Changed / Deprecated

  • Minimum IDE Version Field Deprecated: minIdeVersion in PluginMetadata is now deprecated in favor of minPluginApiVersion and retained only for binary compatibility with existing plugins
  • Plugin Manifest Metadata: Plugin manifests now use plugin.min_plugin_api_version instead of plugin.min_ide_version
  • Plugin Security Validation: Removed the requirement for minIdeVersion to be non-blank during manifest validation; now only mainClass is required
  • Migration Path: Plugins still declaring only plugin.min_ide_version will trigger a migration warning during load

Breaking Changes

  • Plugin API compatibility is now actively enforced; plugins with incompatible major versions or requiring newer API versions than the IDE provides will fail to load with PluginApiIncompatibleException
  • Any plugins already using version strings in minIdeVersion must migrate to minPluginApiVersion with proper SemVer formatting

Risks & Best Practices

  • Baseline Lock: Setting CURRENT to 1.0.0 establishes this as the binary compatibility baseline; any future API changes must increment the version accordingly
  • Breaking Change Detection: The binary compatibility validator will block commits that introduce unintended API breakages, which is beneficial but requires careful planning for intentional major version bumps
  • Plugin Compatibility: Existing plugins relying on min_ide_version will continue to work but with deprecation warnings; developers should update their plugin manifests
  • SemVer Strict Validation: Plugin API version strings must follow strict SemVer format (x.y.z); malformed versions will cause load failures rather than silent fallbacks

Testing

  • Added comprehensive test suite for PluginApiVersion covering SemVer parsing, version comparison, shorthand formats, and malformed input handling
  • Added integration tests for PluginApiVersionChecker validating compatibility logic across version combinations and exception scenarios

Walkthrough

Adds a public plugin API surface and PluginApiVersion type, reads min_plugin_api_version from manifests, enforces semantic compatibility via PluginApiVersionChecker (with specific exception handling), updates loader logging, and wires binary-compatibility validation into the build.

Changes

Plugin API & Compatibility

Layer / File(s) Summary
Public plugin API surface
plugin-api/api/plugin-api.api
Adds comprehensive public plugin API: IPlugin, PluginContext, extensions, services, data models, and CgtTemplateBuilder.
PluginApiVersion and metadata
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/IPlugin.kt, plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/PluginApiVersion.kt
Introduces PluginApiVersion value class and adds PluginMetadata.minPluginApiVersion while deprecating minIdeVersion.
Loader, Manifest & logging changes
plugin-manager/src/main/kotlin/.../loaders/PluginLoader.kt, plugin-manager/src/main/kotlin/.../loaders/PluginManifest.kt
Reads plugin.min_plugin_api_version from manifests, maps it into PluginMetadata, makes IDE-version fields nullable/deprecated, and replaces Android Log calls with SLF4J logging in the loader.
Compatibility checker, exception, manager flow & tests
plugin-manager/src/main/kotlin/.../security/PluginApiVersionChecker.kt, plugin-manager/src/main/kotlin/.../security/PluginApiIncompatibleException.kt, plugin-manager/src/main/kotlin/.../core/PluginManager.kt, plugin-manager/src/test/kotlin/...
Adds compatibility rules (same major, required <= current), throws typed PluginApiIncompatibleException on mismatch, integrates checks into plugin load flow with dedicated handling, and adds unit tests for parsing and compatibility behavior.
Build, templates, UI & validation tweak
build.gradle.kts, gradle/libs.versions.toml, templates-impl/src/main/java/.../pluginManifest.kt, plugin-manager/build.gradle.kts, app/src/main/java/.../PluginManagerActivity.kt, plugin-manager/src/main/kotlin/.../PluginSecurityManager.kt
Registers binary-compatibility-validator plugin (v0.18.1), adds module test dependency, switches generated manifest meta-data key to plugin.min_plugin_api_version, updates plugin details UI to show minPluginApiVersion, and relaxes manifest minIdeVersion requirement.

Sequence Diagram(s)

sequenceDiagram
  participant PluginManager
  participant PluginLoader
  participant PluginApiVersionChecker
  participant IPlugin
  PluginManager->>PluginLoader: getPluginMetadata()
  PluginLoader-->>PluginManager: PluginManifest (with minPluginApiVersion)
  PluginManager->>PluginApiVersionChecker: requireCompatible(pluginId, requiredVersion, currentVersion)
  alt Compatible
    PluginApiVersionChecker-->>PluginManager: Validation passes
    PluginManager->>IPlugin: initialize/activate
    IPlugin-->>PluginManager: Plugin loads successfully
  else Incompatible
    PluginApiVersionChecker-->>PluginManager: throws PluginApiIncompatibleException
    PluginManager->>PluginManager: Release sidebar slots & fail load
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • jatezzz
  • jomen-adfa

"🐰
I stitched semvers with nimble paws,
Manifests now show newer laws,
Checker hops in, guards the gate,
Plugins match or meet their fate,
A little carrot for versioned cause."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately and specifically describes the main objective: introducing plugin API binary compatibility tracking and enforcing a version contract, which is the primary focus of all the changes in the changeset.
Description check ✅ Passed The PR description is directly related to the changeset, explaining the motivation for replacing min_ide_version with a real plugin API versioning scheme backed by binary-compatibility-validator.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ADFA-3588

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginLoader.kt (1)

243-264: ⚠️ Potential issue | 🟠 Major

Don't silently promote missing min_plugin_api_version to 1.0.0.

This makes pre-contract plugins look compatible with the new API gate. Because this PR also changes the public plugin API surface, older plugins that haven't been rebuilt can still pass load-time validation and then fail later with linkage/runtime errors. Safer rollout options are: require the field explicitly, or map “missing” to a distinct legacy bucket that the loader blocks until those plugins are republished.

🛠️ Minimal first step
-            val pluginMinPluginApiVersion = metaData.getString("plugin.min_plugin_api_version") ?: "1.0.0"
+            val pluginMinPluginApiVersion = metaData.getString("plugin.min_plugin_api_version")

Then handle null explicitly at the load gate instead of treating it as a valid 1.0.0 declaration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginLoader.kt`
around lines 243 - 264, The code currently coerces a missing
metaData.getString("plugin.min_plugin_api_version") into "1.0.0"; instead,
change pluginMinPluginApiVersion to preserve null (e.g., val
pluginMinPluginApiVersion = metaData.getString("plugin.min_plugin_api_version"))
and update the PluginManifest data class to accept a nullable
minPluginApiVersion (or a distinct legacy marker) so the loader can detect
"missing" explicitly; then enforce the load-time gate (in PluginLoader.load /
validation code) to reject or route null/legacy entries rather than treating
them as compatible with "1.0.0".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiVersionChecker.kt`:
- Around line 42-48: The parse(raw: String) function can throw
NumberFormatException when converting oversized numeric segments with toInt();
wrap the Version(...) construction inside a try-catch that catches
NumberFormatException (and any other NumberFormat-related exceptions you
consider relevant) and return null on failure so malformed/overflowing inputs
map to null as expected by requireCompatible(); update/add a regression test to
feed an oversized segment like "999999999999.0.0" and assert parse returns null
(or that requireCompatible yields MALFORMED_VERSION).

---

Outside diff comments:
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginLoader.kt`:
- Around line 243-264: The code currently coerces a missing
metaData.getString("plugin.min_plugin_api_version") into "1.0.0"; instead,
change pluginMinPluginApiVersion to preserve null (e.g., val
pluginMinPluginApiVersion = metaData.getString("plugin.min_plugin_api_version"))
and update the PluginManifest data class to accept a nullable
minPluginApiVersion (or a distinct legacy marker) so the loader can detect
"missing" explicitly; then enforce the load-time gate (in PluginLoader.load /
validation code) to reject or route null/legacy entries rather than treating
them as compatible with "1.0.0".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce3d0688-fe90-4873-93cd-f6c7e5f45251

📥 Commits

Reviewing files that changed from the base of the PR and between c9ed03b and 4c78cfa.

📒 Files selected for processing (15)
  • app/src/main/java/com/itsaky/androidide/activities/PluginManagerActivity.kt
  • build.gradle.kts
  • gradle/libs.versions.toml
  • plugin-api/api/plugin-api.api
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/IPlugin.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/InternalPluginApi.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/PluginApiVersion.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginLoader.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginManifest.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiIncompatibleException.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiVersionChecker.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/security/PluginSecurityManager.kt
  • plugin-manager/src/test/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiVersionCheckerTest.kt
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/pluginProject/pluginManifest.kt

Comment on lines +42 to +48
private fun parse(raw: String): Version? {
val match = SEMVER.matchEntire(raw.trim()) ?: return null
return Version(
major = match.groupValues[1].toInt(),
minor = match.groupValues[2].ifBlank { "0" }.toInt(),
patch = match.groupValues[3].ifBlank { "0" }.toInt(),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiVersionChecker.kt

Repository: appdevforall/CodeOnTheGo

Length of output: 2453


🏁 Script executed:

find . -type f -name "*PluginApiVersionChecker*Test*" -o -name "*Test*PluginApiVersionChecker*"

Repository: appdevforall/CodeOnTheGo

Length of output: 179


🏁 Script executed:

rg -l "PluginApiVersionChecker" --type kotlin

Repository: appdevforall/CodeOnTheGo

Length of output: 373


🏁 Script executed:

cat -n ./plugin-manager/src/test/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiVersionCheckerTest.kt

Repository: appdevforall/CodeOnTheGo

Length of output: 5711


Catch NumberFormatException in parse() to properly handle oversized numeric segments.

Input like 999999999999.0.0 matches the SEMVER regex but causes toInt() to throw NumberFormatException on overflow, escaping as an uncaught exception instead of returning null. This breaks the intended contract where requireCompatible() expects null to signal MALFORMED_VERSION. Wrap the Version construction in a try-catch block to return null on parse failure, and add a regression test for numeric overflow.

🔧 Targeted fix
     private fun parse(raw: String): Version? {
         val match = SEMVER.matchEntire(raw.trim()) ?: return null
-        return Version(
-            major = match.groupValues[1].toInt(),
-            minor = match.groupValues[2].ifBlank { "0" }.toInt(),
-            patch = match.groupValues[3].ifBlank { "0" }.toInt(),
-        )
+        return try {
+            Version(
+                major = match.groupValues[1].toInt(),
+                minor = match.groupValues[2].ifBlank { "0" }.toInt(),
+                patch = match.groupValues[3].ifBlank { "0" }.toInt(),
+            )
+        } catch (_: NumberFormatException) {
+            null
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiVersionChecker.kt`
around lines 42 - 48, The parse(raw: String) function can throw
NumberFormatException when converting oversized numeric segments with toInt();
wrap the Version(...) construction inside a try-catch that catches
NumberFormatException (and any other NumberFormat-related exceptions you
consider relevant) and return null on failure so malformed/overflowing inputs
map to null as expected by requireCompatible(); update/add a regression test to
feed an oversized segment like "999999999999.0.0" and assert parse returns null
(or that requireCompatible yields MALFORMED_VERSION).

private val SEMVER = Regex("^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?$")

fun isCompatible(required: String, current: String): Boolean {
val r = parse(required) ?: return false
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you improve the variable names?

}

fun requireCompatible(pluginId: String, required: String, current: String) {
val r = parse(required) ?: throw PluginApiIncompatibleException(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

availableVersion = current,
reason = PluginApiIncompatibleException.Reason.MALFORMED_VERSION,
)
val c = parse(current) ?: error("Invalid current plugin API version: '$current'")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here

Comment on lines +1 to +11
package com.itsaky.androidide.plugins

/**
* Plugin API contract version (SemVer). Bump major for binary-incompatible changes
* flagged by `:plugin-api:apiCheck`, minor for additive changes; then run
* `:plugin-api:apiDump` to refresh `plugin-api/api/plugin-api.api`.
*/
object PluginApiVersion {

const val CURRENT: String = "1.0.0"
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugin versions are structured, so using raw Strings is probably not the correct approach, at least at compile time. I suggest using a proper value type instead, which gives us type-safety, correct ordering and keeps the major/minor semantics :

@JvmInline
value class PluginApiVersion private constructor(val raw: String) : Comparable<PluginApiVersion> {
    val major: Int get() = parts()[0]
    val minor: Int get() = parts()[1]
    val patch: Int get() = parts()[2]

    private fun parts() = raw.split('.').map { it.toInt() }

    override fun compareTo(other: PluginApiVersion): Int =
        compareValuesBy(this, other, { it.major }, { it.minor }, { it.patch })

    companion object {
        /** the current plugin API version */
        val CURRENT = parse("1.0.0")
        
        fun parseOrThrow(s: String) = requireNotNull(parse(s)) { "s is invalid" } 
        fun parse(s: String): PluginApiVersion? {
          /* validate major.minor.patch, or return null */
        }
    }
}

A @JvmInline value class ensures that the versions are still strings at runtime, but gives us type-safety at compile-time. After this, we'd use PluginAPiVersion everywhere instead of raw Strings.

Comment on lines +401 to +402
required = manifest.minPluginApiVersion ?: "1.0.0",
current = PluginApiVersion.CURRENT,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace with PluginApiVersion type.

val pluginMinIdeVersion = metaData.getString("plugin.min_ide_version")
val pluginMaxIdeVersion = metaData.getString("plugin.max_ide_version")
if (pluginMinPluginApiVersion == null && pluginMinIdeVersion != null) {
Log.w(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use SF4J Logger instances instead of android.util.Log.

Comment on lines +44 to +46
message = "IDE version strings are timestamps and cannot be compared. Plugin API " +
"compatibility is enforced through minPluginApiVersion. Kept so manifests from " +
"existing plugins keep parsing; will be removed in the next major plugin API release."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This string is repeated at least 3 times. Can we please store it in a const val property somewhere?

Comment on lines +114 to +115
minIdeVersion = minIdeVersion ?: "1.0.0",
minPluginApiVersion = minPluginApiVersion ?: PluginApiVersion.CURRENT,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace with PluginApiVersion type.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace raw strings with PluginApiVersion type.

The comparison can be moved into PluginApiVersion as well, or not (either is fine).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/PluginApiVersion.kt (1)

41-44: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

parse() accepts overflowed numeric segments that later crash on comparison.

At Line 41-44, regex-only validation allows values like 999999999999.0.0; then parts() (Line 29-31) throws NumberFormatException when major/minor/patch are accessed. That breaks the nullable-parse contract and can escape the intended MALFORMED_VERSION path.

🔧 Targeted fix
 fun parse(raw: String): PluginApiVersion? {
     val trimmed = raw.trim()
-    return if (SEMVER.matches(trimmed)) PluginApiVersion(trimmed) else null
+    if (!SEMVER.matches(trimmed)) return null
+    return try {
+        PluginApiVersion(trimmed).also { it.parts() } // force numeric-range validation
+    } catch (_: NumberFormatException) {
+        null
+    }
 }
Kotlin String.toInt() behavior: does it throw NumberFormatException for values outside Int range (e.g., "999999999999")?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/PluginApiVersion.kt`
around lines 41 - 44, The parse() method currently relies only on SEMVER regex
and then constructs PluginApiVersion(trimmed), which later calls parts() and can
throw NumberFormatException for overflowed numeric segments (e.g.,
"999999999999"); change parse() to safely validate numeric segments before
constructing PluginApiVersion: after trimming and SEMVER.matches(trimmed), split
the string into segments (reuse pieces from parts() logic), convert each segment
using safe parsing (e.g., String.toIntOrNull or try-catch around toInt) and
reject the version (return null) if any segment is null or out of Int range,
only then call PluginApiVersion(trimmed); reference parse(), SEMVER,
PluginApiVersion, and parts() in your change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@plugin-manager/src/test/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiVersionTest.kt`:
- Around line 44-51: Add a regression test case for numeric overflow by calling
PluginApiVersion.parse("999999999999.0.0") and assert it returns null (and
optionally assert PluginApiVersion.parseOrThrow("999999999999.0.0") throws);
place this new assertion alongside the existing malformed inputs in the `parse
rejects malformed input by returning null` test to cover the conversion path
used in PluginApiVersion.parse (and protect lines performing numeric
conversion).

---

Duplicate comments:
In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/PluginApiVersion.kt`:
- Around line 41-44: The parse() method currently relies only on SEMVER regex
and then constructs PluginApiVersion(trimmed), which later calls parts() and can
throw NumberFormatException for overflowed numeric segments (e.g.,
"999999999999"); change parse() to safely validate numeric segments before
constructing PluginApiVersion: after trimming and SEMVER.matches(trimmed), split
the string into segments (reuse pieces from parts() logic), convert each segment
using safe parsing (e.g., String.toIntOrNull or try-catch around toInt) and
reject the version (return null) if any segment is null or out of Int range,
only then call PluginApiVersion(trimmed); reference parse(), SEMVER,
PluginApiVersion, and parts() in your change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a77bac80-7b6b-4ba9-8710-4b7270ce016a

📥 Commits

Reviewing files that changed from the base of the PR and between 4c78cfa and 614d85d.

📒 Files selected for processing (11)
  • plugin-api/api/plugin-api.api
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/IPlugin.kt
  • plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/PluginApiVersion.kt
  • plugin-manager/build.gradle.kts
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginLoader.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/loaders/PluginManifest.kt
  • plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiVersionChecker.kt
  • plugin-manager/src/test/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiVersionCheckerTest.kt
  • plugin-manager/src/test/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiVersionTest.kt
  • templates-impl/src/main/java/com/itsaky/androidide/templates/impl/pluginProject/pluginManifest.kt

Comment on lines +44 to +51
fun `parse rejects malformed input by returning null`() {
assertNull(PluginApiVersion.parse("v1.0.0"))
assertNull(PluginApiVersion.parse("1.0.0-snapshot"))
assertNull(PluginApiVersion.parse("1.0.0.0"))
assertNull(PluginApiVersion.parse(""))
assertNull(PluginApiVersion.parse("abc"))
assertNull(PluginApiVersion.parse("1.x.0"))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an overflow regression case to malformed-version tests.

Please add a case like PluginApiVersion.parse("999999999999.0.0") and assert null (and optionally parseOrThrow throws). This protects the Line 29-31 conversion path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugin-manager/src/test/kotlin/com/itsaky/androidide/plugins/manager/security/PluginApiVersionTest.kt`
around lines 44 - 51, Add a regression test case for numeric overflow by calling
PluginApiVersion.parse("999999999999.0.0") and assert it returns null (and
optionally assert PluginApiVersion.parseOrThrow("999999999999.0.0") throws);
place this new assertion alongside the existing malformed inputs in the `parse
rejects malformed input by returning null` test to cover the conversion path
used in PluginApiVersion.parse (and protect lines performing numeric
conversion).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants