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

Forbid using certain functions and classes #57738

Merged
merged 37 commits into from
Jan 31, 2024

Conversation

anton-vlasenko
Copy link
Contributor

@anton-vlasenko anton-vlasenko commented Jan 10, 2024

What?

This PR introduces the ForbiddenFunctionsAndClasses sniff.
It's designed to enforce usage restrictions on specific classes and functions within the Gutenberg project.
While declarations of these classes/functions are permitted, their actual usage is restricted based on defined regular expressions.

Fixes #54745.

Why?

The necessity of this PR arises from the need to avoid using any Gutenberg functions and classes in the block-library packages.
This is because these packages are backported to Core and must not include Gutenberg related functions and classes.

How?

The implementation involves defining regular expressions for restricted classes and functions.
The ForbiddenFunctionsAndClassesSniff class hooks into the PHP CodeSniffer process, scanning the code for instances where these restricted classes or functions are used.

Testing Instructions

  1. Ensure that GitHub CI jobs pass.
  2. Navigate to test/php/gutenberg-coding-standards/.
  3. Run composer update.
  4. Run composer run check-all.

Testing Instructions for Keyboard

Screenshots or screencast

@anton-vlasenko anton-vlasenko added the [Type] Code Quality Issues or PRs that relate to code quality label Jan 10, 2024
Copy link

github-actions bot commented Jan 11, 2024

Flaky tests detected in 0c8ef69.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/7717914499
📝 Reported issues:

@anton-vlasenko anton-vlasenko marked this pull request as ready for review January 17, 2024 16:42
@anton-vlasenko
Copy link
Contributor Author

anton-vlasenko commented Jan 17, 2024

The PR fails CI checks because certain functions are not permitted within the block-library packages. Clarification is needed on whether to ignore or refactor these functions.

The list of problematic functions/classes:

FILE: /src/wp-content/plugins/gutenberg/packages/block-library/src/form/index.php
----------------------------------------------------------------------------------------------------------------------
FOUND 2 ERRORS AFFECTING 2 LINES
----------------------------------------------------------------------------------------------------------------------
49 | ERROR | It's not allowed to call the "gutenberg_is_experiment_enabled()" function as its name matches the
|       | forbidden pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
204 | ERROR | It's not allowed to call the "gutenberg_is_experiment_enabled()" function as its name matches the
|       | forbidden pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
----------------------------------------------------------------------------------------------------------------------


