-
-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ New
Generic.Strings.UnnecessaryHeredoc
sniff
New `Generic.Strings.UnnecessaryHeredoc` sniff which encourages the use of nowdocs instead of heredocs, when there is no interpolation or expressions in the body text. This sniff will hopefully help with the PERCS work as it intends to cover the following PER rule: > A nowdoc SHOULD be used wherever possible. Heredoc MAY be used when a nowdoc does not satisfy requirements. Includes fixer. Includes tests. Includes XML docs.
- Loading branch information
Showing
8 changed files
with
644 additions
and
0 deletions.
There are no files selected for viewing
39 changes: 39 additions & 0 deletions
39
src/Standards/Generic/Docs/Strings/UnnecessaryHeredocStandard.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<documentation title="Unnecessary Heredoc"> | ||
<standard> | ||
<![CDATA[ | ||
If no interpolation or expressions are used in the body of a heredoc, nowdoc syntax should be used instead. | ||
]]> | ||
</standard> | ||
<code_comparison> | ||
<code title="Valid: Using nowdoc syntax for a text string without any interpolation or expressions."> | ||
<![CDATA[ | ||
$nowdoc = <em><<<'EOD'</em> | ||
some text | ||
EOD; | ||
]]> | ||
</code> | ||
<code title="Invalid: Using heredoc syntax for a text string without any interpolation or expressions."> | ||
<![CDATA[ | ||
$heredoc = <em><<<EOD</em> | ||
some text | ||
EOD; | ||
]]> | ||
</code> | ||
</code_comparison> | ||
<code_comparison> | ||
<code title="Valid: Using heredoc syntax for a text string containing interpolation or expressions."> | ||
<![CDATA[ | ||
$heredoc = <em><<<"EOD"</em> | ||
some $text | ||
EOD; | ||
]]> | ||
</code> | ||
<code title="Invalid: Using heredoc syntax for a text string without any interpolation or expressions."> | ||
<![CDATA[ | ||
$heredoc = <em><<<"EOD"</em> | ||
some text | ||
EOD; | ||
]]> | ||
</code> | ||
</code_comparison> | ||
</documentation> |
93 changes: 93 additions & 0 deletions
93
src/Standards/Generic/Sniffs/Strings/UnnecessaryHeredocSniff.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php | ||
/** | ||
* Prefer the use of nowdoc over heredoc. | ||
* | ||
* @author Juliette Reinders Folmer <[email protected]> | ||
* @copyright 2024 PHPCSStandards and contributors | ||
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence | ||
*/ | ||
|
||
namespace PHP_CodeSniffer\Standards\Generic\Sniffs\Strings; | ||
|
||
use PHP_CodeSniffer\Files\File; | ||
use PHP_CodeSniffer\Sniffs\Sniff; | ||
|
||
class UnnecessaryHeredocSniff implements Sniff | ||
{ | ||
|
||
|
||
/** | ||
* Returns an array of tokens this test wants to listen for. | ||
* | ||
* @return array<int|string> | ||
*/ | ||
public function register() | ||
{ | ||
return [T_START_HEREDOC]; | ||
|
||
}//end register() | ||
|
||
|
||
/** | ||
* 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. | ||
* | ||
* @return void | ||
*/ | ||
public function process(File $phpcsFile, $stackPtr) | ||
{ | ||
$tokens = $phpcsFile->getTokens(); | ||
|
||
if (isset($tokens[$stackPtr]['scope_closer']) === false) { | ||
// Just to be safe. Shouldn't be possible as in that case, the opener shouldn't be tokenized | ||
// to T_START_HEREDOC by PHP. | ||
return; | ||
} | ||
|
||
$closer = $tokens[$stackPtr]['scope_closer']; | ||
$body = ''; | ||
|
||
// Collect all the tokens within the heredoc body. | ||
for ($i = ($stackPtr + 1); $i < $closer; $i++) { | ||
$body .= $tokens[$i]['content']; | ||
} | ||
|
||
$tokenizedBody = token_get_all(sprintf("<?php <<<EOD\n%s\nEOD;\n?>", $body)); | ||
foreach ($tokenizedBody as $ptr => $bodyToken) { | ||
if (is_array($bodyToken) === false) { | ||
continue; | ||
} | ||
|
||
if ($bodyToken[0] === T_DOLLAR_OPEN_CURLY_BRACES | ||
|| $bodyToken[0] === T_VARIABLE | ||
) { | ||
// Contains interpolation or expression. | ||
return; | ||
} | ||
|
||
if ($bodyToken[0] === T_CURLY_OPEN | ||
&& is_array($tokenizedBody[($ptr + 1)]) === false | ||
&& $tokenizedBody[($ptr + 1)] === '$' | ||
) { | ||
// Contains interpolation or expression. | ||
return; | ||
} | ||
} | ||
|
||
$warning = 'Detected heredoc without interpolation or expressions. Use nowdoc syntax instead'; | ||
|
||
$fix = $phpcsFile->addFixableWarning($warning, $stackPtr, 'Found'); | ||
if ($fix === true) { | ||
$identifier = trim(ltrim($tokens[$stackPtr]['content'], '<')); | ||
$replaceWith = "'".trim($identifier, '"')."'"; | ||
$replacement = str_replace($identifier, $replaceWith, $tokens[$stackPtr]['content']); | ||
$phpcsFile->fixer->replaceToken($stackPtr, $replacement); | ||
} | ||
|
||
}//end process() | ||
|
||
|
||
}//end class |
108 changes: 108 additions & 0 deletions
108
src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.1.inc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<?php | ||
|
||
/* | ||
* Test file 1 and 2 mirror each other, with file 1 containing non-indented cross-version compatible heredoc/nowdoc syntax, | ||
* while the code samples in file 2 use PHP 7.3+ flexible heredoc/nowdoc syntax. | ||
* | ||
* These two files should be kept in sync! | ||
*/ | ||
|
||
$nowdoc = <<<'EOD' | ||
some text | ||
EOD; | ||
|
||
$heredoc = <<<END | ||
some $foo text | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
some {$foo[0]} text | ||
END; | ||
|
||
$heredoc = <<<END | ||
{$foo?->bar} | ||
END; | ||
|
||
$heredoc = <<< "END" | ||
some ${beers::softdrink} | ||
END; | ||
|
||
$heredoc = <<< END | ||
{${$object->getName()}} text | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
some {${getName()}} | ||
END; | ||
|
||
$heredoc = <<<END | ||
${substr('laruence', 0, 2)} | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
some {$foo['bar']->baz()()} | ||
END; | ||
|
||
$heredoc = <<<END | ||
{$obj->values[3]->name} text | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
some ${$bar} | ||
END; | ||
|
||
$heredoc = <<<END | ||
${foo->bar} text | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
${foo["${bar}"]} text | ||
END; | ||
|
||
$heredoc = <<<END | ||
some ${foo["${bar[\'baz\']}"]} | ||
END; | ||
$heredoc = <<<"END" | ||
${foo->{${'a'}}} text | ||
END; | ||
|
||
$heredoc = <<<END | ||
some {$foo->{$baz[1]}} | ||
END; | ||
|
||
$heredoc = <<<END | ||
some text | ||
{${beers::$ale}} | ||
some text | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
$people->john's wife greeted $people->robert. | ||
END; | ||
|
||
$heredoc = <<<END | ||
Let's make sure it also works with this: {$arr[foo][3]} | ||
END; | ||
|
||
$heredoc = <<<END | ||
Testing ${foo["${bar | ||
['baz'] | ||
}"]} and more testing | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
Testing {${foo["${bar | ||
['baz'] | ||
}"]}} and more testing | ||
END; | ||
|
||
$heredoc = <<<END | ||
some text | ||
END; | ||
|
||
$heredoc = <<< "END" | ||
some text | ||
some \$text | ||
some text | ||
END; |
108 changes: 108 additions & 0 deletions
108
src/Standards/Generic/Tests/Strings/UnnecessaryHeredocUnitTest.1.inc.fixed
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<?php | ||
|
||
/* | ||
* Test file 1 and 2 mirror each other, with file 1 containing non-indented cross-version compatible heredoc/nowdoc syntax, | ||
* while the code samples in file 2 use PHP 7.3+ flexible heredoc/nowdoc syntax. | ||
* | ||
* These two files should be kept in sync! | ||
*/ | ||
|
||
$nowdoc = <<<'EOD' | ||
some text | ||
EOD; | ||
|
||
$heredoc = <<<END | ||
some $foo text | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
some {$foo[0]} text | ||
END; | ||
|
||
$heredoc = <<<END | ||
{$foo?->bar} | ||
END; | ||
|
||
$heredoc = <<< "END" | ||
some ${beers::softdrink} | ||
END; | ||
|
||
$heredoc = <<< END | ||
{${$object->getName()}} text | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
some {${getName()}} | ||
END; | ||
|
||
$heredoc = <<<END | ||
${substr('laruence', 0, 2)} | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
some {$foo['bar']->baz()()} | ||
END; | ||
|
||
$heredoc = <<<END | ||
{$obj->values[3]->name} text | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
some ${$bar} | ||
END; | ||
|
||
$heredoc = <<<END | ||
${foo->bar} text | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
${foo["${bar}"]} text | ||
END; | ||
|
||
$heredoc = <<<END | ||
some ${foo["${bar[\'baz\']}"]} | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
${foo->{${'a'}}} text | ||
END; | ||
|
||
$heredoc = <<<END | ||
some {$foo->{$baz[1]}} | ||
END; | ||
|
||
$heredoc = <<<END | ||
some text | ||
{${beers::$ale}} | ||
some text | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
$people->john's wife greeted $people->robert. | ||
END; | ||
|
||
$heredoc = <<<END | ||
Let's make sure it also works with this: {$arr[foo][3]} | ||
END; | ||
|
||
$heredoc = <<<END | ||
Testing ${foo["${bar | ||
['baz'] | ||
}"]} and more testing | ||
END; | ||
|
||
$heredoc = <<<"END" | ||
Testing {${foo["${bar | ||
['baz'] | ||
}"]}} and more testing | ||
END; | ||
|
||
$heredoc = <<<'END' | ||
some text | ||
END; | ||
|
||
$heredoc = <<< 'END' | ||
some text | ||
some \$text | ||
some text | ||
END; |
Oops, something went wrong.