holistic-ruby 0.1.6 → 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 (43) 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 +1 -1
  6. data/lib/holistic/database/migrations.rb +9 -6
  7. data/lib/holistic/database/node.rb +33 -25
  8. data/lib/holistic/database/relation.rb +21 -0
  9. data/lib/holistic/database.rb +57 -0
  10. data/lib/holistic/document/unsaved/record.rb +13 -0
  11. data/lib/holistic/extensions/ruby/stdlib.rb +3 -3
  12. data/lib/holistic/language_server/requests/text_document/completion.rb +13 -3
  13. data/lib/holistic/language_server/stdio/parser.rb +2 -2
  14. data/lib/holistic/language_server/stdio/start.rb +1 -1
  15. data/lib/holistic/ruby/autocompletion/suggest.rb +22 -22
  16. data/lib/holistic/ruby/parser/constant_resolution.rb +3 -3
  17. data/lib/holistic/ruby/parser/live_editing/process_file_changed.rb +3 -3
  18. data/lib/holistic/ruby/parser/program_visitor.rb +41 -5
  19. data/lib/holistic/ruby/parser.rb +45 -2
  20. data/lib/holistic/ruby/reference/delete.rb +3 -3
  21. data/lib/holistic/ruby/reference/record.rb +8 -0
  22. data/lib/holistic/ruby/reference/repository.rb +1 -1
  23. data/lib/holistic/ruby/reference/store.rb +14 -3
  24. data/lib/holistic/ruby/scope/delete.rb +2 -2
  25. data/lib/holistic/ruby/scope/lexical.rb +1 -1
  26. data/lib/holistic/ruby/scope/list_class_methods.rb +19 -0
  27. data/lib/holistic/ruby/scope/list_instance_methods.rb +19 -0
  28. data/lib/holistic/ruby/scope/list_references.rb +2 -2
  29. data/lib/holistic/ruby/scope/outline.rb +2 -2
  30. data/lib/holistic/ruby/scope/record.rb +9 -3
  31. data/lib/holistic/ruby/scope/repository.rb +7 -3
  32. data/lib/holistic/ruby/scope/store.rb +12 -12
  33. data/lib/holistic/ruby/type_inference/clue/method_call.rb +1 -0
  34. data/lib/holistic/ruby/type_inference/clue/reference_to_superclass.rb +9 -0
  35. data/lib/holistic/ruby/type_inference/clue/scope_reference.rb +1 -0
  36. data/lib/holistic/ruby/type_inference/processing_queue.rb +21 -12
  37. data/lib/holistic/ruby/type_inference/resolver/class_method.rb +9 -0
  38. data/lib/holistic/ruby/type_inference/resolver/instance_method.rb +9 -0
  39. data/lib/holistic/ruby/type_inference/resolver/scope.rb +24 -0
  40. data/lib/holistic/ruby/type_inference/solve.rb +18 -53
  41. data/lib/holistic/version.rb +1 -1
  42. metadata +10 -3
  43. data/lib/holistic/database/table.rb +0 -78
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43d41e307a4ae8b8b904fda778342ef5d175db25580697f597f606f3aa2a0173
4
- data.tar.gz: 5286406cf4e0f5a5cb21745b3f7d49268eb934e4002e76d1ba43f9dfdda034fd
3
+ metadata.gz: b5eda7bf8e9da75eb3716452dfc2ebafdbaf35af0a53aef452c64bd95cd00bd8
4
+ data.tar.gz: 5522698edac5824011fd5aeee3ba4c71eb5cfee51b956c3d5942e329c7644072
5
5
  SHA512:
6
- metadata.gz: 882cac6ec7c32d309898aab43f001a3fcf0e497b6c019569472db3880b792e95fe50158b8672690c80237e0a9c6ac361ede5b79a09e2784ddc944d103c54eaba
7
- data.tar.gz: 64e1becad926aba30e7c9f25b294a2ff9b53dcdfe7affc5e59792054cf21b17b3509eb7fc80e1819fecc9f7d7e22957df3bda2c03990b9e77476c3bc1fa956e0
6
+ metadata.gz: 06caa82194be9d2d2d9832aefd9132ab3d3cc1838be8b2cff9cd64f3fbafa36a9f0f79c40384eb4439adfd224a559605a9e328ae7dfb722235870b535f0d155f
7
+ data.tar.gz: a542c510078f1f528b4621000ba87843d0544e23dea77123bcf627e79d17f8dcdcb987be4122bd0f39c7f96533fc57d1a26e489e0cc25b6f5e0e5dfc68c436d3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- holistic-ruby (0.1.4)
4
+ holistic-ruby (0.1.7)
5
5
  activesupport (~> 7.0)
6
6
  syntax_tree (~> 6.0)
7
7
  zeitwerk (~> 2.6)
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  ## Installation for Sublime Text
6
6
 
