robotcode-robot 0.68.2__py3-none-any.whl → 0.68.5__py3-none-any.whl

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.
@@ -0,0 +1,523 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ import io
5
+ import threading
6
+ import weakref
7
+ from logging import CRITICAL
8
+ from pathlib import Path
9
+ from typing import (
10
+ Any,
11
+ Callable,
12
+ Iterable,
13
+ Iterator,
14
+ List,
15
+ Optional,
16
+ Tuple,
17
+ Union,
18
+ cast,
19
+ )
20
+
21
+ from robot.parsing.lexer.tokens import Token
22
+ from robotcode.core.documents_manager import DocumentsManager
23
+ from robotcode.core.event import event
24
+ from robotcode.core.filewatcher import FileWatcherManagerBase
25
+ from robotcode.core.language import language_id_filter
26
+ from robotcode.core.text_document import TextDocument
27
+ from robotcode.core.uri import Uri
28
+ from robotcode.core.utils.logging import LoggingDescriptor
29
+ from robotcode.core.workspace import Workspace, WorkspaceFolder
30
+
31
+ from ..config.model import RobotBaseProfile
32
+ from ..utils import get_robot_version
33
+ from ..utils.stubs import Languages
34
+ from .imports_manager import ImportsManager
35
+ from .namespace import DocumentType, Namespace
36
+ from .workspace_config import CacheConfig, RobotConfig
37
+
38
+
39
+ class UnknownFileTypeError(Exception):
40
+ pass
41
+
42
+
43
+ class DocumentsCacheHelper:
44
+ _logger = LoggingDescriptor()
45
+
46
+ def __init__(
47
+ self,
48
+ workspace: Workspace,
49
+ documents_manager: DocumentsManager,
50
+ file_watcher_manager: FileWatcherManagerBase,
51
+ robot_profile: Optional[RobotBaseProfile],
52
+ ) -> None:
53
+ self.workspace = workspace
54
+ self.documents_manager = documents_manager
55
+ self.file_watcher_manager = file_watcher_manager
56
+
57
+ self.robot_profile = robot_profile or RobotBaseProfile()
58
+
59
+ self._imports_managers_lock = threading.RLock()
60
+ self._imports_managers: weakref.WeakKeyDictionary[WorkspaceFolder, ImportsManager] = weakref.WeakKeyDictionary()
61
+ self._default_imports_manager: Optional[ImportsManager] = None
62
+ self._workspace_languages: weakref.WeakKeyDictionary[
63
+ WorkspaceFolder, Optional[Languages]
64
+ ] = weakref.WeakKeyDictionary()
65
+
66
+ def get_languages_for_document(self, document_or_uri: Union[TextDocument, Uri, str]) -> Optional[Languages]:
67
+ if get_robot_version() < (6, 0):
68
+ return None
69
+
70
+ from robot.conf.languages import (
71
+ Languages as RobotLanguages,
72
+ )
73
+
74
+ uri: Union[Uri, str]
75
+
76
+ if isinstance(document_or_uri, TextDocument):
77
+ uri = document_or_uri.uri
78
+ else:
79
+ uri = document_or_uri
80
+
81
+ folder = self.workspace.get_workspace_folder(uri)
82
+
83
+ if folder is None:
84
+ return None
85
+
86
+ if folder in self._workspace_languages:
87
+ return self._workspace_languages[folder]
88
+
89
+ self._logger.debug(lambda: f"Get language config for {uri} in workspace {folder.uri}")
90
+ config = self.workspace.get_configuration(RobotConfig, folder.uri)
91
+
92
+ languages = [str(v) for v in self.robot_profile.languages or []]
93
+ languages += config.languages or []
94
+
95
+ if not languages:
96
+ self._workspace_languages[folder] = None
97
+ return None
98
+
99
+ result = RobotLanguages()
100
+ for lang in languages:
101
+ try:
102
+ result.add_language(lang)
103
+ except ValueError as e:
104
+ ex = e
105
+ self._logger.exception(
106
+ lambda: f"Language configuration is not valid: {ex}"
107
+ "\nPlease check your 'robotcode.robot.language' configuration.",
108
+ exc_info=ex,
109
+ level=CRITICAL,
110
+ )
111
+
112
+ self._workspace_languages[folder] = result
113
+
114
+ return cast(Languages, RobotLanguages(result.languages))
115
+
116
+ def build_languages_from_model(
117
+ self, document: TextDocument, model: ast.AST
118
+ ) -> Tuple[Optional[Languages], Optional[Languages]]:
119
+ if get_robot_version() < (6, 0):
120
+ return (None, None)
121
+
122
+ from robot.conf.languages import (
123
+ Languages as RobotLanguages,
124
+ )
125
+
126
+ # pyright: ignore[reportMissingImports]
127
+ from robot.parsing.model.blocks import File
128
+
129
+ workspace_langs = self.get_languages_for_document(document)
130
+
131
+ return (
132
+ cast(
133
+ Languages,
134
+ RobotLanguages(
135
+ [
136
+ *(workspace_langs.languages if workspace_langs else []),
137
+ *(model.languages if isinstance(model, File) else []),
138
+ ]
139
+ ),
140
+ ),
141
+ workspace_langs,
142
+ )
143
+
144
+ def get_document_type(self, document: TextDocument) -> DocumentType:
145
+ return document.get_cache(self.__get_document_type)
146
+
147
+ def __get_document_type(self, document: TextDocument) -> DocumentType:
148
+ path = document.uri.to_path()
149
+ suffix = path.suffix.lower()
150
+
151
+ if path.name == "__init__.robot":
152
+ return DocumentType.INIT
153
+ if suffix == ".robot":
154
+ return DocumentType.GENERAL
155
+ if suffix == ".resource":
156
+ return DocumentType.RESOURCE
157
+
158
+ return DocumentType.UNKNOWN
159
+
160
+ def get_tokens(self, document: TextDocument, data_only: bool = False) -> List[Token]:
161
+ if data_only:
162
+ return document.get_cache(self.__get_tokens_data_only)
163
+ return document.get_cache(self.__get_tokens)
164
+
165
+ def __get_tokens_data_only(self, document: TextDocument) -> List[Token]:
166
+ document_type = self.get_document_type(document)
167
+ if document_type == DocumentType.INIT:
168
+ return self.get_init_tokens(document, True)
169
+ if document_type == DocumentType.GENERAL:
170
+ return self.get_general_tokens(document, True)
171
+ if document_type == DocumentType.RESOURCE:
172
+ return self.get_resource_tokens(document, True)
173
+
174
+ raise UnknownFileTypeError(str(document.uri))
175
+
176
+ def __get_tokens(self, document: TextDocument) -> List[Token]:
177
+ document_type = self.get_document_type(document)
178
+ if document_type == DocumentType.INIT:
179
+ return self.get_init_tokens(document)
180
+ if document_type == DocumentType.GENERAL:
181
+ return self.get_general_tokens(document)
182
+ if document_type == DocumentType.RESOURCE:
183
+ return self.get_resource_tokens(document)
184
+
185
+ raise UnknownFileTypeError(str(document.uri))
186
+
187
+ def get_general_tokens(self, document: TextDocument, data_only: bool = False) -> List[Token]:
188
+ if data_only:
189
+ return document.get_cache(self.__get_general_tokens_data_only)
190
+ return document.get_cache(self.__get_general_tokens)
191
+
192
+ def __internal_get_tokens(
193
+ self,
194
+ source: Any,
195
+ data_only: bool = False,
196
+ tokenize_variables: bool = False,
197
+ lang: Any = None,
198
+ ) -> Any:
199
+ import robot.api
200
+
201
+ if get_robot_version() >= (6, 0):
202
+ return robot.api.get_tokens(
203
+ source,
204
+ data_only=data_only,
205
+ tokenize_variables=tokenize_variables,
206
+ lang=lang,
207
+ )
208
+
209
+ return robot.api.get_tokens(source, data_only=data_only, tokenize_variables=tokenize_variables)
210
+
211
+ def __internal_get_resource_tokens(
212
+ self,
213
+ source: Any,
214
+ data_only: bool = False,
215
+ tokenize_variables: bool = False,
216
+ lang: Any = None,
217
+ ) -> Any:
218
+ import robot.api
219
+
220
+ if get_robot_version() >= (6, 0):
221
+ return robot.api.get_resource_tokens(
222
+ source,
223
+ data_only=data_only,
224
+ tokenize_variables=tokenize_variables,
225
+ lang=lang,
226
+ )
227
+
228
+ return robot.api.get_resource_tokens(source, data_only=data_only, tokenize_variables=tokenize_variables)
229
+
230
+ def __internal_get_init_tokens(
231
+ self,
232
+ source: Any,
233
+ data_only: bool = False,
234
+ tokenize_variables: bool = False,
235
+ lang: Any = None,
236
+ ) -> Any:
237
+ import robot.api
238
+
239
+ if get_robot_version() >= (6, 0):
240
+ return robot.api.get_init_tokens(
241
+ source,
242
+ data_only=data_only,
243
+ tokenize_variables=tokenize_variables,
244
+ lang=lang,
245
+ )
246
+
247
+ return robot.api.get_init_tokens(source, data_only=data_only, tokenize_variables=tokenize_variables)
248
+
249
+ def __get_general_tokens_data_only(self, document: TextDocument) -> List[Token]:
250
+ lang = self.get_languages_for_document(document)
251
+
252
+ def get(text: str) -> List[Token]:
253
+ with io.StringIO(text) as content:
254
+ return [e for e in self.__internal_get_tokens(content, True, lang=lang)]
255
+
256
+ return self.__get_tokens_internal(document, get)
257
+
258
+ def __get_general_tokens(self, document: TextDocument) -> List[Token]:
259
+ lang = self.get_languages_for_document(document)
260
+
261
+ def get(text: str) -> List[Token]:
262
+ with io.StringIO(text) as content:
263
+ return [e for e in self.__internal_get_tokens(content, lang=lang)]
264
+
265
+ return self.__get_tokens_internal(document, get)
266
+
267
+ def __get_tokens_internal(self, document: TextDocument, get: Callable[[str], List[Token]]) -> List[Token]:
268
+ return get(document.text())
269
+
270
+ def get_resource_tokens(self, document: TextDocument, data_only: bool = False) -> List[Token]:
271
+ if data_only:
272
+ return document.get_cache(self.__get_resource_tokens_data_only)
273
+
274
+ return document.get_cache(self.__get_resource_tokens)
275
+
276
+ def __get_resource_tokens_data_only(self, document: TextDocument) -> List[Token]:
277
+ lang = self.get_languages_for_document(document)
278
+
279
+ def get(text: str) -> List[Token]:
280
+ with io.StringIO(text) as content:
281
+ return [e for e in self.__internal_get_resource_tokens(content, True, lang=lang)]
282
+
283
+ return self.__get_tokens_internal(document, get)
284
+
285
+ def __get_resource_tokens(self, document: TextDocument) -> List[Token]:
286
+ lang = self.get_languages_for_document(document)
287
+
288
+ def get(text: str) -> List[Token]:
289
+ with io.StringIO(text) as content:
290
+ return [e for e in self.__internal_get_resource_tokens(content, lang=lang)]
291
+
292
+ return self.__get_tokens_internal(document, get)
293
+
294
+ def get_init_tokens(self, document: TextDocument, data_only: bool = False) -> List[Token]:
295
+ if data_only:
296
+ return document.get_cache(self.__get_init_tokens_data_only)
297
+ return document.get_cache(self.__get_init_tokens)
298
+
299
+ def __get_init_tokens_data_only(self, document: TextDocument) -> List[Token]:
300
+ lang = self.get_languages_for_document(document)
301
+
302
+ def get(text: str) -> List[Token]:
303
+ with io.StringIO(text) as content:
304
+ return [e for e in self.__internal_get_init_tokens(content, True, lang=lang)]
305
+
306
+ return self.__get_tokens_internal(document, get)
307
+
308
+ def __get_init_tokens(self, document: TextDocument) -> List[Token]:
309
+ lang = self.get_languages_for_document(document)
310
+
311
+ def get(text: str) -> List[Token]:
312
+ with io.StringIO(text) as content:
313
+ return [e for e in self.__internal_get_init_tokens(content, lang=lang)]
314
+
315
+ return self.__get_tokens_internal(document, get)
316
+
317
+ def get_model(self, document: TextDocument, data_only: bool = True) -> ast.AST:
318
+ document_type = self.get_document_type(document)
319
+
320
+ if document_type == DocumentType.INIT:
321
+ return self.get_init_model(document, data_only)
322
+ if document_type == DocumentType.GENERAL:
323
+ return self.get_general_model(document, data_only)
324
+ if document_type == DocumentType.RESOURCE:
325
+ return self.get_resource_model(document, data_only)
326
+
327
+ raise UnknownFileTypeError(f"Unknown file type '{document.uri}'.")
328
+
329
+ def __get_model(
330
+ self,
331
+ document: TextDocument,
332
+ tokens: Iterable[Any],
333
+ document_type: DocumentType,
334
+ ) -> ast.AST:
335
+ from robot.parsing.parser.parser import _get_model
336
+
337
+ def get_tokens(source: str, data_only: bool = False, lang: Any = None) -> Iterator[Token]:
338
+ for t in tokens:
339
+ yield t
340
+
341
+ if get_robot_version() >= (6, 0):
342
+ model = _get_model(get_tokens, document.uri.to_path(), False, None, None)
343
+ else:
344
+ model = _get_model(get_tokens, document.uri.to_path(), False, None)
345
+
346
+ model.source = str(document.uri.to_path())
347
+ model.model_type = document_type
348
+
349
+ return cast(ast.AST, model)
350
+
351
+ def get_general_model(self, document: TextDocument, data_only: bool = True) -> ast.AST:
352
+ if data_only:
353
+ return document.get_cache(
354
+ self.__get_general_model_data_only,
355
+ self.get_general_tokens(document, True),
356
+ )
357
+ return document.get_cache(self.__get_general_model, self.get_general_tokens(document))
358
+
359
+ def __get_general_model_data_only(self, document: TextDocument, tokens: Iterable[Any]) -> ast.AST:
360
+ return self.__get_model(document, tokens, DocumentType.GENERAL)
361
+
362
+ def __get_general_model(self, document: TextDocument, tokens: Iterable[Any]) -> ast.AST:
363
+ return self.__get_model(document, tokens, DocumentType.GENERAL)
364
+
365
+ def get_resource_model(self, document: TextDocument, data_only: bool = True) -> ast.AST:
366
+ if data_only:
367
+ return document.get_cache(
368
+ self.__get_resource_model_data_only,
369
+ self.get_resource_tokens(document, True),
370
+ )
371
+
372
+ return document.get_cache(self.__get_resource_model, self.get_resource_tokens(document))
373
+
374
+ def __get_resource_model_data_only(self, document: TextDocument, tokens: Iterable[Any]) -> ast.AST:
375
+ return self.__get_model(document, tokens, DocumentType.RESOURCE)
376
+
377
+ def __get_resource_model(self, document: TextDocument, tokens: Iterable[Any]) -> ast.AST:
378
+ return self.__get_model(document, tokens, DocumentType.RESOURCE)
379
+
380
+ def get_init_model(self, document: TextDocument, data_only: bool = True) -> ast.AST:
381
+ if data_only:
382
+ return document.get_cache(
383
+ self.__get_init_model_data_only,
384
+ self.get_init_tokens(document, True),
385
+ )
386
+ return document.get_cache(self.__get_init_model, self.get_init_tokens(document))
387
+
388
+ def __get_init_model_data_only(self, document: TextDocument, tokens: Iterable[Any]) -> ast.AST:
389
+ return self.__get_model(document, tokens, DocumentType.INIT)
390
+
391
+ def __get_init_model(self, document: TextDocument, tokens: Iterable[Any]) -> ast.AST:
392
+ return self.__get_model(document, tokens, DocumentType.INIT)
393
+
394
+ def get_namespace(self, document: TextDocument) -> Namespace:
395
+ return document.get_cache(self.__get_namespace)
396
+
397
+ def __get_namespace(self, document: TextDocument) -> Namespace:
398
+ return self.__get_namespace_for_document_type(document, None)
399
+
400
+ def get_resource_namespace(self, document: TextDocument) -> Namespace:
401
+ return document.get_cache(self.__get_resource_namespace)
402
+
403
+ def __get_resource_namespace(self, document: TextDocument) -> Namespace:
404
+ return self.__get_namespace_for_document_type(document, DocumentType.RESOURCE)
405
+
406
+ def get_init_namespace(self, document: TextDocument) -> Namespace:
407
+ return document.get_cache(self.__get_init_namespace)
408
+
409
+ def __get_init_namespace(self, document: TextDocument) -> Namespace:
410
+ return self.__get_namespace_for_document_type(document, DocumentType.INIT)
411
+
412
+ def get_general_namespace(self, document: TextDocument) -> Namespace:
413
+ return document.get_cache(self.__get_general_namespace)
414
+
415
+ def __get_general_namespace(self, document: TextDocument) -> Namespace:
416
+ return self.__get_namespace_for_document_type(document, DocumentType.GENERAL)
417
+
418
+ @event
419
+ def namespace_invalidated(sender, namespace: Namespace) -> None:
420
+ ...
421
+
422
+ def __invalidate_namespace(self, sender: Namespace) -> None:
423
+ document = sender.document
424
+ if document is not None:
425
+ document.invalidate_cache()
426
+
427
+ self.namespace_invalidated(self, sender, callback_filter=language_id_filter(document))
428
+
429
+ def __get_namespace_for_document_type(
430
+ self, document: TextDocument, document_type: Optional[DocumentType]
431
+ ) -> Namespace:
432
+ if document_type is not None and document_type == DocumentType.INIT:
433
+ model = self.get_init_model(document)
434
+ elif document_type is not None and document_type == DocumentType.RESOURCE:
435
+ model = self.get_resource_model(document)
436
+ elif document_type is not None and document_type == DocumentType.GENERAL:
437
+ model = self.get_general_model(document)
438
+ else:
439
+ model = self.get_model(document)
440
+
441
+ imports_manager = self.get_imports_manager(document)
442
+
443
+ languages, workspace_languages = self.build_languages_from_model(document, model)
444
+
445
+ result = Namespace(
446
+ imports_manager,
447
+ model,
448
+ str(document.uri.to_path()),
449
+ document,
450
+ document_type,
451
+ languages,
452
+ workspace_languages,
453
+ )
454
+ result.has_invalidated.add(self.__invalidate_namespace)
455
+ result.has_imports_changed.add(self.__invalidate_namespace)
456
+
457
+ return result
458
+
459
+ def create_imports_manager(self, root_uri: Uri) -> ImportsManager:
460
+ cache_base_path = self.calc_cache_path(root_uri)
461
+
462
+ robot_config = self.workspace.get_configuration(RobotConfig, root_uri)
463
+
464
+ cache_config = self.workspace.get_configuration(CacheConfig, root_uri)
465
+ environment = self.robot_profile.env or {}
466
+ if robot_config.env:
467
+ environment.update(robot_config.env)
468
+
469
+ variables = {
470
+ **{k1: str(v1) for k1, v1 in (self.robot_profile.variables or {}).items()},
471
+ **robot_config.variables,
472
+ }
473
+ variable_files = [
474
+ *(str(k) for k in self.robot_profile.variable_files or []),
475
+ *robot_config.variable_files,
476
+ ]
477
+
478
+ return ImportsManager(
479
+ self.documents_manager,
480
+ self.file_watcher_manager,
481
+ self,
482
+ root_uri.to_path(),
483
+ variables,
484
+ variable_files,
485
+ environment,
486
+ cache_config.ignored_libraries,
487
+ cache_config.ignored_variables,
488
+ cache_base_path,
489
+ )
490
+
491
+ def default_imports_manager(self) -> ImportsManager:
492
+ with self._imports_managers_lock:
493
+ if self._default_imports_manager is None:
494
+ self._default_imports_manager = self.create_imports_manager(
495
+ self.workspace.root_uri
496
+ if self.workspace.root_uri is not None
497
+ else Uri.from_path(Path(".").absolute())
498
+ )
499
+
500
+ return self._default_imports_manager
501
+
502
+ def get_imports_manager(self, document: TextDocument) -> ImportsManager:
503
+ return self.get_imports_manager_for_uri(document.uri)
504
+
505
+ def get_imports_manager_for_uri(self, uri: Uri) -> ImportsManager:
506
+ return self.get_imports_manager_for_workspace_folder(self.workspace.get_workspace_folder(uri))
507
+
508
+ def get_imports_manager_for_workspace_folder(self, folder: Optional[WorkspaceFolder]) -> ImportsManager:
509
+ if folder is None:
510
+ if len(self.workspace.workspace_folders) == 1:
511
+ folder = self.workspace.workspace_folders[0]
512
+ else:
513
+ return self.default_imports_manager()
514
+
515
+ with self._imports_managers_lock:
516
+ if folder not in self._imports_managers:
517
+ self._imports_managers[folder] = self.create_imports_manager(folder.uri)
518
+
519
+ return self._imports_managers[folder]
520
+
521
+ def calc_cache_path(self, folder_uri: Uri) -> Path:
522
+ # TODO: cache path should be configurable, save cache in vscode workspace folder or in robotcode cache folder
523
+ return folder_uri.to_path()
@@ -13,7 +13,8 @@ from typing import (
13
13
 
14
14
  from robot.parsing.lexer.tokens import Token
15
15
  from robotcode.core.lsp.types import Position, Range
16
- from robotcode.robot.utils.ast import range_from_token
16
+
17
+ from ..utils.ast import range_from_token
17
18
 
18
19
  if TYPE_CHECKING:
19
20
  from robotcode.robot.diagnostics.library_doc import KeywordDoc, LibraryDoc
@@ -0,0 +1,33 @@
1
+ from typing import final
2
+
3
+ DIAGNOSTICS_SOURCE_NAME = "robotcode.namespace"
4
+
5
+
6
+ @final
7
+ class Error:
8
+ VARIABLE_NOT_FOUND = "VariableNotFound"
9
+ ENVIROMMENT_VARIABLE_NOT_FOUND = "EnvirommentVariableNotFound"
10
+ KEYWORD_NOT_FOUND = "KeywordNotFound"
11
+ LIBRARY_CONTAINS_NO_KEYWORDS = "LibraryContainsNoKeywords"
12
+ POSSIBLE_CIRCULAR_IMPORT = "PossibleCircularImport"
13
+ RESOURCE_EMPTY = "ResourceEmpty"
14
+ IMPORT_CONTAINS_ERRORS = "ImportContainsErrors"
15
+ RECURSIVE_IMPORT = "RecursiveImport"
16
+ RESOURCE_ALREADY_IMPORTED = "ResourceAlreadyImported"
17
+ VARIABLES_ALREADY_IMPORTED = "VariablesAlreadyImported"
18
+ LIBRARY_ALREADY_IMPORTED = "LibraryAlreadyImported"
19
+ LIBRARY_OVERRIDES_BUILTIN = "LibraryOverridesBuiltIn"
20
+ DEPRECATED_KEYWORD = "DeprecatedKeyword"
21
+ KEYWORD_CONTAINS_ERRORS = "KeywordContainsErrors"
22
+ RESERVED_KEYWORD = "ReservedKeyword"
23
+ PRIVATE_KEYWORD = "PrivateKeyword"
24
+ INCORRECT_USE = "IncorrectUse"
25
+ KEYWORD_NAME_EMPTY = "KeywordNameEmpty"
26
+ CODE_UNREACHABLE = "CodeUnreachable"
27
+ TESTCASE_NAME_EMPTY = "TestCaseNameEmpty"
28
+ KEYWORD_CONTAINS_NORMAL_AND_EMBBEDED_ARGUMENTS = "KeywordContainsNormalAndEmbbededArguments"
29
+ DEPRECATED_HYPHEN_TAG = "DeprecatedHyphenTag"
30
+ DEPRECATED_RETURN_SETTING = "DeprecatedReturnSetting"
31
+ DEPRECATED_FORCE_TAG = "DeprecatedForceTag"
32
+ IMPORT_REQUIRES_VALUE = "ImportRequiresValue"
33
+ KEYWORD_ERROR = "KeywordError"