Skip to content

Commit d0b3ac5

Browse files
Breaking changes checker split classes (#36125)
* split classes * update messages * cleanup --------- Co-authored-by: Catalina Peralta <[email protected]>
1 parent 785c0ae commit d0b3ac5

File tree

5 files changed

+228
-201
lines changed

5 files changed

+228
-201
lines changed

scripts/breaking_changes_checker/breaking_changes_tracker.py

Lines changed: 14 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,6 @@ class BreakingChangeType(str, Enum):
2929
REMOVED_OR_RENAMED_MODULE = "RemovedOrRenamedModule"
3030
REMOVED_FUNCTION_KWARGS = "RemovedFunctionKwargs"
3131

32-
# General non-breaking changes
33-
class ChangeType(str, Enum):
34-
ADDED_CLIENT = "AddedClient"
35-
ADDED_CLIENT_METHOD = "AddedClientMethod"
36-
ADDED_CLASS = "AddedClass"
37-
ADDED_CLASS_METHOD = "AddedClassMethod"
38-
ADDED_CLASS_METHOD_PARAMETER = "AddedClassMethodParameter"
39-
ADDED_CLASS_PROPERTY = "AddedClassProperty"
40-
ADDED_FUNCTION_PARAMETER = "AddedFunctionParameter"
4132

4233
class BreakingChangesTracker:
4334
REMOVED_OR_RENAMED_CLIENT_MSG = \
@@ -97,50 +88,20 @@ class BreakingChangesTracker:
9788
"The function '{}.{}' changed from accepting keyword arguments to not accepting them in " \
9889
"the current version"
9990

100-
# ----------------- General Changes -----------------
101-
ADDED_CLIENT_MSG = \
102-
"The client '{}.{}' was added in the current version"
103-
ADDED_CLIENT_METHOD_MSG = \
104-
"The '{}.{}' client method '{}' was added in the current version"
105-
ADDED_CLASS_MSG = \
106-
"The model or publicly exposed class '{}.{}' was added in the current version"
107-
ADDED_CLASS_METHOD_MSG = \
108-
"The '{}.{}' method '{}' was added in the current version"
109-
ADDED_CLASS_METHOD_PARAMETER_MSG = \
110-
"The model or publicly exposed class '{}.{}' had property '{}' added in the {} method in the current version"
111-
ADDED_FUNCTION_PARAMETER_MSG = \
112-
"The function '{}.{}' had parameter '{}' added in the current version"
113-
ADDED_CLASS_PROPERTY_MSG = \
114-
"The model or publicly exposed class '{}.{}' had property '{}' added in the current version"
115-
116-
11791
def __init__(self, stable: Dict, current: Dict, diff: Dict, package_name: str, **kwargs: Any) -> None:
11892
self.stable = stable
11993
self.current = current
12094
self.diff = diff
12195
self.breaking_changes = []
122-
self.features_added = []
12396
self.package_name = package_name
12497
self.module_name = None
12598
self.class_name = None
12699
self.function_name = None
127100
self.parameter_name = None
128101
self.ignore = kwargs.get("ignore", None)
129-
self.changelog = kwargs.get("changelog", False)
130-
131-
def __str__(self):
132-
formatted = "\n"
133-
for bc in self.breaking_changes:
134-
formatted += bc + "\n"
135-
136-
formatted += f"\nFound {len(self.breaking_changes)} breaking changes.\n"
137-
formatted += "\nSee aka.ms/azsdk/breaking-changes-tool to resolve " \
138-
"any reported breaking changes or false positives.\n"
139-
return formatted
102+
self.previous_version = kwargs.get("previous_version", None)
140103

141104
def run_checks(self) -> None:
142-
if self.changelog:
143-
self.run_non_breaking_change_diff_checks()
144105
self.run_breaking_change_diff_checks()
145106
self.check_parameter_ordering() # not part of diff
146107

@@ -157,88 +118,6 @@ def run_breaking_change_diff_checks(self) -> None:
157118
self.run_class_level_diff_checks(module)
158119
self.run_function_level_diff_checks(module)
159120

160-
def run_non_breaking_change_diff_checks(self) -> None:
161-
for module_name, module in self.diff.items():
162-
self.module_name = module_name
163-
if self.module_name not in self.stable and not isinstance(self.module_name, jsondiff.Symbol):
164-
continue # TODO add this to reported changes
165-
166-
self.run_non_breaking_class_level_diff_checks(module)
167-
168-
def run_non_breaking_class_level_diff_checks(self, module: Dict) -> None:
169-
for class_name, class_components in module.get("class_nodes", {}).items():
170-
self.class_name = class_name
171-
stable_class_nodes = self.stable[self.module_name]["class_nodes"]
172-
if not isinstance(class_name, jsondiff.Symbol):
173-
if self.class_name not in stable_class_nodes:
174-
if self.class_name.endswith("Client"):
175-
# This is a new client
176-
fa = (
177-
self.ADDED_CLIENT_MSG,
178-
ChangeType.ADDED_CLIENT,
179-
self.module_name, class_name
180-
)
181-
self.features_added.append(fa)
182-
else:
183-
# This is a new class
184-
fa = (
185-
self.ADDED_CLASS_MSG,
186-
ChangeType.ADDED_CLASS,
187-
self.module_name, class_name
188-
)
189-
self.features_added.append(fa)
190-
else:
191-
# Check existing class for new methods
192-
stable_methods_node = stable_class_nodes[self.class_name]["methods"]
193-
for method_name, method_components in class_components.get("methods", {}).items():
194-
self.function_name = method_name
195-
if not isinstance(self.function_name, jsondiff.Symbol):
196-
if self.function_name not in stable_methods_node:
197-
if self.class_name.endswith("Client"):
198-
# This is a new client method
199-
fa = (
200-
self.ADDED_CLIENT_METHOD_MSG,
201-
ChangeType.ADDED_CLIENT_METHOD,
202-
self.module_name, self.class_name, method_name
203-
)
204-
self.features_added.append(fa)
205-
else:
206-
# This is a new class method
207-
fa = (
208-
self.ADDED_CLASS_METHOD_MSG,
209-
ChangeType.ADDED_CLASS_METHOD,
210-
self.module_name, class_name, method_name
211-
)
212-
self.features_added.append(fa)
213-
else:
214-
# Check existing methods for new parameters
215-
stable_parameters_node = stable_methods_node[self.function_name]["parameters"]
216-
current_parameters_node = self.current[self.module_name]["class_nodes"][self.class_name]["methods"][self.function_name]["parameters"]
217-
for param_name, param_components in method_components.get("parameters", {}).items():
218-
self.parameter_name = param_name
219-
if self.parameter_name not in stable_parameters_node and \
220-
not isinstance(self.parameter_name, jsondiff.Symbol):
221-
if self.function_name == "__init__":
222-
# If this is a new class property skip reporting it here and let the class properties check handle it.
223-
# This is because we'll get multiple reports for the same new property if it's a parameter in __init__
224-
# and a class level attribute.
225-
if self.parameter_name in class_components.get("properties", {}).keys():
226-
continue
227-
self.check_non_positional_parameter_added(
228-
current_parameters_node[param_name]
229-
)
230-
stable_property_node = stable_class_nodes[self.class_name]["properties"]
231-
for property_name, property_components in class_components.get("properties", {}).items():
232-
if property_name not in stable_property_node and \
233-
not isinstance(property_name, jsondiff.Symbol):
234-
fa = (
235-
self.ADDED_CLASS_PROPERTY_MSG,
236-
ChangeType.ADDED_CLASS_PROPERTY,
237-
self.module_name, class_name, property_name
238-
)
239-
self.features_added.append(fa)
240-
241-
242121
def run_class_level_diff_checks(self, module: Dict) -> None:
243122
for class_name, class_components in module.get("class_nodes", {}).items():
244123
self.class_name = class_name
@@ -540,23 +419,6 @@ def check_positional_parameter_added(self, current_parameters_node: Dict) -> Non
540419
)
541420
)
542421

