diff --git a/lua/hopcsharp/database/utils.lua b/lua/hopcsharp/database/utils.lua index 1e8c37a..5bfcad6 100644 --- a/lua/hopcsharp/database/utils.lua +++ b/lua/hopcsharp/database/utils.lua @@ -29,6 +29,8 @@ M.types = { ENUM = 5, METHOD = 6, CONSTRUCTOR = 7, + FIELD = 8, + PROPERTY = 9, } M.reference_types = { @@ -39,6 +41,7 @@ M.reference_types = { TYPE_ARGUMENT = 5, TYPEOF_EXPRESSION = 6, PARAMETER = 7, + MEMBER_ACCESS = 8, } M.get_type_name = function(type) @@ -69,6 +72,14 @@ M.get_type_name = function(type) if type == M.types.CONSTRUCTOR then return 'constructor' end + + if type == M.types.FIELD then + return 'field' + end + + if type == M.types.PROPERTY then + return 'property' + end end M.get_reference_type_name = function(type) @@ -99,6 +110,10 @@ M.get_reference_type_name = function(type) if type == M.reference_types.PARAMETER then return 'parameter' end + + if type == M.reference_types.MEMBER_ACCESS then + return 'member' + end end return M diff --git a/lua/hopcsharp/parse/definition.lua b/lua/hopcsharp/parse/definition.lua index cafc04b..fb24a88 100644 --- a/lua/hopcsharp/parse/definition.lua +++ b/lua/hopcsharp/parse/definition.lua @@ -36,6 +36,11 @@ M.__parse_definitions = function(tree, path_id, namespace_id, file_content, writ type = dbutils.types.INTERFACE elseif parent_node_type == 'constructor_declaration' then type = dbutils.types.CONSTRUCTOR + elseif parent_node_type == 'variable_declarator' then + -- see query for field_declaration + type = dbutils.types.FIELD + elseif parent_node_type == 'property_declaration' then + type = dbutils.types.PROPERTY end local row, column, _, _ = node:range() diff --git a/lua/hopcsharp/parse/query.lua b/lua/hopcsharp/parse/query.lua index ea26839..85fae3a 100644 --- a/lua/hopcsharp/parse/query.lua +++ b/lua/hopcsharp/parse/query.lua @@ -11,6 +11,8 @@ M.declaration_identifier = utils.__get_query([[ (method_declaration name: (identifier) @name) (interface_declaration name: (identifier) @name) (constructor_declaration name: (identifier) @name) + (property_declaration name: (identifier) @name) + (field_declaration (variable_declaration (variable_declarator name: (identifier) @name))) ] ]]) @@ -28,10 +30,11 @@ M.reference = utils.__get_query([[ (invocation_expression function: [ (identifier) @name (generic_name (identifier) @name) - (member_access_expression name: (identifier) @name) - (member_access_expression name: (generic_name (identifier) @name)) ]) + (member_access_expression name: (identifier) @name) + (member_access_expression name: (generic_name (identifier) @name)) + (variable_declaration type: [ (identifier) @name (generic_name (identifier) @name) diff --git a/lua/hopcsharp/parse/reference.lua b/lua/hopcsharp/parse/reference.lua index c511ad8..bf5592f 100644 --- a/lua/hopcsharp/parse/reference.lua +++ b/lua/hopcsharp/parse/reference.lua @@ -15,7 +15,7 @@ M.__parse_reference = function(tree, path_id, namespace_id, file_content, writer -- should not be a problem with nulls -- those nodes are always inside other nodes - if parent_node_type == 'generic_name' or parent_node_type == 'member_access_expression' then + if parent_node_type == 'generic_name' then parent_node_type = node:parent():parent():type() end @@ -28,9 +28,7 @@ M.__parse_reference = function(tree, path_id, namespace_id, file_content, writer elseif parent_node_type == 'invocation_expression' then type = dbutils.reference_types.METHOD_INVOCATION elseif parent_node_type == 'member_access_expression' then - -- in context of a query that is being used, - -- it must be a method invocation - type = dbutils.reference_types.METHOD_INVOCATION + type = dbutils.reference_types.MEMBER_ACCESS elseif parent_node_type == 'attribute' then type = dbutils.reference_types.ATTRIBUTE elseif parent_node_type == 'variable_declaration' then diff --git a/test/parse/definition_spec.lua b/test/parse/definition_spec.lua index c7481e2..f6968ce 100644 --- a/test/parse/definition_spec.lua +++ b/test/parse/definition_spec.lua @@ -84,4 +84,77 @@ describe('parse.definition', function() assert(rows[1].namespace == 'This.Is.Namespace.One') end, writer) end) + + it('__parse_definitions populates database correctly (fields and properties)', function() + database.__drop_db() + local path = vim.fn.getcwd() .. '/test/sources/ClassWithPropertiesAndFields.cs' + local writer = BufferedWriter:new(database.__get_db(), 1) + local db = database.__get_db() + + parse.__parse_tree(path, function(tree, path_id, file_content, wr) + local namespace_id = namespace.__parse_namespaces(tree:root(), path_id, file_content) + definition.__parse_definitions(tree:root(), path_id, namespace_id, file_content, wr) + + local rows = db:eval(query.get_definition_by_name_and_type('m_Field1', utils.types.FIELD)) + -- one for class, one for record and one for struct + assert(#rows == 3) + assert(rows[1].name == 'm_Field1') + assert(rows[1].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[1].type == utils.types.FIELD) + assert(rows[1].namespace == 'This.Is.Namespace.One') + assert(rows[2].name == 'm_Field1') + assert(rows[2].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[2].type == utils.types.FIELD) + assert(rows[2].namespace == 'This.Is.Namespace.One') + assert(rows[3].name == 'm_Field1') + assert(rows[3].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[3].type == utils.types.FIELD) + assert(rows[3].namespace == 'This.Is.Namespace.One') + + rows = db:eval(query.get_definition_by_name_and_type('m_Field2', utils.types.FIELD)) + assert(#rows == 3) + assert(rows[1].name == 'm_Field2') + assert(rows[1].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[1].type == utils.types.FIELD) + assert(rows[1].namespace == 'This.Is.Namespace.One') + assert(rows[2].name == 'm_Field2') + assert(rows[2].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[2].type == utils.types.FIELD) + assert(rows[2].namespace == 'This.Is.Namespace.One') + assert(rows[3].name == 'm_Field2') + assert(rows[3].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[3].type == utils.types.FIELD) + assert(rows[3].namespace == 'This.Is.Namespace.One') + + rows = db:eval(query.get_definition_by_name_and_type('Property1', utils.types.PROPERTY)) + assert(#rows == 3) + assert(rows[1].name == 'Property1') + assert(rows[1].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[1].type == utils.types.PROPERTY) + assert(rows[1].namespace == 'This.Is.Namespace.One') + assert(rows[2].name == 'Property1') + assert(rows[2].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[2].type == utils.types.PROPERTY) + assert(rows[2].namespace == 'This.Is.Namespace.One') + assert(rows[3].name == 'Property1') + assert(rows[3].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[3].type == utils.types.PROPERTY) + assert(rows[3].namespace == 'This.Is.Namespace.One') + + rows = db:eval(query.get_definition_by_name_and_type('Property2', utils.types.PROPERTY)) + assert(#rows == 3) + assert(rows[1].name == 'Property2') + assert(rows[1].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[1].type == utils.types.PROPERTY) + assert(rows[1].namespace == 'This.Is.Namespace.One') + assert(rows[2].name == 'Property2') + assert(rows[2].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[2].type == utils.types.PROPERTY) + assert(rows[2].namespace == 'This.Is.Namespace.One') + assert(rows[3].name == 'Property2') + assert(rows[3].path:match('test/sources/ClassWithPropertiesAndFields.cs$')) + assert(rows[3].type == utils.types.PROPERTY) + assert(rows[3].namespace == 'This.Is.Namespace.One') + end, writer) + end) end) diff --git a/test/parse/query_spec.lua b/test/parse/query_spec.lua index 9e4e5f0..6dce294 100644 --- a/test/parse/query_spec.lua +++ b/test/parse/query_spec.lua @@ -161,6 +161,58 @@ describe('parse.query', function() assert(visited_count == 2) end) + it('declaration identifier - property', function() + local content = [[ + namespace My.Test.Namespace; + public class Class1 { + public int Property { get; set; } + } + ]] + + local visited = false + local parser = assert(vim.treesitter.get_string_parser(content, 'c_sharp', { error = false })) + parser:parse(false, function(_, trees) + assert(trees) + parser:for_each_tree(function(tree, _) + assert(tree) + for _, node, _, _ in query.declaration_identifier:iter_captures(tree:root(), content, 0, -1) do + if node:parent():type() == 'property_declaration' then + local name = vim.treesitter.get_node_text(node, content, nil) + visited = true + assert(name == 'Property') + end + end + end) + end) + assert(visited) + end) + + it('declaration identifier - field', function() + local content = [[ + namespace My.Test.Namespace; + public class Class1 { + private int m_DeclaredField; + } + ]] + + local visited = false + local parser = assert(vim.treesitter.get_string_parser(content, 'c_sharp', { error = false })) + parser:parse(false, function(_, trees) + assert(trees) + parser:for_each_tree(function(tree, _) + assert(tree) + for _, node, _, _ in query.declaration_identifier:iter_captures(tree:root(), content, 0, -1) do + if node:parent():type() == 'variable_declarator' then + local name = vim.treesitter.get_node_text(node, content, nil) + visited = true + assert(name == 'm_DeclaredField') + end + end + end) + end) + assert(visited) + end) + it('base identifier', function() local content = [[ namespace My.Test.Namespace; diff --git a/test/parse/reference_spec.lua b/test/parse/reference_spec.lua index 92ff04a..6b8c5b9 100644 --- a/test/parse/reference_spec.lua +++ b/test/parse/reference_spec.lua @@ -69,14 +69,14 @@ describe('parse.reference', function() assert(row.namespace == 'This.Is.Reference.Namespace') end - -- method Run - rows = db:eval(query.get_reference_by_name_and_type('Run', utils.reference_types.METHOD_INVOCATION)) + -- method Run (member accessed) + rows = db:eval(query.get_reference_by_name_and_type('Run', utils.reference_types.MEMBER_ACCESS)) assert(#rows == 2) for _, row in ipairs(rows) do assert(row.name == 'Run') assert(row.path:match('test/sources/hop_to_reference.cs$')) - assert(row.type == utils.reference_types.METHOD_INVOCATION) + assert(row.type == utils.reference_types.MEMBER_ACCESS) assert(row.namespace == 'This.Is.Reference.Namespace') end diff --git a/test/sources/ClassWithPropertiesAndFields.cs b/test/sources/ClassWithPropertiesAndFields.cs new file mode 100644 index 0000000..12d270e --- /dev/null +++ b/test/sources/ClassWithPropertiesAndFields.cs @@ -0,0 +1,24 @@ + +namespace This.Is.Namespace.One; + +public class ClassWithPropertiesAndFields { + private bool m_Field1; + private bool m_Field2; + public bool Property1 { get; set; } + public bool Property2 { get; set; } +} + +public record RecordWithPropertiesAndFields { + private bool m_Field1; + private bool m_Field2; + public bool Property1 { get; set; } + public bool Property2 { get; set; } +} + +public struct StructWithPropertiesAndFields { + private bool m_Field1; + private bool m_Field2; + public bool Property1 { get; set; } + public bool Property2 { get; set; } +} +