diff --git a/plugins/wpgraphql-debug-extensions/src/Analysis/Rules/ExcessiveFields.php b/plugins/wpgraphql-debug-extensions/src/Analysis/Rules/ExcessiveFields.php new file mode 100644 index 00000000..0ee9e3ad --- /dev/null +++ b/plugins/wpgraphql-debug-extensions/src/Analysis/Rules/ExcessiveFields.php @@ -0,0 +1,141 @@ +query_analyzer->get_schema(); + } + if ( null === $schema ) { + $this->internalNote = 'Excessive field selection analysis requires a GraphQL schema.'; + return [ + 'triggered' => false, + 'message' => $this->internalNote, + ]; + } + + $fieldCounts = []; + + try { + $ast = Parser::parse( $query ); + } catch (SyntaxError $error) { + $this->internalNote = 'Excessive field selection analysis failed due to GraphQL syntax error: ' . $error->getMessage(); + error_log( 'WPGraphQL Debug Extensions: ' . $this->internalNote ); + return [ + 'triggered' => false, + 'message' => $this->internalNote, + ]; + } + + // Use TypeInfo to correctly resolve parent types during traversal + $typeInfo = new TypeInfo( $schema ); + + Visitor::visit( + $ast, + Visitor::visitWithTypeInfo( + $typeInfo, + [ + 'Field' => function (FieldNode $node) use (&$fieldCounts, $typeInfo) { + $parentType = $typeInfo->getParentType(); + if ( $parentType ) { + $typeName = $parentType->name; + if ( ! isset( $fieldCounts[ $typeName ] ) ) { + $fieldCounts[ $typeName ] = 0; + } + $fieldCounts[ $typeName ]++; + } + }, + ] + ) + ); + + $threshold = apply_filters( + 'graphql_debug_rule_excessive_fields_threshold', + $this->fieldThreshold, + $query, + $variables, + $schema + ); + + $triggered = false; + $details = []; + foreach ( $fieldCounts as $type => $count ) { + if ( $count > $threshold ) { + $triggered = true; + $details[] = sprintf( + 'Type "%s" selects %d fields, exceeding the threshold of %d.', + $type, + $count, + $threshold + ); + } + } + + $message = $triggered + ? 'Over-fetching detected: ' . implode( ' ', $details ) + : 'No excessive fields detected.'; + + $this->internalNote = $message; + + return [ + 'triggered' => $triggered, + 'message' => $message, + ]; + } + + /** + * Return the unique key for this analyzer rule. + * + * @return string + */ + public function getKey(): string { + return 'excessiveFieldsRule'; + } +} diff --git a/plugins/wpgraphql-debug-extensions/src/Plugin.php b/plugins/wpgraphql-debug-extensions/src/Plugin.php index e99816c8..b655d9f4 100644 --- a/plugins/wpgraphql-debug-extensions/src/Plugin.php +++ b/plugins/wpgraphql-debug-extensions/src/Plugin.php @@ -11,6 +11,7 @@ use AxeWP\GraphQL\Helper\Helper; use WPGraphQL\Debug\Analysis\QueryAnalyzer; +use WPGraphQL\Debug\Analysis\Rules\ExcessiveFields; use WPGraphQL\Debug\Analysis\Rules\NestedQuery; use WPGraphQL\Utils\QueryAnalyzer as OriginalQueryAnalyzer; use WPGraphQL\Debug\Analysis\Rules\Complexity; @@ -86,6 +87,7 @@ private function setup(): void { 'complexity' => [ 'class' => Complexity::class, 'args' => [] ], 'unfiltered_lists' => [ 'class' => UnfilteredLists::class, 'args' => [] ], 'nested_query' => [ 'class' => NestedQuery::class, 'args' => [ ] ], + 'excessive_fields' => [ 'class' => ExcessiveFields::class, 'args' => [ ] ], ]; $analyzer_items = apply_filters( 'graphql_debug_extensions_analyzer_items', $analyzer_items );