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
« 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
5from __future__ import annotations
7import re
8from collections import namedtuple
9from collections.abc import Generator
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)
30PragmaRepresenter = namedtuple("PragmaRepresenter", "action messages")
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)
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]
53TOK_REGEX = "|".join(
54 f"(?P<{token_name:s}>{token_rgx:s})"
55 for token_name, token_rgx in TOKEN_SPECIFICATION
56)
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)
67class PragmaParserError(Exception):
68 """A class for exceptions thrown by pragma_parser module."""
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)
79class UnRecognizedOptionError(PragmaParserError):
80 """Thrown in case the of a valid but unrecognized option."""
83class InvalidPragmaError(PragmaParserError):
84 """Thrown in case the pragma is invalid."""
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 = ""
93 for mo in re.finditer(TOK_REGEX, pylint_pragma):
94 kind = mo.lastgroup
95 value = mo.group()
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")
128 previous_token = value
130 if action:
131 yield emit_pragma_representer(action, messages)
132 else:
133 raise UnRecognizedOptionError("The keyword is unknown", previous_token)