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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +5 -5
- data/exe/holistic-ruby +0 -1
- data/lib/holistic/application.rb +1 -1
- data/lib/holistic/database/migrations.rb +9 -6
- data/lib/holistic/database/node.rb +33 -25
- data/lib/holistic/database/relation.rb +21 -0
- data/lib/holistic/database.rb +57 -0
- data/lib/holistic/document/unsaved/record.rb +13 -0
- data/lib/holistic/extensions/ruby/stdlib.rb +3 -3
- data/lib/holistic/language_server/requests/text_document/completion.rb +13 -3
- data/lib/holistic/language_server/stdio/parser.rb +2 -2
- data/lib/holistic/language_server/stdio/start.rb +1 -1
- data/lib/holistic/ruby/autocompletion/suggest.rb +22 -22
- data/lib/holistic/ruby/parser/constant_resolution.rb +3 -3
- data/lib/holistic/ruby/parser/live_editing/process_file_changed.rb +3 -3
- data/lib/holistic/ruby/parser/program_visitor.rb +41 -5
- data/lib/holistic/ruby/parser.rb +45 -2
- data/lib/holistic/ruby/reference/delete.rb +3 -3
- data/lib/holistic/ruby/reference/record.rb +8 -0
- data/lib/holistic/ruby/reference/repository.rb +1 -1
- data/lib/holistic/ruby/reference/store.rb +14 -3
- data/lib/holistic/ruby/scope/delete.rb +2 -2
- data/lib/holistic/ruby/scope/lexical.rb +1 -1
- data/lib/holistic/ruby/scope/list_class_methods.rb +19 -0
- data/lib/holistic/ruby/scope/list_instance_methods.rb +19 -0
- data/lib/holistic/ruby/scope/list_references.rb +2 -2
- data/lib/holistic/ruby/scope/outline.rb +2 -2
- data/lib/holistic/ruby/scope/record.rb +9 -3
- data/lib/holistic/ruby/scope/repository.rb +7 -3
- data/lib/holistic/ruby/scope/store.rb +12 -12
- data/lib/holistic/ruby/type_inference/clue/method_call.rb +1 -0
- data/lib/holistic/ruby/type_inference/clue/reference_to_superclass.rb +9 -0
- data/lib/holistic/ruby/type_inference/clue/scope_reference.rb +1 -0
- data/lib/holistic/ruby/type_inference/processing_queue.rb +21 -12
- data/lib/holistic/ruby/type_inference/resolver/class_method.rb +9 -0
- data/lib/holistic/ruby/type_inference/resolver/instance_method.rb +9 -0
- data/lib/holistic/ruby/type_inference/resolver/scope.rb +24 -0
- data/lib/holistic/ruby/type_inference/solve.rb +18 -53
- data/lib/holistic/version.rb +1 -1
- metadata +10 -3
- data/lib/holistic/database/table.rb +0 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b5eda7bf8e9da75eb3716452dfc2ebafdbaf35af0a53aef452c64bd95cd00bd8
|
4
|
+
data.tar.gz: 5522698edac5824011fd5aeee3ba4c71eb5cfee51b956c3d5942e329c7644072
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 06caa82194be9d2d2d9832aefd9132ab3d3cc1838be8b2cff9cd64f3fbafa36a9f0f79c40384eb4439adfd224a559605a9e328ae7dfb722235870b535f0d155f
|
7
|
+
data.tar.gz: a542c510078f1f528b4621000ba87843d0544e23dea77123bcf627e79d17f8dcdcb987be4122bd0f39c7f96533fc57d1a26e489e0cc25b6f5e0e5dfc68c436d3
|
data/Gemfile.lock
CHANGED
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
|
29
|
-
*
|
30
|
-
*
|
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
data/lib/holistic/application.rb
CHANGED
@@ -2,19 +2,22 @@
|
|
2
2
|
|
3
3
|
module Holistic::Database::Migrations
|
4
4
|
Run = ->(database) do
|
5
|
-
# scope parent-children relation
|
6
|
-
database.
|
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.
|
12
|
+
database.define_relation(name: :referenced_scope, inverse_of: :referenced_by)
|
10
13
|
|
11
14
|
# reference definition
|
12
|
-
database.
|
15
|
+
database.define_relation(name: :located_in_scope, inverse_of: :contains_many_references)
|
13
16
|
|
14
17
|
# scope location in files
|
15
|
-
database.
|
18
|
+
database.define_relation(name: :defines_scopes, inverse_of: :scope_defined_in_file)
|
16
19
|
|
17
20
|
# reference location in files
|
18
|
-
database.
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 || !
|
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
|
26
|
+
def has_complete_message?
|
27
27
|
!@in_header && @content_length == @buffer.length
|
28
28
|
end
|
29
29
|
|
@@ -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.
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
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
|
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.
|
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.
|
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.
|
116
|
+
resolved_scope = from_scope.lexical_children.find { |scope| scope.name == name }
|
117
117
|
|
118
|
-
if resolved_scope.nil? && from_scope.
|
119
|
-
resolved_scope = resolve_scope(name:, from_scope: from_scope.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
57
|
+
def recalculate_type_inference(application:, references:)
|
58
58
|
references.each do |reference|
|
59
|
-
|
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
|
-
|
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:
|
data/lib/holistic/ruby/parser.rb
CHANGED
@@ -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 #{
|
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
|
-
|
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
|
-
|
11
|
-
|
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
|
-
|
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
|
@@ -9,10 +9,21 @@ module Holistic::Ruby::Reference
|
|
9
9
|
|
10
10
|
reference = database.store(location.identifier, record)
|
11
11
|
|
12
|
-
|
13
|
-
|
12
|
+
reference.relation(:located_in_scope).add!(scope)
|
13
|
+
reference.relation(:reference_defined_in_file).add!(location.file)
|
14
14
|
|
15
|
-
|
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
|
-
|
18
|
+
scope.relation(:scope_defined_in_file).delete!(location_to_remove.declaration.file)
|
19
19
|
|
20
20
|
if scope.locations.empty?
|
21
|
-
|
21
|
+
scope.relation(:lexical_parent).delete!(scope.lexical_parent)
|
22
22
|
|
23
23
|
database.delete(fully_qualified_name)
|
24
24
|
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.
|
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.
|
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.
|
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
|
11
|
-
def
|
12
|
-
def
|
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.
|
38
|
-
scope.locations.
|
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:,
|
8
|
-
fully_qualified_name = build_fully_qualified_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
|
-
|
10
|
+
scope = database.find(fully_qualified_name)
|
11
11
|
|
12
|
-
if
|
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
|
-
|
15
|
+
scope = database.store(fully_qualified_name, record)
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
scope.locations << location
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
scope.relation(:lexical_parent).add!(lexical_parent)
|
21
|
+
scope.relation(:scope_defined_in_file).add!(location.declaration.file)
|
22
22
|
|
23
|
-
|
23
|
+
scope
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
-
def build_fully_qualified_name(
|
28
|
+
def build_fully_qualified_name(lexical_parent:, kind:, name:)
|
29
29
|
parent_fully_qualified_name =
|
30
|
-
case
|
30
|
+
case lexical_parent.kind
|
31
31
|
when Kind::ROOT then ""
|
32
|
-
else
|
32
|
+
else lexical_parent.fully_qualified_name
|
33
33
|
end
|
34
34
|
|
35
35
|
separator =
|
@@ -1,19 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
10
|
+
def push(item)
|
11
|
+
@queue.push(item)
|
12
|
+
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
17
|
-
|
25
|
+
@queue.pop
|
26
|
+
end
|
18
27
|
end
|
19
28
|
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
|
-
|
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
|
-
|
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
|
-
|
28
|
+
return if reference_to_scope_clue.nil?
|
26
29
|
|
27
|
-
|
30
|
+
Resolver::Scope.resolve(
|
28
31
|
application:,
|
29
|
-
nesting:
|
30
|
-
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
|
-
|
39
|
-
elsif scope.instance_method? && scope.
|
40
|
-
|
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 =
|
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 ||
|
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
|
-
|
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
|
data/lib/holistic/version.rb
CHANGED
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.
|
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-
|
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/
|
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
|