projectRootPath(); $scanRoots = [$root . '/pages']; $modulesDir = $root . '/modules'; if (is_dir($modulesDir)) { foreach (scandir($modulesDir) ?: [] as $moduleEntry) { if ($moduleEntry === '.' || $moduleEntry === '..') { continue; } $modulePages = $modulesDir . '/' . $moduleEntry . '/pages'; if (is_dir($modulePages)) { $scanRoots[] = $modulePages; } } } $violations = []; foreach ($scanRoots as $scanRoot) { if (!is_dir($scanRoot)) { continue; } $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($scanRoot, \FilesystemIterator::SKIP_DOTS)); /** @var \SplFileInfo $file */ foreach ($iterator as $file) { if (!$file->isFile() || $file->getExtension() !== 'phtml') { continue; } $name = $file->getFilename(); $openCount = substr_count($name, '('); $closeCount = substr_count($name, ')'); $relativePath = str_replace($root . '/', '', $file->getPathname()); if ($openCount !== $closeCount) { $violations[] = sprintf('%s — mismatched parens', $relativePath); continue; } // Partials (`_form.phtml`, shared includes) have zero paren groups — skip them. if ($openCount === 0) { continue; } // Routed views must have exactly one paren group: `(