Skip to content

Commit be22867

Browse files
authored
Magic comments to turn off specific detections (#17)
Added magic comment functionality to turn off specific detections.
1 parent b1a14f9 commit be22867

20 files changed

+235
-146
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ test_ci: venv
3232
@echo -e "${GREEN}======== Installing Stacy for Clarity ========${NC}"
3333
./venv/bin/pip install $(REPO_ROOT)
3434
@echo -e "${GREEN}======== Testing detectors ========${NC}"
35-
cd tests/ && ../venv/bin/python3 -m unittest test_module1 > $(GITHUB_WORKSPACE)/test.out 2>&1 && cd ..
35+
cd tests && ../venv/bin/python3 -m unittest test_module1 > $(GITHUB_WORKSPACE)/test.out 2>&1 && cd ..
3636

3737
unittest: venv
3838
./venv/bin/pip uninstall stacy-analyzer -y > /dev/null 2>&1

src/stacy_analyzer/analyzer.py

+2-22
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,9 @@
44
import sys
55
from importlib.util import spec_from_file_location, module_from_spec
66

7+
from stacy_analyzer.linter_runner import LinterRunner
78
from stacy_analyzer.print_message import TerminalColors
8-
from stacy_analyzer.visitor import LinterRunner, Visitor, Finding
9-
10-
11-
class Number(object):
12-
def __init__(self, n):
13-
self.value = n
14-
15-
def val(self):
16-
return self.value
17-
18-
def add(self, n2):
19-
self.value += n2.val()
20-
21-
def __add__(self, n2):
22-
return self.__class__(self.value + n2.val())
23-
24-
def __str__(self):
25-
return str(self.val())
26-
27-
@classmethod
28-
def addall(cls, number_obj_iter):
29-
cls(sum(n.val() for n in number_obj_iter))
9+
from stacy_analyzer.visitor import Visitor, Finding
3010

3111

3212
class Analyzer:

src/stacy_analyzer/detectors/AssertBlockHeight.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from tree_sitter import Node
2-
3-
from stacy_analyzer.visitor import Visitor, NodeIterator
2+
from stacy_analyzer.node_iterator import NodeIterator
3+
from stacy_analyzer.visitor import Visitor
44

55

66
class AssertBlockHeight(Visitor):

src/stacy_analyzer/detectors/CallInsideAsContract.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from tree_sitter import Node
2+
from stacy_analyzer.visitor import Visitor
3+
from stacy_analyzer.node_iterator import NodeIterator
24

3-
from stacy_analyzer.visitor import Visitor, NodeIterator
45

56

67
class CallInsideAsContract(Visitor):

src/stacy_analyzer/detectors/DivideBeforeMultiply.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from tree_sitter import Node
2+
from stacy_analyzer.visitor import Visitor
3+
from stacy_analyzer.node_iterator import NodeIterator
24

3-
from stacy_analyzer.visitor import Visitor, NodeIterator
45

56

67
class DivideBeforeMultiply(Visitor):

src/stacy_analyzer/detectors/PrivateFunctionNotUsed.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from tree_sitter import Node
2-
32
from stacy_analyzer.visitor import Visitor
43

54

src/stacy_analyzer/detectors/ToDoComment.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from tree_sitter import Node
2-
32
from stacy_analyzer.visitor import Visitor
43

54

src/stacy_analyzer/detectors/TxSenderInAssert.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from tree_sitter import Node
2+
from stacy_analyzer.visitor import Visitor
3+
from stacy_analyzer.node_iterator import NodeIterator
24

3-
from stacy_analyzer.visitor import Visitor, NodeIterator
45

56

67
class TxSenderInAssert(Visitor):

src/stacy_analyzer/detectors/UnusedArguments.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from tree_sitter import Node
2-
from stacy_analyzer.visitor import Visitor, NodeIterator
2+
from stacy_analyzer.visitor import Visitor
3+
from stacy_analyzer.node_iterator import NodeIterator
34

45

56
class UnusedArguments(Visitor):

src/stacy_analyzer/detectors/UnusedLetVariables.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from tree_sitter import Node
2-
from stacy_analyzer.visitor import Visitor, NodeIterator
2+
from stacy_analyzer.visitor import Visitor
3+
from stacy_analyzer.node_iterator import NodeIterator
34

45

56
class UnusedLetVariables(Visitor):
@@ -30,3 +31,4 @@ def visit_node(self, node: Node, i):
3031
self.HELP = ""
3132
self.FOOTNOTE = f"Consider removing '{k}' from let function since its not used."
3233
self.add_finding(v, v)
34+

src/stacy_analyzer/detectors/UnwrapPanicUsage.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from tree_sitter import Node
2-
32
from stacy_analyzer.visitor import Visitor
43

54

src/stacy_analyzer/detectors/UpdatedFunctions.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from tree_sitter import Node
2-
32
from stacy_analyzer.visitor import Visitor
43

54

src/stacy_analyzer/detectors/VarCouldBeConstant.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from tree_sitter import Node
2-
32
from stacy_analyzer.visitor import Visitor
43

54

src/stacy_analyzer/linter_runner.py

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import re
2+
3+
import tree_sitter_clarity
4+
from stacy_analyzer.print_message import TerminalColors
5+
from tree_sitter import Language, Tree, Node, Parser
6+
7+
__CLARITY__ = Language(tree_sitter_clarity.language())
8+
__TIMES__ = 3
9+
10+
from stacy_analyzer.visitor import Visitor
11+
from stacy_analyzer.node_iterator import NodeIterator
12+
13+
14+
class LinterRunner:
15+
source: str
16+
tree: Tree
17+
root_node: Node
18+
iterator: NodeIterator
19+
lints: [Visitor]
20+
round_number: int
21+
allowed_lints: dict[str, ([int], Node)] = {}
22+
23+
def __init__(self, source: str, print_output: bool, src_name: str):
24+
self.src_name = src_name
25+
self.source = source
26+
parser = Parser(__CLARITY__)
27+
self.tree = parser.parse(bytes(self.source, "utf8"))
28+
self.root_node = self.tree.root_node
29+
self.iterator = NodeIterator(self.root_node)
30+
self.lints = []
31+
self.round_number = 0
32+
self.print_output = print_output
33+
self.allowed_lints = {}
34+
35+
def run_lints(self, node: Node):
36+
for lint in self.lints:
37+
lint.visit_node(node, self.round_number)
38+
39+
def add_lint(self, lint):
40+
self.lints.append(lint)
41+
return self
42+
43+
def add_lints(self, lint_classes: [Visitor]):
44+
for lint_class in lint_classes:
45+
lint = lint_class(self.print_output)
46+
lint.add_source(self.source, self.src_name)
47+
self.lints.append(lint)
48+
49+
def reset_cursor(self):
50+
self.iterator = NodeIterator(self.root_node)
51+
52+
def run(self):
53+
self.pre_run_checks()
54+
for i in range(__TIMES__):
55+
self.round_number = self.round_number + 1
56+
57+
for node in self.iterator:
58+
self.run_lints(node)
59+
self.reset_cursor()
60+
61+
self.post_run_checks()
62+
63+
return [finding for lint in self.lints
64+
for finding in lint.get_findings()]
65+
66+
def pre_run_checks(self):
67+
for node in self.iterator:
68+
allowed: [AllowedComment] = check_comments(node)
69+
if not allowed:
70+
continue
71+
for allowed_comment in allowed:
72+
if allowed_comment.name not in self.allowed_lints:
73+
self.allowed_lints[allowed_comment.name] = (
74+
[allowed_comment.line_number], allowed_comment.comment_node)
75+
else:
76+
self.allowed_lints[allowed_comment.name][0].append(allowed_comment.line_number)
77+
78+
self.reset_cursor()
79+
for lint in self.lints:
80+
lint.set_ignores(self.allowed_lints)
81+
82+
def post_run_checks(self):
83+
for lint in self.lints:
84+
findings: dict[str, ([int], Node)] = lint.get_ignored_findings()
85+
name = lint.Name
86+
if name not in self.allowed_lints or findings[name][0] == []:
87+
continue
88+
warn_magic_comment(findings[name][0], name)
89+
90+
91+
class AllowedComment:
92+
name: str
93+
comment_node: Node
94+
line_number: int
95+
96+
def __init__(self, finding: str, comment_node: Node):
97+
self.name = finding
98+
self.comment_node = comment_node
99+
self.line_number = comment_node.range.start_point.row + 1
100+
101+
102+
def extract_detectors(comment_text: str) -> [str]:
103+
allow_comment = re.search(r'#\[allow\((.*?)\)]', comment_text)
104+
return allow_comment.group(1).split(',') if allow_comment else []
105+
106+
107+
def check_comments(node) -> [AllowedComment]:
108+
if node.grammar_name != "comment":
109+
return []
110+
comment_text = node.text.decode("utf-8")
111+
detectors = extract_detectors(comment_text)
112+
113+
return [AllowedComment(detector.strip(), node) for detector in detectors]
114+
115+
116+
def warn_magic_comment(lines: [int], name: str):
117+
for line in lines:
118+
print(f"{TerminalColors.ERROR}[WARNING]{TerminalColors.ENDC} "
119+
f"Magic comment in line {line} for detector {TerminalColors.BOLD}{name}{TerminalColors.ENDC} is not used")

src/stacy_analyzer/node_iterator.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from tree_sitter import Node, TreeCursor
2+
3+
4+
class NodeIterator:
5+
root_node: Node
6+
cursor: TreeCursor
7+
visited = []
8+
9+
def __init__(self, node: Node):
10+
self.root_node = node
11+
self.cursor = node.walk()
12+
self.visited = []
13+
14+
while self.cursor.goto_first_child():
15+
pass
16+
17+
def next(self) -> Node | None:
18+
while True:
19+
node = self.node()
20+
21+
if node not in self.visited:
22+
if self.cursor.goto_first_child():
23+
continue
24+
self.visited.append(node)
25+
return node
26+
27+
if self.cursor.goto_next_sibling():
28+
while self.cursor.goto_first_child():
29+
pass
30+
else:
31+
32+
if not self.cursor.goto_parent():
33+
return None
34+
parent_node = self.cursor.node
35+
self.visited.append(parent_node)
36+
return parent_node
37+
38+
def node(self) -> Node | None:
39+
return self.cursor.node
40+
41+
def __iter__(self):
42+
return self
43+
44+
def __next__(self) -> Node | None:
45+
node = self.next()
46+
if node is None:
47+
raise StopIteration
48+
return node
49+

src/stacy_analyzer/print_message.py

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
class TerminalColors:
1010
HEADER = '\033[95m' if tty else ''
1111
OKCYAN = '\033[96m' if tty else ''
12+
ERROR = '\033[31;1;4m' if tty else ''
13+
BOLD = '\033[1m' if tty else ''
1214
ENDC = '\033[0m' if tty else ''
1315

1416

0 commit comments

Comments
 (0)