Coverage for src/castep_linter/error_logging/error_types.py: 63%

1"""Module to handle errors, warnings and info messages"""

2 from typing import ClassVar

3

4 from castep_linter.fortran.fortran_node import FortranNode

5

6

7 class FortranMsgBase:

8 """Base class for other static analysis problems to inherit from"""

9

10 ERROR_TYPE: ClassVar[str] = "NONE"

11 ERROR_STYLE: ClassVar[str] = "grey"

12 LINE_NUMBER_OFFSET = 8

13 ERROR_SEVERITY: ClassVar[int] = 100

14

15 def __init__(self, node: FortranNode, message: str) -> None:

16 self.message = message

17 self.start_point = node.node.start_point # TODO FIX

18 self.end_point = node.node.end_point

19

20 def print_err(self, filename: str, console) -> None:

21 """Print the error to the supplied console"""

22 console.print(self, style=self.ERROR_STYLE)

23 context = self.context(filename, underline=True)

24 if context:

25 console.print(context)

26

27 def context(self, filename, *, underline=False):

28 """Print a line of context for the current error"""

29 context = ""

30

31 with open(filename, "rb") as fd:

32 start_line, start_char = self.start_point

33 end_line, end_char = self.end_point

34 num_lines = end_line - start_line + 1

35 num_chars = end_char - start_char

36

37 file_str = str(filename)

38

39 if num_lines == 1:

40 line = fd.read().splitlines()[start_line].decode(errors="replace")

41 context = f"{file_str}:{start_line+1:{self.LINE_NUMBER_OFFSET}}>{line}"

42 if underline:

43 context += (

44 "\n"

45 + " " * (len(file_str) + 1)

46 + " " * (self.LINE_NUMBER_OFFSET + 1)

47 + " " * start_char

48 + "^" * num_chars

49 )

50 return context

51

52 def __repr__(self):

53 return f"{self.ERROR_TYPE}: {self.message}"

54

55

56 class FortranError(FortranMsgBase):

57 """Fatal static analysis problem in code"""

58

59 ERROR_TYPE: ClassVar[str] = "Error"

60 ERROR_STYLE: ClassVar[str] = "red"

61 ERROR_SEVERITY: ClassVar[int] = 2

62

63

64 class FortranWarning(FortranMsgBase):

65 """Warning message from static analysis"""

66

67 ERROR_TYPE: ClassVar[str] = "Warning"

68 ERROR_STYLE: ClassVar[str] = "yellow"

69 ERROR_SEVERITY: ClassVar[int] = 1

70

71

72 class FortranInfo(FortranMsgBase):

73 """Warning message from static analysis"""

74

75 ERROR_TYPE: ClassVar[str] = "Info"

76 ERROR_STYLE: ClassVar[str] = "Blue"

77 ERROR_SEVERITY: ClassVar[int] = 0

78

79

80 def new_fortran_error(level: str, node: FortranNode, message: str) -> FortranMsgBase:

81 """Generate a new fortran diagnostic message"""

82 cls = FortranMsgBase

83 if level == "Error":

84 cls = FortranError

85 elif level == "Warning":

86 cls = FortranWarning

87 elif level == "Info": 87 ↛ 90line 87 didn't jump to line 90, because the condition on line 87 was never false

88 cls = FortranInfo

89 else:

90 raise ValueError("Unknown fortran diagnostic message type: " + level)

91 return cls(node, message)

92

93

94 FORTRAN_ERRORS = {

95 "Error": FortranError,

96 "Warn": FortranWarning,

97 "Info": FortranInfo,

98}

99

100 ERROR_SEVERITY = {k: v.ERROR_SEVERITY for k, v in FORTRAN_ERRORS.items()}