holistic-ruby 0.1.1 → 0.1.6

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +1 -1
  4. data/lib/holistic/application.rb +12 -4
  5. data/lib/holistic/database/migrations.rb +20 -0
  6. data/lib/holistic/database/node.rb +29 -0
  7. data/lib/holistic/database/table.rb +53 -53
  8. data/lib/holistic/document/file/record.rb +10 -0
  9. data/lib/holistic/document/file/repository.rb +24 -0
  10. data/lib/holistic/document/file/store.rb +13 -0
  11. data/lib/holistic/document/location.rb +4 -6
  12. data/lib/holistic/document/unsaved/record.rb +0 -4
  13. data/lib/holistic/extensions/events.rb +9 -1
  14. data/lib/holistic/extensions/ruby/stdlib.rb +26 -12
  15. data/lib/holistic/language_server/requests/lifecycle/initialize.rb +5 -10
  16. data/lib/holistic/language_server/requests/text_document/completion.rb +11 -8
  17. data/lib/holistic/language_server/requests/text_document/did_close.rb +6 -12
  18. data/lib/holistic/language_server/requests/text_document/did_open.rb +1 -0
  19. data/lib/holistic/language_server/requests/text_document/did_save.rb +5 -9
  20. data/lib/holistic/language_server/requests/text_document/find_references.rb +7 -4
  21. data/lib/holistic/language_server/requests/text_document/go_to_definition.rb +3 -2
  22. data/lib/holistic/ruby/autocompletion/suggest.rb +65 -15
  23. data/lib/holistic/ruby/parser/constant_resolution.rb +60 -9
  24. data/lib/holistic/ruby/parser/live_editing/process_file_changed.rb +24 -21
  25. data/lib/holistic/ruby/parser/nesting_syntax.rb +1 -0
  26. data/lib/holistic/ruby/parser/program_visitor.rb +57 -44
  27. data/lib/holistic/ruby/parser.rb +14 -9
  28. data/lib/holistic/ruby/reference/delete.rb +18 -0
  29. data/lib/holistic/ruby/reference/find_referenced_scope.rb +2 -2
  30. data/lib/holistic/ruby/reference/record.rb +7 -8
  31. data/lib/holistic/ruby/reference/repository.rb +19 -41
  32. data/lib/holistic/ruby/reference/store.rb +18 -0
  33. data/lib/holistic/ruby/scope/delete.rb +29 -0
  34. data/lib/holistic/ruby/scope/kind.rb +6 -5
  35. data/lib/holistic/ruby/scope/lexical.rb +11 -0
  36. data/lib/holistic/ruby/scope/list_references.rb +2 -2
  37. data/lib/holistic/ruby/scope/location.rb +9 -9
  38. data/lib/holistic/ruby/scope/outline.rb +8 -8
  39. data/lib/holistic/ruby/scope/record.rb +15 -47
  40. data/lib/holistic/ruby/scope/repository.rb +24 -25
  41. data/lib/holistic/ruby/scope/store.rb +45 -0
  42. data/lib/holistic/ruby/type_inference/processing_queue.rb +19 -0
  43. data/lib/holistic/ruby/type_inference/solve.rb +23 -21
  44. data/lib/holistic/ruby/type_inference/solve_pending_references.rb +3 -1
  45. data/lib/holistic/version.rb +1 -1
  46. metadata +13 -9
  47. data/lib/holistic/document/file.rb +0 -36
  48. data/lib/holistic/ruby/parser/table_of_contents.rb +0 -17
  49. data/lib/holistic/ruby/reference/register.rb +0 -15
  50. data/lib/holistic/ruby/reference/unregister.rb +0 -11
  51. data/lib/holistic/ruby/scope/register.rb +0 -31
  52. data/lib/holistic/ruby/scope/unregister.rb +0 -27
  53. data/lib/holistic/ruby/type_inference/conclusion.rb +0 -20
@@ -6,6 +6,12 @@ module Holistic::Ruby::Autocompletion
6
6
 
7
7
  Suggestion = ::Data.define(:code, :kind)
8
8
 
9
+ StartsWithLowerCaseLetter = ->(code) do
10
+ return false if [".", ":", "@"].include?(code[0])
11
+
12
+ code[0] == code[0].downcase
13
+ end
14
+
9
15
  def call(code:, scope:)
10
16
  lookup_scope = scope
11
17
 
@@ -13,12 +19,66 @@ module Holistic::Ruby::Autocompletion
13
19
  lookup_scope = lookup_scope.parent until lookup_scope.root?
14
20
  end
15
21
 
16
- suggest_namespaces_from_scope(code:, scope: lookup_scope)
22
+ if StartsWithLowerCaseLetter[code]
23
+ suggest_local_methods_from_current_scope(code:, scope: lookup_scope)
24
+ elsif code.include?(".")
25
+ suggest_methods_from_scope(code:, scope: lookup_scope)
26
+ else
27
+ suggest_namespaces_from_scope(code:, scope: lookup_scope)
28
+ end
17
29
  end
18
30
 
19
31
  private
20
32
 
21
- NonMethods = ->(scope) { !scope.method? }
33
+ def suggest_local_methods_from_current_scope(code:, scope:)
34
+ suggestions = []
35
+
36
+ method_to_autocomplete = code
37
+
38
+ if scope.instance_method?
39
+ sibling_methods = scope.parent.children.filter { _1.instance_method? }
40
+
41
+ sibling_methods.each do |method_scope|
42
+ if method_scope.name.start_with?(method_to_autocomplete)
43
+ suggestions << Suggestion.new(code: method_scope.name, kind: method_scope.kind)
44
+ end
45
+ end
46
+ elsif scope.class_method?
47
+ sibling_methods = scope.parent.children.filter { _1.class_method? }
48
+
49
+ sibling_methods.each do |method_scope|
50
+ if method_scope.name.start_with?(method_to_autocomplete)
51
+ suggestions << Suggestion.new(code: method_scope.name, kind: method_scope.kind)
52
+ end
53
+ end
54
+ end
55
+
56
+ suggestions
57
+ end
58
+
59
+ def suggest_methods_from_scope(code:, scope:)
60
+ suggestions = []
61
+
62
+ partial_namespaces = code.split(/(::|\.)/).compact_blank
63
+ method_to_autocomplete = partial_namespaces.pop.then { _1 == "." ? "" : _1 }
64
+ namespaces_to_resolve = partial_namespaces.reject { _1 == "::" || _1 == "." }
65
+
66
+ namespaces_to_resolve.each do |namespace_name|
67
+ scope = resolve_scope(name: namespace_name, from_scope: scope)
68
+
69
+ return suggestions if scope.nil?
70
+ end
71
+
72
+ class_methods = scope.children.filter { _1.class_method? }
73
+
74
+ class_methods.each do |method_scope|
75
+ if method_scope.name.start_with?(method_to_autocomplete)
76
+ suggestions << Suggestion.new(code: method_scope.name, kind: method_scope.kind)
77
+ end
78
+ end
79
+
80
+ suggestions
81
+ end
22
82
 
23
83
  def suggest_namespaces_from_scope(code:, scope:)
24
84
  suggestions = []
@@ -33,22 +93,12 @@ module Holistic::Ruby::Autocompletion
33
93
  return suggestions if scope.nil?
34
94
  end
35
95
 
36
- # special case when user did not type :: at the end but the current word
37
- # is matches an existing namespace. In this case, suggestions will start with ::.
38
- # For example:
39
- #
40
- # \/ cursor here
41
- # typing: "::MyApp::Payments"
42
- # suggestions: ["::Record", "::SendReminder"]
43
- resolve_scope(name: namespace_to_autocomplete, from_scope: scope)&.then do |fully_typed_scope|
44
- scope = fully_typed_scope
45
- namespace_to_autocomplete = ""
46
- end
47
-
48
96
  should_search_upwards = namespaces_to_resolve.empty?
49
97
 
50
98
  search = ->(scope) do
51
- scope.children.filter(&NonMethods).each do |child_scope|
99
+ scope.children.each do |child_scope|
100
+ next if child_scope.method?
101
+
52
102
  if child_scope.name.start_with?(namespace_to_autocomplete)
53
103
  suggestions << Suggestion.new(code: child_scope.name, kind: child_scope.kind)
54
104
  end
@@ -2,12 +2,18 @@
2
2
 
3
3
  module Holistic::Ruby::Parser
4
4
  class ConstantResolution
5
- attr_reader :scope_repository, :scope
5
+ module MethodRegistrationMode
6
+ INSTANCE_METHODS = :instance_methods
7
+ CLASS_METHODS = :class_methods
8
+ end
9
+
10
+ attr_reader :scope_repository, :scope, :method_registration_mode
6
11
 
7
- def initialize(scope_repository:, root_scope:)
12
+ def initialize(scope_repository:)
8
13
  @scope_repository = scope_repository
9
- @scope = root_scope
14
+ @scope = scope_repository.root
10
15
  @constant_resolution_possibilities = ["::"]
16
+ @method_registration_mode = MethodRegistrationMode::INSTANCE_METHODS
11
17
  end
12
18
 
13
19
  def current
@@ -19,8 +25,8 @@ module Holistic::Ruby::Parser
19
25
 
20
26
  nesting.each do |name|
21
27
  @scope =
22
- ::Holistic::Ruby::Scope::Register.call(
23
- repository: @scope_repository,
28
+ ::Holistic::Ruby::Scope::Store.call(
29
+ database: @scope_repository.database,
24
30
  parent: @scope,
25
31
  kind: ::Holistic::Ruby::Scope::Kind::MODULE,
26
32
  name:,
@@ -28,12 +34,17 @@ module Holistic::Ruby::Parser
28
34
  )
29
35
  end
30
36
 
37
+ registered_module_scope = @scope
38
+
31
39
  @constant_resolution_possibilities.unshift(@scope.fully_qualified_name)
32
40
 
33
41
  block.call
34
42
 
35
- @scope = starting_scope
43
+ change_method_registration_mode_to_instance_methods!
36
44
  @constant_resolution_possibilities.shift
45
+ @scope = starting_scope
46
+
47
+ registered_module_scope
37
48
  end
38
49
 
39
50
  def register_child_class(nesting:, location:, &block)
@@ -41,8 +52,8 @@ module Holistic::Ruby::Parser
41
52
 
42
53
  nesting.each do |name|
43
54
  @scope =
44
- ::Holistic::Ruby::Scope::Register.call(
45
- repository: @scope_repository,
55
+ ::Holistic::Ruby::Scope::Store.call(
56
+ database: @scope_repository.database,
46
57
  parent: @scope,
47
58
  kind: ::Holistic::Ruby::Scope::Kind::CLASS,
48
59
  name:,
@@ -50,12 +61,52 @@ module Holistic::Ruby::Parser
50
61
  )
51
62
  end
52
63
 
64
+ registered_class_scope = @scope
65
+
53
66
  @constant_resolution_possibilities.unshift(@scope.fully_qualified_name)
54
67
 
55
68
  block.call
56
69
 
57
- @scope = starting_scope
70
+ change_method_registration_mode_to_instance_methods!
58
71
  @constant_resolution_possibilities.shift
72
+ @scope = starting_scope
73
+
74
+ registered_class_scope
75
+ end
76
+
77
+ def register_child_method(nesting:, location:, kind:, &block)
78
+ starting_scope = @scope
79
+
80
+ nesting.each do |name|
81
+ @scope =
82
+ ::Holistic::Ruby::Scope::Store.call(
83
+ database: @scope_repository.database,
84
+ parent: @scope,
85
+ kind:,
86
+ name:,
87
+ location:
88
+ )
89
+ end
90
+
91
+ registered_method_scope = @scope
92
+
93
+ block.call
94
+
95
+ @scope = starting_scope
96
+
97
+ registered_method_scope
98
+ end
99
+
100
+ def method_registration_class_methods?
101
+ method_registration_mode == MethodRegistrationMode::CLASS_METHODS
102
+ end
103
+
104
+ def change_method_registration_mode_to_class_methods!
105
+ @method_registration_mode = MethodRegistrationMode::CLASS_METHODS
106
+ end
107
+
108
+ def change_method_registration_mode_to_instance_methods!
109
+ @method_registration_mode = MethodRegistrationMode::INSTANCE_METHODS
59
110
  end
60
111
  end
61
112
  end
@@ -4,56 +4,59 @@ module Holistic::Ruby::Parser
4
4
  module LiveEditing::ProcessFileChanged
5
5
  extend self
6
6
 
7
- def call(application:, file:)
8
- references_to_recalculate = identify_references_to_recalculate_type_inference(application:, file:)
7
+ def call(application:, file_path:, content:)
8
+ # TODO: do not build the AST twice
9
+ return unless HasValidSyntax[content]
9
10
 
10
- unregister_scopes_in_file(application:, file:)
11
- unregsiter_references_in_file(application:, file:)
11
+ references_to_recalculate = identify_references_to_recalculate(application:, file_path:)
12
12
 
13
- parse_again(application:, file:)
13
+ delete_scopes_in_file(application:, file_path:)
14
+ delete_references_in_file(application:, file_path:)
15
+
16
+ parse_again(application:, file_path:, content:)
14
17
 
15
18
  recalculate_type_inference_for_references(application:, references: references_to_recalculate)
16
19
  end
17
20
 
18
21
  private
19
22
 
20
- def identify_references_to_recalculate_type_inference(application:, file:)
23
+ def identify_references_to_recalculate(application:, file_path:)
21
24
  # we need to reject references declared in the same because they're already going to be
22
- # unregistered and reparsed. If we don't do that, we'll end up with duplicated reference records.
25
+ # reparsed. If we don't do that, we'll end up with duplicated reference records.
23
26
 
24
27
  application.references
25
- .list_references_to_scopes_in_file(scopes: application.scopes, file_path: file.path)
26
- .reject { _1.location.file_path == file.path }
28
+ .list_references_to_scopes_in_file(scopes: application.scopes, file_path: file_path)
29
+ .reject { _1.location.file.path == file_path }
27
30
  end
28
31
 
29
- def unregister_scopes_in_file(application:, file:)
30
- application.scopes.list_scopes_in_file(file.path).each do |scope|
31
- ::Holistic::Ruby::Scope::Unregister.call(
32
- repository: application.scopes,
32
+ def delete_scopes_in_file(application:, file_path:)
33
+ application.scopes.list_scopes_in_file(file_path).each do |scope|
34
+ ::Holistic::Ruby::Scope::Delete.call(
35
+ database: application.database,
33
36
  fully_qualified_name: scope.fully_qualified_name,
34
- file_path: file.path
37
+ file_path:
35
38
  )
36
39
  end
37
40
  end
38
41
 
39
- def unregsiter_references_in_file(application:, file:)
40
- application.references.list_references_in_file(file.path).each do |reference|
41
- ::Holistic::Ruby::Reference::Unregister.call(
42
- repository: application.references,
42
+ def delete_references_in_file(application:, file_path:)
43
+ application.references.list_references_in_file(file_path).each do |reference|
44
+ ::Holistic::Ruby::Reference::Delete.call(
45
+ database: application.database,
43
46
  reference: reference
44
47
  )
45
48
  end
46
49
  end
47
50
 
48
- def parse_again(application:, file:)
49
- ParseFile.call(application:, file:)
51
+ def parse_again(application:, file_path:, content:)
52
+ ParseFile.call(application:, file_path:, content:)
50
53
 
51
54
  ::Holistic::Ruby::TypeInference::SolvePendingReferences.call(application:)
52
55
  end
53
56
 
54
57
  def recalculate_type_inference_for_references(application:, references:)
55
58
  references.each do |reference|
56
- reference.conclusion = ::Holistic::Ruby::TypeInference::Conclusion.pending
59
+ application.database.disconnect(source: reference.referenced_scope, target: reference, name: :referenced_by, inverse_of: :referenced_scope)
57
60
 
58
61
  ::Holistic::Ruby::TypeInference::Solve.call(application:, reference:)
59
62
  end
@@ -14,6 +14,7 @@ module Holistic::Ruby::Parser
14
14
  when ::SyntaxTree::Const then nesting_syntax << node.value
15
15
  when ::SyntaxTree::VCall then append.(node.child_nodes.first)
16
16
  when ::SyntaxTree::Ident then nesting_syntax << node.value
17
+ when ::SyntaxTree::Kw then nesting_syntax << node.value
17
18
  when ::SyntaxTree::IVar then nesting_syntax << node.value
18
19
  when ::SyntaxTree::Period then nesting_syntax << "."
19
20
  when ::SyntaxTree::Paren then append.(node.child_nodes[1]) # node.child_nodes[0] is ::SyntaxTree::LParen
@@ -14,52 +14,63 @@ module Holistic::Ruby::Parser
14
14
 
15
15
  visit_methods do
16
16
  def visit_module(node)
17
- declaration_node, body_node = node.child_nodes
17
+ declaration_node, body_statements_node = node.child_nodes
18
18
 
19
19
  nesting = NestingSyntax.build(declaration_node)
20
- location = build_scope_location(declaration_node:, body_node:)
20
+ location = build_scope_location(declaration_node:, body_node: node)
21
21
 
22
22
  @constant_resolution.register_child_module(nesting:, location:) do
23
- visit(body_node)
23
+ visit(body_statements_node)
24
24
  end
25
25
  end
26
26
 
27
27
  def visit_class(node)
28
- declaration_node, superclass_node, body_node = node.child_nodes
28
+ declaration_node, superclass_node, body_statements_node = node.child_nodes
29
29
 
30
30
  if superclass_node
31
31
  register_reference(nesting: NestingSyntax.build(superclass_node), location: build_location(superclass_node))
32
32
  end
33
33
 
34
34
  nesting = NestingSyntax.build(declaration_node)
35
- location = build_scope_location(declaration_node:, body_node:)
35
+ location = build_scope_location(declaration_node:, body_node: node)
36
36
 
37
- @constant_resolution.register_child_class(nesting:, location:) do
38
- visit(body_node)
37
+ class_scope = @constant_resolution.register_child_class(nesting:, location:) do
38
+ visit(body_statements_node)
39
+ end
40
+
41
+ @application.extensions.dispatch(:class_scope_registered, { class_scope:, location: })
42
+ end
43
+
44
+ def visit_command(node)
45
+ command_name_node, args_node = node.child_nodes
46
+
47
+ if command_name_node.value == "extend"
48
+ is_extending_self = args_node.child_nodes.size == 1 && NestingSyntax.build(args_node.child_nodes.first).to_s == "self"
49
+
50
+ @constant_resolution.change_method_registration_mode_to_class_methods! if is_extending_self
39
51
  end
52
+
53
+ visit(args_node)
40
54
  end
41
55
 
42
56
  def visit_def(node)
43
- instance_node, period_node, method_name_node, _params, body_node = node.child_nodes
57
+ instance_node, period_node, method_name_node, _params, body_statements_node = node.child_nodes
58
+
59
+ nesting = NestingSyntax.new(method_name_node.value)
60
+ location = build_scope_location(declaration_node: method_name_node, body_node: node)
44
61
 
45
- method_name =
46
- if instance_node.present? && period_node.present?
47
- instance_node.child_nodes.first.value + period_node.value + method_name_node.value
62
+ kind =
63
+ if instance_node.present? && instance_node.child_nodes.first.value == "self"
64
+ ::Holistic::Ruby::Scope::Kind::CLASS_METHOD
65
+ elsif @constant_resolution.method_registration_class_methods?
66
+ ::Holistic::Ruby::Scope::Kind::CLASS_METHOD
48
67
  else
49
- method_name_node.value
68
+ ::Holistic::Ruby::Scope::Kind::INSTANCE_METHOD
50
69
  end
51
70
 
52
- location = build_scope_location(declaration_node: method_name_node, body_node:)
53
-
54
- ::Holistic::Ruby::Scope::Register.call(
55
- repository: @application.scopes,
56
- parent: @constant_resolution.scope,
57
- kind: ::Holistic::Ruby::Scope::Kind::METHOD,
58
- name: method_name,
59
- location:
60
- )
61
-
62
- visit(body_node)
71
+ @constant_resolution.register_child_method(nesting:, location:, kind:) do
72
+ visit(body_statements_node)
73
+ end
63
74
  end
64
75
 
65
76
  def visit_vcall(node)
@@ -69,8 +80,9 @@ module Holistic::Ruby::Parser
69
80
  resolution_possibilities: @constant_resolution.current
70
81
  )
71
82
 
72
- ::Holistic::Ruby::Reference::Register.call(
73
- repository: @application.references,
83
+ ::Holistic::Ruby::Reference::Store.call(
84
+ database: @application.database,
85
+ processing_queue: @application.type_inference_processing_queue,
74
86
  scope: @constant_resolution.scope,
75
87
  clues: [method_call_clue],
76
88
  location: build_location(node)
@@ -103,8 +115,9 @@ module Holistic::Ruby::Parser
103
115
  resolution_possibilities: @constant_resolution.current
104
116
  )
105
117
 
106
- ::Holistic::Ruby::Reference::Register.call(
107
- repository: @application.references,
118
+ ::Holistic::Ruby::Reference::Store.call(
119
+ database: @application.database,
120
+ processing_queue: @application.type_inference_processing_queue,
108
121
  scope: @constant_resolution.scope,
109
122
  clues: [method_call_clue],
110
123
  location: build_location(method_name_node || instance_node)
@@ -128,22 +141,27 @@ module Holistic::Ruby::Parser
128
141
  nesting = NestingSyntax.build(assign_node)
129
142
  location = build_scope_location(declaration_node: assign_node, body_node: block_node)
130
143
 
131
- @constant_resolution.register_child_class(nesting:, location:) do
144
+ class_scope = @constant_resolution.register_child_class(nesting:, location:) do
132
145
  visit(block_node)
133
146
  end
134
147
 
148
+ @application.extensions.dispatch(:class_scope_registered, { class_scope:, location: })
149
+
135
150
  return
136
151
  end
137
152
 
138
153
  location = build_scope_location(declaration_node: assign_node, body_node:)
139
154
 
140
- ::Holistic::Ruby::Scope::Register.call(
141
- repository: @application.scopes,
142
- parent: @constant_resolution.scope,
143
- kind: ::Holistic::Ruby::Scope::Kind::LAMBDA,
144
- name: assign_node.child_nodes.first.value,
145
- location:
146
- )
155
+ lambda_scope =
156
+ ::Holistic::Ruby::Scope::Store.call(
157
+ database: @application.database,
158
+ parent: @constant_resolution.scope,
159
+ kind: ::Holistic::Ruby::Scope::Kind::LAMBDA,
160
+ name: assign_node.child_nodes.first.value,
161
+ location:
162
+ )
163
+
164
+ @application.extensions.dispatch(:lambda_scope_registered, { lambda_scope:, location: })
147
165
 
148
166
  visit(body_node)
149
167
  end
@@ -169,8 +187,9 @@ module Holistic::Ruby::Parser
169
187
  resolution_possibilities: @constant_resolution.current
170
188
  )
171
189
 
172
- ::Holistic::Ruby::Reference::Register.call(
173
- repository: @application.references,
190
+ ::Holistic::Ruby::Reference::Store.call(
191
+ database: @application.database,
192
+ processing_queue: @application.type_inference_processing_queue,
174
193
  scope: @constant_resolution.scope,
175
194
  clues: [clue],
176
195
  location:
@@ -193,13 +212,7 @@ module Holistic::Ruby::Parser
193
212
  start_column = node.location.start_column
194
213
  end_column = node.location.end_column
195
214
 
196
- # syntax_tree seems to have a bug with the bodystmt node.
197
- # It sets the end_column lower than the start_column.
198
- if start_line == end_line && start_column > end_column
199
- start_column, end_column = end_column, start_column
200
- end
201
-
202
- ::Holistic::Document::Location.new(file_path: file.path, start_line:, start_column:, end_line:, end_column:)
215
+ ::Holistic::Document::Location.new(file:, start_line:, start_column:, end_line:, end_column:)
203
216
  end
204
217
  end
205
218
  end
@@ -1,13 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Holistic::Ruby::Parser
4
- ParseFile = ->(application:, file:) do
5
- program = ::SyntaxTree.parse(file.read)
4
+ HasValidSyntax = ->(content) do
5
+ ::SyntaxTree.parse(content)
6
6
 
7
- constant_resolution = ConstantResolution.new(
8
- scope_repository: application.scopes,
9
- root_scope: application.root_scope
10
- )
7
+ true
8
+ rescue ::SyntaxTree::Parser::ParseError
9
+ false
10
+ end
11
+
12
+ ParseFile = ->(application:, file_path:, content:) do
13
+ program = ::SyntaxTree.parse(content)
14
+
15
+ constant_resolution = ConstantResolution.new(scope_repository: application.scopes)
16
+
17
+ file = ::Holistic::Document::File::Store.call(database: application.database, file_path:)
11
18
 
12
19
  visitor = ProgramVisitor.new(application:, constant_resolution:, file:)
13
20
 
@@ -18,9 +25,7 @@ module Holistic::Ruby::Parser
18
25
 
19
26
  ParseDirectory = ->(application:, directory_path:) do
20
27
  ::Dir.glob("#{directory_path}/**/*.rb").map do |file_path|
21
- file = ::Holistic::Document::File.new(path: file_path)
22
-
23
- ParseFile[application:, file:]
28
+ ParseFile.call(application:, file_path:, content: ::File.read(file_path))
24
29
  end
25
30
  end
26
31
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::Reference
4
+ module Delete
5
+ extend self
6
+
7
+ def call(database:, reference:)
8
+ database.delete(reference.identifier)
9
+
10
+ database.disconnect(source: reference.location.file, target: reference, name: :defines_references, inverse_of: :reference_defined_in_file)
11
+ database.disconnect(source: reference.located_in_scope, target: reference, name: :contains_many_references, inverse_of: :located_in_scope)
12
+
13
+ if reference.referenced_scope
14
+ database.disconnect(source: reference.referenced_scope, target: reference, name: :referenced_by, inverse_of: :referenced_scope)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -8,9 +8,9 @@ module Holistic::Ruby::Reference
8
8
  reference = application.references.find_by_cursor(cursor)
9
9
 
10
10
  return :not_found if reference.nil?
11
- return :could_not_find_referenced_scope if reference.conclusion.dependency_identifier.nil?
11
+ return :could_not_find_referenced_scope if reference.referenced_scope.nil?
12
12
 
13
- referenced_scope = application.scopes.find_by_fully_qualified_name(reference.conclusion.dependency_identifier)
13
+ referenced_scope = reference.referenced_scope
14
14
 
15
15
  [:referenced_scope_found, {reference:, referenced_scope:}]
16
16
  end
@@ -1,13 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Holistic::Ruby::Reference
4
- Record = ::Struct.new(
5
- :scope,
6
- :clues,
7
- :conclusion,
8
- :location,
9
- keyword_init: true
10
- ) do
11
- def identifier = location.identifier
4
+ class Record < ::Holistic::Database::Node
5
+ def identifier = attr(:identifier)
6
+ def clues = attr(:clues)
7
+ def location = attr(:location)
8
+
9
+ def referenced_scope = has_one(:referenced_scope)
10
+ def located_in_scope = has_one(:located_in_scope)
12
11
  end
13
12
  end
@@ -2,68 +2,46 @@
2
2
 
3
3
  module Holistic::Ruby::Reference
4
4
  class Repository
5
- attr_reader :table
5
+ attr_reader :database
6
6
 
7
- INDICES = [
8
- :file_path,
9
- :type_inference_status,
10
- :referenced_scope_fully_qualified_name
11
- ].freeze
12
-
13
- def initialize
14
- @table = ::Holistic::Database::Table.new(primary_attribute: :identifier, indices: INDICES)
15
- end
16
-
17
- def register_reference(reference)
18
- table.update({
19
- reference:,
20
- identifier: reference.identifier,
21
- file_path: reference.location.file_path,
22
- type_inference_status: reference.conclusion.status,
23
- referenced_scope_fully_qualified_name: reference.conclusion.dependency_identifier
24
- })
7
+ def initialize(database:)
8
+ @database = database
25
9
  end
26
10
 
27
11
  def find_by_cursor(cursor)
28
- table.filter(:file_path, cursor.file_path).map { _1[:reference] }.each do |reference|
29
- return reference if reference.location.contains?(cursor)
12
+ @database.find(cursor.file_path)&.then do |file|
13
+ file.defines_references.find do |reference|
14
+ reference.location.contains?(cursor)
15
+ end
30
16
  end
31
-
32
- nil
33
- end
34
-
35
- def list_references_to(fully_qualified_scope_name)
36
- table.filter(:referenced_scope_fully_qualified_name, fully_qualified_scope_name).map { _1[:reference] }
37
17
  end
38
18
 
39
19
  def list_references_in_file(file_path)
40
- table.filter(:file_path, file_path).map { _1[:reference] }
20
+ @database.find(file_path)&.defines_references || []
41
21
  end
42
22
 
43
23
  def list_references_to_scopes_in_file(scopes:, file_path:)
44
- scopes.list_scopes_in_file(file_path).flat_map do |scope|
45
- table.filter(:referenced_scope_fully_qualified_name, scope.fully_qualified_name).map { _1[:reference] }
24
+ references = @database.find(file_path)&.defines_scopes&.flat_map do |scope|
25
+ scope.referenced_by
46
26
  end
47
- end
48
-
49
- def list_references_pending_type_inference_conclusion
50
- table.filter(:type_inference_status, ::Holistic::Ruby::TypeInference::STATUS_PENDING).map { _1[:reference] }
51
- end
52
27
 
53
- def delete(identifier)
54
- table.delete(identifier)
28
+ references || []
55
29
  end
56
30
 
57
31
  concerning :TestHelpers do
32
+ def all
33
+ @database.all.filter { _1.is_a?(Record) }
34
+ end
35
+
58
36
  def find_reference_to(scope_name)
59
- table.all.map { _1[:reference] }.find do |reference|
60
- reference.conclusion.dependency_identifier == scope_name || reference.clues.find { _1.to_s == scope_name }
37
+ all.find do |node|
38
+ node.referenced_scope&.fully_qualified_name == scope_name || node.clues&.find { _1.to_s == scope_name }
61
39
  end
62
40
  end
63
41
 
64
42
  def find_by_code_content(code_content)
65
- table.all.map { _1[:reference] }.find do |reference|
66
- reference.clues.find { _1.to_s == code_content }
43
+ all.find do |node|
44
+ node.clues&.find { _1.to_s == code_content }
67
45
  end
68
46
  end
69
47
  end