diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-07-21 02:54:18 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-07-23 22:07:16 +0900 |
| commit | 765b7749d31675d3cbf541b146ead1aafd6d6ae9 (patch) | |
| tree | 11f8a07cd9e3d4ef92bb2f75a0811e41b1d928ba /scripts/plugin-class-classifier/src/Report.php | |
| parent | 770013c7097b6b5c0c49c7fa46d9235c308c4ad5 (diff) | |
| download | php-shirabe-765b7749d31675d3cbf541b146ead1aafd6d6ae9.tar.gz php-shirabe-765b7749d31675d3cbf541b146ead1aafd6d6ae9.tar.zst php-shirabe-765b7749d31675d3cbf541b146ead1aafd6d6ae9.zip | |
feat(plugin-class-classifier): add deterministic plugin-boundary classifier
Decides, for every composer/composer class, how it is treated at the
plugin boundary (rust-proxy / rust-snapshot / contract / two-world /
php-native / unsupported) so that upstream updates re-classify new or
rewritten classes without re-deriving the design by hand. Rules and
category definitions live in docs/dev/plugin-class-classification.md;
the tool (PHP + nikic/PHP-Parser) implements them as a reachability
closure with direction marks, per-method pure/mutator analysis, and a
leaf-first fixed point for unreachable classes, with three small
versioned exception lists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'scripts/plugin-class-classifier/src/Report.php')
| -rw-r--r-- | scripts/plugin-class-classifier/src/Report.php | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/scripts/plugin-class-classifier/src/Report.php b/scripts/plugin-class-classifier/src/Report.php new file mode 100644 index 00000000..e24363fe --- /dev/null +++ b/scripts/plugin-class-classifier/src/Report.php @@ -0,0 +1,103 @@ +<?php + +declare(strict_types=1); + +namespace Shirabe\PluginClassifier; + +final class Report +{ + public function __construct(private readonly Classifier $classifier) + { + } + + public function writeJson(string $path): void + { + $vendor = []; + foreach ($this->classifier->closure->vendorReachable as $fqcn => $bits) { + $info = VendorPackages::lookup($fqcn); + $vendor[] = [ + 'fqcn' => $fqcn, + 'package' => $info['package'], + 'category' => $info['category'], + 'direction' => $bits === 3 ? 'both' : ($bits === 1 ? 'provided' : 'consumed'), + ]; + } + usort($vendor, static fn (array $a, array $b) => strcmp($a['fqcn'], $b['fqcn'])); + + $builtins = array_keys($this->classifier->closure->builtinReachable); + sort($builtins); + + $vendorPackages = []; + foreach (VendorPackages::PREFIXES as $prefix => $info) { + $vendorPackages[$info['package']] = $info['category']; + } + ksort($vendorPackages); + + $data = [ + 'categories' => $this->countByCategory(), + 'classes' => array_values($this->classifier->rows), + 'vendorPackages' => $vendorPackages, + 'vendorTypes' => $vendor, + 'reachableBuiltins' => $builtins, + 'violations' => $this->classifier->violations, + ]; + + $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + if (file_put_contents($path, $json . "\n") === false) { + throw new \RuntimeException("cannot write $path"); + } + } + + public function printSummary(): void + { + $counts = $this->countByCategory(); + echo "# Plugin boundary classification\n\n"; + echo "| category | classes |\n|---|---|\n"; + foreach ($counts as $category => $count) { + echo "| $category | $count |\n"; + } + + echo "\n## Reachable surface\n\n"; + foreach (['rust-proxy', 'rust-snapshot', 'contract'] as $category) { + $names = []; + foreach ($this->classifier->rows as $row) { + if ($row['category'] === $category && $row['reachable']) { + $names[] = $row['fqcn'] . ($row['direction'] === 'both' ? ' (both)' : ''); + } + } + echo '### ' . $category . ' (' . count($names) . ")\n\n"; + foreach ($names as $name) { + echo "- $name\n"; + } + echo "\n"; + } + + $overridden = array_filter($this->classifier->rows, static fn (array $r) => isset($r['computedCategory'])); + if ($overridden !== []) { + echo "## Overridden\n\n"; + foreach ($overridden as $row) { + echo "- {$row['fqcn']}: {$row['computedCategory']} -> {$row['category']}\n"; + } + echo "\n"; + } + + if ($this->classifier->violations !== []) { + echo "## Violations\n\n"; + foreach ($this->classifier->violations as $violation) { + echo "- $violation\n"; + } + echo "\n"; + } + } + + /** @return array<string, int> */ + private function countByCategory(): array + { + $counts = array_fill_keys(Classifier::CATEGORIES, 0); + foreach ($this->classifier->rows as $row) { + $counts[$row['category']]++; + } + + return $counts; + } +} |
