11from pathlib import Path
22from textwrap import indent
3- from typing import Set , Tuple
3+ from typing import List , Optional , Set , Tuple
44
55import click
66
77from robotcode .analyze .config import AnalyzeConfig
8- from robotcode .core .lsp .types import DiagnosticSeverity
8+ from robotcode .core .lsp .types import Diagnostic , DiagnosticSeverity
99from 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
1013from robotcode .plugin import Application , pass_application
1114from robotcode .robot .config .loader import (
1215 load_robot_config_from_path ,
1316)
1417from robotcode .robot .config .utils import get_config_files
1518
1619from .__version__ import __version__
17- from .code_analyzer import CodeAnalyzer
20+ from .code_analyzer import CodeAnalyzer , DocumentDiagnosticReport , FolderDiagnosticReport
1821
1922
2023@click .group (
@@ -44,6 +47,7 @@ def analyze(app: Application) -> None:
4447
4548class Statistic :
4649 def __init__ (self ) -> None :
50+ self .folders : Set [WorkspaceFolder ] = set ()
4751 self .files : Set [TextDocument ] = set ()
4852 self .errors = 0
4953 self .warnings = 0
@@ -69,6 +73,7 @@ def __str__(self) -> str:
6973 "-f" ,
7074 "--filter" ,
7175 "filter" ,
76+ metavar = "PATTERN" ,
7277 type = str ,
7378 multiple = True ,
7479 help = """\
@@ -86,25 +91,32 @@ def __str__(self) -> str:
8691@click .option (
8792 "-V" ,
8893 "--variablefile" ,
89- metavar = "path " ,
94+ metavar = "PATH " ,
9095 type = str ,
9196 multiple = True ,
9297 help = "Python or YAML file file to read variables from. see `robot --variablefile` option." ,
9398)
9499@click .option (
95100 "-P" ,
96101 "--pythonpath" ,
97- metavar = "path " ,
102+ metavar = "PATH " ,
98103 type = str ,
99104 multiple = True ,
100- help = "Additional locations (directories, ZIPs, JARs) where to search test libraries"
105+ help = "Additional locations where to search test libraries"
101106 " and other extensions when they are imported. see `robot --pythonpath` option." ,
102107)
103108@click .argument (
104109 "paths" , nargs = - 1 , type = click .Path (exists = True , dir_okay = True , file_okay = True , readable = True , path_type = Path )
105110)
106111@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 :
108120 """\
109121 Performs static code analysis to detect syntax errors, missing keywords or variables,
110122 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:
132144 * (app .config .profiles or []), verbose_callback = app .verbose , error_callback = app .error
133145 ).evaluated_with_env ()
134146
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+
135165 statistics = Statistic ()
136166 for e in CodeAnalyzer (
137167 app = app ,
138168 analysis_config = analyzer_config .to_workspace_analysis_config (),
139169 robot_profile = robot_profile ,
140170 root_folder = root_folder ,
141171 ).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 )
164186
165187 statistics_str = str (statistics )
166188 if statistics .errors > 0 :
@@ -172,3 +194,51 @@ def code(app: Application, filter: Tuple[str], paths: Tuple[Path]) -> None:
172194
173195 except (TypeError , ValueError ) as e :
174196 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