Skip to content

Conversation

carlos-granados
Copy link
Contributor

In several cases we have found previously that a rule was applied (it returned a Node object from the refactor() method) even when it had not actually changed the code. A couple of examples:

This generates two problems:

  • We have reduced performance as we have to deal with a changed node even though it has not actually changed
  • And, more important, we may be reporting that a rule has been applied without any real code change and this can be quite confusing for the users who might be left wondering why a rule seems to have been applied without need

In this PR we implement a fail-safe check that applies to all our tests where we test that a file has not changed. If in this case we detect that a rule has been applied, then we make this test fail so that this kind of problems can be detected early while the rule is in development.

This PR also fixes several cases in our current tests which had this problem.

If this gets accepted, I will work to fix similar cases in the rector-symfony, rector-doctrine, rector-phpunit and rector-downgrade-php repos

$file->changeHasChanged(true);
}

$rectorWithLineChanges = $file->getRectorWithLineChanges();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we have applied any rector rule, we create a FileDiff object even if the code has not actually changed. This FileDiff object will have an empty diff.

$this->assertStringMatchesFormat($expectedFileContents, $changedContents, $failureMessage);
}

if ($inputFileContents === $expectedFileContents && $numAppliedRectorClasses > 0) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we expect that the code should not change but there has been any Rector rule applied then we throw an error to let the user know that this is incorrect

$rectorClasses = [];

foreach ($this->processResult->getFileDiffs() as $fileDiff) {
foreach ($this->processResult->getFileDiffs(false) as $fileDiff) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here we want to get all file diffs, including those where there was no actual change

* @return FileDiff[]
*/
public function getFileDiffs(): array
public function getFileDiffs(bool $onlyWithChanges = true): array
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In most cases we will just be interested in the file diffs where there was an actual change, so we filter out those where there was no change recorded

#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->expectException(RectorRuleShouldNotBeAppliedException::class);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this particular test, the refactor() function returns on purpose a Node element even though it does not change it. This is done to confirm that the code that handles this node does not throw an error. So in this particular case we need to confirm that we know that this rule would be applied even though it does not change the code

Copy link
Member

Choose a reason for hiding this comment

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

use ob_start() and ob_get_clean() to get echo warning should be the way on this on purpose test

#[DataProvider('provideData')]
public function test(string $filePath): void
{
$this->expectException(RectorRuleShouldNotBeAppliedException::class);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To test the new functionality we run a rule that returns a Node even though it does not change it and then we confirm that we catch this case and throw an exception informing us of the problem

@TomasVotruba
Copy link
Member

Thanks for the PR 👍 It seems quite heavy to review.
Could you extract some easy-pick PRs, so we can handle it sooner?

@carlos-granados
Copy link
Contributor Author

@TomasVotruba I can separate the fixes to the existing tests from the actual new test that revealed them, let me do that

@carlos-granados
Copy link
Contributor Author

I have created some PRs, need to leave now, will continue tomorrow

@carlos-granados
Copy link
Contributor Author

@TomasVotruba now that all the changes to the existing rules have been merged I have rebased this PR to only leave the changes for the new test

if ($inputFileContents === $expectedFileContents && $numAppliedRectorClasses > 0) {
$failureMessage = $fixtureNameMessage . PHP_EOL . PHP_EOL
. 'File not changed but some Rector rules applied:' . PHP_EOL . $appliedRulesList;
throw new RectorRuleShouldNotBeAppliedException($failureMessage);
Copy link
Member

Choose a reason for hiding this comment

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

I prefer symfony warning instead of throw to make less noise or make return on purpose on test

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @samsonasik, not sure I understand what you mean, can you provide some example code?

Copy link
Member

Choose a reason for hiding this comment

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

Just simple echo seems enough:

echo  'File not changed but some Rector rules applied:' . PHP_EOL . $appliedRulesList . PHP_EOL,

Actually, there is SymfonyStyle service, but seems skipped under unit test

$this->symfonyStyle->warning(sprintf(
'These rules are registered in both sets and "withRules()". Remove them from "withRules()" to avoid duplications: %s* %s',
PHP_EOL . PHP_EOL,
implode(' * ', $setAndRulesDuplicatedRegistrations) . PHP_EOL
));

so only e2e test can show the warning.

@carlos-granados
Copy link
Contributor Author

@samsonasik I have updated the code to print a warning instead of throwing an error, you can see how it looks here, for example: https://github.com/rectorphp/rector-src/actions/runs/13995973300/job/39191131180?pr=6794

@samsonasik
Copy link
Member

Looks good 👍


Runtime:       PHP 8.2.28
Configuration: /home/runner/work/rector-src/rector-src/phpunit.xml

...............................................................  63 / 184 ( 34%)
..............
WARNING: On fixture file "skip_already_added.php.inc" for test "Rector\Doctrine\Tests\CodeQuality\Rector\Class_\YamlToAttributeDoctrineMappingRector\YamlToAttributeDoctrineMappingRectorTest"
File not changed but some Rector rules applied:
 * Rector\Doctrine\CodeQuality\Rector\Class_\YamlToAttributeDoctrineMappingRector

Test keep running ok so we can continue improve while wait for improving on the rector rules 👍

@samsonasik
Copy link
Member

This may need verify on changed by DocblockUpdater service, which change the comment attribute

@carlos-granados
Copy link
Contributor Author

This may need verify on changed by DocblockUpdater service, which change the comment attribute

I don't think this would be a problem. Even if the DocblockUpdater updates the comments, this would either change the final code of the file or not. If it does not change it and we have a test that checks that it does not change and a rule is applied, then we flag this as a warning. Any other case is ignored by this code

@samsonasik
Copy link
Member

I checked on rector-symfony rules, it seems somehow change docblock not really detected, which may need to be verified more...

@carlos-granados
Copy link
Contributor Author

I checked on rector-symfony rules, it seems somehow change docblock not really detected, which may need to be verified more...

Mmm, let me test this over the weekend, I'll let you know

@carlos-granados
Copy link
Contributor Author

@samsonasik I tested the rector-symfony repo with this code and I could not see any case of docblock change not detected. I fixed all the cases detected by this PR and created another PR in that repo rectorphp/rector-symfony#711 so from my side I am not seeing anything that does not work as expected. If you still see some issues, can you please provide more information? Thanks!

@carlos-granados
Copy link
Contributor Author

While working on the Doctrine repo changes I realised that we need to change the PhpDocTypeChanger to return a boolean when changing the type for a @var doc tag. Is this what you meant, @samsonasik ? I have added the change here, let me know if you would prefer to see it as a separate PR

@samsonasik
Copy link
Member

Yes, separate PR would be better 👍

@carlos-granados
Copy link
Contributor Author

Yes, separate PR would be better 👍

OK, moved to a new PR #6813

@carlos-granados
Copy link
Contributor Author

@TomasVotruba @samsonasik all the issues in the rector/* repos have now been fixed so this should be ready to go if you believe it is OK

@samsonasik
Copy link
Member

I am restarting packages tests to see if no longer warning in tests

@samsonasik
Copy link
Member

package tests seems still have warnings, ref https://github.com/rectorphp/rector-src/actions/runs/14168592413/job/40439865009?pr=6794

@samsonasik
Copy link
Member

@samsonasik
Copy link
Member

It seeems ok now, let's give it a try, thank you @carlos-granados

@samsonasik samsonasik merged commit 24ffb63 into rectorphp:main Apr 12, 2025
44 checks passed
@TomasVotruba
Copy link
Member

Thank you @carlos-granados 🥳

@carlos-granados carlos-granados deleted the detect-no-changes branch April 17, 2025 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants