holistic-ruby 0.1.4 → 0.1.7

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/README.md +5 -5
  4. data/exe/holistic-ruby +0 -1
  5. data/lib/holistic/application.rb +12 -4
  6. data/lib/holistic/database/migrations.rb +23 -0
  7. data/lib/holistic/database/node.rb +37 -0
  8. data/lib/holistic/database/relation.rb +21 -0
  9. data/lib/holistic/database.rb +57 -0
  10. data/lib/holistic/document/file/record.rb +10 -0
  11. data/lib/holistic/document/file/repository.rb +24 -0
  12. data/lib/holistic/document/file/store.rb +13 -0
  13. data/lib/holistic/document/location.rb +4 -6
  14. data/lib/holistic/document/unsaved/record.rb +12 -3
  15. data/lib/holistic/extensions/ruby/stdlib.rb +8 -8
  16. data/lib/holistic/language_server/requests/lifecycle/initialize.rb +5 -10
  17. data/lib/holistic/language_server/requests/text_document/completion.rb +16 -5
  18. data/lib/holistic/language_server/requests/text_document/did_close.rb +5 -9
  19. data/lib/holistic/language_server/requests/text_document/did_open.rb +1 -0
  20. data/lib/holistic/language_server/requests/text_document/did_save.rb +5 -9
  21. data/lib/holistic/language_server/requests/text_document/find_references.rb +7 -4
  22. data/lib/holistic/language_server/requests/text_document/go_to_definition.rb +3 -2
  23. data/lib/holistic/language_server/stdio/parser.rb +2 -2
  24. data/lib/holistic/language_server/stdio/start.rb +1 -1
  25. data/lib/holistic/ruby/autocompletion/suggest.rb +45 -25
  26. data/lib/holistic/ruby/parser/constant_resolution.rb +11 -11
  27. data/lib/holistic/ruby/parser/live_editing/process_file_changed.rb +23 -23
  28. data/lib/holistic/ruby/parser/program_visitor.rb +62 -29
  29. data/lib/holistic/ruby/parser.rb +51 -11
  30. data/lib/holistic/ruby/reference/delete.rb +18 -0
  31. data/lib/holistic/ruby/reference/find_referenced_scope.rb +2 -2
  32. data/lib/holistic/ruby/reference/record.rb +15 -8
  33. data/lib/holistic/ruby/reference/repository.rb +19 -41
  34. data/lib/holistic/ruby/reference/store.rb +29 -0
  35. data/lib/holistic/ruby/scope/delete.rb +29 -0
  36. data/lib/holistic/ruby/scope/lexical.rb +11 -0
  37. data/lib/holistic/ruby/scope/list_class_methods.rb +19 -0
  38. data/lib/holistic/ruby/scope/list_instance_methods.rb +19 -0
  39. data/lib/holistic/ruby/scope/list_references.rb +3 -3
  40. data/lib/holistic/ruby/scope/location.rb +9 -9
  41. data/lib/holistic/ruby/scope/outline.rb +10 -10
  42. data/lib/holistic/ruby/scope/record.rb +20 -50
  43. data/lib/holistic/ruby/scope/repository.rb +29 -26
  44. data/lib/holistic/ruby/scope/store.rb +45 -0
  45. data/lib/holistic/ruby/type_inference/clue/method_call.rb +1 -0
  46. data/lib/holistic/ruby/type_inference/clue/reference_to_superclass.rb +9 -0
  47. data/lib/holistic/ruby/type_inference/clue/scope_reference.rb +1 -0
  48. data/lib/holistic/ruby/type_inference/processing_queue.rb +28 -0
  49. data/lib/holistic/ruby/type_inference/resolver/class_method.rb +9 -0
  50. data/lib/holistic/ruby/type_inference/resolver/instance_method.rb +9 -0
  51. data/lib/holistic/ruby/type_inference/resolver/scope.rb +24 -0
  52. data/lib/holistic/ruby/type_inference/solve.rb +25 -69
  53. data/lib/holistic/ruby/type_inference/solve_pending_references.rb +3 -1
  54. data/lib/holistic/version.rb +1 -1
  55. metadata +21 -10
  56. data/lib/holistic/database/table.rb +0 -78
  57. data/lib/holistic/document/file.rb +0 -36
  58. data/lib/holistic/ruby/parser/table_of_contents.rb +0 -17
  59. data/lib/holistic/ruby/reference/register.rb +0 -15
  60. data/lib/holistic/ruby/reference/unregister.rb +0 -11
  61. data/lib/holistic/ruby/scope/register.rb +0 -31
  62. data/lib/holistic/ruby/scope/unregister.rb +0 -27
  63. data/lib/holistic/ruby/type_inference/conclusion.rb +0 -20
@@ -6,14 +6,22 @@ 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
 
12
18
  if code.start_with?("::")
13
- lookup_scope = lookup_scope.parent until lookup_scope.root?
19
+ lookup_scope = lookup_scope.lexical_parent until lookup_scope.root?
14
20
  end
15
21
 
16
- if code.include?(".")
22
+ if StartsWithLowerCaseLetter[code]
23
+ suggest_local_methods_from_current_scope(code:, scope: lookup_scope)
24
+ elsif code.include?(".")
17
25
  suggest_methods_from_scope(code:, scope: lookup_scope)
18
26
  else
19
27
  suggest_namespaces_from_scope(code:, scope: lookup_scope)
@@ -22,12 +30,32 @@ module Holistic::Ruby::Autocompletion
22
30
 
23
31
  private
24
32
 
25
- NonMethods = ->(scope) { !Methods[scope] }
26
- Methods = ->(scope) { scope.instance_method? || scope.class_method? }
33
+ def suggest_local_methods_from_current_scope(code:, scope:)
34
+ suggestions = []
35
+
36
+ sibling_methods =
37
+ case scope.kind
38
+ when ::Holistic::Ruby::Scope::Kind::CLASS_METHOD
39
+ ::Holistic::Ruby::Scope::ListClassMethods.call(scope: scope.lexical_parent)
40
+ when ::Holistic::Ruby::Scope::Kind::INSTANCE_METHOD
41
+ ::Holistic::Ruby::Scope::ListInstanceMethods.call(scope: scope.lexical_parent)
42
+ when ::Holistic::Ruby::Scope::Kind::ROOT
43
+ # TODO: global functions?
44
+
45
+ return []
46
+ else
47
+ raise "unexpected scope kind: #{scope.kind}"
48
+ end
49
+
50
+ sibling_methods.each do |method_scope|
51
+ if method_scope.name.start_with?(code)
52
+ suggestions << Suggestion.new(code: method_scope.name, kind: method_scope.kind)
53
+ end
54
+ end
55
+
56
+ suggestions
57
+ end
27
58
 
28
- # Payment. <--
29
- # Payment::Notifications. <--
30
- # current_user.a
31
59
  def suggest_methods_from_scope(code:, scope:)
32
60
  suggestions = []
33
61
 
@@ -41,7 +69,9 @@ module Holistic::Ruby::Autocompletion
41
69
  return suggestions if scope.nil?
42
70
  end
43
71
 
44
- scope.children.filter(&:class_method?).each do |method_scope|
72
+ class_methods = ::Holistic::Ruby::Scope::ListClassMethods.call(scope:)
73
+
74
+ class_methods.each do |method_scope|
45
75
  if method_scope.name.start_with?(method_to_autocomplete)
46
76
  suggestions << Suggestion.new(code: method_scope.name, kind: method_scope.kind)
47
77
  end
@@ -63,28 +93,18 @@ module Holistic::Ruby::Autocompletion
63
93
  return suggestions if scope.nil?
64
94
  end
65
95
 
66
- # special case when user did not type :: at the end but the current word
67
- # is matches an existing namespace. In this case, suggestions will start with ::.
68
- # For example:
69
- #
70
- # \/ cursor here
71
- # typing: "::MyApp::Payments"
72
- # suggestions: ["::Record", "::SendReminder"]
73
- resolve_scope(name: namespace_to_autocomplete, from_scope: scope)&.then do |fully_typed_scope|
74
- scope = fully_typed_scope
75
- namespace_to_autocomplete = ""
76
- end
77
-
78
96
  should_search_upwards = namespaces_to_resolve.empty?
79
97
 
80
98
  search = ->(scope) do
81
- scope.children.filter(&NonMethods).each do |child_scope|
99
+ scope.lexical_children.each do |child_scope|
100
+ next if child_scope.method?
101
+
82
102
  if child_scope.name.start_with?(namespace_to_autocomplete)
83
103
  suggestions << Suggestion.new(code: child_scope.name, kind: child_scope.kind)
84
104
  end
85
105
  end
86
106
 
87
- search.(scope.parent) if scope.parent.present? && should_search_upwards
107
+ search.(scope.lexical_parent) if scope.lexical_parent.present? && should_search_upwards
88
108
  end
89
109
 
90
110
  search.(scope)
@@ -93,10 +113,10 @@ module Holistic::Ruby::Autocompletion
93
113
  end
94
114
 
95
115
  def resolve_scope(name:, from_scope:)
96
- resolved_scope = from_scope.children.find { |scope| scope.name == name }
116
+ resolved_scope = from_scope.lexical_children.find { |scope| scope.name == name }
97
117
 
98
- if resolved_scope.nil? && from_scope.parent.present?
99
- resolved_scope = resolve_scope(name:, from_scope: from_scope.parent)
118
+ if resolved_scope.nil? && from_scope.lexical_parent.present?
119
+ resolved_scope = resolve_scope(name:, from_scope: from_scope.lexical_parent)
100
120
  end
101
121
 
102
122
  resolved_scope
@@ -9,9 +9,9 @@ module Holistic::Ruby::Parser
9
9
 
10
10
  attr_reader :scope_repository, :scope, :method_registration_mode
11
11
 
12
- def initialize(scope_repository:, root_scope:)
12
+ def initialize(scope_repository:)
13
13
  @scope_repository = scope_repository
14
- @scope = root_scope
14
+ @scope = scope_repository.root
15
15
  @constant_resolution_possibilities = ["::"]
16
16
  @method_registration_mode = MethodRegistrationMode::INSTANCE_METHODS
17
17
  end
@@ -25,9 +25,9 @@ module Holistic::Ruby::Parser
25
25
 
26
26
  nesting.each do |name|
27
27
  @scope =
28
- ::Holistic::Ruby::Scope::Register.call(
29
- repository: @scope_repository,
30
- parent: @scope,
28
+ ::Holistic::Ruby::Scope::Store.call(
29
+ database: @scope_repository.database,
30
+ lexical_parent: @scope,
31
31
  kind: ::Holistic::Ruby::Scope::Kind::MODULE,
32
32
  name:,
33
33
  location:
@@ -52,9 +52,9 @@ module Holistic::Ruby::Parser
52
52
 
53
53
  nesting.each do |name|
54
54
  @scope =
55
- ::Holistic::Ruby::Scope::Register.call(
56
- repository: @scope_repository,
57
- parent: @scope,
55
+ ::Holistic::Ruby::Scope::Store.call(
56
+ database: @scope_repository.database,
57
+ lexical_parent: @scope,
58
58
  kind: ::Holistic::Ruby::Scope::Kind::CLASS,
59
59
  name:,
60
60
  location:
@@ -79,9 +79,9 @@ module Holistic::Ruby::Parser
79
79
 
80
80
  nesting.each do |name|
81
81
  @scope =
82
- ::Holistic::Ruby::Scope::Register.call(
83
- repository: @scope_repository,
84
- parent: @scope,
82
+ ::Holistic::Ruby::Scope::Store.call(
83
+ database: @scope_repository.database,
84
+ lexical_parent: @scope,
85
85
  kind:,
86
86
  name:,
87
87
  location:
@@ -4,59 +4,59 @@ module Holistic::Ruby::Parser
4
4
  module LiveEditing::ProcessFileChanged
5
5
  extend self
6
6
 
7
- def call(application:, file:)
7
+ def call(application:, file_path:, content:)
8
8
  # TODO: do not build the AST twice
9
- return unless HasValidSyntax[file:]
9
+ return unless HasValidSyntax[content]
10
10
 
11
- references_to_recalculate = identify_references_to_recalculate(application:, file:)
11
+ references_to_recalculate = identify_references_to_recalculate(application:, file_path:)
12
12
 
13
- unregister_scopes_in_file(application:, file:)
14
- unregsiter_references_in_file(application:, file:)
13
+ delete_scopes_in_file(application:, file_path:)
14
+ delete_references_in_file(application:, file_path:)
15
15
 
16
- parse_again(application:, file:)
16
+ parse_again(application:, file_path:, content:)
17
17
 
18
- recalculate_type_inference_for_references(application:, references: references_to_recalculate)
18
+ recalculate_type_inference(application:, references: references_to_recalculate)
19
19
  end
20
20
 
21
21
  private
22
22
 
23
- def identify_references_to_recalculate(application:, file:)
23
+ def identify_references_to_recalculate(application:, file_path:)
24
24
  # we need to reject references declared in the same because they're already going to be
25
25
  # reparsed. If we don't do that, we'll end up with duplicated reference records.
26
26
 
27
27
  application.references
28
- .list_references_to_scopes_in_file(scopes: application.scopes, file_path: file.path)
29
- .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 }
30
30
  end
31
31
 
32
- def unregister_scopes_in_file(application:, file:)
33
- application.scopes.list_scopes_in_file(file.path).each do |scope|
34
- ::Holistic::Ruby::Scope::Unregister.call(
35
- 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,
36
36
  fully_qualified_name: scope.fully_qualified_name,
37
- file_path: file.path
37
+ file_path:
38
38
  )
39
39
  end
40
40
  end
41
41
 
42
- def unregsiter_references_in_file(application:, file:)
43
- application.references.list_references_in_file(file.path).each do |reference|
44
- ::Holistic::Ruby::Reference::Unregister.call(
45
- 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,
46
46
  reference: reference
47
47
  )
48
48
  end
49
49
  end
50
50
 
51
- def parse_again(application:, file:)
52
- ParseFile.call(application:, file:)
51
+ def parse_again(application:, file_path:, content:)
52
+ ParseFile.call(application:, file_path:, content:)
53
53
 
54
54
  ::Holistic::Ruby::TypeInference::SolvePendingReferences.call(application:)
55
55
  end
56
56
 
57
- def recalculate_type_inference_for_references(application:, references:)
57
+ def recalculate_type_inference(application:, references:)
58
58
  references.each do |reference|
59
- reference.conclusion = ::Holistic::Ruby::TypeInference::Conclusion.pending
59
+ reference.relation(:referenced_scope).delete!(reference.referenced_scope)
60
60
 
61
61
  ::Holistic::Ruby::TypeInference::Solve.call(application:, reference:)
62
62
  end
@@ -14,28 +14,43 @@ 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
29
-
30
- if superclass_node
31
- register_reference(nesting: NestingSyntax.build(superclass_node), location: build_location(superclass_node))
32
- end
28
+ declaration_node, superclass_node, body_statements_node = node.child_nodes
33
29
 
34
30
  nesting = NestingSyntax.build(declaration_node)
35
- location = build_scope_location(declaration_node:, body_node:)
31
+ location = build_scope_location(declaration_node:, body_node: node)
36
32
 
37
33
  class_scope = @constant_resolution.register_child_class(nesting:, location:) do
38
- visit(body_node)
34
+ visit(body_statements_node)
35
+ end
36
+
37
+ if superclass_node
38
+ reference_to_scope_clue = ::Holistic::Ruby::TypeInference::Clue::ScopeReference.new(
39
+ nesting: NestingSyntax.build(superclass_node),
40
+ resolution_possibilities: @constant_resolution.current
41
+ )
42
+
43
+ reference_to_superclass_clue = ::Holistic::Ruby::TypeInference::Clue::ReferenceToSuperclass.new(
44
+ subclass_scope: class_scope
45
+ )
46
+
47
+ ::Holistic::Ruby::Reference::Store.call(
48
+ database: @application.database,
49
+ processing_queue: @application.type_inference_processing_queue,
50
+ scope: @constant_resolution.scope,
51
+ clues: [reference_to_scope_clue, reference_to_superclass_clue],
52
+ location: build_location(superclass_node)
53
+ )
39
54
  end
40
55
 
41
56
  @application.extensions.dispatch(:class_scope_registered, { class_scope:, location: })
@@ -50,14 +65,35 @@ module Holistic::Ruby::Parser
50
65
  @constant_resolution.change_method_registration_mode_to_class_methods! if is_extending_self
51
66
  end
52
67
 
68
+ if command_name_node.value == "include"
69
+ superclass_nesting_syntax = NestingSyntax.build(args_node.child_nodes.first)
70
+
71
+ reference_to_scope_clue = ::Holistic::Ruby::TypeInference::Clue::ScopeReference.new(
72
+ nesting: superclass_nesting_syntax,
73
+ resolution_possibilities: @constant_resolution.current
74
+ )
75
+
76
+ reference_to_superclass_clue = ::Holistic::Ruby::TypeInference::Clue::ReferenceToSuperclass.new(
77
+ subclass_scope: @constant_resolution.scope
78
+ )
79
+
80
+ ::Holistic::Ruby::Reference::Store.call(
81
+ database: @application.database,
82
+ processing_queue: @application.type_inference_processing_queue,
83
+ scope: @constant_resolution.scope,
84
+ clues: [reference_to_scope_clue, reference_to_superclass_clue],
85
+ location: build_location(node)
86
+ )
87
+ end
88
+
53
89
  visit(args_node)
54
90
  end
55
91
 
56
92
  def visit_def(node)
57
- instance_node, period_node, method_name_node, _params, body_node = node.child_nodes
93
+ instance_node, period_node, method_name_node, _params, body_statements_node = node.child_nodes
58
94
 
59
95
  nesting = NestingSyntax.new(method_name_node.value)
60
- location = build_scope_location(declaration_node: method_name_node, body_node:)
96
+ location = build_scope_location(declaration_node: method_name_node, body_node: node)
61
97
 
62
98
  kind =
63
99
  if instance_node.present? && instance_node.child_nodes.first.value == "self"
@@ -69,7 +105,7 @@ module Holistic::Ruby::Parser
69
105
  end
70
106
 
71
107
  @constant_resolution.register_child_method(nesting:, location:, kind:) do
72
- visit(body_node)
108
+ visit(body_statements_node)
73
109
  end
74
110
  end
75
111
 
@@ -80,8 +116,9 @@ module Holistic::Ruby::Parser
80
116
  resolution_possibilities: @constant_resolution.current
81
117
  )
82
118
 
83
- ::Holistic::Ruby::Reference::Register.call(
84
- repository: @application.references,
119
+ ::Holistic::Ruby::Reference::Store.call(
120
+ database: @application.database,
121
+ processing_queue: @application.type_inference_processing_queue,
85
122
  scope: @constant_resolution.scope,
86
123
  clues: [method_call_clue],
87
124
  location: build_location(node)
@@ -114,8 +151,9 @@ module Holistic::Ruby::Parser
114
151
  resolution_possibilities: @constant_resolution.current
115
152
  )
116
153
 
117
- ::Holistic::Ruby::Reference::Register.call(
118
- repository: @application.references,
154
+ ::Holistic::Ruby::Reference::Store.call(
155
+ database: @application.database,
156
+ processing_queue: @application.type_inference_processing_queue,
119
157
  scope: @constant_resolution.scope,
120
158
  clues: [method_call_clue],
121
159
  location: build_location(method_name_node || instance_node)
@@ -151,9 +189,9 @@ module Holistic::Ruby::Parser
151
189
  location = build_scope_location(declaration_node: assign_node, body_node:)
152
190
 
153
191
  lambda_scope =
154
- ::Holistic::Ruby::Scope::Register.call(
155
- repository: @application.scopes,
156
- parent: @constant_resolution.scope,
192
+ ::Holistic::Ruby::Scope::Store.call(
193
+ database: @application.database,
194
+ lexical_parent: @constant_resolution.scope,
157
195
  kind: ::Holistic::Ruby::Scope::Kind::LAMBDA,
158
196
  name: assign_node.child_nodes.first.value,
159
197
  location:
@@ -185,8 +223,9 @@ module Holistic::Ruby::Parser
185
223
  resolution_possibilities: @constant_resolution.current
186
224
  )
187
225
 
188
- ::Holistic::Ruby::Reference::Register.call(
189
- repository: @application.references,
226
+ ::Holistic::Ruby::Reference::Store.call(
227
+ database: @application.database,
228
+ processing_queue: @application.type_inference_processing_queue,
190
229
  scope: @constant_resolution.scope,
191
230
  clues: [clue],
192
231
  location:
@@ -209,13 +248,7 @@ module Holistic::Ruby::Parser
209
248
  start_column = node.location.start_column
210
249
  end_column = node.location.end_column
211
250
 
212
- # syntax_tree seems to have a bug with the bodystmt node.
213
- # It sets the end_column lower than the start_column.
214
- if start_line == end_line && start_column > end_column
215
- start_column, end_column = end_column, start_column
216
- end
217
-
218
- ::Holistic::Document::Location.new(file_path: file.path, start_line:, start_column:, end_line:, end_column:)
251
+ ::Holistic::Document::Location.new(file:, start_line:, start_column:, end_line:, end_column:)
219
252
  end
220
253
  end
221
254
  end
@@ -1,34 +1,74 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Holistic::Ruby::Parser
4
- HasValidSyntax = ->(file:) do
5
- ::SyntaxTree.parse(file.read)
4
+ HasValidSyntax = ->(content) do
5
+ ::SyntaxTree.parse(content)
6
6
 
7
7
  true
8
8
  rescue ::SyntaxTree::Parser::ParseError
9
9
  false
10
10
  end
11
11
 
12
- ParseFile = ->(application:, file:) do
13
- program = ::SyntaxTree.parse(file.read)
12
+ ParseFile = ->(application:, file_path:, content:) do
13
+ program = ::SyntaxTree.parse(content)
14
14
 
15
- constant_resolution = ConstantResolution.new(
16
- scope_repository: application.scopes,
17
- root_scope: application.root_scope
18
- )
15
+ constant_resolution = ConstantResolution.new(scope_repository: application.scopes)
16
+
17
+ file = ::Holistic::Document::File::Store.call(database: application.database, file_path:)
19
18
 
20
19
  visitor = ProgramVisitor.new(application:, constant_resolution:, file:)
21
20
 
22
21
  visitor.visit(program)
23
22
  rescue ::SyntaxTree::Parser::ParseError
24
- ::Holistic.logger.info("syntax error on file #{file.path}")
23
+ ::Holistic.logger.info("syntax error on file #{file_path}")
24
+ end
25
+
26
+ class PerformanceMetrics
27
+ def initialize
28
+ @amount_of_files = 0
29
+ @total_read_time = 0
30
+ @total_parse_time = 0
31
+ end
32
+
33
+ def start_file_read!
34
+ @amount_of_files += 1
35
+ @file_read_before = ::Process.clock_gettime(Process::CLOCK_MONOTONIC)
36
+ end
37
+
38
+ def end_file_read!
39
+ @total_read_time += ::Process.clock_gettime(Process::CLOCK_MONOTONIC) - @file_read_before
40
+ end
41
+
42
+ def start_parse!
43
+ @parse_before = ::Process.clock_gettime(Process::CLOCK_MONOTONIC)
44
+ end
45
+
46
+ def end_parse!
47
+ @total_parse_time += ::Process.clock_gettime(Process::CLOCK_MONOTONIC) - @parse_before
48
+ end
49
+
50
+ def inspect
51
+ {
52
+ amount_of_files: @amount_of_files,
53
+ total_read_time: @total_read_time,
54
+ total_parse_time: @total_parse_time
55
+ }.inspect
56
+ end
25
57
  end
26
58
 
27
59
  ParseDirectory = ->(application:, directory_path:) do
60
+ performance_metrics = PerformanceMetrics.new
61
+
28
62
  ::Dir.glob("#{directory_path}/**/*.rb").map do |file_path|
29
- file = ::Holistic::Document::File.new(path: file_path)
63
+ performance_metrics.start_file_read!
64
+ content = ::File.read(file_path)
65
+ performance_metrics.end_file_read!
30
66
 
31
- ParseFile[application:, file:]
67
+ performance_metrics.start_parse!
68
+ ParseFile.call(application:, file_path:, content:)
69
+ performance_metrics.end_parse!
32
70
  end
71
+
72
+ performance_metrics
33
73
  end
34
74
  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
+ reference.relation(:reference_defined_in_file).delete!(reference.location.file)
11
+ reference.relation(:located_in_scope).delete!(reference.located_in_scope)
12
+
13
+ if reference.referenced_scope
14
+ reference.relation(:referenced_scope).delete!(reference.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,20 @@
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)
11
+
12
+ def find_clue(clue_kind)
13
+ clues.find { |clue| clue.is_a?(clue_kind) }
14
+ end
15
+
16
+ def inspect
17
+ "<#{self.class.name} clues=[#{clues}] referenced_scope=#{referenced_scope&.fully_qualified_name}>"
18
+ end
12
19
  end
13
20
  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.to_a
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