-
-
Notifications
You must be signed in to change notification settings - Fork 492
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
Add ReturnType Sniff #1084
Add ReturnType Sniff #1084
Conversation
b9e86b1
to
df7a043
Compare
My own notes: To get the line numbers that have errors in tests: # In WPCS directory.
$ phpcs --standard=WordPress -s WordPress/Tests/Functions/ReturnTypeUnitTest.inc --sniffs=WordPress.Functions.ReturnType To run tests: # In WPCS directory.
$ ../phpcs/vendor/bin/phpunit --bootstrap="./Test/phpcs3-bootstrap.php" --filter WordPress ../phpcs/tests/AllTests.php To check code standards for this new code: # In WPCS directory.
$ phpcs WordPress/Sniffs/Functions --standard=bin/phpcs.xml
$ phpcs WordPress/Tests/Functions --standard=bin/phpcs.xml |
There is an open PR for return type declarations upstream: squizlabs/PHP_CodeSniffer#1219 |
The one I based mine on is referenced in an issue labelled with 3.2.0, whereas the one you've highlighted isn't :-) My base also handles nullable return types as well. I'm happy to defer to upstream if/as/when something gets there, but in the meantime, we could be using this today. |
df7a043
to
26b4f84
Compare
Rebased to hopefully fix the test failures. Further test cases to add and apply fixes for: function fooh(): // a comment
array {}
function fooi() // a comment
: array {} should be fixed to become: function fooh(): array {} // a comment
function fooi(): array {} // a comment |
Only slightly adapted from the [work](https://github.com/zendframework/zend-coding-standard/blob/52b435c714879609262aa36b004c4075cd967acc/src/ZendCodingStandard/Sniffs/Formatting/ReturnTypeSniff.php) Michał Bundyra (@webimpress) did for the Zend Coding Standard. Required format for WordPress is the same as most others in the PHP community: - no space before the colon - exactly one space after colon before return type - no space after nullable operator (hasn't been discussed yet, but I see no reason to vary) - simple types should be given as lowercase End result: ```php function foo(): bool { // Simple return type. }; function foo(): ?string { // Nullable simple return type. }; function foo( $a ): \MyClass { // Return type of a class. }; ``` ❗️ Note: Not yet added to `WordPress-Core/ruleset.xml`. See #547.
26b4f84
to
09100f7
Compare
$end = $this->phpcsFile->findNext( array( T_SEMICOLON, T_OPEN_CURLY_BRACKET ), $stackPtr + 1 ); | ||
$last = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $end - 1, null, true ); | ||
$invalid = (int) $this->phpcsFile->findNext( array( T_STRING, T_NS_SEPARATOR, T_RETURN_TYPE ), $first, $last + 1, true ); | ||
if ( $invalid ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a particular reason to check for invalid tokens here? Doesn't that just indicate a parse error/live coding? If so, we shouldn't be giving an error here, just bowing out if we can't continue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, but if we can determine that it appears to be not invalid, then we can also then check and fix any of the simple return types not being in lowercase as well.
If it's an invalid token, then it appears to try and remove it (but may leave the colon - I don't see any unit tests covering this).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible test cases:
function a(): invalidtoken {}
function b(): invalid_token {}
function c() :invalid_token {}
But what should they be fixed to (if anything)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that we should attempt to fix them. We don't attempt to fix or even warn about syntax errors elsewhere. See #793
Also, those examples are valid tokens, aren't they? (T_STRING
is valid, might be a class name.) It would have to be something like this:
function a(): $foo {}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm in agreement with @JDGrimes here. I'd actually say that this whole check for invalid tokens should be removed, as in line 109 -122.
|
||
if ( $first === $last | ||
&& ! in_array( $returnType, $this->simple_return_types, true ) | ||
&& in_array( strtolower( $returnType ), $this->simple_return_types, true ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This array could be flipped and isset()
used instead. Although I guess we're trying to stay close to the upstream version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no upstream as such, so flipped array in 7bcea29.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I've had a closer look at this sniff.
There are a couple of pertinent questions I'd like to pose:
Licensing
We've had discussions about licensing issues with copying code from other standards before. You've indicated that the sniff is largely based on work from @webimpress for the Zend Framework standard which is copyrighted and licensed under BSD-3. I'm not a legal copyright expert, so is this usage permitted or would it be better/preferred to use Composer to add the ZF standard to a project and just include that one sniff in a custom ruleset ?
That way you could also already use the sniff today without threading on other people's work.
Code style
I did a compare between this version and the original and - aside from code style -, there are hardly any differences. Should in that case the original code style be preserved to make compare & updating based on the original easier and the sniff be excluded from our own code style check ? The same as we previously did with the ArrayDeclaration
sniff. In that case, we also did not unit test the sniff in WPCS as that was the responsibility of the original library and was done extensively there.
See #703
Basic principle of the rules/sniff:
- What about function declarations with quite some variables/defaults making for a very long line ? By the looks of the unit tests, the return type declaration is now fixed to be on the same line as the closing parenthesis. Should new lines be allowed ? Possibly as a configurable property, like in several of the Squiz sniffs
ignoreNewlines
?
Ref: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Customisable-Sniff-Properties#squizstringsconcatenationspacing and several others. - I see no mention of "exactly one space between the end of the return type and the brace". While this is already checked via another sniff (
Generic.Functions.OpeningFunctionBraceKernighanRitchie
), it should possibly be mentioned in the WP Core coding standards handbook.
For that matter, the "brace should be on the same line as the class/function declaration" rule is implied in code samples used in the handbook in the "space usage" section, but it isn't actually made explicit either. /cc @pento
Code:
See inline comments. Gone through it with a fine toothcomb. Hope you'll find it useful. /cc @webimpress
namespace WordPress\Sniffs\Functions; | ||
|
||
use PHP_CodeSniffer_Tokens as Tokens; | ||
use WordPress\Sniff; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpicking: the order of the use statements everywhere else is:
- the class which is being extended
- any other class being used.
use WordPress\Sniff; | ||
|
||
/** | ||
* Enforces formatting of return types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe make the "formatting" expected a little more specific ?
'array' => true, | ||
'bool' => true, | ||
'callable' => true, | ||
'double' => true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
double
is not a valid return type. (Same goes for real
and integer
for that matter.)
See: http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.types - especially the note about aliases not being accepted.
Test: https://3v4l.org/bqF4j
Interestingly enough parent
is not in that list, but is an accepted return type.
Also: as of PHP 7.2, object
will be added to the list of valid return types. See: https://wiki.php.net/rfc/object-typehint
function fooa(): array {} // Good. | ||
function foob(): array {} // Bad. | ||
function fooc(): array {} // Bad. | ||
function food(): array {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the sniff has removed the comment instead of moving it around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I took the comment out of the .fixed file to make the tests pass for now :-)
$this->phpcsFile->fixer->beginChangeset(); | ||
$token = $colon - 1; | ||
do { | ||
$this->phpcsFile->fixer->replaceToken( $token, '' ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The token type for tokens to be removed should be checked here to be T_WHITESPACE
, otherwise comments could be accidentally removed.
This check should be done before the error is thrown so as not to throw an unfixable fixableError
. See: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/blob/master/WordPress/Sniffs/Arrays/ArrayDeclarationSpacingSniff.php#L101-L105 for some example code dealing with a similar situation.
Unit test:
function abc() /* Return declaration. */ : array {
return array();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd come across the disappearing comments but removed them from the tests until I'd come up with a solution.
if ( T_WHITESPACE === $this->tokens[ $nullable + 1 ]['code'] ) { | ||
$error = 'Space is not not allowed after nullable operator.'; | ||
$fix = $this->phpcsFile->addFixableError( $error, $nullable + 1, 'SpaceAfterNullable' ); | ||
if ( $fix ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if ( true === $fix )
$first = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $nullable ?: $colon ) + 1, null, true ); | ||
$end = $this->phpcsFile->findNext( array( T_SEMICOLON, T_OPEN_CURLY_BRACKET ), $stackPtr + 1 ); | ||
$last = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $end - 1, null, true ); | ||
$invalid = (int) $this->phpcsFile->findNext( array( T_STRING, T_NS_SEPARATOR, T_RETURN_TYPE ), $first, $last + 1, true ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't cast to (int) and check in the if against false.
|
||
$first = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $nullable ?: $colon ) + 1, null, true ); | ||
$end = $this->phpcsFile->findNext( array( T_SEMICOLON, T_OPEN_CURLY_BRACKET ), $stackPtr + 1 ); | ||
$last = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $end - 1, null, true ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check $end !== false
before using it. This would throw notices when code is checked in live coding situations in an IDE.
$end = $this->phpcsFile->findNext( array( T_SEMICOLON, T_OPEN_CURLY_BRACKET ), $stackPtr + 1 ); | ||
$last = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, $end - 1, null, true ); | ||
$invalid = (int) $this->phpcsFile->findNext( array( T_STRING, T_NS_SEPARATOR, T_RETURN_TYPE ), $first, $last + 1, true ); | ||
if ( $invalid ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm in agreement with @JDGrimes here. I'd actually say that this whole check for invalid tokens should be removed, as in line 109 -122.
} | ||
|
||
$return_type = trim( $this->phpcsFile->getTokensAsString( $first, $last - $first + 1 ) ); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole check can be simplified as we already received the T_RETURN_TYPE
as the $stackPtr
and simple return types are only one token:
$contentLC = strtolower( $this->tokens[ $stackPtr ]['content'] );
if ( isset( $this->simple_return_types[ $contentLC ] && $contentLC !== $this->tokens[ $stackPtr ]['content'] ) {
// Throw fixable error.
// ... reusing the $contentLC variable...
}
$error = 'There must be exactly one space after colon and before return type declaration.'; | ||
$fix = $this->phpcsFile->addFixableError( $error, $colon + 1, 'TooManySpacesAfterColon' ); | ||
$error = 'There must be exactly one space between the colon and the return type. Found: %s'; | ||
$data = array( 'more than one' ); /* @var @todo Count actual whitespaces */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WIP :-)
- Remove double type, add object type. - Add more explicit feedback for too many spaces after colon.
If comments are found between closing parenthesis and colon, or colon and return type, then mark as an (unfixable) error.
My understanding is, that as long as we keep copyright / credits, BSD-3 can be used in MIT projects. In this case (and it may not make a difference), their work is not yet on their master branch, or within a release, so it may not even count as being published. With the PR feedback given, it's also had some significant changes to it, with sections deleted, and more test cases addressed )i.e. more than just syntax changes).
Unless @webimpress strongly dictates otherwise, I'd be against this. As mentioned, more test cases have been added and catered for, and even the error messages have more of WPCS flavour about them. I'm more than happy to add more explicit credits as to the original source, but there is definitely some non-trivial changes made, and I'd hope that in the spirit of open source, these changes could be added back into the Zend Coding Standard, or otherwise make it upstream so everyone could benefit.
As above - there are more than just code style changes now (since you wrote the comment). Theirs is an unreleased Sniff, and ours handles things in a better way.
There's at least one test case that allows for
As you said, it's not in the handbook, and covered by another sniff anyway, so I'm not sure what you need this PR to clarify here?
Very useful, thanks - this was my first attempt at amending the internals of a Sniff (albeit starting from a very good base from @webimpress), so it was a case of jumping in at the deep-end and figuring out what the workflow should be. Definitely useful to have detailed feedback! |
Copyright starts at the moment something is written. Not when it is published. If I understand it correctly, the sniff is not yet merged into the ZFCS ? In that case, it might well be that their license does not apply (yet) and that it falls under the ""normal" copyright, i.e. all rights reserved unless otherwise mentioned and the sniff itself did not contain any such mentions, so that would - for the moment - make it off-limits.
I'd like to hear from @webimpress here, but yes, as - since the original PR was made - the changes have become more extensive, it can be seen as a derivative work.
Nothing for this PR here, but I did wonder if the handbook could do with clarification on that point and this sniff inspired that thought which is why I mentioned it here. |
@jrfnl @GaryJones The main thing I have changed there is possibility to configure the sniff - you can set how many spaces must be before/after colon and after nullable operator. Probably it still could be improved, but all tests provided here also pass with my sniff. |
@webimpress Thanks for following up :-) Would your plan be to get this sniff into PHPCS repo itself? |
Side note for this PR: WordPress.WhiteSpace.ControlStructureSpacing.NoSpaceAfterCloseParenthesis may need adjusting, as it complains of a "space being needed between opening control structure and closing parenthesis" when the space before the colon is missing. |
Related to #1071 - The |
Just thinking: should there be some unit tests covering closures in combination with return type declarations ? |
Only slightly adapted from the work Michał Bundyra (@webimpress) did for the Zend Coding Standard.
Required format for WordPress is the same as most others in the PHP community:
End result:
❗️ Note: Not yet added to
WordPress-Core/ruleset.xml
.See #547.