diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 4008efa1b4d4e..a324657fad6f7 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -82,6 +82,7 @@
/packages/scripts @gziolo @ntwb @nerrad @ajitbohra @ryanwelcher
/packages/stylelint-config @ntwb
/test/e2e @kevin940726 @Mamaduka
+/test/php/gutenberg-coding-standards @anton-vlasenko
# UI Components
/packages/components @ajitbohra
diff --git a/composer.json b/composer.json
index 3f16ba495a94c..134e366befdb9 100644
--- a/composer.json
+++ b/composer.json
@@ -32,8 +32,18 @@
"wp-coding-standards/wpcs": "^2.2",
"sirbrillig/phpcs-variable-analysis": "^2.8",
"spatie/phpunit-watcher": "^1.23",
- "yoast/phpunit-polyfills": "^1.0"
+ "yoast/phpunit-polyfills": "^1.0",
+ "gutenberg/gutenberg-coding-standards": "@dev"
},
+ "repositories": [
+ {
+ "type": "path",
+ "url": "./test/php/gutenberg-coding-standards",
+ "options": {
+ "symlink": false
+ }
+ }
+ ],
"require": {
"composer/installers": "~1.0"
},
diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php
index 816f7e414ad79..41120a882ed23 100644
--- a/lib/class-wp-duotone-gutenberg.php
+++ b/lib/class-wp-duotone-gutenberg.php
@@ -32,6 +32,10 @@
* @since 6.3.0
*/
+if ( class_exists( 'WP_Duotone_Gutenberg' ) ) {
+ return;
+}
+
/**
* Manages duotone block supports and global styles.
*
diff --git a/lib/class-wp-theme-json-data-gutenberg.php b/lib/class-wp-theme-json-data-gutenberg.php
index 2e5ea474346a9..db0737ebea08b 100644
--- a/lib/class-wp-theme-json-data-gutenberg.php
+++ b/lib/class-wp-theme-json-data-gutenberg.php
@@ -6,6 +6,10 @@
* @since 6.1.0
*/
+if ( class_exists( 'WP_Theme_JSON_Data_Gutenberg' ) ) {
+ return;
+}
+
/**
* Class to provide access to update a theme.json structure.
*/
diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php
index 0745ee06b84a2..60e69632eedb6 100644
--- a/lib/class-wp-theme-json-gutenberg.php
+++ b/lib/class-wp-theme-json-gutenberg.php
@@ -6,6 +6,10 @@
* @since 5.8.0
*/
+if ( class_exists( 'WP_Theme_JSON_Gutenberg' ) ) {
+ return;
+}
+
/**
* Class that encapsulates the processing of structures that adhere to the theme.json spec.
*
diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php
index 1e825e3c6bbe4..39721742946cd 100644
--- a/lib/class-wp-theme-json-resolver-gutenberg.php
+++ b/lib/class-wp-theme-json-resolver-gutenberg.php
@@ -6,6 +6,10 @@
* @since 5.8.0
*/
+if ( class_exists( 'WP_Theme_JSON_Resolver_Gutenberg' ) ) {
+ return;
+}
+
/**
* Class that abstracts the processing of the different data sources
* for site-level config and offers an API to work with them.
diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-attribute-token.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-attribute-token.php
index 2c52164a979f0..cc03c1441ee04 100644
--- a/lib/compat/wordpress-6.2/html-api/class-wp-html-attribute-token.php
+++ b/lib/compat/wordpress-6.2/html-api/class-wp-html-attribute-token.php
@@ -7,6 +7,10 @@
* @since 6.2.0
*/
+if ( class_exists( 'WP_HTML_Attribute_Token' ) ) {
+ return;
+}
+
/**
* Data structure for the attribute token that allows to drastically improve performance.
*
diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-span.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-span.php
index d92778cd3a222..e38bc55192317 100644
--- a/lib/compat/wordpress-6.2/html-api/class-wp-html-span.php
+++ b/lib/compat/wordpress-6.2/html-api/class-wp-html-span.php
@@ -7,6 +7,10 @@
* @since 6.2.0
*/
+if ( class_exists( 'WP_HTML_Span' ) ) {
+ return;
+}
+
/**
* Represents a textual span inside an HTML document.
*
diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php
index 7edb67f9f0423..d61180074f608 100644
--- a/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php
+++ b/lib/compat/wordpress-6.2/html-api/class-wp-html-tag-processor.php
@@ -26,6 +26,10 @@
* @since 6.2.0
*/
+if ( class_exists( 'WP_HTML_Tag_Processor' ) ) {
+ return;
+}
+
/**
* Modifies attributes in an HTML document for tags matching a query.
*
diff --git a/lib/compat/wordpress-6.2/html-api/class-wp-html-text-replacement.php b/lib/compat/wordpress-6.2/html-api/class-wp-html-text-replacement.php
index 912b4a56a5eb4..b3f70c8e7c57f 100644
--- a/lib/compat/wordpress-6.2/html-api/class-wp-html-text-replacement.php
+++ b/lib/compat/wordpress-6.2/html-api/class-wp-html-text-replacement.php
@@ -7,6 +7,10 @@
* @since 6.2.0
*/
+if ( class_exists( 'WP_HTML_Text_Replacement' ) ) {
+ return;
+}
+
/**
* Data structure used to replace existing content from start to end that allows to drastically improve performance.
*
diff --git a/lib/compat/wordpress-6.2/rest-api.php b/lib/compat/wordpress-6.2/rest-api.php
index c0f098fa6893a..97f7daecdff2f 100644
--- a/lib/compat/wordpress-6.2/rest-api.php
+++ b/lib/compat/wordpress-6.2/rest-api.php
@@ -91,43 +91,46 @@ function gutenberg_modify_rest_sidebars_response( $response ) {
}
add_filter( 'rest_prepare_sidebar', 'gutenberg_modify_rest_sidebars_response' );
-
-/**
- * Add the `block_types` value to the `pattern-directory-item` schema.
- *
- * @since 6.2.0 Added 'block_types' property.
- */
-function add_block_pattern_block_types_schema() {
- register_rest_field(
- 'pattern-directory-item',
- 'block_types',
- array(
- 'schema' => array(
- 'description' => __( 'The block types which can use this pattern.', 'gutenberg' ),
- 'type' => 'array',
- 'uniqueItems' => true,
- 'items' => array( 'type' => 'string' ),
- 'context' => array( 'view', 'embed' ),
- ),
- )
- );
+if ( ! function_exists( 'add_block_pattern_block_types_schema' ) ) {
+ /**
+ * Add the `block_types` value to the `pattern-directory-item` schema.
+ *
+ * @since 6.2.0 Added 'block_types' property.
+ */
+ function add_block_pattern_block_types_schema() {
+ register_rest_field(
+ 'pattern-directory-item',
+ 'block_types',
+ array(
+ 'schema' => array(
+ 'description' => __( 'The block types which can use this pattern.', 'gutenberg' ),
+ 'type' => 'array',
+ 'uniqueItems' => true,
+ 'items' => array( 'type' => 'string' ),
+ 'context' => array( 'view', 'embed' ),
+ ),
+ )
+ );
+ }
}
add_filter( 'rest_api_init', 'add_block_pattern_block_types_schema' );
-/**
- * Add the `block_types` value into the API response.
- *
- * @since 6.2.0 Added 'block_types' property.
- *
- * @param WP_REST_Response $response The response object.
- * @param object $raw_pattern The unprepared pattern.
- */
-function filter_block_pattern_response( $response, $raw_pattern ) {
- $data = $response->get_data();
- $data['block_types'] = array_map( 'sanitize_text_field', $raw_pattern->meta->wpop_block_types );
- $response->set_data( $data );
- return $response;
+if ( ! function_exists( 'filter_block_pattern_response' ) ) {
+ /**
+ * Add the `block_types` value into the API response.
+ *
+ * @since 6.2.0 Added 'block_types' property.
+ *
+ * @param WP_REST_Response $response The response object.
+ * @param object $raw_pattern The unprepared pattern.
+ */
+ function filter_block_pattern_response( $response, $raw_pattern ) {
+ $data = $response->get_data();
+ $data['block_types'] = array_map( 'sanitize_text_field', $raw_pattern->meta->wpop_block_types );
+ $response->set_data( $data );
+ return $response;
+ }
}
add_filter( 'rest_prepare_block_pattern', 'filter_block_pattern_response', 10, 2 );
diff --git a/lib/compat/wordpress-6.3/kses.php b/lib/compat/wordpress-6.3/kses.php
index 23eee580f831a..b0b7356d2dac1 100644
--- a/lib/compat/wordpress-6.3/kses.php
+++ b/lib/compat/wordpress-6.3/kses.php
@@ -7,21 +7,23 @@
* @package gutenberg
*/
-/**
- * Mark CSS safe if it contains grid functions
- *
- * This function should not be backported to core.
- *
- * @param bool $allow_css Whether the CSS is allowed.
- * @param string $css_test_string The CSS to test.
- */
-function allow_grid_functions_in_styles( $allow_css, $css_test_string ) {
- if ( preg_match(
- '/^grid-template-columns:\s*repeat\([0-9,a-z-\s\(\)]*\)$/',
- $css_test_string
- ) ) {
- return true;
+if ( ! function_exists( 'allow_grid_functions_in_styles' ) ) {
+ /**
+ * Mark CSS safe if it contains grid functions
+ *
+ * This function should not be backported to core.
+ *
+ * @param bool $allow_css Whether the CSS is allowed.
+ * @param string $css_test_string The CSS to test.
+ */
+ function allow_grid_functions_in_styles( $allow_css, $css_test_string ) {
+ if ( preg_match(
+ '/^grid-template-columns:\s*repeat\([0-9,a-z-\s\(\)]*\)$/',
+ $css_test_string
+ ) ) {
+ return true;
+ }
+ return $allow_css;
}
- return $allow_css;
}
add_filter( 'safecss_filter_attr_allow_css', 'allow_grid_functions_in_styles', 10, 2 );
diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php
index 90898c0b71e24..ecb8f52392fef 100644
--- a/lib/compat/wordpress-6.3/rest-api.php
+++ b/lib/compat/wordpress-6.3/rest-api.php
@@ -52,12 +52,13 @@ function gutenberg_update_templates_template_parts_rest_controller( $args, $post
}
add_filter( 'register_post_type_args', 'gutenberg_update_templates_template_parts_rest_controller', 10, 2 );
-/**
- * Add the `modified` value to the `wp_template` schema.
- *
- * @since 6.3.0 Added 'modified' property and response value.
- */
-function add_modified_wp_template_schema() {
+if ( ! function_exists( 'add_modified_wp_template_schema' ) ) {
+ /**
+ * Add the `modified` value to the `wp_template` schema.
+ *
+ * @since 6.3.0 Added 'modified' property and response value.
+ */
+ function add_modified_wp_template_schema() {
register_rest_field(
array( 'wp_template', 'wp_template_part' ),
'modified',
@@ -80,6 +81,7 @@ function add_modified_wp_template_schema() {
},
)
);
+ }
}
add_filter( 'rest_api_init', 'add_modified_wp_template_schema' );
diff --git a/lib/compat/wordpress-6.3/theme-previews.php b/lib/compat/wordpress-6.3/theme-previews.php
index 26153d74878b5..e885d389b94fe 100644
--- a/lib/compat/wordpress-6.3/theme-previews.php
+++ b/lib/compat/wordpress-6.3/theme-previews.php
@@ -51,14 +51,15 @@ function gutenberg_attach_theme_preview_middleware() {
);
}
-/**
- * Temporary function to add a live preview button to block themes.
- * Remove when https://core.trac.wordpress.org/ticket/58190 lands.
- */
-function add_live_preview_button() {
- global $pagenow;
- if ( 'themes.php' === $pagenow ) {
- ?>
+if ( ! function_exists( 'add_live_preview_button' ) ) {
+ /**
+ * Temporary function to add a live preview button to block themes.
+ * Remove when https://core.trac.wordpress.org/ticket/58190 lands.
+ */
+ function add_live_preview_button() {
+ global $pagenow;
+ if ( 'themes.php' === $pagenow ) {
+ ?>
-
+if ( ! function_exists( 'block_theme_activate_nonce' ) ) {
+ /**
+ * Adds a nonce for the theme activation link.
+ */
+ function block_theme_activate_nonce() {
+ $nonce_handle = 'switch-theme_' . gutenberg_get_theme_preview_path();
+ ?>
-
-
-
-
-
-
-
+
+
+
+
+
+
./vendor/*
+ ./test/php/gutenberg-coding-standards/*
@@ -109,4 +110,19 @@
/phpunit/*
+
+
+
+ ./phpunit/*
+ ./packages/*
+ ./bin/generate-gutenberg-php
+
+
+
+
+
+
+
+
+
diff --git a/test/php/gutenberg-coding-standards/.gitignore b/test/php/gutenberg-coding-standards/.gitignore
new file mode 100644
index 0000000000000..bfec4c3c303b1
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/.gitignore
@@ -0,0 +1,5 @@
+vendor
+composer.lock
+phpunit.xml
+phpcs.xml
+.phpcs.xml
diff --git a/test/php/gutenberg-coding-standards/.phpcs.xml.dist b/test/php/gutenberg-coding-standards/.phpcs.xml.dist
new file mode 100644
index 0000000000000..d6862f9a00fa9
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/.phpcs.xml.dist
@@ -0,0 +1,88 @@
+
+
+
+ The Coding standard for the Gutenberg Coding Standards itself.
+
+
+
+ .
+
+
+ */vendor/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/CodeAnalysis/GuardedFunctionAndClassNamesSniff.php b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/CodeAnalysis/GuardedFunctionAndClassNamesSniff.php
new file mode 100644
index 0000000000000..65c42071192b4
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Sniffs/CodeAnalysis/GuardedFunctionAndClassNamesSniff.php
@@ -0,0 +1,221 @@
+onRegisterEvent();
+
+ return array( T_FUNCTION, T_CLASS );
+ }
+
+ /**
+ * Processes function and class 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'] ) {
+ $this->processFunctionToken( $phpcsFile, $stackPtr );
+ return;
+ }
+
+ if ( 'T_CLASS' === $token['type'] ) {
+ $this->processClassToken( $phpcsFile, $stackPtr );
+ }
+ }
+
+ /**
+ * Functions should be wrapped with !function_exists() to avoid fatal errors.
+ * E.g.:
+ * if ( ! function_exists( 'wp_get_navigation' ) ) {
+ * function wp_get_navigation( $slug ) { ... }
+ * }
+ *
+ * @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 ) {
+ $tokens = $phpcsFile->getTokens();
+ $functionToken = $phpcsFile->findNext( T_STRING, $stackPointer );
+
+ $wrappingTokensToCheck = array(
+ T_CLASS,
+ T_INTERFACE,
+ T_TRAIT,
+ );
+
+ foreach ( $wrappingTokensToCheck as $wrappingTokenToCheck ) {
+ if ( false !== $phpcsFile->getCondition( $functionToken, $wrappingTokenToCheck, false ) ) {
+ // This sniff only processes functions, not class methods.
+ return;
+ }
+ }
+
+ $name = $tokens[ $functionToken ]['content'];
+ foreach ( $this->functionsWhiteList as $functionRegExp ) {
+ if ( preg_match( $functionRegExp, $name ) ) {
+ // Ignore whitelisted function names.
+ return;
+ }
+ }
+
+ $errorMessage = sprintf( 'The "%s()" function should be guarded against redeclaration.', $name );
+
+ $wrappingIfToken = $phpcsFile->getCondition( $functionToken, T_IF, false );
+ if ( false === $wrappingIfToken ) {
+ $phpcsFile->addError( $errorMessage, $functionToken, 'FunctionNotGuardedAgainstRedeclaration' );
+
+ return;
+ }
+
+ $content = $phpcsFile->getTokensAsString( $wrappingIfToken, $functionToken - $wrappingIfToken );
+
+ $regexp = sprintf( '/if\s*\(\s*!\s*function_exists\s*\(\s*(\'|")%s(\'|")/', preg_quote( $name, '/' ) );
+ $result = preg_match( $regexp, $content );
+ if ( 1 !== $result ) {
+ $phpcsFile->addError( $errorMessage, $functionToken, 'FunctionNotGuardedAgainstRedeclaration' );
+ }
+ }
+
+ /**
+ * Classes should be wrapped with !function_exists() to avoid fatal errors.
+ * E.g.:
+ * if ( class_exists( 'WP_Navigation' ) ) {
+ * return;
+ * }
+ *
+ * Alternatively:
+ *
+ * if ( ! class_exists( 'WP_Navigation' ) ) {
+ * class WP_Navigation { ... }
+ * }
+ *
+ * @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 processClassToken( File $phpcsFile, $stackPointer ) {
+ $tokens = $phpcsFile->getTokens();
+ $classToken = $phpcsFile->findNext( T_STRING, $stackPointer );
+ $className = $tokens[ $classToken ]['content'];
+
+ foreach ( $this->classesWhiteList as $classnameRegExp ) {
+ if ( preg_match( $classnameRegExp, $className ) ) {
+ // Ignore whitelisted class names.
+ return;
+ }
+ }
+
+ $errorMessage = sprintf( 'The "%s" class should be guarded against redeclaration.', $className );
+
+ $wrappingIfToken = $phpcsFile->getCondition( $classToken, T_IF, false );
+ if ( false !== $wrappingIfToken ) {
+ $endOfWrappingIfToken = $phpcsFile->findEndOfStatement( $wrappingIfToken );
+ $content = $phpcsFile->getTokensAsString( $wrappingIfToken, $endOfWrappingIfToken - $wrappingIfToken );
+ $regexp = sprintf( '/if\s*\(\s*!\s*class_exists\s*\(\s*(\'|")%s(\'|")/', preg_quote( $className, '/' ) );
+ $result = preg_match( $regexp, $content );
+ if ( 1 === $result ) {
+ return;
+ }
+ }
+
+ $previousIfToken = $phpcsFile->findPrevious( T_IF, $classToken );
+ if ( false === $previousIfToken ) {
+ $phpcsFile->addError( $errorMessage, $classToken, 'ClassNotGuardedAgainstRedeclaration' );
+
+ return;
+ }
+
+ $endOfPreviousIfToken = $phpcsFile->findEndOfStatement( $previousIfToken );
+ $content = $phpcsFile->getTokensAsString( $previousIfToken, $endOfPreviousIfToken - $previousIfToken );
+ $regexp = sprintf( '/if\s*\(\s*class_exists\s*\(\s*(\'|")%s(\'|")/', preg_quote( $className, '/' ) );
+ $result = preg_match( $regexp, $content );
+
+ if ( 1 === $result ) {
+ $returnToken = $phpcsFile->findNext( T_RETURN, $previousIfToken, $endOfPreviousIfToken );
+ if ( false !== $returnToken ) {
+ return;
+ }
+ }
+
+ $phpcsFile->addError( $errorMessage, $classToken, 'ClassNotGuardedAgainstRedeclaration' );
+ }
+
+ /**
+ * The purpose of this method is to sanitize the input data
+ * after the properties have been set.
+ */
+ private function onRegisterEvent() {
+ $this->functionsWhiteList = self::sanitize( $this->functionsWhiteList );
+ $this->classesWhiteList = self::sanitize( $this->classesWhiteList );
+ }
+
+ /**
+ * Input data needs to be sanitized.
+ *
+ * @param array $values The values being sanitized.
+ *
+ * @return array
+ */
+ private static function sanitize( $values ) {
+ $values = array_map( 'trim', $values );
+
+ return array_filter( $values );
+ }
+}
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/GuardedFunctionAndClassNamesUnitTest.inc b/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/GuardedFunctionAndClassNamesUnitTest.inc
new file mode 100644
index 0000000000000..5db9946d68ae3
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/Gutenberg/Tests/CodeAnalysis/GuardedFunctionAndClassNamesUnitTest.inc
@@ -0,0 +1,26 @@
+ =>
+ */
+ public function getErrorList() {
+ return array(
+ 17 => 1,
+ 25 => 1,
+ );
+ }
+
+ /**
+ * Returns the lines where warnings should occur.
+ *
+ * @return array =>
+ */
+ public function getWarningList() {
+ return array();
+ }
+}
diff --git a/test/php/gutenberg-coding-standards/Gutenberg/ruleset.xml b/test/php/gutenberg-coding-standards/Gutenberg/ruleset.xml
new file mode 100644
index 0000000000000..e899c9bed0528
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/Gutenberg/ruleset.xml
@@ -0,0 +1,8 @@
+
+
+
+ Gutenberg Coding Standards
+
+
+
+
diff --git a/test/php/gutenberg-coding-standards/README.md b/test/php/gutenberg-coding-standards/README.md
new file mode 100644
index 0000000000000..51f6574fa2053
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/README.md
@@ -0,0 +1,3 @@
+# Gutenberg Coding Standards for Gutenberg
+
+This project is a collection of [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) rules (sniffs) to validate code developed for Gutenberg. It ensures code quality and adherence to coding conventions.
\ No newline at end of file
diff --git a/test/php/gutenberg-coding-standards/Tests/bootstrap.php b/test/php/gutenberg-coding-standards/Tests/bootstrap.php
new file mode 100644
index 0000000000000..f28528bc1b972
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/Tests/bootstrap.php
@@ -0,0 +1,86 @@
+ true,
+);
+
+$allStandards = PHP_CodeSniffer\Util\Standards::getInstalledStandards();
+$allStandards[] = 'Generic';
+
+$standardsToIgnore = array();
+foreach ( $allStandards as $standard ) {
+ if ( isset( $gbcsStandards[ $standard ] ) === true ) {
+ continue;
+ }
+
+ $standardsToIgnore[] = $standard;
+}
+
+$standardsToIgnoreString = implode( ',', $standardsToIgnore );
+
+// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- This is not production, but test code.
+putenv( "PHPCS_IGNORE_TESTS={$standardsToIgnoreString}" );
+
+// Clean up.
+unset( $ds, $phpcsDir, $composerPHPCSPath, $allStandards, $standardsToIgnore, $standard, $standardsToIgnoreString );
diff --git a/test/php/gutenberg-coding-standards/composer.json b/test/php/gutenberg-coding-standards/composer.json
new file mode 100644
index 0000000000000..f4ca88e49e2ae
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/composer.json
@@ -0,0 +1,65 @@
+{
+ "name": "gutenberg/gutenberg-coding-standards",
+ "type": "phpcodesniffer-standard",
+ "description": "PHP_CodeSniffer rules (sniffs) to enforce Gutenberg coding conventions",
+ "keywords": [
+ "phpcs",
+ "standards",
+ "static analysis",
+ "Gutenberg"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Contributors",
+ "homepage": "https://github.com/WordPress/gutenberg/graphs/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.4",
+ "ext-filter": "*",
+ "squizlabs/php_codesniffer": "^3.7.2"
+ },
+ "require-dev": {
+ "phpcompatibility/php-compatibility": "^9.0",
+ "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0",
+ "php-parallel-lint/php-parallel-lint": "^1.3.2",
+ "php-parallel-lint/php-console-highlighter": "^1.0.0",
+ "dealerdirect/phpcodesniffer-composer-installer": "*",
+ "wp-coding-standards/wpcs": "^2.2"
+ },
+ "suggest": {
+ "ext-mbstring": "For improved results"
+ },
+ "config": {
+ "allow-plugins": {
+ "dealerdirect/phpcodesniffer-composer-installer": true
+ }
+ },
+ "scripts": {
+ "lint": [
+ "@php ./vendor/php-parallel-lint/php-parallel-lint/parallel-lint . -e php --show-deprecated --exclude vendor --exclude .git"
+ ],
+ "check-cs": [
+ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcs"
+ ],
+ "fix-cs": [
+ "@php ./vendor/squizlabs/php_codesniffer/bin/phpcbf"
+ ],
+ "run-tests": [
+ "@php ./vendor/phpunit/phpunit/phpunit -c phpunit.xml.dist --filter Gutenberg ./vendor/squizlabs/php_codesniffer/tests/AllTests.php"
+ ],
+ "check-all": [
+ "@lint",
+ "@check-cs",
+ "@run-tests"
+ ]
+ },
+ "scripts-descriptions": {
+ "lint": "Lint PHP files against parse errors.",
+ "check-cs": "Run the PHPCS script against the entire codebase.",
+ "fix-cs": "Run the PHPCBF script to fix all the autofixable violations on the codebase.",
+ "run-tests": "Run all the unit tests for the Gutenberg Coding Standards sniffs.",
+ "check-all": "Run all checks (lint, phpcs) and tests."
+ }
+}
diff --git a/test/php/gutenberg-coding-standards/phpunit.xml.dist b/test/php/gutenberg-coding-standards/phpunit.xml.dist
new file mode 100644
index 0000000000000..0e9d9cb5b7507
--- /dev/null
+++ b/test/php/gutenberg-coding-standards/phpunit.xml.dist
@@ -0,0 +1,16 @@
+
+
+
+
+
+ ./Gutenberg/Tests/
+
+
+
+