Skip to content

Add class whitespace doc#37

Draft
sirreal wants to merge 14 commits into
trunkfrom
add-class-whitespace-doc
Draft

Add class whitespace doc#37
sirreal wants to merge 14 commits into
trunkfrom
add-class-whitespace-doc

Conversation

@sirreal

@sirreal sirreal commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Trac ticket:

Use of AI Tools


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

sirreal added 8 commits June 2, 2026 16:10
Note that the `class_name` argument to `WP_HTML_Tag_Processor::add_class()`
and `WP_HTML_Processor::add_class()` is inserted verbatim into the `class`
attribute, which is a whitespace-separated token list. Whitespace in the
value therefore adds multiple classes; a single class whose name contains
whitespace cannot be expressed.
Drop the inline enumeration of HTML ASCII whitespace code points and
shorten the explanation; keep the worked single- and multi-class
examples.
Identify the term and list the characters (tab, line feed, form feed,
carriage return, space) so readers know exactly what splits a value
into multiple class names.
Replace the paraphrased whitespace list with a blockquote of the
Infra Standard definition and add a `@see` link to the source.
Adopt the simpler note on the Tag Processor and mirror it on the HTML
Processor so both `add_class()` docblocks read the same.
Rewrite the lead sentence so it parses on one read, and restore the
explicit "not one class" cue on the multi-class example so the
docblock directly contradicts the natural misreading.
State directly that the value is added verbatim and may yield multiple
class names in the element's class list — "may" because trailing or
leading whitespace alone still produces a single class.
The body prose already names ASCII whitespace and explains the
multi-class effect, so the `@see` link and expanded `@param`
description are redundant. Restore the original `@param` text.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for responsive breakpoint states (such as mobile and tablet media queries) within the WP_Theme_JSON class, updating style compilation, sanitization, and variation handling accordingly. It also updates global variable PHPDoc annotations in the Twenty Eleven theme and clarifies class name handling in the HTML API processors. Feedback on these changes focuses on adding robust array type guards (using is_array) when accessing breakpoint nodes, nested elements, spacing, and pseudo-selectors to prevent PHP warnings or type errors on malformed theme.json structures.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

I am having trouble creating individual review comments. Click here to see my feedback.

src/wp-includes/class-wp-theme-json.php (2949-2950)

medium

Ensure that the breakpoint node is an array before processing it, to prevent potential type errors or warnings if the theme.json structure is malformed.

				foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
					if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ) && is_array( $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ) ) {

src/wp-includes/class-wp-theme-json.php (2969-2974)

medium

Safely access the breakpoint node as an array to avoid warnings when checking for pseudo-selectors on malformed or non-array breakpoint nodes.

						foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
							$bp_node = $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ?? null;
							if ( is_array( $bp_node ) && isset( $bp_node[ $pseudo_selector ] ) ) {
								$has_responsive_pseudo = true;
								break;
							}
						}

src/wp-includes/class-wp-theme-json.php (3014-3027)

medium

Use a safe array guard for the breakpoint node before checking for pseudo-selectors to prevent PHP warnings on malformed inputs.

						foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
							$bp_node = $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ?? null;
							if ( is_array( $bp_node ) && isset( $bp_node[ $pseudo_selector ] ) ) {
								$nodes[] = array(
									'name'        => $name,
									'path'        => array( 'styles', 'blocks', $name, $breakpoint, $pseudo_selector ),
									'media_query' => static::RESPONSIVE_BREAKPOINTS[ $breakpoint ],
									'selector'    => static::append_to_selector( $selector, $pseudo_selector ),
									'selectors'   => $pseudo_feature_selectors,
									'elements'    => $selectors[ $name ]['elements'] ?? array(),
									'variations'  => $variation_selectors,
									'css'         => static::append_to_selector( $selector, $pseudo_selector ),
								);
							}
						}

src/wp-includes/class-wp-theme-json.php (3050-3058)

medium

