postprocess-phan.php 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. <?php
  2. abstract class Suppressor {
  3. /**
  4. * @param string $input
  5. * @return bool do errors remain
  6. */
  7. abstract public function suppress( $input );
  8. /**
  9. * @param string[] $source
  10. * @param string $type
  11. * @param int $lineno
  12. * @return bool
  13. */
  14. protected function isSuppressed( array $source, $type, $lineno ) {
  15. return $lineno > 0 && preg_match(
  16. "|/\*\* @suppress {$type} |",
  17. $source[$lineno - 1]
  18. );
  19. }
  20. }
  21. class TextSuppressor extends Suppressor {
  22. /**
  23. * @param string $input
  24. * @return bool do errors remain
  25. */
  26. public function suppress( $input ) {
  27. $hasErrors = false;
  28. $errors = [];
  29. foreach ( explode( "\n", $input ) as $error ) {
  30. if ( empty( $error ) ) {
  31. continue;
  32. }
  33. if ( !preg_match( '/^(.*):(\d+) (Phan\w+) (.*)$/', $error, $matches ) ) {
  34. echo "Failed to parse line: $error\n";
  35. continue;
  36. }
  37. list( $source, $file, $lineno, $type, $message ) = $matches;
  38. $errors[$file][] = [
  39. 'orig' => $error,
  40. // convert from 1 indexed to 0 indexed
  41. 'lineno' => $lineno - 1,
  42. 'type' => $type,
  43. ];
  44. }
  45. foreach ( $errors as $file => $fileErrors ) {
  46. $source = file( $file );
  47. foreach ( $fileErrors as $error ) {
  48. if ( !$this->isSuppressed( $source, $error['type'], $error['lineno'] ) ) {
  49. echo $error['orig'], "\n";
  50. $hasErrors = true;
  51. }
  52. }
  53. }
  54. return $hasErrors;
  55. }
  56. }
  57. class CheckStyleSuppressor extends Suppressor {
  58. /**
  59. * @param string $input
  60. * @return bool True do errors remain
  61. */
  62. public function suppress( $input ) {
  63. $dom = new DOMDocument();
  64. $dom->loadXML( $input );
  65. $hasErrors = false;
  66. // DOMNodeList's are "live", convert to an array so it works as expected
  67. $files = [];
  68. foreach ( $dom->getElementsByTagName( 'file' ) as $file ) {
  69. $files[] = $file;
  70. }
  71. foreach ( $files as $file ) {
  72. $errors = [];
  73. foreach ( $file->getElementsByTagName( 'error' ) as $error ) {
  74. $errors[] = $error;
  75. }
  76. $source = file( $file->getAttribute( 'name' ) );
  77. $fileHasErrors = false;
  78. foreach ( $errors as $error ) {
  79. $lineno = $error->getAttribute( 'line' ) - 1;
  80. $type = $error->getAttribute( 'source' );
  81. if ( $this->isSuppressed( $source, $type, $lineno ) ) {
  82. $error->parentNode->removeChild( $error );
  83. } else {
  84. $fileHasErrors = true;
  85. $hasErrors = true;
  86. }
  87. }
  88. if ( !$fileHasErrors ) {
  89. $file->parentNode->removeChild( $file );
  90. }
  91. }
  92. echo $dom->saveXML();
  93. return $hasErrors;
  94. }
  95. }
  96. class NoopSuppressor extends Suppressor {
  97. private $mode;
  98. public function __construct( $mode ) {
  99. $this->mode = $mode;
  100. }
  101. public function suppress( $input ) {
  102. echo "Unsupported output mode: {$this->mode}\n$input";
  103. return true;
  104. }
  105. }
  106. $opt = getopt( "m:", [ "output-mode:" ] );
  107. // if provided multiple times getopt returns an array
  108. if ( isset( $opt['m'] ) ) {
  109. $mode = $opt['m'];
  110. } elseif ( isset( $mode['output-mode'] ) ) {
  111. $mode = $opt['output-mode'];
  112. } else {
  113. $mode = 'text';
  114. }
  115. if ( is_array( $mode ) ) {
  116. // If an option is passed multiple times getopt returns an
  117. // array. Just take the last one.
  118. $mode = end( $mode );
  119. }
  120. switch ( $mode ) {
  121. case 'text':
  122. $suppressor = new TextSuppressor();
  123. break;
  124. case 'checkstyle':
  125. $suppressor = new CheckStyleSuppressor();
  126. break;
  127. default:
  128. $suppressor = new NoopSuppressor( $mode );
  129. }
  130. $input = file_get_contents( 'php://stdin' );
  131. $hasErrors = $suppressor->suppress( $input );
  132. if ( $hasErrors ) {
  133. exit( 1 );
  134. }