holistic-ruby 0.1.0
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 +7 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +52 -0
- data/LICENSE.txt +21 -0
- data/README.md +35 -0
- data/Rakefile +8 -0
- data/config/logging.rb +6 -0
- data/exe/holistic-ruby +6 -0
- data/holistic-ruby.gemspec +34 -0
- data/lib/holistic/application.rb +29 -0
- data/lib/holistic/background_process.rb +11 -0
- data/lib/holistic/database/table.rb +78 -0
- data/lib/holistic/document/cursor.rb +9 -0
- data/lib/holistic/document/file.rb +36 -0
- data/lib/holistic/document/location.rb +35 -0
- data/lib/holistic/document/unsaved/change.rb +24 -0
- data/lib/holistic/document/unsaved/collection.rb +21 -0
- data/lib/holistic/document/unsaved/record.rb +83 -0
- data/lib/holistic/extensions/events.rb +37 -0
- data/lib/holistic/extensions/ruby/stdlib.rb +43 -0
- data/lib/holistic/language_server/current.rb +11 -0
- data/lib/holistic/language_server/format/file_uri.rb +19 -0
- data/lib/holistic/language_server/lifecycle.rb +59 -0
- data/lib/holistic/language_server/message.rb +21 -0
- data/lib/holistic/language_server/protocol.rb +45 -0
- data/lib/holistic/language_server/request.rb +21 -0
- data/lib/holistic/language_server/requests/lifecycle/exit.rb +10 -0
- data/lib/holistic/language_server/requests/lifecycle/initialize.rb +75 -0
- data/lib/holistic/language_server/requests/lifecycle/initialized.rb +13 -0
- data/lib/holistic/language_server/requests/lifecycle/shutdown.rb +14 -0
- data/lib/holistic/language_server/requests/text_document/completion.rb +68 -0
- data/lib/holistic/language_server/requests/text_document/did_change.rb +30 -0
- data/lib/holistic/language_server/requests/text_document/did_close.rb +33 -0
- data/lib/holistic/language_server/requests/text_document/did_open.rb +16 -0
- data/lib/holistic/language_server/requests/text_document/did_save.rb +33 -0
- data/lib/holistic/language_server/requests/text_document/find_references.rb +52 -0
- data/lib/holistic/language_server/requests/text_document/go_to_definition.rb +64 -0
- data/lib/holistic/language_server/response.rb +39 -0
- data/lib/holistic/language_server/router.rb +48 -0
- data/lib/holistic/language_server/stdio/parser.rb +65 -0
- data/lib/holistic/language_server/stdio/server.rb +46 -0
- data/lib/holistic/language_server/stdio/start.rb +48 -0
- data/lib/holistic/ruby/autocompletion/suggest.rb +75 -0
- data/lib/holistic/ruby/parser/constant_resolution.rb +61 -0
- data/lib/holistic/ruby/parser/live_editing/process_file_changed.rb +62 -0
- data/lib/holistic/ruby/parser/nesting_syntax.rb +76 -0
- data/lib/holistic/ruby/parser/program_visitor.rb +205 -0
- data/lib/holistic/ruby/parser/table_of_contents.rb +17 -0
- data/lib/holistic/ruby/parser.rb +26 -0
- data/lib/holistic/ruby/reference/find_referenced_scope.rb +18 -0
- data/lib/holistic/ruby/reference/record.rb +13 -0
- data/lib/holistic/ruby/reference/register.rb +15 -0
- data/lib/holistic/ruby/reference/repository.rb +71 -0
- data/lib/holistic/ruby/reference/unregister.rb +11 -0
- data/lib/holistic/ruby/scope/kind.rb +11 -0
- data/lib/holistic/ruby/scope/list_references.rb +32 -0
- data/lib/holistic/ruby/scope/location.rb +43 -0
- data/lib/holistic/ruby/scope/outline.rb +52 -0
- data/lib/holistic/ruby/scope/record.rb +52 -0
- data/lib/holistic/ruby/scope/register.rb +31 -0
- data/lib/holistic/ruby/scope/repository.rb +49 -0
- data/lib/holistic/ruby/scope/unregister.rb +27 -0
- data/lib/holistic/ruby/type_inference/clue/method_call.rb +15 -0
- data/lib/holistic/ruby/type_inference/clue/scope_reference.rb +13 -0
- data/lib/holistic/ruby/type_inference/conclusion.rb +20 -0
- data/lib/holistic/ruby/type_inference/solve.rb +110 -0
- data/lib/holistic/ruby/type_inference/solve_pending_references.rb +13 -0
- data/lib/holistic/version.rb +5 -0
- data/lib/holistic.rb +27 -0
- metadata +158 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::Reference
|
4
|
+
module FindReferencedScope
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(application:, cursor:)
|
8
|
+
reference = application.references.find_by_cursor(cursor)
|
9
|
+
|
10
|
+
return :not_found if reference.nil?
|
11
|
+
return :could_not_find_referenced_scope if reference.conclusion.dependency_identifier.nil?
|
12
|
+
|
13
|
+
referenced_scope = application.scopes.find_by_fully_qualified_name(reference.conclusion.dependency_identifier)
|
14
|
+
|
15
|
+
[:referenced_scope_found, {reference:, referenced_scope:}]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::Reference
|
4
|
+
module Register
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(repository:, scope:, clues:, location:)
|
8
|
+
conclusion = ::Holistic::Ruby::TypeInference::Conclusion.pending
|
9
|
+
|
10
|
+
reference = ::Holistic::Ruby::Reference::Record.new(scope:, clues:, location:, conclusion:)
|
11
|
+
|
12
|
+
repository.register_reference(reference)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::Reference
|
4
|
+
class Repository
|
5
|
+
attr_reader :table
|
6
|
+
|
7
|
+
INDICES = [
|
8
|
+
:file_path,
|
9
|
+
:type_inference_status,
|
10
|
+
:referenced_scope_fully_qualified_name
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@table = ::Holistic::Database::Table.new(primary_attribute: :identifier, indices: INDICES)
|
15
|
+
end
|
16
|
+
|
17
|
+
def register_reference(reference)
|
18
|
+
table.update({
|
19
|
+
reference:,
|
20
|
+
identifier: reference.identifier,
|
21
|
+
file_path: reference.location.file_path,
|
22
|
+
type_inference_status: reference.conclusion.status,
|
23
|
+
referenced_scope_fully_qualified_name: reference.conclusion.dependency_identifier
|
24
|
+
})
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_by_cursor(cursor)
|
28
|
+
table.filter(:file_path, cursor.file_path).map { _1[:reference] }.each do |reference|
|
29
|
+
return reference if reference.location.contains?(cursor)
|
30
|
+
end
|
31
|
+
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def list_references_to(fully_qualified_scope_name)
|
36
|
+
table.filter(:referenced_scope_fully_qualified_name, fully_qualified_scope_name).map { _1[:reference] }
|
37
|
+
end
|
38
|
+
|
39
|
+
def list_references_in_file(file_path)
|
40
|
+
table.filter(:file_path, file_path).map { _1[:reference] }
|
41
|
+
end
|
42
|
+
|
43
|
+
def list_references_to_scopes_in_file(scopes:, file_path:)
|
44
|
+
scopes.list_scopes_in_file(file_path).flat_map do |scope|
|
45
|
+
table.filter(:referenced_scope_fully_qualified_name, scope.fully_qualified_name).map { _1[:reference] }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def list_references_pending_type_inference_conclusion
|
50
|
+
table.filter(:type_inference_status, ::Holistic::Ruby::TypeInference::STATUS_PENDING).map { _1[:reference] }
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete(identifier)
|
54
|
+
table.delete(identifier)
|
55
|
+
end
|
56
|
+
|
57
|
+
concerning :TestHelpers do
|
58
|
+
def find_reference_to(scope_name)
|
59
|
+
table.all.map { _1[:reference] }.find do |reference|
|
60
|
+
reference.conclusion.dependency_identifier == scope_name || reference.clues.find { _1.to_s == scope_name }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def find_by_code_content(code_content)
|
65
|
+
table.all.map { _1[:reference] }.find do |reference|
|
66
|
+
reference.clues.find { _1.to_s == code_content }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::Scope
|
4
|
+
module ListReferences
|
5
|
+
extend self
|
6
|
+
|
7
|
+
QueryReferencesRecursively = ->(application, scope) do
|
8
|
+
references_to_scope = application.references.list_references_to(scope.fully_qualified_name)
|
9
|
+
|
10
|
+
references_to_child_scopes = scope.children.flat_map { QueryReferencesRecursively.call(application, _1) }
|
11
|
+
|
12
|
+
references_to_scope + references_to_child_scopes
|
13
|
+
end
|
14
|
+
|
15
|
+
Relevance = ->(reference) do
|
16
|
+
# TODO: should the location answer the kind of file it is? application code, config, spec, etc. Not sure.
|
17
|
+
looks_like_a_spec = reference.location.file_path.include?("_spec.rb") || reference.location.file_path.include?("_test.rb")
|
18
|
+
|
19
|
+
looks_like_a_spec ? 1 : 0
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(application:, cursor:)
|
23
|
+
scope = application.scopes.find_by_cursor(cursor)
|
24
|
+
|
25
|
+
return :not_found if scope.nil?
|
26
|
+
|
27
|
+
references = QueryReferencesRecursively.call(application, scope).sort_by(&Relevance)
|
28
|
+
|
29
|
+
[:references_listed, {references:}]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::Scope
|
4
|
+
class Location
|
5
|
+
class Collection
|
6
|
+
attr_reader :scope, :items
|
7
|
+
|
8
|
+
def initialize(scope, location)
|
9
|
+
raise ::ArgumentError if location.present? && !location.is_a?(Location)
|
10
|
+
|
11
|
+
@scope = scope
|
12
|
+
@items = location.nil? ? [] : [location]
|
13
|
+
end
|
14
|
+
|
15
|
+
def main
|
16
|
+
location_matching_scope_name || items.first
|
17
|
+
end
|
18
|
+
|
19
|
+
delegate :<<, to: :items
|
20
|
+
delegate :each, to: :items
|
21
|
+
delegate :map, to: :items
|
22
|
+
delegate :reject!, to: :items
|
23
|
+
delegate :any?, to: :items
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def location_matching_scope_name
|
28
|
+
scope_name_in_snake_case = scope.name.underscore
|
29
|
+
|
30
|
+
items.find do |location|
|
31
|
+
::File.basename(location.declaration.file_path) == "#{scope_name_in_snake_case}.rb"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :declaration, :body
|
37
|
+
|
38
|
+
def initialize(declaration:, body:)
|
39
|
+
@declaration = declaration
|
40
|
+
@body = body
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::Scope
|
4
|
+
module Outline
|
5
|
+
extend self
|
6
|
+
|
7
|
+
Result = ::Struct.new(
|
8
|
+
:declarations,
|
9
|
+
:dependencies,
|
10
|
+
:references,
|
11
|
+
:dependants,
|
12
|
+
keyword_init: true
|
13
|
+
)
|
14
|
+
|
15
|
+
QueryChildScopesRecursively = ->(application, scope) do
|
16
|
+
scope.children + scope.children.flat_map { QueryChildScopesRecursively[application, _1] }
|
17
|
+
end
|
18
|
+
|
19
|
+
QueryDependenciesRecursively = ->(application, outlined_scope, scope) do
|
20
|
+
is_local_dependency = ->(reference) do
|
21
|
+
scope = application.scopes.find_by_fully_qualified_name(reference.conclusion.dependency_identifier)
|
22
|
+
|
23
|
+
scope.eql?(outlined_scope) || scope.descendant?(outlined_scope)
|
24
|
+
end
|
25
|
+
|
26
|
+
dependencies = []
|
27
|
+
|
28
|
+
scope.locations.each do |scope_location|
|
29
|
+
application.references
|
30
|
+
.list_references_in_file(scope_location.declaration.file_path)
|
31
|
+
.filter { |reference| reference.scope == scope }
|
32
|
+
.filter { |reference| reference.conclusion.dependency_identifier.present? }
|
33
|
+
.reject(&is_local_dependency)
|
34
|
+
.tap { dependencies.concat(_1) }
|
35
|
+
end
|
36
|
+
|
37
|
+
scope.children.map(&QueryDependenciesRecursively.curry[application, outlined_scope]).flatten.concat(dependencies)
|
38
|
+
end
|
39
|
+
|
40
|
+
def call(application:, scope:)
|
41
|
+
declarations = QueryChildScopesRecursively.call(application, scope).sort_by { _1.fully_qualified_name }
|
42
|
+
|
43
|
+
dependencies = QueryDependenciesRecursively.call(application, scope, scope).uniq { _1.conclusion.dependency_identifier }
|
44
|
+
|
45
|
+
references = application.references.list_references_to(scope.fully_qualified_name)
|
46
|
+
|
47
|
+
dependants = references.map { |reference| reference.scope }.uniq
|
48
|
+
|
49
|
+
Result.new(declarations:, dependencies:, references:, dependants:)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::Scope
|
4
|
+
class Record
|
5
|
+
attr_reader :kind, :name, :parent, :children, :locations
|
6
|
+
|
7
|
+
def initialize(kind:, name:, parent:, location: nil)
|
8
|
+
@kind = kind
|
9
|
+
@name = name
|
10
|
+
@parent = parent
|
11
|
+
@locations = Location::Collection.new(self, location)
|
12
|
+
@children = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def fully_qualified_name
|
16
|
+
return "" if root?
|
17
|
+
|
18
|
+
separator =
|
19
|
+
if kind == Kind::METHOD
|
20
|
+
"#"
|
21
|
+
else
|
22
|
+
"::"
|
23
|
+
end
|
24
|
+
|
25
|
+
"#{parent.fully_qualified_name}#{separator}#{name}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def root?
|
29
|
+
parent.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
def lambda?
|
33
|
+
kind == Kind::LAMBDA
|
34
|
+
end
|
35
|
+
|
36
|
+
def class?
|
37
|
+
kind == Kind::CLASS
|
38
|
+
end
|
39
|
+
|
40
|
+
def module?
|
41
|
+
kind == Kind::MODULE
|
42
|
+
end
|
43
|
+
|
44
|
+
def method?
|
45
|
+
kind == Kind::METHOD
|
46
|
+
end
|
47
|
+
|
48
|
+
def descendant?(other)
|
49
|
+
parent.present? && (parent == other || parent.descendant?(other))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::Scope
|
4
|
+
module Register
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(repository:, parent:, kind:, name:, location:)
|
8
|
+
child_scope = append_location_to_existing_scope(scope: parent, name:, location:) || add_new_scope(parent:, kind:, name:, location:)
|
9
|
+
|
10
|
+
repository.register_scope(child_scope)
|
11
|
+
|
12
|
+
child_scope
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def append_location_to_existing_scope(scope:, name:, location:)
|
18
|
+
child_scope = scope.children.find { _1.name == name }
|
19
|
+
|
20
|
+
return if child_scope.nil?
|
21
|
+
|
22
|
+
child_scope.tap { _1.locations << location }
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_new_scope(parent:, kind:, name:, location:)
|
26
|
+
child_scope = Record.new(kind:, name:, parent:, location:)
|
27
|
+
|
28
|
+
child_scope.tap { parent.children << _1 }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::Scope
|
4
|
+
class Repository
|
5
|
+
attr_reader :table
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@table = ::Holistic::Database::Table.new(primary_attribute: :fully_qualified_name, indices: [:file_paths])
|
9
|
+
end
|
10
|
+
|
11
|
+
def register_scope(scope)
|
12
|
+
table.update({
|
13
|
+
fully_qualified_name: scope.fully_qualified_name,
|
14
|
+
file_paths: scope.locations.map { |scope_location| scope_location.declaration.file_path },
|
15
|
+
scope:
|
16
|
+
})
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_by_fully_qualified_name(fully_qualified_name)
|
20
|
+
table.find(fully_qualified_name).try(:dig, :scope)
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_by_cursor(cursor)
|
24
|
+
table.filter(:file_paths, cursor.file_path).map { _1[:scope] }.each do |scope|
|
25
|
+
return scope if scope.locations.any? { _1.declaration.contains?(cursor) }
|
26
|
+
end
|
27
|
+
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_inner_most_scope_by_cursor(cursor)
|
32
|
+
scopes = table.filter(:file_paths, cursor.file_path).map { _1[:scope] }
|
33
|
+
|
34
|
+
matching_scopes = scopes.filter do |scope|
|
35
|
+
scope.locations.any? { _1.body.contains?(cursor) }
|
36
|
+
end
|
37
|
+
|
38
|
+
matching_scopes.last
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_by_fully_qualified_name(fully_qualified_name)
|
42
|
+
table.delete(fully_qualified_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def list_scopes_in_file(file_path)
|
46
|
+
table.filter(:file_paths, file_path).map { _1[:scope] }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::Scope
|
4
|
+
module Unregister
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(repository:, fully_qualified_name:, file_path:)
|
8
|
+
scope = repository.find_by_fully_qualified_name(fully_qualified_name)
|
9
|
+
|
10
|
+
return :scope_not_found if scope.nil?
|
11
|
+
|
12
|
+
updated_locations = scope.locations.reject! { |scope_location| scope_location.declaration.file_path == file_path }
|
13
|
+
|
14
|
+
return :scope_not_defined_in_speciefied_file if updated_locations.nil?
|
15
|
+
|
16
|
+
if updated_locations.empty?
|
17
|
+
scope.parent.children.delete(scope)
|
18
|
+
|
19
|
+
repository.delete_by_fully_qualified_name(fully_qualified_name)
|
20
|
+
else
|
21
|
+
repository.register_scope(scope)
|
22
|
+
end
|
23
|
+
|
24
|
+
:definition_unregistered
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::TypeInference::Clue
|
4
|
+
MethodCall = ::Data.define(
|
5
|
+
:nesting,
|
6
|
+
:method_name,
|
7
|
+
:resolution_possibilities
|
8
|
+
) do
|
9
|
+
def to_s
|
10
|
+
return method_name if nesting.nil?
|
11
|
+
|
12
|
+
"#{nesting}.#{method_name}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::TypeInference
|
4
|
+
STATUS_PENDING = :pending
|
5
|
+
STATUS_DONE = :done
|
6
|
+
|
7
|
+
Conclusion = ::Data.define(:status, :dependency_identifier) do
|
8
|
+
def self.pending
|
9
|
+
new(status: STATUS_PENDING, dependency_identifier: nil)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.unresolved
|
13
|
+
new(status: STATUS_DONE, dependency_identifier: nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.done(dependency_identifier)
|
17
|
+
new(status: STATUS_DONE, dependency_identifier:)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::TypeInference
|
4
|
+
module Solve
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(application:, reference:)
|
8
|
+
reference.conclusion =
|
9
|
+
solve_scope_reference(application:, reference:) ||
|
10
|
+
solve_method_call(application:, reference:) ||
|
11
|
+
Conclusion.unresolved
|
12
|
+
|
13
|
+
application.references.register_reference(reference)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def solve_scope_reference(application:, reference:)
|
19
|
+
has_scope_reference_clue =
|
20
|
+
reference.clues.one? && reference.clues.first.is_a?(Clue::ScopeReference)
|
21
|
+
|
22
|
+
return unless has_scope_reference_clue
|
23
|
+
|
24
|
+
scope_reference_clue = reference.clues.first
|
25
|
+
|
26
|
+
referenced_scope = resolve_scope(
|
27
|
+
application:,
|
28
|
+
nesting: scope_reference_clue.nesting,
|
29
|
+
resolution_possibilities: scope_reference_clue.resolution_possibilities
|
30
|
+
)
|
31
|
+
|
32
|
+
if referenced_scope.present?
|
33
|
+
return Conclusion.done(referenced_scope.fully_qualified_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
SolveMethodCallInCurrentScope = ->(application:, reference:, method_call_clue:) do
|
40
|
+
referenced_method = resolve_method(application:, scope: reference.scope, method_name: method_call_clue.method_name)
|
41
|
+
|
42
|
+
return if referenced_method.nil?
|
43
|
+
|
44
|
+
Conclusion.done(referenced_method.fully_qualified_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
SolveMethodCallInSpecifiedScope = ->(application:, reference:, method_call_clue:) do
|
48
|
+
referenced_scope = resolve_scope(
|
49
|
+
application:,
|
50
|
+
nesting: method_call_clue.nesting,
|
51
|
+
resolution_possibilities: method_call_clue.resolution_possibilities
|
52
|
+
)
|
53
|
+
|
54
|
+
return if referenced_scope.nil?
|
55
|
+
|
56
|
+
referenced_method = resolve_method(application:, scope: referenced_scope, method_name: method_call_clue.method_name)
|
57
|
+
referenced_method ||= application.extensions.dispatch(:resolve_method_call_known_scope, { reference:, referenced_scope:, method_call_clue: })
|
58
|
+
|
59
|
+
Conclusion.done(referenced_method.fully_qualified_name) if referenced_method.present?
|
60
|
+
end
|
61
|
+
|
62
|
+
SolveMethodCallInLocalVariable = ->(application:, reference:, method_call_clue:) do
|
63
|
+
# local_variable_name = method_call_clue.nesting.to_s
|
64
|
+
# referenced_scope = guess_scope_for_local_variable(scope: reference.scope, name: local_variable_name)
|
65
|
+
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def solve_method_call(application:, reference:)
|
70
|
+
has_method_call_clue = reference.clues.one? && reference.clues.first.is_a?(Clue::MethodCall)
|
71
|
+
|
72
|
+
return unless has_method_call_clue
|
73
|
+
|
74
|
+
method_call_clue = reference.clues.first
|
75
|
+
|
76
|
+
if method_call_clue.nesting.nil?
|
77
|
+
SolveMethodCallInCurrentScope.call(application:, reference:, method_call_clue:)
|
78
|
+
elsif method_call_clue.nesting.constant?
|
79
|
+
SolveMethodCallInSpecifiedScope.call(application:, reference:, method_call_clue:)
|
80
|
+
else
|
81
|
+
SolveMethodCallInLocalVariable.call(application:, reference:, method_call_clue:)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def resolve_scope(application:, nesting:, resolution_possibilities:)
|
86
|
+
resolution_possibilities = ["::"] if nesting.root_scope_resolution?
|
87
|
+
|
88
|
+
resolution_possibilities.each do |resolution_candidate|
|
89
|
+
fully_qualified_scope_name =
|
90
|
+
if resolution_candidate == "::"
|
91
|
+
"::#{nesting.to_s}"
|
92
|
+
else
|
93
|
+
"#{resolution_candidate}::#{nesting.to_s}"
|
94
|
+
end
|
95
|
+
|
96
|
+
scope = application.scopes.find_by_fully_qualified_name(fully_qualified_scope_name)
|
97
|
+
|
98
|
+
return scope if scope.present?
|
99
|
+
end
|
100
|
+
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def resolve_method(application:, scope:, method_name:)
|
105
|
+
method_fully_qualified_name = "#{scope.fully_qualified_name}##{method_name}"
|
106
|
+
|
107
|
+
application.scopes.find_by_fully_qualified_name(method_fully_qualified_name)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::TypeInference
|
4
|
+
module SolvePendingReferences
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(application:)
|
8
|
+
application.references.list_references_pending_type_inference_conclusion.each do |reference|
|
9
|
+
Solve.call(application:, reference:)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/holistic.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "uri"
|
5
|
+
require "logger"
|
6
|
+
|
7
|
+
require "zeitwerk"
|
8
|
+
require "syntax_tree"
|
9
|
+
|
10
|
+
require "active_support/concern"
|
11
|
+
require "active_support/core_ext/module/concerning"
|
12
|
+
require "active_support/notifications"
|
13
|
+
require "active_support/inflector"
|
14
|
+
|
15
|
+
require_relative "../config/logging"
|
16
|
+
|
17
|
+
loader = ::Zeitwerk::Loader.for_gem
|
18
|
+
loader.setup
|
19
|
+
|
20
|
+
module Holistic
|
21
|
+
extend self
|
22
|
+
|
23
|
+
@logger = ::Logger.new(ENV["HOLISTIC_LOG_OUTPUT"])
|
24
|
+
attr_reader :logger
|
25
|
+
end
|
26
|
+
|
27
|
+
loader.eager_load
|