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

Enforce valid function names in the packages/block-library/src/*/*.php files #53438

Merged
merged 49 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
42dd2b5
Commit WIP.
anton-vlasenko Aug 3, 2023
d7f7212
Commit WIP.
anton-vlasenko Aug 7, 2023
881a594
1. Rename sniff.
anton-vlasenko Aug 8, 2023
695e2bf
Remove redundant file.
anton-vlasenko Aug 8, 2023
3b650ad
Fix sniff's name.
anton-vlasenko Aug 8, 2023
ce561b1
List allowed function names in the error message.
anton-vlasenko Aug 8, 2023
10eae71
1. Fix doc blocks.
anton-vlasenko Aug 8, 2023
332f003
Fix error message.
anton-vlasenko Aug 8, 2023
8f74d2a
Improve error message.
anton-vlasenko Aug 8, 2023
081259d
Add the new rule to the phpcs.xml.dist file.
anton-vlasenko Aug 8, 2023
5bdb79e
Add doing it wrong as dynamic properties are not supported in PHP 8.2…
anton-vlasenko Aug 8, 2023
975187f
Improve error message.
anton-vlasenko Aug 8, 2023
1563116
Improve error message.
anton-vlasenko Aug 8, 2023
c3a6cdc
Improve error message.
anton-vlasenko Aug 8, 2023
addaea8
Ensure that the rule checks all PHP files, not just the index.php files.
anton-vlasenko Aug 9, 2023
8bf5c72
Refactor variable names to use lowercase.
anton-vlasenko Aug 9, 2023
ecc45fd
Add exceptions to ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
a3ef82a
Add exceptions to ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
c6029de
Add exceptions to ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
1e54af8
Add exceptions to ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
a51c0ba
Add exceptions to ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
32c3a27
Add exceptions to ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
0ee1871
Add exceptions to the ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
50dfbcc
Add exceptions to the ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
07a9a15
Add exceptions to the ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
e276723
Add exceptions to the ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
4c12e14
Add exceptions to the ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
aed8f1b
Add exceptions to the ValidBlockLibraryFunctionName rule.
anton-vlasenko Aug 9, 2023
2991293
Better sanitization of parameters.
anton-vlasenko Aug 9, 2023
5266d88
Update docblock.
anton-vlasenko Aug 9, 2023
deccd15
Update changlog.
anton-vlasenko Aug 10, 2023
6c3135e
Implement a check for edge cases where the function name terminates w…
anton-vlasenko Aug 10, 2023
148c47a
Use blockquote instead of double quote in the error message.
anton-vlasenko Aug 10, 2023
aa7820f
Make the sanitize_directory_name method static.
anton-vlasenko Aug 10, 2023
8c47356
Update the commment.
anton-vlasenko Aug 10, 2023
93e42d7
Revert adding the phpcs:ignore statements.
anton-vlasenko Aug 11, 2023
c28feeb
Add ability to whilelist certain functions.
anton-vlasenko Aug 11, 2023
fdb738b
Revert adding the phpcs:ignore statements.
anton-vlasenko Aug 11, 2023
b6178b4
Revert adding the phpcs:ignore statements.
anton-vlasenko Aug 11, 2023
9076c0d
Add the list of allowed functions.
anton-vlasenko Aug 11, 2023
b3c9010
Fix typo.
anton-vlasenko Aug 11, 2023
888d82f
Add comment to php.xml.dist with the reasoning for the list.
anton-vlasenko Aug 11, 2023
62e9c44
Revert the changes as we don't use phpcs:disable statements now.
anton-vlasenko Aug 11, 2023
a4bd446
Fix comment.
anton-vlasenko Aug 11, 2023
a869ff4
Remove redundant code.
anton-vlasenko Aug 11, 2023
523e1ed
Improve text of the comment.
anton-vlasenko Aug 11, 2023
d1f38ac
Improve text of the comment.
anton-vlasenko Aug 11, 2023
8ab646f
Replace "backported" with "consumes" (props @hellofromtonya).
anton-vlasenko Aug 16, 2023
1c25491
Remove the last sentence.
anton-vlasenko Aug 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,59 @@
</property>
</properties>
</rule>

<rule ref="Gutenberg.NamingConventions.ValidBlockLibraryFunctionName">
<include-pattern>./packages/block-library/src/*/*.php</include-pattern>
<properties>
<property name="prefixes" type="array">
<element value="block_core_"/>
<element value="render_block_core_"/>
<element value="register_block_core_"/>
Copy link
Member

