Skip to content

Commit

Permalink
NewNowdocQuotedHeredoc: Add sniffing for quoted heredoc identifiers.
Browse files Browse the repository at this point in the history
As the logic needed is nearly the same - especially for the recognition of these tokens in PHP 5.2 -, I've added sniffing for quoted heredoc identifiers to the nowdoc sniff and renamed the sniff to match.
As the nowdoc sniff was only recently introduced and only contained in the most recent release, this should not cause a significant backward-compatibility break.

Includes unit tests.
Includes improved documentation.
  • Loading branch information
jrfnl committed Apr 3, 2017
1 parent 6d345c1 commit 7463ce1
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 24 deletions.
84 changes: 72 additions & 12 deletions Sniffs/PHP/NewNowdocQuotedHeredocSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
/**
* PHPCompatibility_Sniffs_PHP_NewNowdocQuotedHeredocSniff.
*
* PHP 5.3 introduces Nowdoc syntax and (double) quoted identifiers for heredocs.
*
* @category PHP
* @package PHPCompatibility
* @author Juliette Reinders Folmer <phpcompatibility_nospam@adviesenzo.nl>
Expand Down Expand Up @@ -39,6 +41,8 @@ public function register()
$targets[] = constant('T_END_NOWDOC');
}

$targets[] = T_START_HEREDOC;

return $targets;

}//end register()
Expand All @@ -51,7 +55,7 @@ public function register()
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return int|void On older PHP versions passes a pointer to the nowdoc closer
* @return int|void On older PHP versions passes a pointer to the nowdoc/heredoc closer
* to skip passed anything in between in regards to processing
* the file for this sniff.
*/
Expand All @@ -61,18 +65,44 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
return;
}

$tokens = $phpcsFile->getTokens();
$tokens = $phpcsFile->getTokens();
$isNowdoc = false;
$isHeredoc = false;

switch ($tokens[$stackPtr]['type']) {
case 'T_START_NOWDOC':
case 'T_END_NOWDOC':
$isNowdoc = true;
break;

case 'T_START_HEREDOC':
/*
* If we have a heredoc opener, make sure we only report on double quoted identifiers.
* A double quoted identifier will have the opening quote on position 3 in the string:
* `<<<"ID"`
*/
if ($tokens[$stackPtr]['content'][3] !== '"') {
return;
}

$isHeredoc = true;
break;
}

