diff --git a/WordPress-Extra/ruleset.xml b/WordPress-Extra/ruleset.xml
index 370dc23a5f..6260d4fabb 100644
--- a/WordPress-Extra/ruleset.xml
+++ b/WordPress-Extra/ruleset.xml
@@ -68,6 +68,7 @@
+
diff --git a/WordPress-VIP/ruleset.xml b/WordPress-VIP/ruleset.xml
index 23c386b6a6..33b75e585b 100644
--- a/WordPress-VIP/ruleset.xml
+++ b/WordPress-VIP/ruleset.xml
@@ -88,4 +88,8 @@
%s() is highly discouraged, please use vip_safe_wp_remote_get() instead.
+
+
+
+
diff --git a/WordPress/Sniff.php b/WordPress/Sniff.php
index 70f763ac9e..a397ec60bd 100644
--- a/WordPress/Sniff.php
+++ b/WordPress/Sniff.php
@@ -2220,4 +2220,62 @@ public function has_html_open_tag( $tag_name, $stackPtr = null, $content = false
return false;
}
+ /**
+ * Check whether a T_CONST token is a class constant declaration.
+ *
+ * @since 0.14.0
+ *
+ * @param int $stackPtr The position in the stack of the T_CONST token to verify.
+ *
+ * @return bool
+ */
+ public function is_class_constant( $stackPtr ) {
+ if ( ! isset( $this->tokens[ $stackPtr ] ) || T_CONST !== $this->tokens[ $stackPtr ]['code'] ) {
+ return false;
+ }
+
+ // Note: traits can not declare constants.
+ $valid_scopes = array(
+ 'T_CLASS' => true,
+ 'T_ANON_CLASS' => true,
+ 'T_INTERFACE' => true,
+ );
+
+ return $this->valid_direct_scope( $stackPtr, $valid_scopes );
+ }
+
+ /**
+ * Check whether the direct wrapping scope of a token is within a limited set of
+ * acceptable tokens.
+ *
+ * Used to check, for instance, if a T_CONST is a class constant.
+ *
+ * @since 0.14.0
+ *
+ * @param int $stackPtr The position in the stack of the token to verify.
+ * @param array $valid_scopes Array of token types.
+ * Keys should be the token types in string format
+ * to allow for newer token types.
+ * Value is irrelevant.
+ *
+ * @return bool
+ */
+ protected function valid_direct_scope( $stackPtr, array $valid_scopes ) {
+ if ( empty( $this->tokens[ $stackPtr ]['conditions'] ) ) {
+ return false;
+ }
+
+ /*
+ * Check only the direct wrapping scope of the token.
+ */
+ $conditions = array_keys( $this->tokens[ $stackPtr ]['conditions'] );
+ $ptr = array_pop( $conditions );
+
+ if ( ! isset( $this->tokens[ $ptr ] ) ) {
+ return false;
+ }
+
+ return isset( $valid_scopes[ $this->tokens[ $ptr ]['type'] ] );
+ }
+
}
diff --git a/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php b/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php
new file mode 100644
index 0000000000..7e47b042ea
--- /dev/null
+++ b/WordPress/Sniffs/WP/DiscouragedConstantsSniff.php
@@ -0,0 +1,221 @@
+ 'get_stylesheet_directory()',
+ 'TEMPLATEPATH' => 'get_template_directory()',
+ 'PLUGINDIR' => 'WP_PLUGIN_DIR',
+ 'MUPLUGINDIR' => 'WPMU_PLUGIN_DIR',
+ 'HEADER_IMAGE' => 'add_theme_support( \'custom-header\' )',
+ 'NO_HEADER_TEXT' => 'add_theme_support( \'custom-header\' )',
+ 'HEADER_TEXTCOLOR' => 'add_theme_support( \'custom-header\' )',
+ 'HEADER_IMAGE_WIDTH' => 'add_theme_support( \'custom-header\' )',
+ 'HEADER_IMAGE_HEIGHT' => 'add_theme_support( \'custom-header\' )',
+ 'BACKGROUND_COLOR' => 'add_theme_support( \'custom-background\' )',
+ 'BACKGROUND_IMAGE' => 'add_theme_support( \'custom-background\' )',
+ );
+
+ /**
+ * Array of functions to check.
+ *
+ * @since 0.14.0
+ *
+ * @var array =>
+ */
+ protected $target_functions = array(
+ 'define' => 1,
+ );
+
+ /**
+ * Array of tokens which if found preceding the $stackPtr indicate that a T_STRING is not a constant.
+ *
+ * @var array
+ */
+ private $preceding_tokens_to_ignore = array(
+ T_NAMESPACE => true,
+ T_USE => true,
+ T_CLASS => true,
+ T_TRAIT => true,
+ T_INTERFACE => true,
+ T_EXTENDS => true,
+ T_IMPLEMENTS => true,
+ T_NEW => true,
+ T_FUNCTION => true,
+ T_DOUBLE_COLON => true,
+ T_OBJECT_OPERATOR => true,
+ T_INSTANCEOF => true,
+ T_GOTO => true,
+
+ );
+
+ /**
+ * Processes this test, when one of its tokens is encountered.
+ *
+ * @since 0.14.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.
+ */
+ public function process_token( $stackPtr ) {
+ if ( isset( $this->target_functions[ strtolower( $this->tokens[ $stackPtr ]['content'] ) ] ) ) {
+ // Disallow excluding function groups for this sniff.
+ $this->exclude = '';
+
+ return parent::process_token( $stackPtr );
+
+ } else {
+ return $this->process_arbitrary_tstring( $stackPtr );
+ }
+ }
+
+ /**
+ * Process an arbitrary T_STRING token to determine whether it is one of the target constants.
+ *
+ * @since 0.14.0
+ *
+ * @param int $stackPtr The position of the current token in the stack.
+ *
+ * @return void
+ */
+ public function process_arbitrary_tstring( $stackPtr ) {
+ $content = $this->tokens[ $stackPtr ]['content'];
+
+ if ( ! isset( $this->discouraged_constants[ $content ] ) ) {
+ return;
+ }
+
+ $next = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $stackPtr + 1 ), null, true );
+ if ( false !== $next && T_OPEN_PARENTHESIS === $this->tokens[ $next ]['code'] ) {
+ // Function call or declaration.
+ return;
+ }
+
+ $prev = $this->phpcsFile->findPrevious( Tokens::$emptyTokens, ( $stackPtr - 1 ), null, true );
+ if ( false !== $prev && isset( $this->preceding_tokens_to_ignore[ $this->tokens[ $prev ]['code'] ] ) ) {
+ // Not the use of a constant.
+ return;
+ }
+
+ if ( false !== $prev
+ && T_NS_SEPARATOR === $this->tokens[ $prev ]['code']
+ && T_STRING === $this->tokens[ ( $prev - 1 ) ]['code']
+ ) {
+ // Namespaced constant of the same name.
+ return;
+ }
+
+ if ( false !== $prev
+ && T_CONST === $this->tokens[ $prev ]['code']
+ && true === $this->is_class_constant( $prev )
+ ) {
+ // Class constant of the same name.
+ return;
+ }
+
+ /*
+ * Deal with a number of variations of use statements.
+ */
+ for ( $i = $stackPtr; $i > 0; $i-- ) {
+ if ( $this->tokens[ $i ]['line'] !== $this->tokens[ $stackPtr ]['line'] ) {
+ break;
+ }
+ }
+
+ $first_on_line = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $i + 1 ), null, true );
+ if ( false !== $first_on_line && T_USE === $this->tokens[ $first_on_line ]['code'] ) {
+ $next_on_line = $this->phpcsFile->findNext( Tokens::$emptyTokens, ( $first_on_line + 1 ), null, true );
+ if ( false !== $next_on_line ) {
+ if ( ( T_STRING === $this->tokens[ $next_on_line ]['code']
+ && 'const' === $this->tokens[ $next_on_line ]['content'] )
+ || T_CONST === $this->tokens[ $next_on_line ]['code'] // Happens in some PHPCS versions.
+ ) {
+ $has_ns_sep = $this->phpcsFile->findNext( T_NS_SEPARATOR, ( $next_on_line + 1 ), $stackPtr );
+ if ( false !== $has_ns_sep ) {
+ // Namespaced const (group) use statement.
+ return;
+ }
+ } else {
+ // Not a const use statement.
+ return;
+ }
+ }
+ }
+
+ // Ok, this is really one of the discouraged constants.
+ $this->phpcsFile->addWarning(
+ 'Found usage of constant "%s". Use %s instead.',
+ $stackPtr,
+ 'UsageFound',
+ array(
+ $content,
+ $this->discouraged_constants[ $content ],
+ )
+ );
+ }
+
+ /**
+ * Process the parameters of a matched `define` function call.
+ *
+ * @since 0.14.0
+ *
+ * @param int $stackPtr The position of the current token in the stack.
+ * @param array $group_name The name of the group which was matched.
+ * @param string $matched_content The token content (function name) which was matched.
+ * @param array $parameters Array with information about the parameters.
+ *
+ * @return void
+ */
+ public function process_parameters( $stackPtr, $group_name, $matched_content, $parameters ) {
+ $function_name = strtolower( $matched_content );
+ $target_param = $this->target_functions[ $function_name ];
+
+ // Was the target parameter passed ?
+ if ( ! isset( $parameters[ $target_param ] ) ) {
+ return;
+ }
+
+ $raw_content = $this->strip_quotes( $parameters[ $target_param ]['raw'] );
+
+ if ( isset( $this->discouraged_constants[ $raw_content ] ) ) {
+ $this->phpcsFile->addWarning(
+ 'Found declaration of constant "%s". Use %s instead.',
+ $stackPtr,
+ 'DeclarationFound',
+ array(
+ $raw_content,
+ $this->discouraged_constants[ $raw_content ],
+ )
+ );
+ }
+ }
+
+} // End class.
diff --git a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc
new file mode 100644
index 0000000000..687705e7fc
--- /dev/null
+++ b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.inc
@@ -0,0 +1,72 @@
+STYLESHEETPATH;
+use const SomeNamespace\STYLESHEETPATH as SSP; // PHP 5.6+
+use const SomeNamespace\{STYLESHEETPATH, TEMPLATEPATH}; // PHP 7.0+
+define( 'My\STYLESHEETPATH', 'something' );
+
+// Ok, not usage of the constant as such.
+if ( defined( 'STYLESHEETPATH' ) ) { // Ok.
+ // Do something unrelated.
+}
+
+
+/*
+ * These are all bad.
+ */
+echo STYLESHEETPATH;
+echo \STYLESHEETPATH; // Global constant.
+$folder = basename( TEMPLATEPATH );
+include PLUGINDIR . '/js/myfile.js';
+echo MUPLUGINDIR;
+echo HEADER_IMAGE;
+echo NO_HEADER_TEXT;
+echo HEADER_TEXTCOLOR;
+echo HEADER_IMAGE_WIDTH;
+echo HEADER_IMAGE_HEIGHT;
+echo BACKGROUND_COLOR;
+echo BACKGROUND_IMAGE;
+
+use const STYLESHEETPATH as SSP;
+use const ABC as STYLESHEETPATH;
+
+switch( STYLESHEETPATH ) {
+ case STYLESHEETPATH:
+ break;
+}
+
+define( 'STYLESHEETPATH', 'something' );
+const STYLESHEETPATH = 'something';
diff --git a/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php
new file mode 100644
index 0000000000..3e60ba4bc2
--- /dev/null
+++ b/WordPress/Tests/WP/DiscouragedConstantsUnitTest.php
@@ -0,0 +1,59 @@
+ =>
+ */
+ public function getErrorList() {
+ return array();
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @return array =>
+ */
+ public function getWarningList() {
+ return array(
+ 50 => 1,
+ 51 => 1,
+ 52 => 1,
+ 53 => 1,
+ 54 => 1,
+ 55 => 1,
+ 56 => 1,
+ 57 => 1,
+ 58 => 1,
+ 59 => 1,
+ 60 => 1,
+ 61 => 1,
+ 63 => 1,
+ 64 => 1,
+ 66 => 1,
+ 67 => 1,
+ 71 => 1,
+ 72 => 1,
+ );
+ }
+
+} // End class.