Choose a reason for hiding this comment

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

I see functions that start build_template_part_, so maybe we could open also build_block_core_template_part in case folks want to use that. I don't have strong opinions on that, as they could also do block_core_tempalte_part_build.

Copy link
Contributor Author

@anton-vlasenko anton-vlasenko Aug 11, 2023

Choose a reason for hiding this comment

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

I don't have a preference either. However, please note that the linting rule not only checks the prefix name but also the directory name, as requested here.

Copy link
Member

Choose a reason for hiding this comment

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

It's fine as is for now, but we should keep monitoring and extending the list if that is useful.

</property>
<!--
The following list of functions is final and must not be extended.
It includes functions that cannot be renamed due to backward compatibility concerns.
-->
<property name="allowed_functions" type="array">
<element value="_delete_custom_logo_on_remove_site_logo"/>
<element value="_delete_site_logo_on_remove_custom_logo"/>
<element value="_delete_site_logo_on_remove_custom_logo_on_setup_theme"/>
<element value="_delete_site_logo_on_remove_theme_mods"/>
<element value="_override_custom_logo_theme_mod"/>
<element value="_sync_custom_logo_to_site_logo"/>
<element value="_wp_rest_api_autosave_meta"/>
<element value="_wp_rest_api_force_autosave_difference"/>
<element value="apply_block_core_search_border_style"/>
<element value="apply_block_core_search_border_styles"/>
<element value="build_dropdown_script_block_core_categories"/>
<element value="build_template_part_block_area_variations"/>
<element value="build_template_part_block_instance_variations"/>
<element value="build_template_part_block_variations"/>
<element value="build_variation_for_navigation_link"/>
<element value="classnames_for_block_core_search"/>
<element value="comments_block_form_defaults"/>
<element value="enqueue_legacy_post_comments_block_styles"/>
<element value="get_block_core_avatar_border_attributes"/>
<element value="get_block_core_post_featured_image_border_attributes"/>
<element value="get_block_core_post_featured_image_overlay_element_markup"/>
<element value="get_border_color_classes_for_block_core_search"/>
<element value="get_color_classes_for_block_core_search"/>
<element value="get_typography_classes_for_block_core_search"/>
<element value="get_typography_styles_for_block_core_search"/>
<element value="gutenberg_block_core_file_update_interactive_view_script"/>
<element value="gutenberg_block_core_navigation_update_interactive_view_script"/>
<element value="post_comments_form_block_form_defaults"/>
<element value="register_block_core_site_icon_setting"/>
<element value="register_legacy_post_comments_block"/>
<element value="styles_for_block_core_search"/>
<element value="wp_add_footnotes_revisions_to_post_meta"/>
<element value="wp_add_footnotes_to_revision"/>
<element value="wp_get_footnotes_from_revision"/>
<element value="wp_keep_footnotes_revision_id"/>
<element value="wp_latest_comments_draft_or_post_title"/>
<element value="wp_restore_footnotes_from_revision"/>
<element value="wp_save_footnotes_meta"/>
</property>
</properties>
</rule>
</ruleset>
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php
/**
* Gutenberg Coding Standards.
*
* @package gutenberg/gutenberg-coding-standards
* @link https://github.com/WordPress/gutenberg
* @license https://opensource.org/licenses/MIT MIT
*/

namespace GutenbergCS\Gutenberg\Sniffs\NamingConventions;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;

