Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include more helpful output in type completeness check #16327

Merged
merged 9 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/static-analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ jobs:
BASE_SCORE=$(jq -r '.typeCompleteness.completenessScore' prefect-analysis-base.json)
echo "base_score=$BASE_SCORE" >> $GITHUB_OUTPUT

- name: Checkout current branch
run: |
git checkout ${{ github.head_ref || github.ref_name }}

- name: Compare scores
run: |
CURRENT_SCORE=$(echo ${{ steps.calculate_current_score.outputs.current_score }})
Expand All @@ -110,6 +114,7 @@ jobs:
if (( $(echo "$BASE_SCORE > $CURRENT_SCORE" | bc -l) )); then
echo "❌ Type completeness score has decreased from $BASE_SCORE to $CURRENT_SCORE" >> $GITHUB_STEP_SUMMARY
echo "Please add type annotations to your code to increase the type completeness score." >> $GITHUB_STEP_SUMMARY
uv run scripts/pyright_diff.py prefect-analysis-base.json prefect-analysis.json >> $GITHUB_STEP_SUMMARY
exit 1
elif (( $(echo "$BASE_SCORE < $CURRENT_SCORE" | bc -l) )); then
echo "✅ Type completeness score has increased from $BASE_SCORE to $CURRENT_SCORE" >> $GITHUB_STEP_SUMMARY
Expand Down
88 changes: 88 additions & 0 deletions scripts/pyright_diff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import json
import sys
from typing import Any, Dict, NamedTuple


class Diagnostic(NamedTuple):
"""Structured representation of a diagnostic for easier table formatting."""

file: str
line: int
character: int
severity: str
message: str


def normalize_diagnostic(diagnostic: Dict[Any, Any]) -> Dict[Any, Any]:
"""Normalize a diagnostic by removing or standardizing volatile fields."""
normalized = diagnostic.copy()
normalized.pop("time", None)
normalized.pop("version", None)
return normalized


def load_and_normalize_file(file_path: str) -> Dict[Any, Any]:
"""Load a JSON file and normalize its contents."""
with open(file_path, "r") as f:
data = json.load(f)
return normalize_diagnostic(data)


def parse_diagnostic(diag: Dict[Any, Any]) -> Diagnostic:
"""Convert a diagnostic dict into a Diagnostic object."""
file = diag.get("file", "unknown_file")
message = diag.get("message", "no message")
range_info = diag.get("range", {})
start = range_info.get("start", {})
line = start.get("line", 0)
char = start.get("character", 0)
severity = diag.get("severity", "unknown")

return Diagnostic(file, line, char, severity, message)


def format_markdown_table(diagnostics: list[Diagnostic]) -> str:
"""Format list of diagnostics as a markdown table."""
if not diagnostics:
return "\nNo new errors found!"

table = ["| File | Location | Message |", "|------|----------|---------|"]

for diag in sorted(diagnostics, key=lambda x: (x.file, x.line, x.character)):
# Escape pipe characters and replace newlines with HTML breaks
message = diag.message.replace("|", "\\|").replace("\n", "<br>")
location = f"L{diag.line}:{diag.character}"
table.append(f"| {diag.file} | {location} | {message} |")

return "\n".join(table)


def compare_pyright_outputs(base_file: str, new_file: str) -> None:
"""Compare two pyright JSON output files and display only new errors."""
base_data = load_and_normalize_file(base_file)
new_data = load_and_normalize_file(new_file)

# Group diagnostics by file
base_diags = set()
new_diags = set()

# Process diagnostics from type completeness symbols
for data, diag_set in [(base_data, base_diags), (new_data, new_diags)]:
for symbol in data.get("typeCompleteness", {}).get("symbols", []):
for diag in symbol.get("diagnostics", []):
if diag.get("severity", "") == "error":
diag_set.add(parse_diagnostic(diag))

# Find new errors
new_errors = list(new_diags - base_diags)

print("\n## New Pyright Errors\n")
print(format_markdown_table(new_errors))


if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python pyright_diff.py <base.json> <new.json>")
sys.exit(1)

compare_pyright_outputs(sys.argv[1], sys.argv[2])
Loading