Source for file FileCommentSniff.php
Documentation is available at FileCommentSniff.php
* Parses and verifies the doc comments for files.
* @author Greg Sherwood <gsherwood@squiz.net>
* @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
namespace PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Common;
class FileCommentSniff implements Sniff
* Tags in correct order and related info.
'allow_multiple' => false ,
'allow_multiple' => false ,
'allow_multiple' => false ,
'allow_multiple' => true ,
'allow_multiple' => true ,
'allow_multiple' => false ,
'allow_multiple' => false ,
'allow_multiple' => true ,
'allow_multiple' => true ,
'allow_multiple' => false ,
'allow_multiple' => false ,
* Returns an array of tokens this test wants to listen for.
public function register ()
return array (T_OPEN_TAG );
* Processes this test, when one of its tokens is encountered.
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
public function process (File $phpcsFile, $stackPtr)
$tokens = $phpcsFile->getTokens ();
// Find the next non whitespace token.
$commentStart = $phpcsFile->findNext (T_WHITESPACE , ($stackPtr + 1 ), null , true );
// Allow declare() statements at the top of the file.
if ($tokens[$commentStart]['code'] === T_DECLARE ) {
$semicolon = $phpcsFile->findNext (T_SEMICOLON, ($commentStart + 1 ));
$commentStart = $phpcsFile->findNext (T_WHITESPACE , ($semicolon + 1 ), null , true );
if ($tokens[$commentStart]['code'] === T_COMMENT ) {
if (strstr($tokens[$commentStart]['content'], 'vim:') !== false ) {
$commentStart = $phpcsFile->findNext (
$errorToken = ($stackPtr + 1 );
if (isset ($tokens[$errorToken]) === false ) {
if ($tokens[$commentStart]['code'] === T_CLOSE_TAG ) {
// We are only interested if this is the first open tag.
return ($phpcsFile->numTokens + 1 );
} else if ($tokens[$commentStart]['code'] === T_COMMENT ) {
$error = 'You must use "/**" style comments for a file comment';
$phpcsFile->addError ($error, $errorToken, 'WrongStyle');
$phpcsFile->recordMetric ($stackPtr, 'File has doc comment', 'yes');
return ($phpcsFile->numTokens + 1 );
} else if ($commentStart === false
$phpcsFile->addError ('Missing file doc comment', $errorToken, 'Missing');
$phpcsFile->recordMetric ($stackPtr, 'File has doc comment', 'no');
return ($phpcsFile->numTokens + 1 );
$commentEnd = $tokens[$commentStart]['comment_closer'];
$nextToken = $phpcsFile->findNext (
if (in_array($tokens[$nextToken]['code'], $ignore) === true ) {
$phpcsFile->addError ('Missing file doc comment', $stackPtr, 'Missing');
$phpcsFile->recordMetric ($stackPtr, 'File has doc comment', 'no');
return ($phpcsFile->numTokens + 1 );
$phpcsFile->recordMetric ($stackPtr, 'File has doc comment', 'yes');
// Check the PHP Version, which should be in some text before the first tag.
for ($i = ($commentStart + 1 ); $i < $commentEnd; $i++ ) {
$error = 'PHP version not specified';
$phpcsFile->addWarning ($error, $commentEnd, 'MissingVersion');
$this->processTags ($phpcsFile, $stackPtr, $commentStart);
// Ignore the rest of the file.
return ($phpcsFile->numTokens + 1 );
* Processes each required or optional tag.
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param int $commentStart Position in the stack where the comment started.
protected function processTags ($phpcsFile, $stackPtr, $commentStart)
$tokens = $phpcsFile->getTokens ();
if (get_class($this) === 'PHP_CodeSniffer\Standards\PEAR\Sniffs\Commenting\FileCommentSniff') {
$commentEnd = $tokens[$commentStart]['comment_closer'];
foreach ($tokens[$commentStart]['comment_tags'] as $tag) {
$name = $tokens[$tag]['content'];
if (isset ($this->tags[$name]) === false ) {
if ($this->tags[$name]['allow_multiple'] === false && isset ($tagTokens[$name]) === true ) {
$error = 'Only one %s tag is allowed in a %s comment';
$phpcsFile->addError ($error, $tag, 'Duplicate'. ucfirst(substr($name, 1 )). 'Tag', $data);
$tagTokens[$name][] = $tag;
if ($string === false || $tokens[$string]['line'] !== $tokens[$tag]['line']) {
$error = 'Content missing for %s tag in %s comment';
$phpcsFile->addError ($error, $tag, 'Empty'. ucfirst(substr($name, 1 )). 'Tag', $data);
// Check if the tags are in the correct position.
foreach ($this->tags as $tag => $tagData) {
if (isset ($tagTokens[$tag]) === false ) {
if ($tagData['required'] === true ) {
$error = 'Missing %s tag in %s comment';
$phpcsFile->addError ($error, $commentEnd, 'Missing'. ucfirst(substr($tag, 1 )). 'Tag', $data);
$method = 'process'. substr($tag, 1 );
// Process each tag if a method is defined.
if (isset ($foundTags[$pos]) === false ) {
if ($foundTags[$pos] !== $tag) {
$error = 'The tag in position %s should be the %s tag';
$phpcsFile->addError ($error, $tokens[$commentStart]['comment_tags'][$pos], ucfirst(substr($tag, 1 )). 'TagOrder', $data);
// Account for multiple tags.
while (isset ($foundTags[$pos]) === true && $foundTags[$pos] === $tag) {
* Process the category tag.
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param array $tags The tokens for these tags.
protected function processCategory ($phpcsFile, array $tags)
$tokens = $phpcsFile->getTokens ();
foreach ($tags as $tag) {
if ($tokens[($tag + 2 )]['code'] !== T_DOC_COMMENT_STRING ) {
$content = $tokens[($tag + 2 )]['content'];
if (Common ::isUnderscoreName ($content) !== true ) {
$nameBits = explode('_', $newContent);
foreach ($nameBits as $bit) {
$error = 'Category name "%s" is not valid; consider "%s" instead';
$validName = trim($newName, '_');
$phpcsFile->addError ($error, $tag, 'InvalidCategory', $data);
* Process the package tag.
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param array $tags The tokens for these tags.
protected function processPackage ($phpcsFile, array $tags)
$tokens = $phpcsFile->getTokens ();
foreach ($tags as $tag) {
$content = $tokens[($tag + 2 )]['content'];
if (Common ::isUnderscoreName ($content) === true ) {
$newContent = trim($newContent, '_');
$newContent = preg_replace('/[^A-Za-z_]/', '', $newContent);
if ($newContent === '') {
$error = 'Package name "%s" is not valid';
$phpcsFile->addError ($error, $tag, 'InvalidPackageValue', $data);
$nameBits = explode('_', $newContent);
foreach ($nameBits as $bit) {
$error = 'Package name "%s" is not valid; consider "%s" instead';
$validName = trim($newName, '_');
$phpcsFile->addError ($error, $tag, 'InvalidPackage', $data);
* Process the subpackage tag.
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param array $tags The tokens for these tags.
protected function processSubpackage ($phpcsFile, array $tags)
$tokens = $phpcsFile->getTokens ();
foreach ($tags as $tag) {
$content = $tokens[($tag + 2 )]['content'];
if (Common ::isUnderscoreName ($content) === true ) {
$nameBits = explode('_', $newContent);
foreach ($nameBits as $bit) {
$error = 'Subpackage name "%s" is not valid; consider "%s" instead';
$validName = trim($newName, '_');
$phpcsFile->addError ($error, $tag, 'InvalidSubpackage', $data);
}//end processSubpackage()
* Process the author tag(s) that this header comment has.
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param array $tags The tokens for these tags.
protected function processAuthor ($phpcsFile, array $tags)
$tokens = $phpcsFile->getTokens ();
foreach ($tags as $tag) {
$content = $tokens[($tag + 2 )]['content'];
// Dot character cannot be the first or last character in the local-part.
$localMiddle = $local. '.\w';
if (preg_match('/^([^<]*)\s+<(['. $local. '](['. $localMiddle. ']*['. $local. '])*@[\da-zA-Z][-.\w]*[\da-zA-Z]\.[a-zA-Z]{2,7})>$/', $content) === 0 ) {
$error = 'Content of the @author tag must be in the form "Display Name <username@example.com>"';
$phpcsFile->addError ($error, $tag, 'InvalidAuthors');
* Process the copyright tags.
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param array $tags The tokens for these tags.
protected function processCopyright ($phpcsFile, array $tags)
$tokens = $phpcsFile->getTokens ();
foreach ($tags as $tag) {
$content = $tokens[($tag + 2 )]['content'];
if (preg_match('/^([0-9]{4})((.{1})([0-9]{4}))? (.+)$/', $content, $matches) !== 0 ) {
// Check earliest-latest year order.
if ($matches[3 ] !== '' && $matches[3 ] !== null ) {
if ($matches[3 ] !== '-') {
$error = 'A hyphen must be used between the earliest and latest year';
$phpcsFile->addError ($error, $tag, 'CopyrightHyphen');
if ($matches[4 ] !== '' && $matches[4 ] !== null && $matches[4 ] < $matches[1 ]) {
$error = " Invalid year span \"$matches[1]$matches[3]$matches[4]\" found; consider \"$matches[4]-$matches[1]\" instead";
$phpcsFile->addWarning ($error, $tag, 'InvalidCopyright');
$error = '@copyright tag must contain a year and the name of the copyright holder';
$phpcsFile->addError ($error, $tag, 'IncompleteCopyright');
}//end processCopyright()
* Process the license tag.
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param array $tags The tokens for these tags.
protected function processLicense ($phpcsFile, array $tags)
$tokens = $phpcsFile->getTokens ();
foreach ($tags as $tag) {
$content = $tokens[($tag + 2 )]['content'];
preg_match('/^([^\s]+)\s+(.*)/', $content, $matches);
if (count($matches) !== 3 ) {
$error = '@license tag must contain a URL and a license name';
$phpcsFile->addError ($error, $tag, 'IncompleteLicense');
* Process the version tag.
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param array $tags The tokens for these tags.
protected function processVersion ($phpcsFile, array $tags)
$tokens = $phpcsFile->getTokens ();
foreach ($tags as $tag) {
$content = $tokens[($tag + 2 )]['content'];
if (strstr($content, 'CVS:') === false
&& strstr($content, 'SVN:') === false
&& strstr($content, 'GIT:') === false
&& strstr($content, 'HG:') === false
$error = 'Invalid version "%s" in file comment; consider "CVS: <cvs_id>" or "SVN: <svn_id>" or "GIT: <git_id>" or "HG: <hg_id>" instead';
$phpcsFile->addWarning ($error, $tag, 'InvalidVersion', $data);
Documentation generated on Mon, 11 Mar 2019 15:27:28 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|