Guard against non-array breakpoint nodes when checking for nested elements to prevent PHP warnings.

					foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
						$bp_node = $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ?? null;
						if ( is_array( $bp_node ) && isset( $bp_node['elements'][ $element ] ) ) {
							$nodes[] = array(
								'path'        => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
								'selector'    => $element_selector,
								'media_query' => static::RESPONSIVE_BREAKPOINTS[ $breakpoint ],
							);
						}
					}

src/wp-includes/class-wp-theme-json.php (3065-3072)

medium

Ensure the breakpoint node is an array before checking for nested element pseudo-selectors.

							if ( ! $has_element_pseudo ) {
								foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $bp ) {
									$bp_node = $theme_json['styles']['blocks'][ $name ][ $bp ] ?? null;
									if ( is_array( $bp_node ) && isset( $bp_node['elements'][ $element ][ $pseudo_selector ] ) ) {
										$has_element_pseudo = true;
										break;
									}
								}
							}

src/wp-includes/class-wp-theme-json.php (3091-3099)

medium

Safely check nested element pseudo-selectors on breakpoint nodes by ensuring they are arrays.

								foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
									$bp_node = $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ?? null;
									if ( is_array( $bp_node ) && isset( $bp_node['elements'][ $element ][ $pseudo_selector ] ) ) {
										$nodes[] = array(
											'path'        => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
											'selector'    => static::append_to_selector( $element_selector, $pseudo_selector ),
											'media_query' => static::RESPONSIVE_BREAKPOINTS[ $breakpoint ],
										);
									}
								}

src/wp-includes/class-wp-theme-json.php (3211-3214)

medium

Verify that the style variation breakpoint node is an array before proceeding to process its styles.

				foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
					if ( ! isset( $style_variation_node[ $breakpoint ] ) || ! is_array( $style_variation_node[ $breakpoint ] ) ) {
						continue;
					}

src/wp-includes/class-wp-theme-json.php (3271-3281)

medium

Ensure spacing is an array before checking for blockGap to avoid PHP warnings on malformed variation nodes.

					if ( isset( $breakpoint_node['spacing'] ) && is_array( $breakpoint_node['spacing'] ) && isset( $breakpoint_node['spacing']['blockGap'] ) ) {
						$variation_layout_metadata             = $style_variation;
						$variation_layout_metadata['selector'] = $style_variation['selector'] . $block_metadata['css'];
						$variation_responsive_css             .= $this->get_layout_styles(
							$variation_layout_metadata,
							array(
								'node'        => $breakpoint_node,
								'media_query' => $breakpoint_media,
							)
						);
					}

src/wp-includes/class-wp-theme-json.php (3284-3288)

medium