7
7
  1. Make sure you have the [LSP package installed](https://github.com/sublimelsp/LSP).
8
- 2. Install the gem `$ gem install holistic-ruby`
8
+ 2. Install the gem with `$ gem install holistic-ruby`
9
9
  3. Go to `Preferences > Package Settings > LSP > Settings` and add:
10
10
 
11
11
  ```json
@@ -25,10 +25,10 @@
25
25
 
26
26
  * Go to definition.
27
27
  * Find references.
28
- * Autocompletion for namespaces, methods and local variables.
29
- * Syntax highlighting boundaries based on packwerk.
30
- * Outline dependencies.
31
- * Glossary.
28
+ * Autocompletion for namespaces and methods.
29
+ * (WIP) Outline dependencies.
30
+ * (WIP) Syntax highlighting boundaries based on packwerk.
31
+ * (WIP) Glossary.
32
32
 
33
33
  ## Why is it a toy language server?
34
34
 
data/exe/holistic-ruby CHANGED
@@ -3,4 +3,3 @@
3
3
  require "holistic"
4
4
 
5
5
  ::Holistic::LanguageServer::Stdio::Start.call
6
-
@@ -7,7 +7,7 @@ module Holistic
7
7
  def initialize(name:, root_directory:)
8
8
  @name = name
9
9
  @root_directory = root_directory
10
- @database = Database::Table.new.tap(&Database::Migrations::Run)
10
+ @database = Database.new.tap(&Database::Migrations::Run)
11
11
  end
12
12
 
13
13
  def extensions
@@ -2,19 +2,22 @@
2
2
 
3
3
  module Holistic::Database::Migrations
4
4
  Run = ->(database) do
5
- # scope parent-children relation
6
- database.define_connection(name: :children, inverse_of: :parent)
5
+ # scope lexical parent-children relation
6
+ database.define_relation(name: :lexical_children, inverse_of: :lexical_parent)
7
+
8
+ # scope inheritance and mixins
9
+ database.define_relation(name: :ancestors, inverse_of: :descendants)
7
10
 
8
11
  # type inference conclusion
9
- database.define_connection(name: :referenced_scope, inverse_of: :referenced_by)
12
+ database.define_relation(name: :referenced_scope, inverse_of: :referenced_by)
10
13
 
11
14
  # reference definition
12
- database.define_connection(name: :located_in_scope, inverse_of: :contains_many_references)
15
+ database.define_relation(name: :located_in_scope, inverse_of: :contains_many_references)
13
16
 
14
17
  # scope location in files
15
- database.define_connection(name: :defines_scopes, inverse_of: :scope_defined_in_file)
18
+ database.define_relation(name: :defines_scopes, inverse_of: :scope_defined_in_file)
16
19
 
17
20
  # reference location in files
18
- database.define_connection(name: :defines_references, inverse_of: :reference_defined_in_file)
21
+ database.define_relation(name: :defines_references, inverse_of: :reference_defined_in_file)
19
22
  end
20
23
  end
@@ -1,29 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Holistic::Database
4
- class Node
5
- attr_accessor :attributes, :connections, :__database__
6
-
7
- def initialize(id, attributes)
8
- @id = id
9
- @attributes = attributes
10
- @connections = ::Hash.new { |hash, key| hash[key] = ::Set.new }
11
- end
12
-
13
- def attr(attribute_name)
14
- @attributes[attribute_name]
15
- end
16
-
17
- def has_many(connection_name)
18
- @connections[connection_name].to_a
19
- end
20
-
21
- def has_one(connection_name)
22
- @connections[connection_name].first
23
- end
24
-
25
- def __set_database__(database)
26
- @__database__ = database
27
- end
3
+ class Holistic::Database::Node
4
+ attr_accessor :attributes, :relations, :__database__
5
+
6
+ def initialize(id, attributes)
7
+ @id = id
8
+ @attributes = attributes
9
+ @relations = ::Hash.new(&method(:build_relation_hash))
10
+ end
11
+
12
+ def attr(attribute_name)
13
+ @attributes[attribute_name]
14
+ end
15
+
16
+ def relation(relation_name)
17
+ @relations[relation_name]
18
+ end
19
+
20
+ def has_many(connection_name)
21
+ @relations[connection_name].to_a
22
+ end
23
+
24
+ def has_one(connection_name)
25
+ @relations[connection_name].first
26
+ end
27
+
28
+ private
29
+
30
+ def build_relation_hash(hash, name)
31
+ inverse_of = __database__.relations.dig(name, :inverse_of)
32
+
33
+ raise ::ArgumentError, "unknown relation: #{name}" if inverse_of.nil?
34
+
35
+ hash[name] = ::Holistic::Database::Relation.new(node: self, name:, inverse_of:)
28
36
  end
29
37
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Holistic::Database::Relation < ::Set
4
+ def initialize(node:, name:, inverse_of:)
5
+ @node = node
6
+ @name = name
7
+ @inverse_of = inverse_of
8
+
9
+ super()
10
+ end
11
+
12
+ def add!(related_node)
13
+ @node.relations[@name].add(related_node)
14
+ related_node.relations[@inverse_of].add(@node)
15
+ end
16
+
17
+ def delete!(related_node)
18
+ @node.relations[@name].delete(related_node)
19
+ related_node.relations[@inverse_of].delete(@node)
20
+ end
21
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Holistic::Database
4
+ attr_reader :records, :relations
5
+
6
+ def initialize
7
+ @records = ::Hash.new
8
+ @relations = ::Hash.new
9
+ end
10
+
11
+ def define_relation(name:, inverse_of:)
12
+ raise ::ArgumentError if @relations.key?(name) || @relations.key?(inverse_of)
13
+
14
+ @relations[name] = { inverse_of: }
15
+ @relations[inverse_of] = { inverse_of: name }
16
+ end
17
+
18
+ def store(id, node_or_attrs)
19
+ if @records.key?(id)
20
+ return @records[id]&.tap do |node|
21
+ node.attributes =
22
+ case node_or_attrs
23
+ in ::Hash then node_or_attrs
24
+ in Node then node_or_attrs.attributes
25
+ end
26
+ end
27
+ end
28
+
29
+ node =
30
+ case node_or_attrs
31
+ in ::Hash then Node.new(id, node_or_attrs)
32
+ in Node then node_or_attrs
33
+ end
34
+
35
+ node.__database__ = self
36
+
37
+ @records[id] = node
38
+ end
39
+
40
+ def find(id)
41
+ @records[id]
42
+ end
43
+
44
+ def delete(id)
45
+ records.delete(id)
46
+ end
47
+
48
+ concerning :TestHelpers do
49
+ def all
50
+ records.values
51
+ end
52
+
53
+ def size
54
+ records.size
55
+ end
56
+ end
57
+ end
@@ -54,6 +54,14 @@ module Holistic::Document
54
54
  line = 0
55
55
  column = 0
56
56
 
57
+ # first edition to the document is special because we can't iterate over the content to find the insert position.
58
+ # there is nothing to iterate over.
59
+ if @content.empty? && change.insertion?
60
+ @content = change.text
61
+
62
+ return
63
+ end
64
+
57
65
  @content.each_char.with_index do |char, index|
58
66
  if change.insertion? && change.starts_on?(line, column)
59
67
  content.insert(index, change.text)
@@ -74,6 +82,11 @@ module Holistic::Document
74
82
  column += 1
75
83
  end
76
84
  end
85
+
86
+ # off-by-one error to insert at the of the document
87
+ if change.insertion? && change.starts_on?(line, column)
88
+ content.insert(@content.length, change.text)
89
+ end
77
90
  end
78
91
  end
79
92
  end
@@ -19,12 +19,12 @@ module Holistic::Extensions::Ruby
19
19
  RegisterClassConstructor = ->(application, params) do
20
20
  class_scope, location = params[:class_scope], params[:location]
21
21
 
22
- has_overridden_new_method = class_scope.children.find { _1.instance_method? && _1.name == "initialize" }
22
+ has_overridden_new_method = class_scope.lexical_children.find { _1.instance_method? && _1.name == "initialize" }
23
23
 
24
24
  unless has_overridden_new_method
25
25
  ::Holistic::Ruby::Scope::Store.call(
26
26
  database: application.database,
27
- parent: class_scope,
27
+ lexical_parent: class_scope,
28
28
  kind: ::Holistic::Ruby::Scope::Kind::CLASS_METHOD,
29
29
  name: "new",
30
30
  location:
@@ -40,7 +40,7 @@ module Holistic::Extensions::Ruby
40
40
  LAMBDA_METHODS.each do |method_name|
41
41
  ::Holistic::Ruby::Scope::Store.call(
42
42
  database: application.database,
43
- parent: lambda_scope,
43
+ lexical_parent: lambda_scope,
44
44
  kind: ::Holistic::Ruby::Scope::Kind::CLASS_METHOD,
45
45
  name: method_name,
46
46
  location:
@@ -8,8 +8,12 @@ module Holistic::LanguageServer
8
8
  cursor = build_cursor_from_request_params(request)
9
9
 
10
10
  document = request.application.unsaved_documents.find(cursor.file_path)
11
-
12
- return request.respond_with(nil) if document.nil?
11
+
12
+ if document.nil?
13
+ ::Holistic.logger.info("aborting completion because document was not found for #{cursor.file_path}")
14
+
15
+ return request.respond_with(nil)
16
+ end
13
17
 
14
18
  if document.has_unsaved_changes?
15
19
  ::Holistic::Ruby::Parser::LiveEditing::ProcessFileChanged.call(
@@ -21,10 +25,16 @@ module Holistic::LanguageServer
21
25
 
22
26
  code = document.expand_code(cursor)
23
27
 
24
- return request.respond_with(nil) if code.blank?
28
+ if code.blank?
29
+ ::Holistic.logger.info("aborting completion because code under cursor was blank: #{cursor.inspect}")
30
+
31
+ return request.respond_with(nil)
32
+ end
25
33
 
26
34
  scope = request.application.scopes.find_inner_most_scope_by_cursor(cursor) || request.application.scopes.root
27
35
 
36
+ ::Holistic.logger.info("scope under cursor is: #{scope.fully_qualified_name}")
37
+
28
38
  suggestions = ::Holistic::Ruby::Autocompletion::Suggest.call(code:, scope:)
29
39
 
30
40
  respond_with_suggestions(request, suggestions)
@@ -11,7 +11,7 @@ module Holistic::LanguageServer
11
11
 
12
12
  def ingest(payload)
13
13
  payload.each_char do |char|
14
- if @in_header || !completed?
14
+ if @in_header || !has_complete_message?
15
15
  @buffer.concat(char)
16
16
  else
17
17
  @overflow_from_previous_ingestion.concat(char)
@@ -23,7 +23,7 @@ module Holistic::LanguageServer
23
23
  end
24
24
  end
25
25
 
26
- def completed?
26
+ def has_complete_message?
27
27
  !@in_header && @content_length == @buffer.length
28
28
  end
29
29
 
@@ -15,7 +15,7 @@ module Holistic::LanguageServer
15
15
  server.start_input_loop do |payload|
16
16
  parser.ingest(payload)
17
17
 
18
- while parser.completed?
18
+ while parser.has_complete_message?
19
19
  response = ::Holistic::LanguageServer::Router.dispatch(parser.message)
20
20
 
21
21
  case response
@@ -16,7 +16,7 @@ module Holistic::Ruby::Autocompletion
16
16
  lookup_scope = scope
17
17
 
18
18
  if code.start_with?("::")
19
- lookup_scope = lookup_scope.parent until lookup_scope.root?
19
+ lookup_scope = lookup_scope.lexical_parent until lookup_scope.root?
20
20
  end
21
21
 
22
22
  if StartsWithLowerCaseLetter[code]
@@ -33,23 +33,23 @@ module Holistic::Ruby::Autocompletion
33
33
  def suggest_local_methods_from_current_scope(code:, scope:)
34
34
  suggestions = []
35
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
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}"
45
48
  end
46
- elsif scope.class_method?
47
- sibling_methods = scope.parent.children.filter { _1.class_method? }
48
49
 
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
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
53
  end
54
54
  end
55
55
 
@@ -69,7 +69,7 @@ module Holistic::Ruby::Autocompletion
69
69
  return suggestions if scope.nil?
70
70
  end
71
71
 
72
- class_methods = scope.children.filter { _1.class_method? }
72
+ class_methods = ::Holistic::Ruby::Scope::ListClassMethods.call(scope:)
73
73
 
74
74
  class_methods.each do |method_scope|
75
75
  if method_scope.name.start_with?(method_to_autocomplete)
@@ -96,7 +96,7 @@ module Holistic::Ruby::Autocompletion
96
96
  should_search_upwards = namespaces_to_resolve.empty?
97
97
 
98
98
  search = ->(scope) do
99
- scope.children.each do |child_scope|
99
+ scope.lexical_children.each do |child_scope|
100
100
  next if child_scope.method?
101
101
 
102
102
  if child_scope.name.start_with?(namespace_to_autocomplete)
@@ -104,7 +104,7 @@ module Holistic::Ruby::Autocompletion
104
104
  end
105
105
  end
106
106
 
107
- search.(scope.parent) if scope.parent.present? && should_search_upwards
107
+ search.(scope.lexical_parent) if scope.lexical_parent.present? && should_search_upwards
108
108
  end
109
109
 
110
110
  search.(scope)
@@ -113,10 +113,10 @@ module Holistic::Ruby::Autocompletion
113
113
  end
114
114
 
115
115
  def resolve_scope(name:, from_scope:)
116
- resolved_scope = from_scope.children.find { |scope| scope.name == name }
116
+ resolved_scope = from_scope.lexical_children.find { |scope| scope.name == name }
117
117
 
118
- if resolved_scope.nil? && from_scope.parent.present?
119
- 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)
120
120
  end
121
121
 
122
122
  resolved_scope
@@ -27,7 +27,7 @@ module Holistic::Ruby::Parser
27
27
  @scope =
28
28
  ::Holistic::Ruby::Scope::Store.call(
29
29
  database: @scope_repository.database,
30
- parent: @scope,
30
+ lexical_parent: @scope,
31
31
  kind: ::Holistic::Ruby::Scope::Kind::MODULE,
32
32
  name:,
33
33
  location:
@@ -54,7 +54,7 @@ module Holistic::Ruby::Parser
54
54
  @scope =
55
55
  ::Holistic::Ruby::Scope::Store.call(
56
56
  database: @scope_repository.database,
57
- parent: @scope,
57
+ lexical_parent: @scope,
58
58
  kind: ::Holistic::Ruby::Scope::Kind::CLASS,
59
59
  name:,
60
60
  location:
@@ -81,7 +81,7 @@ module Holistic::Ruby::Parser
81
81
  @scope =
82
82
  ::Holistic::Ruby::Scope::Store.call(
83
83
  database: @scope_repository.database,
84
- parent: @scope,
84
+ lexical_parent: @scope,
85
85
  kind:,
86
86
  name:,
87
87
  location:
@@ -15,7 +15,7 @@ module Holistic::Ruby::Parser
15
15
 
16
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
@@ -54,9 +54,9 @@ module Holistic::Ruby::Parser
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
- application.database.disconnect(source: reference.referenced_scope, target: reference, name: :referenced_by, inverse_of: :referenced_scope)
59
+ reference.relation(:referenced_scope).delete!(reference.referenced_scope)
60
60
 
61
61
  ::Holistic::Ruby::TypeInference::Solve.call(application:, reference:)
62
62
  end
@@ -27,10 +27,6 @@ module Holistic::Ruby::Parser
27
27
  def visit_class(node)
28
28
  declaration_node, superclass_node, body_statements_node = node.child_nodes
29
29
 
30
- if superclass_node
31
- register_reference(nesting: NestingSyntax.build(superclass_node), location: build_location(superclass_node))
32
- end
33
-
34
30
  nesting = NestingSyntax.build(declaration_node)
35
31
  location = build_scope_location(declaration_node:, body_node: node)
36
32
 
@@ -38,6 +34,25 @@ module Holistic::Ruby::Parser
38
34
  visit(body_statements_node)
39
35
  end
40
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
+ )
54
+ end
55
+
41
56
  @application.extensions.dispatch(:class_scope_registered, { class_scope:, location: })
42
57
  end
43
58
 
@@ -50,6 +65,27 @@ 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
 
@@ -155,7 +191,7 @@ module Holistic::Ruby::Parser
155
191
  lambda_scope =
156
192
  ::Holistic::Ruby::Scope::Store.call(
157
193
  database: @application.database,
158
- parent: @constant_resolution.scope,
194
+ lexical_parent: @constant_resolution.scope,
159
195
  kind: ::Holistic::Ruby::Scope::Kind::LAMBDA,
160
196
  name: assign_node.child_nodes.first.value,
161
197
  location:
@@ -20,12 +20,55 @@ module Holistic::Ruby::Parser
20
20
 
21
21
  visitor.visit(program)
22
22
  rescue ::SyntaxTree::Parser::ParseError
23
- ::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
24
57
  end
25
58
 
26
59
  ParseDirectory = ->(application:, directory_path:) do
60
+ performance_metrics = PerformanceMetrics.new
61
+
27
62
  ::Dir.glob("#{directory_path}/**/*.rb").map do |file_path|
28
- ParseFile.call(application:, file_path:, content: ::File.read(file_path))
63
+ performance_metrics.start_file_read!
64
+ content = ::File.read(file_path)
65
+ performance_metrics.end_file_read!
66
+
67
+ performance_metrics.start_parse!
68
+ ParseFile.call(application:, file_path:, content:)
69
+ performance_metrics.end_parse!
29
70
  end
71
+
72
+ performance_metrics
30
73
  end
31
74
  end
@@ -7,11 +7,11 @@ module Holistic::Ruby::Reference
7
7
  def call(database:, reference:)
8
8
  database.delete(reference.identifier)
9
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)
10
+ reference.relation(:reference_defined_in_file).delete!(reference.location.file)
11
+ reference.relation(:located_in_scope).delete!(reference.located_in_scope)
12
12
 
13
13
  if reference.referenced_scope
14
- database.disconnect(source: reference.referenced_scope, target: reference, name: :referenced_by, inverse_of: :referenced_scope)
14
+ reference.relation(:referenced_scope).delete!(reference.referenced_scope)
15
15
  end
16
16
  end
17
17
  end
@@ -8,5 +8,13 @@ module Holistic::Ruby::Reference
8
8
 
9
9
  def referenced_scope = has_one(:referenced_scope)
10
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
11
19
  end
12
20
  end
@@ -22,7 +22,7 @@ module Holistic::Ruby::Reference
22
22
 
23
23
  def list_references_to_scopes_in_file(scopes:, file_path:)
24
24
  references = @database.find(file_path)&.defines_scopes&.flat_map do |scope|
25
- scope.referenced_by
25
+ scope.referenced_by.to_a
26
26
  end
27
27
 
28
28
  references || []
@@ -9,10 +9,21 @@ module Holistic::Ruby::Reference
9
9
 
10
10
  reference = database.store(location.identifier, record)
11
11
 
12
- database.connect(source: scope, target: reference, name: :contains_many_references, inverse_of: :located_in_scope)
13
- database.connect(source: location.file, target: reference, name: :defines_references, inverse_of: :reference_defined_in_file)
12
+ reference.relation(:located_in_scope).add!(scope)
13
+ reference.relation(:reference_defined_in_file).add!(location.file)
14
14
 
15
- processing_queue.push(reference)
15
+ # resolving reference to superclasses need to happen before resolving reference to methods because the
16
+ # relation ancestor-descentand needs to exist beforehand.
17
+ # in other words, if we try to resolve a reference to a method *before* resolving the superclass
18
+ # we might get a miss.
19
+ should_resolve_type_inference_with_high_priority =
20
+ reference.find_clue(::Holistic::Ruby::TypeInference::Clue::ReferenceToSuperclass).present?
21
+
22
+ if should_resolve_type_inference_with_high_priority
23
+ processing_queue.push_with_high_priority(reference)
24
+ else
25
+ processing_queue.push(reference)
26
+ end
16
27
  end
17
28
  end
18
29
  end
@@ -15,10 +15,10 @@ module Holistic::Ruby::Scope
15
15
 
16
16
  scope.locations.delete(location_to_remove)
17
17
 
18
- database.disconnect(source: location_to_remove.declaration.file, target: scope, name: :defines_scopes, inverse_of: :scope_defined_in_file)
18
+ scope.relation(:scope_defined_in_file).delete!(location_to_remove.declaration.file)
19
19
 
20
20
  if scope.locations.empty?
21
- database.disconnect(source: scope.parent, target: scope, name: :children, inverse_of: :parent)
21
+ scope.relation(:lexical_parent).delete!(scope.lexical_parent)
22
22
 
23
23
  database.delete(fully_qualified_name)
24
24
  end
@@ -4,7 +4,7 @@ module Holistic::Ruby::Scope::Lexical
4
4
  extend self
5
5
 
6
6
  def descendant?(child:, parent:)
7
- child_parent = child.parent
7
+ child_parent = child.lexical_parent
8
8
 
9
9
  child_parent.present? && (child_parent == parent || descendant?(child: child_parent, parent:))
10
10
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::Scope::ListClassMethods
4
+ extend self
5
+
6
+ def call(scope:)
7
+ class_methods = scope.lexical_children.filter(&:class_method?)
8
+ class_method_names = ::Set.new(class_methods.map(&:name))
9
+
10
+ ancestor_methods = scope.ancestors.flat_map do |ancestor|
11
+ ancestor_methods = call(scope: ancestor)
12
+
13
+ # reject parent methods that were overriden by the subclass
14
+ ancestor_methods.reject { |method| class_method_names.include?(method.name) }
15
+ end
16
+
17
+ class_methods + ancestor_methods
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::Scope::ListInstanceMethods
4
+ extend self
5
+
6
+ def call(scope:)
7
+ instance_methods = scope.lexical_children.filter(&:instance_method?)
8
+ instance_method_names = ::Set.new(instance_methods.map(&:name))
9
+
10
+ ancestor_methods = scope.ancestors.flat_map do |ancestor|
11
+ ancestor_methods = call(scope: ancestor)
12
+
13
+ # reject parent methods that were overriden by the subclass
14
+ ancestor_methods.reject { |method| instance_method_names.include?(method.name) }
15
+ end
16
+
17
+ instance_methods + ancestor_methods
18
+ end
19
+ end
@@ -5,9 +5,9 @@ module Holistic::Ruby::Scope
5
5
  extend self
6
6
 
7
7
  QueryReferencesRecursively = ->(application, scope) do
8
- references_to_scope = scope.referenced_by
8
+ references_to_scope = scope.referenced_by.to_a
9
9
 
10
- references_to_child_scopes = scope.children.flat_map { QueryReferencesRecursively.call(application, _1) }
10
+ references_to_child_scopes = scope.lexical_children.flat_map { QueryReferencesRecursively.call(application, _1) }
11
11
 
12
12
  references_to_scope + references_to_child_scopes
13
13
  end
@@ -13,7 +13,7 @@ module Holistic::Ruby::Scope
13
13
  )
14
14
 
15
15
  QueryChildScopesRecursively = ->(application, scope) do
16
- scope.children + scope.children.flat_map { QueryChildScopesRecursively[application, _1] }
16
+ scope.lexical_children.to_a + scope.lexical_children.flat_map { QueryChildScopesRecursively[application, _1] }
17
17
  end
18
18
 
19
19
  QueryDependenciesRecursively = ->(application, outlined_scope, scope) do
@@ -34,7 +34,7 @@ module Holistic::Ruby::Scope
34
34
  .tap { dependencies.concat(_1) }
35
35
  end
36
36
 
37
- scope.children.map(&QueryDependenciesRecursively.curry[application, outlined_scope]).flatten.concat(dependencies)
37
+ scope.lexical_children.map(&QueryDependenciesRecursively.curry[application, outlined_scope]).flatten.concat(dependencies)
38
38
  end
39
39
 
40
40
  def call(application:, scope:)
@@ -7,14 +7,20 @@ module Holistic::Ruby::Scope
7
7
  def name = attr(:name)
8
8
  def kind = attr(:kind)
9
9
 
10
- def parent = has_one(:parent)
11
- def children = has_many(:children)
12
- def referenced_by = has_many(:referenced_by)
10
+ def lexical_parent = has_one(:lexical_parent)
11
+ def lexical_children = has_many(:lexical_children)
12
+ def ancestors = has_many(:ancestors)
13
+ def descendants = has_many(:descendants)
14
+ def referenced_by = has_many(:referenced_by)
13
15
 
14
16
  def root? = kind == Kind::ROOT
15
17
  def class? = kind == Kind::CLASS
16
18
  def class_method? = kind == Kind::CLASS_METHOD
17
19
  def instance_method? = kind == Kind::INSTANCE_METHOD
18
20
  def method? = class_method? || instance_method?
21
+
22
+ def inspect
23
+ "<#{self.class.name} kind=#{kind} fully_qualified_name=#{fully_qualified_name}>"
24
+ end
19
25
  end
20
26
  end
@@ -34,11 +34,15 @@ module Holistic::Ruby::Scope
34
34
 
35
35
  return nil if file.nil?
36
36
 
37
- matching_scopes = file.defines_scopes.filter do |scope|
38
- scope.locations.any? { _1.body.contains?(cursor) }
37
+ matching_scopes = file.defines_scopes.filter_map do |scope|
38
+ scope.locations.find { |location| location.body.contains?(cursor) }&.then do |location|
39
+ { location:, scope: }
40
+ end
39
41
  end
40
42
 
41
- matching_scopes.last
43
+ inner_most_matching_scope = matching_scopes.sort_by { |match| match[:location].declaration.start_line }.last
44
+
45
+ inner_most_matching_scope&.then { _1[:scope] }
42
46
  end
43
47
 
44
48
  def list_scopes_in_file(file_path)
@@ -4,32 +4,32 @@ module Holistic::Ruby::Scope
4
4
  module Store
5
5
  extend self
6
6
 
7
- def call(database:, parent:, kind:, name:, location:)
8
- fully_qualified_name = build_fully_qualified_name(parent:, kind:, name:)
7
+ def call(database:, lexical_parent:, kind:, name:, location:)
8
+ fully_qualified_name = build_fully_qualified_name(lexical_parent:, kind:, name:)
9
9
 
10
- child_scope = database.find(fully_qualified_name)
10
+ scope = database.find(fully_qualified_name)
11
11
 
12
- if child_scope.nil?
12
+ if scope.nil?
13
13
  record = Record.new(fully_qualified_name, { fully_qualified_name:, name:, kind:, locations: Location::Collection.new(name) })
14
14
 
15
- child_scope = database.store(fully_qualified_name, record)
15
+ scope = database.store(fully_qualified_name, record)
16
16
  end
17
17
 
18
- child_scope.locations << location
18
+ scope.locations << location
19
19
 
20
- database.connect(source: parent, target: child_scope, name: :children, inverse_of: :parent)
21
- database.connect(source: location.declaration.file, target: child_scope, name: :defines_scopes, inverse_of: :scope_defined_in_file)
20
+ scope.relation(:lexical_parent).add!(lexical_parent)
21
+ scope.relation(:scope_defined_in_file).add!(location.declaration.file)
22
22
 
23
- child_scope
23
+ scope
24
24
  end
25
25
 
26
26
  private
27
27
 
28
- def build_fully_qualified_name(parent:, kind:, name:)
28
+ def build_fully_qualified_name(lexical_parent:, kind:, name:)
29
29
  parent_fully_qualified_name =
30
- case parent.kind
30
+ case lexical_parent.kind
31
31
  when Kind::ROOT then ""
32
- else parent.fully_qualified_name
32
+ else lexical_parent.fully_qualified_name
33
33
  end
34
34
 
35
35
  separator =
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Holistic::Ruby::TypeInference::Clue
4
+ # TODO: Rename to ReferenceToMethod
4
5
  MethodCall = ::Data.define(
5
6
  :nesting,
6
7
  :method_name,
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::TypeInference::Clue
4
+ ReferenceToSuperclass = ::Data.define(:subclass_scope) do
5
+ def to_s
6
+ "superclass of #{subclass_scope.fully_qualified_name}"
7
+ end
8
+ end
9
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Holistic::Ruby::TypeInference::Clue
4
+ # TODO: rename to ReferenceToScope
4
5
  ScopeReference = ::Struct.new(
5
6
  :nesting,
6
7
  :resolution_possibilities,
@@ -1,19 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Holistic::Ruby::TypeInference::ProcessingQueue
4
- def initialize
5
- @queue = ::Queue.new
6
- end
3
+ module Holistic::Ruby::TypeInference
4
+ class ProcessingQueue
5
+ def initialize
6
+ @high_priority_queue = ::Queue.new
7
+ @queue = ::Queue.new
8
+ end
7
9
 
8
- def push(reference)
9
- @queue.push(reference)
10
- end
10
+ def push(item)
11
+ @queue.push(item)
12
+ end
11
13
 
12
- def pop
13
- @queue.pop(true)
14
- end
14
+ def push_with_high_priority(item)
15
+ @high_priority_queue.push(item)
16
+ end
17
+
18
+ def empty?
19
+ @high_priority_queue.empty? && @queue.empty?
20
+ end
21
+
22
+ def pop
23
+ return @high_priority_queue.pop if @high_priority_queue.size > 0
15
24
 
16
- def empty?
17
- @queue.empty?
25
+ @queue.pop
26
+ end
18
27
  end
19
28
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::TypeInference::Resolver::ClassMethod
4
+ extend self
5
+
6
+ def resolve(scope:, method_name:)
7
+ ::Holistic::Ruby::Scope::ListClassMethods.call(scope:).find { _1.name == method_name }
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::TypeInference::Resolver::InstanceMethod
4
+ extend self
5
+
6
+ def resolve(scope:, method_name:)
7
+ ::Holistic::Ruby::Scope::ListInstanceMethods.call(scope:).find { _1.name == method_name }
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Holistic::Ruby::TypeInference::Resolver::Scope
4
+ extend self
5
+
6
+ def resolve(application:, nesting:, resolution_possibilities:)
7
+ resolution_possibilities = ["::"] if nesting.root_scope_resolution?
8
+
9
+ resolution_possibilities.each do |resolution_candidate|
10
+ fully_qualified_scope_name =
11
+ if resolution_candidate == "::"
12
+ "::#{nesting.to_s}"
13
+ else
14
+ "#{resolution_candidate}::#{nesting.to_s}"
15
+ end
16
+
17
+ scope = application.scopes.find(fully_qualified_scope_name)
18
+
19
+ return scope if scope.present?
20
+ end
21
+
22
+ nil
23
+ end
24
+ end
@@ -10,24 +10,27 @@ module Holistic::Ruby::TypeInference
10
10
  solve_method_call(application:, reference:)
11
11
 
12
12
  if referenced_scope
13
- application.database.connect(source: reference, target: referenced_scope, name: :referenced_scope, inverse_of: :referenced_by)
13
+ reference.relation(:referenced_scope).add!(referenced_scope)
14
+
15
+ # NOTE: should this be an event that is handled by stdlib? I guess inheritance support with dedicated syntax
16
+ # is part of the language core, so it makes sense being here. Let me think about this for a bit.
17
+ reference.find_clue(Clue::ReferenceToSuperclass)&.then do |reference_to_superclass_clue|
18
+ referenced_scope.relation(:descendants).add!(reference_to_superclass_clue.subclass_scope)
19
+ end
14
20
  end
15
21
  end
16
22
 
17
23
  private
18
24
 
19
25
  def solve_scope_reference(application:, reference:)
20
- has_scope_reference_clue =
21
- reference.clues.one? && reference.clues.first.is_a?(Clue::ScopeReference)
22
-
23
- return unless has_scope_reference_clue
26
+ reference_to_scope_clue = reference.find_clue(Clue::ScopeReference)
24
27
 
25
- scope_reference_clue = reference.clues.first
28
+ return if reference_to_scope_clue.nil?
26
29
 
27
- resolve_scope(
30
+ Resolver::Scope.resolve(
28
31
  application:,
29
- nesting: scope_reference_clue.nesting,
30
- resolution_possibilities: scope_reference_clue.resolution_possibilities
32
+ nesting: reference_to_scope_clue.nesting,
33
+ resolution_possibilities: reference_to_scope_clue.resolution_possibilities
31
34
  )
32
35
  end
33
36
 
@@ -35,14 +38,14 @@ module Holistic::Ruby::TypeInference
35
38
  scope = reference.located_in_scope
36
39
 
37
40
  if scope.class_method?
38
- resolve_class_method(application:, scope: scope.parent, method_name: method_call_clue.method_name)
39
- elsif scope.instance_method? && scope.parent.present?
40
- resolve_instance_method(application:, scope: scope.parent, method_name: method_call_clue.method_name)
41
+ Resolver::ClassMethod.resolve(scope: scope.lexical_parent, method_name: method_call_clue.method_name)
42
+ elsif scope.instance_method? && scope.lexical_parent.present?
43
+ Resolver::InstanceMethod.resolve(scope: scope.lexical_parent, method_name: method_call_clue.method_name)
41
44
  end
42
45
  end
43
46
 
44
47
  SolveMethodCallInSpecifiedScope = ->(application:, reference:, method_call_clue:) do
45
- referenced_scope = resolve_scope(
48
+ referenced_scope = Resolver::Scope.resolve(
46
49
  application:,
47
50
  nesting: method_call_clue.nesting,
48
51
  resolution_possibilities: method_call_clue.resolution_possibilities
@@ -52,14 +55,7 @@ module Holistic::Ruby::TypeInference
52
55
 
53
56
  referenced_method = application.extensions.dispatch(:resolve_method_call_known_scope, { reference:, referenced_scope:, method_call_clue: })
54
57
 
55
- referenced_method || resolve_class_method(application:, scope: referenced_scope, method_name: method_call_clue.method_name)
56
- end
57
-
58
- SolveMethodCallInLocalVariable = ->(application:, reference:, method_call_clue:) do
59
- # local_variable_name = method_call_clue.nesting.to_s
60
- # referenced_scope = guess_scope_for_local_variable(scope: reference.scope, name: local_variable_name)
61
-
62
- nil
58
+ referenced_method || Resolver::ClassMethod.resolve(scope: referenced_scope, method_name: method_call_clue.method_name)
63
59
  end
64
60
 
65
61
  def solve_method_call(application:, reference:)
@@ -74,39 +70,8 @@ module Holistic::Ruby::TypeInference
74
70
  elsif method_call_clue.nesting.constant?
75
71
  SolveMethodCallInSpecifiedScope.call(application:, reference:, method_call_clue:)
76
72
  else
77
- SolveMethodCallInLocalVariable.call(application:, reference:, method_call_clue:)
78
- end
79
- end
80
-
81
- def resolve_scope(application:, nesting:, resolution_possibilities:)
82
- resolution_possibilities = ["::"] if nesting.root_scope_resolution?
83
-
84
- resolution_possibilities.each do |resolution_candidate|
85
- fully_qualified_scope_name =
86
- if resolution_candidate == "::"
87
- "::#{nesting.to_s}"
88
- else
89
- "#{resolution_candidate}::#{nesting.to_s}"
90
- end
91
-
92
- scope = application.scopes.find(fully_qualified_scope_name)
93
-
94
- return scope if scope.present?
73
+ nil # TODO
95
74
  end
96
-
97
- nil
98
- end
99
-
100
- def resolve_instance_method(application:, scope:, method_name:)
101
- method_fully_qualified_name = "#{scope.fully_qualified_name}##{method_name}"
102
-
103
- application.scopes.find(method_fully_qualified_name)
104
- end
105
-
106
- def resolve_class_method(application:, scope:, method_name:)
107
- method_fully_qualified_name = "#{scope.fully_qualified_name}.#{method_name}"
108
-
109
- application.scopes.find(method_fully_qualified_name)
110
75
  end
111
76
  end
112
77
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Holistic
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.7"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: holistic-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luiz Vasconcellos
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-30 00:00:00.000000000 Z
11
+ date: 2023-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: syntax_tree
@@ -73,9 +73,10 @@ files:
73
73
  - lib/holistic.rb
74
74
  - lib/holistic/application.rb
75
75
  - lib/holistic/background_process.rb
76
+ - lib/holistic/database.rb
76
77
  - lib/holistic/database/migrations.rb
77
78
  - lib/holistic/database/node.rb
78
- - lib/holistic/database/table.rb
79
+ - lib/holistic/database/relation.rb
79
80
  - lib/holistic/document/cursor.rb
80
81
  - lib/holistic/document/file/record.rb
81
82
  - lib/holistic/document/file/repository.rb
@@ -122,6 +123,8 @@ files:
122
123
  - lib/holistic/ruby/scope/delete.rb
123
124
  - lib/holistic/ruby/scope/kind.rb
124
125
  - lib/holistic/ruby/scope/lexical.rb
126
+ - lib/holistic/ruby/scope/list_class_methods.rb
127
+ - lib/holistic/ruby/scope/list_instance_methods.rb
125
128
  - lib/holistic/ruby/scope/list_references.rb
126
129
  - lib/holistic/ruby/scope/location.rb
127
130
  - lib/holistic/ruby/scope/outline.rb
@@ -129,8 +132,12 @@ files:
129
132
  - lib/holistic/ruby/scope/repository.rb
130
133
  - lib/holistic/ruby/scope/store.rb
131
134
  - lib/holistic/ruby/type_inference/clue/method_call.rb
135
+ - lib/holistic/ruby/type_inference/clue/reference_to_superclass.rb
132
136
  - lib/holistic/ruby/type_inference/clue/scope_reference.rb
133
137
  - lib/holistic/ruby/type_inference/processing_queue.rb
138
+ - lib/holistic/ruby/type_inference/resolver/class_method.rb
139
+ - lib/holistic/ruby/type_inference/resolver/instance_method.rb
140
+ - lib/holistic/ruby/type_inference/resolver/scope.rb
134
141
  - lib/holistic/ruby/type_inference/solve.rb
135
142
  - lib/holistic/ruby/type_inference/solve_pending_references.rb
136
143
  - lib/holistic/version.rb
@@ -1,78 +0,0 @@
1
-
2
- # frozen_string_literal: true
3
-
4
- module Holistic::Database
5
- class Table
6
- attr_reader :records, :connections
7
-
8
- def initialize
9
- @records = ::Hash.new
10
- @connections = ::Hash.new
11
- end
12
-
13
- def define_connection(name:, inverse_of:)
14
- raise ::ArgumentError if @connections.key?(name) || @connections.key?(inverse_of)
15
-
16
- @connections[name] = { inverse_of: }
17
- @connections[inverse_of] = { inverse_of: name }
18
- end
19
-
20
- def store(id, node_or_attrs)
21
- if @records.key?(id)
22
- return @records[id]&.tap do |node|
23
- node.attributes =
24
- case node_or_attrs
25
- in ::Hash then node_or_attrs
26
- in Node then node_or_attrs.attributes
27
- end
28
- end
29
- end
30
-
31
- node =
32
- case node_or_attrs
33
- in ::Hash then Node.new(id, node_or_attrs)
34
- in Node then node_or_attrs
35
- end
36
-
37
- node.__set_database__(self)
38
-
39
- @records[id] = node
40
- end
41
-
42
- def connect(source:, target:, name:, inverse_of:)
43
- connection = @connections[name]
44
-
45
- raise ::ArgumentError if connection.nil? || connection[:inverse_of] != inverse_of
46
-
47
- source.connections[name].add(target)
48
- target.connections[inverse_of].add(source)
49
- end
50
-
51
- def disconnect(source:, target:, name:, inverse_of:)
52
- connection = @connections[name]
53
-
54
- raise ::ArgumentError if connection.nil? || connection[:inverse_of] != inverse_of
55
-
56
- source.connections[name].delete(target)
57
- target.connections[inverse_of].delete(source)
58
- end
59
-
60
- def find(id)
61
- @records[id]
62
- end
63
-
64
- def delete(id)
65
- records.delete(id)
66
- end
67
-
68
- concerning :TestHelpers do
69
- def all
70
- records.values
71
- end
72
-
73
- def size
74
- records.size
75
- end
76
- end
77
- end
78
- end