Skip to content

Commit 1d153ff

Browse files
committed
AC-15344:: Add change level to breaking change table report in SVC
1 parent 0054175 commit 1d153ff

File tree

1 file changed

+127
-0
lines changed

1 file changed

+127
-0
lines changed

src/Reporter/BreakingChangeTableReporter.php

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)