Skip to content

Commit a78cf81

Browse files
authored
Added -A, -B and -C flags for context in messages (#22)
Added -A, -B and -C flags for context in messages, with the same functionality as `grep` of giving more context on each find.
1 parent 563475a commit a78cf81

File tree

6 files changed

+66
-24
lines changed

6 files changed

+66
-24
lines changed

src/stacy_analyzer/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.1.0"
1+
__version__ = "1.0.0"
22

33
from .analyzer import *
44

src/stacy_analyzer/analyzer.py

+25-5
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,39 @@ def main(self):
5252
lint_parser.add_argument("--exclude", nargs="+", type=str,
5353
help="Comma-separated list of detector names to exclude")
5454
list_detectors = subparsers.add_parser("detectors", help="List detectors")
55-
55+
lint_parser.add_argument("-A", nargs="+", type=int,
56+
help="Print A lines of trailing context after findings.")
57+
lint_parser.add_argument("-B", nargs="+", type=int,
58+
help="Print B lines of leading context before findings.")
59+
lint_parser.add_argument("-C", nargs="+", type=int,
60+
help="Print C lines of leading and trailing context after and before findings. Takes precedence over -A and -B")
5661
user_args = arg_parser.parse_args()
5762
if user_args.command == "lint":
63+
64+
tc = 0
65+
lc = 0
66+
67+
if user_args.A is not None:
68+
tc = user_args.A[0]
69+
70+
if user_args.B is not None:
71+
lc = user_args.B[0]
72+
73+
if user_args.C is not None:
74+
tc = user_args.C[0]
75+
lc = user_args.C[0]
76+
5877
filters = list(self.DETECTOR_MAP.keys()) if user_args.filter is None else user_args.filter[0].split(',')
5978
excludes = [] if user_args.exclude is None else user_args.exclude[0].split(',')
6079
detectors = self.get_detectors(filters, excludes)
6180
path = user_args.path
6281
if path.endswith(".clar"):
63-
self.lint_file(path, detectors, True)
82+
self.lint_file(path, detectors, True, lc, tc)
6483
else:
6584
for root, _, files in os.walk(path):
6685
for file in files:
6786
if file.endswith(".clar"):
68-
self.lint_file(os.path.join(root, file), detectors, True)
87+
self.lint_file(os.path.join(root, file), detectors, True, lc, tc)
6988

7089
if user_args.command == "detectors":
7190
convert_camel_case = lambda s: s[0] + ''.join(' ' + c if c.isupper() else c for c in s[1:])
@@ -103,7 +122,8 @@ def get_detectors(self, filters: str, excludes: str):
103122

104123
return [self.DETECTOR_MAP[name] for name in filtered_names]
105124

106-
def lint_file(self, filename, lints: [Visitor], print_output: bool):
125+
def lint_file(self, filename, lints: [Visitor], print_output: bool, leading: int, trailing: int):
126+
107127
if print_output:
108128
if self.isatty:
109129
print(f"{TerminalColors.HEADER}====== Linting {filename}... ======{TerminalColors.ENDC}")
@@ -113,7 +133,7 @@ def lint_file(self, filename, lints: [Visitor], print_output: bool):
113133
source = file.read()
114134

115135
runner: LinterRunner = LinterRunner(source, print_output, filename)
116-
runner.add_lints(lints)
136+
runner.add_lints(lints, leading, trailing)
117137

118138
findings: [Finding] = runner.run()
119139

src/stacy_analyzer/linter_runner.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ def add_lint(self, lint):
4040
self.lints.append(lint)
4141
return self
4242

43-
def add_lints(self, lint_classes: [Visitor]):
43+
def add_lints(self, lint_classes: [Visitor], leading: int, trailing: int):
4444
for lint_class in lint_classes:
4545
lint = lint_class(self.print_output)
46+
lint.set_context(leading, trailing)
4647
lint.add_source(self.source, self.src_name)
4748
self.lints.append(lint)
4849

src/stacy_analyzer/print_message.py

+26-12
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,42 @@ class TerminalColors:
1111
OKCYAN = '\033[96m' if tty else ''
1212
ERROR = '\033[31;1;4m' if tty else ''
1313
BOLD = '\033[1m' if tty else ''
14+
GREY = '\033[38;5;248m' if tty else ''
1415
ENDC = '\033[0m' if tty else ''
16+
EXCYAN = ''
17+
1518

1619

1720
def pretty_print_warn(visitor, parent: Node, specific_node: Node, msg: str, help_msg: str | None,
18-
footnote: str | None):
21+
footnote: str | None, leading_context: int, trailing_context: int):
1922
line_number = parent.start_point.row + 1
2023
num_size_spaces = " " * (int(math.log10(line_number)) + 2)
21-
contract_code = visitor.source.split('\n')[line_number - 1]
22-
start_tabs = contract_code.count('\t') + 1
23-
contract_code = contract_code.replace('\t', ' ')
2424

