From f16b34f1d08672ca11f6906fe30876f5a850dcce Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
Date: Sun, 26 Feb 2023 15:06:35 +0100
Subject: [PATCH] Implement GH-10024: support linting multiple files at once
using php -l
This is supported in both the CLI and CGI modes. For CLI this required
little changes.
For CGI, the tricky part was that the options parsing happens inside the
loop. This means that options passed after the -l flag were previously
simply ignored. As we now re-enter the loop we would parse the options
again, and if they are handled but don't set the script name, then CGI
will think you want to read from standard in. To keep the same "don't
parse options" behaviour I simply wrapped the options handling inside an
if.
Closes GH-10024.
Closes GH-10710.
---
NEWS | 3 ++
UPGRADING | 3 ++
sapi/cgi/cgi_main.c | 7 ++-
sapi/cgi/tests/012.phpt | 117 ++++++++++++++++++++++++++++++++++++++++
sapi/cli/php_cli.c | 11 +++-
sapi/cli/tests/024.phpt | 110 +++++++++++++++++++++++++++++++++++++
6 files changed, 249 insertions(+), 2 deletions(-)
create mode 100644 sapi/cgi/tests/012.phpt
create mode 100644 sapi/cli/tests/024.phpt
diff --git a/NEWS b/NEWS
index 328acc0a524..29305565784 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,9 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.3.0alpha4
+- CLI:
+ . Implement GH-10024 (support linting multiple files at once using php -l).
+ (nielsdos)
06 Jul 2023, PHP 8.3.0alpha3
diff --git a/UPGRADING b/UPGRADING
index b709ccacd51..53dfe04237c 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -87,6 +87,9 @@ PHP 8.3 UPGRADE NOTES
in a parent class or implemented interface.
RFC: https://wiki.php.net/rfc/marking_overriden_methods
+- CLI
+ . It is now possible to lint multiple files.
+
- Posix
. posix_getrlimit() now takes an optional $res parameter to allow fetching a
single resource limit.
diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c
index bf0f233118b..156599f6f9d 100644
--- a/sapi/cgi/cgi_main.c
+++ b/sapi/cgi/cgi_main.c
@@ -2372,6 +2372,7 @@ parent_loop_end:
}
}
+do_repeat:
if (script_file) {
/* override path_translated if -f on command line */
if (SG(request_info).path_translated) efree(SG(request_info).path_translated);
@@ -2512,7 +2513,6 @@ parent_loop_end:
PG(during_request_startup) = 0;
if (php_lint_script(&file_handle) == SUCCESS) {
zend_printf("No syntax errors detected in %s\n", ZSTR_VAL(file_handle.filename));
- exit_status = 0;
} else {
zend_printf("Errors parsing %s\n", ZSTR_VAL(file_handle.filename));
exit_status = -1;
@@ -2581,6 +2581,11 @@ fastcgi_request_done:
}
}
}
+ if (behavior == PHP_MODE_LINT && argc - 1 > php_optind) {
+ php_optind++;
+ script_file = NULL;
+ goto do_repeat;
+ }
break;
}
diff --git a/sapi/cgi/tests/012.phpt b/sapi/cgi/tests/012.phpt
new file mode 100644
index 00000000000..7f981b7da8d
--- /dev/null
+++ b/sapi/cgi/tests/012.phpt
@@ -0,0 +1,117 @@
+--TEST--
+multiple files syntax check
+--SKIPIF--
+
+--INI--
+display_errors=stdout
+--FILE--
+/dev/null";
+ }
+ exec($cmd, $output, $exit_code);
+ print_r($output);
+ // Normalize Windows vs Linux exit codes. On Windows exit code -1 is actually -1 instead of 255.
+ if ($exit_code < 0) {
+ $exit_code += 256;
+ }
+ var_dump($exit_code);
+}
+
+$php = get_cgi_path();
+reset_env_vars();
+
+$filename_good = __DIR__."/012_good.test.php";
+$filename_good_escaped = escapeshellarg($filename_good);
+$filename_bad = __DIR__."/012_bad.test.php";
+$filename_bad_escaped = escapeshellarg($filename_bad);
+
+$code = '
+
+';
+
+file_put_contents($filename_bad, $code);
+
+run_and_output("$php -n -l $filename_good_escaped $filename_good_escaped");
+run_and_output("$php -n -l $filename_good_escaped some.unknown $filename_good_escaped");
+run_and_output("$php -n -l $filename_good_escaped $filename_bad_escaped $filename_good_escaped");
+run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped");
+run_and_output("$php -n -l $filename_bad_escaped some.unknown $filename_bad_escaped");
+run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped some.unknown");
+
+echo "Done\n";
+?>
+--CLEAN--
+
+--EXPECTF--
+Array
+(
+ [0] => No syntax errors detected in %s012_good.test.php
+ [1] => No syntax errors detected in %s012_good.test.php
+)
+int(0)
+Array
+(
+ [0] => No syntax errors detected in %s012_good.test.php
+ [1] => No input file specified.
+)
+int(255)
+Array
+(
+ [0] => No syntax errors detected in %s012_good.test.php
+ [1] =>
+ [2] => Parse error: syntax error, unexpected token "private", expecting "{" in %s012_bad.test.php on line 5
+ [3] => Errors parsing %s012_bad.test.php
+ [4] => No syntax errors detected in %s012_good.test.php
+)
+int(255)
+Array
+(
+ [0] =>
+ [1] => Parse error: syntax error, unexpected token "private", expecting "{" in %s012_bad.test.php on line 5
+ [2] => Errors parsing %s012_bad.test.php
+ [3] =>
+ [4] => Parse error: syntax error, unexpected token "private", expecting "{" in %s012_bad.test.php on line 5
+ [5] => Errors parsing %s012_bad.test.php
+)
+int(255)
+Array
+(
+ [0] =>
+ [1] => Parse error: syntax error, unexpected token "private", expecting "{" in %s012_bad.test.php on line 5
+ [2] => Errors parsing %s012_bad.test.php
+ [3] => No input file specified.
+)
+int(255)
+Array
+(
+ [0] =>
+ [1] => Parse error: syntax error, unexpected token "private", expecting "{" in %s012_bad.test.php on line 5
+ [2] => Errors parsing %s012_bad.test.php
+ [3] =>
+ [4] => Parse error: syntax error, unexpected token "private", expecting "{" in %s012_bad.test.php on line 5
+ [5] => Errors parsing %s012_bad.test.php
+ [6] => No input file specified.
+)
+int(255)
+Done
diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c
index c3598003058..546c4ba997d 100644
--- a/sapi/cli/php_cli.c
+++ b/sapi/cli/php_cli.c
@@ -734,6 +734,10 @@ static int do_cli(int argc, char **argv) /* {{{ */
break;
}
behavior=PHP_MODE_LINT;
+ /* We want to set the error exit status if at least one lint failed.
+ * If all were successful we set the exit status to 0.
+ * We already set EG(exit_status) here such that only failures set the exit status. */
+ EG(exit_status) = 0;
break;
case 'q': /* do not generate HTTP headers */
@@ -962,7 +966,6 @@ do_repeat:
case PHP_MODE_LINT:
if (php_lint_script(&file_handle) == SUCCESS) {
zend_printf("No syntax errors detected in %s\n", php_self);
- EG(exit_status) = 0;
} else {
zend_printf("Errors parsing %s\n", php_self);
EG(exit_status) = 255;
@@ -1128,9 +1131,15 @@ out:
}
if (request_started) {
php_request_shutdown((void *) 0);
+ request_started = 0;
}
if (translated_path) {
free(translated_path);
+ translated_path = NULL;
+ }
+ if (behavior == PHP_MODE_LINT && argc > php_optind && strcmp(argv[php_optind],"--")) {
+ script_file = NULL;
+ goto do_repeat;
}
/* Don't repeat fork()ed processes. */
if (--num_repeats && pid == getpid()) {
diff --git a/sapi/cli/tests/024.phpt b/sapi/cli/tests/024.phpt
new file mode 100644
index 00000000000..bfd24679fd3
--- /dev/null
+++ b/sapi/cli/tests/024.phpt
@@ -0,0 +1,110 @@
+--TEST--
+multiple files syntax check
+--SKIPIF--
+
+--FILE--
+
+';
+
+file_put_contents($filename_bad, $code);
+
+run_and_output("$php -n -l $filename_good_escaped $filename_good_escaped 2>&1");
+run_and_output("$php -n -l $filename_good_escaped some.unknown $filename_good_escaped 2>&1");
+run_and_output("$php -n -l $filename_good_escaped $filename_bad_escaped $filename_good_escaped 2>&1");
+run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped 2>&1");
+run_and_output("$php -n -l $filename_bad_escaped some.unknown $filename_bad_escaped 2>&1");
+run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped some.unknown 2>&1");
+
+echo "Done\n";
+?>
+--CLEAN--
+
+--EXPECTF--
+Array
+(
+ [0] => No syntax errors detected in %s024_good.test.php
+ [1] => No syntax errors detected in %s024_good.test.php
+)
+int(0)
+Array
+(
+ [0] => No syntax errors detected in %s024_good.test.php
+ [1] => Could not open input file: some.unknown
+ [2] => No syntax errors detected in %s024_good.test.php
+)
+int(1)
+Array
+(
+ [0] => No syntax errors detected in %s024_good.test.php
+ [1] =>
+ [2] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
+ [3] => Errors parsing %s024_bad.test.php
+ [4] => No syntax errors detected in %s024_good.test.php
+)
+int(255)
+Array
+(
+ [0] =>
+ [1] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
+ [2] => Errors parsing %s024_bad.test.php
+ [3] =>
+ [4] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
+ [5] => Errors parsing %s024_bad.test.php
+)
+int(255)
+Array
+(
+ [0] =>
+ [1] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
+ [2] => Errors parsing %s024_bad.test.php
+ [3] => Could not open input file: some.unknown
+ [4] =>
+ [5] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
+ [6] => Errors parsing %s024_bad.test.php
+)
+int(255)
+Array
+(
+ [0] =>
+ [1] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
+ [2] => Errors parsing %s024_bad.test.php
+ [3] =>
+ [4] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
+ [5] => Errors parsing %s024_bad.test.php
+ [6] => Could not open input file: some.unknown
+)
+int(1)
+Done