PhpLintTask.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <?php
  2. /*
  3. * $Id: PhpLintTask.php 905 2010-10-05 16:28:03Z mrook $
  4. *
  5. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16. *
  17. * This software consists of voluntary contributions made by many individuals
  18. * and is licensed under the LGPL. For more information please see
  19. * <http://phing.info>.
  20. */
  21. require_once 'phing/Task.php';
  22. require_once 'phing/util/DataStore.php';
  23. require_once 'phing/system/io/FileWriter.php';
  24. /**
  25. * A PHP lint task. Checking syntax of one or more PHP source file.
  26. *
  27. * @author Knut Urdalen <knut.urdalen@telio.no>
  28. * @author Stefan Priebsch <stefan.priebsch@e-novative.de>
  29. * @version $Id: PhpLintTask.php 905 2010-10-05 16:28:03Z mrook $
  30. * @package phing.tasks.ext
  31. */
  32. class PhpLintTask extends Task {
  33. protected $file; // the source file (from xml attribute)
  34. protected $filesets = array(); // all fileset objects assigned to this task
  35. protected $errorProperty;
  36. protected $haltOnFailure = false;
  37. protected $hasErrors = false;
  38. protected $badFiles = array();
  39. protected $interpreter = ''; // php interpreter to use for linting
  40. protected $logLevel = Project::MSG_INFO;
  41. protected $cache = null;
  42. protected $tofile = null;
  43. protected $deprecatedAsError = false;
  44. /**
  45. * Initialize the interpreter with the Phing property
  46. */
  47. public function __construct() {
  48. $this->setInterpreter(Phing::getProperty('php.interpreter'));
  49. }
  50. /**
  51. * Override default php interpreter
  52. * @todo Do some sort of checking if the path is correct but would
  53. * require traversing the systems executeable path too
  54. * @param string $sPhp
  55. */
  56. public function setInterpreter($sPhp) {
  57. $this->Interpreter = $sPhp;
  58. }
  59. /**
  60. * The haltonfailure property
  61. * @param boolean $aValue
  62. */
  63. public function setHaltOnFailure($aValue) {
  64. $this->haltOnFailure = $aValue;
  65. }
  66. /**
  67. * File to be performed syntax check on
  68. * @param PhingFile $file
  69. */
  70. public function setFile(PhingFile $file) {
  71. $this->file = $file;
  72. }
  73. /**
  74. * Set an property name in which to put any errors.
  75. * @param string $propname
  76. */
  77. public function setErrorproperty($propname)
  78. {
  79. $this->errorProperty = $propname;
  80. }
  81. /**
  82. * Whether to store last-modified times in cache
  83. *
  84. * @param PhingFile $file
  85. */
  86. public function setCacheFile(PhingFile $file)
  87. {
  88. $this->cache = new DataStore($file);
  89. }
  90. /**
  91. * Whether to store last-modified times in cache
  92. *
  93. * @param PhingFile $file
  94. */
  95. public function setToFile(PhingFile $tofile)
  96. {
  97. $this->tofile = $tofile;
  98. }
  99. /**
  100. * Nested creator, creates a FileSet for this task
  101. *
  102. * @return FileSet The created fileset object
  103. */
  104. function createFileSet() {
  105. $num = array_push($this->filesets, new FileSet());
  106. return $this->filesets[$num-1];
  107. }
  108. /**
  109. * Set level of log messages generated (default = info)
  110. * @param string $level
  111. */
  112. public function setLevel($level)
  113. {
  114. switch ($level)
  115. {
  116. case "error": $this->logLevel = Project::MSG_ERR; break;
  117. case "warning": $this->logLevel = Project::MSG_WARN; break;
  118. case "info": $this->logLevel = Project::MSG_INFO; break;
  119. case "verbose": $this->logLevel = Project::MSG_VERBOSE; break;
  120. case "debug": $this->logLevel = Project::MSG_DEBUG; break;
  121. }
  122. }
  123. /**
  124. * Sets whether to treat deprecated warnings (introduced in PHP 5.3) as errors
  125. * @param boolean $deprecatedAsError
  126. */
  127. public function setDeprecatedAsError($deprecatedAsError)
  128. {
  129. $this->deprecatedAsError = $deprecatedAsError;
  130. }
  131. /**
  132. * Execute lint check against PhingFile or a FileSet
  133. */
  134. public function main() {
  135. if(!isset($this->file) and count($this->filesets) == 0) {
  136. throw new BuildException("Missing either a nested fileset or attribute 'file' set");
  137. }
  138. if($this->file instanceof PhingFile) {
  139. $this->lint($this->file->getPath());
  140. } else { // process filesets
  141. $project = $this->getProject();
  142. foreach($this->filesets as $fs) {
  143. $ds = $fs->getDirectoryScanner($project);
  144. $files = $ds->getIncludedFiles();
  145. $dir = $fs->getDir($this->project)->getPath();
  146. foreach($files as $file) {
  147. $this->lint($dir.DIRECTORY_SEPARATOR.$file);
  148. }
  149. }
  150. }
  151. // write list of 'bad files' to file (if specified)
  152. if ($this->tofile) {
  153. $writer = new FileWriter($this->tofile);
  154. foreach ($this->badFiles as $file => $msg) {
  155. $writer->write($file . "=" . $msg . PHP_EOL);
  156. }
  157. $writer->close();
  158. }
  159. if ($this->haltOnFailure && $this->hasErrors) throw new BuildException('Syntax error(s) in PHP files: '.implode(', ',$this->badFiles));
  160. }
  161. /**
  162. * Performs the actual syntax check
  163. *
  164. * @param string $file
  165. * @return void
  166. */
  167. protected function lint($file) {
  168. $command = $this->Interpreter == ''
  169. ? 'php'
  170. : $this->Interpreter;
  171. $command .= ' -l ';
  172. if(file_exists($file)) {
  173. if(is_readable($file)) {
  174. if ($this->cache)
  175. {
  176. $lastmtime = $this->cache->get($file);
  177. if ($lastmtime >= filemtime($file))
  178. {
  179. $this->log("Not linting '" . $file . "' due to cache", Project::MSG_DEBUG);
  180. return false;
  181. }
  182. }
  183. $messages = array();
  184. exec($command.'"'.$file.'" 2>&1', $messages);
  185. if(!preg_match('/^No syntax errors detected/', $messages[0])) {
  186. if (count($messages) < 2 ) {
  187. $this->log("Could not parse file", Project::MSG_ERR);
  188. } else {
  189. for ($i = 0; $i < count($messages) - 1; $i++) {
  190. $message = $messages[$i];
  191. if (trim($message) == '') {
  192. continue;
  193. }
  194. if (!preg_match('/^(.*)Deprecated:/', $message) || $this->deprecatedAsError) {
  195. $this->log($message, $this->logLevel);
  196. if ($this->errorProperty) {
  197. $this->project->setProperty($this->errorProperty, $message);
  198. }
  199. if (!isset($this->badFiles[$file])) {
  200. $this->badFiles[$file] = $message;
  201. }
  202. $this->hasErrors = true;
  203. }
  204. }
  205. }
  206. } else {
  207. $this->log($file.': No syntax errors detected', $this->logLevel);
  208. }
  209. if ($this->cache)
  210. {
  211. $this->cache->put($file, filemtime($file));
  212. }
  213. } else {
  214. throw new BuildException('Permission denied: '.$file);
  215. }
  216. } else {
  217. throw new BuildException('File not found: '.$file);
  218. }
  219. }
  220. }