Skip to content

Commit

Permalink
Tokenizer: support hash comment for ignore annotations
Browse files Browse the repository at this point in the history
Until now, for the PHPCS native ignore annotations, only `//` slash or `/* */` star-style comments were supported.

This adds support for PHPCS native ignore annotations using `#` hash-style comments.

Includes unit tests and syncing of the `ltrim()` used in `File::process()` with the `ltrim()` used in `Tokenizer::createPositionMap()`.
  • Loading branch information
jrfnl committed Apr 27, 2021
1 parent b84c541 commit 807c10c
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/Files/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ public function process()
|| $token['code'] === T_DOC_COMMENT_TAG
|| ($inTests === true && $token['code'] === T_INLINE_HTML))
) {
$commentText = ltrim($this->tokens[$stackPtr]['content'], ' /*');
$commentText = ltrim($this->tokens[$stackPtr]['content'], " \t/*#");
$commentTextLower = strtolower($commentText);
if (strpos($commentText, '@codingStandards') !== false) {
if (strpos($commentText, '@codingStandardsIgnoreFile') !== false) {
Expand Down
2 changes: 1 addition & 1 deletion src/Tokenizers/Tokenizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ private function createPositionMap()
|| $this->tokens[$i]['code'] === T_DOC_COMMENT_TAG
|| ($inTests === true && $this->tokens[$i]['code'] === T_INLINE_HTML)
) {
$commentText = ltrim($this->tokens[$i]['content'], " \t/*");
$commentText = ltrim($this->tokens[$i]['content'], " \t/*#");
$commentText = rtrim($commentText, " */\t\r\n");
$commentTextLower = strtolower($commentText);
if (strpos($commentText, '@codingStandards') !== false) {
Expand Down
170 changes: 167 additions & 3 deletions tests/Core/ErrorSuppressionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,46 @@ public function testSuppressError()
$this->assertEquals(0, $numErrors);
$this->assertCount(0, $errors);

// Process with inline hash comment suppression.
$content = '<?php '.PHP_EOL.'# phpcs:disable'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'# phpcs:enable';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(0, $numErrors);
$this->assertCount(0, $errors);

// Process with multi-line inline hash comment suppression, tab-indented.
$content = '<?php '.PHP_EOL."\t".'# For reasons'.PHP_EOL."\t".'# phpcs:disable'.PHP_EOL."\t".'$var = FALSE;'.PHP_EOL."\t".'# phpcs:enable';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(0, $numErrors);
$this->assertCount(0, $errors);

// Process with inline hash @ comment suppression.
$content = '<?php '.PHP_EOL.'# @phpcs:disable'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'# @phpcs:enable';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(0, $numErrors);
$this->assertCount(0, $errors);

// Process with inline hash comment suppression mixed case.
$content = '<?php '.PHP_EOL.'# PHPCS:Disable'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'# pHPcs:enabLE';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(0, $numErrors);
$this->assertCount(0, $errors);

// Process with inline comment suppression (deprecated syntax).
$content = '<?php '.PHP_EOL.'// @codingStandardsIgnoreStart'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'// @codingStandardsIgnoreEnd';
$file = new DummyFile($content, $ruleset, $config);
Expand Down Expand Up @@ -217,6 +257,26 @@ public function testSuppressSomeErrors()
$this->assertEquals(1, $numErrors);
$this->assertCount(1, $errors);

// Process with suppression (hash comment).
$content = '<?php '.PHP_EOL.'# phpcs:disable'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'# phpcs:enable'.PHP_EOL.'$var = TRUE;';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(1, $numErrors);
$this->assertCount(1, $errors);

// Process with @ suppression (hash comment).
$content = '<?php '.PHP_EOL.'# @phpcs:disable'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'# @phpcs:enable'.PHP_EOL.'$var = TRUE;';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(1, $numErrors);
$this->assertCount(1, $errors);

// Process with suppression (deprecated syntax).
$content = '<?php '.PHP_EOL.'// @codingStandardsIgnoreStart'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'// @codingStandardsIgnoreEnd'.PHP_EOL.'$var = TRUE;';
$file = new DummyFile($content, $ruleset, $config);
Expand Down Expand Up @@ -372,6 +432,26 @@ public function testSuppressLine()
$this->assertEquals(1, $numErrors);
$this->assertCount(1, $errors);

// Process with suppression on line before (hash comment).
$content = '<?php '.PHP_EOL.'# phpcs:ignore'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'$var = FALSE;';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(1, $numErrors);
$this->assertCount(1, $errors);

// Process with @ suppression on line before (hash comment).
$content = '<?php '.PHP_EOL.'# @phpcs:ignore'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'$var = FALSE;';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(1, $numErrors);
$this->assertCount(1, $errors);

// Process with suppression on line before.
$content = '<?php '.PHP_EOL.'/* phpcs:ignore */'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'$var = FALSE;';
$file = new DummyFile($content, $ruleset, $config);
Expand Down Expand Up @@ -432,6 +512,26 @@ public function testSuppressLine()
$this->assertEquals(1, $numErrors);
$this->assertCount(1, $errors);

// Process with suppression on same line (hash comment).
$content = '<?php '.PHP_EOL.'$var = FALSE; # phpcs:ignore'.PHP_EOL.'$var = FALSE;';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(1, $numErrors);
$this->assertCount(1, $errors);

// Process with @ suppression on same line (hash comment).
$content = '<?php '.PHP_EOL.'$var = FALSE; # @phpcs:ignore'.PHP_EOL.'$var = FALSE;';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(1, $numErrors);
$this->assertCount(1, $errors);

// Process with suppression on same line (deprecated syntax).
$content = '<?php '.PHP_EOL.'$var = FALSE; // @codingStandardsIgnoreLine'.PHP_EOL.'$var = FALSE;';
$file = new DummyFile($content, $ruleset, $config);
Expand Down Expand Up @@ -478,6 +578,16 @@ public function testNestedSuppressLine()
$this->assertEquals(0, $numErrors);
$this->assertCount(0, $errors);

// Process with disable/enable suppression and no single line suppression (hash comment).
$content = '<?php '.PHP_EOL.'# phpcs:disable'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'$var = TRUE;'.PHP_EOL.'# phpcs:enable';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(0, $numErrors);
$this->assertCount(0, $errors);

// Process with disable/enable suppression and no single line suppression (deprecated syntax).
$content = '<?php '.PHP_EOL.'// @codingStandardsIgnoreStart'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'$var = TRUE;'.PHP_EOL.'// @codingStandardsIgnoreEnd';
$file = new DummyFile($content, $ruleset, $config);
Expand Down Expand Up @@ -508,6 +618,16 @@ public function testNestedSuppressLine()
$this->assertEquals(0, $numErrors);
$this->assertCount(0, $errors);

// Process with line @ suppression nested within disable/enable @ suppression (hash comment).
$content = '<?php '.PHP_EOL.'# @phpcs:disable'.PHP_EOL.'# @phpcs:ignore'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'$var = TRUE;'.PHP_EOL.'# @phpcs:enable';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(0, $numErrors);
$this->assertCount(0, $errors);

// Process with line suppression nested within disable/enable suppression (deprecated syntax).
$content = '<?php '.PHP_EOL.'// @codingStandardsIgnoreStart'.PHP_EOL.'// @codingStandardsIgnoreLine'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'$var = TRUE;'.PHP_EOL.'// @codingStandardsIgnoreEnd';
$file = new DummyFile($content, $ruleset, $config);
Expand Down Expand Up @@ -554,6 +674,16 @@ public function testSuppressScope()
$this->assertEquals(0, $numErrors);
$this->assertCount(0, $errors);

// Process with suppression (hash comment).
$content = '<?php '.PHP_EOL.'class MyClass() {'.PHP_EOL.'#phpcs:disable'.PHP_EOL.'function myFunction() {'.PHP_EOL.'#phpcs:enable'.PHP_EOL.'$this->foo();'.PHP_EOL.'}'.PHP_EOL.'}';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$this->assertEquals(0, $numErrors);
$this->assertCount(0, $errors);

// Process with suppression.
$content = '<?php '.PHP_EOL.'class MyClass() {'.PHP_EOL.'//@phpcs:disable'.PHP_EOL.'function myFunction() {'.PHP_EOL.'//@phpcs:enable'.PHP_EOL.'$this->foo();'.PHP_EOL.'}'.PHP_EOL.'}';
$file = new DummyFile($content, $ruleset, $config);
Expand Down Expand Up @@ -647,6 +777,26 @@ public function testSuppressFile()
$this->assertEquals(0, $numWarnings);
$this->assertCount(0, $warnings);

// Process with suppression (hash comment).
$content = '<?php '.PHP_EOL.'# phpcs:ignoreFile'.PHP_EOL.'//TODO: write some code';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$warnings = $file->getWarnings();
$numWarnings = $file->getWarningCount();
$this->assertEquals(0, $numWarnings);
$this->assertCount(0, $warnings);

// Process with @ suppression (hash comment).
$content = '<?php '.PHP_EOL.'# @phpcs:ignoreFile'.PHP_EOL.'//TODO: write some code';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$warnings = $file->getWarnings();
$numWarnings = $file->getWarningCount();
$this->assertEquals(0, $numWarnings);
$this->assertCount(0, $warnings);

// Process with suppression (deprecated syntax).
$content = '<?php '.PHP_EOL.'// @codingStandardsIgnoreFile'.PHP_EOL.'//TODO: write some code';
$file = new DummyFile($content, $ruleset, $config);
Expand Down Expand Up @@ -780,6 +930,20 @@ public function testDisableSelected()
$this->assertEquals(0, $numWarnings);
$this->assertCount(0, $warnings);

// Suppress a single sniff with reason (hash comment).
$content = '<?php '.PHP_EOL.'# phpcs:disable Generic.Commenting.Todo -- for reasons'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'//TODO: write some code';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

$errors = $file->getErrors();
$numErrors = $file->getErrorCount();
$warnings = $file->getWarnings();
$numWarnings = $file->getWarningCount();
$this->assertEquals(1, $numErrors);
$this->assertCount(1, $errors);
$this->assertEquals(0, $numWarnings);
$this->assertCount(0, $warnings);

// Suppress multiple sniffs.
$content = '<?php '.PHP_EOL.'// phpcs:disable Generic.Commenting.Todo,Generic.PHP.LowerCaseConstant'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'//TODO: write some code';
$file = new DummyFile($content, $ruleset, $config);
Expand Down Expand Up @@ -942,7 +1106,7 @@ public function testEnableSelected()
$this->assertCount(1, $warnings);

// Suppress multiple sniffs and re-enable one.
$content = '<?php '.PHP_EOL.'// phpcs:disable Generic.Commenting.Todo,Generic.PHP.LowerCaseConstant'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'//TODO: write some code'.PHP_EOL.'// phpcs:enable Generic.Commenting.Todo'.PHP_EOL.'//TODO: write some code'.PHP_EOL.'$var = FALSE;';
$content = '<?php '.PHP_EOL.'# phpcs:disable Generic.Commenting.Todo,Generic.PHP.LowerCaseConstant'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'//TODO: write some code'.PHP_EOL.'# phpcs:enable Generic.Commenting.Todo'.PHP_EOL.'//TODO: write some code'.PHP_EOL.'$var = FALSE;';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

Expand Down Expand Up @@ -998,7 +1162,7 @@ public function testEnableSelected()
$this->assertCount(1, $warnings);

// Suppress a category and re-enable a whole standard.
$content = '<?php '.PHP_EOL.'// phpcs:disable Generic.Commenting'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'//TODO: write some code'.PHP_EOL.'// phpcs:enable Generic'.PHP_EOL.'//TODO: write some code';
$content = '<?php '.PHP_EOL.'# phpcs:disable Generic.Commenting'.PHP_EOL.'$var = FALSE;'.PHP_EOL.'//TODO: write some code'.PHP_EOL.'# phpcs:enable Generic'.PHP_EOL.'//TODO: write some code';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

Expand Down Expand Up @@ -1143,7 +1307,7 @@ public function testIgnoreSelected()
$this->assertCount(0, $warnings);

// Suppress a category of sniffs.
$content = '<?php '.PHP_EOL.'// phpcs:ignore Generic.Commenting'.PHP_EOL.'$var = FALSE; //TODO: write some code'.PHP_EOL.'$var = FALSE; //TODO: write some code';
$content = '<?php '.PHP_EOL.'# phpcs:ignore Generic.Commenting'.PHP_EOL.'$var = FALSE; //TODO: write some code'.PHP_EOL.'$var = FALSE; //TODO: write some code';
$file = new DummyFile($content, $ruleset, $config);
$file->process();

Expand Down

0 comments on commit 807c10c

Please sign in to comment.