123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601 |
- <?php
- /*
- * $Id: PhpCodeSnifferTask.php 905 2010-10-05 16:28:03Z mrook $
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * This software consists of voluntary contributions made by many individuals
- * and is licensed under the LGPL. For more information please see
- * <http://phing.info>.
- */
- require_once 'phing/Task.php';
- /**
- * A PHP code sniffer task. Checking the style of one or more PHP source files.
- *
- * @author Dirk Thomas <dirk.thomas@4wdmedia.de>
- * @version $Id: PhpCodeSnifferTask.php 905 2010-10-05 16:28:03Z mrook $
- * @package phing.tasks.ext
- */
- class PhpCodeSnifferTask extends Task {
- protected $file; // the source file (from xml attribute)
- protected $filesets = array(); // all fileset objects assigned to this task
- // parameters for php code sniffer
- protected $standard = 'Generic';
- protected $sniffs = array();
- protected $showWarnings = true;
- protected $showSources = false;
- protected $reportWidth = 80;
- protected $verbosity = 0;
- protected $tabWidth = 0;
- protected $allowedFileExtensions = array('php');
- protected $ignorePatterns = false;
- protected $noSubdirectories = false;
- protected $configData = array();
- // parameters to customize output
- protected $showSniffs = false;
- protected $format = 'default';
- protected $formatters = array();
- /**
- * Holds the type of the doc generator
- *
- * @var string
- */
- protected $docGenerator = '';
- /**
- * Holds the outfile for the documentation
- *
- * @var PhingFile
- */
- protected $docFile = null;
- private $haltonerror = false;
- private $haltonwarning = false;
- /**
- * Load the necessary environment for running PHP_CodeSniffer.
- *
- * @throws BuildException
- * @return void
- */
- public function init()
- {
- /**
- * Determine PHP_CodeSniffer version number
- */
- preg_match('/\d\.\d\.\d/', shell_exec('phpcs --version'), $version);
- if (version_compare($version[0], '1.2.2') < 0) {
- throw new BuildException(
- 'PhpCodeSnifferTask requires PHP_CodeSniffer version >= 1.2.2',
- $this->getLocation()
- );
- }
- }
- /**
- * File to be performed syntax check on
- * @param PhingFile $file
- */
- public function setFile(PhingFile $file) {
- $this->file = $file;
- }
- /**
- * Nested creator, creates a FileSet for this task
- *
- * @return FileSet The created fileset object
- */
- function createFileSet() {
- $num = array_push($this->filesets, new FileSet());
- return $this->filesets[$num-1];
- }
- /**
- * Sets the coding standard to test for
- *
- * @param string $standard The coding standard
- *
- * @return void
- */
- public function setStandard($standard)
- {
- if (!class_exists('PHP_CodeSniffer')) {
- include_once 'PHP/CodeSniffer.php';
- }
- if (PHP_CodeSniffer::isInstalledStandard($standard) === false) {
- // They didn't select a valid coding standard, so help them
- // out by letting them know which standards are installed.
- $installedStandards = PHP_CodeSniffer::getInstalledStandards();
- $numStandards = count($installedStandards);
- $errMsg = '';
- if ($numStandards === 0) {
- $errMsg = 'No coding standards are installed.';
- } else {
- $lastStandard = array_pop($installedStandards);
- if ($numStandards === 1) {
- $errMsg = 'The only coding standard installed is ' . $lastStandard;
- } else {
- $standardList = implode(', ', $installedStandards);
- $standardList .= ' and ' . $lastStandard;
- $errMsg = 'The installed coding standards are ' . $standardList;
- }
- }
- throw new BuildException(
- 'ERROR: the "' . $standard . '" coding standard is not installed. ' . $errMsg,
- $this->getLocation()
- );
- }
- $this->standard = $standard;
- }
- /**
- * Sets the sniffs which the standard should be restricted to
- * @param string $sniffs
- */
- public function setSniffs($sniffs)
- {
- $token = ' ,;';
- $sniff = strtok($sniffs, $token);
- while ($sniff !== false) {
- $this->sniffs[] = $sniff;
- $sniff = strtok($token);
- }
- }
- /**
- * Sets the type of the doc generator
- *
- * @param string $generator HTML or Text
- *
- * @return void
- */
- public function setDocGenerator($generator)
- {
- $this->docGenerator = $generator;
- }
- /**
- * Sets the outfile for the documentation
- *
- * @param PhingFile $file The outfile for the doc
- *
- * @return void
- */
- public function setDocFile(PhingFile $file)
- {
- $this->docFile = $file;
- }
- /**
- * Sets the flag if warnings should be shown
- * @param boolean $show
- */
- public function setShowWarnings($show)
- {
- $this->showWarnings = StringHelper::booleanValue($show);
- }
- /**
- * Sets the flag if sources should be shown
- *
- * @param boolean $show Whether to show sources or not
- *
- * @return void
- */
- public function setShowSources($show)
- {
- $this->showSources = StringHelper::booleanValue($show);
- }
- /**
- * Sets the width of the report
- *
- * @param int $width How wide the screen reports should be.
- *
- * @return void
- */
- public function setReportWidth($width)
- {
- $this->reportWidth = (int) $width;
- }
- /**
- * Sets the verbosity level
- * @param int $level
- */
- public function setVerbosity($level)
- {
- $this->verbosity = (int)$level;
- }
- /**
- * Sets the tab width to replace tabs with spaces
- * @param int $width
- */
- public function setTabWidth($width)
- {
- $this->tabWidth = (int)$width;
- }
- /**
- * Sets the allowed file extensions when using directories instead of specific files
- * @param array $extensions
- */
- public function setAllowedFileExtensions($extensions)
- {
- $this->allowedFileExtensions = array();
- $token = ' ,;';
- $ext = strtok($extensions, $token);
- while ($ext !== false) {
- $this->allowedFileExtensions[] = $ext;
- $ext = strtok($token);
- }
- }
- /**
- * Sets the ignore patterns to skip files when using directories instead of specific files
- * @param array $extensions
- */
- public function setIgnorePatterns($patterns)
- {
- $this->ignorePatterns = array();
- $token = ' ,;';
- $pattern = strtok($patterns, $token);
- while ($pattern !== false) {
- $this->ignorePatterns[] = $pattern;
- $pattern = strtok($token);
- }
- }
- /**
- * Sets the flag if subdirectories should be skipped
- * @param boolean $subdirectories
- */
- public function setNoSubdirectories($subdirectories)
- {
- $this->noSubdirectories = StringHelper::booleanValue($subdirectories);
- }
- /**
- * Creates a config parameter for this task
- *
- * @return Parameter The created parameter
- */
- public function createConfig() {
- $num = array_push($this->configData, new Parameter());
- return $this->configData[$num-1];
- }
- /**
- * Sets the flag if the used sniffs should be listed
- * @param boolean $show
- */
- public function setShowSniffs($show)
- {
- $this->showSniffs = StringHelper::booleanValue($show);
- }
- /**
- * Sets the output format
- * @param string $format
- */
- public function setFormat($format)
- {
- $this->format = $format;
- }
- /**
- * Create object for nested formatter element.
- * @return CodeSniffer_FormatterElement
- */
- public function createFormatter () {
- $num = array_push($this->formatters,
- new PhpCodeSnifferTask_FormatterElement());
- return $this->formatters[$num-1];
- }
- /**
- * Sets the haltonerror flag
- * @param boolean $value
- */
- function setHaltonerror($value)
- {
- $this->haltonerror = $value;
- }
- /**
- * Sets the haltonwarning flag
- * @param boolean $value
- */
- function setHaltonwarning($value)
- {
- $this->haltonwarning = $value;
- }
- /**
- * Executes PHP code sniffer against PhingFile or a FileSet
- */
- public function main() {
- if (!class_exists('PHP_CodeSniffer')) {
- include_once 'PHP/CodeSniffer.php';
- }
- if(!isset($this->file) and count($this->filesets) == 0) {
- throw new BuildException("Missing either a nested fileset or attribute 'file' set");
- }
- if (count($this->formatters) == 0) {
- // turn legacy format attribute into formatter
- $fmt = new PhpCodeSnifferTask_FormatterElement();
- $fmt->setType($this->format);
- $fmt->setUseFile(false);
- $this->formatters[] = $fmt;
- }
- if (!isset($this->file))
- {
- $fileList = array();
- $project = $this->getProject();
- foreach ($this->filesets as $fs) {
- $ds = $fs->getDirectoryScanner($project);
- $files = $ds->getIncludedFiles();
- $dir = $fs->getDir($this->project)->getAbsolutePath();
- foreach ($files as $file) {
- $fileList[] = $dir.DIRECTORY_SEPARATOR.$file;
- }
- }
- }
- $codeSniffer = new PHP_CodeSniffer($this->verbosity, $this->tabWidth);
- $codeSniffer->setAllowedFileExtensions($this->allowedFileExtensions);
- if (is_array($this->ignorePatterns)) $codeSniffer->setIgnorePatterns($this->ignorePatterns);
- foreach ($this->configData as $configData) {
- $codeSniffer->setConfigData($configData->getName(), $configData->getValue(), true);
- }
- if ($this->file instanceof PhingFile) {
- $codeSniffer->process($this->file->getPath(), $this->standard, $this->sniffs, $this->noSubdirectories);
- } else {
- $codeSniffer->process($fileList, $this->standard, $this->sniffs, $this->noSubdirectories);
- }
- $report = $this->printErrorReport($codeSniffer);
- // generate the documentation
- if ($this->docGenerator !== '' && $this->docFile !== null) {
- ob_start();
- $codeSniffer->generateDocs($this->standard, $this->sniffs, $this->docGenerator);
- $output = ob_get_contents();
- ob_end_clean();
- // write to file
- $outputFile = $this->docFile->getPath();
- $check = file_put_contents($outputFile, $output);
- if (is_bool($check) && !$check) {
- throw new BuildException('Error writing doc to ' . $outputFile);
- }
- } elseif ($this->docGenerator !== '' && $this->docFile === null) {
- $codeSniffer->generateDocs($this->standard, $this->sniffs, $this->docGenerator);
- }
- if ($this->haltonerror && $report['totals']['errors'] > 0)
- {
- throw new BuildException('phpcodesniffer detected ' . $report['totals']['errors']. ' error' . ($report['totals']['errors'] > 1 ? 's' : ''));
- }
- if ($this->haltonwarning && $report['totals']['warnings'] > 0)
- {
- throw new BuildException('phpcodesniffer detected ' . $report['totals']['warnings'] . ' warning' . ($report['totals']['warnings'] > 1 ? 's' : ''));
- }
- }
- /**
- * Prints the error report.
- *
- * @param PHP_CodeSniffer $phpcs The PHP_CodeSniffer object containing
- * the errors.
- *
- * @return int The number of error and warning messages shown.
- */
- protected function printErrorReport($phpcs)
- {
- if ($this->showSniffs) {
- $sniffs = $phpcs->getSniffs();
- $sniffStr = '';
- foreach ($sniffs as $sniff) {
- $sniffStr .= '- ' . $sniff.PHP_EOL;
- }
- $this->log('The list of used sniffs (#' . count($sniffs) . '): ' . PHP_EOL . $sniffStr, Project::MSG_INFO);
- }
- $filesViolations = $phpcs->getFilesErrors();
- $reporting = new PHP_CodeSniffer_Reporting();
- $report = $reporting->prepare($filesViolations, $this->showWarnings);
- // process output
- foreach ($this->formatters as $fe) {
- switch ($fe->getType()) {
- case 'default':
- // default format goes to logs, no buffering
- $this->outputCustomFormat($report);
- $fe->setUseFile(false);
- break;
- default:
- $reportFile = '';
- if ($fe->getUseFile()) {
- $reportFile = $fe->getOutfile()->getPath();
- ob_start();
- }
- $reporting->printReport(
- $fe->getType(),
- $filesViolations,
- $this->showWarnings,
- $this->showSources,
- $reportFile,
- $this->reportWidth
- );
- // reporting class uses ob_end_flush(), but we don't want
- // an output if we use a file
- if ($fe->getUseFile()) {
- ob_end_clean();
- }
- break;
- }
- }
- return $report;
- }
- /**
- * Outputs the results with a custom format
- *
- * @param array $report Packaged list of all errors in each file
- */
- protected function outputCustomFormat($report) {
- $files = $report['files'];
- foreach ($files as $file => $attributes) {
- $errors = $attributes['errors'];
- $warnings = $attributes['warnings'];
- $messages = $attributes['messages'];
- if ($errors > 0) {
- $this->log($file . ': ' . $errors . ' error' . ($errors > 1 ? 's' : '') . ' detected', Project::MSG_ERR);
- $this->outputCustomFormatMessages($messages, 'ERROR');
- } else {
- $this->log($file . ': No syntax errors detected', Project::MSG_VERBOSE);
- }
- if ($warnings > 0) {
- $this->log($file . ': ' . $warnings . ' warning' . ($warnings > 1 ? 's' : '') . ' detected', Project::MSG_WARN);
- $this->outputCustomFormatMessages($messages, 'WARNING');
- }
- }
- $totalErrors = $report['totals']['errors'];
- $totalWarnings = $report['totals']['warnings'];
- $this->log(count($files) . ' files where checked', Project::MSG_INFO);
- if ($totalErrors > 0) {
- $this->log($totalErrors . ' error' . ($totalErrors > 1 ? 's' : '') . ' detected', Project::MSG_ERR);
- } else {
- $this->log('No syntax errors detected', Project::MSG_INFO);
- }
- if ($totalWarnings > 0) {
- $this->log($totalWarnings . ' warning' . ($totalWarnings > 1 ? 's' : '') . ' detected', Project::MSG_INFO);
- }
- }
- /**
- * Outputs the messages of a specific type for one file
- * @param array $messages
- * @param string $type
- */
- protected function outputCustomFormatMessages($messages, $type) {
- foreach ($messages as $line => $messagesPerLine) {
- foreach ($messagesPerLine as $column => $messagesPerColumn) {
- foreach ($messagesPerColumn as $message) {
- $msgType = $message['type'];
- if ($type == $msgType) {
- $logLevel = Project::MSG_INFO;
- if ($msgType == 'ERROR') {
- $logLevel = Project::MSG_ERR;
- } else if ($msgType == 'WARNING') {
- $logLevel = Project::MSG_WARN;
- }
- $text = $message['message'];
- $string = $msgType . ' in line ' . $line . ' column ' . $column . ': ' . $text;
- $this->log($string, $logLevel);
- }
- }
- }
- }
- }
- } //end phpCodeSnifferTask
- class PhpCodeSnifferTask_FormatterElement extends DataType {
- /**
- * Type of output to generate
- * @var string
- */
- protected $type = "";
- /**
- * Output to file?
- * @var bool
- */
- protected $useFile = true;
- /**
- * Output file.
- * @var string
- */
- protected $outfile = "";
- /**
- * Validate config.
- */
- public function parsingComplete () {
- if(empty($this->type)) {
- throw new BuildException("Format missing required 'type' attribute.");
- }
- if ($useFile && empty($this->outfile)) {
- throw new BuildException("Format requires 'outfile' attribute when 'useFile' is true.");
- }
- }
- public function setType ($type) {
- $this->type = $type;
- }
- public function getType () {
- return $this->type;
- }
- public function setUseFile ($useFile) {
- $this->useFile = $useFile;
- }
- public function getUseFile () {
- return $this->useFile;
- }
- public function setOutfile (PhingFile $outfile) {
- $this->outfile = $outfile;
- }
- public function getOutfile () {
- return $this->outfile;
- }
- } //end FormatterElement
|