From 29d9e9c28a67d92e0bc1948209f270345c0f9fb7 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 26 Jun 2026 13:05:45 -0400 Subject: [PATCH 1/9] Extend no-namespace/inc-folder exclusions to all mu-plugin entry points We still want the filename flexibility and ability to use namespaces in regular first-party plugin entrypoints, not just single-file MU-plugins --- CHANGELOG.md | 4 + HM/Sniffs/Files/FunctionFileNameSniff.php | 6 +- HM/Sniffs/Files/MuPluginEntryPointTrait.php | 84 +++++++++++++++++++ HM/Sniffs/Files/MuPluginFileTrait.php | 27 ------ .../Files/NamespaceDirectoryNameSniff.php | 6 +- HM/Tests/Files/FunctionFileNameUnitTest.php | 5 ++ .../mu-plugins/headered/loader.php | 10 +++ .../mu-plugins/sub-plugin/sub-plugin.php | 5 ++ .../mu-plugins/with-plugin-file/plugin.php | 5 ++ .../Files/NamespaceDirectoryNameUnitTest.php | 5 ++ .../mu-plugins/headered/loader.php | 8 ++ .../mu-plugins/sub-plugin/sub-plugin.php | 3 + .../mu-plugins/with-plugin-file/plugin.php | 3 + 13 files changed, 138 insertions(+), 33 deletions(-) create mode 100644 HM/Sniffs/Files/MuPluginEntryPointTrait.php delete mode 100644 HM/Sniffs/Files/MuPluginFileTrait.php create mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/headered/loader.php create mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/sub-plugin/sub-plugin.php create mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/with-plugin-file/plugin.php create mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/headered/loader.php create mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/sub-plugin/sub-plugin.php create mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/with-plugin-file/plugin.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 41718999..28974579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ +## 2.3.0 + +- Allow the entry-point file of a plugin nested in `mu-plugins/`/`client-mu-plugins/` (named `/.php` or `/plugin.php`, or any file declaring a `Plugin Name` header) to declare a namespace without being named `namespace.php` #XXX + ## 2.2.1 - Do not flag get_block_wrapper_attributes as an escaping risk, this is a false positive as function escapes internally #340 diff --git a/HM/Sniffs/Files/FunctionFileNameSniff.php b/HM/Sniffs/Files/FunctionFileNameSniff.php index 001cd013..891302d6 100644 --- a/HM/Sniffs/Files/FunctionFileNameSniff.php +++ b/HM/Sniffs/Files/FunctionFileNameSniff.php @@ -8,7 +8,7 @@ * Sniff to check for namespaced functions are in `namespace.php`. */ class FunctionFileNameSniff implements Sniff { - use MuPluginFileTrait; + use MuPluginEntryPointTrait; public function register() { return array( T_FUNCTION ); @@ -27,8 +27,8 @@ public function process( File $phpcsFile, $stackPtr ) { return; } - if ( $this->is_single_file_mu_plugin( $phpcsFile->getFileName() ) ) { - // Single-file plugins cannot be split into a namespace.php file. + if ( $this->is_mu_plugin_entry_point( $phpcsFile ) ) { + // Mu-plugin entry points cannot be split into a namespace.php file. return; } diff --git a/HM/Sniffs/Files/MuPluginEntryPointTrait.php b/HM/Sniffs/Files/MuPluginEntryPointTrait.php new file mode 100644 index 00000000..f0eeba03 --- /dev/null +++ b/HM/Sniffs/Files/MuPluginEntryPointTrait.php @@ -0,0 +1,84 @@ +/` carrying the plugin + * header; by convention it is `/.php` or `/plugin.php`, + * but any such file declaring a Plugin Name header counts. + * + * Both are useful patterns to support: they can't be split into an inc/ + * directory or a named namespace.php, so they're exempt from those rules. + */ +trait MuPluginEntryPointTrait { + /** + * Is the file being checked a mu-plugin entry point? + * + * @param File $phpcsFile The file being scanned. + * @return bool True if the file is a single-file mu-plugin or a nested plugin entry point. + */ + protected function is_mu_plugin_entry_point( File $phpcsFile ) { + $path = $phpcsFile->getFilename(); + + // Normalize the directory separator across operating systems. + if ( DIRECTORY_SEPARATOR !== '/' ) { + $path = str_replace( DIRECTORY_SEPARATOR, '/', $path ); + } + + // Single-file mu-plugin: a direct child of (client-)mu-plugins/. + if ( preg_match( '#/(?:client-)?mu-plugins/[^/]+\.php$#', $path ) ) { + return true; + } + + // Only a file directly inside a plugin directory can be its entry + // point; anything deeper is ordinary plugin code. + if ( ! preg_match( '#/(?:client-)?mu-plugins/([^/]+)/([^/]+)\.php$#', $path, $matches ) ) { + return false; + } + + // Conventional entry-point names: /.php or /plugin.php. + [ , $dir, $file ] = $matches; + if ( $file === 'plugin' || $file === $dir ) { + return true; + } + + // Otherwise fall back to the authoritative test: a Plugin Name header. + return $this->has_plugin_header( $phpcsFile ); + } + + /** + * Does the file open with a Plugin Name header comment? + * + * @param File $phpcsFile The file being scanned. + * @return bool True if a Plugin Name header precedes the first line of code. + */ + protected function has_plugin_header( File $phpcsFile ) { + $tokens = $phpcsFile->getTokens(); + + foreach ( $tokens as $token ) { + // Allow the opening tag and whitespace before the header. + if ( $token['code'] === T_OPEN_TAG || $token['code'] === T_WHITESPACE ) { + continue; + } + + // Scan the leading comment block(s) for the header field. + if ( in_array( $token['code'], Tokens::$commentTokens, true ) ) { + if ( stripos( $token['content'], 'Plugin Name:' ) !== false ) { + return true; + } + continue; + } + + // First real code reached; the header (if any) is before it. + break; + } + + return false; + } +} diff --git a/HM/Sniffs/Files/MuPluginFileTrait.php b/HM/Sniffs/Files/MuPluginFileTrait.php deleted file mode 100644 index 2c5487ec..00000000 --- a/HM/Sniffs/Files/MuPluginFileTrait.php +++ /dev/null @@ -1,27 +0,0 @@ -is_single_file_mu_plugin( $full ) ) { - // Single-file mu-plugins will naturally never have an inc/ directory. + if ( $this->is_mu_plugin_entry_point( $phpcsFile ) ) { + // Mu-plugin entry points will naturally never have an inc/ directory. return; } diff --git a/HM/Tests/Files/FunctionFileNameUnitTest.php b/HM/Tests/Files/FunctionFileNameUnitTest.php index 142d66c9..f7f5bcc2 100644 --- a/HM/Tests/Files/FunctionFileNameUnitTest.php +++ b/HM/Tests/Files/FunctionFileNameUnitTest.php @@ -52,6 +52,11 @@ public function getErrorList() { // Single-file mu-plugins can't all be named namespace.php. 'my-plugin.php', 'my-client-plugin.php', + // Nested plugin entry points are exempt too: /.php, + // /plugin.php, or any file with a Plugin Name header. + 'sub-plugin.php', + 'plugin.php', + 'loader.php', ]; if ( in_array( $file, $pass, true ) ) { return []; diff --git a/HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/headered/loader.php b/HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/headered/loader.php new file mode 100644 index 00000000..ac56bbc1 --- /dev/null +++ b/HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/headered/loader.php @@ -0,0 +1,10 @@ +/.php, + // /plugin.php, or any file with a Plugin Name header. + 'sub-plugin.php', + 'plugin.php', + 'loader.php', ]; if ( in_array( $file, $pass, true ) ) { return []; diff --git a/HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/headered/loader.php b/HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/headered/loader.php new file mode 100644 index 00000000..9eb62d16 --- /dev/null +++ b/HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/headered/loader.php @@ -0,0 +1,8 @@ + Date: Fri, 26 Jun 2026 15:45:55 -0400 Subject: [PATCH 2/9] Broaden entry-point rule relaxation to plugins/, not just (client-)?mu-plugins/ --- CHANGELOG.md | 2 +- HM/Sniffs/Files/FunctionFileNameSniff.php | 6 ++--- .../Files/NamespaceDirectoryNameSniff.php | 6 ++--- ...intTrait.php => PluginEntryPointTrait.php} | 25 +++++++++++-------- HM/Tests/Files/FunctionFileNameUnitTest.php | 6 +++-- .../plugins/regular-plugin/helpers.php | 5 ++++ .../plugins/regular-plugin/plugin.php | 5 ++++ .../plugins/standalone/standalone.php | 5 ++++ .../Files/NamespaceDirectoryNameUnitTest.php | 6 +++-- .../plugins/regular-plugin/helpers.php | 3 +++ .../plugins/regular-plugin/plugin.php | 3 +++ .../plugins/standalone/standalone.php | 3 +++ 12 files changed, 53 insertions(+), 22 deletions(-) rename HM/Sniffs/Files/{MuPluginEntryPointTrait.php => PluginEntryPointTrait.php} (68%) create mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/plugins/regular-plugin/helpers.php create mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/plugins/regular-plugin/plugin.php create mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/plugins/standalone/standalone.php create mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/plugins/regular-plugin/helpers.php create mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/plugins/regular-plugin/plugin.php create mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/plugins/standalone/standalone.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 28974579..f58ed55f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ and fill in the contents as you go. This simplifies later release management. -- ## 2.3.0 -- Allow the entry-point file of a plugin nested in `mu-plugins/`/`client-mu-plugins/` (named `/.php` or `/plugin.php`, or any file declaring a `Plugin Name` header) to declare a namespace without being named `namespace.php` #XXX +- Allow a plugin's entry-point file -- the main file in a plugin folder under `plugins/`, `mu-plugins/`, or `client-mu-plugins/`, named `/.php` or `/plugin.php` or declaring a `Plugin Name` header -- to declare a namespace without being named `namespace.php` #XXX ## 2.2.1 diff --git a/HM/Sniffs/Files/FunctionFileNameSniff.php b/HM/Sniffs/Files/FunctionFileNameSniff.php index 891302d6..37d35558 100644 --- a/HM/Sniffs/Files/FunctionFileNameSniff.php +++ b/HM/Sniffs/Files/FunctionFileNameSniff.php @@ -8,7 +8,7 @@ * Sniff to check for namespaced functions are in `namespace.php`. */ class FunctionFileNameSniff implements Sniff { - use MuPluginEntryPointTrait; + use PluginEntryPointTrait; public function register() { return array( T_FUNCTION ); @@ -27,8 +27,8 @@ public function process( File $phpcsFile, $stackPtr ) { return; } - if ( $this->is_mu_plugin_entry_point( $phpcsFile ) ) { - // Mu-plugin entry points cannot be split into a namespace.php file. + if ( $this->is_plugin_entry_point( $phpcsFile ) ) { + // Plugin entry points cannot be split into a namespace.php file. return; } diff --git a/HM/Sniffs/Files/NamespaceDirectoryNameSniff.php b/HM/Sniffs/Files/NamespaceDirectoryNameSniff.php index 49cbefa8..f59747e3 100644 --- a/HM/Sniffs/Files/NamespaceDirectoryNameSniff.php +++ b/HM/Sniffs/Files/NamespaceDirectoryNameSniff.php @@ -8,7 +8,7 @@ * Namespaced things must be in directories matching the namespace. */ class NamespaceDirectoryNameSniff implements Sniff { - use MuPluginEntryPointTrait; + use PluginEntryPointTrait; /** * Returns an array of tokens this test wants to listen for. @@ -52,8 +52,8 @@ public function process( File $phpcsFile, $stackPtr ) { $directory = str_replace( DIRECTORY_SEPARATOR, '/', $directory ); } - if ( $this->is_mu_plugin_entry_point( $phpcsFile ) ) { - // Mu-plugin entry points will naturally never have an inc/ directory. + if ( $this->is_plugin_entry_point( $phpcsFile ) ) { + // Plugin entry points will naturally never have an inc/ directory. return; } diff --git a/HM/Sniffs/Files/MuPluginEntryPointTrait.php b/HM/Sniffs/Files/PluginEntryPointTrait.php similarity index 68% rename from HM/Sniffs/Files/MuPluginEntryPointTrait.php rename to HM/Sniffs/Files/PluginEntryPointTrait.php index f0eeba03..3b54e428 100644 --- a/HM/Sniffs/Files/MuPluginEntryPointTrait.php +++ b/HM/Sniffs/Files/PluginEntryPointTrait.php @@ -5,25 +5,27 @@ use PHP_CodeSniffer\Util\Tokens; /** - * Shared helper for detecting must-use plugin entry points. + * Shared helper for detecting plugin entry points. * * Two shapes qualify. A single-file mu-plugin lives as a direct child of a - * `mu-plugins/` directory (or `client-mu-plugins/`, on VIP). A nested plugin's - * entry point is the file inside `mu-plugins//` carrying the plugin - * header; by convention it is `/.php` or `/plugin.php`, - * but any such file declaring a Plugin Name header counts. + * `mu-plugins/` directory (or `client-mu-plugins/`, on VIP); this is specific + * to mu-plugins, whose flat auto-loading means a plugin physically can't be + * split into a folder. A nested plugin's entry point is the file directly + * inside its own folder under `plugins/` (or `mu-plugins/`/`client-mu-plugins/`) + * carrying the plugin header; by convention it is `/.php` or + * `/plugin.php`, but any such file declaring a Plugin Name header counts. * * Both are useful patterns to support: they can't be split into an inc/ * directory or a named namespace.php, so they're exempt from those rules. */ -trait MuPluginEntryPointTrait { +trait PluginEntryPointTrait { /** - * Is the file being checked a mu-plugin entry point? + * Is the file being checked a plugin entry point? * * @param File $phpcsFile The file being scanned. * @return bool True if the file is a single-file mu-plugin or a nested plugin entry point. */ - protected function is_mu_plugin_entry_point( File $phpcsFile ) { + protected function is_plugin_entry_point( File $phpcsFile ) { $path = $phpcsFile->getFilename(); // Normalize the directory separator across operating systems. @@ -36,9 +38,10 @@ protected function is_mu_plugin_entry_point( File $phpcsFile ) { return true; } - // Only a file directly inside a plugin directory can be its entry - // point; anything deeper is ordinary plugin code. - if ( ! preg_match( '#/(?:client-)?mu-plugins/([^/]+)/([^/]+)\.php$#', $path, $matches ) ) { + // Nested entry point: a file directly inside a plugin's own folder, + // under plugins/, mu-plugins/ or client-mu-plugins/. Anything deeper + // is ordinary plugin code. + if ( ! preg_match( '#/(?:(?:client-)?mu-)?plugins/([^/]+)/([^/]+)\.php$#', $path, $matches ) ) { return false; } diff --git a/HM/Tests/Files/FunctionFileNameUnitTest.php b/HM/Tests/Files/FunctionFileNameUnitTest.php index f7f5bcc2..7f3dfb92 100644 --- a/HM/Tests/Files/FunctionFileNameUnitTest.php +++ b/HM/Tests/Files/FunctionFileNameUnitTest.php @@ -52,11 +52,13 @@ public function getErrorList() { // Single-file mu-plugins can't all be named namespace.php. 'my-plugin.php', 'my-client-plugin.php', - // Nested plugin entry points are exempt too: /.php, - // /plugin.php, or any file with a Plugin Name header. + // Nested plugin entry points are exempt too -- in plugins/ as well + // as mu-plugins/ -- by name (/.php, /plugin.php) or + // by a Plugin Name header. 'sub-plugin.php', 'plugin.php', 'loader.php', + 'standalone.php', ]; if ( in_array( $file, $pass, true ) ) { return []; diff --git a/HM/Tests/Files/FunctionFileNameUnitTest/plugins/regular-plugin/helpers.php b/HM/Tests/Files/FunctionFileNameUnitTest/plugins/regular-plugin/helpers.php new file mode 100644 index 00000000..e74ae76b --- /dev/null +++ b/HM/Tests/Files/FunctionFileNameUnitTest/plugins/regular-plugin/helpers.php @@ -0,0 +1,5 @@ +/.php, - // /plugin.php, or any file with a Plugin Name header. + // Nested plugin entry points are exempt too -- in plugins/ as well + // as mu-plugins/ -- by name (/.php, /plugin.php) or + // by a Plugin Name header. 'sub-plugin.php', 'plugin.php', 'loader.php', + 'standalone.php', ]; if ( in_array( $file, $pass, true ) ) { return []; diff --git a/HM/Tests/Files/NamespaceDirectoryNameUnitTest/plugins/regular-plugin/helpers.php b/HM/Tests/Files/NamespaceDirectoryNameUnitTest/plugins/regular-plugin/helpers.php new file mode 100644 index 00000000..1a253fb3 --- /dev/null +++ b/HM/Tests/Files/NamespaceDirectoryNameUnitTest/plugins/regular-plugin/helpers.php @@ -0,0 +1,3 @@ + Date: Fri, 26 Jun 2026 16:05:12 -0400 Subject: [PATCH 3/9] Remove overly-complex "Plugin Name:" plugin header comment detection logic --- CHANGELOG.md | 2 +- HM/Sniffs/Files/PluginEntryPointTrait.php | 52 +++---------------- HM/Tests/Files/FunctionFileNameUnitTest.php | 4 +- .../mu-plugins/headered/loader.php | 10 ---- .../Files/NamespaceDirectoryNameUnitTest.php | 4 +- .../mu-plugins/headered/loader.php | 8 --- 6 files changed, 11 insertions(+), 69 deletions(-) delete mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/headered/loader.php delete mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/headered/loader.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f58ed55f..4fd34bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ and fill in the contents as you go. This simplifies later release management. -- ## 2.3.0 -- Allow a plugin's entry-point file -- the main file in a plugin folder under `plugins/`, `mu-plugins/`, or `client-mu-plugins/`, named `/.php` or `/plugin.php` or declaring a `Plugin Name` header -- to declare a namespace without being named `namespace.php` #XXX +- Allow a plugin's entry-point file -- a single-file mu-plugin, or the main file in a plugin folder under `plugins/`, `mu-plugins/`, or `client-mu-plugins/` named `/.php` or `/plugin.php` -- to declare a namespace without being named `namespace.php` #XXX ## 2.2.1 diff --git a/HM/Sniffs/Files/PluginEntryPointTrait.php b/HM/Sniffs/Files/PluginEntryPointTrait.php index 3b54e428..77ed9fc8 100644 --- a/HM/Sniffs/Files/PluginEntryPointTrait.php +++ b/HM/Sniffs/Files/PluginEntryPointTrait.php @@ -2,7 +2,6 @@ namespace HM\Sniffs\Files; use PHP_CodeSniffer\Files\File; -use PHP_CodeSniffer\Util\Tokens; /** * Shared helper for detecting plugin entry points. @@ -10,10 +9,10 @@ * Two shapes qualify. A single-file mu-plugin lives as a direct child of a * `mu-plugins/` directory (or `client-mu-plugins/`, on VIP); this is specific * to mu-plugins, whose flat auto-loading means a plugin physically can't be - * split into a folder. A nested plugin's entry point is the file directly - * inside its own folder under `plugins/` (or `mu-plugins/`/`client-mu-plugins/`) - * carrying the plugin header; by convention it is `/.php` or - * `/plugin.php`, but any such file declaring a Plugin Name header counts. + * split into a folder. A nested plugin's entry point is the conventionally + * named main file directly inside its own folder -- `/.php` or + * `/plugin.php` -- under `plugins/`, `mu-plugins/` or + * `client-mu-plugins/`. * * Both are useful patterns to support: they can't be split into an inc/ * directory or a named namespace.php, so they're exempt from those rules. @@ -38,50 +37,15 @@ protected function is_plugin_entry_point( File $phpcsFile ) { return true; } - // Nested entry point: a file directly inside a plugin's own folder, - // under plugins/, mu-plugins/ or client-mu-plugins/. Anything deeper - // is ordinary plugin code. + // Nested entry point: the conventionally named main file directly + // inside a plugin's own folder, under plugins/, mu-plugins/ or + // client-mu-plugins/. Anything deeper is ordinary plugin code. if ( ! preg_match( '#/(?:(?:client-)?mu-)?plugins/([^/]+)/([^/]+)\.php$#', $path, $matches ) ) { return false; } // Conventional entry-point names: /.php or /plugin.php. [ , $dir, $file ] = $matches; - if ( $file === 'plugin' || $file === $dir ) { - return true; - } - - // Otherwise fall back to the authoritative test: a Plugin Name header. - return $this->has_plugin_header( $phpcsFile ); - } - - /** - * Does the file open with a Plugin Name header comment? - * - * @param File $phpcsFile The file being scanned. - * @return bool True if a Plugin Name header precedes the first line of code. - */ - protected function has_plugin_header( File $phpcsFile ) { - $tokens = $phpcsFile->getTokens(); - - foreach ( $tokens as $token ) { - // Allow the opening tag and whitespace before the header. - if ( $token['code'] === T_OPEN_TAG || $token['code'] === T_WHITESPACE ) { - continue; - } - - // Scan the leading comment block(s) for the header field. - if ( in_array( $token['code'], Tokens::$commentTokens, true ) ) { - if ( stripos( $token['content'], 'Plugin Name:' ) !== false ) { - return true; - } - continue; - } - - // First real code reached; the header (if any) is before it. - break; - } - - return false; + return $file === 'plugin' || $file === $dir; } } diff --git a/HM/Tests/Files/FunctionFileNameUnitTest.php b/HM/Tests/Files/FunctionFileNameUnitTest.php index 7f3dfb92..37412441 100644 --- a/HM/Tests/Files/FunctionFileNameUnitTest.php +++ b/HM/Tests/Files/FunctionFileNameUnitTest.php @@ -53,11 +53,9 @@ public function getErrorList() { 'my-plugin.php', 'my-client-plugin.php', // Nested plugin entry points are exempt too -- in plugins/ as well - // as mu-plugins/ -- by name (/.php, /plugin.php) or - // by a Plugin Name header. + // as mu-plugins/ -- when named /.php or /plugin.php. 'sub-plugin.php', 'plugin.php', - 'loader.php', 'standalone.php', ]; if ( in_array( $file, $pass, true ) ) { diff --git a/HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/headered/loader.php b/HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/headered/loader.php deleted file mode 100644 index ac56bbc1..00000000 --- a/HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/headered/loader.php +++ /dev/null @@ -1,10 +0,0 @@ -/.php, /plugin.php) or - // by a Plugin Name header. + // as mu-plugins/ -- when named /.php or /plugin.php. 'sub-plugin.php', 'plugin.php', - 'loader.php', 'standalone.php', ]; if ( in_array( $file, $pass, true ) ) { diff --git a/HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/headered/loader.php b/HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/headered/loader.php deleted file mode 100644 index 9eb62d16..00000000 --- a/HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/headered/loader.php +++ /dev/null @@ -1,8 +0,0 @@ - Date: Fri, 26 Jun 2026 16:05:28 -0400 Subject: [PATCH 4/9] Refine logic for path-based plugin detection to simplify the fast path --- HM/Sniffs/Files/PluginEntryPointTrait.php | 31 +++++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/HM/Sniffs/Files/PluginEntryPointTrait.php b/HM/Sniffs/Files/PluginEntryPointTrait.php index 77ed9fc8..927709af 100644 --- a/HM/Sniffs/Files/PluginEntryPointTrait.php +++ b/HM/Sniffs/Files/PluginEntryPointTrait.php @@ -6,16 +6,20 @@ /** * Shared helper for detecting plugin entry points. * - * Two shapes qualify. A single-file mu-plugin lives as a direct child of a - * `mu-plugins/` directory (or `client-mu-plugins/`, on VIP); this is specific - * to mu-plugins, whose flat auto-loading means a plugin physically can't be - * split into a folder. A nested plugin's entry point is the conventionally - * named main file directly inside its own folder -- `/.php` or - * `/plugin.php` -- under `plugins/`, `mu-plugins/` or - * `client-mu-plugins/`. + * We support two patterns: * - * Both are useful patterns to support: they can't be split into an inc/ - * directory or a named namespace.php, so they're exempt from those rules. + * "Single-file mu-plugins" live as a direct child of a `mu-plugins/` directory + * (or `client-mu-plugins/`, on VIP) and encapsulate a complete plugin within + * one single PHP file. + * + * A plugin entrypoint may alternatively live nested within a plugin folder, + * conventionally named after its parent folder or else called plugin.php. + * These ordinary plugin entrypoints can live within `plugins/`, `mu-plugins/` + * or `client-mu-plugins/` alike. + * + * Both are useful patterns to support, and should be exempt from certain rules: + * entrypoint files can't be split into an inc/ directory and collide or lose + * meaning if renamed to namespace.php. */ trait PluginEntryPointTrait { /** @@ -32,6 +36,11 @@ protected function is_plugin_entry_point( File $phpcsFile ) { $path = str_replace( DIRECTORY_SEPARATOR, '/', $path ); } + // Naming a file plugin.php conventionally signals it's a plugin entrypoint. + if ( basename( $path ) === 'plugin.php' ) { + return true; + } + // Single-file mu-plugin: a direct child of (client-)mu-plugins/. if ( preg_match( '#/(?:client-)?mu-plugins/[^/]+\.php$#', $path ) ) { return true; @@ -44,8 +53,8 @@ protected function is_plugin_entry_point( File $phpcsFile ) { return false; } - // Conventional entry-point names: /.php or /plugin.php. + // Check for conventional pattern /.php. [ , $dir, $file ] = $matches; - return $file === 'plugin' || $file === $dir; + return $file === $dir; } } From 5daf5d99cee9ae755e04c954afff8bee7dbcaec4 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 26 Jun 2026 16:13:36 -0400 Subject: [PATCH 5/9] Document this PR's number in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd34bbd..bd9b7e7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ and fill in the contents as you go. This simplifies later release management. -- ## 2.3.0 -- Allow a plugin's entry-point file -- a single-file mu-plugin, or the main file in a plugin folder under `plugins/`, `mu-plugins/`, or `client-mu-plugins/` named `/.php` or `/plugin.php` -- to declare a namespace without being named `namespace.php` #XXX +- Allow a plugin's entry-point file -- a single-file mu-plugin, or the main file in a plugin folder under `plugins/`, `mu-plugins/`, or `client-mu-plugins/` named `/.php` or `/plugin.php` -- to declare a namespace without being named `namespace.php` #343 ## 2.2.1 From 12cca66cfbb228c0269ad61d0492a3632cb7a163 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 26 Jun 2026 16:24:44 -0400 Subject: [PATCH 6/9] Remove unneeded fixtures --- .../plugins/regular-plugin/helpers.php | 5 ----- .../plugins/regular-plugin/helpers.php | 3 --- 2 files changed, 8 deletions(-) delete mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/plugins/regular-plugin/helpers.php delete mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/plugins/regular-plugin/helpers.php diff --git a/HM/Tests/Files/FunctionFileNameUnitTest/plugins/regular-plugin/helpers.php b/HM/Tests/Files/FunctionFileNameUnitTest/plugins/regular-plugin/helpers.php deleted file mode 100644 index e74ae76b..00000000 --- a/HM/Tests/Files/FunctionFileNameUnitTest/plugins/regular-plugin/helpers.php +++ /dev/null @@ -1,5 +0,0 @@ - Date: Fri, 26 Jun 2026 16:27:30 -0400 Subject: [PATCH 7/9] Rename negative test case for clarity --- .../mu-plugins/nested/{nested-plugin.php => improperly-named.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/nested/{nested-plugin.php => improperly-named.php} (100%) diff --git a/HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/nested/nested-plugin.php b/HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/nested/improperly-named.php similarity index 100% rename from HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/nested/nested-plugin.php rename to HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/nested/improperly-named.php From 3d0c7b882efe2f7f8cba622ba563995ca70cdd4c Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 26 Jun 2026 16:32:01 -0400 Subject: [PATCH 8/9] Reword changelog entry for plugin naming rule relaxation --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc8dd1ca..74778fd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ and fill in the contents as you go. This simplifies later release management. -- ## 2.3.0 -- Allow a plugin's entry-point file -- a single-file mu-plugin, or the main file in a plugin folder under `plugins/`, `mu-plugins/`, or `client-mu-plugins/` named `/.php` or `/plugin.php` -- to declare a namespace without being named `namespace.php` #343 +- Broaden relaxation of "must be named namespace.php" check for plugin entrypoint files to allow the patterns `/plugin.php` or `/.php` in both regular and mu-plugin folders. #343 - Do not lint PHP within `build/` and `dist/` build directories #342 ## 2.2.1 From e6a246ac8c2aeb4ac60d07969b3f3b48cee6b060 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 26 Jun 2026 16:39:03 -0400 Subject: [PATCH 9/9] Rename fixtures for consistency and comprehensibility --- HM/Tests/Files/FunctionFileNameUnitTest.php | 2 +- .../mu-plugins/plugin-name/plugin-name.php | 5 +++++ .../mu-plugins/sub-plugin/sub-plugin.php | 5 ----- .../standalone.php => plugin-name/plugin-name.php} | 0 .../plugins/regular-plugin/plugin.php | 5 ----- .../plugins/with-plugin-file/plugin.php | 5 +++++ HM/Tests/Files/NamespaceDirectoryNameUnitTest.php | 3 +-- .../mu-plugins/plugin-name/plugin-name.php | 3 +++ .../mu-plugins/sub-plugin/sub-plugin.php | 3 --- .../standalone.php => plugin-name/plugin-name.php} | 0 .../plugins/regular-plugin/plugin.php | 3 --- .../plugins/with-plugin-file/plugin.php | 3 +++ 12 files changed, 18 insertions(+), 19 deletions(-) create mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/plugin-name/plugin-name.php delete mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/sub-plugin/sub-plugin.php rename HM/Tests/Files/FunctionFileNameUnitTest/plugins/{standalone/standalone.php => plugin-name/plugin-name.php} (100%) delete mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/plugins/regular-plugin/plugin.php create mode 100644 HM/Tests/Files/FunctionFileNameUnitTest/plugins/with-plugin-file/plugin.php create mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/plugin-name/plugin-name.php delete mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/sub-plugin/sub-plugin.php rename HM/Tests/Files/NamespaceDirectoryNameUnitTest/plugins/{standalone/standalone.php => plugin-name/plugin-name.php} (100%) delete mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/plugins/regular-plugin/plugin.php create mode 100644 HM/Tests/Files/NamespaceDirectoryNameUnitTest/plugins/with-plugin-file/plugin.php diff --git a/HM/Tests/Files/FunctionFileNameUnitTest.php b/HM/Tests/Files/FunctionFileNameUnitTest.php index 37412441..3f268376 100644 --- a/HM/Tests/Files/FunctionFileNameUnitTest.php +++ b/HM/Tests/Files/FunctionFileNameUnitTest.php @@ -56,7 +56,7 @@ public function getErrorList() { // as mu-plugins/ -- when named /.php or /plugin.php. 'sub-plugin.php', 'plugin.php', - 'standalone.php', + 'plugin-name.php', ]; if ( in_array( $file, $pass, true ) ) { return []; diff --git a/HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/plugin-name/plugin-name.php b/HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/plugin-name/plugin-name.php new file mode 100644 index 00000000..4f1a4ffa --- /dev/null +++ b/HM/Tests/Files/FunctionFileNameUnitTest/mu-plugins/plugin-name/plugin-name.php @@ -0,0 +1,5 @@ +/.php or /plugin.php. - 'sub-plugin.php', 'plugin.php', - 'standalone.php', + 'plugin-name.php', ]; if ( in_array( $file, $pass, true ) ) { return []; diff --git a/HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/plugin-name/plugin-name.php b/HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/plugin-name/plugin-name.php new file mode 100644 index 00000000..09b061a7 --- /dev/null +++ b/HM/Tests/Files/NamespaceDirectoryNameUnitTest/mu-plugins/plugin-name/plugin-name.php @@ -0,0 +1,3 @@ +