, indirect ones as lit/ (reached via mcp-adapter-execute-ability), * and error codes without the internal lit_ prefix of the WP_Error code. */ final class ToolNamingTest extends TestCase { /** @return array Ability name without "lit/" => isDirect(). */ private static function abilities(): array { $root = dirname(__DIR__, 3); $abilities = []; $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root . '/src')); foreach ($files as $file) { if (! str_ends_with((string) $file, 'Ability.php')) { continue; } $class = 'LIT\\MCP\\' . str_replace(['/', '.php'], ['\\', ''], substr((string) $file, strlen($root . '/src/'))); $reflection = new ReflectionClass($class); if ($reflection->isInterface() || ! $reflection->implementsInterface(Ability::class)) { continue; } /** @var Ability $ability */ $ability = $reflection->newInstanceWithoutConstructor(); $abilities[substr($ability->name(), strlen('lit/'))] = $ability->isDirect(); } ksort($abilities); return $abilities; } /** @return array Path => content of the guides and all PHP sources. */ private static function texts(): array { $root = dirname(__DIR__, 3); $texts = []; foreach (glob($root . '/docs/guides/*.md') ?: [] as $file) { $texts[$file] = (string) file_get_contents($file); } $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root . '/src')); foreach ($files as $file) { if (str_ends_with((string) $file, '.php')) { $texts[(string) $file] = (string) file_get_contents((string) $file); } } return $texts; } public function testIndirectToolsAreNeverNamedLikeDirectOnes(): void { $abilities = self::abilities(); self::assertArrayHasKey('menu-set-items', $abilities); foreach (self::texts() as $path => $text) { preg_match_all('/\blit-([a-z]+(?:-[a-z]+)*)/', $text, $matches); foreach ($matches[1] as $name) { self::assertFalse( ($abilities[$name] ?? true) === false, sprintf('%s names the indirect tool lit/%s as lit-%s', basename($path), $name, $name) ); } preg_match_all('#\blit/([a-z]+(?:-[a-z]+)*)#', $text, $matches); foreach ($matches[1] as $name) { self::assertFalse( ($abilities[$name] ?? false) === true && str_ends_with($path, '.md'), sprintf('%s names the direct tool lit-%s as lit/%s', basename($path), $name, $name) ); } } } public function testGuidesUseTheErrorCodesTheClientSees(): void { foreach (self::texts() as $path => $text) { if (str_ends_with($path, '.md')) { self::assertDoesNotMatchRegularExpression('/`lit_(?!mcp)[a-z_]+`/', $text, basename($path)); } } } public function testOverviewListsExactlyTheDirectTools(): void { $overview = (string) file_get_contents(dirname(__DIR__, 3) . '/docs/guides/overview.md'); preg_match('/Direkt sichtbar:(.*?)\n\n/s', $overview, $section); preg_match_all('/`lit-([a-z-]+)`/', $section[1] ?? '', $listed); $direct = array_keys(array_filter(self::abilities())); $listedNames = $listed[1]; sort($listedNames); self::assertSame($direct, $listedNames); } }