sintonia/library/phing/tasks/ext/PhpCodeSnifferTask.php

601 lines
18 KiB
PHP
Raw Normal View History

<?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