Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DX] Make ClassAnnotationMatcher differentiate between known and unknown classes #2319

Merged

Conversation

dritter
Copy link
Contributor

@dritter dritter commented May 15, 2022

Before this change, a call to ClassAnnotationMatcher->resolveTagFullyQualifiedName() could not distinguish between a known and unknown class. This is relevant for rules that transform @var annotations to type hints (@var annotation may point to an unknown class). Internally resolveTagFullyQualifiedName() delegates to UseImportNameMatcher->matchNameWithUses(), which simply returns the original string if not found. If this happens, the rule cannot tell, if this is a global class, or an unknown one.

This PR changes ClassAnnotationMatcher->resolveTagFullyQualifiedName() to return null if the class is not known.
There is one caveat: If the unknown class was imported anyway, we do not detect this case (see ClassAnnotationMatcher/Fixture/fixture.php.inc#L18-L19). IMHO such a case is better handled in a separate rule..

Background:
I stumbled upon this when working on Add Rule to convert Doctrine String Classes in targetEntity to Class Constant , where a targetEntity might point to an unknown (or global) class.

@dritter dritter force-pushed the fix_unknown_classes_not_recognized branch 3 times, most recently from 45fb95a to 259d044 Compare May 28, 2022 20:36
@dritter
Copy link
Contributor Author

dritter commented May 28, 2022

@samsonasik Tests are green now. Could you take another look?

/** @var \My\Namespace\UnknownClass */
private $unknownNamespacedClass;
// /** @var OtherUnknownClass */
// private $unknownUsedClass;
Copy link
Member

Choose a reason for hiding this comment

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

why it is commented?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a testcase we currently cannot cover because the OtherUnknownClass is imported (= used). So, this is a case we cannot detect properly. As far as I know, PHP ignores use statements pointing to non existant classes.
I could have removed this case, but I think it is worth mentioning. I'll write a helpful comment..

Copy link
Member

Choose a reason for hiding this comment

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

How about create a test case for direct rector rule usage? eg, for DoctrineTargetEntityStringToClassConstantRector usage.

I think that will be clearer for the purpose and use case.

You can add the test case to tests/Issues directory as the rule is from rector-doctrine

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, the test is now specific for name resolution. I mean, yes, DoctrineTargetEntityStringToClassConstantRector will benefit from this feature, but other users of ClassAnnotationMatcher->resolveTagFullyQualifiedName() could benefit as well. So, I suggest to keep this test at a central place (here).

Copy link
Member

Choose a reason for hiding this comment

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

I am not sure without real failing test case example in a rule, docblock is depends on the statement next to it, so it will be still read by next stmt (as property), possibly by children of VarTagValueNode

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright. I changed the code to check if the class really exists and added some more tests. WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Could you add test for usage in rule, eg: on DoctrineTargetEntityStringToClassConstantRector?

You can add the test case to tests/Issues directory as the rule is from rector-doctrine

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah. Now I understood what you meant in the first place. Apologies for getting you wrong and thanks for your patience. I added a Testcase under tests/Issues/DoNotReplaceUnknownTargetEntities. This test fails, because the DoctrineTargetEntityStringToClassConstantRector needs to use ClassAnnotationMatcher->resolveTagToKnownFullyQualifiedName() now.

Copy link
Member

Choose a reason for hiding this comment

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

could you make test passed then?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made the tests reflect the status quo for now, so they pass. IMHO, it would be better to mark them as skipped, but I couldn't figure out how to do that.
I'll create other PRs in rector-doctrine to fix them properly.

@dritter dritter requested a review from TomasVotruba as a code owner May 30, 2022 17:35
@dritter dritter force-pushed the fix_unknown_classes_not_recognized branch from 19489bc to be78bf6 Compare May 30, 2022 20:13
Copy link
Member

@samsonasik samsonasik left a comment

Choose a reason for hiding this comment

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

Ok for me 👍

private $unknownAliasedUsedClass;
/** @var \UnknownGlobalClass */
private $unknownGlobalClass;
}
Copy link
Member

Choose a reason for hiding this comment

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

Could you split the test case by shared topic?
It is easier to debug, when one of them breaks.

Feel free to nest those in /Fixture/NonExistingClass directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure why phpstan fails. I get no error, when run locally..

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I rebased to the latest main and phpstan passes now.

@dritter dritter force-pushed the fix_unknown_classes_not_recognized branch from 7205dd1 to 80f91f2 Compare June 5, 2022 18:12
@TomasVotruba
Copy link
Member

Thank you @dritter for extensive work on this 👏

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