robotcode-analyze 0.96.0__tar.gz → 0.97.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/PKG-INFO +4 -5
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/pyproject.toml +3 -3
- robotcode_analyze-0.97.0/src/robotcode/analyze/__version__.py +1 -0
- robotcode_analyze-0.97.0/src/robotcode/analyze/cli.py +244 -0
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/src/robotcode/analyze/code_analyzer.py +35 -14
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/src/robotcode/analyze/config.py +33 -0
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/src/robotcode/analyze/diagnostics_context.py +13 -4
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/src/robotcode/analyze/robot_framework_language_provider.py +17 -5
- robotcode_analyze-0.96.0/src/robotcode/analyze/__version__.py +0 -1
- robotcode_analyze-0.96.0/src/robotcode/analyze/cli.py +0 -146
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/.gitignore +0 -0
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/LICENSE.txt +0 -0
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/README.md +0 -0
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/src/robotcode/analyze/__init__.py +0 -0
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/src/robotcode/analyze/hooks.py +0 -0
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/src/robotcode/analyze/language_provider.py +0 -0
- {robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/src/robotcode/analyze/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: robotcode-analyze
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.97.0
|
|
4
4
|
Summary: RobotCode analyze plugin for Robot Framework
|
|
5
5
|
Project-URL: Homepage, https://robotcode.io
|
|
6
6
|
Project-URL: Donate, https://opencollective.com/robotcode
|
|
@@ -10,7 +10,6 @@ Project-URL: Issues, https://github.com/robotcodedev/robotcode/issues
|
|
|
10
10
|
Project-URL: Source, https://github.com/robotcodedev/robotcode
|
|
11
11
|
Author-email: Daniel Biehl <dbiehl@live.de>
|
|
12
12
|
License: Apache-2.0
|
|
13
|
-
License-File: LICENSE.txt
|
|
14
13
|
Classifier: Development Status :: 5 - Production/Stable
|
|
15
14
|
Classifier: Framework :: Robot Framework
|
|
16
15
|
Classifier: Framework :: Robot Framework :: Tool
|
|
@@ -25,9 +24,9 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
25
24
|
Classifier: Topic :: Utilities
|
|
26
25
|
Classifier: Typing :: Typed
|
|
27
26
|
Requires-Python: >=3.8
|
|
28
|
-
Requires-Dist: robotcode-plugin==0.
|
|
29
|
-
Requires-Dist: robotcode-robot==0.
|
|
30
|
-
Requires-Dist: robotcode==0.
|
|
27
|
+
Requires-Dist: robotcode-plugin==0.97.0
|
|
28
|
+
Requires-Dist: robotcode-robot==0.97.0
|
|
29
|
+
Requires-Dist: robotcode==0.97.0
|
|
31
30
|
Requires-Dist: robotframework>=4.1.0
|
|
32
31
|
Description-Content-Type: text/markdown
|
|
33
32
|
|
|
@@ -27,9 +27,9 @@ classifiers = [
|
|
|
27
27
|
]
|
|
28
28
|
dependencies = [
|
|
29
29
|
"robotframework>=4.1.0",
|
|
30
|
-
"robotcode-plugin==0.
|
|
31
|
-
"robotcode-robot==0.
|
|
32
|
-
"robotcode==0.
|
|
30
|
+
"robotcode-plugin==0.97.0",
|
|
31
|
+
"robotcode-robot==0.97.0",
|
|
32
|
+
"robotcode==0.97.0",
|
|
33
33
|
]
|
|
34
34
|
dynamic = ["version"]
|
|
35
35
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.97.0"
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from textwrap import indent
|
|
3
|
+
from typing import List, Optional, Set, Tuple
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from robotcode.analyze.config import AnalyzeConfig
|
|
8
|
+
from robotcode.core.lsp.types import Diagnostic, DiagnosticSeverity
|
|
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
|
|
13
|
+
from robotcode.plugin import Application, pass_application
|
|
14
|
+
from robotcode.robot.config.loader import (
|
|
15
|
+
load_robot_config_from_path,
|
|
16
|
+
)
|
|
17
|
+
from robotcode.robot.config.utils import get_config_files
|
|
18
|
+
|
|
19
|
+
from .__version__ import __version__
|
|
20
|
+
from .code_analyzer import CodeAnalyzer, DocumentDiagnosticReport, FolderDiagnosticReport
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.group(
|
|
24
|
+
add_help_option=True,
|
|
25
|
+
invoke_without_command=False,
|
|
26
|
+
)
|
|
27
|
+
@click.version_option(
|
|
28
|
+
version=__version__,
|
|
29
|
+
package_name="robotcode.analyze",
|
|
30
|
+
prog_name="RobotCode Analyze",
|
|
31
|
+
)
|
|
32
|
+
@pass_application
|
|
33
|
+
def analyze(app: Application) -> None:
|
|
34
|
+
"""\
|
|
35
|
+
The analyze command provides various subcommands for analyzing Robot Framework code.
|
|
36
|
+
These subcommands support specialized tasks, such as code analysis, style checking or dependency graphs.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
SEVERITY_COLORS = {
|
|
41
|
+
DiagnosticSeverity.ERROR: "red",
|
|
42
|
+
DiagnosticSeverity.WARNING: "yellow",
|
|
43
|
+
DiagnosticSeverity.INFORMATION: "blue",
|
|
44
|
+
DiagnosticSeverity.HINT: "cyan",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Statistic:
|
|
49
|
+
def __init__(self) -> None:
|
|
50
|
+
self.folders: Set[WorkspaceFolder] = set()
|
|
51
|
+
self.files: Set[TextDocument] = set()
|
|
52
|
+
self.errors = 0
|
|
53
|
+
self.warnings = 0
|
|
54
|
+
self.infos = 0
|
|
55
|
+
self.hints = 0
|
|
56
|
+
|
|
57
|
+
def __str__(self) -> str:
|
|
58
|
+
return (
|
|
59
|
+
f"Files: {len(self.files)}, Errors: {self.errors}, Warnings: {self.warnings}, "
|
|
60
|
+
f"Infos: {self.infos}, Hints: {self.hints}"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@analyze.command(
|
|
65
|
+
add_help_option=True,
|
|
66
|
+
)
|
|
67
|
+
@click.version_option(
|
|
68
|
+
version=__version__,
|
|
69
|
+
package_name="robotcode.analyze",
|
|
70
|
+
prog_name="RobotCode Analyze",
|
|
71
|
+
)
|
|
72
|
+
@click.option(
|
|
73
|
+
"-f",
|
|
74
|
+
"--filter",
|
|
75
|
+
"filter",
|
|
76
|
+
metavar="PATTERN",
|
|
77
|
+
type=str,
|
|
78
|
+
multiple=True,
|
|
79
|
+
help="""\
|
|
80
|
+
Glob pattern to filter files to analyze. Can be specified multiple times.
|
|
81
|
+
""",
|
|
82
|
+
)
|
|
83
|
+
@click.option(
|
|
84
|
+
"-v",
|
|
85
|
+
"--variable",
|
|
86
|
+
metavar="name:value",
|
|
87
|
+
type=str,
|
|
88
|
+
multiple=True,
|
|
89
|
+
help="Set variables in the test data. see `robot --variable` option.",
|
|
90
|
+
)
|
|
91
|
+
@click.option(
|
|
92
|
+
"-V",
|
|
93
|
+
"--variablefile",
|
|
94
|
+
metavar="PATH",
|
|
95
|
+
type=str,
|
|
96
|
+
multiple=True,
|
|
97
|
+
help="Python or YAML file file to read variables from. see `robot --variablefile` option.",
|
|
98
|
+
)
|
|
99
|
+
@click.option(
|
|
100
|
+
"-P",
|
|
101
|
+
"--pythonpath",
|
|
102
|
+
metavar="PATH",
|
|
103
|
+
type=str,
|
|
104
|
+
multiple=True,
|
|
105
|
+
help="Additional locations where to search test libraries"
|
|
106
|
+
" and other extensions when they are imported. see `robot --pythonpath` option.",
|
|
107
|
+
)
|
|
108
|
+
@click.argument(
|
|
109
|
+
"paths", nargs=-1, type=click.Path(exists=True, dir_okay=True, file_okay=True, readable=True, path_type=Path)
|
|
110
|
+
)
|
|
111
|
+
@pass_application
|
|
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:
|
|
120
|
+
"""\
|
|
121
|
+
Performs static code analysis to detect syntax errors, missing keywords or variables,
|
|
122
|
+
missing arguments, and more on the given *PATHS*. *PATHS* can be files or directories.
|
|
123
|
+
If no PATHS are given, the current directory is used.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
config_files, root_folder, _ = get_config_files(
|
|
127
|
+
paths,
|
|
128
|
+
app.config.config_files,
|
|
129
|
+
root_folder=app.config.root,
|
|
130
|
+
no_vcs=app.config.no_vcs,
|
|
131
|
+
verbose_callback=app.verbose,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
robot_config = load_robot_config_from_path(
|
|
136
|
+
*config_files, extra_tools={"robotcode-analyze": AnalyzeConfig}, verbose_callback=app.verbose
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
analyzer_config = robot_config.tool.get("robotcode-analyze", None) if robot_config.tool is not None else None
|
|
140
|
+
if analyzer_config is None:
|
|
141
|
+
analyzer_config = AnalyzeConfig()
|
|
142
|
+
|
|
143
|
+
robot_profile = robot_config.combine_profiles(
|
|
144
|
+
*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error
|
|
145
|
+
).evaluated_with_env()
|
|
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
|
+
|
|
165
|
+
statistics = Statistic()
|
|
166
|
+
for e in CodeAnalyzer(
|
|
167
|
+
app=app,
|
|
168
|
+
analysis_config=analyzer_config.to_workspace_analysis_config(),
|
|
169
|
+
robot_profile=robot_profile,
|
|
170
|
+
root_folder=root_folder,
|
|
171
|
+
).run(paths=paths, filter=filter):
|
|
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)
|
|
186
|
+
|
|
187
|
+
statistics_str = str(statistics)
|
|
188
|
+
if statistics.errors > 0:
|
|
189
|
+
statistics_str = click.style(statistics_str, fg="red")
|
|
190
|
+
|
|
191
|
+
app.echo(statistics_str)
|
|
192
|
+
|
|
193
|
+
app.exit(statistics.errors)
|
|
194
|
+
|
|
195
|
+
except (TypeError, ValueError) as e:
|
|
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
|
+
)
|
{robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/src/robotcode/analyze/code_analyzer.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Iterable, List, Optional
|
|
3
|
+
from typing import Iterable, List, Optional, Union
|
|
4
4
|
|
|
5
5
|
from robotcode.core.ignore_spec import IgnoreSpec
|
|
6
6
|
from robotcode.core.lsp.types import Diagnostic
|
|
@@ -12,7 +12,6 @@ from robotcode.plugin import Application
|
|
|
12
12
|
from robotcode.robot.config.model import RobotBaseProfile
|
|
13
13
|
from robotcode.robot.diagnostics.workspace_config import WorkspaceAnalysisConfig
|
|
14
14
|
|
|
15
|
-
from .config import AnalyzeConfig
|
|
16
15
|
from .diagnostics_context import DiagnosticHandlers, DiagnosticsContext
|
|
17
16
|
from .robot_framework_language_provider import RobotFrameworkLanguageProvider
|
|
18
17
|
|
|
@@ -23,16 +22,23 @@ class DocumentDiagnosticReport:
|
|
|
23
22
|
items: List[Diagnostic]
|
|
24
23
|
|
|
25
24
|
|
|
25
|
+
@dataclass
|
|
26
|
+
class FolderDiagnosticReport:
|
|
27
|
+
folder: WorkspaceFolder
|
|
28
|
+
items: List[Diagnostic]
|
|
29
|
+
|
|
30
|
+
|
|
26
31
|
class CodeAnalyzer(DiagnosticsContext):
|
|
27
32
|
def __init__(
|
|
28
33
|
self,
|
|
29
34
|
app: Application,
|
|
30
|
-
|
|
35
|
+
analysis_config: WorkspaceAnalysisConfig,
|
|
31
36
|
robot_profile: RobotBaseProfile,
|
|
32
37
|
root_folder: Optional[Path],
|
|
33
38
|
):
|
|
34
39
|
self.app = app
|
|
35
|
-
self.
|
|
40
|
+
self._analysis_config = analysis_config or WorkspaceAnalysisConfig()
|
|
41
|
+
|
|
36
42
|
self._robot_profile = robot_profile
|
|
37
43
|
self._root_folder = root_folder if root_folder is not None else Path.cwd()
|
|
38
44
|
|
|
@@ -53,8 +59,8 @@ class CodeAnalyzer(DiagnosticsContext):
|
|
|
53
59
|
handler.verbose_callback = app.verbose
|
|
54
60
|
|
|
55
61
|
@property
|
|
56
|
-
def analysis_config(self) ->
|
|
57
|
-
return
|
|
62
|
+
def analysis_config(self) -> WorkspaceAnalysisConfig:
|
|
63
|
+
return self._analysis_config
|
|
58
64
|
|
|
59
65
|
@property
|
|
60
66
|
def profile(self) -> RobotBaseProfile:
|
|
@@ -72,8 +78,23 @@ class CodeAnalyzer(DiagnosticsContext):
|
|
|
72
78
|
def diagnostics(self) -> DiagnosticHandlers:
|
|
73
79
|
return self._dispatcher
|
|
74
80
|
|
|
75
|
-
def run(
|
|
81
|
+
def run(
|
|
82
|
+
self, paths: Iterable[Path] = {}, filter: Iterable[str] = {}
|
|
83
|
+
) -> Iterable[Union[DocumentDiagnosticReport, FolderDiagnosticReport]]:
|
|
76
84
|
for folder in self.workspace.workspace_folders:
|
|
85
|
+
self.app.verbose(f"Initialize folder {folder.uri.to_path()}")
|
|
86
|
+
initialize_result = self.diagnostics.initialize_folder(folder)
|
|
87
|
+
if initialize_result is not None:
|
|
88
|
+
diagnostics: List[Diagnostic] = []
|
|
89
|
+
for item in initialize_result:
|
|
90
|
+
if item is None:
|
|
91
|
+
continue
|
|
92
|
+
elif isinstance(item, BaseException):
|
|
93
|
+
self.app.error(f"Error analyzing {folder.uri.to_path()}: {item}")
|
|
94
|
+
else:
|
|
95
|
+
diagnostics.extend(item)
|
|
96
|
+
if diagnostics:
|
|
97
|
+
yield FolderDiagnosticReport(folder, diagnostics)
|
|
77
98
|
|
|
78
99
|
documents = self.collect_documents(folder, paths=paths, filter=filter)
|
|
79
100
|
|
|
@@ -81,16 +102,16 @@ class CodeAnalyzer(DiagnosticsContext):
|
|
|
81
102
|
for document in documents:
|
|
82
103
|
analyze_result = self.diagnostics.analyze_document(document)
|
|
83
104
|
if analyze_result is not None:
|
|
84
|
-
diagnostics
|
|
105
|
+
diagnostics = []
|
|
85
106
|
for item in analyze_result:
|
|
86
107
|
if item is None:
|
|
87
108
|
continue
|
|
88
109
|
elif isinstance(item, BaseException):
|
|
89
|
-
self.app.error(f"Error analyzing {document.uri}: {item}")
|
|
110
|
+
self.app.error(f"Error analyzing {document.uri.to_path()}: {item}")
|
|
90
111
|
else:
|
|
91
112
|
diagnostics.extend(item)
|
|
92
|
-
|
|
93
|
-
|
|
113
|
+
if diagnostics:
|
|
114
|
+
yield DocumentDiagnosticReport(document, diagnostics)
|
|
94
115
|
|
|
95
116
|
self.app.verbose(f"Collect Diagnostics for {len(documents)} documents")
|
|
96
117
|
for document in documents:
|
|
@@ -101,11 +122,11 @@ class CodeAnalyzer(DiagnosticsContext):
|
|
|
101
122
|
if item is None:
|
|
102
123
|
continue
|
|
103
124
|
elif isinstance(item, BaseException):
|
|
104
|
-
self.app.error(f"Error analyzing {document.uri}: {item}")
|
|
125
|
+
self.app.error(f"Error analyzing {document.uri.to_path()}: {item}")
|
|
105
126
|
else:
|
|
106
127
|
diagnostics.extend(item)
|
|
107
|
-
|
|
108
|
-
|
|
128
|
+
if diagnostics:
|
|
129
|
+
yield DocumentDiagnosticReport(document, diagnostics)
|
|
109
130
|
|
|
110
131
|
def collect_documents(
|
|
111
132
|
self, folder: WorkspaceFolder, paths: Iterable[Path] = {}, filter: Iterable[str] = {}
|
|
@@ -3,6 +3,12 @@ from dataclasses import dataclass
|
|
|
3
3
|
from typing import List, Optional
|
|
4
4
|
|
|
5
5
|
from robotcode.robot.config.model import BaseOptions, field
|
|
6
|
+
from robotcode.robot.diagnostics.workspace_config import (
|
|
7
|
+
AnalysisDiagnosticModifiersConfig,
|
|
8
|
+
AnalysisRobotConfig,
|
|
9
|
+
WorkspaceAnalysisConfig,
|
|
10
|
+
)
|
|
11
|
+
from robotcode.robot.diagnostics.workspace_config import CacheConfig as WorkspaceCacheConfig
|
|
6
12
|
|
|
7
13
|
|
|
8
14
|
@dataclass
|
|
@@ -246,3 +252,30 @@ class AnalyzeConfig(BaseOptions):
|
|
|
246
252
|
extend_global_library_search_order: Optional[List[str]] = field(
|
|
247
253
|
description="Extend the global library search order setting."
|
|
248
254
|
)
|
|
255
|
+
|
|
256
|
+
def to_workspace_analysis_config(self) -> WorkspaceAnalysisConfig:
|
|
257
|
+
return WorkspaceAnalysisConfig(
|
|
258
|
+
exclude_patterns=self.exclude_patterns or [],
|
|
259
|
+
cache=(
|
|
260
|
+
WorkspaceCacheConfig(
|
|
261
|
+
# TODO savelocation
|
|
262
|
+
ignored_libraries=self.cache.ignored_libraries or [],
|
|
263
|
+
ignored_variables=self.cache.ignored_variables or [],
|
|
264
|
+
ignore_arguments_for_library=self.cache.ignore_arguments_for_library or [],
|
|
265
|
+
)
|
|
266
|
+
if self.cache is not None
|
|
267
|
+
else WorkspaceCacheConfig()
|
|
268
|
+
),
|
|
269
|
+
robot=AnalysisRobotConfig(global_library_search_order=self.global_library_search_order or []),
|
|
270
|
+
modifiers=(
|
|
271
|
+
AnalysisDiagnosticModifiersConfig(
|
|
272
|
+
ignore=self.modifiers.ignore or [],
|
|
273
|
+
error=self.modifiers.error or [],
|
|
274
|
+
warning=self.modifiers.warning or [],
|
|
275
|
+
information=self.modifiers.information or [],
|
|
276
|
+
hint=self.modifiers.hint or [],
|
|
277
|
+
)
|
|
278
|
+
if self.modifiers is not None
|
|
279
|
+
else AnalysisDiagnosticModifiersConfig()
|
|
280
|
+
),
|
|
281
|
+
)
|
{robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/src/robotcode/analyze/diagnostics_context.py
RENAMED
|
@@ -5,20 +5,29 @@ from robotcode.core.event import event
|
|
|
5
5
|
from robotcode.core.language import language_id_filter
|
|
6
6
|
from robotcode.core.lsp.types import Diagnostic
|
|
7
7
|
from robotcode.core.text_document import TextDocument
|
|
8
|
-
from robotcode.core.workspace import Workspace
|
|
8
|
+
from robotcode.core.workspace import Workspace, WorkspaceFolder
|
|
9
9
|
from robotcode.robot.config.model import RobotBaseProfile
|
|
10
10
|
from robotcode.robot.diagnostics.workspace_config import WorkspaceAnalysisConfig
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class DiagnosticHandlers:
|
|
14
14
|
@event
|
|
15
|
-
def
|
|
15
|
+
def document_analyzers(sender, document: TextDocument) -> Optional[List[Diagnostic]]: ...
|
|
16
|
+
@event
|
|
17
|
+
def folder_initializers(sender, folder: WorkspaceFolder) -> Optional[List[Diagnostic]]: ...
|
|
16
18
|
|
|
17
19
|
@event
|
|
18
20
|
def collectors(sender, document: TextDocument) -> Optional[List[Diagnostic]]: ...
|
|
19
21
|
|
|
22
|
+
def initialize_folder(self, folder: WorkspaceFolder) -> List[Union[List[Diagnostic], BaseException, None]]:
|
|
23
|
+
return self.folder_initializers(
|
|
24
|
+
self,
|
|
25
|
+
folder,
|
|
26
|
+
return_exceptions=True,
|
|
27
|
+
)
|
|
28
|
+
|
|
20
29
|
def analyze_document(self, document: TextDocument) -> List[Union[List[Diagnostic], BaseException, None]]:
|
|
21
|
-
return self.
|
|
30
|
+
return self.document_analyzers(
|
|
22
31
|
self,
|
|
23
32
|
document,
|
|
24
33
|
callback_filter=language_id_filter(document),
|
|
@@ -37,7 +46,7 @@ class DiagnosticHandlers:
|
|
|
37
46
|
class DiagnosticsContext(ABC):
|
|
38
47
|
@property
|
|
39
48
|
@abstractmethod
|
|
40
|
-
def analysis_config(self) ->
|
|
49
|
+
def analysis_config(self) -> WorkspaceAnalysisConfig: ...
|
|
41
50
|
|
|
42
51
|
@property
|
|
43
52
|
@abstractmethod
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import glob
|
|
1
2
|
import sys
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import Any, Iterable, List, Optional
|
|
@@ -46,7 +47,8 @@ class RobotFrameworkLanguageProvider(LanguageProvider):
|
|
|
46
47
|
)
|
|
47
48
|
|
|
48
49
|
self.diagnostics_context.workspace.documents.on_read_document_text.add(self.on_read_document_text)
|
|
49
|
-
self.diagnostics_context.diagnostics.
|
|
50
|
+
self.diagnostics_context.diagnostics.folder_initializers.add(self.analyze_folder)
|
|
51
|
+
self.diagnostics_context.diagnostics.document_analyzers.add(self.analyze_document)
|
|
50
52
|
|
|
51
53
|
def _update_python_path(self) -> None:
|
|
52
54
|
if self.diagnostics_context.workspace.root_uri is not None:
|
|
@@ -56,8 +58,9 @@ class RobotFrameworkLanguageProvider(LanguageProvider):
|
|
|
56
58
|
pa = Path(self.diagnostics_context.workspace.root_uri.to_path(), pa)
|
|
57
59
|
|
|
58
60
|
absolute_path = str(pa.absolute())
|
|
59
|
-
|
|
60
|
-
sys.path
|
|
61
|
+
for f in glob.glob(absolute_path):
|
|
62
|
+
if Path(f).is_dir() and f not in sys.path:
|
|
63
|
+
sys.path.insert(0, f)
|
|
61
64
|
|
|
62
65
|
@language_id("robotframework")
|
|
63
66
|
def on_read_document_text(self, sender: Any, uri: Uri) -> str:
|
|
@@ -70,6 +73,10 @@ class RobotFrameworkLanguageProvider(LanguageProvider):
|
|
|
70
73
|
|
|
71
74
|
extensions = self.LANGUAGE_DEFINITION.extensions
|
|
72
75
|
|
|
76
|
+
exclude_patterns = [
|
|
77
|
+
*self.diagnostics_context.analysis_config.exclude_patterns,
|
|
78
|
+
*(config.exclude_patterns or []),
|
|
79
|
+
]
|
|
73
80
|
return filter(
|
|
74
81
|
lambda f: f.suffix.lower() in extensions,
|
|
75
82
|
iter_files(
|
|
@@ -77,7 +84,7 @@ class RobotFrameworkLanguageProvider(LanguageProvider):
|
|
|
77
84
|
ignore_files=[ROBOT_IGNORE_FILE, GIT_IGNORE_FILE],
|
|
78
85
|
include_hidden=False,
|
|
79
86
|
parent_spec=IgnoreSpec.from_list(
|
|
80
|
-
[*DEFAULT_SPEC_RULES, *
|
|
87
|
+
[*DEFAULT_SPEC_RULES, *exclude_patterns],
|
|
81
88
|
folder.uri.to_path(),
|
|
82
89
|
),
|
|
83
90
|
verbose_callback=self.verbose_callback,
|
|
@@ -90,4 +97,9 @@ class RobotFrameworkLanguageProvider(LanguageProvider):
|
|
|
90
97
|
|
|
91
98
|
namespace.analyze()
|
|
92
99
|
|
|
93
|
-
return namespace.get_diagnostics()
|
|
100
|
+
return self._document_cache.get_diagnostic_modifier(document).modify_diagnostics(namespace.get_diagnostics())
|
|
101
|
+
|
|
102
|
+
def analyze_folder(self, sender: Any, folder: WorkspaceFolder) -> Optional[List[Diagnostic]]:
|
|
103
|
+
imports_manager = self._document_cache.get_imports_manager_for_workspace_folder(folder)
|
|
104
|
+
|
|
105
|
+
return imports_manager.diagnostics
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.96.0"
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
from textwrap import indent
|
|
3
|
-
from typing import Set, Tuple
|
|
4
|
-
|
|
5
|
-
import click
|
|
6
|
-
|
|
7
|
-
from robotcode.analyze.config import AnalyzeConfig
|
|
8
|
-
from robotcode.core.lsp.types import DiagnosticSeverity
|
|
9
|
-
from robotcode.core.text_document import TextDocument
|
|
10
|
-
from robotcode.plugin import Application, pass_application
|
|
11
|
-
from robotcode.robot.config.loader import (
|
|
12
|
-
load_robot_config_from_path,
|
|
13
|
-
)
|
|
14
|
-
from robotcode.robot.config.utils import get_config_files
|
|
15
|
-
|
|
16
|
-
from .__version__ import __version__
|
|
17
|
-
from .code_analyzer import CodeAnalyzer
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@click.group(
|
|
21
|
-
add_help_option=True,
|
|
22
|
-
invoke_without_command=False,
|
|
23
|
-
)
|
|
24
|
-
@click.version_option(
|
|
25
|
-
version=__version__,
|
|
26
|
-
package_name="robotcode.analyze",
|
|
27
|
-
prog_name="RobotCode Analyze",
|
|
28
|
-
)
|
|
29
|
-
@pass_application
|
|
30
|
-
def analyze(app: Application) -> None:
|
|
31
|
-
"""\
|
|
32
|
-
The analyze command provides various subcommands for analyzing Robot Framework code.
|
|
33
|
-
These subcommands support specialized tasks, such as code analysis, style checking or dependency graphs.
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
SEVERITY_COLORS = {
|
|
38
|
-
DiagnosticSeverity.ERROR: "red",
|
|
39
|
-
DiagnosticSeverity.WARNING: "yellow",
|
|
40
|
-
DiagnosticSeverity.INFORMATION: "blue",
|
|
41
|
-
DiagnosticSeverity.HINT: "cyan",
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class Statistic:
|
|
46
|
-
def __init__(self) -> None:
|
|
47
|
-
self.files: Set[TextDocument] = set()
|
|
48
|
-
self.errors = 0
|
|
49
|
-
self.warnings = 0
|
|
50
|
-
self.infos = 0
|
|
51
|
-
self.hints = 0
|
|
52
|
-
|
|
53
|
-
def __str__(self) -> str:
|
|
54
|
-
return (
|
|
55
|
-
f"Files: {len(self.files)}, Errors: {self.errors}, Warnings: {self.warnings}, "
|
|
56
|
-
f"Infos: {self.infos}, Hints: {self.hints}"
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@analyze.command(
|
|
61
|
-
add_help_option=True,
|
|
62
|
-
)
|
|
63
|
-
@click.version_option(
|
|
64
|
-
version=__version__,
|
|
65
|
-
package_name="robotcode.analyze",
|
|
66
|
-
prog_name="RobotCode Analyze",
|
|
67
|
-
)
|
|
68
|
-
@click.option(
|
|
69
|
-
"-f",
|
|
70
|
-
"--filter",
|
|
71
|
-
"filter",
|
|
72
|
-
type=str,
|
|
73
|
-
multiple=True,
|
|
74
|
-
help="""\
|
|
75
|
-
Glob pattern to filter files to analyze. Can be specified multiple times.
|
|
76
|
-
""",
|
|
77
|
-
)
|
|
78
|
-
@click.argument(
|
|
79
|
-
"paths", nargs=-1, type=click.Path(exists=True, dir_okay=True, file_okay=True, readable=True, path_type=Path)
|
|
80
|
-
)
|
|
81
|
-
@pass_application
|
|
82
|
-
def code(app: Application, filter: Tuple[str], paths: Tuple[Path]) -> None:
|
|
83
|
-
"""\
|
|
84
|
-
Performs static code analysis to detect syntax errors, missing keywords or variables,
|
|
85
|
-
missing arguments, and more on the given *PATHS*. *PATHS* can be files or directories.
|
|
86
|
-
If no PATHS are given, the current directory is used.
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
config_files, root_folder, _ = get_config_files(
|
|
90
|
-
paths,
|
|
91
|
-
app.config.config_files,
|
|
92
|
-
root_folder=app.config.root,
|
|
93
|
-
no_vcs=app.config.no_vcs,
|
|
94
|
-
verbose_callback=app.verbose,
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
try:
|
|
98
|
-
robot_config = load_robot_config_from_path(
|
|
99
|
-
*config_files, extra_tools={"robotcode-analyze": AnalyzeConfig}, verbose_callback=app.verbose
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
analyzer_config = robot_config.tool.get("robotcode-analyze", None) if robot_config.tool is not None else None
|
|
103
|
-
if analyzer_config is None:
|
|
104
|
-
analyzer_config = AnalyzeConfig()
|
|
105
|
-
|
|
106
|
-
robot_profile = robot_config.combine_profiles(
|
|
107
|
-
*(app.config.profiles or []), verbose_callback=app.verbose, error_callback=app.error
|
|
108
|
-
).evaluated_with_env()
|
|
109
|
-
|
|
110
|
-
statistics = Statistic()
|
|
111
|
-
for e in CodeAnalyzer(
|
|
112
|
-
app=app, config=analyzer_config, robot_profile=robot_profile, root_folder=root_folder
|
|
113
|
-
).run(paths=paths, filter=filter):
|
|
114
|
-
statistics.files.add(e.document)
|
|
115
|
-
|
|
116
|
-
doc_path = e.document.uri.to_path().relative_to(root_folder) if root_folder else e.document.uri.to_path()
|
|
117
|
-
if e.items:
|
|
118
|
-
|
|
119
|
-
for item in e.items:
|
|
120
|
-
severity = item.severity if item.severity is not None else DiagnosticSeverity.ERROR
|
|
121
|
-
|
|
122
|
-
if severity == DiagnosticSeverity.ERROR:
|
|
123
|
-
statistics.errors += 1
|
|
124
|
-
elif severity == DiagnosticSeverity.WARNING:
|
|
125
|
-
statistics.warnings += 1
|
|
126
|
-
elif severity == DiagnosticSeverity.INFORMATION:
|
|
127
|
-
statistics.infos += 1
|
|
128
|
-
elif severity == DiagnosticSeverity.HINT:
|
|
129
|
-
statistics.hints += 1
|
|
130
|
-
|
|
131
|
-
app.echo(
|
|
132
|
-
f"{doc_path}:{item.range.start.line + 1}:{item.range.start.character + 1}: "
|
|
133
|
-
+ click.style(f"[{severity.name[0]}] {item.code}", fg=SEVERITY_COLORS[severity])
|
|
134
|
-
+ f": {indent(item.message, prefix=' ').strip()}",
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
statistics_str = str(statistics)
|
|
138
|
-
if statistics.errors > 0:
|
|
139
|
-
statistics_str = click.style(statistics_str, fg="red")
|
|
140
|
-
|
|
141
|
-
app.echo(statistics_str)
|
|
142
|
-
|
|
143
|
-
app.exit(statistics.errors)
|
|
144
|
-
|
|
145
|
-
except (TypeError, ValueError) as e:
|
|
146
|
-
raise click.ClickException(str(e)) from e
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robotcode_analyze-0.96.0 → robotcode_analyze-0.97.0}/src/robotcode/analyze/language_provider.py
RENAMED
|
File without changes
|
|
File without changes
|