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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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"