Ensure elements is an array and each element_node is an array before processing nested element styles.

					if ( isset( $breakpoint_node['elements'] ) && is_array( $breakpoint_node['elements'] ) && ! empty( $block_elements ) ) {
						foreach ( $breakpoint_node['elements'] as $element_name => $element_node ) {
							if ( ! isset( $block_elements[ $element_name ] ) || ! is_array( $element_node ) ) {
								continue;
							}

src/wp-includes/class-wp-theme-json.php (3312-3321)

medium

Ensure that the pseudo-selector style definition is an array before passing it to compute_style_properties.

							if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
								foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
									if ( ! isset( $element_node[ $pseudo_selector ] ) || ! is_array( $element_node[ $pseudo_selector ] ) ) {
										continue;
									}

									$pseudo_declarations = static::compute_style_properties( $element_node[ $pseudo_selector ], $settings, null, $this->theme_json );
									if ( empty( $pseudo_declarations ) ) {
										continue;
									}

src/wp-includes/class-wp-theme-json.php (4060-4070)

medium

Ensure the breakpoint node and its nested elements and blocks keys are arrays before calling sanitization methods.

			foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
				if ( isset( $input[ $breakpoint ] ) && is_array( $input[ $breakpoint ] ) ) {
					$output[ $breakpoint ] = static::remove_insecure_styles( $input[ $breakpoint ] );

					if ( isset( $input[ $breakpoint ]['elements'] ) && is_array( $input[ $breakpoint ]['elements'] ) ) {
						$output[ $breakpoint ]['elements'] = static::remove_insecure_element_styles( $input[ $breakpoint ]['elements'] );
					}

					if ( isset( $input[ $breakpoint ]['blocks'] ) && is_array( $input[ $breakpoint ]['blocks'] ) ) {
						$output[ $breakpoint ]['blocks'] = static::remove_insecure_inner_block_styles( $input[ $breakpoint ]['blocks'] );
					}

src/wp-includes/class-wp-theme-json.php (4109-4119)

medium

Verify that the variation breakpoint node and its sub-keys are arrays before sanitizing them.

					foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
						if ( isset( $variation_input[ $breakpoint ] ) && is_array( $variation_input[ $breakpoint ] ) ) {
							$variation_output[ $breakpoint ] = static::remove_insecure_styles( $variation_input[ $breakpoint ] );

							if ( isset( $variation_input[ $breakpoint ]['elements'] ) && is_array( $variation_input[ $breakpoint ]['elements'] ) ) {
								$variation_output[ $breakpoint ]['elements'] = static::remove_insecure_element_styles( $variation_input[ $breakpoint ]['elements'] );
							}

							if ( isset( $variation_input[ $breakpoint ]['blocks'] ) && is_array( $variation_input[ $breakpoint ]['blocks'] ) ) {
								$variation_output[ $breakpoint ]['blocks'] = static::remove_insecure_inner_block_styles( $variation_input[ $breakpoint ]['blocks'] );
							}

src/wp-includes/class-wp-theme-json.php (4197-4209)

medium

Ensure the element breakpoint node and its pseudo-selectors are arrays before processing them.

				foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
					if ( isset( $element_input[ $breakpoint ] ) && is_array( $element_input[ $breakpoint ] ) ) {
						$element_output[ $breakpoint ] = static::remove_insecure_styles( $element_input[ $breakpoint ] );

						if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
							foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
								if ( isset( $element_input[ $breakpoint ][ $pseudo_selector ] ) && is_array( $element_input[ $breakpoint ][ $pseudo_selector ] ) ) {
									$element_output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $breakpoint ][ $pseudo_selector ] );
								}
							}
						}
					}
				}

src/wp-includes/class-wp-theme-json.php (4235-4247)

medium

Ensure the inner block breakpoint node and its pseudo-selectors are arrays before processing them.

			foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
				if ( isset( $block_input[ $breakpoint ] ) && is_array( $block_input[ $breakpoint ] ) ) {
					$block_output[ $breakpoint ] = static::remove_insecure_styles( $block_input[ $breakpoint ] );

					if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_type ] ) ) {
						foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_type ] as $pseudo_selector ) {
							if ( isset( $block_input[ $breakpoint ][ $pseudo_selector ] ) && is_array( $block_input[ $breakpoint ][ $pseudo_selector ] ) ) {
								$block_output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $block_input[ $breakpoint ][ $pseudo_selector ] );
							}
						}
					}
				}
			}

sirreal added 6 commits June 2, 2026 16:49
…e value.

The class name itself is HTML-escaped when written into the attribute;
only the whitespace passes through unchanged, which is what splits the
value into multiple class names.
…ace.

A class name cannot contain whitespace; the HTML class attribute is a
whitespace-separated token list. Pass-through whitespace in the
`$wanted_class` / `$class_name` argument can therefore never match a
real token, so reject it up front and return false. Document the new
behavior in both the Tag Processor and HTML Processor docblocks.

Behavior note: previously, `add_class('a b')` followed by
`remove_class('a b')` was an accidental no-op because the REMOVE
overwrote the ADD in the keyed updates map. With the short-circuit the
REMOVE is rejected and the ADD survives, so the element gets classes
"a" and "b" as the lone `add_class()` call would have produced.
The short-circuit is purely a performance refinement of an already
guaranteed result (no token can contain whitespace), not a meaningful
behavior change worth versioning. Keep the inline docblock note.
Add a `_doing_it_wrong()` call on the whitespace short-circuit in
`has_class()` and `remove_class()` so developers passing a value that
can never identify a class token see an actionable warning rather than
a silent false.
… docblocks.

The `_doing_it_wrong()` warning now communicates the constraint at the
point of misuse, so the docblock note is redundant.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant