holistic-ruby 0.1.4 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
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