Skip to content

Commit

Permalink
Fixed bug #2732 : PSR12.Files.FileHeader misidentifies file header in…
Browse files Browse the repository at this point in the history
… mixed content file

This required a rewrite of the sniff to process the entire file looking for a file header. It's still impossible to determine if a docblock is a file comment or not, but it should work with more code now.
  • Loading branch information
gsherwood committed Jan 29, 2020
1 parent e9ebf52 commit 327a08c
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 43 deletions.
1 change: 1 addition & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ http://pear.php.net/dtd/package-2.0.xsd">
-- Thanks to Matthew Peveler for the patch
- Fixed bug #2730 : PSR12.ControlStructures.ControlStructureSpacing does not ignore comments between conditions
-- Thanks to Juliette Reinders Folmer for the patch
- Fixed bug #2732 : PSR12.Files.FileHeader misidentifies file header in mixed content file
- Fixed bug #2745 : AbstractArraySniff wrong indices when mixed coalesce and ternary values
-- Thanks to Michał Bundyra for the patch
- Fixed bug #2748 : Wrong end of statement for fn closures
Expand Down
147 changes: 104 additions & 43 deletions src/Standards/PSR12/Sniffs/Files/FileHeaderSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,86 @@ public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

/*
First, gather information about the statements inside
the file header.
*/
$possibleHeaders = [];

$searchFor = Tokens::$ooScopeTokens;
$searchFor[T_OPEN_TAG] = T_OPEN_TAG;

$openTag = $stackPtr;
do {
$headerLines = $this->getHeaderLines($phpcsFile, $openTag);
if (empty($headerLines) === true && $openTag === $stackPtr) {
// No content in the file.
return;
}

$possibleHeaders[$openTag] = $headerLines;
if (count($headerLines) > 1) {
break;
}

$next = $phpcsFile->findNext($searchFor, ($openTag + 1));
if (isset(Tokens::$ooScopeTokens[$tokens[$next]['code']]) === true) {
// Once we find an OO token, the file content has
// definitely started.
break;
}

$openTag = $next;
} while ($openTag !== false);

if ($openTag === false) {
// We never found a proper file header
// so use the first one as the header.
$openTag = $stackPtr;
} else if (count($possibleHeaders) > 1) {
// There are other PHP blocks before the file header.
$error = 'The file header must be the first content in the file';
$phpcsFile->addError($error, $openTag, 'HeaderPosition');
} else {
// The first possible header was the file header block,
// so make sure it is the first content in the file.
if ($openTag !== 0) {
// Allow for hashbang lines.
$hashbang = false;
if ($tokens[($openTag - 1)]['code'] === T_INLINE_HTML) {
$content = trim($tokens[($openTag - 1)]['content']);
if (substr($content, 0, 2) === '#!') {
$hashbang = true;
}
}

if ($hashbang === false) {
$error = 'The file header must be the first content in the file';
$phpcsFile->addError($error, $openTag, 'HeaderPosition');
}
}
}//end if

$this->processHeaderLines($phpcsFile, $possibleHeaders[$openTag]);

return $phpcsFile->numTokens;

}//end process()


/**
* Gather information about the statements inside a possible file header.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current
* token in the stack.
*
* @return array
*/
public function getHeaderLines(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

$next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
if ($next === false) {
return [];
}

$headerLines = [];
$headerLines[] = [
Expand All @@ -54,17 +130,18 @@ public function process(File $phpcsFile, $stackPtr)
'end' => $stackPtr,
];

$next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 1), null, true);
if ($next === false) {
return;
}

$foundDocblock = false;

$commentOpeners = Tokens::$scopeOpeners;
unset($commentOpeners[T_NAMESPACE]);
unset($commentOpeners[T_DECLARE]);
unset($commentOpeners[T_USE]);
unset($commentOpeners[T_IF]);
unset($commentOpeners[T_WHILE]);
unset($commentOpeners[T_FOR]);
unset($commentOpeners[T_FOREACH]);
unset($commentOpeners[T_DO]);
unset($commentOpeners[T_TRY]);

