servo/python/tidy/linting_report.py
Jerens Lensun 1c4797809a
Mach: add type check on python tidy folder (#38043)
Introduce `python/tidy` folder in pyrefly type checker

Testing: Manual testing via `./mach test-tidy` command

---------

Signed-off-by: Jerens Lensun <jerensslensun@gmail.com>
Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>
Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
2025-07-17 07:35:11 +00:00

110 lines
3.6 KiB
Python

# Copyright 2025 The Servo Project Developers. See the COPYRIGHT
# file at the top-level directory of this distribution.
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.
from dataclasses import dataclass
from typing import Any, Literal, Optional
@dataclass
class GithubAnnotation:
file_name: str
line_start: int
line_end: int
level: Literal["notice", "warning", "error"]
title: str
message: str
column_start: Optional[int] = None
column_end: Optional[int] = None
class GitHubAnnotationManager:
def __init__(self, annotation_prefix: str, limit: int = 10) -> None:
self.annotation_prefix: str = annotation_prefix
self.limit: int = limit
self.total_count: int = 0
def clean_path(self, path: str) -> str:
return path.removeprefix("./")
def escape(self, s: str) -> str:
return s.replace("\r", "%0D").replace("\n", "%0A")
def emit_annotation(
self,
title: str,
message: str,
file_name: str,
line_start: int,
line_end: int | None = None,
annotation_level: Literal["notice", "warning", "error"] = "error",
column_start: int | None = None,
column_end: int | None = None,
) -> None:
if self.total_count >= self.limit:
return
if line_end is None:
line_end = line_start
annotation = GithubAnnotation(
title=f"{self.annotation_prefix}: {self.escape(title)}",
message=self.escape(message),
file_name=self.clean_path(file_name),
line_start=line_start,
line_end=line_end,
level=annotation_level,
column_start=column_start,
column_end=column_end,
)
line_info = f"line={annotation.line_start},endLine={annotation.line_end},title={annotation.title}"
column_info = ""
if line_start == line_end and annotation.column_end is not None and annotation.column_start is not None:
column_info = f"col={annotation.column_start},endColumn={annotation.column_end},"
print(f"::{annotation.level} file={annotation.file_name},{column_info}{line_info}::{annotation.message}")
self.total_count += 1
def emit_annotations_for_clippy(self, data: list[dict[str, Any]]) -> None:
severenty_map: dict[str, Literal["notice", "warning", "error"]] = {
"help": "notice",
"note": "notice",
"warning": "warning",
"error": "error",
}
for item in data:
if self.total_count >= self.limit:
break
message = item.get("message")
if not message:
continue
spans = message.get("spans") or []
primary_span = next((span for span in spans if span.get("is_primary")), None)
if not primary_span:
continue
annotation_level = severenty_map.get(message.get("level"), "error")
title = message.get("message", "")
rendered_message = message.get("rendered", "")
self.emit_annotation(
title,
rendered_message,
primary_span["file_name"],
primary_span["line_start"],
primary_span["line_end"],
annotation_level,
primary_span["column_start"],
primary_span["column_end"],
)