jaclang 0.8.0__py3-none-any.whl → 0.8.2__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.
Potentially problematic release.
This version of jaclang might be problematic. Click here for more details.
- jaclang/__init__.py +6 -0
- jaclang/cli/cli.py +23 -50
- jaclang/compiler/codeinfo.py +0 -1
- jaclang/compiler/jac.lark +14 -22
- jaclang/compiler/larkparse/jac_parser.py +2 -2
- jaclang/compiler/parser.py +378 -531
- jaclang/compiler/passes/main/__init__.py +0 -14
- jaclang/compiler/passes/main/annex_pass.py +2 -8
- jaclang/compiler/passes/main/cfg_build_pass.py +39 -13
- jaclang/compiler/passes/main/def_impl_match_pass.py +14 -13
- jaclang/compiler/passes/main/def_use_pass.py +4 -7
- jaclang/compiler/passes/main/import_pass.py +6 -14
- jaclang/compiler/passes/main/inheritance_pass.py +2 -2
- jaclang/compiler/passes/main/pyast_gen_pass.py +428 -799
- jaclang/compiler/passes/main/pyast_load_pass.py +115 -311
- jaclang/compiler/passes/main/pyjac_ast_link_pass.py +8 -7
- jaclang/compiler/passes/main/sym_tab_build_pass.py +3 -3
- jaclang/compiler/passes/main/sym_tab_link_pass.py +6 -9
- jaclang/compiler/passes/main/tests/fixtures/symtab_link_tests/action/actions.jac +1 -5
- jaclang/compiler/passes/main/tests/fixtures/symtab_link_tests/main.jac +1 -8
- jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +5 -9
- jaclang/compiler/passes/main/tests/test_decl_impl_match_pass.py +7 -8
- jaclang/compiler/passes/main/tests/test_import_pass.py +5 -18
- jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +2 -6
- jaclang/compiler/passes/main/tests/test_sub_node_pass.py +1 -3
- jaclang/compiler/passes/main/tests/test_sym_tab_link_pass.py +20 -17
- jaclang/compiler/passes/tool/doc_ir_gen_pass.py +425 -216
- jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -0
- jaclang/compiler/passes/tool/tests/fixtures/archetype_frmt.jac +14 -0
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +5 -4
- jaclang/compiler/passes/tool/tests/fixtures/import_fmt.jac +6 -0
- jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +3 -3
- jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +9 -0
- jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +18 -3
- jaclang/compiler/passes/tool/tests/test_unparse_validate.py +2 -2
- jaclang/compiler/program.py +22 -66
- jaclang/compiler/tests/fixtures/fam.jac +2 -2
- jaclang/compiler/tests/fixtures/pkg_import_lib/__init__.jac +1 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib/sub/__init__.jac +1 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib/sub/helper.jac +3 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib/tools.jac +3 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib_py/__init__.py +5 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib_py/sub/__init__.py +3 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib_py/sub/helper.jac +3 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib_py/tools.jac +3 -0
- jaclang/compiler/tests/fixtures/pkg_import_main.jac +10 -0
- jaclang/compiler/tests/fixtures/pkg_import_main_py.jac +11 -0
- jaclang/compiler/tests/test_importer.py +30 -13
- jaclang/compiler/tests/test_parser.py +1 -0
- jaclang/compiler/unitree.py +488 -320
- jaclang/langserve/__init__.jac +1 -0
- jaclang/langserve/engine.jac +503 -0
- jaclang/langserve/sem_manager.jac +309 -0
- jaclang/langserve/server.jac +201 -0
- jaclang/langserve/tests/server_test/test_lang_serve.py +139 -48
- jaclang/langserve/tests/server_test/utils.py +35 -6
- jaclang/langserve/tests/session.jac +294 -0
- jaclang/langserve/tests/test_sem_tokens.py +2 -2
- jaclang/langserve/tests/test_server.py +8 -7
- jaclang/langserve/utils.jac +51 -30
- jaclang/runtimelib/archetype.py +128 -6
- jaclang/runtimelib/builtin.py +17 -14
- jaclang/runtimelib/importer.py +51 -76
- jaclang/runtimelib/machine.py +469 -305
- jaclang/runtimelib/meta_importer.py +86 -0
- jaclang/runtimelib/tests/fixtures/graph_purger.jac +24 -26
- jaclang/runtimelib/tests/fixtures/other_root_access.jac +25 -16
- jaclang/runtimelib/tests/fixtures/traversing_save.jac +7 -5
- jaclang/runtimelib/tests/test_jaseci.py +3 -1
- jaclang/runtimelib/utils.py +3 -3
- jaclang/tests/fixtures/arch_rel_import_creation.jac +23 -23
- jaclang/tests/fixtures/async_ability.jac +43 -10
- jaclang/tests/fixtures/async_function.jac +18 -0
- jaclang/tests/fixtures/async_walker.jac +17 -12
- jaclang/tests/fixtures/backward_edge_visit.jac +31 -0
- jaclang/tests/fixtures/builtin_printgraph.jac +85 -0
- jaclang/tests/fixtures/builtin_printgraph_json.jac +21 -0
- jaclang/tests/fixtures/builtin_printgraph_mermaid.jac +16 -0
- jaclang/tests/fixtures/chandra_bugs2.jac +20 -13
- jaclang/tests/fixtures/concurrency.jac +1 -1
- jaclang/tests/fixtures/create_dynamic_archetype.jac +25 -28
- jaclang/tests/fixtures/deep/deeper/deep_outer_import.jac +7 -4
- jaclang/tests/fixtures/deep/deeper/snd_lev.jac +2 -2
- jaclang/tests/fixtures/deep/deeper/snd_lev_dup.jac +6 -0
- jaclang/tests/fixtures/deep/one_lev.jac +2 -2
- jaclang/tests/fixtures/deep/one_lev_dup.jac +4 -3
- jaclang/tests/fixtures/dynamic_archetype.jac +19 -12
- jaclang/tests/fixtures/edge_ability.jac +49 -0
- jaclang/tests/fixtures/foo.jac +14 -22
- jaclang/tests/fixtures/guess_game.jac +1 -1
- jaclang/tests/fixtures/here_usage_error.jac +21 -0
- jaclang/tests/fixtures/here_visitor_usage.jac +21 -0
- jaclang/tests/fixtures/jac_from_py.py +1 -1
- jaclang/tests/fixtures/jp_importer.jac +6 -6
- jaclang/tests/fixtures/jp_importer_auto.jac +5 -3
- jaclang/tests/fixtures/node_del.jac +30 -36
- jaclang/tests/fixtures/unicode_strings.jac +24 -0
- jaclang/tests/fixtures/visit_traversal.jac +47 -0
- jaclang/tests/fixtures/walker_update.jac +5 -7
- jaclang/tests/test_cli.py +12 -7
- jaclang/tests/test_language.py +218 -145
- jaclang/tests/test_reference.py +9 -4
- jaclang/tests/test_typecheck.py +13 -26
- jaclang/utils/helpers.py +14 -6
- jaclang/utils/lang_tools.py +9 -8
- jaclang/utils/module_resolver.py +23 -0
- jaclang/utils/tests/test_lang_tools.py +2 -1
- jaclang/utils/treeprinter.py +3 -4
- {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/METADATA +4 -3
- {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/RECORD +112 -94
- {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/WHEEL +1 -1
- jaclang/compiler/passes/main/tests/fixtures/main_err.jac +0 -6
- jaclang/compiler/passes/main/tests/fixtures/second_err.jac +0 -4
- jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +0 -644
- jaclang/compiler/passes/tool/tests/test_doc_ir_gen_pass.py +0 -29
- jaclang/langserve/__init__.py +0 -1
- jaclang/langserve/engine.py +0 -553
- jaclang/langserve/sem_manager.py +0 -383
- jaclang/langserve/server.py +0 -167
- jaclang/langserve/tests/session.py +0 -255
- jaclang/tests/fixtures/builtin_dotgen.jac +0 -42
- jaclang/tests/fixtures/builtin_dotgen_json.jac +0 -21
- jaclang/tests/fixtures/deep/deeper/__init__.jac +0 -1
- {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/entry_points.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Test for Jac language server[VSCE] features"""
|
|
2
|
+
|
|
2
3
|
import os
|
|
3
4
|
import pytest
|
|
4
5
|
|
|
@@ -12,24 +13,22 @@ from lsprotocol.types import (
|
|
|
12
13
|
VersionedTextDocumentIdentifier,
|
|
13
14
|
TextDocumentIdentifier,
|
|
14
15
|
)
|
|
15
|
-
from jaclang.langserve.engine import JacLangServer
|
|
16
|
-
from jaclang.langserve.server import (
|
|
17
|
-
did_open,
|
|
18
|
-
did_save,
|
|
19
|
-
did_change,
|
|
20
|
-
formatting,
|
|
21
|
-
)
|
|
22
16
|
from jaclang.langserve.tests.server_test.utils import (
|
|
23
17
|
create_temp_jac_file,
|
|
24
18
|
get_code,
|
|
25
19
|
get_jac_file_path,
|
|
20
|
+
get_simple_code,
|
|
26
21
|
create_ls_with_workspace, # new helper
|
|
27
22
|
)
|
|
28
23
|
from jaclang.vendor.pygls.uris import from_fs_path
|
|
29
24
|
from jaclang.vendor.pygls.workspace import Workspace
|
|
25
|
+
from jaclang import JacMachineInterface as _
|
|
26
|
+
from jaclang.langserve.engine import JacLangServer
|
|
27
|
+
from jaclang.langserve.server import did_open, did_save, did_change, formatting
|
|
30
28
|
|
|
31
29
|
JAC_FILE = get_jac_file_path()
|
|
32
30
|
|
|
31
|
+
|
|
33
32
|
class TestLangServe:
|
|
34
33
|
"""Test class for Jac language server features."""
|
|
35
34
|
|
|
@@ -96,7 +95,7 @@ class TestLangServe:
|
|
|
96
95
|
temp_file_path = create_temp_jac_file(code)
|
|
97
96
|
uri = from_fs_path(temp_file_path)
|
|
98
97
|
ls.lsp._workspace = Workspace(os.path.dirname(temp_file_path), ls)
|
|
99
|
-
|
|
98
|
+
|
|
100
99
|
params = DidOpenTextDocumentParams(
|
|
101
100
|
text_document=TextDocumentItem(
|
|
102
101
|
uri=uri,
|
|
@@ -131,7 +130,9 @@ class TestLangServe:
|
|
|
131
130
|
ls.shutdown()
|
|
132
131
|
assert hasattr(sem_tokens, "data")
|
|
133
132
|
assert isinstance(sem_tokens.data, list)
|
|
134
|
-
assert
|
|
133
|
+
assert (
|
|
134
|
+
len(sem_tokens.data) == 0
|
|
135
|
+
) # TODO: we should retain the sem tokens, will be fixed in next PR
|
|
135
136
|
|
|
136
137
|
os.remove(temp_file_path)
|
|
137
138
|
|
|
@@ -142,14 +143,17 @@ class TestLangServe:
|
|
|
142
143
|
temp_file_path = create_temp_jac_file(code)
|
|
143
144
|
uri, ls = create_ls_with_workspace(temp_file_path)
|
|
144
145
|
|
|
145
|
-
await did_open(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
await did_open(
|
|
147
|
+
ls,
|
|
148
|
+
DidOpenTextDocumentParams(
|
|
149
|
+
text_document=TextDocumentItem(
|
|
150
|
+
uri=uri,
|
|
151
|
+
language_id="jac",
|
|
152
|
+
version=1,
|
|
153
|
+
text=code,
|
|
154
|
+
)
|
|
155
|
+
),
|
|
156
|
+
)
|
|
153
157
|
|
|
154
158
|
params = DidSaveTextDocumentParams(
|
|
155
159
|
text_document=TextDocumentItem(
|
|
@@ -167,12 +171,14 @@ class TestLangServe:
|
|
|
167
171
|
|
|
168
172
|
# Now simulate a syntax error by updating the workspace and saving
|
|
169
173
|
broken_code = get_code("error")
|
|
170
|
-
ls.workspace.put_text_document(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
ls.workspace.put_text_document(
|
|
175
|
+
TextDocumentItem(
|
|
176
|
+
uri=uri,
|
|
177
|
+
language_id="jac",
|
|
178
|
+
version=3,
|
|
179
|
+
text=broken_code,
|
|
180
|
+
)
|
|
181
|
+
)
|
|
176
182
|
params = DidSaveTextDocumentParams(
|
|
177
183
|
text_document=TextDocumentItem(
|
|
178
184
|
uri=uri,
|
|
@@ -182,6 +188,9 @@ class TestLangServe:
|
|
|
182
188
|
)
|
|
183
189
|
)
|
|
184
190
|
await did_save(ls, params)
|
|
191
|
+
sem_tokens = ls.get_semantic_tokens(uri)
|
|
192
|
+
# semantic tokens should still be present even if there is a syntax error
|
|
193
|
+
assert len(sem_tokens.data) == 300
|
|
185
194
|
diagnostics = ls.diagnostics.get(uri, [])
|
|
186
195
|
assert isinstance(diagnostics, list)
|
|
187
196
|
assert len(diagnostics) == 1
|
|
@@ -197,26 +206,31 @@ class TestLangServe:
|
|
|
197
206
|
temp_file_path = create_temp_jac_file(code)
|
|
198
207
|
uri, ls = create_ls_with_workspace(temp_file_path)
|
|
199
208
|
|
|
200
|
-
await did_open(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
209
|
+
await did_open(
|
|
210
|
+
ls,
|
|
211
|
+
DidOpenTextDocumentParams(
|
|
212
|
+
text_document=TextDocumentItem(
|
|
213
|
+
uri=uri,
|
|
214
|
+
language_id="jac",
|
|
215
|
+
version=1,
|
|
216
|
+
text=code,
|
|
217
|
+
)
|
|
218
|
+
),
|
|
219
|
+
)
|
|
208
220
|
|
|
209
221
|
# No error, should be no diagnostics
|
|
210
222
|
params = DidChangeTextDocumentParams(
|
|
211
223
|
text_document=VersionedTextDocumentIdentifier(uri=uri, version=2),
|
|
212
|
-
content_changes=[{"text": "\n" + code}]
|
|
224
|
+
content_changes=[{"text": "\n" + code}],
|
|
225
|
+
)
|
|
226
|
+
ls.workspace.put_text_document(
|
|
227
|
+
TextDocumentItem(
|
|
228
|
+
uri=uri,
|
|
229
|
+
language_id="jac",
|
|
230
|
+
version=2,
|
|
231
|
+
text="\n" + code,
|
|
232
|
+
)
|
|
213
233
|
)
|
|
214
|
-
ls.workspace.put_text_document(TextDocumentItem(
|
|
215
|
-
uri=uri,
|
|
216
|
-
language_id="jac",
|
|
217
|
-
version=2,
|
|
218
|
-
text="\n" + code,
|
|
219
|
-
))
|
|
220
234
|
await did_change(ls, params)
|
|
221
235
|
diagnostics = ls.diagnostics.get(uri, [])
|
|
222
236
|
assert isinstance(diagnostics, list)
|
|
@@ -227,15 +241,20 @@ class TestLangServe:
|
|
|
227
241
|
error_code = "\nerror"
|
|
228
242
|
params = DidChangeTextDocumentParams(
|
|
229
243
|
text_document=VersionedTextDocumentIdentifier(uri=uri, version=3),
|
|
230
|
-
content_changes=[{"text": error_code + code}]
|
|
244
|
+
content_changes=[{"text": error_code + code}],
|
|
245
|
+
)
|
|
246
|
+
ls.workspace.put_text_document(
|
|
247
|
+
TextDocumentItem(
|
|
248
|
+
uri=uri,
|
|
249
|
+
language_id="jac",
|
|
250
|
+
version=3,
|
|
251
|
+
text=error_code + code,
|
|
252
|
+
)
|
|
231
253
|
)
|
|
232
|
-
ls.workspace.put_text_document(TextDocumentItem(
|
|
233
|
-
uri=uri,
|
|
234
|
-
language_id="jac",
|
|
235
|
-
version=3,
|
|
236
|
-
text=error_code + code,
|
|
237
|
-
))
|
|
238
254
|
await did_change(ls, params)
|
|
255
|
+
sem_tokens = ls.get_semantic_tokens(uri)
|
|
256
|
+
# semantic tokens should still be present even if there is a syntax error
|
|
257
|
+
assert len(sem_tokens.data) == 300
|
|
239
258
|
diagnostics = ls.diagnostics.get(uri, [])
|
|
240
259
|
assert isinstance(diagnostics, list)
|
|
241
260
|
assert len(diagnostics) == 1
|
|
@@ -251,12 +270,84 @@ class TestLangServe:
|
|
|
251
270
|
uri, ls = create_ls_with_workspace(temp_file_path)
|
|
252
271
|
params = DocumentFormattingParams(
|
|
253
272
|
text_document=TextDocumentIdentifier(uri=uri),
|
|
254
|
-
options={"tabSize": 4, "insertSpaces": True}
|
|
273
|
+
options={"tabSize": 4, "insertSpaces": True},
|
|
255
274
|
)
|
|
256
275
|
edits = formatting(ls, params)
|
|
257
276
|
assert isinstance(edits, list)
|
|
258
277
|
assert isinstance(edits[0], TextEdit)
|
|
259
|
-
assert
|
|
278
|
+
assert (
|
|
279
|
+
len(edits[0].new_text) > 100
|
|
280
|
+
) # it is a random number to check if the text is changed
|
|
260
281
|
print(edits[0].new_text)
|
|
261
282
|
ls.shutdown()
|
|
262
|
-
os.remove(temp_file_path)
|
|
283
|
+
os.remove(temp_file_path)
|
|
284
|
+
|
|
285
|
+
@pytest.mark.asyncio
|
|
286
|
+
async def test_multifile_workspace(self):
|
|
287
|
+
"""Test opening multiple Jac files in a workspace."""
|
|
288
|
+
code1 = get_simple_code("")
|
|
289
|
+
code2 = get_simple_code("error")
|
|
290
|
+
temp_file_path1 = create_temp_jac_file(code1)
|
|
291
|
+
temp_file_path2 = create_temp_jac_file(code2)
|
|
292
|
+
|
|
293
|
+
uri1, ls = create_ls_with_workspace(temp_file_path1)
|
|
294
|
+
uri2 = from_fs_path(temp_file_path2)
|
|
295
|
+
|
|
296
|
+
await did_open(
|
|
297
|
+
ls,
|
|
298
|
+
DidOpenTextDocumentParams(
|
|
299
|
+
text_document=TextDocumentItem(
|
|
300
|
+
uri=uri1,
|
|
301
|
+
language_id="jac",
|
|
302
|
+
version=1,
|
|
303
|
+
text=code1,
|
|
304
|
+
)
|
|
305
|
+
),
|
|
306
|
+
)
|
|
307
|
+
await did_open(
|
|
308
|
+
ls,
|
|
309
|
+
DidOpenTextDocumentParams(
|
|
310
|
+
text_document=TextDocumentItem(
|
|
311
|
+
uri=uri2,
|
|
312
|
+
language_id="jac",
|
|
313
|
+
version=1,
|
|
314
|
+
text=code2,
|
|
315
|
+
)
|
|
316
|
+
),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
diagnostics1 = ls.diagnostics.get(uri1, [])
|
|
320
|
+
diagnostics2 = ls.diagnostics.get(uri2, [])
|
|
321
|
+
assert len(diagnostics1) == 0
|
|
322
|
+
assert len(diagnostics2) == 1
|
|
323
|
+
assert diagnostics2[0].message == "Syntax Error"
|
|
324
|
+
|
|
325
|
+
before_sem_tokens_1 = ls.get_semantic_tokens(uri1)
|
|
326
|
+
before_sem_tokens_2 = ls.get_semantic_tokens(uri2)
|
|
327
|
+
assert len(before_sem_tokens_1.data) == 15
|
|
328
|
+
assert len(before_sem_tokens_2.data) == 0
|
|
329
|
+
|
|
330
|
+
changed_code = get_simple_code("glob x = 90;")
|
|
331
|
+
ls.workspace.put_text_document(
|
|
332
|
+
TextDocumentItem(
|
|
333
|
+
uri=uri1,
|
|
334
|
+
language_id="jac",
|
|
335
|
+
version=2,
|
|
336
|
+
text=changed_code,
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
params = DidChangeTextDocumentParams(
|
|
340
|
+
text_document=VersionedTextDocumentIdentifier(uri=uri1, version=2),
|
|
341
|
+
content_changes=[{"text": changed_code}],
|
|
342
|
+
)
|
|
343
|
+
await did_change(ls, params)
|
|
344
|
+
|
|
345
|
+
after_sem_tokens_1 = ls.get_semantic_tokens(uri1)
|
|
346
|
+
after_sem_tokens_2 = ls.get_semantic_tokens(uri2)
|
|
347
|
+
|
|
348
|
+
assert len(after_sem_tokens_1.data) == 20
|
|
349
|
+
assert len(after_sem_tokens_2.data) == 0
|
|
350
|
+
|
|
351
|
+
ls.shutdown()
|
|
352
|
+
os.remove(temp_file_path1)
|
|
353
|
+
os.remove(temp_file_path2)
|
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
"""Unit test utilities for JacLangServer."""
|
|
2
|
+
|
|
2
3
|
import os
|
|
3
4
|
import tempfile
|
|
4
5
|
|
|
5
|
-
from jaclang.langserve.engine import JacLangServer
|
|
6
6
|
from jaclang.vendor.pygls.uris import from_fs_path
|
|
7
7
|
from jaclang.vendor.pygls.workspace import Workspace
|
|
8
8
|
|
|
9
9
|
from textwrap import dedent
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
def get_jac_file_path():
|
|
12
13
|
"""Return the absolute path to the sample Jac file used for testing."""
|
|
13
14
|
return os.path.abspath(
|
|
14
|
-
os.path.join(
|
|
15
|
+
os.path.join(
|
|
16
|
+
os.path.dirname(__file__), "../../../../examples/manual_code/circle.jac"
|
|
17
|
+
)
|
|
15
18
|
)
|
|
16
19
|
|
|
20
|
+
|
|
17
21
|
def create_temp_jac_file(initial_content: str = "") -> str:
|
|
18
22
|
"""Create a temporary Jac file with optional initial content and return its path."""
|
|
19
|
-
temp = tempfile.NamedTemporaryFile(
|
|
23
|
+
temp = tempfile.NamedTemporaryFile(
|
|
24
|
+
delete=False, suffix=".jac", mode="w", encoding="utf-8"
|
|
25
|
+
)
|
|
20
26
|
temp.write(initial_content)
|
|
21
27
|
temp.close()
|
|
22
28
|
return temp.name
|
|
23
29
|
|
|
30
|
+
|
|
24
31
|
def get_code(code: str) -> str:
|
|
25
32
|
"""Generate a sample Jac code snippet with optional test code injected."""
|
|
26
|
-
jac_code = dedent(
|
|
33
|
+
jac_code = dedent(
|
|
34
|
+
f'''
|
|
27
35
|
"""
|
|
28
36
|
This module demonstrates a simple circle class and a function to calculate
|
|
29
37
|
the area of a circle in all of Jac's glory.
|
|
@@ -104,12 +112,33 @@ def get_code(code: str) -> str:
|
|
|
104
112
|
c = Circle(RAD);
|
|
105
113
|
check c.shape_type == ShapeType.CIRCLE;
|
|
106
114
|
}}
|
|
107
|
-
'''
|
|
115
|
+
'''
|
|
116
|
+
)
|
|
108
117
|
return jac_code
|
|
109
118
|
|
|
119
|
+
|
|
120
|
+
def get_simple_code(code: str) -> str:
|
|
121
|
+
"""Generate a sample Jac code snippet with optional test code injected."""
|
|
122
|
+
jac_code = dedent(
|
|
123
|
+
f"""
|
|
124
|
+
|
|
125
|
+
# Unit Tests!
|
|
126
|
+
glob expected_area0 = 78.53981633974483;
|
|
127
|
+
glob expected_area1 = 78.53981633974483;
|
|
128
|
+
{code}
|
|
129
|
+
glob expected_area2 = 78.53981633974483;
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
)
|
|
134
|
+
return jac_code
|
|
135
|
+
|
|
136
|
+
|
|
110
137
|
def create_ls_with_workspace(file_path: str):
|
|
111
138
|
"""Create JacLangServer and workspace for a given file path, return (uri, ls)."""
|
|
139
|
+
from jaclang.langserve.engine import JacLangServer
|
|
140
|
+
|
|
112
141
|
ls = JacLangServer()
|
|
113
142
|
uri = from_fs_path(file_path)
|
|
114
143
|
ls.lsp._workspace = Workspace(os.path.dirname(file_path), ls)
|
|
115
|
-
return uri, ls
|
|
144
|
+
return uri, ls
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""Provides LSP session helpers for testing."""
|
|
2
|
+
|
|
3
|
+
import os;
|
|
4
|
+
import subprocess;
|
|
5
|
+
import sys;
|
|
6
|
+
import from concurrent.futures { Future, ThreadPoolExecutor }
|
|
7
|
+
import from threading { Event }
|
|
8
|
+
|
|
9
|
+
import from pylsp_jsonrpc.dispatchers { MethodDispatcher }
|
|
10
|
+
import from pylsp_jsonrpc.endpoint { Endpoint }
|
|
11
|
+
import from pylsp_jsonrpc.streams { JsonRpcStreamReader, JsonRpcStreamWriter }
|
|
12
|
+
|
|
13
|
+
import defaults;
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
with entry {
|
|
17
|
+
LSP_EXIT_TIMEOUT = 5000;
|
|
18
|
+
PUBLISH_DIAGNOSTICS = 'textDocument/publishDiagnostics';
|
|
19
|
+
WINDOW_LOG_MESSAGE = 'window/logMessage';
|
|
20
|
+
WINDOW_SHOW_MESSAGE = 'window/showMessage';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
"""Send and Receive messages over LSP as a test LS Client."""
|
|
25
|
+
class LspSession (MethodDispatcher) {
|
|
26
|
+
def init(self: LspSession, cwd: Any = None) {
|
|
27
|
+
self.cwd = cwd if cwd else os.getcwd() ;
|
|
28
|
+
self._thread_pool = ThreadPoolExecutor();
|
|
29
|
+
self._sub = None;
|
|
30
|
+
self._writer = None;
|
|
31
|
+
self._reader = None;
|
|
32
|
+
self._endpoint = None;
|
|
33
|
+
self._notification_callbacks = {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
"""Context manager entrypoint.\n\n shell=True needed for pytest-cov to work in subprocess.\n """
|
|
37
|
+
def __enter__(self: LspSession) {
|
|
38
|
+
self._sub = subprocess.Popen(
|
|
39
|
+
["jac", "run",
|
|
40
|
+
os.path.join(os.path.dirname(os.path.dirname(__file__)), 'server.jac')],
|
|
41
|
+
stdout=subprocess.PIPE,
|
|
42
|
+
stdin=subprocess.PIPE,
|
|
43
|
+
stderr=subprocess.DEVNULL,
|
|
44
|
+
bufsize=0,
|
|
45
|
+
cwd=self.cwd,
|
|
46
|
+
env=os.environ,
|
|
47
|
+
shell=('WITH_COVERAGE' in os.environ)
|
|
48
|
+
);
|
|
49
|
+
self._writer = JsonRpcStreamWriter(os.fdopen(self._sub.stdin.fileno(), 'wb'));
|
|
50
|
+
self._reader = JsonRpcStreamReader(os.fdopen(self._sub.stdout.fileno(), 'rb'));
|
|
51
|
+
dispatcher =
|
|
52
|
+
{PUBLISH_DIAGNOSTICS : self._publish_diagnostics , WINDOW_SHOW_MESSAGE : self._window_show_message , WINDOW_LOG_MESSAGE : self._window_log_message };
|
|
53
|
+
self._endpoint = Endpoint(dispatcher, self._writer.write);
|
|
54
|
+
self._thread_pool.submit(self._reader.listen, self._endpoint.consume);
|
|
55
|
+
return self;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def __exit__(self: LspSession, typ: Any, value: Any, _tb: Any) {
|
|
59
|
+
self.shutdown(True);
|
|
60
|
+
try {
|
|
61
|
+
self._sub.terminate();
|
|
62
|
+
} except Exception as e {
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
self._endpoint.shutdown();
|
|
67
|
+
self._thread_pool.shutdown();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
"""Sends the initialize request to LSP server."""
|
|
71
|
+
def initialize(
|
|
72
|
+
self: LspSession,
|
|
73
|
+
initialize_params: Any = None,
|
|
74
|
+
process_server_capabilities: Any = None
|
|
75
|
+
) {
|
|
76
|
+
server_initialized = Event();
|
|
77
|
+
def _after_initialize(fut: Any) {
|
|
78
|
+
if process_server_capabilities {
|
|
79
|
+
process_server_capabilities(fut.result());
|
|
80
|
+
}
|
|
81
|
+
self.initialized();
|
|
82
|
+
server_initialized.set();
|
|
83
|
+
}
|
|
84
|
+
self._send_request(
|
|
85
|
+
'initialize',
|
|
86
|
+
params=initialize_params
|
|
87
|
+
if (initialize_params is not None)
|
|
88
|
+
else defaults.VSCODE_DEFAULT_INITIALIZE
|
|
89
|
+
,
|
|
90
|
+
handle_response=_after_initialize
|
|
91
|
+
);
|
|
92
|
+
server_initialized.wait();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
"""Sends the initialized notification to LSP server."""
|
|
96
|
+
def initialized(self: LspSession, initialized_params: Any = None) {
|
|
97
|
+
if (initialized_params is None) {
|
|
98
|
+
initialized_params = {};
|
|
99
|
+
}
|
|
100
|
+
self._endpoint.notify('initialized', initialized_params);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
"""Sends the shutdown request to LSP server."""
|
|
104
|
+
def shutdown(
|
|
105
|
+
self: LspSession,
|
|
106
|
+
should_exit: Any,
|
|
107
|
+
exit_timeout: Any = LSP_EXIT_TIMEOUT
|
|
108
|
+
) {
|
|
109
|
+
def _after_shutdown(_: Any) {
|
|
110
|
+
if should_exit {
|
|
111
|
+
self.exit_lsp(exit_timeout);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
self._send_request('shutdown', handle_response=_after_shutdown);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
"""Handles LSP server process exit."""
|
|
118
|
+
def exit_lsp(self: LspSession, exit_timeout: Any = LSP_EXIT_TIMEOUT) {
|
|
119
|
+
self._endpoint.notify('exit');
|
|
120
|
+
assert (self._sub.wait(exit_timeout) == 0) ;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
"""Sends text document completion request to LSP server."""
|
|
124
|
+
def text_document_completion(self: LspSession, completion_params: Any) {
|
|
125
|
+
fut = self._send_request('textDocument/completion', params=completion_params);
|
|
126
|
+
return fut.result();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
"""Sends text document rename request to LSP server."""
|
|
130
|
+
def text_document_rename(self: LspSession, rename_params: Any) {
|
|
131
|
+
fut = self._send_request('textDocument/rename', params=rename_params);
|
|
132
|
+
return fut.result();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
"""Sends text document code action request to LSP server."""
|
|
136
|
+
def text_document_code_action(self: LspSession, code_action_params: Any) {
|
|
137
|
+
fut = self._send_request('textDocument/codeAction', params=code_action_params);
|
|
138
|
+
return fut.result();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
"""Sends text document hover request to LSP server."""
|
|
142
|
+
def text_document_hover(self: LspSession, hover_params: Any) {
|
|
143
|
+
fut = self._send_request('textDocument/hover', params=hover_params);
|
|
144
|
+
return fut.result();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
"""Sends text document formatting request to LSP server."""
|
|
148
|
+
def text_document_formatting(self: LspSession, formatting_params: Any) {
|
|
149
|
+
fut = self._send_request('textDocument/formatting', params=formatting_params);
|
|
150
|
+
return fut.result();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
"""Sends text document hover request to LSP server."""
|
|
154
|
+
def text_document_signature_help(self: LspSession, signature_help_params: Any) {
|
|
155
|
+
fut = self._send_request(
|
|
156
|
+
'textDocument/signatureHelp',
|
|
157
|
+
params=signature_help_params
|
|
158
|
+
);
|
|
159
|
+
return fut.result();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
"""Sends text document declaration request to LSP server."""
|
|
163
|
+
def text_document_declaration(self: LspSession, declaration_params: Any) {
|
|
164
|
+
fut = self._send_request('textDocument/declaration', params=declaration_params);
|
|
165
|
+
return fut.result();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
"""Sends text document definition request to LSP server."""
|
|
169
|
+
def text_document_definition(self: LspSession, definition_params: Any) {
|
|
170
|
+
fut = self._send_request('textDocument/definition', params=definition_params);
|
|
171
|
+
return fut.result();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
"""Sends text document symbol request to LSP server."""
|
|
175
|
+
def text_document_symbol(self: LspSession, document_symbol_params: Any) {
|
|
176
|
+
fut = self._send_request(
|
|
177
|
+
'textDocument/documentSymbol',
|
|
178
|
+
params=document_symbol_params
|
|
179
|
+
);
|
|
180
|
+
return fut.result();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
"""Sends text document highlight request to LSP server."""
|
|
184
|
+
def text_document_highlight(self: LspSession, document_highlight_params: Any) {
|
|
185
|
+
fut = self._send_request(
|
|
186
|
+
'textDocument/documentHighlight',
|
|
187
|
+
params=document_highlight_params
|
|
188
|
+
);
|
|
189
|
+
return fut.result();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
"""Sends text document references request to LSP server."""
|
|
193
|
+
def text_document_references(self: LspSession, references_params: Any) {
|
|
194
|
+
fut = self._send_request('textDocument/references', params=references_params);
|
|
195
|
+
return fut.result();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
"""Sends workspace symbol request to LSP server."""
|
|
199
|
+
def workspace_symbol(self: LspSession, workspace_symbol_params: Any) {
|
|
200
|
+
fut = self._send_request('workspace/symbol', params=workspace_symbol_params);
|
|
201
|
+
return fut.result();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
"""Sends completion item resolve request to LSP server."""
|
|
205
|
+
def completion_item_resolve(self: LspSession, resolve_params: Any) {
|
|
206
|
+
fut = self._send_request('completionItem/resolve', params=resolve_params);
|
|
207
|
+
return fut.result();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
"""Sends did change notification to LSP Server."""
|
|
211
|
+
def notify_did_change(self: LspSession, did_change_params: Any) {
|
|
212
|
+
self._send_notification('textDocument/didChange', params=did_change_params);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
"""Sends did save notification to LSP Server."""
|
|
216
|
+
def notify_did_save(self: LspSession, did_save_params: Any) {
|
|
217
|
+
self._send_notification('textDocument/didSave', params=did_save_params);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
"""Sends did open notification to LSP Server."""
|
|
221
|
+
def notify_did_open(self: LspSession, did_open_params: Any) {
|
|
222
|
+
self._send_notification('textDocument/didOpen', params=did_open_params);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
"""Set custom LS notification handler."""
|
|
226
|
+
def set_notification_callback(
|
|
227
|
+
self: LspSession,
|
|
228
|
+
notification_name: Any,
|
|
229
|
+
callback: Any
|
|
230
|
+
) {
|
|
231
|
+
self._notification_callbacks[notification_name] = callback;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
"""Gets callback if set or default callback for a given LS notification."""
|
|
235
|
+
def get_notification_callback(self: LspSession, notification_name: Any) {
|
|
236
|
+
try {
|
|
237
|
+
return self._notification_callbacks[notification_name];
|
|
238
|
+
} except KeyError {
|
|
239
|
+
def _default_handler(_params: Any) -> None {}
|
|
240
|
+
return _default_handler;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
"""Internal handler for text document publish diagnostics."""
|
|
246
|
+
def _publish_diagnostics(self: LspSession, publish_diagnostics_params: Any) {
|
|
247
|
+
return self._handle_notification(
|
|
248
|
+
PUBLISH_DIAGNOSTICS,
|
|
249
|
+
publish_diagnostics_params
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
"""Internal handler for window log message."""
|
|
254
|
+
def _window_log_message(self: LspSession, window_log_message_params: Any) {
|
|
255
|
+
return self._handle_notification(WINDOW_LOG_MESSAGE, window_log_message_params);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
"""Internal handler for window show message."""
|
|
259
|
+
def _window_show_message(self: LspSession, window_show_message_params: Any) {
|
|
260
|
+
return self._handle_notification(
|
|
261
|
+
WINDOW_SHOW_MESSAGE,
|
|
262
|
+
window_show_message_params
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
"""Internal handler for notifications."""
|
|
267
|
+
def _handle_notification(self: LspSession, notification_name: Any, params: Any) {
|
|
268
|
+
fut = Future();
|
|
269
|
+
def _handler() {
|
|
270
|
+
callback = self.get_notification_callback(notification_name);
|
|
271
|
+
callback(params);
|
|
272
|
+
fut.set_result(None);
|
|
273
|
+
}
|
|
274
|
+
self._thread_pool.submit(_handler);
|
|
275
|
+
return fut;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
"""Sends {name} request to the LSP server."""
|
|
279
|
+
def _send_request(
|
|
280
|
+
self: LspSession,
|
|
281
|
+
name: Any,
|
|
282
|
+
params: Any = None,
|
|
283
|
+
handle_response: Any = lambda f: Any: f.done()
|
|
284
|
+
) {
|
|
285
|
+
fut = self._endpoint.request(name, params);
|
|
286
|
+
fut.add_done_callback(handle_response);
|
|
287
|
+
return fut;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
"""Sends {name} notification to the LSP server."""
|
|
291
|
+
def _send_notification(self: LspSession, name: Any, params: Any = None) {
|
|
292
|
+
self._endpoint.notify(name, params);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
4
|
import lsprotocol.types as lspt
|
|
5
|
-
|
|
6
|
-
from jaclang.langserve.sem_manager import SemTokManager
|
|
5
|
+
from jaclang import JacMachineInterface as _
|
|
7
6
|
from jaclang.utils.test import TestCase
|
|
7
|
+
from jaclang.langserve.sem_manager import SemTokManager
|
|
8
8
|
|
|
9
9
|
from typing import Tuple
|
|
10
10
|
|