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

Feature/prefix globals warning #208

Merged
merged 1 commit into from
May 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
188 changes: 188 additions & 0 deletions WPThemeReview/Sniffs/CoreFunctionality/PrefixAllGlobalsSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<?php
/**
* WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/

namespace WPThemeReview\Sniffs\CoreFunctionality;

use WordPressCS\WordPress\Sniffs\NamingConventions\PrefixAllGlobalsSniff as WPCSPrefixAllGlobalsSniff;

/**
* Verify that everything defined in the global namespace is prefixed with a theme specific prefix.
*
* This sniff extends the upstream WPCS PrefixAllGlobalsSniff. The differences are:
* - For non-prefixed global variables, an error will only be thrown when the variable
* is created outside of a theme template file.
*
* @link https://github.com/WPTRT/WPThemeReview/issues/205
* @link https://github.com/WPTRT/WPThemeReview/issues/201
* @link https://github.com/WPTRT/WPThemeReview/issues/200
*
* The sniff does not currently allow for mimetype sublevel only file names,
* such as `plain.php`.
*
* @since 0.2.0
*/
class PrefixAllGlobalsSniff extends WPCSPrefixAllGlobalsSniff {

/**
* Regex to recognize more complex typical theme template file names.
*
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#single-post
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#custom-taxonomies
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#custom-post-types
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#embeds
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/#attachment
* @link https://developer.wordpress.org/themes/template-files-section/partial-and-miscellaneous-template-files/#content-slug-php
* @link https://wphierarchy.com/
* @link https://en.wikipedia.org/wiki/Media_type#Naming
*
* @since 0.2.0
*
* @var string
*/
const COMPLEX_THEME_TEMPLATE_NAME_REGEX = '`
^ # Anchor to the beginning of the string.
(?:
# Template file prefixes with subtype.
(?:archive|author|category|content|embed|page|single|tag|taxonomy)
-[^\.]+ # These need to be followed by a dash and some chars.
|
(?:application|audio|example|font|image|message|model|multipart|text|video) #Top-level mime-types
(?:_[^\.]+)? # Optionally followed by an underscore and a sub-type.
)\.php$ # End in .php and anchor to the end of the string.
`Dx';

/**
* The list of allowed folders to check the file path against.
*
* The WPThemereview standards contains a base set for this property in the ruleset.xml.
* This array can be extended in a custom ruleset.
*
* @since 0.2.0
*
* @var array
*/
public $allowed_folders = [];

/**
* List of plain template file names.
*
* @link https://developer.wordpress.org/themes/basics/template-hierarchy/
* @link https://developer.wordpress.org/themes/template-files-section/partial-and-miscellaneous-template-files/#content-slug-php
* @link https://wphierarchy.com/
* @link https://en.wikipedia.org/wiki/Media_type#Naming
*
* @since 0.2.0
*
* @var array
*/
protected $simple_theme_template_file_names = [
// Plain primary template file names.
'404.php' => true,
'archive.php' => true,
'home.php' => true,
'index.php' => true,
'page.php' => true,
'search.php' => true,
'single.php' => true,
'singular.php' => true,

// Plain secondary template file names.
'attachment.php' => true,
'author.php' => true,
'category.php' => true,
'date.php' => true,
'embed.php' => true,
'front-page.php' => true,
'single-post.php' => true,
'tag.php' => true,
'taxonomy.php' => true,

// Plain partial and miscellaneous template file names.
'comments.php' => true,
'footer.php' => true,
'header.php' => true,
'sidebar.php' => true,

// Top-leve mime types.
'application.php' => true,
'audio.php' => true,
'example.php' => true,
'font.php' => true,
'image.php' => true,
'message.php' => true,
'model.php' => true,
'multipart.php' => true,
'text.php' => true,
'video.php' => true,
];

/**
* Check that defined global variables are prefixed.
*
* This overloads the parent method to allow for non-prefixed variables to be declared
* in template files as those are included from within a function and therefore would be
* local to that function.
*
* @since 0.2.0
*
* @param int $stackPtr The position of the current token in the stack.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
protected function process_variable_assignment( $stackPtr ) {

// Usage of `strip_quotes` is to ensure `stdin_path` passed by IDEs does not include quotes.
$file = $this->strip_quotes( $this->phpcsFile->getFileName() );

$fileName = basename( $file );

if ( \defined( '\PHP_CODESNIFFER_IN_TESTS' ) ) {
$fileName = str_replace( '.inc', '.php', $fileName );
}

// Don't process in case of a template file or folder.
if ( isset( $this->simple_theme_template_file_names[ $fileName ] ) === true ) {
return;
}

if ( preg_match( self::COMPLEX_THEME_TEMPLATE_NAME_REGEX, $fileName ) === 1 ) {
return;
}

if ( $this->is_from_allowed_folder( $file ) ) {
return;
}

// Not a typical template file name, defer to the prefix checking in the parent sniff.
return parent::process_variable_assignment( $stackPtr );
}

/**
* Checks if the given file path is located in the $allowed_folders array.
*
* @since 0.2.0
*
* @param string $path Full path of the sniffed file.
* @return boolean
*/
private function is_from_allowed_folder( $path ) {
if ( empty( $this->allowed_folders ) || ! is_array( $this->allowed_folders ) ) {
return false;
}

foreach ( $this->allowed_folders as $folder ) {
jrfnl marked this conversation as resolved.
Show resolved Hide resolved
if ( strrpos( $path, DIRECTORY_SEPARATOR . $folder . DIRECTORY_SEPARATOR ) !== false ) {
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[] my_theme
<?php

$my_theme_var = 123; // OK, prefixed.
$var = 'Value'; // OK, template file.

// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[] my_theme
<?php

$my_theme_var = 123; // OK, prefixed.
$var = 'Value'; // Error. Not in a template file.

// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[] my_theme
<?php

$my_theme_var = 123; // OK, prefixed.
$var = 'Value'; // OK, template file.

// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[] my_theme
<?php

$my_theme_var = 123; // OK, prefixed.
$var = 'Value'; // OK, template file.

// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[] my_theme
// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals allowed_folders[] template-parts,templates,partials,page-templates
<?php
$my_theme_var = 123; // OK, prefixed.
$var = 'Value'; // OK, file in allowed folder.

// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[]
// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals allowed_folders[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[] my_theme
// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals allowed_folders[] template-parts,templates,partials,page-templates
<?php
$my_theme_var = 123; // OK, prefixed.
$var = 'Value'; // OK, file in allowed folder.

// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[]
// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals allowed_folders[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[] my_theme
<?php

$my_theme_var = 123; // OK, prefixed.
$var = 'Value'; // Error. Not in a template file.

// phpcs:set WPThemeReview.CoreFunctionality.PrefixAllGlobals prefixes[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
// Dummy file. The real test files are in the PrefixAllGlobalsTests subfolder.
69 changes: 69 additions & 0 deletions WPThemeReview/Tests/CoreFunctionality/PrefixAllGlobalsUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php
/**
* Unit test class for WPThemeReview Coding Standard.
*
* @package WPTRT\WPThemeReview
* @link https://github.com/WPTRT/WPThemeReview
* @license https://opensource.org/licenses/MIT MIT
*/

namespace WPThemeReview\Tests\CoreFunctionality;

use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;

/**
* Unit test class for the PrefixAllGlobals sniff.
*
* @package WPTRT\WPThemeReview
*
* @since 0.2.0
*/
class PrefixAllGlobalsUnitTest extends AbstractSniffUnitTest {

/**
* Get a list of all test files to check.
*
* @param string $testFileBase The base path that the unit tests files will have.
*
* @return string[]
*/
protected function getTestFiles( $testFileBase ) {
$sep = \DIRECTORY_SEPARATOR;
$test_files = glob( dirname( $testFileBase ) . $sep . 'PrefixAllGlobalsTests{' . $sep . ',' . $sep . '*' . $sep . '}*.inc', \GLOB_BRACE );

if ( ! empty( $test_files ) ) {
return $test_files;
}

return array( $testFileBase . '.inc' );
}

/**
* Returns the lines where errors should occur.
*
* @param string $testFile The name of the file being tested.
* @return array <int line number> => <int number of errors>
*/
public function getErrorList( $testFile = '' ) {
switch ( $testFile ) {
case 'footer_widgets.inc':
case 'social-share.inc':
return array(
5 => 1,
);

default:
return array();
}
}

/**
* Returns the lines where warnings should occur.
*
* @return array <int line number> => <int number of warnings>
*/
public function getWarningList() {
return array();
}

}
13 changes: 11 additions & 2 deletions WPThemeReview/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,17 @@
<!-- Verify that everything in the global namespace is prefixed. -->
<!-- Covers: https://make.wordpress.org/themes/handbook/review/required/#code - last bullet. -->
<!-- NOTE: this sniff needs a custom property to be set for it to be activated. -->
<!-- See: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#naming-conventions-prefix-everything-in-the-global-namespace-->
<rule ref="WordPress.NamingConventions.PrefixAllGlobals"/>
<!-- See: https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties#naming-conventions-prefix-everything-in-the-global-namespace -->
<rule ref="WPThemeReview.CoreFunctionality.PrefixAllGlobals">
<properties>
<property name="allowed_folders" type="array">
<element value="template-parts"/>
<element value="templates"/>
<element value="partials"/>
<element value="page-templates"/>
</property>
</properties>
</rule>

<!-- Check for correct spelling of WordPress. -->
<!-- Covers: https://make.wordpress.org/themes/handbook/review/required/#naming - third bullet. -->
Expand Down