Skip to content

Commit

Permalink
✨ PHP 7.4: New NewPHPOpenTagEOF sniff
Browse files Browse the repository at this point in the history
> `<?php` at the end of the file (without trailing newline) will now be interpreted as an opening PHP tag.
> Previously it was interpreted either as `<? php` and resulted in a syntax error (with short_open_tag=1) or was interpreted as a literal `<?php` string (with short_open_tag=0).

Refs:
* https://github.com/php/php-src/blob/30de357fa14480468132bbc22a272aeb91789ba8/UPGRADING#L37-L40
* php/php-src@c9acc90#diff-7748eb3bfdd3bf962553f6f9f2723c45

## Implementation notes:

This sniff will not work on PHP 5.3 in combination with PHPCS < 2.6.0 with `short_open_tag=On` due to a tokenizer bug in PHPCS itself in those older versions.

Related: 835

The sniff needs different code to detect the issue depending on whether or not `short_open_tag`s is enabled on the system running the sniffs.

By default, the Travis images have `short_open_tag=Off`.

To make sure that both situations are unit tested, two of the existing builds will now change the PHP ini config of the Travis image to ensure that the sniff is also tested with `short_open_tag=On`.
  • Loading branch information
jrfnl committed Jul 21, 2019
1 parent 3db1bf1 commit 65d48b9
Show file tree
Hide file tree
Showing 10 changed files with 422 additions and 2 deletions.
16 changes: 14 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,14 @@ jobs:
#### CODE COVERAGE STAGE ####
# N.B.: Coverage is only checked on the lowest and highest stable PHP versions for all PHPCS versions.
# These builds are left out off the "test" stage so as not to duplicate test runs.
#
# The `CUSTOM_INI` configuration is used to allow testing with different PHP ini settings.
# Added against one high/high and one low/medium build, though having separate builds for this
# would be preferred.
# Docs: https://docs.travis-ci.com/user/languages/php/#custom-php-configuration
- stage: coverage
php: 7.3
env: PHPCS_VERSION="dev-master" COVERALLS_VERSION="^2.0"
env: PHPCS_VERSION="dev-master" COVERALLS_VERSION="^2.0" CUSTOM_INI=1
- php: 7.3
env: PHPCS_VERSION="2.3.0" COVERALLS_VERSION="^2.0"
# PHP 7.3+ is only fully supported icw PHPCS 2.9.2 and 3.3.1+.
Expand All @@ -96,7 +101,7 @@ jobs:
env: PHPCS_VERSION="dev-master" COVERALLS_VERSION="^1.0"
- php: 5.3
dist: precise
env: PHPCS_VERSION=">=2.0,<3.0" LINT=1 COVERALLS_VERSION="^1.0"
env: PHPCS_VERSION=">=2.0,<3.0" LINT=1 COVERALLS_VERSION="^1.0" CUSTOM_INI=1
- php: 5.3
dist: precise
env: PHPCS_VERSION="2.3.0" COVERALLS_VERSION="^1.0"
Expand All @@ -112,6 +117,13 @@ before_install:
phpenv config-rm xdebug.ini || echo 'No xdebug config.'
fi
# Allow for testing with different PHP ini configurations.
- |
if [[ "$CUSTOM_INI" == 1 ]]; then
echo 'short_open_tag = On' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
fi
- php -r "echo ini_get('short_open_tag');"

- export XMLLINT_INDENT=" "

# Toggle the PHPUnit versions if necessary, depending on the known Travis configurations.
Expand Down
140 changes: 140 additions & 0 deletions PHPCompatibility/Sniffs/Miscellaneous/NewPHPOpenTagEOFSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2019 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/

namespace PHPCompatibility\Sniffs\Miscellaneous;

use PHPCompatibility\Sniff;
use PHP_CodeSniffer_File as File;

/**
* PHP 7.4 now supports stand-alone PHP tags at the end of a file (without new line).
*
* > `<?php` at the end of the file (without trailing newline) will now be
* > interpreted as an opening PHP tag. Previously it was interpreted either as
* > `<? php` and resulted in a syntax error (with short_open_tag=1) or was
* > interpreted as a literal `<?php` string (with short_open_tag=0).
*
* @internal Due to an issue with the Tokenizer, this sniff will not work correctly
* on PHP 5.3 in combination with PHPCS < 2.6.0 when short_open_tag is `On`.
* As this is causing "Undefined offset" notices, there is nothing we can
* do to work-around this.
*
* @link https://github.com/php/php-src/blob/30de357fa14480468132bbc22a272aeb91789ba8/UPGRADING#L37-L40
*
* PHP version 7.4
*
* @since 9.2.0
*/
class NewPHPOpenTagEOFSniff extends Sniff
{

/**
* Whether or not short open tags is enabled on the install running the sniffs.
*
* @var bool
*/
protected $shortOpenTags;


/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
$targets = array(
\T_OPEN_TAG_WITH_ECHO,
);

$this->shortOpenTags = (bool) ini_get('short_open_tag');
if ($this->shortOpenTags === false) {
$targets[] = \T_INLINE_HTML;
} else {
$targets[] = \T_STRING;
}

if (version_compare(\PHP_VERSION_ID, '70399', '>')) {
$targets[] = \T_OPEN_TAG;
}

return $targets;
}


/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
if ($this->supportsBelow('7.3') === false) {
return;
}

if ($stackPtr !== ($phpcsFile->numTokens - 1)) {
// We're only interested in the last token in the file.
return;
}

$tokens = $phpcsFile->getTokens();
$contents = $tokens[$stackPtr]['content'];
$error = false;

switch ($tokens[$stackPtr]['code']) {
case \T_INLINE_HTML:
// PHP < 7.4 with short open tags off.
if ($contents === '<?php') {
$error = true;
} elseif ($contents === '<?=') {
// Also cover short open echo tags in PHP 5.3 with short open tags off.
$error = true;
}
break;

case \T_STRING:
// PHP < 7.4 with short open tags on.
if ($contents === 'php'
&& $tokens[($stackPtr - 1)]['code'] === \T_OPEN_TAG
&& $tokens[($stackPtr - 1)]['content'] === '<?'
) {
$error = true;
}
break;

case \T_OPEN_TAG_WITH_ECHO:
// PHP 5.4+.
if (rtrim($contents) === '<?=') {
$error = true;
}
break;

case \T_OPEN_TAG:
// PHP 7.4+.
if ($contents === '<?php') {
$error = true;
}
break;
}

if ($error === true) {
$phpcsFile->addError(
'A PHP open tag at the end of a file, without trailing newline, was not supported in PHP 7.3 or earlier and would result in a syntax error or be interpreted as a literal string',
$stackPtr,
'Found'
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

echo 'a';
?>

<?php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

echo 'a';
?>

<?=
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

echo 'a';
?>

<div>Some HTML</div>

<?php
// Some more PHP.
echo 'something else';

?>
<?php
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php

echo 'a';
?><?php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

echo 'a';
?>

<?=
Loading

0 comments on commit 65d48b9

Please sign in to comment.