Files
openidea/sdk/rust/openidea-plugin/tests/wit_coverage.rs
l0ng-ai 78e0c7a23e feat(plugins): finalize plugin API before release, concurrent providers
Plugin API (breaking; the plugin system is unreleased):
- Remove the runtime `contributions` interface (register-command,
  register-language, register-theme). Commands, languages and themes are
  declared in plugin.toml only, so everything a plugin contributes goes
  through the install preview and consent. The registry drops runtime
  scope, `contribute` and `dispose_runtime`; inline `definition` in
  language and theme declarations is gone.
- `completion-item.kind` is a string (the 25 LSP kinds; unknown kinds show
  no icon), like the other open-ended kinds.
- `lifecycle.activate` and `commands.run` return `types.error` instead of a
  string. The host maps it to the matching error kind, and only `failed`
  counts against the plugin: cancelling, a missing grant or an unknown
  command does not. The SDK's `Plugin` returns `Result<_, Error>`; plain
  messages convert to `Error::Failed`.

Runtime:
- Editor providers of several plugins are called at the same time
  (`Actor::submit` + `Pending::wait`): the wait is the slowest plugin's,
  not the sum, so each gets the full edit-path budget.

Versioning and tooling:
- Components are checked at install and by `openidea-plugin check` for the
  plugin API they were built for; another major or a newer minor is
  refused with what to do.
- Tests: the SDK's WIT matches the host's, a simulated next-minor host
  links every shipped component, every host function has an SDK wrapper,
  released APIs only grow (no snapshot until the first release).
- plugins/build.sh builds every checked-in component, records a source
  fingerprint next to it, and `--check` fails on stale components; CI runs
  it plus fmt, clippy (host and wasm) and tests for the SDK, the official
  plugins and the fixtures.
- The API evolution rules and the checklist for a new host function are in
  plugins/README.md; the design notes under docs/ are removed.
2026-11-04 02:41:46 +08:00

54 lines
2.3 KiB
Rust

//! Every function the plugin API imports is reachable through the SDK: a new
//! WIT function without a wrapper in `src/` (and so in the mock host, which
//! mirrors the wrappers) fails here instead of going unnoticed by plugin
//! authors. The check is textual: free functions must be called as
//! `wit::<interface>::<function>(`, resource methods as `.<method>(`.
use std::path::Path;
use wit_parser::{FunctionKind, Resolve, WorldItem};
fn sdk_source() -> String {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut out = String::new();
for entry in std::fs::read_dir(dir).unwrap() {
let path = entry.unwrap().path();
if path.extension().is_some_and(|e| e == "rs") {
out.push_str(&std::fs::read_to_string(path).unwrap());
}
}
out
}
#[test]
fn every_imported_function_has_an_sdk_wrapper() {
let mut resolve = Resolve::default();
let (pkg, _) = resolve.push_dir(Path::new(env!("CARGO_MANIFEST_DIR")).join("wit")).unwrap();
let source = sdk_source();
let world = resolve.packages[pkg].worlds["plugin"];
let mut missing = Vec::new();
let mut checked = 0;
for item in resolve.worlds[world].imports.values() {
let WorldItem::Interface { id, .. } = item else { continue };
let iface = &resolve.interfaces[*id];
if iface.package != Some(pkg) {
continue; // WASI
}
let module = iface.name.as_deref().unwrap().replace('-', "_");
for f in iface.functions.values() {
let name = f.item_name().replace('-', "_");
let call = match f.kind {
FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => format!("wit::{module}::{name}("),
FunctionKind::Constructor(_) => "::new(".to_owned(),
FunctionKind::Static(_) | FunctionKind::AsyncStatic(_) => format!("::{name}("),
_ => format!(".{name}("),
};
checked += 1;
if !source.contains(&call) {
missing.push(format!("{}.{} (expected `{call}`)", iface.name.as_deref().unwrap(), f.name));
}
}
}
assert!(checked > 30, "only {checked} functions found: is the world still called `plugin`?");
assert!(missing.is_empty(), "plugin API functions without an SDK wrapper:\n {}", missing.join("\n "));
}