| | 1 | <?php |
| | 2 | |
| | 3 | require_once dirname( dirname( dirname( __FILE__ ) ) ) . '/vendor/autoload.php'; |
| | 4 | |
| | 5 | class Tests_DocBlocks extends WP_UnitTestCase { |
| | 6 | |
| | 7 | protected static $ignore_empty = true; |
| | 8 | protected static $method_refs = array(); |
| | 9 | protected static $function_refs = array(); |
| | 10 | |
| | 11 | public function remove_variadic_params( $param_doc ) { |
| | 12 | return ( false === strpos( $param_doc, ',...' ) ); |
| | 13 | } |
| | 14 | |
| | 15 | /** |
| | 16 | * Test a function or method for a given class |
| | 17 | * |
| | 18 | * @dataProvider dataReflectionTestFunctions |
| | 19 | * |
| | 20 | * @param string|array $function The function name, or array of class name and method name. |
| | 21 | * @param string $file The file name. |
| | 22 | */ |
| | 23 | public function testFunctionDocBlock( $function, $filename ) { |
| | 24 | |
| | 25 | if ( is_array( $function ) ) { |
| | 26 | $name = $function[0] . '::' . $function[1] . '()'; |
| | 27 | $ref = self::$method_refs[ $filename ][ $function[0] ][ $name ]; |
| | 28 | } else { |
| | 29 | $name = $function; |
| | 30 | $ref = self::$function_refs[ $filename ][ $name ]; |
| | 31 | } |
| | 32 | |
| | 33 | $name = ltrim( $name, '\\' ); |
| | 34 | $docblock = $ref->getDocblock(); |
| | 35 | |
| | 36 | if ( self::$ignore_empty && empty( $docblock ) ) { |
| | 37 | return; |
| | 38 | } |
| | 39 | |
| | 40 | $this->assertNotEmpty( $docblock, sprintf( |
| | 41 | 'The docblock for `%s` should not be missing.', |
| | 42 | $name |
| | 43 | ) ); |
| | 44 | |
| | 45 | $method_params = $ref->getArguments(); |
| | 46 | $doc_params = $docblock->getTagsByName( 'param' ); |
| | 47 | $desc = $docblock->getDescription(); |
| | 48 | |
| | 49 | if ( self::$ignore_empty && empty( $desc ) ) { |
| | 50 | return; |
| | 51 | } |
| | 52 | |
| | 53 | $this->assertNotEmpty( $desc, sprintf( |
| | 54 | 'The docblock description for `%s` should not be empty.', |
| | 55 | $name |
| | 56 | ) ); |
| | 57 | |
| | 58 | if ( count( $method_params ) < count( $doc_params ) ) { |
| | 59 | $non_variadic_doc_params = array_filter( $doc_params, array( $this, 'remove_variadic_params' ) ); |
| | 60 | } else { |
| | 61 | $non_variadic_doc_params = $doc_params; |
| | 62 | } |
| | 63 | |
| | 64 | $this->assertSame( count( $method_params ), count( $non_variadic_doc_params ), sprintf( |
| | 65 | 'The number of @param docs for `%s` should match its number of parameters.', |
| | 66 | $name |
| | 67 | ) ); |
| | 68 | |
| | 69 | foreach ( $method_params as $i => $param ) { |
| | 70 | |
| | 71 | $param_doc = $doc_params[ $i ]; |
| | 72 | $description = (string) $param_doc->getDescription(); |
| | 73 | $is_hash = ( ( 0 === strpos( $description, '{' ) ) && ( ( strlen( $description ) - 1 ) === strrpos( $description, '}' ) ) ); |
| | 74 | |
| | 75 | if ( $is_hash ) { |
| | 76 | $lines = explode( "\n", $description ); |
| | 77 | $description = $lines[1]; |
| | 78 | } |
| | 79 | |
| | 80 | if ( self::$ignore_empty && empty( $description ) ) { |
| | 81 | return; |
| | 82 | } |
| | 83 | |
| | 84 | $this->assertNotEmpty( $description, sprintf( |
| | 85 | 'The @param description for the `$%s` parameter of `%s` should not be empty.', |
| | 86 | $param_doc->getVariableName(), |
| | 87 | $name |
| | 88 | ) ); |
| | 89 | |
| | 90 | $param_doc_name = $param_doc->getVariableName(); |
| | 91 | $param_doc_type = (string) $param_doc->getType(); |
| | 92 | |
| | 93 | $this->assertSame( $param->getName(), $param_doc_name, sprintf( |
| | 94 | 'The @param name for the `%s` parameter of `%s` is incorrect.', |
| | 95 | $param->getName(), |
| | 96 | $name |
| | 97 | ) ); |
| | 98 | |
| | 99 | // if ( $param->isOptional() ) { |
| | 100 | // $this->assertNotFalse( strpos( $description, 'Optional' ), sprintf( |
| | 101 | // 'The @param description for the optional `%s` parameter of `%s` should state that it is optional.', |
| | 102 | // $param_doc_name, |
| | 103 | // $name |
| | 104 | // ) ); |
| | 105 | // } else { |
| | 106 | // $this->assertFalse( strpos( $description, 'Optional.' ), sprintf( |
| | 107 | // 'The @param description for the required `%s` parameter of `%s` should not state that it is optional.', |
| | 108 | // $param_doc_name, |
| | 109 | // $name |
| | 110 | // ) ); |
| | 111 | // } |
| | 112 | |
| | 113 | // if ( $param->isDefaultValueAvailable() ) { |
| | 114 | // $this->assertNotFalse( strpos( $description, 'Default ' ), sprintf( |
| | 115 | // 'The @param description for the `%s` parameter of `%s` should state its default value.', |
| | 116 | // $param_doc_name, |
| | 117 | // $name |
| | 118 | // ) ); |
| | 119 | // } else { |
| | 120 | // $this->assertFalse( strpos( $description, 'Default ' ), sprintf( |
| | 121 | // 'The @param description for the `%s` parameter of `%s` should not state a default value.', |
| | 122 | // $param_doc_name, |
| | 123 | // $name |
| | 124 | // ) ); |
| | 125 | // } |
| | 126 | |
| | 127 | } |
| | 128 | |
| | 129 | } |
| | 130 | |
| | 131 | protected static function rsearch( $folder, $ext ) { |
| | 132 | $iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $folder ) ); |
| | 133 | |
| | 134 | $files = array(); |
| | 135 | |
| | 136 | foreach ( $iterator as $file ) { |
| | 137 | |
| | 138 | if ( $file->isDir() ) { |
| | 139 | continue; |
| | 140 | } |
| | 141 | if ( $ext !== $file->getExtension() ) { |
| | 142 | continue; |
| | 143 | } |
| | 144 | |
| | 145 | $files[] = $file->getPathname(); |
| | 146 | |
| | 147 | } |
| | 148 | return $files; |
| | 149 | } |
| | 150 | |
| | 151 | public function dataReflectionTestFunctions() { |
| | 152 | |
| | 153 | $to_search = ABSPATH . 'wp-includes/'; |
| | 154 | |
| | 155 | // Recursive: |
| | 156 | $files = self::rsearch( $to_search, 'php' ); |
| | 157 | |
| | 158 | // Non-recursive |
| | 159 | // $files = glob( $to_search . '*.php' ); |
| | 160 | |
| | 161 | $ignore_files = array( |
| | 162 | |
| | 163 | ABSPATH . 'wp-admin/includes/class-pclzip.php', |
| | 164 | ABSPATH . 'wp-admin/includes/class-ftp.php', |
| | 165 | ABSPATH . 'wp-admin/includes/class-ftp-pure.php', |
| | 166 | ABSPATH . 'wp-admin/includes/class-ftp-sockets.php', |
| | 167 | ABSPATH . 'wp-admin/includes/export.php', |
| | 168 | ABSPATH . 'wp-admin/includes/file.php', |
| | 169 | |
| | 170 | ABSPATH . 'wp-includes/class-IXR.php', |
| | 171 | ABSPATH . 'wp-includes/rss.php', |
| | 172 | |
| | 173 | ); |
| | 174 | $files = array_diff( $files, $ignore_files ); |
| | 175 | $files = array_filter( $files, function( $file ) { |
| | 176 | |
| | 177 | $ignore_dirs = array( |
| | 178 | ABSPATH . 'wp-includes/ID3/', |
| | 179 | ABSPATH . 'wp-includes/SimplePie/', |
| | 180 | ABSPATH . 'wp-includes/Text/', |
| | 181 | ); |
| | 182 | |
| | 183 | foreach ( $ignore_dirs as $dir ) { |
| | 184 | if ( 0 === strpos( $file, $dir ) ) { |
| | 185 | return false; |
| | 186 | } |
| | 187 | } |
| | 188 | return true; |
| | 189 | |
| | 190 | } ); |
| | 191 | $data = array(); |
| | 192 | |
| | 193 | $projectFactory = \phpDocumentor\Reflection\Php\ProjectFactory::createInstance(); |
| | 194 | |
| | 195 | foreach ( $files as $project_file ) { |
| | 196 | try { |
| | 197 | $project = $projectFactory->create( 'WordPress', array( $project_file ) ); |
| | 198 | } catch ( InvalidArgumentException $e ) { |
| | 199 | // This catches files where functions are defined within functions. |
| | 200 | // eg. redirect_canonical()::lowercase_octets() in wp-includes/canonical.php |
| | 201 | continue; |
| | 202 | } |
| | 203 | |
| | 204 | foreach ( $project->getFiles() as $filename => $file ) { |
| | 205 | |
| | 206 | foreach ( $file->getFunctions() as $f => $func_ref ) { |
| | 207 | |
| | 208 | self::$function_refs[ $filename ][ $f ] = $func_ref; |
| | 209 | |
| | 210 | $data[] = array( |
| | 211 | (string) $func_ref->getFqsen(), |
| | 212 | $filename, |
| | 213 | ); |
| | 214 | |
| | 215 | } |
| | 216 | |
| | 217 | foreach ( $file->getClasses() as $c => $class_ref ) { |
| | 218 | |
| | 219 | foreach ( $class_ref->getMethods() as $m => $method_ref ) { |
| | 220 | |
| | 221 | self::$method_refs[ $filename ][ $c ][ $m ] = $method_ref; |
| | 222 | |
| | 223 | $data[] = array( |
| | 224 | array( |
| | 225 | (string) $class_ref->getFqsen(), |
| | 226 | $method_ref->getName(), |
| | 227 | ), |
| | 228 | $filename, |
| | 229 | ); |
| | 230 | |
| | 231 | } |
| | 232 | |
| | 233 | } |
| | 234 | |
| | 235 | } |
| | 236 | |
| | 237 | } |
| | 238 | |
| | 239 | return $data; |
| | 240 | |
| | 241 | } |
| | 242 | |
| | 243 | } |