-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathcheck_annotations.py
More file actions
126 lines (105 loc) · 4.18 KB
/
check_annotations.py
File metadata and controls
126 lines (105 loc) · 4.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import re
from typing import Mapping
import libcst as cst
from libcst import CSTVisitor
from libcst._nodes.base import CSTNode # noqa: F401
from libcst.metadata import ParentNodeProvider
from libcst.metadata.base_provider import ProviderT # noqa: F401
from pylint.utils.pragma_parser import parse_pragma
NOQA_PATTERN = re.compile(r"^#\s*noqa(:\s+[A-Z]+[A-Z0-9]+)?", re.IGNORECASE)
__all__ = ["is_disabled_by_annotations"]
class _GatherCommentNodes(CSTVisitor):
METADATA_DEPENDENCIES = (ParentNodeProvider,)
messages: list[str]
def __init__(
self,
metadata: Mapping[ProviderT, Mapping[CSTNode, object]],
messages: list[str],
) -> None:
self.comments: list[cst.Comment] = []
super().__init__()
self.metadata = metadata
self.messages = messages
def leave_Comment(self, original_node: cst.Comment) -> None:
self.comments.append(original_node)
def _process_simple_statement_line(self, stmt: cst.SimpleStatementLine) -> bool:
# has a trailing comment string anywhere in the node
stmt.body[0].visit(self)
# has a trailing comment string anywhere in the node
if stmt.trailing_whitespace.comment:
self.comments.append(stmt.trailing_whitespace.comment)
for comment in self.comments:
trailing_comment_string = comment.value
if trailing_comment_string and self._noqa_message_match(
trailing_comment_string
):
return True
if trailing_comment_string and self._is_pylint_disable_unused_imports(
trailing_comment_string
):
return True
# has a comment right above it
match stmt:
case cst.SimpleStatementLine(
leading_lines=[
*_,
cst.EmptyLine(comment=cst.Comment(value=comment_string)),
]
):
return self._noqa_message_match(comment_string) or (
self._is_pylint_disable_next_unused_imports(comment_string)
)
return False
def is_disabled_by_linter(self, node: cst.CSTNode) -> bool:
"""
Check if the import has a #noqa or # pylint: disable(-next) comment attached to it.
"""
match self.get_metadata(ParentNodeProvider, node):
case cst.SimpleStatementLine() as stmt:
return self._process_simple_statement_line(stmt)
case cst.Expr() as expr:
match self.get_metadata(ParentNodeProvider, expr):
case cst.SimpleStatementLine() as stmt:
return self._process_simple_statement_line(stmt)
return False
def _noqa_message_match(self, comment: str) -> bool:
if not (match := NOQA_PATTERN.match(comment)):
return False
if match.group(1):
return match.group(1).strip(":").strip() in self.messages
return True
def _is_pylint_disable_unused_imports(self, comment: str) -> bool:
# If pragma parse fails, ignore
try:
parsed = parse_pragma(comment)
for p in parsed:
if p.action == "disable" and any(
message in p.messages for message in self.messages
):
return True
except Exception:
pass
return False
def _is_pylint_disable_next_unused_imports(self, comment: str) -> bool:
# If pragma parse fails, ignore
try:
parsed = parse_pragma(comment)
for p in parsed:
if p.action == "disable-next" and any(
message in p.messages for message in self.messages
):
return True
except Exception:
pass
return False
def is_disabled_by_annotations(
node: cst.CSTNode,
metadata: Mapping[ProviderT, Mapping[CSTNode, object]],
messages: list[str],
) -> bool:
"""
Check if the import has a #noqa or # pylint: disable(-next) comment attached to it.
"""
visitor = _GatherCommentNodes(metadata, messages)
node.visit(visitor)
return visitor.is_disabled_by_linter(node)