1
1
from pathlib import Path
2
2
from textwrap import indent
3
- from typing import Set , Tuple
3
+ from typing import List , Optional , Set , Tuple
4
4
5
5
import click
6
6
7
7
from robotcode .analyze .config import AnalyzeConfig
8
- from robotcode .core .lsp .types import DiagnosticSeverity
8
+ from robotcode .core .lsp .types import Diagnostic , DiagnosticSeverity
9
9
from robotcode .core .text_document import TextDocument
10
+ from robotcode .core .uri import Uri
11
+ from robotcode .core .utils .path import try_get_relative_path
12
+ from robotcode .core .workspace import WorkspaceFolder
10
13
from robotcode .plugin import Application , pass_application
11
14
from robotcode .robot .config .loader import (
12
15
load_robot_config_from_path ,
13
16
)
14
17
from robotcode .robot .config .utils import get_config_files
15
18
16
19
from .__version__ import __version__
17
- from .code_analyzer import CodeAnalyzer
20
+ from .code_analyzer import CodeAnalyzer , DocumentDiagnosticReport , FolderDiagnosticReport
18
21
19
22
20
23
@click .group (
@@ -44,6 +47,7 @@ def analyze(app: Application) -> None:
44
47
45
48
class Statistic :
46
49
def __init__ (self ) -> None :
50
+ self .folders : Set [WorkspaceFolder ] = set ()
47
51
self .files : Set [TextDocument ] = set ()
48
52
self .errors = 0
49
53
self .warnings = 0
@@ -69,6 +73,7 @@ def __str__(self) -> str:
69
73
"-f" ,
70
74
"--filter" ,
71
75
"filter" ,
76
+ metavar = "PATTERN" ,
72
77
type = str ,
73
78
multiple = True ,
74
79
help = """\
@@ -86,25 +91,32 @@ def __str__(self) -> str:
86
91
@click .option (
87
92
"-V" ,
88
93
"--variablefile" ,
89
- metavar = "path " ,
94
+ metavar = "PATH " ,
90
95
type = str ,
91
96
multiple = True ,
92
97
help = "Python or YAML file file to read variables from. see `robot --variablefile` option." ,
93
98
)
94
99
@click .option (
95
100
"-P" ,
96
101
"--pythonpath" ,
97
- metavar = "path " ,
102
+ metavar = "PATH " ,
98
103
type = str ,
99
104
multiple = True ,
100
- help = "Additional locations (directories, ZIPs, JARs) where to search test libraries"
105
+ help = "Additional locations where to search test libraries"
101
106
" and other extensions when they are imported. see `robot --pythonpath` option." ,
102
107
)
103
108
@click .argument (
104
109
"paths" , nargs = - 1 , type = click .Path (exists = True , dir_okay = True , file_okay = True , readable = True , path_type = Path )
105
110
)
106
111
@pass_application
107
- def code (app : Application , filter : Tuple [str ], paths : Tuple [Path ]) -> None :
112
+ def code (
113
+ app : Application ,
114
+ filter : Tuple [str ],
115
+ variable : Tuple [str , ...],
116
+ variablefile : Tuple [str , ...],
117
+ pythonpath : Tuple [str , ...],
118
+ paths : Tuple [Path ],
119
+ ) -> None :
108
120
"""\
109
121
Performs static code analysis to detect syntax errors, missing keywords or variables,
110
122
missing arguments, and more on the given *PATHS*. *PATHS* can be files or directories.
@@ -132,35 +144,45 @@ def code(app: Application, filter: Tuple[str], paths: Tuple[Path]) -> None:
132
144
* (app .config .profiles or []), verbose_callback = app .verbose , error_callback = app .error
133
145
).evaluated_with_env ()
134
146
147
+ if variable :
148
+ if robot_profile .variables is None :
149
+ robot_profile .variables = {}
150
+ for v in variable :
151
+ name , value = v .split (":" , 1 ) if ":" in v else (v , "" )
152
+ robot_profile .variables .update ({name : value })
153
+
154
+ if pythonpath :
155
+ if robot_profile .python_path is None :
156
+ robot_profile .python_path = []
157
+ robot_profile .python_path .extend (pythonpath )
158
+
159
+ if variablefile :
160
+ if robot_profile .variable_files is None :
161
+ robot_profile .variable_files = []
162
+ for vf in variablefile :
163
+ robot_profile .variable_files .append (vf )
164
+
135
165
statistics = Statistic ()
136
166
for e in CodeAnalyzer (
137
167
app = app ,
138
168
analysis_config = analyzer_config .to_workspace_analysis_config (),
139
169
robot_profile = robot_profile ,
140
170
root_folder = root_folder ,
141
171
).run (paths = paths , filter = filter ):
142
- statistics .files .add (e .document )
143
-
144
- doc_path = e .document .uri .to_path ().relative_to (root_folder ) if root_folder else e .document .uri .to_path ()
145
- if e .items :
146
-
147
- for item in e .items :
148
- severity = item .severity if item .severity is not None else DiagnosticSeverity .ERROR
149
-
150
- if severity == DiagnosticSeverity .ERROR :
151
- statistics .errors += 1
152
- elif severity == DiagnosticSeverity .WARNING :
153
- statistics .warnings += 1
154
- elif severity == DiagnosticSeverity .INFORMATION :
155
- statistics .infos += 1
156
- elif severity == DiagnosticSeverity .HINT :
157
- statistics .hints += 1
158
-
159
- app .echo (
160
- f"{ doc_path } :{ item .range .start .line + 1 } :{ item .range .start .character + 1 } : "
161
- + click .style (f"[{ severity .name [0 ]} ] { item .code } " , fg = SEVERITY_COLORS [severity ])
162
- + f": { indent (item .message , prefix = ' ' ).strip ()} " ,
163
- )
172
+ if isinstance (e , FolderDiagnosticReport ):
173
+ statistics .folders .add (e .folder )
174
+
175
+ if e .items :
176
+ _print_diagnostics (app , root_folder , statistics , e .items , e .folder .uri .to_path ())
177
+
178
+ elif isinstance (e , DocumentDiagnosticReport ):
179
+ statistics .files .add (e .document )
180
+
181
+ doc_path = (
182
+ e .document .uri .to_path ().relative_to (root_folder ) if root_folder else e .document .uri .to_path ()
183
+ )
184
+ if e .items :
185
+ _print_diagnostics (app , root_folder , statistics , e .items , doc_path )
164
186
165
187
statistics_str = str (statistics )
166
188
if statistics .errors > 0 :
@@ -172,3 +194,51 @@ def code(app: Application, filter: Tuple[str], paths: Tuple[Path]) -> None:
172
194
173
195
except (TypeError , ValueError ) as e :
174
196
raise click .ClickException (str (e )) from e
197
+
198
+
199
+ def _print_diagnostics (
200
+ app : Application ,
201
+ root_folder : Optional [Path ],
202
+ statistics : Statistic ,
203
+ diagnostics : List [Diagnostic ],
204
+ folder_path : Optional [Path ],
205
+ print_range : bool = True ,
206
+ ) -> None :
207
+ for item in diagnostics :
208
+ severity = item .severity if item .severity is not None else DiagnosticSeverity .ERROR
209
+
210
+ if severity == DiagnosticSeverity .ERROR :
211
+ statistics .errors += 1
212
+ elif severity == DiagnosticSeverity .WARNING :
213
+ statistics .warnings += 1
214
+ elif severity == DiagnosticSeverity .INFORMATION :
215
+ statistics .infos += 1
216
+ elif severity == DiagnosticSeverity .HINT :
217
+ statistics .hints += 1
218
+
219
+ app .echo (
220
+ (
221
+ (
222
+ f"{ folder_path } :"
223
+ + (f"{ item .range .start .line + 1 } :{ item .range .start .character + 1 } : " if print_range else " " )
224
+ )
225
+ if folder_path and folder_path != root_folder
226
+ else " "
227
+ )
228
+ + click .style (f"[{ severity .name [0 ]} ] { item .code } " , fg = SEVERITY_COLORS [severity ])
229
+ + f": { indent (item .message , prefix = ' ' ).strip ()} " ,
230
+ )
231
+
232
+ if item .related_information :
233
+ for related in item .related_information or []:
234
+ related_path = try_get_relative_path (Uri (related .location .uri ).to_path (), root_folder )
235
+
236
+ app .echo (
237
+ f" { related_path } :"
238
+ + (
239
+ f"{ related .location .range .start .line + 1 } :{ related .location .range .start .character + 1 } : "
240
+ if print_range
241
+ else " "
242
+ )
243
+ + f"{ indent (related .message , prefix = ' ' ).strip ()} " ,
244
+ )
0 commit comments