25-
arrows = "^" * (specific_node.end_point.column - specific_node.start_point.column)
26-
spaces = " " * ((specific_node.start_point.column * start_tabs) + 1)
25+
all_lines = visitor.source.split('\n')
26+
total_lines = len(all_lines)
27+
28+
start_line = max(0, line_number - leading_context - 1)
29+
end_line = min(total_lines, line_number + trailing_context)
2730

2831
print(f"{TerminalColors.OKCYAN}Warning:{TerminalColors.ENDC} {msg}")
32+
print(f" {num_size_spaces}{TerminalColors.OKCYAN}|{TerminalColors.ENDC}")
33+
34+
for i in range(start_line, end_line):
35+
current_line = i + 1
36+
contract_code = all_lines[i]
37+
start_tabs = contract_code.count('\t') + 1
38+
contract_code = contract_code.replace('\t', ' ')
39+
40+
print(f" {TerminalColors.OKCYAN}{current_line} |{TerminalColors.ENDC} {contract_code}")
41+
42+
if current_line == line_number:
43+
arrows = "^" * (specific_node.end_point.column - specific_node.start_point.column)
44+
spaces = " " * ((specific_node.start_point.column * start_tabs) + 1)
45+
print(f" {num_size_spaces}{TerminalColors.OKCYAN}|{TerminalColors.ENDC}{spaces}{TerminalColors.OKCYAN}{arrows}{TerminalColors.ENDC}")
46+
if help_msg is not None:
47+
print(f" {num_size_spaces}{TerminalColors.OKCYAN}|{TerminalColors.ENDC}{spaces}{help_msg}")
2948

30-
print(f" {num_size_spaces}|")
31-
print(f" {line_number} | {contract_code}")
32-
print(f" {num_size_spaces}|{spaces}{TerminalColors.OKCYAN}{arrows}{TerminalColors.ENDC}")
33-
if help_msg is not None:
34-
print(f" {num_size_spaces}|{spaces}{help_msg}")
3549
if footnote is not None:
36-
print(f" {num_size_spaces}{TerminalColors.OKCYAN}Note: {TerminalColors.ENDC}{footnote}")
50+
print(f" {num_size_spaces}{TerminalColors.OKCYAN}Note: {TerminalColors.GREY}{footnote}{TerminalColors.ENDC}")
3751

3852
print()

src/stacy_analyzer/visitor.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from tree_sitter import Node
55

66
from stacy_analyzer.print_message import pretty_print_warn
7-
7+
LEADING_CONTEXT = 0
8+
TRAILING_CONTEXT = 0
89

910
@dataclass
1011
class Location:
@@ -32,13 +33,19 @@ class Visitor:
3233
FOOTNOTE: str | None
3334
print_output: bool
3435
ignores: dict[str, ([int], Node)]
36+
leading_context: int
37+
trailing_context: int
3538

36-
def __init__(self, print_output: bool):
39+
def __init__(self, print_output: bool, ):
3740
self.ignores = {}
3841
self.source = self.src_name = None
3942
self.findings = []
4043
self.print_output = print_output
4144

45+
def set_context(self, leading: int, trailing: int):
46+
self.leading_context = leading
47+
self.trailing_context = trailing
48+
4249
def add_source(self, src: str, src_name: str = None):
4350
self.source = src
4451
self.src_name = src_name
@@ -58,7 +65,7 @@ def add_finding(self, node: Node, specific_node: Node):
5865
return
5966

6067
if self.print_output:
61-
pretty_print_warn(self, node, specific_node, self.MSG, self.HELP, self.FOOTNOTE)
68+
pretty_print_warn(self, node, specific_node, self.MSG, self.HELP, self.FOOTNOTE, self.leading_context, self.trailing_context)
6269

6370
parent = node.parent
6471
line_number = parent.start_point.row + 1

tests/test_module1.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def test_detector_file(self):
2323
a = Analyzer()
2424
for detector in a.DETECTOR_MAP.values():
2525
a = Analyzer()
26-
ret = a.lint_file(filename, [detector], False)
26+
ret = a.lint_file(filename, [detector], False, 0, 0)
2727
is_vulnerable_path = "vulnerable" in filename
2828
is_correct_detector = detector.__name__.lower() in filename.replace('_', '')
2929

@@ -45,7 +45,7 @@ def test_profile_time(self):
4545
start = time.time()
4646
for _ in range(1000):
4747
a = Analyzer()
48-
a.lint_file(filename, [detectorKlass for detectorKlass in lints], False)
48+
a.lint_file(filename, [detectorKlass for detectorKlass in lints], False, 0, 0)
4949
end = time.time()
5050
print(f'Running 1000 times in `unused_arguments` test case. Took: {end - start:f}s')
5151

0 commit comments

Comments
 (0)