-
-
Notifications
You must be signed in to change notification settings - Fork 78
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
Create namespace for whitelisted classes #138
Create namespace for whitelisted classes #138
Conversation
While testing this code in a project, we ran across the problem that defines are placed outside the namespace. This is causing fatal errors. Placing the define inside the namespace resolves the error. |
Having to resolve this, I think we need to wrap -anything- in a namespace group as this is the method of prefixing used in this tool. This would result in: // Global namespace define
namespace {
define('SOME_DEFINE', 'value');
}
// Prefixed class
namespace Humbug {
class b {}
}
// Global namespace class
namespace {
class c {}
} |
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.
Big 👍
I added comments; a few are regarding some missing tests and others are mostly nitpicks.
Huge thanks for this one 😄
], | ||
|
||
[ | ||
'spec' => <<<'SPEC' |
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.
good use of the format:
[
'spec' => '',
'payload' => '',
]
but I think it's a case simple enough where we can use the simplified format:
'spec content' => 'payload content'
As you can find here
], | ||
|
||
[ | ||
'spec' => <<<'SPEC' |
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 would add two more cases:
- One with multiple different namespaces like you can find here (it doesn't need to be the same it's just for the idea)
- One with two classes belonging to the global namespace: one which is a whitelisted one, so
AppKernel
which as a result will be prefixed, and another which is not likeClosure
which should not be prefixed
use PhpParser\NodeVisitorAbstract; | ||
|
||
/** | ||
* Creates a namespace for whitelisted classes. |
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.
[...] belonging to the global namespace
. It is an important part as the whitelisting feature is different depending of if the class belongs or not to the global namespace cf. global-namespace-whitelisting and whitelist
final class NamespaceStmtCreator extends NodeVisitorAbstract | ||
{ | ||
private $prefix; | ||
|
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.
let's remove the blank lines between those properties
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.
Added those because the code style checker was marking them as errors. Will remove the lines.
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.
Ah right. I need to figure out which parameter in the CS fixer it is to disable that, it really annoys me
* @param string $prefix | ||
* @param NamespaceStmtCollection $namespaceStatements | ||
* @param callable $globalWhitelister | ||
*/ |
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 doc here is not useful as completely redundant with the signature, I know I may not have been consistent everywhere but I would remove it still :)
{ | ||
return ($node instanceof Class_) | ||
? $this->addNamespaceStmt($node) | ||
: $node; |
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.
return ($node instanceof Class_)
? $this->addNamespaceStmt($node)
: $node
;
} | ||
|
||
/** | ||
* @param Class_ $node |
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.
same here, useless phpdoc
@@ -0,0 +1,85 @@ | |||
<?php |
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 also a few other cases that you can find in specs/class
that needs to be accounted for. For example it should also apply to:
- abstract classes
- interfaces
traits: they cannot be whitelisted; may require a test that it doesn't work there stillanonymous: they cannot be whitelisted; may require a test that it doesn't work there still
Additional functionality: I've used the If no namespace exists, the global namespace will be wrapped around it. This resolves the problems described before. |
(Sorry I'm not so good at squashing commits and pushing after everything is done ;)) |
Don't worry about it I'll do a "Squash & Merge", only the CI may resent you a bit if you push them one by one but I think Travis & Scrutinizer got better at it |
} | ||
|
||
/** | ||
* @param Node[] $nodes List of nodes to find whitelisted nodes from. |
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 think the signature is descriptive enough, no need for the List [...]
comment. I would keep the phpdoc as it does add the type array
, but I tend to avoid comments when the code is self-explanatory as the problem with comments/docs is that they easily get out of date with the code they are documenting. So keeping it to minimal is better IMO
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.
Do you prefer to have an empty docblock or just none at all?
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.
In the projects we do at Yoast, we have very strict documentation rules.
We don't find ourselves having a lot of outdated documentation entries in general.
The fact that we are in a WordPress environment, where backwards compatibility is a major thing might have something to do with it, but I don't think that is the only factor in it.
Writing the documentation mostly helps me re-evaluate what I wrote functionally and if the naming of the variables and logic are actually the best solutions.
My two cents on the subject.
I'm happy to oblige to the standards and approach you set for your project.
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 think @param Node[] $nodes
is enough here
If it's too "redundant" with the code, I really prefer to avoid the comments. For public interfaces, non trivial things or for example the doc to explain the purpose of the class I tend to be in favour comments :)
} | ||
|
||
/** | ||
* @return bool |
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 need for the phpdoc here
return null !== $node->name && ($this->globalWhitelister)($node->name); | ||
}); | ||
|
||
return !empty($nodes); |
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.
as the returned type is an array I think [] !== $nodes
or with a count is more expressive here
public function beforeTraverse(array $nodes) | ||
{ | ||
$classes = array_filter($nodes, [$this, 'isWhitelistableNode']); | ||
if (empty($classes)) { |
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.
as the returned type is an array I think [] !== $nodes or with a count is more expressive here
While writing specs, I came across another problem when a global namespace is already defined around a class. 'Traits in different namespace.' => <<<'PHP'
<?php
namespace Foo {
trait SomeTrait{}
}
namespace {
class AppKernel{}
}
----
<?php
namespace Humbug\Foo{
trait SomeTrait
{
}
}
namespace Humbug {
class AppKernel
{
}
}
PHP As the namespace prefixer should pick this up and add the namespace for the whitelisted class in the global namespace. |
You mean right now we would have: <?php
namespace Foo {
trait SomeTrait{}
}
namespace {
class AppKernel{}
}
----
<?php
namespace Humbug\Foo{
trait SomeTrait
{
}
}
namespace Humbug {
class AppKernel
{
}
} instead of having canonical namespaces, i.e. only one |
Hope this output helps to describe it a bit better. I would expect the
|
We're clear on the expected input/expected output. I wonder if the work you're doing shouldn't be in the |
That's a good point, I initially started a new visitor because prefixing and creating seemed like two concepts. |
Updated the code and added the desired StyleCI disables to remove the checks on property separation and const separation. |
Also put the StyleCI changes in a separate PR: #139 |
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.
Awesome! Could you rebase it (for the CS failure)?
|
||
/** | ||
* @param string $prefix | ||
* @param NamespaceStmtCollection $namespaceStatements | ||
* @param callable $globalWhitelister |
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.
let's just remove the phpdoc here
'Defines should be wrapped in namespace alongside whitelisted class.' => <<<'PHP' | ||
<?php | ||
|
||
define( "MY_DEFINE", "value" ); |
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.
small extra space here :)
Make sure all code is wrapped in global namespace if a whitelisted class is in the same file.
As the global namespace is implicitly always present, we are making it explicit when needed by adding the statement to the stack. Added extra test for complex situation with non-whitespaced class in combination with whitespaced class.
Simplify by combining two if-statements
381286a
to
e4e0849
Compare
Many thanks @moorscode, that was a critical bug :) |
Technical choices:
This is the simplest solution to the problem we could think of.
Global namespace
Decided to not put classes in a global namespace that are not whitelisted in a file, which contains a whitelisted class.We have tried this, but it resulted in having to put every class in anamespace { }
wrap, which fails a lot of tests.As PSR-0 declares:
This means each class is in a file by itself, and is in a namespace of at least one level: a top-level vendor name.
Updated the functionality to make sure any code that is not wrapped in a namespace will receive the global namespace. This prevents invalid PHP from being generated.
Paired on it together with @atimmer
Fixes #135