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