holistic-ruby 0.1.6 → 0.1.7

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