Skip to content

Commit f16b34f

Browse files
committed
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.
1 parent dde1d9e commit f16b34f

File tree

6 files changed

+249
-2
lines changed

6 files changed

+249
-2
lines changed

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 8.3.0alpha4
44

5+
- CLI:
6+
. Implement GH-10024 (support linting multiple files at once using php -l).
7+
(nielsdos)
58

69
06 Jul 2023, PHP 8.3.0alpha3
710

UPGRADING

+3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ PHP 8.3 UPGRADE NOTES
8787
in a parent class or implemented interface.
8888
RFC: https://wiki.php.net/rfc/marking_overriden_methods
8989

90+
- CLI
91+
. It is now possible to lint multiple files.
92+
9093
- Posix
9194
. posix_getrlimit() now takes an optional $res parameter to allow fetching a
9295
single resource limit.

sapi/cgi/cgi_main.c

+6-1
Original file line numberDiff line numberDiff line change
@@ -2372,6 +2372,7 @@ consult the installation file that came with this distribution, or visit \n\
23722372
}
23732373
}
23742374

2375+
do_repeat:
23752376
if (script_file) {
23762377
/* override path_translated if -f on command line */
23772378
if (SG(request_info).path_translated) efree(SG(request_info).path_translated);
@@ -2512,7 +2513,6 @@ consult the installation file that came with this distribution, or visit \n\
25122513
PG(during_request_startup) = 0;
25132514
if (php_lint_script(&file_handle) == SUCCESS) {
25142515
zend_printf("No syntax errors detected in %s\n", ZSTR_VAL(file_handle.filename));
2515-
exit_status = 0;
25162516
} else {
25172517
zend_printf("Errors parsing %s\n", ZSTR_VAL(file_handle.filename));
25182518
exit_status = -1;
@@ -2581,6 +2581,11 @@ consult the installation file that came with this distribution, or visit \n\
25812581
}
25822582
}
25832583
}
2584+
if (behavior == PHP_MODE_LINT && argc - 1 > php_optind) {
2585+
php_optind++;
2586+
script_file = NULL;
2587+
goto do_repeat;
2588+
}
25842589
break;
25852590
}
25862591

sapi/cgi/tests/012.phpt

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
--TEST--
2+
multiple files syntax check
3+
--SKIPIF--
4+
<?php include "skipif.inc"; ?>
5+
--INI--
6+
display_errors=stdout
7+
--FILE--
8+
<?php
9+
include "include.inc";
10+
11+
function run_and_output($cmd) {
12+
if (!defined("PHP_WINDOWS_VERSION_MAJOR")) {
13+
$cmd .= " 2>/dev/null";
14+
}
15+
exec($cmd, $output, $exit_code);
16+
print_r($output);
17+
// Normalize Windows vs Linux exit codes. On Windows exit code -1 is actually -1 instead of 255.
18+
if ($exit_code < 0) {
19+
$exit_code += 256;
20+
}
21+
var_dump($exit_code);
22+
}
23+
24+
$php = get_cgi_path();
25+
reset_env_vars();
26+
27+
$filename_good = __DIR__."/012_good.test.php";
28+
$filename_good_escaped = escapeshellarg($filename_good);
29+
$filename_bad = __DIR__."/012_bad.test.php";
30+
$filename_bad_escaped = escapeshellarg($filename_bad);
31+
32+
$code = '
33+
<?php
34+
35+
echo "hi";
36+
';
37+
38+
file_put_contents($filename_good, $code);
39+
40+
$code = '
41+
<?php
42+
43+
class test
44+
private $var;
45+
}
46+
47+
?>
48+
';
49+
50+
file_put_contents($filename_bad, $code);
51+
52+
run_and_output("$php -n -l $filename_good_escaped $filename_good_escaped");
53+
run_and_output("$php -n -l $filename_good_escaped some.unknown $filename_good_escaped");
54+
run_and_output("$php -n -l $filename_good_escaped $filename_bad_escaped $filename_good_escaped");
55+
run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped");
56+
run_and_output("$php -n -l $filename_bad_escaped some.unknown $filename_bad_escaped");
57+
run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped some.unknown");
58+
59+
echo "Done\n";
60+
?>
61+
--CLEAN--
62+
<?php
63+
@unlink($filename_good);
64+
@unlink($filename_bad);
65+
?>
66+
--EXPECTF--
67+
Array
68+
(
69+
[0] => No syntax errors detected in %s012_good.test.php
70+
[1] => No syntax errors detected in %s012_good.test.php
71+
)
72+
int(0)
73+
Array
74+
(
75+
[0] => No syntax errors detected in %s012_good.test.php
76+
[1] => No input file specified.
77+
)
78+
int(255)
79+
Array
80+
(
81+
[0] => No syntax errors detected in %s012_good.test.php
82+
[1] => <br />
83+
[2] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
84+
[3] => Errors parsing %s012_bad.test.php
85+
[4] => No syntax errors detected in %s012_good.test.php
86+
)
87+
int(255)
88+
Array
89+
(
90+
[0] => <br />
91+
[1] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
92+
[2] => Errors parsing %s012_bad.test.php
93+
[3] => <br />
94+
[4] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
95+
[5] => Errors parsing %s012_bad.test.php
96+
)
97+
int(255)
98+
Array
99+
(
100+
[0] => <br />
101+
[1] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
102+
[2] => Errors parsing %s012_bad.test.php
103+
[3] => No input file specified.
104+
)
105+
int(255)
106+
Array
107+
(
108+
[0] => <br />
109+
[1] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
110+
[2] => Errors parsing %s012_bad.test.php
111+
[3] => <br />
112+
[4] => <b>Parse error</b>: syntax error, unexpected token &quot;private&quot;, expecting &quot;{&quot; in <b>%s012_bad.test.php</b> on line <b>5</b><br />
113+
[5] => Errors parsing %s012_bad.test.php
114+
[6] => No input file specified.
115+
)
116+
int(255)
117+
Done

sapi/cli/php_cli.c

+10-1
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,10 @@ static int do_cli(int argc, char **argv) /* {{{ */
734734
break;
735735
}
736736
behavior=PHP_MODE_LINT;
737+
/* We want to set the error exit status if at least one lint failed.
738+
* If all were successful we set the exit status to 0.
739+
* We already set EG(exit_status) here such that only failures set the exit status. */
740+
EG(exit_status) = 0;
737741
break;
738742

