Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Squiz.PHP.EmbeddedPhp false positive when using PHP short open tag #588

Closed
4 tasks done
rodrigoprimo opened this issue Aug 8, 2024 · 6 comments
Closed
4 tasks done

Comments

@rodrigoprimo
Copy link
Contributor

Describe the bug

When the short_open_tag ini directive is enabled, the sniff Squiz.PHP.EmbeddedPhp raises a false positive error for code that uses a single-line statement with the short open tag.

Code sample

<? echo 'This is a short open tag'; ?>

To reproduce

Steps to reproduce the behavior:

  1. Enable the short_open_tag ini directive in the PHP configuration file.
  2. Create a file called test.php with the code sample above.
  3. Run phpcs --standard=Squiz --sniffs=Squiz.PHP.EmbeddedPhp test.php
  4. See the error message displayed
FILE: test.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 9 | ERROR | [x] Expected 1 space after opening PHP tag; 2 found
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

Expected behavior

PHPCS should report no errors.

Versions (please complete the following information)

Operating System Ubuntu 24.04
PHP version 8.3
PHP_CodeSniffer version master
Standard Squiz
Install type git clone

Additional context

The sniff assumes that there is always a space after the PHP open tag that is part of the content of the T_OPEN_TAG token. That is true for the PHP long open tag, but not for the PHP short open tag. When processing a short open tag, the sniff "sees" an extra space that doesn't exist, causing it to report the error, saying that it expected one space but found two.

Here is the code which was added in 3172a21:

// Check that there is one, and only one space at the start of the statement.
$leadingSpace = 0;
if ($tokens[$stackPtr]['code'] === T_OPEN_TAG) {
// The long open tag token in a single line tag set always contains a single space after it.
$leadingSpace = 1;
}

I believe the sniff should be fixed by changing the if condition highlighted above to differentiate between the short open tag and the long open tag when the directive short_open_tag is enabled. This could be done by checking the value of $tokens[$stackPtr]['cotent']. I wonder if there is a better way to do it.

Please confirm

  • I have searched the issue list and am not opening a duplicate issue.
  • I have read the Contribution Guidelines and this is not a support question.
  • I confirm that this bug is a bug in PHP_CodeSniffer and not in one of the external standards.
  • I have verified the issue still exists in the master branch of PHP_CodeSniffer.
@jrfnl
Copy link
Member

jrfnl commented Aug 8, 2024

@rodrigoprimo Nice find. Low priority to fix as it is uncommon for short open tags (not echo) to be enabled, let alone used.

I believe the sniff should be fixed by changing the if condition highlighted above to differentiate between the short open tag and the long open tag when the directive short_open_tag is enabled. This could be done by checking the value of $tokens[$stackPtr]['cotent']. I wonder if there is a better way to do it.

I don't think the directive short_open_tag needs to be checked, as if it is off, <? would not be tokenized as T_OPEN_TAG and wouldn't trigger the sniff.

Checking with the current condition + a content check for stripos($content, '<?php') === 0 should be sufficient I think ?
Note the use of stripos() - it should also be ensured that there are tests with <?PHP to safeguard that this new condition is properly covered.

@rodrigoprimo
Copy link
Contributor Author

Thanks for checking, @jrfnl. I agree that this is a low-priority issue, as you said. It seems somewhat straightforward to fix it, so I might give it a try if I have some time in the next few days.

@rodrigoprimo
Copy link
Contributor Author

rodrigoprimo commented Aug 8, 2024

Noting here that I found a scenario where this bug, combined with another sniff, can cause a parse error. Running the following PHPCBF command against the same code sample used in the description of the issue will result in invalid PHP file.

phpcbf test.php --standard=Generic,Squiz --sniffs=Squiz.PHP.EmbeddedPhp,Generic.PHP.DisallowShortOpenTag

Here is the resulting PHP file:

<?php phpecho 'This is a short open tag'; ?>

Here is the part of the phpcbf -vvv output that shows what is causing the problem:

---START FILE CONTENT---
1|<? echo 'This is a short open tag'; ?>
2|
--- END FILE CONTENT ---
	Generic.PHP.DisallowShortOpenTag:67 replaced token 0 (T_OPEN_TAG on line 1) "<?" => "<?php"
	Squiz.PHP.EmbeddedPhp:398 replaced token 1 (T_WHITESPACE on line 1) "·echo" => "echo"
        => Fixing file: 2/2 violations remaining [made 1 pass]...               
	* fixed 2 violations, starting loop 2 *
---START FILE CONTENT---
1|<?phpecho 'This is a short open tag'; ?>
2|
--- END FILE CONTENT ---
	Generic.PHP.DisallowShortOpenTag:67 replaced token 0 (T_OPEN_TAG on line 1) "<?" => "<?php·"
        => Fixing file: 1/2 violations remaining [made 2 passes]...             
	* fixed 1 violations, starting loop 3 *
---START FILE CONTENT---
1|<?php phpecho 'This is a short open tag'; ?>
2|
--- END FILE CONTENT ---
        => Fixing file: 0/2 violations remaining [made 3 passes]...             
DONE in 1ms
	=> File was overwritten

On a first pass, Generic.PHP.DisallowShortOpenTag replaces <? with <?php. Then Squiz.PHP.EmbeddedPhp removes the whitespace before the echo keyword. Resulting in <?phpecho. On a second pass, <? is tokenized as T_OPEN_TAG and phpecho as T_STRING. So, Generic.PHP.DisallowShortOpenTag replaces <? with <?php again, resulting in <?php phpecho.

@jrfnl
Copy link
Member

jrfnl commented Aug 8, 2024

@rodrigoprimo Good catch, still low prio.

Having said that, might be good to check if the Generic.PHP.DisallowShortOpenTag sniff fixer checks whether there is a whitespace char after the short open tag before replacing it and adjusts the replacement value if not.
That would be a separate issue though.

@rodrigoprimo
Copy link
Contributor Author

Having said that, might be good to check if the Generic.PHP.DisallowShortOpenTag sniff fixer checks whether there is a whitespace char after the short open tag before replacing it and adjusts the replacement value if not.
That would be a separate issue though.

@jrfnl, good point, I checked, and the sniff does check that:

$correctOpening = '<?php';
if (isset($tokens[($stackPtr + 1)]) === true && $tokens[($stackPtr + 1)]['code'] !== T_WHITESPACE) {
// Avoid creation of invalid open tags like <?phpecho if the original was <?echo .
$correctOpening .= ' ';
}


The problem only happens when the two sniffs are executed together. So it seems to be a case of conflict between the fixers. I'm assuming it is not worth investigating this further as the issue in Squiz.PHP.EmbeddedPhp will be fixed and, as you said, this is low priority as the PHP short open tag is not commonly used. But please let me know if you want me to look more into this.

@jrfnl
Copy link
Member

jrfnl commented Sep 1, 2024

Closing as fixed via #591

@jrfnl jrfnl closed this as completed Sep 1, 2024
@jrfnl jrfnl added this to the 3.10.x Next milestone Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants