Skip to content

Add the possibility to ignore rector rules at the file level#5952

Closed
carlos-granados wants to merge 5 commits intorectorphp:mainfrom
carlos-granados:rector-ignore
Closed

Add the possibility to ignore rector rules at the file level#5952
carlos-granados wants to merge 5 commits intorectorphp:mainfrom
carlos-granados:rector-ignore

Conversation

@carlos-granados
Copy link
Copy Markdown
Contributor

Currently it is possible to skip a file or list of files when applying rector rules and it is also possible to skip a particular rule. You can also skip a particular rule for a file or a list of files. But it is not possible to skip a single case of applying a rule in a file without also skipping all the other cases of that rule in that file.

This PR follows a similar solution implemented by PHPStan with their @phpstan-ignore and @phpstan-ignore-next-line tags and implements two comment tags:

  • @rector-ignore-next-line will let you skip any rector rule that might be applied to the node defined in the next line
  • @rector-ignore RULE_NAME will let you skip any particular rector rule that might be applied to the node defined in the next line. RULE_NAME needs to be the name of the class for that particular rule (for example NumericReturnTypeFromStrictScalarReturnsRector) without a namespace. If you want to skip more than one rule you can list them separated with commas

Includes a test for the new functionality, see the comments in the PR for more info

Comment thread src/Skipper/Contract/SkipVoterInterface.php Outdated
Comment thread src/Skipper/Skipper/SkipSkipper.php Outdated
use Rector\Skipper\Contract\SkipVoterInterface;
use Rector\Skipper\Skipper\SkipSkipper;

final readonly class CommentSkipVoter implements SkipVoterInterface
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use a dedicated service instead, and use on Skipper::shouldSkipCurrentNode() to avoid bc break due to new ?Node $node parameter addition.

* @var array<class-string<SkipVoterInterface>>
*/
private const SKIP_VOTER_CLASSES = [ClassSkipVoter::class];
private const SKIP_VOTER_CLASSES = [ClassSkipVoter::class, CommentSkipVoter::class];
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We add a new SkipVoter that will look at the comments for any node

public function match(string | object $element): bool;

public function shouldSkip(string | object $element, string $filePath): bool;
public function shouldSkip(string | object $element, string $filePath, ?Node $node): bool;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We modify the SkipVoterInterface so that the node is also passed to the shouldSkip function

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That bc break, additional functionality can be added Skipper::shouldSkipCurrentNode() to avoid bc break.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean adding this to the interface? 🤔

}

public function shouldSkip(string | object $element, string $filePath): bool
public function shouldSkip(string | object $element, string $filePath, ?Node $node): bool
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modify the existing voter to follow the new interface

use Rector\Skipper\Contract\SkipVoterInterface;
use Rector\Skipper\Skipper\SkipSkipper;

final readonly class CommentSkipVoter implements SkipVoterInterface
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new voter. It calls a new function in the SkipSkipper helper which is the one that actually checks the comments


final readonly class SkipSkipper
{
private const RECTOR_IGNORE_NEXT_LINE_TAG = '@rector-ignore-next-line';
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the two tags that we expect to see in the comments

return $this->myVariable;
}

// @rector-ignore AddParamTypeFromPropertyTypeRector, AddVoidReturnTypeWhereNoReturnRector
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be possible to list more than one rule

$this->myVariable = $myVariable;
}

// @rector-ignore AddParamTypeFromPropertyTypeRector
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should also be possible to have more than one comment

}

