Skip to content

Commit 1ac9247

Browse files
committed
feat: enhance diagnostics and analysis features with progress indicators in verbose mode and performance improvements
1 parent 9b4eca7 commit 1ac9247

File tree

10 files changed

+359
-130
lines changed

10 files changed

+359
-130
lines changed

.vscode/launch.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@
4040
"processId": "${command:pickProcess}",
4141
"justMyCode": false
4242
},
43+
{
44+
"name": "Python: Attach using Process",
45+
"type": "debugpy",
46+
"request": "attach",
47+
"processId": 41720,
48+
"justMyCode": false
49+
},
4350
{
4451
"name": "Python: Create Cmd Line Doc",
4552
"type": "debugpy",

packages/analyze/src/robotcode/analyze/code/cli.py

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import functools
2+
import time
13
from enum import Flag
24
from pathlib import Path
35
from textwrap import indent
@@ -36,43 +38,73 @@ class ReturnCode(Flag):
3638
HINTS = 8
3739

3840

39-
class Statistic:
41+
class ResultCollector:
4042
def __init__(self, exit_code_mask: ExitCodeMask) -> None:
4143
self.exit_code_mask = exit_code_mask
4244
self._folders: Set[WorkspaceFolder] = set()
4345
self._files: Set[TextDocument] = set()
44-
self._diagnostics: List[Union[DocumentDiagnosticReport, FolderDiagnosticReport]] = []
46+
self.diagnostics: List[Union[DocumentDiagnosticReport, FolderDiagnosticReport]] = []
47+
self._start_time = time.time()
48+
self._end_time = self._start_time
49+
50+
@staticmethod
51+
def _format_duration(seconds: float) -> str:
52+
total_seconds = max(0.0, seconds)
53+
54+
hours, remainder = divmod(total_seconds, 3600.0)
55+
minutes, seconds_remainder = divmod(remainder, 60.0)
56+
57+
hours_int = int(hours)
58+
minutes_int = int(minutes)
59+
60+
if hours_int > 0:
61+
return f"{hours_int}h {minutes_int}m {seconds_remainder:.2f}s"
62+
if minutes_int > 0:
63+
return f"{minutes_int}m {seconds_remainder:.2f}s"
64+
return f"{seconds_remainder:.2f}s"
65+
66+
def start(self) -> float:
67+
self._start_time = time.time()
68+
return self._start_time
69+
70+
def stop(self) -> float:
71+
self._end_time = time.time()
72+
return self._end_time
4573

4674
@property
75+
def elapsed(self) -> float:
76+
return self._end_time - self._start_time
77+
78+
@functools.cached_property
4779
def errors(self) -> int:
4880
return sum(
49-
len([i for i in e.items if i.severity == DiagnosticSeverity.ERROR]) for e in self._diagnostics if e.items
81+
len([i for i in e.items if i.severity == DiagnosticSeverity.ERROR]) for e in self.diagnostics if e.items
5082
)
5183

52-
@property
84+
@functools.cached_property
5385
def warnings(self) -> int:
5486
return sum(
55-
len([i for i in e.items if i.severity == DiagnosticSeverity.WARNING]) for e in self._diagnostics if e.items
87+
len([i for i in e.items if i.severity == DiagnosticSeverity.WARNING]) for e in self.diagnostics if e.items
5688
)
5789

58-
@property
90+
@functools.cached_property
5991
def infos(self) -> int:
6092
return sum(
6193
len([i for i in e.items if i.severity == DiagnosticSeverity.INFORMATION])
62-
for e in self._diagnostics
94+
for e in self.diagnostics
6395
if e.items
6496
)
6597

66-
@property
98+
@functools.cached_property
6799
def hints(self) -> int:
68100
return sum(
69-
len([i for i in e.items if i.severity == DiagnosticSeverity.HINT]) for e in self._diagnostics if e.items
101+
len([i for i in e.items if i.severity == DiagnosticSeverity.HINT]) for e in self.diagnostics if e.items
70102
)
71103

72104
def add_diagnostics_report(
73105
self, diagnostics_report: Union[DocumentDiagnosticReport, FolderDiagnosticReport]
74106
) -> None:
75-
self._diagnostics.append(diagnostics_report)
107+
self.diagnostics.append(diagnostics_report)
76108

77109
if isinstance(diagnostics_report, FolderDiagnosticReport):
78110
self._folders.add(diagnostics_report.folder)
@@ -83,6 +115,7 @@ def __str__(self) -> str:
83115
return (
84116
f"Files: {len(self._files)}, Errors: {self.errors}, Warnings: {self.warnings}, "
85117
f"Infos: {self.infos}, Hints: {self.hints}"
118+
f" (in {self._format_duration(self.elapsed)})"
86119
)
87120

88121
def calculate_return_code(self) -> ReturnCode:
@@ -363,32 +396,37 @@ def code(
363396
app.verbose(f"Using analyzer_config: {analyzer_config}")
364397
app.verbose(f"Using exit code mask: {mask}")
365398

366-
statistics = Statistic(mask)
367-
for e in CodeAnalyzer(
368-
app=app,
369-
analysis_config=analyzer_config.to_workspace_analysis_config(),
370-
robot_profile=robot_profile,
371-
root_folder=root_folder,
372-
).run(paths=paths, filter=filter):
373-
statistics.add_diagnostics_report(e)
374-
375-
if isinstance(e, FolderDiagnosticReport):
376-
if e.items:
377-
_print_diagnostics(app, root_folder, e.items, e.folder.uri.to_path())
378-
elif isinstance(e, DocumentDiagnosticReport):
379-
doc_path = (
380-
e.document.uri.to_path().relative_to(root_folder) if root_folder else e.document.uri.to_path()
381-
)
382-
if e.items:
383-
_print_diagnostics(app, root_folder, e.items, doc_path)
399+
result_collector = ResultCollector(mask)
400+
result_collector.start()
401+
try:
402+
for e in CodeAnalyzer(
403+
app=app,
404+
analysis_config=analyzer_config.to_workspace_analysis_config(),
405+
robot_profile=robot_profile,
406+
root_folder=root_folder,
407+
).run(paths=paths, filter=filter):
408+
result_collector.add_diagnostics_report(e)
409+
410+
for e in result_collector.diagnostics:
411+
if isinstance(e, FolderDiagnosticReport):
412+
if e.items:
413+
_print_diagnostics(app, root_folder, e.items, e.folder.uri.to_path())
414+
elif isinstance(e, DocumentDiagnosticReport):
415+
doc_path = (
416+
e.document.uri.to_path().relative_to(root_folder) if root_folder else e.document.uri.to_path()
417+
)
418+
if e.items:
419+
_print_diagnostics(app, root_folder, e.items, doc_path)
420+
finally:
421+
result_collector.stop()
384422

385-
statistics_str = str(statistics)
386-
if statistics.errors > 0:
423+
statistics_str = str(result_collector)
424+
if result_collector.errors > 0:
387425
statistics_str = click.style(statistics_str, fg="red")
388426

389427
app.echo(statistics_str)
390428

391-
app.exit(statistics.calculate_return_code().value)
429+
app.exit(result_collector.calculate_return_code().value)
392430

393431
except (TypeError, ValueError) as e:
394432
raise click.ClickException(str(e)) from e

packages/analyze/src/robotcode/analyze/code/code_analyzer.py

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -98,35 +98,35 @@ def run(
9898

9999
documents = self.collect_documents(folder, paths=paths, filter=filter)
100100

101-
self.app.verbose(f"Analyzing {len(documents)} documents")
102-
for document in documents:
103-
analyze_result = self.diagnostics.analyze_document(document)
104-
if analyze_result is not None:
105-
diagnostics = []
106-
for item in analyze_result:
107-
if item is None:
108-
continue
109-
elif isinstance(item, BaseException):
110-
self.app.error(f"Error analyzing {document.uri.to_path()}: {item}")
111-
else:
112-
diagnostics.extend(item)
113-
114-
yield DocumentDiagnosticReport(document, diagnostics)
115-
116-
self.app.verbose(f"Collect Diagnostics for {len(documents)} documents")
117-
for document in documents:
118-
analyze_result = self.diagnostics.collect_diagnostics(document)
119-
if analyze_result is not None:
120-
diagnostics = []
121-
for item in analyze_result:
122-
if item is None:
123-
continue
124-
elif isinstance(item, BaseException):
125-
self.app.error(f"Error collecting diagnostics for {document.uri.to_path()}: {item}")
126-
else:
127-
diagnostics.extend(item)
128-
129-
yield DocumentDiagnosticReport(document, diagnostics)
101+
with self.app.progressbar(documents, label="Analyzing Documents") as progressbar:
102+
for document in progressbar:
103+
analyze_result = self.diagnostics.analyze_document(document)
104+
if analyze_result is not None:
105+
diagnostics = []
106+
for item in analyze_result:
107+
if item is None:
108+
continue
109+
elif isinstance(item, BaseException):
110+
self.app.error(f"Error analyzing {document.uri.to_path()}: {item}")
111+
else:
112+
diagnostics.extend(item)
113+
114+
yield DocumentDiagnosticReport(document, diagnostics)
115+
116+
with self.app.progressbar(documents, label="Collecting Diagnostics") as progressbar:
117+
for document in progressbar:
118+
analyze_result = self.diagnostics.collect_diagnostics(document)
119+
if analyze_result is not None:
120+
diagnostics = []
121+
for item in analyze_result:
122+
if item is None:
123+
continue
124+
elif isinstance(item, BaseException):
125+
self.app.error(f"Error collecting diagnostics for {document.uri.to_path()}: {item}")
126+
else:
127+
diagnostics.extend(item)
128+
129+
yield DocumentDiagnosticReport(document, diagnostics)
130130

131131
def collect_documents(
132132
self, folder: WorkspaceFolder, paths: Iterable[Path] = {}, filter: Iterable[str] = {}
@@ -139,8 +139,10 @@ def collect_documents(
139139
documents: List[TextDocument] = []
140140

141141
self.app.verbose(f"Collecting files in workspace folder '{folder.uri.to_path()}'")
142+
file_counter = 0
142143
for handler in self.language_handlers:
143144
for file in handler.collect_workspace_folder_files(folder):
145+
file_counter += 1
144146
try:
145147
document = self.workspace.documents.get_or_open_document(file)
146148
document_path = normalized_path(document.uri.to_path()).as_posix()
@@ -158,4 +160,7 @@ def collect_documents(
158160
self.app.error(f"Error reading {file}: {e}")
159161
continue
160162

163+
self.app.verbose(f"{file_counter} files in workspace folder '{folder.uri.to_path()}'")
164+
self.app.verbose(f"Collected {len(documents)} files for analyzing in workspace folder '{folder.uri.to_path()}'")
165+
161166
return documents

packages/analyze/src/robotcode/analyze/code/diagnostics_context.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,29 @@ def document_analyzers(sender, document: TextDocument) -> Optional[List[Diagnost
1717
def folder_analyzers(sender, folder: WorkspaceFolder) -> Optional[List[Diagnostic]]: ...
1818

1919
@event
20-
def collectors(sender, document: TextDocument) -> Optional[List[Diagnostic]]: ...
20+
def document_collectors(sender, document: TextDocument) -> Optional[List[Diagnostic]]: ...
2121

2222
def analyze_folder(self, folder: WorkspaceFolder) -> List[Union[List[Diagnostic], BaseException, None]]:
2323
return self.folder_analyzers(
2424
self,
2525
folder,
26-
return_exceptions=True,
26+
return_exceptions=False,
2727
)
2828

2929
def analyze_document(self, document: TextDocument) -> List[Union[List[Diagnostic], BaseException, None]]:
3030
return self.document_analyzers(
3131
self,
3232
document,
3333
callback_filter=language_id_filter(document),
34-
return_exceptions=True,
34+
return_exceptions=False,
3535
)
3636

3737
def collect_diagnostics(self, document: TextDocument) -> List[Union[List[Diagnostic], BaseException, None]]:
38-
return self.collectors(
38+
return self.document_collectors(
3939
self,
4040
document,
4141
callback_filter=language_id_filter(document),
42-
return_exceptions=True,
42+
return_exceptions=False,
4343
)
4444

4545

0 commit comments

Comments
 (0)