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.
- 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
|