Files
archived-doc-base/scripts/qa/check-examples.php
Gina Peter Banyard efe5283eed Update QA script licenses to PHP 3.01 (#110)
Also drive by fix of my name
2023-12-06 04:18:45 +00:00

241 lines
7.8 KiB
PHP

<?php
/*
+----------------------------------------------------------------------+
| Copyright (c) 1997-2023 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt. |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net, so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Philip Olson <philip@php.net> |
| Etienne Kneuss <colder@php.net> |
+----------------------------------------------------------------------+
$Id$
*/
/*
PURPOSE:
- Lint checks every DocBook <example> and <informalexample> in the
PHP Manual
TODO:
- Compare expected output with actual (so not just a lint check)
- Compare all of phpdoc-all (with stripped comments)
- Inspect EDITOR code, too hackish?
- Consider move from regex to xmlreader or dom
- Check other <title> conditions aside from <function>
- Make Windows friendly, and consider passing in args to exec()
NOTES:
- It's possible to check one extension at a time, by passing in said
(any) directory
*/
// These usually demonstrate actual failures and the script needs to be improved
// (probably by checking the result) and not doing a linter check
const KNOWN_FILE_FAILURES = [
'.manual.xml',
// Just ignore the migration guides for the moment
'appendices/',
'language/basic-syntax.xml',
'language/control-structures/alternative-syntax.xml',
'language/control-structures/continue.xml',
'language/control-structures/declare.xml',
'language/control-structures/elseif.xml',
'language/control-structures/goto.xml',
'language/control-structures/match.xml',
'language/enumerations.xml',
'language/exceptions.xml',
'language/functions.xml',
'language/namespaces.xml',
'language/oop5/basic.xml',
'language/oop5/decon.xml',
'language/oop5/final.xml',
'language/oop5/inheritance.xml',
'language/oop5/properties.xml',
'language/operators.xml',
'language/variables.xml',
'language/types/array.xml',
'language/types/declarations.xml',
'language/types/string.xml',
'reference/strings/functions/echo.xml',
// Those need to be fixed
// Do not run this script on programs which are not PHP
'reference/yaf/yaf-loader.xml',
'reference/yaf/yaf_application/construct.xml',
'reference/yaf/yaf_router/addconfig.xml',
// Uses ...
'reference/ev/evwatcher/keepalive.xml',
// Gets out of the CDATA to does a reference via methodname tag
'reference/event/eventhttpconnection/setclosecallback.xml',
// ignore for now but should be easy fix
'reference/mbstring/functions/mb-decode-numericentity.xml',
'reference/mbstring/functions/mb-encode-numericentity.xml',
];
const REGEX_EXAMPLE_FIND = '#<(informalexample|example)>\s*(?:<title>(.+)</title>)?\s*<programlisting(?: role="php")?>\s*<!\[CDATA\[(.+)]]>\s*</programlisting>(.*)</\1>#isU';
const REGEX_OUTPUT_FIND = '#&example\.outputs.*?;\s*<screen>\s*<!\[CDATA\[(.+)]]>\s*</screen>#isU';
const REGEX_TITLE_CLEAN = '#<function>([\w_-]+)</function>#';
$opts = getopt('p:t:o::h');
if (empty($opts) || isset($opts['h'])) {
usage();
}
if (empty($opts['p']) || empty($opts['t'])) {
echo "\nInfo: Both -p (phpdoc path) and -t (temporary directory) must be set\n";
usage();
}
if (!is_dir($opts['p']) || !is_dir($opts['t'])) {
trigger_error('Unknown directory passed in', E_USER_WARNING);
usage();
}
$tmpdir = $opts['t'];
$path = $opts['p'];
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $file) {
$filename = $file->getPathname();
if (!$file->isFile() || pathinfo($filename, PATHINFO_EXTENSION) !== 'xml' || is_known_failure($filename)) {
continue;
}
$examples = get_examples($filename);
if ($examples) {
foreach ($examples as $example) {
$parsed_example = validate_example($example, "$tmpdir/phplint{$_SERVER['REQUEST_TIME']}.php");
if (isset($parsed_example['error_line_num'])) {
$errors[] = $parsed_example;
}
}
}
}
if (empty($errors)) {
echo "No errors were found in examples!\n";
exit(0);
} else {
$o = '';
foreach ($errors as $error) {
echo "Filename : $error[filename]\n";
echo "Error : $error[return]\n";
echo "Line number : $error[error_line_num]\n";
echo "Example title : $error[title]\n";
echo "Example type : $error[type]\n";
echo "Example num : $error[num]\n";
echo "Line -1 : " . $error['error_line'][0] . "\n";
echo "Line : " . $error['error_line'][1] . "\n";
echo "Line +1 : " . $error['error_line'][2] . "\n";
echo "-----------------------\n";
$o .= " $error[filename]";
}
if (isset($opts['o'])) {
if (empty($opts['o'])) {
if (!empty($_SERVER['EDITOR'])) {
$opts['o'] = $_SERVER['EDITOR'];
} else {
echo $o;
exit;
}
}
shell_exec($opts['o'] . $o);
}
}
function get_examples(string $filename): array {
$content = file_get_contents($filename);
$info = [];
if ($number = preg_match_all(REGEX_EXAMPLE_FIND, $content, $matches)) {
// Found at least one example
foreach ($matches[3] as $num => $example) {
// Expected output as suggested by the XML
$expected = 'Unknown';
if (preg_match(REGEX_OUTPUT_FIND, $matches[4][$num], $match)) {
if (!empty($match[1])) {
$expected = $match[1];
}
}
// Type: example or informalexample
$type = trim($matches[1][$num]);
// Title: Most <example>'s have <title>'s
$title = 'Unknown';
if (!empty($matches[2][$num])) {
$title = preg_replace(REGEX_TITLE_CLEAN, '\1()', $matches[2][$num]);
}
$info[] = [
'title' => trim(str_replace("\n", '', $title)),
'filename' => trim($filename),
'example' => trim($example),
'expected' => trim($expected),
'type' => trim($type),
'num' => (int) $num + 1,
];
}
}
return $info;
}
function validate_example ($info, $filename) {
if (!file_exists($filename) && !touch($filename)) {
trigger_error("Could not create file $filename for lint checking", E_USER_ERROR);
}
file_put_contents($filename, $info['example']);
exec("php -l {$filename} 2>&1", $out, $return);
$fq = preg_quote($filename);
$info['return'] = trim(preg_replace(array("@(in $fq.*)@", "@Errors parsing $fq@"), '', $out[0]));
// Has it always been "No syntax errors detected"? Let's assume so for now
if (!str_contains($info['return'], 'No syntax errors detected')) {
$parts = explode("\n", $info['example']);
$error_line_num = (int) trim(substr($out[0], strpos($out[0], 'on line')+8));
$info['error_line_num'] = $error_line_num;
$info['error_line'][0] = trim($parts[($error_line_num-1)]);
$info['error_line'][1] = trim($parts[$error_line_num]);
if (isset($parts[($error_line_num+1)])) {
$info['error_line'][2] = trim($parts[($error_line_num+1)]);
} else {
$info['error_line'][2] = 'None';
}
}
return $info;
}
function is_known_failure(string $filename): bool {
foreach (KNOWN_FILE_FAILURES as $fail) {
if (str_contains($filename, $fail)) {
return true;
}
}
return false;
}
function usage() {
echo "\n------ DocBook Lint Checker ------\n";
echo "Usage: php {$_SERVER['argv'][0]} -p /path/to/php/doc/cvs -t /path/to/tmp/dir\n";
echo "Example: php {$_SERVER['argv'][0]} -p /cvs/phpdoc/en -t /tmp\n";
echo "Optional: -o to load files in EDITOR (" . $_SERVER['EDITOR'] . ") or -o /path/to/editor\n";
exit;
}