Skip to content

Commit

Permalink
Sniff for declare strict types
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes authored and kukulich committed Jan 14, 2016
1 parent 61c5250 commit 716a873
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 0 deletions.
107 changes: 107 additions & 0 deletions SlevomatCodingStandard/Sniffs/Typehints/DeclareStrictTypesSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

namespace SlevomatCodingStandard\Sniffs\Typehints;

use PHP_CodeSniffer_File;
use SlevomatCodingStandard\Helpers\TokenHelper;

class DeclareStrictTypesSniff implements \PHP_CodeSniffer_Sniff
{

const CODE_DECLARE_STRICT_TYPES_MISSING = 'declareStrictTypesMissing';

const CODE_INCORRECT_WHITESPACE_BETWEEN_OPEN_TAG_AND_DECLARE = 'incorrectWhitespaceBetweenOpenTagAndDeclare';

public $newlinesCountBetweenOpenTagAndDeclare = 0;

/** @var string[] */
private static $alreadyProcessedFiles = [];

/**
* @return integer[]
*/
public function register()
{
return [
T_OPEN_TAG,
];
}

/**
* @param \PHP_CodeSniffer_File $phpcsFile
* @param integer $openTagPointer
*/
public function process(PHP_CodeSniffer_File $phpcsFile, $openTagPointer)
{
if (isset(self::$alreadyProcessedFiles[$phpcsFile->getFilename()])) {
return;
}

self::$alreadyProcessedFiles[$phpcsFile->getFilename()] = true;

$tokens = $phpcsFile->getTokens();
$declarePointer = TokenHelper::findNextNonWhitespace($phpcsFile, $openTagPointer + 1);
if ($tokens[$declarePointer]['code'] !== T_DECLARE) {
$this->reportMissingDeclareStrict($phpcsFile, $openTagPointer);
return;
}
$stringPointer = $phpcsFile->findNext(T_STRING, $declarePointer + 1);
if ($tokens[$stringPointer]['content'] !== 'strict_types') {
$this->reportMissingDeclareStrict($phpcsFile, $declarePointer);
return;
}

$numberPointer = $phpcsFile->findNext(T_LNUMBER, $stringPointer + 1);
if ($tokens[$numberPointer]['content'] !== '1') {
$this->reportMissingDeclareStrict($phpcsFile, $declarePointer);
return;
}

$openingWhitespace = substr($tokens[$openTagPointer]['content'], strlen('<?php'));
$newlinesCountBetweenOpenTagAndDeclare = (int) trim($this->newlinesCountBetweenOpenTagAndDeclare);
if ($newlinesCountBetweenOpenTagAndDeclare === 0) {
if ($openingWhitespace !== ' ') {
$phpcsFile->addError(
'There must be a single space between the PHP open tag and declare statement.',
$declarePointer,
self::CODE_INCORRECT_WHITESPACE_BETWEEN_OPEN_TAG_AND_DECLARE
);
}
} else {
$startToken = $openTagPointer + 1;
do {
$possibleWhitespacePointer = TokenHelper::findNextAnyToken($phpcsFile, $startToken);
if ($possibleWhitespacePointer !== null && $tokens[$possibleWhitespacePointer]['code'] === T_WHITESPACE) {
$openingWhitespace .= $tokens[$possibleWhitespacePointer]['content'];
}
$startToken++;
} while ($possibleWhitespacePointer !== null && $tokens[$possibleWhitespacePointer]['code'] === T_WHITESPACE);
$newlinesCount = substr_count($openingWhitespace, $phpcsFile->eolChar);
if ($newlinesCount !== $newlinesCountBetweenOpenTagAndDeclare) {
$phpcsFile->addError(
sprintf(
'Expected %d newlines between PHP open tag and declare statement, found %d.',
$newlinesCountBetweenOpenTagAndDeclare,
$newlinesCount
),
$declarePointer,
self::CODE_INCORRECT_WHITESPACE_BETWEEN_OPEN_TAG_AND_DECLARE
);
}
}
}

/**
* @param \PHP_CodeSniffer_File $phpcsFile
* @param integer $openTagPointer
*/
private function reportMissingDeclareStrict(PHP_CodeSniffer_File $phpcsFile, $openTagPointer)
{
$phpcsFile->addError(
'Missing declare(strict_types=1).',
$openTagPointer,
self::CODE_DECLARE_STRICT_TYPES_MISSING
);
}

}
1 change: 1 addition & 0 deletions ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<ruleset name="Slevomat Coding Standard">
<rule ref="SlevomatCodingStandard/ruleset.xml">
<exclude name="SlevomatCodingStandard.Exceptions.ReferenceThrowableOnly"/>
<exclude name="SlevomatCodingStandard.Typehints.DeclareStrictTypes"/>
</rule>
<rule ref="vendor/consistence/coding-standard/Consistence/ruleset.xml" />
<rule ref="SlevomatCodingStandard.Files.TypeNameMatchesFileName">
Expand Down
83 changes: 83 additions & 0 deletions tests/Sniffs/Typehints/DeclareStrictTypesSniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

namespace SlevomatCodingStandard\Sniffs\Typehints;

class DeclareStrictTypesSniffTest extends \SlevomatCodingStandard\Sniffs\TestCase
{

public function testMultipleOpenTagsInFile()
{
$this->assertNoSniffErrorInFile($this->checkFile(__DIR__ . '/data/declareStrictTypesMultipleOpenTags.php'));
}

public function dataDeclareStrictTypesMissing()
{
return [
[
__DIR__ . '/data/declareStrictTypesMissing.php',
1,
],
[
__DIR__ . '/data/declareTicks.php',
3,
],
[
__DIR__ . '/data/declareStrictTypesZero.php',
3,
],
];
}

/**
* @dataProvider dataDeclareStrictTypesMissing
* @param string $file
* @param integer $line
*/
public function testDeclareStrictTypesMissing($file, $line)
{
$report = $this->checkFile($file);
$this->assertSniffError(
$report,
$line,
DeclareStrictTypesSniff::CODE_DECLARE_STRICT_TYPES_MISSING
);
}

public function testDeclareStrictTwoNewlines()
{
$file = __DIR__ . '/data/declareStrictTypesTwoNewlines.php';
$this->assertNoSniffErrorInFile($this->checkFile($file, [
'newlinesCountBetweenOpenTagAndDeclare' => ' 2 ',
]));
}

public function testDeclareStrictTwoNewlinesError()
{
$report = $this->checkFile(__DIR__ . '/data/declareStrictTypesTwoNewlinesError.php');
$this->assertSniffError(
$report,
3,
DeclareStrictTypesSniff::CODE_INCORRECT_WHITESPACE_BETWEEN_OPEN_TAG_AND_DECLARE,
'There must be a single space between the PHP open tag and declare statement.'
);
}

public function testDeclareStrictOneSpaceError()
{
$report = $this->checkFile(__DIR__ . '/data/declareStrictTypesOneSpaceError.php', [
'newlinesCountBetweenOpenTagAndDeclare' => '2',
]);
$this->assertSniffError(
$report,
1,
DeclareStrictTypesSniff::CODE_INCORRECT_WHITESPACE_BETWEEN_OPEN_TAG_AND_DECLARE,
'Expected 2 newlines between PHP open tag and declare statement, found 0.'
);
}

public function testDeclareStrictOneSpace()
{
$this->assertNoSniffErrorInFile($this->checkFile(__DIR__ . '/data/declareStrictTypesOneSpace.php'));
}

}
6 changes: 6 additions & 0 deletions tests/Sniffs/Typehints/data/declareStrictTypesMissing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

class Foo
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php declare(strict_types = 1);

?><html><?php

?></html>
1 change: 1 addition & 0 deletions tests/Sniffs/Typehints/data/declareStrictTypesOneSpace.php
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?php declare(strict_types=1);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?php declare(strict_types=1);
3 changes: 3 additions & 0 deletions tests/Sniffs/Typehints/data/declareStrictTypesTwoNewlines.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

declare(strict_types=1);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

declare(strict_types=1);
3 changes: 3 additions & 0 deletions tests/Sniffs/Typehints/data/declareStrictTypesZero.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

declare(strict_types=0);
3 changes: 3 additions & 0 deletions tests/Sniffs/Typehints/data/declareTicks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

declare(ticks = 1000);

0 comments on commit 716a873

Please sign in to comment.