gallery: Fix phan annotation for ImageGalleryBase::getImages
[mediawiki.git] / tests / parser / TestFileEditor.php
blob6986286cdef1f8e5762942feb71dee15cf3aeb5f
1 <?php
3 class TestFileEditor {
4 /** @var string[] */
5 private $lines;
6 /** @var int */
7 private $numLines;
8 /** @var array<string,true> */
9 private $deletions;
10 /** @var array<string,array<string,array>> */
11 private $changes;
12 /** @var int */
13 private $pos = 0;
14 /** @var callable|false */
15 private $warningCallback;
16 /** @var string */
17 private $result = '';
19 public static function edit( $text, array $deletions, array $changes, $warningCallback = null ) {
20 $editor = new self( $text, $deletions, $changes, $warningCallback );
21 $editor->execute();
22 return $editor->result;
25 private function __construct( $text, array $deletions, array $changes, $warningCallback ) {
26 $this->lines = explode( "\n", $text );
27 $this->numLines = count( $this->lines );
28 $this->deletions = array_fill_keys( $deletions, true );
29 $this->changes = $changes;
30 $this->warningCallback = $warningCallback;
33 private function execute() {
34 while ( $this->pos < $this->numLines ) {
35 $line = $this->lines[$this->pos];
36 switch ( $this->getHeading( $line ) ) {
37 case 'test':
38 $this->parseTest();
39 break;
40 case 'hooks':
41 case 'functionhooks':
42 $this->parseHooks();
43 break;
44 default:
45 if ( $this->pos < $this->numLines - 1 ) {
46 $line .= "\n";
48 $this->emitComment( $line );
49 $this->pos++;
52 foreach ( $this->deletions as $deletion => $unused ) {
53 $this->warning( "Could not find test \"$deletion\" to delete it" );
55 foreach ( $this->changes as $test => $sectionChanges ) {
56 foreach ( $sectionChanges as $section => $change ) {
57 $this->warning( "Could not find section \"$section\" in test \"$test\" " .
58 "to {$change['op']} it" );
63 private function warning( $text ) {
64 $cb = $this->warningCallback;
65 if ( $cb ) {
66 $cb( $text );
70 private function getHeading( $line ) {
71 if ( preg_match( '/^!!\s*(\S+)/', $line, $m ) ) {
72 return $m[1];
73 } else {
74 return false;
78 private function parseTest() {
79 $test = [];
80 $line = $this->lines[$this->pos++];
81 $heading = $this->getHeading( $line );
82 $section = [
83 'name' => $heading,
84 'headingLine' => $line,
85 'contents' => ''
88 while ( $this->pos < $this->numLines ) {
89 $line = $this->lines[$this->pos++];
90 $nextHeading = $this->getHeading( $line );
91 if ( $nextHeading === 'end' ) {
92 $test[] = $section;
94 // Add trailing line breaks to the "end" section, to allow for neat deletions
95 $trail = '';
96 while ( $this->lines[$this->pos] === '' && $this->pos < $this->numLines - 1 ) {
97 $trail .= "\n";
98 $this->pos++;
101 $test[] = [
102 'name' => 'end',
103 'headingLine' => $line,
104 'contents' => $trail
106 $this->emitTest( $test );
107 return;
108 } elseif ( $nextHeading !== false ) {
109 $test[] = $section;
110 $heading = $nextHeading;
111 $section = [
112 'name' => $heading,
113 'headingLine' => $line,
114 'contents' => ''
116 } else {
117 $section['contents'] .= "$line\n";
121 throw new OutOfBoundsException( 'Unexpected end of file' );
124 private function parseHooks() {
125 $line = $this->lines[$this->pos++];
126 $heading = $this->getHeading( $line );
127 $expectedEnd = 'end' . $heading;
128 $contents = "$line\n";
130 do {
131 $line = $this->lines[$this->pos++];
132 $nextHeading = $this->getHeading( $line );
133 $contents .= "$line\n";
134 } while ( $this->pos < $this->numLines && $nextHeading !== $expectedEnd );
136 if ( $nextHeading !== $expectedEnd ) {
137 throw new UnexpectedValueException( 'Unexpected end of file' );
139 $this->emitHooks( $heading, $contents );
142 protected function emitComment( $contents ) {
143 $this->result .= $contents;
146 protected function emitTest( $test ) {
147 $testName = false;
148 foreach ( $test as $section ) {
149 if ( $section['name'] === 'test' ) {
150 $testName = rtrim( $section['contents'], "\n" );
153 if ( isset( $this->deletions[$testName] ) ) {
154 // Acknowledge deletion
155 unset( $this->deletions[$testName] );
156 return;
158 if ( isset( $this->changes[$testName] ) ) {
159 $changes =& $this->changes[$testName];
160 foreach ( $test as $i => $section ) {
161 $sectionName = $section['name'];
162 if ( isset( $changes[$sectionName] ) ) {
163 $change = $changes[$sectionName];
164 switch ( $change['op'] ) {
165 case 'rename':
166 $test[$i]['name'] = $change['value'];
167 $test[$i]['headingLine'] = "!! {$change['value']}";
168 break;
169 case 'update':
170 $test[$i]['contents'] = $change['value'];
171 break;
172 case 'delete':
173 $test[$i]['deleted'] = true;
174 break;
175 default:
176 throw new UnexpectedValueException( "Unknown op: {$change['op']}" );
178 // Acknowledge
179 // Note that we use the old section name for the rename op
180 unset( $changes[$sectionName] );
184 foreach ( $test as $section ) {
185 if ( isset( $section['deleted'] ) ) {
186 continue;
188 $this->result .= $section['headingLine'] . "\n";
189 $this->result .= $section['contents'];
193 protected function emitHooks( $heading, $contents ) {
194 $this->result .= $contents;