/**
* @rector-ignore AddParamTypeFromPropertyTypeRector
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or use a comment with several lines

$this->myVariable = $myVariable;
}

// @rector-ignore-next-line
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use the ignore next line tag, it should ignore all rules

$this->myVariable = $myVariable;
}

// @rector-ignore AddParamTypeFromPropertyTypeRector
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we ignore one rule, all other rules should still apply

{
private int $myVariable;

// @rector-ignore NumericReturnTypeFromStrictScalarReturnsRector
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be fqcn so exact class name will be validated to avoid 2 class (core vs override custom rule conflict)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having to use the FQCN would not be very convenient as you would end up with comments like

// @rector-ignore Rector\TypeDeclaration\Rector\ClassMethod\NumericReturnTypeFromStrictScalarReturnsRector

I tried to implement it so that you should import the class by doing something like:

use Rector\TypeDeclaration\Rector\ClassMethod\NumericReturnTypeFromStrictScalarReturnsRector;

...
// @rector-ignore NumericReturnTypeFromStrictScalarReturnsRector

My problem was then converting this to a FQCN when reading the comment so that I could compare it to the class name of the current rule. I could not find a way to do this, any pointers would be helpful

@carlos-granados
Copy link
Copy Markdown
Contributor Author

@samsonasik saw your comments about the BC break but not sure how to proceed here. The shouldSkip function is called from the shouldSkipElementAndFilePath method. This method expects a list of skipVoters, all of them following the SkipVoterInterface interface. I am not sure how I could use another service to provide the node to the CommentSkipVoter. Can you elaborate?

@samsonasik
Copy link
Copy Markdown
Member

The new service should not implements SkipVoterInterface, but a dedicated service, like PathSkipper class:

final readonly class PathSkipper

then, use it on after this line:

return $this->rectifiedAnalyzer->hasRectified($rectorClass, $node);

should be something like:

-return $this->rectifiedAnalyzer->hasRectified($rectorClass, $node);
+if ($this->rectifiedAnalyzer->hasRectified($rectorClass, $node)) {
+    return true;
+}
+
+return $this->commentSkipper->shouldSkip($rectorClass, $node);

@carlos-granados
Copy link
Copy Markdown
Contributor Author

@samsonasik understood now, I will work to modify this PR to follow your suggestion

@samsonasik
Copy link
Copy Markdown
Member

Btw, the similar functionality was exists with @noRector doc, it was removed by @TomasVotruba at

You can check the reasoning there, and if there is new proposal for it, I will let @TomasVotruba decide

@carlos-granados
Copy link
Copy Markdown
Contributor Author

Btw, the similar functionality was exists with @noRector doc, it was removed by @TomasVotruba at

You can check the reasoning there, and if there is new proposal for it, I will let @TomasVotruba decide

Didn't know about this. Will check it!

@carlos-granados
Copy link
Copy Markdown
Contributor Author

carlos-granados commented Jun 12, 2024

@samsonasik added two changes:

  • Converted the CommentSkipVoter into a CommentSkipper service
  • Use FQN when comparing excluded rules. You can either use:
// @rector-ignore Rector\TypeDeclaration\Rector\ClassMethod\NumericReturnTypeFromStrictScalarReturnsRector

or

use Rector\TypeDeclaration\Rector\ClassMethod\NumericReturnTypeFromStrictScalarReturnsRector;
...
// @rector-ignore NumericReturnTypeFromStrictScalarReturnsRector

@samsonasik
Copy link
Copy Markdown
Member

If docblock tag is generic, classnames can be collected from PhpDocInfo, something like this

$genericTagClassNames = $phpDocInfo->getGenericTagClassNames();

@carlos-granados
Copy link
Copy Markdown
Contributor Author

If docblock tag is generic, classnames can be collected from PhpDocInfo, something like this

$genericTagClassNames = $phpDocInfo->getGenericTagClassNames();

The problem is that we want to be able to use comments which are not docblocks and these do not get parsed in the same way

@samsonasik
Copy link
Copy Markdown
Member

The generic is something like @see SomeClass which pointed to full classname already, if @norector detected as generic, next value will getted as classnames, you can then filter by generic name, so it pointed to correct target generic type node.

@TomasVotruba
Copy link
Copy Markdown
Member

TomasVotruba commented Jun 17, 2024

Hi @carlos-granados , thanks for patience and appologies for my late reply.
I took a time to think about this deeply and was distracted by other work and open-source.

Using dev tool configuration in code seems like a wrong design. That's why I removed it in the past: #5952 (comment)

Code Sniffer/PHPStan have such a feature, but it doesn't bring any new value to the tool. In practise, it also makes false positive ignored hard to spot, as part of the code. We often have to go through it one by one and check, and check if still valid. PHPStan reports such cases, but Rector does not. It would also cost us a performance hit.

Having one place to ignore errors is the best design choice, as there place of least surprise. We avoid cases like "oh, we forgot to check every file for the docblock and that's why this upgrade rule was missed".

Another problem is, that when we had such a feature, it was used as "ignore every rule", only because it was easy and possible.

That's why I'm rejecting this feature.

Thanks for understanding 🙏

@carlos-granados
Copy link
Copy Markdown
Contributor Author

carlos-granados commented Jun 18, 2024 via email

@carlos-granados carlos-granados deleted the rector-ignore branch August 22, 2024 11:41
@carlos-granados
Copy link
Copy Markdown
Contributor Author

If anyone else is interested in this option, it is available in this fork: https://github.com/carlos-granados/rector

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants