mirror of
https://github.com/php/doc-base.git
synced 2026-03-24 23:32:15 +01:00
241 lines
7.8 KiB
PHP
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;
|
|
} |