/*
* In PHP 5.2 the T_NOWDOC tokens aren't recognized yet and PHPCS does not
* backfill for it, so we have to sniff for a specific combination of tokens.
* In PHP 5.2 the T_NOWDOC and the quoted T_HEREDOC tokens aren't recognized yet
* and PHPCS does not backfill for it, so we have to sniff for a specific
* combination of tokens.
*/
if ($tokens[$stackPtr]['code'] === T_SL) {
if (isset($tokens[($stackPtr+1)]) === false || $tokens[($stackPtr + 1)]['code'] !== T_LESS_THAN) {
/*
* Check for the right token combination.
*/
if (isset($tokens[($stackPtr + 1)]) === false || $tokens[($stackPtr + 1)]['code'] !== T_LESS_THAN) {
return;
}

if (isset($tokens[($stackPtr+2)]) === false
if (isset($tokens[($stackPtr + 2)]) === false
|| $tokens[($stackPtr + 2)]['code'] !== T_CONSTANT_ENCAPSED_STRING) {
return;
}
Expand All @@ -83,14 +113,30 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
* must start with a non-digit character or underscore"
* @link http://php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc
*/
if (preg_match('`^\'([a-z][a-z0-9_]*)\'$`iD', $tokens[($stackPtr + 2)]['content'], $matches) < 1) {
if (preg_match('`^(["\'])([a-z][a-z0-9_]*)\1$`iD', $tokens[($stackPtr + 2)]['content'], $matches) < 1) {
return;
}

// Remember whether we found a nowdoc or a heredoc.
switch ($matches[1]) {
case "'":
$isNowdoc = true;
break;

case '"':
$isHeredoc = true;
break;

}


/*
* See if we can find the nowdoc/heredoc closer.
*/
$closer = null;

for ($i = ($stackPtr + 3); $i < $phpcsFile->numTokens; $i++) {
$maybeCloser = $phpcsFile->findNext(T_STRING, $i, null, false, $matches[1]);
$maybeCloser = $phpcsFile->findNext(T_STRING, $i, null, false, $matches[2]);
if ($maybeCloser === false) {
return;
}
Expand All @@ -116,11 +162,25 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
}
}

$phpcsFile->addError('Nowdocs are not present in PHP version 5.2 or earlier.', $stackPtr, 'Found');
if ($isNowdoc === true) {
$error = 'Nowdocs are not present in PHP version 5.2 or earlier.';
$phpcsFile->addError($error, $stackPtr, 'Found');

if (isset($closer) !== false) {
// Also throw an error for the closer on older PHP versions and skip forward past it.
// Not needed for newer PHP versions as those will trigger this sniff for the closer token itself.
$phpcsFile->addError($error, $closer, 'Found');
return ($closer + 1);
}
}

if ($isHeredoc === true) {
// Only throw an error for the opener.
$phpcsFile->addError('The Heredoc identifier may not be enclosed in (double) quotes in PHP version 5.2 or earlier.', $stackPtr, 'Found');

if (isset($closer) !== false) {
$phpcsFile->addError('Nowdocs are not present in PHP version 5.2 or earlier.', $closer, 'Found');
return ($closer + 1);
if (isset($closer) !== false) {
return ($closer + 1);
}
}

}//end process()
Expand Down
58 changes: 48 additions & 10 deletions Tests/Sniffs/PHP/NewNowdocQuotedHeredocSniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,50 @@ public function testNowdoc($line)
public function dataNowdoc()
{
return array(
array(11),
array(15),
array(17),
array(19),
array(21),
array(25),
array(28),
array(30),
array(33),
array(36),
array(44),
array(29),
array(32),
array(34),
array(37),
array(40),
array(48),
);
}


/**
* testQuotedHeredoc
*
* @dataProvider dataQuotedHeredoc
*
* @param int $line The line number.
*
* @return void
*/
public function testQuotedHeredoc($line)
{
$file = $this->sniffFile(self::TEST_FILE, '5.2');
$this->assertError($file, $line, 'The Heredoc identifier may not be enclosed in (double) quotes in PHP version 5.2 or earlier.');
}

/**
* Data provider.
*
* @see testQuotedHeredoc()
*
* @return array
*/
public function dataQuotedHeredoc()
{
return array(
array(55),
array(61),
array(69),
array(74),
array(80),
);
}

Expand Down Expand Up @@ -89,14 +123,18 @@ public function dataNoFalsePositives()
$data = array(
array(4),
array(8),
array(26),
array(32),
array(30),
array(36),
array(70),
array(76),
);

// PHPCS 1.x does not support skipping forward.
if (version_compare(PHP_CodeSniffer::VERSION, '2.0', '>=')) {
$data[] = array(38);
$data[] = array(42);
$data[] = array(46);
$data[] = array(82);
$data[] = array(86);
}

return $data;
Expand Down
48 changes: 46 additions & 2 deletions Tests/sniff-examples/new_nowdoc_quoted_heredoc.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
<?php

// Heredoc is ok.
// Normal Heredoc is ok.
$str = <<<EOD
Example of string
spanning multiple lines
using nowdoc syntax.
EOD;

/*
* Nowdoc test cases.
*/

// Nowdoc is not.
$str = <<<'LABEL'
Example of string
Expand Down Expand Up @@ -40,5 +44,45 @@
the ID on a line by itself, like
so:
ID;
Easy, isn't it ?
Easy, or not ?
NECHO;

/*
* Quoted heredoc test cases.
*/

// Quoted heredoc is not.
$str = <<<"LABEL"
Example of string
spanning multiple lines
using nowdoc syntax.
LABEL;

$array = array( 'heredoc' => <<<"a_c"
Example of string
spanning multiple lines
using nowdoc syntax.
a_c
);

// Test properly identifying the closer.
$str = <<<"LABEL"
Now this is not the end: LABEL;
The next line is ;-)
LABEL;

$str = <<<"LABEL"
This is not the end:
LABEL; The next line is ;-)
LABEL;

// Test skipping forward. Doesn't work in PHPCS < 2.0.
echo <<<"NECHO"
You create a quoted heredoc using the
following syntax <<<"ID" to start
and you end the heredoc with
the ID on a line by itself, like
so:
ID;
Easy, or not ?
NECHO;

0 comments on commit 7463ce1

Please sign in to comment.