Coverage for C:\Repos\ekr-pylint\pylint\utils\pragma_parser.py: 33%

55 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-24 10:21 -0500

1# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html 

2# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE 

3# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt 

4 

5from __future__ import annotations 

6 

7import re 

8from collections import namedtuple 

9from collections.abc import Generator 

10 

11# Allow stopping after the first semicolon/hash encountered, 

12# so that an option can be continued with the reasons 

13# why it is active or disabled. 

14OPTION_RGX = r""" 

15 (?:^\s*\#.*|\s*| # Comment line, or whitespaces, 

16 \s*\#.*(?=\#.*?\bpylint:)) # or a beginning of an inline comment 

17 # followed by "pylint:" pragma 

18 (\# # Beginning of comment 

19 .*? # Anything (as little as possible) 

20 \bpylint: # pylint word and column 

21 \s* # Any number of whitespaces 

22 ([^;#\n]+)) # Anything except semicolon or hash or 

23 # newline (it is the second matched group) 

24 # and end of the first matched group 

25 [;#]{0,1} # From 0 to 1 repetition of semicolon or hash 

26""" 

27OPTION_PO = re.compile(OPTION_RGX, re.VERBOSE) 

28 

29 

30PragmaRepresenter = namedtuple("PragmaRepresenter", "action messages") 

31 

32 

33ATOMIC_KEYWORDS = frozenset(("disable-all", "skip-file")) 

34MESSAGE_KEYWORDS = frozenset( 

35 ("disable-next", "disable-msg", "enable-msg", "disable", "enable") 

36) 

37# sorted is necessary because sets are unordered collections and ALL_KEYWORDS 

38# string should not vary between executions 

39# reverse is necessary in order to have the longest keywords first, so that, for example, 

40# 'disable' string should not be matched instead of 'disable-all' 

41ALL_KEYWORDS = "|".join( 

42 sorted(ATOMIC_KEYWORDS | MESSAGE_KEYWORDS, key=len, reverse=True) 

43) 

44 

45 

46TOKEN_SPECIFICATION = [ 

47 ("KEYWORD", rf"\b({ALL_KEYWORDS:s})\b"), 

48 ("MESSAGE_STRING", r"[0-9A-Za-z\-\_]{2,}"), # Identifiers 

49 ("ASSIGN", r"="), # Assignment operator 

50 ("MESSAGE_NUMBER", r"[CREIWF]{1}\d*"), 

51] 

52 

53TOK_REGEX = "|".join( 

54 f"(?P<{token_name:s}>{token_rgx:s})" 

55 for token_name, token_rgx in TOKEN_SPECIFICATION 

56) 

57 

58 

59def emit_pragma_representer(action: str, messages: list[str]) -> PragmaRepresenter: 

60 if not messages and action in MESSAGE_KEYWORDS: 

61 raise InvalidPragmaError( 

62 "The keyword is not followed by message identifier", action 

63 ) 

64 return PragmaRepresenter(action, messages) 

65 

66 

67class PragmaParserError(Exception): 

68 """A class for exceptions thrown by pragma_parser module.""" 

69 

70 def __init__(self, message: str, token: str) -> None: 

71 """:args message: explain the reason why the exception has been thrown 

72 :args token: token concerned by the exception. 

73 """ 

74 self.message = message 

75 self.token = token 

76 super().__init__(self.message) 

77 

78 

79class UnRecognizedOptionError(PragmaParserError): 

80 """Thrown in case the of a valid but unrecognized option.""" 

81 

82 

83class InvalidPragmaError(PragmaParserError): 

84 """Thrown in case the pragma is invalid.""" 

85 

86 

87def parse_pragma(pylint_pragma: str) -> Generator[PragmaRepresenter, None, None]: 

88 action: str | None = None 

89 messages: list[str] = [] 

90 assignment_required = False 

91 previous_token = "" 

92 

93 for mo in re.finditer(TOK_REGEX, pylint_pragma): 

94 kind = mo.lastgroup 

95 value = mo.group() 

96 

97 if kind == "ASSIGN": 

98 if not assignment_required: 

99 if action: 

100 # A keyword has been found previously but doesn't support assignment 

101 raise UnRecognizedOptionError( 

102 "The keyword doesn't support assignment", action 

103 ) 

104 if previous_token: 

105 # Something found previously but not a known keyword 

106 raise UnRecognizedOptionError( 

107 "The keyword is unknown", previous_token 

108 ) 

109 # Nothing at all detected before this assignment 

110 raise InvalidPragmaError("Missing keyword before assignment", "") 

111 assignment_required = False 

112 elif assignment_required: 

113 raise InvalidPragmaError( 

114 "The = sign is missing after the keyword", action or "" 

115 ) 

116 elif kind == "KEYWORD": 

117 if action: 

118 yield emit_pragma_representer(action, messages) 

119 action = value 

120 messages = [] 

121 assignment_required = action in MESSAGE_KEYWORDS 

122 elif kind in {"MESSAGE_STRING", "MESSAGE_NUMBER"}: 

123 messages.append(value) 

124 assignment_required = False 

125 else: 

126 raise RuntimeError("Token not recognized") 

127 

128 previous_token = value 

129 

130 if action: 

131 yield emit_pragma_representer(action, messages) 

132 else: 

133 raise UnRecognizedOptionError("The keyword is unknown", previous_token)