739743
case 'q': /* do not generate HTTP headers */
@@ -962,7 +966,6 @@ static int do_cli(int argc, char **argv) /* {{{ */
962966
case PHP_MODE_LINT:
963967
if (php_lint_script(&file_handle) == SUCCESS) {
964968
zend_printf("No syntax errors detected in %s\n", php_self);
965-
EG(exit_status) = 0;
966969
} else {
967970
zend_printf("Errors parsing %s\n", php_self);
968971
EG(exit_status) = 255;
@@ -1128,9 +1131,15 @@ static int do_cli(int argc, char **argv) /* {{{ */
11281131
}
11291132
if (request_started) {
11301133
php_request_shutdown((void *) 0);
1134+
request_started = 0;
11311135
}
11321136
if (translated_path) {
11331137
free(translated_path);
1138+
translated_path = NULL;
1139+
}
1140+
if (behavior == PHP_MODE_LINT && argc > php_optind && strcmp(argv[php_optind],"--")) {
1141+
script_file = NULL;
1142+
goto do_repeat;
11341143
}
11351144
/* Don't repeat fork()ed processes. */
11361145
if (--num_repeats && pid == getpid()) {

sapi/cli/tests/024.phpt

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
--TEST--
2+
multiple files syntax check
3+
--SKIPIF--
4+
<?php include "skipif.inc"; ?>
5+
--FILE--
6+
<?php
7+
8+
function run_and_output($cmd) {
9+
exec($cmd, $output, $exit_code);
10+
print_r($output);
11+
var_dump($exit_code);
12+
}
13+
14+
$php = getenv('TEST_PHP_EXECUTABLE_ESCAPED');
15+
16+
$filename_good = __DIR__."/024_good.test.php";
17+
$filename_good_escaped = escapeshellarg($filename_good);
18+
$filename_bad = __DIR__."/024_bad.test.php";
19+
$filename_bad_escaped = escapeshellarg($filename_bad);
20+
21+
$code = '
22+
<?php
23+
24+
echo "hi";
25+
';
26+
27+
file_put_contents($filename_good, $code);
28+
29+
$code = '
30+
<?php
31+
32+
class test
33+
private $var;
34+
}
35+
36+
?>
37+
';
38+
39+
file_put_contents($filename_bad, $code);
40+
41+
run_and_output("$php -n -l $filename_good_escaped $filename_good_escaped 2>&1");
42+
run_and_output("$php -n -l $filename_good_escaped some.unknown $filename_good_escaped 2>&1");
43+
run_and_output("$php -n -l $filename_good_escaped $filename_bad_escaped $filename_good_escaped 2>&1");
44+
run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped 2>&1");
45+
run_and_output("$php -n -l $filename_bad_escaped some.unknown $filename_bad_escaped 2>&1");
46+
run_and_output("$php -n -l $filename_bad_escaped $filename_bad_escaped some.unknown 2>&1");
47+
48+
echo "Done\n";
49+
?>
50+
--CLEAN--
51+
<?php
52+
@unlink($filename_good);
53+
@unlink($filename_bad);
54+
?>
55+
--EXPECTF--
56+
Array
57+
(
58+
[0] => No syntax errors detected in %s024_good.test.php
59+
[1] => No syntax errors detected in %s024_good.test.php
60+
)
61+
int(0)
62+
Array
63+
(
64+
[0] => No syntax errors detected in %s024_good.test.php
65+
[1] => Could not open input file: some.unknown
66+
[2] => No syntax errors detected in %s024_good.test.php
67+
)
68+
int(1)
69+
Array
70+
(
71+
[0] => No syntax errors detected in %s024_good.test.php
72+
[1] =>
73+
[2] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
74+
[3] => Errors parsing %s024_bad.test.php
75+
[4] => No syntax errors detected in %s024_good.test.php
76+
)
77+
int(255)
78+
Array
79+
(
80+
[0] =>
81+
[1] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
82+
[2] => Errors parsing %s024_bad.test.php
83+
[3] =>
84+
[4] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
85+
[5] => Errors parsing %s024_bad.test.php
86+
)
87+
int(255)
88+
Array
89+
(
90+
[0] =>
91+
[1] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
92+
[2] => Errors parsing %s024_bad.test.php
93+
[3] => Could not open input file: some.unknown
94+
[4] =>
95+
[5] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
96+
[6] => Errors parsing %s024_bad.test.php
97+
)
98+
int(255)
99+
Array
100+
(
101+
[0] =>
102+
[1] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
103+
[2] => Errors parsing %s024_bad.test.php
104+
[3] =>
105+
[4] => Parse error: syntax error, unexpected token "private", expecting "{" in %s on line %d
106+
[5] => Errors parsing %s024_bad.test.php
107+
[6] => Could not open input file: some.unknown
108+
)
109+
int(1)
110+
Done

0 commit comments

Comments
 (0)