diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-04 03:36:10 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-04 05:43:22 +0900 |
| commit | 261516d5ce6f8b0d69cf9e3da7dd2f0ef1cdc36a (patch) | |
| tree | f635a416745065b52ed0bb0ebb18154b9ef89f3b /crates | |
| parent | 6cb1849473792bd73dbfb6265d363f149f687572 (diff) | |
| download | php-shirabe-261516d5ce6f8b0d69cf9e3da7dd2f0ef1cdc36a.tar.gz php-shirabe-261516d5ce6f8b0d69cf9e3da7dd2f0ef1cdc36a.tar.zst php-shirabe-261516d5ce6f8b0d69cf9e3da7dd2f0ef1cdc36a.zip | |
feat(plugin): generate the worker proxy stubs from the Composer sources
Replace the hand-written proxy stubs under crates/shirabe-php-rpc/php/stubs
with output of scripts/plugin-stub-generator, a deterministic emitter that
derives every stub from the Composer checkout and the classifier report.
Anything it cannot faithfully proxy (by-ref/variadic parameters, magic
methods, public properties, diverging omitted overrides, stale stub files,
a STUB_FILES entry missing in lib.rs) fails generation instead of degrading
silently, so future Composer releases surface new members as explicit
errors rather than silent gaps.
Regenerating the stubs also normalizes the hand-written inconsistencies
(uniform guarded constructors, import-based type spellings) and fixes real
gaps the review of the generated diff uncovered: the BaseIO authentication
methods now carry the real class's untyped signatures, and the previously
missing ConsoleIO::sanitize is materialized together with its private
static helper. A cargo test runs generate-stubs --check to keep the
committed stubs, the generator and the embedded list from drifting apart.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'crates')
11 files changed, 136 insertions, 44 deletions
diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/Composer.php b/crates/shirabe-php-rpc/php/stubs/Composer/Composer.php index 1bb9f64c..de5a6d1b 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/Composer.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/Composer.php @@ -1,17 +1,16 @@ <?php -// Hand-written proxy stub for Composer\Composer, kept in the shape the future stub generator -// will output. Class constants and static methods are materialized (they read no instance -// state); instance methods forward to the Rust-side entity. +// Generated by scripts/plugin-stub-generator; do not edit by hand. +// Proxy stub for Composer\Composer: the public surface forwards to the Rust-side entity over RPC. namespace Composer; -use Composer\Autoload\AutoloadGenerator; -use Composer\Package\Archiver\ArchiveManager; use Composer\Package\Locker; -use Composer\Downloader\DownloadManager; use Composer\Pcre\Preg; use Composer\Plugin\PluginManager; +use Composer\Downloader\DownloadManager; +use Composer\Autoload\AutoloadGenerator; +use Composer\Package\Archiver\ArchiveManager; class Composer extends PartialComposer { diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/EventDispatcher/Event.php b/crates/shirabe-php-rpc/php/stubs/Composer/EventDispatcher/Event.php index 9ce9c12f..fea84c75 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/EventDispatcher/Event.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/EventDispatcher/Event.php @@ -1,7 +1,7 @@ <?php -// Hand-written proxy stub for Composer\EventDispatcher\Event, kept in the shape the future stub -// generator will output: the real public methods, each forwarding to the Rust-side entity. +// Generated by scripts/plugin-stub-generator; do not edit by hand. +// Proxy stub for Composer\EventDispatcher\Event: the public surface forwards to the Rust-side entity over RPC. namespace Composer\EventDispatcher; @@ -12,8 +12,16 @@ class Event implements \ShirabeRustStub /** @var int */ protected $__epoch; - public function __construct(int $rhandle, int $epoch) + public function __construct(int $rhandle = 0, int $epoch = 0) { + if (func_num_args() < 2) { + // Constructing the class from plugin code (a common idiom for e.g. `new BufferIO()`) + // is an open question of the plugin design; only proxy instantiation passes a + // Rust handle. Fail with a diagnosable message instead of an ArgumentCountError. + throw new \RuntimeException( + 'Shirabe does not support constructing ' . static::class . ' inside the plugin process yet' + ); + } $this->__rhandle = $rhandle; $this->__epoch = $epoch; } diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php b/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php index 51cdbe08..55da6415 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php @@ -1,9 +1,7 @@ <?php -// Hand-written proxy stub for Composer\IO\BaseIO, kept in the shape the future stub generator -// will output: every public method of the real class (its own and the IOInterface / -// LoggerInterface surface) forwards to the Rust-side entity. Loading this stub pulls the real -// IOInterface contract from the Composer PHP runtime, which must already be required. +// Generated by scripts/plugin-stub-generator; do not edit by hand. +// Proxy stub for Composer\IO\BaseIO: the public surface forwards to the Rust-side entity over RPC. namespace Composer\IO; @@ -129,22 +127,17 @@ abstract class BaseIO implements IOInterface, \ShirabeRustStub return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getAuthentications', []); } - public function resetAuthentications() - { - return \ShirabeRpcRuntime::callRust($this->__rhandle, 'resetAuthentications', []); - } - - public function hasAuthentication(string $repositoryName) + public function hasAuthentication($repositoryName) { return \ShirabeRpcRuntime::callRust($this->__rhandle, 'hasAuthentication', [$repositoryName]); } - public function getAuthentication(string $repositoryName) + public function getAuthentication($repositoryName) { return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getAuthentication', [$repositoryName]); } - public function setAuthentication(string $repositoryName, string $username, ?string $password = null) + public function setAuthentication($repositoryName, $username, $password = null) { return \ShirabeRpcRuntime::callRust($this->__rhandle, 'setAuthentication', [$repositoryName, $username, $password]); } @@ -198,4 +191,9 @@ abstract class BaseIO implements IOInterface, \ShirabeRustStub { \ShirabeRpcRuntime::callRust($this->__rhandle, 'log', [$level, $message, $context]); } + + public function resetAuthentications() + { + return \ShirabeRpcRuntime::callRust($this->__rhandle, 'resetAuthentications', []); + } } diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/IO/BufferIO.php b/crates/shirabe-php-rpc/php/stubs/Composer/IO/BufferIO.php index e95fb2da..d5e59e90 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/IO/BufferIO.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/IO/BufferIO.php @@ -1,7 +1,7 @@ <?php -// Hand-written proxy stub for Composer\IO\BufferIO, kept in the shape the future stub -// generator will output. +// Generated by scripts/plugin-stub-generator; do not edit by hand. +// Proxy stub for Composer\IO\BufferIO: the public surface forwards to the Rust-side entity over RPC. namespace Composer\IO; diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/IO/ConsoleIO.php b/crates/shirabe-php-rpc/php/stubs/Composer/IO/ConsoleIO.php index 6a4c2ac4..0041363a 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/IO/ConsoleIO.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/IO/ConsoleIO.php @@ -1,15 +1,62 @@ <?php -// Hand-written proxy stub for Composer\IO\ConsoleIO, kept in the shape the future stub -// generator will output. The IOInterface surface is inherited from the BaseIO stub; only the -// public methods ConsoleIO adds are declared here. +// Generated by scripts/plugin-stub-generator; do not edit by hand. +// Proxy stub for Composer\IO\ConsoleIO: the public surface forwards to the Rust-side entity over RPC. namespace Composer\IO; +use Composer\Pcre\Preg; use Symfony\Component\Console\Helper\Table; class ConsoleIO extends BaseIO { + public static function sanitize($messages, bool $allowNewlines = true) + { + // Match ANSI escape sequences: + // - CSI (Control Sequence Introducer): ESC [ params intermediate final + // - OSC (Operating System Command): ESC ] ... ESC \ or BEL + // - Other ESC sequences: ESC followed by any character + $escapePattern = '\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\x1B\].*?(?:\x1B\\\\|\x07)|\x1B.'; + $pattern = $allowNewlines ? "{{$escapePattern}|[\x01-\x09\x0B\x0C\x0E-\x1A]|\r(?!\n)}u" : "{{$escapePattern}|[\x01-\x1A]}u"; + if (is_string($messages)) { + $messages = self::ensureValidUtf8($messages); + return Preg::replace($pattern, '', $messages); + } + + $sanitized = []; + foreach ($messages as $key => $message) { + $message = self::ensureValidUtf8($message); + $sanitized[$key] = Preg::replace($pattern, '', $message); + } + + return $sanitized; + } + + private static function ensureValidUtf8(string $string): string + { + // Quick check: if string is already valid UTF-8, return as-is + if (function_exists('mb_check_encoding') && mb_check_encoding($string, 'UTF-8')) { + return $string; + } + + // Use mb_convert_encoding to replace invalid sequences with '?' + // This makes it visible when data quality issues occur + if (function_exists('mb_convert_encoding')) { + return (string) mb_convert_encoding($string, 'UTF-8', 'UTF-8'); + } + + // Fallback to iconv if mbstring unavailable + if (function_exists('iconv')) { + $cleaned = @iconv('UTF-8', 'UTF-8//TRANSLIT', $string); + if ($cleaned !== false) { + return $cleaned; + } + } + + // Last resort: return as-is (should never happen - Composer requires mbstring OR iconv) + return $string; + } + public function enableDebugging(float $startTime) { return \ShirabeRpcRuntime::callRust($this->__rhandle, 'enableDebugging', [$startTime]); diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/IO/NullIO.php b/crates/shirabe-php-rpc/php/stubs/Composer/IO/NullIO.php index 92e01700..1234ad4a 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/IO/NullIO.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/IO/NullIO.php @@ -1,7 +1,7 @@ <?php -// Hand-written proxy stub for Composer\IO\NullIO, kept in the shape the future stub generator -// will output. The whole surface is inherited from the BaseIO stub. +// Generated by scripts/plugin-stub-generator; do not edit by hand. +// Proxy stub for Composer\IO\NullIO: the public surface forwards to the Rust-side entity over RPC. namespace Composer\IO; diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php b/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php index 2641b267..588800dc 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php @@ -1,18 +1,15 @@ <?php -// Hand-written proxy stub for Composer\PartialComposer, kept in the shape the future stub -// generator will output: the real public methods, each forwarding to the Rust-side entity. -// Methods the Rust dispatcher does not support yet surface as explicit RPC errors. +// Generated by scripts/plugin-stub-generator; do not edit by hand. +// Proxy stub for Composer\PartialComposer: the public surface forwards to the Rust-side entity over RPC. namespace Composer; -use Composer\Autoload\AutoloadGenerator; -use Composer\Config; -use Composer\EventDispatcher\EventDispatcher; -use Composer\Installer\InstallationManager; use Composer\Package\RootPackageInterface; -use Composer\Repository\RepositoryManager; use Composer\Util\Loop; +use Composer\Repository\RepositoryManager; +use Composer\Installer\InstallationManager; +use Composer\EventDispatcher\EventDispatcher; class PartialComposer implements \ShirabeRustStub { diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/Script/Event.php b/crates/shirabe-php-rpc/php/stubs/Composer/Script/Event.php index 80afe1b7..a93d0dfc 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/Script/Event.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/Script/Event.php @@ -1,20 +1,22 @@ <?php -// Hand-written proxy stub for Composer\Script\Event, kept in the shape the future stub -// generator will output. See Composer/EventDispatcher/Event.php. +// Generated by scripts/plugin-stub-generator; do not edit by hand. +// Proxy stub for Composer\Script\Event: the public surface forwards to the Rust-side entity over RPC. namespace Composer\Script; +use Composer\Composer; +use Composer\IO\IOInterface; use Composer\EventDispatcher\Event as BaseEvent; class Event extends BaseEvent { - public function getComposer(): \Composer\Composer + public function getComposer(): Composer { return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getComposer', []); } - public function getIO(): \Composer\IO\IOInterface + public function getIO(): IOInterface { return \ShirabeRpcRuntime::callRust($this->__rhandle, 'getIO', []); } diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs index 9c5dae55..23996a95 100644 --- a/crates/shirabe-php-rpc/src/lib.rs +++ b/crates/shirabe-php-rpc/src/lib.rs @@ -539,8 +539,9 @@ fn call(name: &str, arg: &str) -> PhpMixed { const GLUE_SCRIPT: &str = include_str!("../php/worker.php"); -/// Hand-written proxy stub classes made autoloadable inside the worker, written in the shape the -/// future stub generator will output. +/// Proxy stub classes made autoloadable inside the worker. Generated by +/// `scripts/plugin-stub-generator/generate-stubs`; this list must cover its targets.list (the +/// generator's `--check` mode verifies both the file contents and this list). const STUB_FILES: &[(&str, &str)] = &[ ( "Composer/EventDispatcher/Event.php", diff --git a/crates/shirabe-php-rpc/tests/generated_stubs.rs b/crates/shirabe-php-rpc/tests/generated_stubs.rs new file mode 100644 index 00000000..b4ff8197 --- /dev/null +++ b/crates/shirabe-php-rpc/tests/generated_stubs.rs @@ -0,0 +1,40 @@ +//! Freshness check for the generated proxy stubs: `generate-stubs --check` verifies that the +//! committed stub files and the `STUB_FILES` list in `lib.rs` match what the generator emits +//! from the current Composer checkout and classifier report. +//! +//! The generator needs a PHP interpreter, its composer vendor directory and the classifier +//! report; when any of those is missing the test returns early, following the non-mock test +//! convention of this crate. + +use shirabe_external_packages::symfony::process::PhpExecutableFinder; +use std::path::Path; + +#[test] +fn generated_stubs_are_fresh() { + let Some(php) = PhpExecutableFinder::new().find(false) else { + return; + }; + let tool_dir = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../../scripts/plugin-stub-generator"); + if !tool_dir.join("vendor/autoload.php").is_file() { + return; + } + if !tool_dir + .join("../plugin-class-classifier/report.json") + .is_file() + { + return; + } + + let output = std::process::Command::new(php) + .arg(tool_dir.join("generate-stubs")) + .arg("--check") + .output() + .expect("failed to spawn the stub generator"); + assert!( + output.status.success(), + "generate-stubs --check failed; regenerate the stubs with \ + scripts/plugin-stub-generator/generate-stubs:\n{}", + String::from_utf8_lossy(&output.stderr), + ); +} diff --git a/crates/shirabe/src/plugin/php_plugin_proxy.rs b/crates/shirabe/src/plugin/php_plugin_proxy.rs index 09867079..2b823fa5 100644 --- a/crates/shirabe/src/plugin/php_plugin_proxy.rs +++ b/crates/shirabe/src/plugin/php_plugin_proxy.rs @@ -90,8 +90,8 @@ pub(crate) fn io_stub_class( } else if any.downcast_ref::<crate::io::null_io::NullIO>().is_some() { Ok("Composer\\IO\\NullIO") } else { - // TODO(plugin): only the IO classes with hand-written proxy stubs can cross the - // boundary until a stub generator exists. + // TODO(plugin): only IO implementations with a generated proxy stub can cross the + // boundary; the rest are an explicit error until stubs of their own are generated. Err(anyhow::anyhow!( "no proxy stub class is available for this IO implementation" )) |