FILE: /src/wp-content/plugins/gutenberg/packages/block-library/src/file/index.php
----------------------------------------------------------------------------------------------------------------------
FOUND 3 ERRORS AFFECTING 3 LINES
----------------------------------------------------------------------------------------------------------------------
25 | ERROR | It's not allowed to call the "gutenberg_enqueue_module()" function as its name matches the forbidden
|       | pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
111 | ERROR | It's not allowed to call the "gutenberg_register_module()" function as its name matches the forbidden
|       | pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
113 | ERROR | It's not allowed to call the "gutenberg_url()" function as its name matches the forbidden pattern:
|       | "/^(G|g)utenberg.*$/". (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
----------------------------------------------------------------------------------------------------------------------


FILE: ...src/wp-content/plugins/gutenberg/packages/block-library/src/navigation/index.php
----------------------------------------------------------------------------------------------------------------------
FOUND 3 ERRORS AFFECTING 3 LINES
----------------------------------------------------------------------------------------------------------------------
341 | ERROR | It's not allowed to use the "Gutenberg_Navigation_Fallback" class as its name matches the forbidden
|       | pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.UsedClassInvalid)
431 | ERROR | It's not allowed to call the "gutenberg_register_module()" function as its name matches the forbidden
|       | pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
433 | ERROR | It's not allowed to call the "gutenberg_url()" function as its name matches the forbidden pattern:
|       | "/^(G|g)utenberg.*$/". (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
----------------------------------------------------------------------------------------------------------------------


FILE: /src/wp-content/plugins/gutenberg/packages/block-library/src/image/index.php
----------------------------------------------------------------------------------------------------------------------
FOUND 3 ERRORS AFFECTING 3 LINES
----------------------------------------------------------------------------------------------------------------------
55 | ERROR | It's not allowed to call the "gutenberg_enqueue_module()" function as its name matches the forbidden
|       | pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
362 | ERROR | It's not allowed to call the "gutenberg_register_module()" function as its name matches the forbidden
|       | pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
364 | ERROR | It's not allowed to call the "gutenberg_url()" function as its name matches the forbidden pattern:
|       | "/^(G|g)utenberg.*$/". (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
----------------------------------------------------------------------------------------------------------------------


FILE: /src/wp-content/plugins/gutenberg/packages/block-library/src/search/index.php
----------------------------------------------------------------------------------------------------------------------
FOUND 3 ERRORS AFFECTING 3 LINES
----------------------------------------------------------------------------------------------------------------------
95 | ERROR | It's not allowed to call the "gutenberg_enqueue_module()" function as its name matches the forbidden
|       | pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
217 | ERROR | It's not allowed to call the "gutenberg_register_module()" function as its name matches the forbidden
|       | pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
219 | ERROR | It's not allowed to call the "gutenberg_url()" function as its name matches the forbidden pattern:
|       | "/^(G|g)utenberg.*$/". (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
----------------------------------------------------------------------------------------------------------------------


FILE: ...src/wp-content/plugins/gutenberg/packages/block-library/src/form-input/index.php
----------------------------------------------------------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------------------------------------------------------
35 | ERROR | It's not allowed to call the "gutenberg_is_experiment_enabled()" function as its name matches the
|       | forbidden pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
----------------------------------------------------------------------------------------------------------------------


FILE: /src/wp-content/plugins/gutenberg/packages/block-library/src/pattern/index.php
----------------------------------------------------------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------------------------------------------------------
61 | ERROR | It's not allowed to call the "gutenberg_serialize_blocks()" function as its name matches the forbidden
|       | pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
----------------------------------------------------------------------------------------------------------------------


FILE: /src/wp-content/plugins/gutenberg/packages/block-library/src/query/index.php
----------------------------------------------------------------------------------------------------------------------
FOUND 2 ERRORS AFFECTING 2 LINES
----------------------------------------------------------------------------------------------------------------------
73 | ERROR | It's not allowed to call the "gutenberg_enqueue_module()" function as its name matches the forbidden
|       | pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
141 | ERROR | It's not allowed to call the "gutenberg_register_module()" function as its name matches the forbidden
|       | pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
----------------------------------------------------------------------------------------------------------------------


FILE: ...wordpress.test/src/wp-content/plugins/gutenberg/packages/block-library/src/form-submission-notification/index.php
----------------------------------------------------------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------------------------------------------------------
38 | ERROR | It's not allowed to call the "gutenberg_is_experiment_enabled()" function as its name matches the
|       | forbidden pattern: "/^(G|g)utenberg.*$/".
|       | (Gutenberg.CodeAnalysis.RestrictedFunctionsAndClasses.CalledFunctionInvalid)
----------------------------------------------------------------------------------------------------------------------

@anton-vlasenko
Copy link
Contributor Author

anton-vlasenko commented Jan 17, 2024

I'd appreciate your feedback on that, @gziolo.
Should the sniff ignore these functions/classes? Or should they be removed from the block-library packages?
Also, any feedback on the sniff itself would be helpful.
Does it work as expected?
Is there anything that needs to be fixed, adjusted, or implemented? Thanks!

@gziolo
Copy link
Member

gziolo commented Jan 22, 2024

I'd appreciate your feedback on that, @gziolo.
Should the sniff ignore #57738 (comment) functions/classes? Or should they be removed from the block-library packages?

The sniff works great detecting all listed violations 👍

Some of these code paths are behind the check ensuring they only execute in the Gutenberg context so folks can explicitly disable violations for these lines.

It looks like other calls will fail in WordPress core like these unconditionally calling gutenberg_is_experiment_enabled(). This is where the sniff is the most helpful.

Overall, I find this new rule very helpful as it will remind folks to default to syntax available in WordPress core.

@anton-vlasenko anton-vlasenko force-pushed the try/forbid-calling-disallowed-functions-sniff branch 2 times, most recently from f032c82 to 0277771 Compare January 25, 2024 19:30
@anton-vlasenko
Copy link
Contributor Author

anton-vlasenko commented Jan 26, 2024

@gziolo I really appreciate your feedback.
I've improved the sniff, so now it can detect whether a function/class is guarded (i.e., being called in the Gutenberg context) and not throw errors.
Also, to make the PR pass CI checks, I've added temporary //phpcs:ignore statements (please see ef5388f).

An example of such statement would be:

// Refactoring required: this function must be guarded as it is available only in Gutenberg, not in Core.

I plan to create another GitHub issue specifically to address the refactoring of these occurrences after this PR is merged.

If the PR looks good to you, both in terms of code and functionality, may I ask for your approval? 🙏

wp_register_script_module(
'@wordpress/block-library/file',
defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ? gutenberg_url( '/build/interactivity/file.min.js' ) : includes_url( 'blocks/file/view.min.js' ),
$module_url ?? includes_url( 'blocks/file/view.min.js' ),
Copy link
Contributor

@azaozz azaozz Jan 26, 2024

Choose a reason for hiding this comment

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

Seems this should work in PHP 7.0+. Just unsure if Gutenberg still has to support 5.6?

Copy link
Contributor

Choose a reason for hiding this comment

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

7 or higher according to the plugin directory. There's a whole discussion around whether WP "officially" supports the null coalescing operator or not but I think at this point we should be fine to start using it 😅

Copy link
Member

Choose a reason for hiding this comment

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

You can always initialize the $module_url before IS_GUTENBERG_PLUGIN check to avoid the conditional here.

Copy link
Contributor

Choose a reason for hiding this comment

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

7 or higher according to the plugin directory. There's a whole discussion around whether WP "officially" supports the null coalescing operator or not but I think at this point we should be fine to start using it 😅

The null coalescing operator has not yet been accepted into the WordPress Coding Standards. There is a Trac ticket open for it.

Until this ticket or a proposal for it (and other PHP 7.0 syntax) is accepted into Core, please don't use it (yet) for code that will be merged into Core. Why? It will be get removed once it comes to Core.

So to ease the sync process between GB and Core, I'd advise to not use it yet.

Copy link
Contributor Author

@anton-vlasenko anton-vlasenko Jan 30, 2024

Choose a reason for hiding this comment

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

I thought the null coalescing operator was OK to use in Gutenberg considering its extensive usage throughout the Gutenberg codebase: https://github.com/search?q=repo%3AWordPress%2Fgutenberg+path%3A*.php+language%3APHP+%3F%3F&type=code&ref=advsearch.
But I didn't check if it's actually allowed.
I agree with @hellofromtonya, let's not use ?? until it's officially accepted in Core (and appreciate the link to the Trac discussion).
Fixed in 1b61304a422ff9d8bd1f1bbdf30215f3b1ff75ce.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You can always initialize the $module_url before IS_GUTENBERG_PLUGIN check to avoid the conditional here.

I didn't want to initialize it before the IS_GUTENBERG_PLUGIN check for performance-related reasons. But yes, that was an option.

Copy link
Contributor

@tellthemachines tellthemachines left a comment

Choose a reason for hiding this comment

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

Thanks for the PR, this is looking pretty good! I think we should try and merge this, see how it works and then iterate if needed. Also it looks like most of the refactoring to be done is in experimental blocks that aren't going into 6.5 so our codebase seems to be in a good place overall 😄

$conditions = array_reverse( $tokens[ $stack_pointer ]['conditions'], true );

// Matches the "if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN" string.
$regexp = '/if\s*\(\s*defined\(\s*(\'|")IS_GUTENBERG_PLUGIN(\'|")\s*\)\s*&&\s*IS_GUTENBERG_PLUGIN/';
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if there would ever be a reason to make this check configurable? Checking for IS_GUTENBERG_PLUGIN makes sense in block_library or any other PHP files that may be auto-generated in core from the npm packages. I guess this kind of logic wouldn't be needed at all if we were using the sniff in plugin-specific PHP files.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The sniff is currently only checking the /packages/block-library folder: https://github.com/WordPress/gutenberg/pull/57738/files#diff-05ae9cddcaec1e845771a7db224961439f83ef5939ec67d3a48744cb34d7e58bR157
So, it doesn't check files in other folders.
I hope I've understood your question correctly.

@@ -980,6 +980,8 @@ function block_core_navigation_get_fallback_blocks() {
if ( class_exists( 'WP_Navigation_Fallback' ) ) {
$navigation_post = WP_Navigation_Fallback::get_fallback();
} else {
// Refactoring required: this class must be guarded as it is available only in Gutenberg, and not in Core.
// phpcs:ignore Gutenberg.CodeAnalysis.ForbiddenFunctionsAndClasses.ForbiddenClassUsage
Copy link
Member

@gziolo gziolo Jan 29, 2024

Choose a reason for hiding this comment

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

This is the only file that has the phpcs:ignore comment included that is going to sync to WP core. Will it work correctly without the rule defined? I simply don't know how PHPCS works so the answer will be simple.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It will not generate any errors if the ignored rule is not defined.
However, I've moved these to the phpcs.xml.dist file in 255e3ed to avoid cluttering the code with phpcs:ignore statements. I hope that these files will be refactored soon, allowing for the removal of the exclude-pattern statements.

phpcs.xml.dist Outdated
Comment on lines 160 to 163
<element value="(G|g)utenberg.*"/>
</property>
<property name="forbidden_classes" type="array">
<element value="(G|g)utenberg.*"/>
Copy link
Member

Choose a reason for hiding this comment

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

Nit: technically speaking, it should be enough to use the following:

Suggested change
<element value="(G|g)utenberg.*"/>
</property>
<property name="forbidden_classes" type="array">
<element value="(G|g)utenberg.*"/>
<element value="gutenberg.*"/>
</property>
<property name="forbidden_classes" type="array">
<element value="Gutenberg.*"/>

Although, maybe I'm missing some special cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right.
However, to be on the safer side, I would prefer to allow the first letter to be either 'G' or 'g'.
it's just my personal preference.

Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

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

This is a great start. I left a couple of nitpicks to consider, but none of them are blockers. This should be a big help for the Gutenberg syncing in WordPress Core, as so far it's been always on the editor release leads to catch these code paths.

@youknowriad and @getdave, anything we could miss based on the experience in the WP 6.5 release so far?

@youknowriad
Copy link
Contributor

Great work here 👍 This is going to help us a lot.

Comment on lines 983 to 984
// Refactoring required: this class must be guarded as it is available only in Gutenberg, and not in Core.
// phpcs:ignore Gutenberg.CodeAnalysis.ForbiddenFunctionsAndClasses.ForbiddenClassUsage
Copy link
Contributor

Choose a reason for hiding this comment

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

@tjcafferkey @ockham Did we introduce the called to the Gutenberg_ prefixed version of this class in the recent work on hooks in the Nav block?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm removing this in #58369 I don't see where this class is defined at all.

Copy link
Contributor

@getdave getdave left a comment

Choose a reason for hiding this comment

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

Great work. This will make a big difference. Merge and iterate 👍

Would it help if someone (me?) made an Issue to track resolving the outstanding cases you've had to ignore here? Would be good to get these resolved.

@anton-vlasenko anton-vlasenko force-pushed the try/forbid-calling-disallowed-functions-sniff branch from d185d8f to 1b61304 Compare January 30, 2024 18:16
@anton-vlasenko anton-vlasenko force-pushed the try/forbid-calling-disallowed-functions-sniff branch from 1b61304 to 8249699 Compare January 30, 2024 19:54
@anton-vlasenko
Copy link
Contributor Author

Great work. This will make a big difference. Merge and iterate 👍

Would it help if someone (me?) made an Issue to track resolving the outstanding cases you've had to ignore here? Would be good to get these resolved.

@getdave Awesome. Yes, please go ahead and create a GitHub issue to track resolving the outstanding cases.
FYI, I've moved the ignore statements to the phpcs.xml.dist file in 255e3ed.

@anton-vlasenko anton-vlasenko merged commit 7fd9023 into trunk Jan 31, 2024
55 checks passed
@anton-vlasenko anton-vlasenko deleted the try/forbid-calling-disallowed-functions-sniff branch January 31, 2024 13:19
@anton-vlasenko
Copy link
Contributor Author

anton-vlasenko commented Jan 31, 2024

Thank you all!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Type] Code Quality Issues or PRs that relate to code quality
Projects
None yet
7 participants