do {
switch ($tokens[$next]['code']) {
Expand All @@ -77,6 +154,7 @@ public function process(File $phpcsFile, $stackPtr)
// Make sure this is not a code-level docblock.
$end = $tokens[$next]['comment_closer'];
$docToken = $phpcsFile->findNext(Tokens::$emptyTokens, ($end + 1), null, true);

if (isset($commentOpeners[$tokens[$docToken]['code']]) === false
&& isset(Tokens::$methodPrefixes[$tokens[$docToken]['code']]) === false
) {
Expand Down Expand Up @@ -155,17 +233,23 @@ public function process(File $phpcsFile, $stackPtr)
$next = $phpcsFile->findNext(T_WHITESPACE, ($next + 1), null, true);
} while ($next !== false);

if (count($headerLines) === 1) {
// This is only an open tag and doesn't contain the file header.
// We need to keep checking for one in case they put it further
// down in the file.
return;
}
return $headerLines;

/*
Next, check the spacing and grouping of the statements
inside each header block.
*/
}//end getHeaderLines()


/**
* Check the spacing and grouping of the statements inside each header block.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param array $headerLines Header information, as sourced
* from getHeaderLines().
*
* @return int|null
*/
public function processHeaderLines(File $phpcsFile, $headerLines)
{
$tokens = $phpcsFile->getTokens();

$found = [];

Expand Down Expand Up @@ -301,30 +385,7 @@ public function process(File $phpcsFile, $stackPtr)
}//end if
}//end foreach

/*
Finally, make sure this header block is at the very
top of the file.
*/

if ($stackPtr !== 0) {
// Allow for hashbang lines.
$hashbang = false;
if ($tokens[($stackPtr - 1)]['code'] === T_INLINE_HTML) {
$content = trim($tokens[($stackPtr - 1)]['content']);
if (substr($content, 0, 2) === '#!') {
$hashbang = true;
}
}

if ($hashbang === false) {
$error = 'The file header must be the first content in the file';
$phpcsFile->addError($error, $stackPtr, 'HeaderPosition');
}
}

return $phpcsFile->numTokens;

}//end process()
}//end processHeaderLines()


}//end class
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

// Do nothing
// Do nothing
// Do nothing
21 changes: 21 additions & 0 deletions src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.11.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
/**
* Template file docblock.
*
* @package Vendor\Package
*/

if (!defined('ABSPATH')) {
exit;
}

?>

<p><?php echo 'Some text string'; ?></p>

<?php

/**
* Docblock.
*/
include 'foo.php';
22 changes: 22 additions & 0 deletions src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.11.inc.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/**
* Template file docblock.
*
* @package Vendor\Package
*/

if (!defined('ABSPATH')) {
exit;
}

?>

<p><?php echo 'Some text string'; ?></p>

<?php

/**
* Docblock.
*/
include 'foo.php';
17 changes: 17 additions & 0 deletions src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.12.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
echo 'Some content';
?>
<?php
/**
* The header is not the first thing in the file.
*/

namespace Vendor\Package;

/**
* FooBar is an example class.
*/
class FooBar
{
// ... additional PHP code ...
}
18 changes: 18 additions & 0 deletions src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.12.inc.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
echo 'Some content';
?>
<?php

/**
* The header is not the first thing in the file.
*/

namespace Vendor\Package;

/**
* FooBar is an example class.
*/
class FooBar
{
// ... additional PHP code ...
}
24 changes: 24 additions & 0 deletions src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.13.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/**
* File docblock.
*
* @package Vendor\Package
*/

class Foo {
/**
* Function docblock.
*/
public function bar() {
do_something();
?>
<p>Demo</p>

<?php
/**
* Arbitrary docblock.
*/
api_call();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

// Do nothing
6 changes: 6 additions & 0 deletions src/Standards/PSR12/Tests/Files/FileHeaderUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public function getErrorList($testFile='')
];
case 'FileHeaderUnitTest.5.inc':
return [4 => 1];
case 'FileHeaderUnitTest.7.inc':
case 'FileHeaderUnitTest.10.inc':
case 'FileHeaderUnitTest.11.inc':
return [1 => 1];
case 'FileHeaderUnitTest.12.inc':
return [4 => 2];
default:
return [];
}//end switch
Expand Down

0 comments on commit 327a08c

Please sign in to comment.