Coverage for lintro / parsers / base_issue.py: 100%
33 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Base issue class for all linting tool issues.
3This module provides a common base class for all issue types to reduce
4duplication across the 14+ different issue dataclasses.
6The base class includes:
7- Common fields (file, line, column, message)
8- A to_display_row() method for unified formatting with configurable field mapping
9- A get_severity() method for normalized severity access
10"""
12from __future__ import annotations
14from dataclasses import dataclass, field
15from typing import ClassVar
17from lintro.enums.severity_level import SeverityLevel, normalize_severity_level
20@dataclass
21class BaseIssue:
22 """Base class for all linting issues with unified display support.
24 Provides common fields that are shared across all issue types.
25 Specific issue types should inherit from this class and add their
26 own fields as needed.
28 The to_display_row() method converts the issue to a dictionary format
29 suitable for the unified formatter. Subclasses can customize the mapping
30 by setting DISPLAY_FIELD_MAP class variable instead of overriding the method.
32 Attributes:
33 DISPLAY_FIELD_MAP: Maps display keys to attribute names for custom fields.
34 Default mappings: code->code, severity->severity, fixable->fixable.
35 Example: {"severity": "level"} to map self.level to severity output.
36 DEFAULT_SEVERITY: Fallback severity when the issue has no native value.
37 Override in subclasses (e.g. INFO for pure-formatting tools).
38 file: File path where the issue was found.
39 line: Line number where the issue was found (1-based, 0 means unknown).
40 column: Column number where the issue was found (1-based, 0 means unknown).
41 message: Human-readable description of the issue.
42 doc_url: URL to the rule's documentation page (empty when unavailable).
43 """
45 # Default field mapping - subclasses can override specific keys
46 DISPLAY_FIELD_MAP: ClassVar[dict[str, str]] = {
47 "code": "code",
48 "severity": "severity",
49 "fixable": "fixable",
50 "message": "message",
51 }
53 DEFAULT_SEVERITY: ClassVar[SeverityLevel] = SeverityLevel.WARNING
55 file: str = field(default="")
56 line: int = field(default=0)
57 column: int = field(default=0)
58 message: str = field(default="")
59 doc_url: str = field(default="", repr=False)
61 def get_severity(self) -> SeverityLevel:
62 """Return the normalized severity for this issue.
64 Reads the native severity value via DISPLAY_FIELD_MAP (handles
65 tools that store it as ``level``, ``issue_severity``, etc.),
66 passes it through ``normalize_severity_level()``, and falls back
67 to ``DEFAULT_SEVERITY`` when the value is empty/None.
69 Returns:
70 SeverityLevel: Normalized severity enum value.
71 """
72 attr_name = self.DISPLAY_FIELD_MAP.get("severity", "severity")
73 raw = getattr(self, attr_name, None)
75 if not raw:
76 return self.DEFAULT_SEVERITY
78 if isinstance(raw, SeverityLevel):
79 return raw
81 try:
82 return normalize_severity_level(str(raw))
83 except ValueError:
84 return self.DEFAULT_SEVERITY
86 def to_display_row(self) -> dict[str, str]:
87 """Convert issue to unified display format.
89 Returns a dictionary with standardized keys that the unified formatter
90 can use to create consistent output across all tools.
92 Uses DISPLAY_FIELD_MAP to resolve attribute names, allowing subclasses
93 to customize field mapping without overriding this method.
95 Returns:
96 Dictionary with keys: file, line, column, code, message, severity,
97 fixable, doc_url.
98 """
99 # Get the field mapping (supports inheritance)
100 field_map = self.DISPLAY_FIELD_MAP
102 # Resolve each mapped field
103 code_attr = field_map.get("code", "code")
104 fixable_attr = field_map.get("fixable", "fixable")
105 message_attr = field_map.get("message", "message")
107 code_val = getattr(self, code_attr, None) or ""
108 fixable_val = getattr(self, fixable_attr, False)
109 message_val = getattr(self, message_attr, "") or ""
111 return {
112 "file": self.file,
113 "line": str(self.line) if self.line else "-",
114 "column": str(self.column) if self.column else "-",
115 "code": str(code_val) if code_val else "",
116 "message": message_val,
117 "severity": str(self.get_severity()),
118 "fixable": "Yes" if fixable_val else "",
119 "doc_url": self.doc_url,
120 }