@@ -106,6 +106,11 @@ protected function outputTable(OutputInterface $output, Report $report, $context
106106 $ reportForLevel = $ report [$ context ][$ level ];
107107 /** @var \PHPSemVerChecker\Operation\Operation $operation */
108108 foreach ($ reportForLevel as $ operation ) {
109+ // Skip private method/property changes as they shouldn't be in breaking change reports
110+ if ($ this ->isPrivateChange ($ operation )) {
111+ continue ;
112+ }
113+
109114 $ levelLabel = $ this ->getLevelLabel ($ level );
110115 $ target = $ operation ->getTarget ();
111116 $ reason = $ operation ->getReason ();
@@ -136,6 +141,128 @@ private function getLevelLabel(int $level): string
136141 }
137142 }
138143
144+ /**
145+ * Check if the operation represents a private method or property change
146+ *
147+ * Private changes are filtered out as they don't affect the public API contract.
148+ *
149+ * @param \PHPSemVerChecker\Operation\Operation $operation
150+ * @return bool
151+ */
152+ private function isPrivateChange ($ operation ): bool
153+ {
154+ $ target = $ operation ->getTarget ();
155+ $ reason = $ operation ->getReason ();
156+ $ operationClass = get_class ($ operation );
157+
158+ // For visibility operations, check if they involve private visibility
159+ if ($ operation instanceof \Magento \SemanticVersionChecker \Operation \VisibilityOperation) {
160+ try {
161+ // Use reflection to access protected properties
162+ $ reflection = new \ReflectionClass ($ operation );
163+
164+ if ($ reflection ->hasProperty ('memberBefore ' )) {
165+ $ memberBeforeProperty = $ reflection ->getProperty ('memberBefore ' );
166+ $ memberBeforeProperty ->setAccessible (true );
167+ $ memberBefore = $ memberBeforeProperty ->getValue ($ operation );
168+
169+ if ($ memberBefore && method_exists ('\PHPSemVerChecker\Operation\Visibility ' , 'getForContext ' )) {
170+ $ visibilityBefore = \PHPSemVerChecker \Operation \Visibility::getForContext ($ memberBefore );
171+ // 1 = public, 2 = protected, 3 = private
172+ if ($ visibilityBefore === 3 ) {
173+ return true ;
174+ }
175+ }
176+ }
177+
178+ if ($ reflection ->hasProperty ('memberAfter ' )) {
179+ $ memberAfterProperty = $ reflection ->getProperty ('memberAfter ' );
180+ $ memberAfterProperty ->setAccessible (true );
181+ $ memberAfter = $ memberAfterProperty ->getValue ($ operation );
182+
183+ if ($ memberAfter && method_exists ('\PHPSemVerChecker\Operation\Visibility ' , 'getForContext ' )) {
184+ $ visibilityAfter = \PHPSemVerChecker \Operation \Visibility::getForContext ($ memberAfter );
185+ // 1 = public, 2 = protected, 3 = private
186+ if ($ visibilityAfter === 3 ) {
187+ return true ;
188+ }
189+ }
190+ }
191+ } catch (\Exception $ e ) {
192+ // Fall back to string matching if reflection fails
193+ }
194+ }
195+
196+ // Check if the reason explicitly mentions private visibility
197+ if (preg_match ('/\[private\]/ ' , $ reason )) {
198+ return true ;
199+ }
200+
201+ // Check if the target or reason indicates a private method/property
202+ $ privateIndicators = [
203+ 'private method ' ,
204+ 'private property ' ,
205+ 'Private method ' ,
206+ 'Private property ' ,
207+ '::private ' ,
208+ ' private ' ,
209+ 'private function ' ,
210+ 'private static ' ,
211+ 'visibility has been changed to lower lever from private ' ,
212+ 'visibility has been changed to higher lever from private ' ,
213+ 'visibility has been changed from private ' ,
214+ 'visibility has been changed to private ' ,
215+ 'Method visibility has been changed from public to private ' ,
216+ 'Method visibility has been changed from protected to private ' ,
217+ 'Property visibility has been changed from public to private ' ,
218+ 'Property visibility has been changed from protected to private ' ,
219+ ];
220+
221+ foreach ($ privateIndicators as $ indicator ) {
222+ if (stripos ($ target , $ indicator ) !== false || stripos ($ reason , $ indicator ) !== false ) {
223+ return true ;
224+ }
225+ }
226+
227+ // Check for visibility operations that involve private members
228+ if (strpos ($ operationClass , 'Visibility ' ) !== false ) {
229+ // For visibility operations, check if it involves changing from/to private
230+ if (stripos ($ reason , 'private ' ) !== false ) {
231+ return true ;
232+ }
233+ }
234+
235+ // Check for specific operation classes that handle private changes
236+ $ privateOperationClasses = [
237+ 'PrivateMethod ' ,
238+ 'PrivateProperty ' ,
239+ 'Private ' ,
240+ ];
241+
242+ foreach ($ privateOperationClasses as $ privateClass ) {
243+ if (stripos ($ operationClass , $ privateClass ) !== false ) {
244+ return true ;
245+ }
246+ }
247+
248+ // Check if the target contains patterns that suggest private members
249+ // Pattern: ClassName::privateMethodName or ClassName::$privateProperty
250+ if (preg_match ('/::([a-z_][a-zA-Z0-9_]*|\$[a-z_][a-zA-Z0-9_]*)/ ' , $ target , $ matches )) {
251+ $ memberName = $ matches [1 ];
252+ // If member name starts with underscore (common private naming convention)
253+ if (strpos ($ memberName , '_ ' ) === 0 ) {
254+ return true ;
255+ }
256+ }
257+
258+ // Check for common private method patterns in the target
259+ if (preg_match ('/::(_[a-zA-Z0-9_]+|[a-z][a-zA-Z0-9]*Private[a-zA-Z0-9]*)\(/ ' , $ target )) {
260+ return true ;
261+ }
262+
263+ return false ;
264+ }
265+
139266 /**
140267 * Generate the HTML header line for a report section
141268 *
0 commit comments