/**
*
* This sniff checks function names to ensure they adhere to specified prefixes
* determined by the parent directory name. It enforces that function names start
* with one of the allowed prefixes defined in the sniffer configuration.
*
* @package gutenberg/gutenberg-coding-standards
*
* @since 1.0.0
*/
final class ValidBlockLibraryFunctionNameSniff implements Sniff {
/**
* Target prefixes.
*
* @var array
*/
public $prefixes = array();

/**
* These functions are considered permissible and will be ignored by the sniffer.
*
* @var array
*/
public $allowed_functions = array();

/**
* Registers the tokens that this sniff wants to listen for.
*
* @return array
*/
public function register() {
$this->onRegisterEvent();

return array( T_FUNCTION );
}

/**
* Processes function tokens.
*
* @param File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process( File $phpcsFile, $stackPtr ) {
$tokens = $phpcsFile->getTokens();
$token = $tokens[ $stackPtr ];

if ( 'T_FUNCTION' !== $token['type'] ) {
return;
}

$this->processFunctionToken( $phpcsFile, $stackPtr );
}

/**
* This method analyzes the function token and its name within the provided file.
* It checks if the function name adheres to allowed prefixes based on the parent directory name.
* If the function name is not valid, an error message is added to the code sniffer report.
*
* @param File $phpcsFile The file being scanned.
* @param int $stackPointer The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
private function processFunctionToken( File $phpcsFile, $stackPointer ) {

if ( empty( $this->prefixes ) ) {
// Nothing to process.
return;
}

$tokens = $phpcsFile->getTokens();
$function_token = $phpcsFile->findNext( T_STRING, $stackPointer );

$wrapping_tokens_to_check = array(
T_CLASS,
T_INTERFACE,
T_TRAIT,
);

foreach ( $wrapping_tokens_to_check as $wrapping_token_to_check ) {
if ( false !== $phpcsFile->getCondition( $function_token, $wrapping_token_to_check, false ) ) {
// This sniff only processes functions, not class methods.
return;
}
}

$function_name = $tokens[ $function_token ]['content'];

if ( in_array( $function_name, $this->allowed_functions, true ) ) {
// The function name is included in the list of allowed functions; bypassing further checks.
return;
}

$parent_directory_name = basename( dirname( $phpcsFile->getFilename() ) );

$allowed_function_prefixes = array();
$is_function_name_valid = false;
foreach ( $this->prefixes as $prefix ) {
$prefix = rtrim( $prefix, '_' );
$allowed_function_prefix = $prefix . '_' . self::sanitize_directory_name( $parent_directory_name );
$allowed_function_prefixes[] = $allowed_function_prefix;
// Validate the name's correctness and ensure it does not end with an underscore.
$regexp = sprintf( '/^%s(|_.+)$/', preg_quote( $allowed_function_prefix, '/' ) );
$is_function_name_valid |= ( 1 === preg_match( $regexp, $function_name ) );
}

if ( $is_function_name_valid ) {
return;
}

$error_message = "The function name `{$function_name}()` is invalid because PHP function names in this file should start with one of the following prefixes: `"
. implode( '`, `', $allowed_function_prefixes ) . '`.';
$phpcsFile->addError( $error_message, $function_token, 'FunctionNameInvalid' );
}

/**
* The purpose of this method is to run callbacks
* after the class properties have been set.
*/
private function onRegisterEvent() {
$this->prefixes = self::sanitize( $this->prefixes );
$this->allowed_functions = self::sanitize( $this->allowed_functions );
}

/**
* Sanitize a directory name by converting it to lowercase and replacing non-letter
* and non-digit characters with underscores.
*
* @param string $directory_name
*
* @return string
*/
private static function sanitize_directory_name( $directory_name ) {
// Convert to lowercase.
$directory_name = strtolower( $directory_name );

// Replace non-letter and non-digit characters with underscores.
return preg_replace( '/[^a-z0-9]/', '_', $directory_name );
}

/**
* Sanitize an array of values by trimming each element and removing empty elements.
*
* @param array $values The values being sanitized.
*
* @return array
*/
private static function sanitize( $values ) {
$values = array_map( 'trim', $values );

return array_filter( $values );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
<description>Gutenberg Coding Standards</description>

<rule ref="Gutenberg.CodeAnalysis.GuardedFunctionAndClassNames"/>
<rule ref="Gutenberg.NamingConventions.ValidBlockLibraryFunctionName"/>

</ruleset>
Loading