543-
def check_non_positional_parameter_added(self, current_parameters_node: Dict) -> None:
544-
if current_parameters_node["param_type"] != "positional_or_keyword":
545-
if self.class_name:
546-
self.features_added.append(
547-
(
548-
self.ADDED_CLASS_METHOD_PARAMETER_MSG, ChangeType.ADDED_CLASS_METHOD_PARAMETER,
549-
self.module_name, self.class_name, self.parameter_name, self.function_name
550-
)
551-
)
552-
else:
553-
self.features_added.append(
554-
(
555-
self.ADDED_FUNCTION_PARAMETER_MSG, ChangeType.ADDED_FUNCTION_PARAMETER,
556-
self.module_name, self.function_name, self.parameter_name
557-
)
558-
)
559-
560422
def check_positional_parameter_removed_or_renamed(
561423
self, param_type: str,
562424
deleted: str,
@@ -712,33 +574,23 @@ def get_reportable_breaking_changes(self, ignore_changes: Dict) -> List:
712574
reportable_changes.append(bc)
713575
return reportable_changes
714576

715-
def report_changelog(self) -> None:
716-
# Code borrowed and modified from the previous change log tool
717-
def _build_md(content: list, title: str, buffer: list):
718-
buffer.append(title)
719-
buffer.append("")
720-
for _, bc in enumerate(content):
721-
msg, _, *args = bc
722-
buffer.append(msg.format(*args))
723-
buffer.append("")
724-
return buffer
725-
726-
buffer = []
727-
728-
if self.breaking_changes:
729-
_build_md(self.breaking_changes, "### Breaking Changes", buffer)
730-
if self.features_added:
731-
_build_md(self.features_added, "### Features Added", buffer)
732-
content = "\n".join(buffer).strip()
733-
return content
734-
735-
def report_breaking_changes(self) -> None:
577+
def report_changes(self) -> None:
736578
ignore_changes = self.ignore if self.ignore else IGNORE_BREAKING_CHANGES
737579
if self.package_name in ignore_changes:
738580
self.breaking_changes = self.get_reportable_breaking_changes(ignore_changes)
739581

582+
# If there are no breaking changes after the ignore check, return early
583+
if not self.breaking_changes:
584+
return f"\nNo breaking changes found for {self.package_name} between stable version {self.previous_version} and current version."
585+
586+
formatted = "\n"
740587
for idx, bc in enumerate(self.breaking_changes):
741588
msg, *args = bc
742589
# For simple breaking changes reporting, prepend the change code to the message
743-
msg = "({}): " + msg
744-
self.breaking_changes[idx] = msg.format(*args)
590+
msg = "({}): " + msg + "\n"
591+
formatted += msg.format(*args)
592+
593+
formatted += f"\nFound {len(self.breaking_changes)} breaking changes.\n"
594+
formatted += "\nSee aka.ms/azsdk/breaking-changes-tool to resolve " \
595+
"any reported breaking changes or false positives.\n"
596+
return formatted

0 commit comments

Comments
 (0)