Chris@18: Chris@18: * @copyright 2019 Juliette Reinders Folmer. All rights reserved. Chris@18: * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence Chris@18: */ Chris@18: Chris@18: /** Chris@18: * Validate the PHP_CodeSniffer PEAR package.xml file. Chris@18: */ Chris@18: class ValidatePEARPackageXML Chris@18: { Chris@18: Chris@18: /** Chris@18: * The root directory of the project. Chris@18: * Chris@18: * @var string Chris@18: */ Chris@18: protected $projectRoot = ''; Chris@18: Chris@18: /** Chris@18: * The contents of the package.xml file. Chris@18: * Chris@18: * @var \SimpleXMLElement Chris@18: */ Chris@18: protected $packageXML; Chris@18: Chris@18: /** Chris@18: * List of all files in the repo. Chris@18: * Chris@18: * @var array Chris@18: */ Chris@18: protected $allFiles = []; Chris@18: Chris@18: /** Chris@18: * Valid file roles. Chris@18: * Chris@18: * @var array Chris@18: * Chris@18: * @link https://pear.php.net/manual/en/developers.packagedef.intro.php#developers.packagedef.roles Chris@18: */ Chris@18: private $validRoles = [ Chris@18: 'data' => true, Chris@18: 'doc' => true, Chris@18: 'ext' => true, Chris@18: 'extsrc' => true, Chris@18: 'php' => true, Chris@18: 'script' => true, Chris@18: 'src' => true, Chris@18: 'test' => true, Chris@18: ]; Chris@18: Chris@18: /** Chris@18: * Files encountered in the package.xml tag. Chris@18: * Chris@18: * @var array Chris@18: */ Chris@18: private $listedContents = []; Chris@18: Chris@18: Chris@18: /** Chris@18: * Constructor. Chris@18: */ Chris@18: public function __construct() Chris@18: { Chris@18: $this->projectRoot = dirname(dirname(__DIR__)).'/'; Chris@18: $this->packageXML = simplexml_load_file($this->projectRoot.'package.xml'); Chris@18: Chris@18: $allFiles = (new FileList($this->projectRoot, $this->projectRoot))->getList(); Chris@18: $this->allFiles = array_flip($allFiles); Chris@18: Chris@18: }//end __construct() Chris@18: Chris@18: Chris@18: /** Chris@18: * Validate the file listings in the package.xml file. Chris@18: * Chris@18: * @return void Chris@18: */ Chris@18: public function validate() Chris@18: { Chris@18: $exitCode = 0; Chris@18: if ($this->checkContents() !== true) { Chris@18: $exitCode = 1; Chris@18: } Chris@18: Chris@18: if ($this->checkPHPRelease() !== true) { Chris@18: $exitCode = 1; Chris@18: } Chris@18: Chris@18: exit($exitCode); Chris@18: Chris@18: }//end validate() Chris@18: Chris@18: Chris@18: /** Chris@18: * Validate the file listings in the tag. Chris@18: * Chris@18: * @return bool Chris@18: */ Chris@18: protected function checkContents() Chris@18: { Chris@18: echo PHP_EOL.'Checking Contents tag'.PHP_EOL; Chris@18: echo '====================='.PHP_EOL; Chris@18: Chris@18: $valid = true; Chris@18: Chris@18: /* Chris@18: * - Check that every file that is mentioned in the `` tag exists in the repo. Chris@18: * - Check that the "role" value is valid. Chris@18: * - Check that the "baseinstalldir" value is valid. Chris@18: */ Chris@18: Chris@18: $valid = $this->walkDirTag($this->packageXML->contents); Chris@18: if ($valid === true) { Chris@18: echo "Existing listings in the Contents tag are valid.".PHP_EOL; Chris@18: } Chris@18: Chris@18: /* Chris@18: * Verify that all files in the `src` and the `tests` directories are listed in the `` tag. Chris@18: */ Chris@18: Chris@18: $srcFiles = (new FileList( Chris@18: $this->projectRoot.'src/', Chris@18: $this->projectRoot, Chris@18: '`\.(css|fixed|inc|js|php|xml)$`Di' Chris@18: ))->getList(); Chris@18: $testsFiles = (new FileList( Chris@18: $this->projectRoot.'tests/', Chris@18: $this->projectRoot, Chris@18: '`\.(css|inc|js|php|xml)$`Di' Chris@18: ))->getList(); Chris@18: $files = array_merge($srcFiles, $testsFiles); Chris@18: Chris@18: foreach ($files as $file) { Chris@18: if (isset($this->listedContents[$file]) === true) { Chris@18: continue; Chris@18: } Chris@18: Chris@18: echo "- File '{$file}' is missing from Contents tag.".PHP_EOL; Chris@18: $valid = false; Chris@18: } Chris@18: Chris@18: if ($valid === true) { Chris@18: echo "No missing files in the Contents tag.".PHP_EOL; Chris@18: } Chris@18: Chris@18: return $valid; Chris@18: Chris@18: }//end checkContents() Chris@18: Chris@18: Chris@18: /** Chris@18: * Validate all child tags within a tag. Chris@18: * Chris@18: * @param \SimpleXMLElement $tag The current XML tag to examine. Chris@18: * @param string $currentDirectory The complete relative path to the Chris@18: * directory being examined. Chris@18: * Chris@18: * @return bool Chris@18: */ Chris@18: protected function walkDirTag($tag, $currentDirectory='') Chris@18: { Chris@18: $valid = true; Chris@18: $name = (string) $tag['name']; Chris@18: if ($name !== '/' && empty($name) === false) { Chris@18: $currentDirectory .= $name.'/'; Chris@18: } Chris@18: Chris@18: $children = $tag->children(); Chris@18: foreach ($children as $key => $value) { Chris@18: if ($key === 'dir') { Chris@18: if ($this->walkDirTag($value, $currentDirectory) === false) { Chris@18: $valid = false; Chris@18: } Chris@18: } Chris@18: Chris@18: if ($key === 'file') { Chris@18: if ($this->checkFileTag($value, $currentDirectory) === false) { Chris@18: $valid = false; Chris@18: } Chris@18: } Chris@18: } Chris@18: Chris@18: return $valid; Chris@18: Chris@18: }//end walkDirTag() Chris@18: Chris@18: Chris@18: /** Chris@18: * Validate the information within a tag. Chris@18: * Chris@18: * @param \SimpleXMLElement $tag The current XML tag to examine. Chris@18: * @param string $currentDirectory The complete relative path to the Chris@18: * directory being examined. Chris@18: * Chris@18: * @return bool Chris@18: */ Chris@18: protected function checkFileTag($tag, $currentDirectory='') Chris@18: { Chris@18: $valid = true; Chris@18: $attributes = $tag->attributes(); Chris@18: $baseinstalldir = (string) $attributes['baseinstalldir']; Chris@18: $name = $currentDirectory.(string) $attributes['name']; Chris@18: $role = (string) $attributes['role']; Chris@18: Chris@18: $this->listedContents[$name] = true; Chris@18: Chris@18: if (empty($name) === true) { Chris@18: echo "- Name attribute missing.".PHP_EOL; Chris@18: $valid = false; Chris@18: } else { Chris@18: if (isset($this->allFiles[$name]) === false) { Chris@18: echo "- File '{$name}' does not exist.".PHP_EOL; Chris@18: $valid = false; Chris@18: } Chris@18: Chris@18: if (empty($role) === true) { Chris@18: echo "- Role attribute missing for file '{$name}'.".PHP_EOL; Chris@18: $valid = false; Chris@18: } else { Chris@18: if (isset($this->validRoles[$role]) === false) { Chris@18: echo "- Role for file '{$name}' is invalid.".PHP_EOL; Chris@18: $valid = false; Chris@18: } else { Chris@18: // Limited validation of the "role" tags. Chris@18: if (strpos($name, 'Test.') !== false && $role !== 'test') { Chris@18: echo "- Test files should have the role 'test'. Found: '$role' for file '{$name}'.".PHP_EOL; Chris@18: $valid = false; Chris@18: } else if ((strpos($name, 'Standard.xml') !== false || strpos($name, 'Sniff.php') !== false) Chris@18: && $role !== 'php' Chris@18: ) { Chris@18: echo "- Sniff files, including sniff documentation files should have the role 'php'. Found: '$role' for file '{$name}'.".PHP_EOL; Chris@18: $valid = false; Chris@18: } Chris@18: } Chris@18: Chris@18: if (empty($baseinstalldir) === true) { Chris@18: if ($role !== 'script' && strpos($name, 'tests/') !== 0) { Chris@18: echo "- Baseinstalldir attribute missing for file '{$name}'.".PHP_EOL; Chris@18: $valid = false; Chris@18: } Chris@18: } else { Chris@18: if ($role === 'script' || strpos($name, 'tests/') === 0) { Chris@18: echo "- Baseinstalldir for file '{$name}' should be empty.".PHP_EOL; Chris@18: $valid = false; Chris@18: } Chris@18: Chris@18: if ($role !== 'script' && $baseinstalldir !== 'PHP/CodeSniffer') { Chris@18: echo "- Baseinstalldir for file '{$name}' is invalid.".PHP_EOL; Chris@18: $valid = false; Chris@18: } Chris@18: } Chris@18: }//end if Chris@18: }//end if Chris@18: Chris@18: return $valid; Chris@18: Chris@18: }//end checkFileTag() Chris@18: Chris@18: Chris@18: /** Chris@18: * Validate the file listings in the tags. Chris@18: * Chris@18: * @return bool True if the info in the "phprelease" tags is valid. False otherwise. Chris@18: */ Chris@18: protected function checkPHPRelease() Chris@18: { Chris@18: echo PHP_EOL.'Checking PHPRelease tags'.PHP_EOL; Chris@18: echo '========================'.PHP_EOL; Chris@18: Chris@18: $valid = true; Chris@18: $listedFiles = []; Chris@18: $releaseTags = 1; Chris@18: Chris@18: /* Chris@18: * - Check that every file that is mentioned in the `` tags exists in the repo. Chris@18: * - Check that the "as" value is valid. Chris@18: */ Chris@18: Chris@18: foreach ($this->packageXML->phprelease as $release) { Chris@18: foreach ($release->filelist->install as $install) { Chris@18: $attributes = $install->attributes(); Chris@18: $name = (string) $attributes['name']; Chris@18: $as = (string) $attributes['as']; Chris@18: Chris@18: $listedFiles[$releaseTags][$name] = $as; Chris@18: Chris@18: if (empty($as) === true || empty($name) === true) { Chris@18: continue; Chris@18: } Chris@18: Chris@18: if (isset($this->allFiles[$name]) === false) { Chris@18: echo "- File '{$name}' does not exist.".PHP_EOL; Chris@18: $valid = false; Chris@18: } Chris@18: Chris@18: // Rest of the checks only apply to the test files. Chris@18: if (strpos($name, 'tests/') !== 0) { Chris@18: continue; Chris@18: } Chris@18: Chris@18: // Check validity of the tags for files in the tests root directory. Chris@18: if (preg_match('`^tests/([^/]+\.php)$`', $name, $matches) === 1 Chris@18: && ($as === $name || $as === $matches[1]) Chris@18: ) { Chris@18: continue; Chris@18: } Chris@18: Chris@18: // Check validity of the tags for files in the tests root subdirectories. Chris@18: if (preg_match('`^tests/.+\.(php|inc|js|css|xml)$`', $name) === 1 Chris@18: && $as === str_replace('tests/', 'CodeSniffer/', $name) Chris@18: ) { Chris@18: continue; Chris@18: } Chris@18: Chris@18: echo "- Invalid 'as' attribute '{$as}' for test file '{$name}'.".PHP_EOL; Chris@18: $valid = false; Chris@18: }//end foreach Chris@18: Chris@18: ++$releaseTags; Chris@18: }//end foreach Chris@18: Chris@18: if ($valid === true) { Chris@18: echo "Existing PHPRelease tags are valid.".PHP_EOL; Chris@18: } Chris@18: Chris@18: /* Chris@18: * Verify that all files in the `tests` directory are listed in both `` tags. Chris@18: */ Chris@18: Chris@18: $testFiles = (new FileList($this->projectRoot.'tests/', $this->projectRoot, '`\.(inc|php|js|css|xml)$`Di'))->getList(); Chris@18: Chris@18: foreach ($testFiles as $file) { Chris@18: foreach ($listedFiles as $key => $listed) { Chris@18: if (isset($listed[$file]) === true) { Chris@18: continue; Chris@18: } Chris@18: Chris@18: echo "- File '{$file}' is missing from PHPRelease tag [{$key}] .".PHP_EOL; Chris@18: $valid = false; Chris@18: } Chris@18: } Chris@18: Chris@18: if ($valid === true) { Chris@18: echo "No missing PHPRelease tags.".PHP_EOL; Chris@18: } Chris@18: Chris@18: return $valid; Chris@18: Chris@18: }//end checkPHPRelease() Chris@18: Chris@18: Chris@18: }//end class