From 3a8b196e4f738725c6c45493f007a9013b93f545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Mon, 18 Aug 2025 12:13:12 +0100 Subject: [PATCH 01/23] feat: Find meta comment category declaration. Also corrected issue with conversion from TreeSitter to LSP position which was causing errors with diagnostics. --- src/malls/ts/utils.py | 53 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index d941287..bfd7e88 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -265,16 +265,19 @@ def tree_sitter_to_lsp_position(text: str, pos: Point, new_text: str = None) -> lines = text.splitlines(keepends=True) - line_text = lines[ts_line] + if len(lines) > ts_line: + line_text = lines[ts_line] - # Decode the line text from UTF-8 to a string - line_string = line_text.decode("utf-8") + # Decode the line text from UTF-8 to a string + line_string = line_text.decode("utf-8") - # Get the slice of the string up to the byte offset - string_slice = line_string.encode("utf-8")[:ts_byte_offset].decode("utf-8") + # Get the slice of the string up to the byte offset + string_slice = line_string.encode("utf-8")[:ts_byte_offset].decode("utf-8") - # The length of this slice in UTF-16 code units is the LSP character position - lsp_char = len(string_slice.encode("utf-16-le")) // 2 + # The length of this slice in UTF-16 code units is the LSP character position + lsp_char = len(string_slice.encode("utf-16-le")) // 2 + else: + lsp_char = 0 return Position(line=ts_line, character=lsp_char) @@ -1137,3 +1140,39 @@ def query_for_error_nodes(tree: Tree, text: str, doc_uri: str, notification_stor notification_storage[doc_uri] = [diagnostic] return + + +def find_meta_comment_category_declaration(node: Node): + """ + In a category declaration, we will try to find if the node has + any meta information and, if so, return it. + """ + meta_info = [] + for children in node.children_by_field_name("meta"): + meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + + return meta_info + + +def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None, storage: list = None) -> list: + """ + Given a node and a symbol, this function will find the point + where that symbol is defined. + + Since the node can be of any type, we need to go up the parent + tree until we find a parent from which we can extract relevant + information. + """ + + original_position = (node.start_point, node.end_point) + + while True: + match node.type: + case "category_declaration": + return find_meta_comment_category_declaration(node) + case _: + node = node.parent # go to parent if no info proved relevant + # terminate if there are no more parents + if node is None: + return (None, document_uri) + From bffb15dc29fe47ff0f7b5f8d60f0ca5475a56c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Mon, 18 Aug 2025 12:13:29 +0100 Subject: [PATCH 02/23] tests: Find meta comment for category declaration. --- .../mal/find_meta_comment_function.mal | 24 ++++++++++++++ .../test_find_meta_comment_function.py | 32 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/fixtures/mal/find_meta_comment_function.mal create mode 100644 tests/integration/test_find_meta_comment_function.py diff --git a/tests/fixtures/mal/find_meta_comment_function.mal b/tests/fixtures/mal/find_meta_comment_function.mal new file mode 100644 index 0000000..bcb1e22 --- /dev/null +++ b/tests/fixtures/mal/find_meta_comment_function.mal @@ -0,0 +1,24 @@ +#id: "org.mal-lang.testAnalyzer" +#version:"0.0.0" + +category Example +developer info: "dev cat" +modeler info: "mod cat" +{ + + abstract asset Asset1 + { + let var = c + | compromise + -> var.destroy + } + asset Asset2 extends Asset3 + { + | destroy + } +} +associations +{ + Asset1 [a] * <-- L --> * [c] Asset2 developer info: "some info" + Asset2 [d] 1 <-- M --> 1 [e] Asset2 +} diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/integration/test_find_meta_comment_function.py new file mode 100644 index 0000000..ee65b30 --- /dev/null +++ b/tests/integration/test_find_meta_comment_function.py @@ -0,0 +1,32 @@ +import tree_sitter_mal as ts_mal +import pytest +from tree_sitter import Language, Parser + +from malls.ts.utils import find_meta_comment_function + +MAL_LANGUAGE = Language(ts_mal.language()) +PARSER = Parser(MAL_LANGUAGE) + +parameters = [ + ((3, 12), [b"dev cat", b"mod cat"]) +] + +@pytest.mark.parametrize( + "point,comments", + [(point, comment) for point, comment in parameters], +) +def test_find_meta_comment_function(mal_find_meta_comment_function, point, comments): + tree = PARSER.parse(mal_find_meta_comment_function.read()) + + # get the node + cursor = tree.walk() + while cursor.goto_first_child_for_point(point) is not None: + continue + + # confirm it's an identifier + assert cursor.node.type == "identifier" + + # we use sets to ensure order does not matter + returned_comments = find_meta_comment_function(cursor.node, cursor.node.text) + + assert set(returned_comments) == set(comments) From d59a82ae018277b30e15045c780ae4ca9d1b55a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Mon, 18 Aug 2025 12:22:21 +0100 Subject: [PATCH 03/23] feat: Find meta comment for asset declaration. --- src/malls/ts/utils.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index bfd7e88..af66ba2 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -1154,6 +1154,18 @@ def find_meta_comment_category_declaration(node: Node): return meta_info +def find_meta_comment_asset_declaration(node: Node): + """ + In an asset declaration, we will try to find if the node has + any meta information and, if so, return it. + """ + meta_info = [] + for children in node.children_by_field_name("meta"): + meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + + return meta_info + + def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None, storage: list = None) -> list: """ Given a node and a symbol, this function will find the point @@ -1170,6 +1182,8 @@ def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None match node.type: case "category_declaration": return find_meta_comment_category_declaration(node) + case "asset_declaration": + return find_meta_comment_asset_declaration(node) case _: node = node.parent # go to parent if no info proved relevant # terminate if there are no more parents From a44968826c169dbd4747e29f0b80f991a0935c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Mon, 18 Aug 2025 12:22:47 +0100 Subject: [PATCH 04/23] tests: Find meta comment for asset declaration. --- tests/fixtures/mal/find_meta_comment_function.mal | 2 ++ tests/integration/test_find_meta_comment_function.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/mal/find_meta_comment_function.mal b/tests/fixtures/mal/find_meta_comment_function.mal index bcb1e22..8240564 100644 --- a/tests/fixtures/mal/find_meta_comment_function.mal +++ b/tests/fixtures/mal/find_meta_comment_function.mal @@ -7,6 +7,8 @@ modeler info: "mod cat" { abstract asset Asset1 + developer info: "dev asset" + modeler info: "mod asset" { let var = c | compromise diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/integration/test_find_meta_comment_function.py index ee65b30..c4ed6d1 100644 --- a/tests/integration/test_find_meta_comment_function.py +++ b/tests/integration/test_find_meta_comment_function.py @@ -8,7 +8,8 @@ PARSER = Parser(MAL_LANGUAGE) parameters = [ - ((3, 12), [b"dev cat", b"mod cat"]) + ((3, 12), [b"dev cat", b"mod cat"]), + ((8, 22), [b"dev asset", b"mod asset"]), ] @pytest.mark.parametrize( From b2e6a033d77073ae7d84752731047d32645ebde8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Mon, 18 Aug 2025 12:27:26 +0100 Subject: [PATCH 05/23] feat: Find meta comment for attack step. --- src/malls/ts/utils.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index af66ba2..eda6ff2 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -1142,7 +1142,7 @@ def query_for_error_nodes(tree: Tree, text: str, doc_uri: str, notification_stor return -def find_meta_comment_category_declaration(node: Node): +def find_meta_comment_category_declaration(node: Node) -> list: """ In a category declaration, we will try to find if the node has any meta information and, if so, return it. @@ -1154,7 +1154,7 @@ def find_meta_comment_category_declaration(node: Node): return meta_info -def find_meta_comment_asset_declaration(node: Node): +def find_meta_comment_asset_declaration(node: Node) -> list: """ In an asset declaration, we will try to find if the node has any meta information and, if so, return it. @@ -1166,6 +1166,18 @@ def find_meta_comment_asset_declaration(node: Node): return meta_info +def find_meta_comment_attack_step(node: Node) -> list: + """ + In an attack step, we will try to find if the node has + any meta information and, if so, return it. + """ + meta_info = [] + for children in node.children_by_field_name("meta"): + meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + + return meta_info + + def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None, storage: list = None) -> list: """ Given a node and a symbol, this function will find the point @@ -1184,6 +1196,8 @@ def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None return find_meta_comment_category_declaration(node) case "asset_declaration": return find_meta_comment_asset_declaration(node) + case "attack_step": + return find_meta_comment_attack_step(node) case _: node = node.parent # go to parent if no info proved relevant # terminate if there are no more parents From 59aedf1414061ec17cb652999c8952eea70e121f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Mon, 18 Aug 2025 12:27:39 +0100 Subject: [PATCH 06/23] tests: Find meta comment for attack step. --- tests/fixtures/mal/find_meta_comment_function.mal | 2 ++ tests/integration/test_find_meta_comment_function.py | 1 + 2 files changed, 3 insertions(+) diff --git a/tests/fixtures/mal/find_meta_comment_function.mal b/tests/fixtures/mal/find_meta_comment_function.mal index 8240564..8ba2c2b 100644 --- a/tests/fixtures/mal/find_meta_comment_function.mal +++ b/tests/fixtures/mal/find_meta_comment_function.mal @@ -12,6 +12,8 @@ modeler info: "mod cat" { let var = c | compromise + developer info: "dev attack_step" + modeler info: "mod attack_step" -> var.destroy } asset Asset2 extends Asset3 diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/integration/test_find_meta_comment_function.py index c4ed6d1..eda88cd 100644 --- a/tests/integration/test_find_meta_comment_function.py +++ b/tests/integration/test_find_meta_comment_function.py @@ -10,6 +10,7 @@ parameters = [ ((3, 12), [b"dev cat", b"mod cat"]), ((8, 22), [b"dev asset", b"mod asset"]), + ((13, 13), [b"dev attack_step", b"mod attack_step"]), ] @pytest.mark.parametrize( From 0bf0af09fa11bef13fade4203e58f9a880314a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Mon, 18 Aug 2025 12:55:41 +0100 Subject: [PATCH 07/23] feat: Find meta comment for asset variable. --- src/malls/ts/utils.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index eda6ff2..51f0c33 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -1178,7 +1178,26 @@ def find_meta_comment_attack_step(node: Node) -> list: return meta_info -def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None, storage: list = None) -> list: +def find_meta_comment_asset_variable(node: Node, symbol: str, document_uri: str, storage: dict) -> list: + """ + In an asset variable, we will follow the expression + chain and get the asset where the symbol is defined. + Once we have it, we just have to obtain the meta + comments it contains + """ + asset, _ = find_asset_from_expr(node.child_by_field_name("value"), symbol, document_uri, storage, []) + + if not asset: + return [] + + meta_info = [] + for children in asset.children_by_field_name("meta"): + meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + + return meta_info + + +def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None, storage: dict= None) -> list: """ Given a node and a symbol, this function will find the point where that symbol is defined. @@ -1198,6 +1217,8 @@ def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None return find_meta_comment_asset_declaration(node) case "attack_step": return find_meta_comment_attack_step(node) + case 'asset_variable': + return find_meta_comment_asset_variable(node, symbol, document_uri, storage) case _: node = node.parent # go to parent if no info proved relevant # terminate if there are no more parents From 574abc75d30314774131f8bfa7d146e514644e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Mon, 18 Aug 2025 12:55:55 +0100 Subject: [PATCH 08/23] tests: Find meta comment for asset variable. --- .../mal/find_meta_comment_function.mal | 12 ++++++++- .../test_find_meta_comment_function.py | 27 ++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/tests/fixtures/mal/find_meta_comment_function.mal b/tests/fixtures/mal/find_meta_comment_function.mal index 8ba2c2b..9964e36 100644 --- a/tests/fixtures/mal/find_meta_comment_function.mal +++ b/tests/fixtures/mal/find_meta_comment_function.mal @@ -10,19 +10,29 @@ modeler info: "mod cat" developer info: "dev asset" modeler info: "mod asset" { - let var = c + let var = c.b | compromise developer info: "dev attack_step" modeler info: "mod attack_step" -> var.destroy } + + abstract asset Asset3 + developer info: "dev asset3" + modeler info: "mod asset3" + { + + } + asset Asset2 extends Asset3 { | destroy } + } associations { Asset1 [a] * <-- L --> * [c] Asset2 developer info: "some info" Asset2 [d] 1 <-- M --> 1 [e] Asset2 + Asset3 [b] 1 <-- N --> 1 [f] Asset2 } diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/integration/test_find_meta_comment_function.py index eda88cd..3a2d52f 100644 --- a/tests/integration/test_find_meta_comment_function.py +++ b/tests/integration/test_find_meta_comment_function.py @@ -1,16 +1,21 @@ import tree_sitter_mal as ts_mal import pytest from tree_sitter import Language, Parser +from pathlib import Path -from malls.ts.utils import find_meta_comment_function +from malls.lsp.classes import Document +from malls.lsp.utils import recursive_parsing +from malls.ts.utils import INCLUDED_FILES_QUERY, find_meta_comment_function, run_query MAL_LANGUAGE = Language(ts_mal.language()) PARSER = Parser(MAL_LANGUAGE) +FILE_PATH = str(Path(__file__).parent.parent.resolve()) + "/fixtures/mal/" parameters = [ ((3, 12), [b"dev cat", b"mod cat"]), ((8, 22), [b"dev asset", b"mod asset"]), ((13, 13), [b"dev attack_step", b"mod attack_step"]), + ((12, 18), [b"dev asset3", b"mod asset3"]), ] @pytest.mark.parametrize( @@ -18,7 +23,23 @@ [(point, comment) for point, comment in parameters], ) def test_find_meta_comment_function(mal_find_meta_comment_function, point, comments): - tree = PARSER.parse(mal_find_meta_comment_function.read()) + # build the storage (mimicks the file parsing in the server) + storage = {} + + doc_uri = FILE_PATH + "find_meta_comment_function.mal" + source_encoded = mal_find_meta_comment_function.read() + tree = PARSER.parse(source_encoded) + + storage[doc_uri] = Document(tree, source_encoded, doc_uri) + + # obtain the included files + root_node = tree.root_node + + captures = run_query(root_node, INCLUDED_FILES_QUERY) + if "file_name" in captures: + recursive_parsing(FILE_PATH, captures["file_name"], storage, doc_uri, []) + + ################################### # get the node cursor = tree.walk() @@ -29,6 +50,6 @@ def test_find_meta_comment_function(mal_find_meta_comment_function, point, comme assert cursor.node.type == "identifier" # we use sets to ensure order does not matter - returned_comments = find_meta_comment_function(cursor.node, cursor.node.text) + returned_comments = find_meta_comment_function(cursor.node, cursor.node.text, doc_uri, storage) assert set(returned_comments) == set(comments) From 86a4571ffe423102efa2d073dcf94cf1148cbb05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 16:17:38 +0100 Subject: [PATCH 09/23] feat: Find meta comment for asset variable substitution. --- src/malls/ts/utils.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index 51f0c33..0236873 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -1197,6 +1197,25 @@ def find_meta_comment_asset_variable(node: Node, symbol: str, document_uri: str, return meta_info +def find_meta_comment_asset_variable_subsitution(node: Node, symbol: str, document_uri: str, storage: dict) -> list: + """ + In an asset variable, we will follow the expression + chain and get the asset where the symbol is defined. + Once we have it, we just have to obtain the meta + comments it contains + """ + asset, _ = find_asset_from_expr(node.child_by_field_name("value"), symbol, document_uri, storage, []) + + if not asset: + return [] + + meta_info = [] + for children in asset.children_by_field_name("meta"): + meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + + return meta_info + + def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None, storage: dict= None) -> list: """ Given a node and a symbol, this function will find the point From 7c918ccd01bd72eab5dc2db0eabf18224c8a1085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 18:23:40 +0100 Subject: [PATCH 10/23] feat: Completed find meta for asset variable substition. --- src/malls/ts/utils.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index 0236873..bfbac21 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -1199,16 +1199,34 @@ def find_meta_comment_asset_variable(node: Node, symbol: str, document_uri: str, def find_meta_comment_asset_variable_subsitution(node: Node, symbol: str, document_uri: str, storage: dict) -> list: """ - In an asset variable, we will follow the expression - chain and get the asset where the symbol is defined. - Once we have it, we just have to obtain the meta - comments it contains + In an asset variable substition, we will have to first find + where the variable is defined. Afterwards, follow the expression + chain and get the asset referenced by the variable. Once we have + it, we just have to obtain the meta comments it contains. """ - asset, _ = find_asset_from_expr(node.child_by_field_name("value"), symbol, document_uri, storage, []) + + # find where the variable is defined + variable_node, _ = find_symbol_definition_variable_substitution(node, symbol, document_uri, storage) + + if variable_node is None: + # in case the variable is not defined anywhere + return [] + + # divide the expression + assets = [] + visit_expr(node.children[0].walk(), assets, document_uri, storage) + + # obtain the last expression component (so we find the asset referenced by the variable) + asset_symbol = assets[-1] + + # find the asset the variable refers to + asset, _ = find_asset_from_expr(node.child_by_field_name("value"), asset_symbol, document_uri, storage, assets) if not asset: + # in case the asset is not found return [] + # otherwise get the meta corresponding to that asset meta_info = [] for children in asset.children_by_field_name("meta"): meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) @@ -1242,5 +1260,5 @@ def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None node = node.parent # go to parent if no info proved relevant # terminate if there are no more parents if node is None: - return (None, document_uri) + return [] From 729aac6db2db643f57626bd574b0a6c14c23cc2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 18:34:26 +0100 Subject: [PATCH 11/23] fix: Find meta for asset variable substitution. --- src/malls/ts/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index bfbac21..314955d 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -1214,13 +1214,13 @@ def find_meta_comment_asset_variable_subsitution(node: Node, symbol: str, docume # divide the expression assets = [] - visit_expr(node.children[0].walk(), assets, document_uri, storage) + visit_expr(variable_node.children[-1].children[0].walk(), assets, document_uri, storage) # obtain the last expression component (so we find the asset referenced by the variable) asset_symbol = assets[-1] # find the asset the variable refers to - asset, _ = find_asset_from_expr(node.child_by_field_name("value"), asset_symbol, document_uri, storage, assets) + asset, _ = find_asset_from_expr(variable_node.child_by_field_name("value"), asset_symbol, document_uri, storage, assets) if not asset: # in case the asset is not found @@ -1256,6 +1256,8 @@ def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None return find_meta_comment_attack_step(node) case 'asset_variable': return find_meta_comment_asset_variable(node, symbol, document_uri, storage) + case 'asset_variable_substitution': + return find_meta_comment_asset_variable_subsitution(node, symbol, document_uri, storage) case _: node = node.parent # go to parent if no info proved relevant # terminate if there are no more parents From ec57624db68f93fcef6771d4a0d1e8de732c55a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 18:34:47 +0100 Subject: [PATCH 12/23] tests: Find meta comment for asset variable substitution. --- tests/fixtures/mal/find_meta_comment_function.mal | 2 +- tests/integration/test_find_meta_comment_function.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/mal/find_meta_comment_function.mal b/tests/fixtures/mal/find_meta_comment_function.mal index 9964e36..63744ce 100644 --- a/tests/fixtures/mal/find_meta_comment_function.mal +++ b/tests/fixtures/mal/find_meta_comment_function.mal @@ -14,7 +14,7 @@ modeler info: "mod cat" | compromise developer info: "dev attack_step" modeler info: "mod attack_step" - -> var.destroy + -> var().destroy } abstract asset Asset3 diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/integration/test_find_meta_comment_function.py index 3a2d52f..283320a 100644 --- a/tests/integration/test_find_meta_comment_function.py +++ b/tests/integration/test_find_meta_comment_function.py @@ -16,6 +16,7 @@ ((8, 22), [b"dev asset", b"mod asset"]), ((13, 13), [b"dev attack_step", b"mod attack_step"]), ((12, 18), [b"dev asset3", b"mod asset3"]), + ((16, 12), [b"dev asset3", b"mod asset3"]), ] @pytest.mark.parametrize( From 2a42c46e7a3ba5812d4ea13346dd2dea4b64d76c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 18:47:24 +0100 Subject: [PATCH 13/23] feat: Find meta comment for asset expr. --- src/malls/ts/utils.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index 314955d..9959c70 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -1234,6 +1234,29 @@ def find_meta_comment_asset_variable_subsitution(node: Node, symbol: str, docume return meta_info +def find_meta_comment_asset_expr(node: Node, symbol: str, document_uri: str, storage: dict, pos: tuple) -> list: + """ + In an asset variable substition, we will have to first find + where the variable is defined. Afterwards, follow the expression + chain and get the asset referenced by the variable. Once we have + it, we just have to obtain the meta comments it contains. + """ + + # find asset from expression + asset, _ = find_symbol_reaching(node, symbol, pos, document_uri, storage) + + if not asset: + # in case the asset is not found + return [] + + # otherwise get the meta corresponding to that asset + meta_info = [] + for children in asset.children_by_field_name("meta"): + meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + + return meta_info + + def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None, storage: dict= None) -> list: """ Given a node and a symbol, this function will find the point @@ -1258,6 +1281,8 @@ def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None return find_meta_comment_asset_variable(node, symbol, document_uri, storage) case 'asset_variable_substitution': return find_meta_comment_asset_variable_subsitution(node, symbol, document_uri, storage) + case 'asset_expr': + return find_meta_comment_asset_expr(node, symbol, document_uri, storage, original_position) case _: node = node.parent # go to parent if no info proved relevant # terminate if there are no more parents From d5017e7ac69ca02e7b6ad18e51cef8f3f2d0ff11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 18:47:39 +0100 Subject: [PATCH 14/23] tests: Find meta comment for asset expr. --- tests/fixtures/mal/find_meta_comment_function.mal | 11 +++++++++++ tests/integration/test_find_meta_comment_function.py | 1 + 2 files changed, 12 insertions(+) diff --git a/tests/fixtures/mal/find_meta_comment_function.mal b/tests/fixtures/mal/find_meta_comment_function.mal index 63744ce..9052a30 100644 --- a/tests/fixtures/mal/find_meta_comment_function.mal +++ b/tests/fixtures/mal/find_meta_comment_function.mal @@ -15,6 +15,9 @@ modeler info: "mod cat" developer info: "dev attack_step" modeler info: "mod attack_step" -> var().destroy + + | attack + -> c.b.h.attack4 } abstract asset Asset3 @@ -29,10 +32,18 @@ modeler info: "mod cat" | destroy } + asset Asset4 + developer info: "dev asset4" + modeler info: "mod asset4" + { + & attack4 + } + } associations { Asset1 [a] * <-- L --> * [c] Asset2 developer info: "some info" Asset2 [d] 1 <-- M --> 1 [e] Asset2 Asset3 [b] 1 <-- N --> 1 [f] Asset2 + Asset3 [g] 1 <-- O --> 1 [h] Asset4 } diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/integration/test_find_meta_comment_function.py index 283320a..be94a6d 100644 --- a/tests/integration/test_find_meta_comment_function.py +++ b/tests/integration/test_find_meta_comment_function.py @@ -17,6 +17,7 @@ ((13, 13), [b"dev attack_step", b"mod attack_step"]), ((12, 18), [b"dev asset3", b"mod asset3"]), ((16, 12), [b"dev asset3", b"mod asset3"]), + ((19, 15), [b"dev asset4", b"mod asset4"]), ] @pytest.mark.parametrize( From b8ddb0a6a3457cc621b235786b7df9e36e28e2ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 19:00:29 +0100 Subject: [PATCH 15/23] fix: Problem when searching for asset subtype. --- src/malls/ts/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index 9959c70..7c7f178 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -938,10 +938,12 @@ def find_asset_from_expr(node: Node, symbol: str, document_uri: str, storage: di # of associations while assets: el = assets.pop(0) + el_name = el # if we have a tuple, then we have to find the asset directly, not from the association if type(el) is tuple: + el_name = el[0] node, file = bfs_search( - document_uri, FIND_ASSET_DECLARATION, "asset_declaration", el[0], storage + document_uri, FIND_ASSET_DECLARATION, "asset_declaration", el_name, storage ) else: # retrieve name of asset @@ -949,7 +951,7 @@ def find_asset_from_expr(node: Node, symbol: str, document_uri: str, storage: di if not node: break asset_name = node.children_by_field_name("id")[0].text - if el == symbol: + if el_name == symbol: if type(el) is tuple: result = node else: From 887b1573a844619cbc8ee08ae3943e0cb68191ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 19:00:59 +0100 Subject: [PATCH 16/23] tests: Find meta comment for asset subtype. --- tests/fixtures/mal/find_meta_comment_function.mal | 10 +++++++++- tests/integration/test_find_meta_comment_function.py | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/mal/find_meta_comment_function.mal b/tests/fixtures/mal/find_meta_comment_function.mal index 9052a30..1603a67 100644 --- a/tests/fixtures/mal/find_meta_comment_function.mal +++ b/tests/fixtures/mal/find_meta_comment_function.mal @@ -17,7 +17,8 @@ modeler info: "mod cat" -> var().destroy | attack - -> c.b.h.attack4 + -> c.b.h.attack4, + c.b.h[Asset5].attack5 } abstract asset Asset3 @@ -39,6 +40,13 @@ modeler info: "mod cat" & attack4 } + asset Asset5 extends Asset4 + developer info: "dev asset5" + modeler info: "mod asset5" + { + & attack5 + } + } associations { diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/integration/test_find_meta_comment_function.py index be94a6d..4270f1e 100644 --- a/tests/integration/test_find_meta_comment_function.py +++ b/tests/integration/test_find_meta_comment_function.py @@ -18,6 +18,7 @@ ((12, 18), [b"dev asset3", b"mod asset3"]), ((16, 12), [b"dev asset3", b"mod asset3"]), ((19, 15), [b"dev asset4", b"mod asset4"]), + ((20, 20), [b"dev asset5", b"mod asset5"]), ] @pytest.mark.parametrize( From 4d2535414804fb795948f6c16e2b17fd7e6ac6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 19:11:40 +0100 Subject: [PATCH 17/23] fix: Problem when searching for attack step for subtype. --- src/malls/ts/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index 7c7f178..bf318dd 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -997,8 +997,10 @@ def find_symbol_reaching( assets.pop(-1) # remove last element (which is the attack step) # get the asset where the attack step is defined (last element) if assets: # go down the chain + asset_name = assets[-1] + if type(assets[-1]) is tuple: asset_name = asset_name[0] asset, result_file = find_asset_from_expr( - node, assets[-1], document_uri, storage, assets + node, asset_name, document_uri, storage, assets ) else: asset = node.parent.parent.parent # go to asset From 00f8182b2cced2232a28e4dc37b16e1fd713def2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 19:11:57 +0100 Subject: [PATCH 18/23] tests: Find meta comment for attack step for subtype. --- tests/fixtures/mal/find_meta_comment_function.mal | 2 ++ tests/integration/test_find_meta_comment_function.py | 1 + 2 files changed, 3 insertions(+) diff --git a/tests/fixtures/mal/find_meta_comment_function.mal b/tests/fixtures/mal/find_meta_comment_function.mal index 1603a67..d065970 100644 --- a/tests/fixtures/mal/find_meta_comment_function.mal +++ b/tests/fixtures/mal/find_meta_comment_function.mal @@ -45,6 +45,8 @@ modeler info: "mod cat" modeler info: "mod asset5" { & attack5 + developer info: "dev attack_step_5" + modeler info: "mod attack_step_5" } } diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/integration/test_find_meta_comment_function.py index 4270f1e..aa2d9fc 100644 --- a/tests/integration/test_find_meta_comment_function.py +++ b/tests/integration/test_find_meta_comment_function.py @@ -19,6 +19,7 @@ ((16, 12), [b"dev asset3", b"mod asset3"]), ((19, 15), [b"dev asset4", b"mod asset4"]), ((20, 20), [b"dev asset5", b"mod asset5"]), + ((20, 28), [b"dev attack_step_5", b"mod attack_step_5"]), ] @pytest.mark.parametrize( From 01486820229c164ad132786bf7972435b8e06ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 19:28:29 +0100 Subject: [PATCH 19/23] feat: Find meta comment for association. --- src/malls/ts/utils.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index bf318dd..f76cf7f 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -1240,10 +1240,8 @@ def find_meta_comment_asset_variable_subsitution(node: Node, symbol: str, docume def find_meta_comment_asset_expr(node: Node, symbol: str, document_uri: str, storage: dict, pos: tuple) -> list: """ - In an asset variable substition, we will have to first find - where the variable is defined. Afterwards, follow the expression - chain and get the asset referenced by the variable. Once we have - it, we just have to obtain the meta comments it contains. + In an asset expr, we can simply find where the asset mentioned by the symbol is defined + (via the expression chain) and find the needed meta comments. """ # find asset from expression @@ -1261,6 +1259,28 @@ def find_meta_comment_asset_expr(node: Node, symbol: str, document_uri: str, sto return meta_info +def find_meta_comment_association(node: Node, symbol: str, document_uri: str, storage: dict) -> list: + """ + In an association, we can call the auxiliary `find_symbol_definition_association` + which will find the asset referenced by the symbol or the current node otherwise, + from which we can find the corresponding meta. + """ + + # find the node where the meta is defined (either the current node or an asset node) + result_node, _ = find_symbol_definition_association(node, symbol, document_uri, storage) + + if not result_node: + # in case the asset is not found + return [] + + # otherwise get the meta corresponding to that asset + meta_info = [] + for children in result_node.children_by_field_name("meta"): + meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + + return meta_info + + def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None, storage: dict= None) -> list: """ Given a node and a symbol, this function will find the point @@ -1287,6 +1307,8 @@ def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None return find_meta_comment_asset_variable_subsitution(node, symbol, document_uri, storage) case 'asset_expr': return find_meta_comment_asset_expr(node, symbol, document_uri, storage, original_position) + case 'association': + return find_meta_comment_association(node, symbol, document_uri, storage) case _: node = node.parent # go to parent if no info proved relevant # terminate if there are no more parents From b40ac3d56d6e20cc49493ce5ccaf6ed37fc2ce00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 19:28:42 +0100 Subject: [PATCH 20/23] tests: Find meta comment for association. --- tests/integration/test_find_meta_comment_function.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/integration/test_find_meta_comment_function.py index aa2d9fc..5a0bf34 100644 --- a/tests/integration/test_find_meta_comment_function.py +++ b/tests/integration/test_find_meta_comment_function.py @@ -20,6 +20,9 @@ ((19, 15), [b"dev asset4", b"mod asset4"]), ((20, 20), [b"dev asset5", b"mod asset5"]), ((20, 28), [b"dev attack_step_5", b"mod attack_step_5"]), + ((54, 12), [b"some info"]), + ((54, 21), [b"some info"]), + ((57, 37), [b"dev asset4", b"mod asset4"]), ] @pytest.mark.parametrize( From 7c446f8f56abc475d060a3d56a0ca3ae959be87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Tue, 19 Aug 2025 19:29:30 +0100 Subject: [PATCH 21/23] chore: Linter messages. --- src/malls/ts/utils.py | 66 ++++++++++++------- .../test_find_meta_comment_function.py | 6 +- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/malls/ts/utils.py b/src/malls/ts/utils.py index f76cf7f..a1f5aea 100644 --- a/src/malls/ts/utils.py +++ b/src/malls/ts/utils.py @@ -998,7 +998,8 @@ def find_symbol_reaching( # get the asset where the attack step is defined (last element) if assets: # go down the chain asset_name = assets[-1] - if type(assets[-1]) is tuple: asset_name = asset_name[0] + if type(assets[-1]) is tuple: + asset_name = asset_name[0] asset, result_file = find_asset_from_expr( node, asset_name, document_uri, storage, assets ) @@ -1153,7 +1154,7 @@ def find_meta_comment_category_declaration(node: Node) -> list: """ meta_info = [] for children in node.children_by_field_name("meta"): - meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + meta_info.append(children.child_by_field_name("info").text.strip(b'"')) return meta_info @@ -1165,7 +1166,7 @@ def find_meta_comment_asset_declaration(node: Node) -> list: """ meta_info = [] for children in node.children_by_field_name("meta"): - meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + meta_info.append(children.child_by_field_name("info").text.strip(b'"')) return meta_info @@ -1177,31 +1178,37 @@ def find_meta_comment_attack_step(node: Node) -> list: """ meta_info = [] for children in node.children_by_field_name("meta"): - meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + meta_info.append(children.child_by_field_name("info").text.strip(b'"')) return meta_info -def find_meta_comment_asset_variable(node: Node, symbol: str, document_uri: str, storage: dict) -> list: +def find_meta_comment_asset_variable( + node: Node, symbol: str, document_uri: str, storage: dict +) -> list: """ In an asset variable, we will follow the expression chain and get the asset where the symbol is defined. Once we have it, we just have to obtain the meta comments it contains """ - asset, _ = find_asset_from_expr(node.child_by_field_name("value"), symbol, document_uri, storage, []) + asset, _ = find_asset_from_expr( + node.child_by_field_name("value"), symbol, document_uri, storage, [] + ) if not asset: return [] meta_info = [] for children in asset.children_by_field_name("meta"): - meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + meta_info.append(children.child_by_field_name("info").text.strip(b'"')) return meta_info -def find_meta_comment_asset_variable_subsitution(node: Node, symbol: str, document_uri: str, storage: dict) -> list: +def find_meta_comment_asset_variable_subsitution( + node: Node, symbol: str, document_uri: str, storage: dict +) -> list: """ In an asset variable substition, we will have to first find where the variable is defined. Afterwards, follow the expression @@ -1210,7 +1217,9 @@ def find_meta_comment_asset_variable_subsitution(node: Node, symbol: str, docume """ # find where the variable is defined - variable_node, _ = find_symbol_definition_variable_substitution(node, symbol, document_uri, storage) + variable_node, _ = find_symbol_definition_variable_substitution( + node, symbol, document_uri, storage + ) if variable_node is None: # in case the variable is not defined anywhere @@ -1224,7 +1233,9 @@ def find_meta_comment_asset_variable_subsitution(node: Node, symbol: str, docume asset_symbol = assets[-1] # find the asset the variable refers to - asset, _ = find_asset_from_expr(variable_node.child_by_field_name("value"), asset_symbol, document_uri, storage, assets) + asset, _ = find_asset_from_expr( + variable_node.child_by_field_name("value"), asset_symbol, document_uri, storage, assets + ) if not asset: # in case the asset is not found @@ -1233,12 +1244,14 @@ def find_meta_comment_asset_variable_subsitution(node: Node, symbol: str, docume # otherwise get the meta corresponding to that asset meta_info = [] for children in asset.children_by_field_name("meta"): - meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + meta_info.append(children.child_by_field_name("info").text.strip(b'"')) return meta_info -def find_meta_comment_asset_expr(node: Node, symbol: str, document_uri: str, storage: dict, pos: tuple) -> list: +def find_meta_comment_asset_expr( + node: Node, symbol: str, document_uri: str, storage: dict, pos: tuple +) -> list: """ In an asset expr, we can simply find where the asset mentioned by the symbol is defined (via the expression chain) and find the needed meta comments. @@ -1254,12 +1267,14 @@ def find_meta_comment_asset_expr(node: Node, symbol: str, document_uri: str, sto # otherwise get the meta corresponding to that asset meta_info = [] for children in asset.children_by_field_name("meta"): - meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + meta_info.append(children.child_by_field_name("info").text.strip(b'"')) return meta_info -def find_meta_comment_association(node: Node, symbol: str, document_uri: str, storage: dict) -> list: +def find_meta_comment_association( + node: Node, symbol: str, document_uri: str, storage: dict +) -> list: """ In an association, we can call the auxiliary `find_symbol_definition_association` which will find the asset referenced by the symbol or the current node otherwise, @@ -1276,12 +1291,14 @@ def find_meta_comment_association(node: Node, symbol: str, document_uri: str, st # otherwise get the meta corresponding to that asset meta_info = [] for children in result_node.children_by_field_name("meta"): - meta_info.append(children.child_by_field_name("info").text.strip(b"\"")) + meta_info.append(children.child_by_field_name("info").text.strip(b'"')) return meta_info -def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None, storage: dict= None) -> list: +def find_meta_comment_function( + node: Node, symbol: str, document_uri: str = None, storage: dict = None +) -> list: """ Given a node and a symbol, this function will find the point where that symbol is defined. @@ -1301,17 +1318,20 @@ def find_meta_comment_function(node: Node, symbol: str, document_uri: str = None return find_meta_comment_asset_declaration(node) case "attack_step": return find_meta_comment_attack_step(node) - case 'asset_variable': + case "asset_variable": return find_meta_comment_asset_variable(node, symbol, document_uri, storage) - case 'asset_variable_substitution': - return find_meta_comment_asset_variable_subsitution(node, symbol, document_uri, storage) - case 'asset_expr': - return find_meta_comment_asset_expr(node, symbol, document_uri, storage, original_position) - case 'association': + case "asset_variable_substitution": + return find_meta_comment_asset_variable_subsitution( + node, symbol, document_uri, storage + ) + case "asset_expr": + return find_meta_comment_asset_expr( + node, symbol, document_uri, storage, original_position + ) + case "association": return find_meta_comment_association(node, symbol, document_uri, storage) case _: node = node.parent # go to parent if no info proved relevant # terminate if there are no more parents if node is None: return [] - diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/integration/test_find_meta_comment_function.py index 5a0bf34..f85efb4 100644 --- a/tests/integration/test_find_meta_comment_function.py +++ b/tests/integration/test_find_meta_comment_function.py @@ -1,7 +1,8 @@ -import tree_sitter_mal as ts_mal +from pathlib import Path + import pytest +import tree_sitter_mal as ts_mal from tree_sitter import Language, Parser -from pathlib import Path from malls.lsp.classes import Document from malls.lsp.utils import recursive_parsing @@ -25,6 +26,7 @@ ((57, 37), [b"dev asset4", b"mod asset4"]), ] + @pytest.mark.parametrize( "point,comments", [(point, comment) for point, comment in parameters], From d10c3b5250546f7cd4366d455912d9a41ad2ede2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Wed, 20 Aug 2025 12:35:38 +0100 Subject: [PATCH 22/23] chore: Remove duplicate iteration. --- tests/integration/test_find_meta_comment_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/integration/test_find_meta_comment_function.py index f85efb4..839b69b 100644 --- a/tests/integration/test_find_meta_comment_function.py +++ b/tests/integration/test_find_meta_comment_function.py @@ -29,7 +29,7 @@ @pytest.mark.parametrize( "point,comments", - [(point, comment) for point, comment in parameters], + parameters, ) def test_find_meta_comment_function(mal_find_meta_comment_function, point, comments): # build the storage (mimicks the file parsing in the server) From 8bcf9f84efe7ac27a8836fd9bc54d2bea6e31cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Fonseca?= Date: Wed, 20 Aug 2025 12:36:44 +0100 Subject: [PATCH 23/23] chore: Moved test to unit tests. --- tests/{integration => unit}/test_find_meta_comment_function.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{integration => unit}/test_find_meta_comment_function.py (100%) diff --git a/tests/integration/test_find_meta_comment_function.py b/tests/unit/test_find_meta_comment_function.py similarity index 100% rename from tests/integration/test_find_meta_comment_function.py rename to tests/unit/test_find_meta_comment_function.py