Files
openidea/sdk/rust/openidea-plugin/tests/wit_coverage.rs
T
Pedro Pessoa 8259a81335 feat(plugins): let plugins colour tokens in files of other languages (#1882)
Plugin API 1.3 adds an optional semantic-tokens-provider export (world plugin-semantic-tokens). A plugin declares the semantic-tokens kind under [[contributes.providers]] for any language, including ones it does not contribute, and returns single-line tokens with LSP token kinds and modifiers. The host asks it with the other editor providers (50 ms / 500 ms) when the editor requests semantic tokens and joins the result with the language server's: plugin tokens replace the server tokens they overlap, so the colour scheme styles them like any semantic token. Declaring the kind needs engines.openidea ^1.3.

API 1.2 shipped with 0.1.38, so wit/ and the SDK's copy, ENGINE_VERSION and both API_VERSION constants move to 1.3 and every shipped component is rebuilt. The SDK gains openidea_plugin::semantic_tokens, and register_plugin! accepts any combination of optional exports (providers, views, indexing, syntax, semantic_tokens) instead of a fixed list of pairs.
2026-11-07 04:30:30 +08:00

83 lines
3.5 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 "));
}
#[test]
fn every_optional_export_can_be_registered() {
let mut resolve = Resolve::default();
let (pkg, _) = resolve.push_dir(Path::new(env!("CARGO_MANIFEST_DIR")).join("wit")).unwrap();
let source = sdk_source();
let base = &resolve.worlds[resolve.packages[pkg].worlds["plugin"]];
let mut missing = Vec::new();
let mut checked = 0;
for (name, &id) in &resolve.packages[pkg].worlds {
if !name.starts_with("plugin-") {
continue;
}
for (key, item) in &resolve.worlds[id].exports {
let WorldItem::Interface { id, .. } = item else { continue };
if base.exports.contains_key(key) {
continue;
}
let module = resolve.interfaces[*id].name.as_deref().unwrap().replace('-', "_");
let guest = format!("exports::openidea::plugin::{module}::Guest for");
checked += 1;
if !source.contains(&guest) {
missing.push(format!("{name}: {module}"));
}
}
}
assert!(checked >= 5, "only {checked} optional exports found");
assert!(missing.is_empty(), "optional exports register_plugin! can't export:\n {}", missing.join("\n "));
}