holistic-ruby 0.1.4 → 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 +12 -4
- data/lib/holistic/database/migrations.rb +23 -0
- data/lib/holistic/database/node.rb +37 -0
- data/lib/holistic/database/relation.rb +21 -0
- data/lib/holistic/database.rb +57 -0
- data/lib/holistic/document/file/record.rb +10 -0
- data/lib/holistic/document/file/repository.rb +24 -0
- data/lib/holistic/document/file/store.rb +13 -0
- data/lib/holistic/document/location.rb +4 -6
- data/lib/holistic/document/unsaved/record.rb +12 -3
- data/lib/holistic/extensions/ruby/stdlib.rb +8 -8
- data/lib/holistic/language_server/requests/lifecycle/initialize.rb +5 -10
- data/lib/holistic/language_server/requests/text_document/completion.rb +16 -5
- data/lib/holistic/language_server/requests/text_document/did_close.rb +5 -9
- data/lib/holistic/language_server/requests/text_document/did_open.rb +1 -0
- data/lib/holistic/language_server/requests/text_document/did_save.rb +5 -9
- data/lib/holistic/language_server/requests/text_document/find_references.rb +7 -4
- data/lib/holistic/language_server/requests/text_document/go_to_definition.rb +3 -2
- 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 +45 -25
- data/lib/holistic/ruby/parser/constant_resolution.rb +11 -11
- data/lib/holistic/ruby/parser/live_editing/process_file_changed.rb +23 -23
- data/lib/holistic/ruby/parser/program_visitor.rb +62 -29
- data/lib/holistic/ruby/parser.rb +51 -11
- data/lib/holistic/ruby/reference/delete.rb +18 -0
- data/lib/holistic/ruby/reference/find_referenced_scope.rb +2 -2
- data/lib/holistic/ruby/reference/record.rb +15 -8
- data/lib/holistic/ruby/reference/repository.rb +19 -41
- data/lib/holistic/ruby/reference/store.rb +29 -0
- data/lib/holistic/ruby/scope/delete.rb +29 -0
- data/lib/holistic/ruby/scope/lexical.rb +11 -0
- 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 +3 -3
- data/lib/holistic/ruby/scope/location.rb +9 -9
- data/lib/holistic/ruby/scope/outline.rb +10 -10
- data/lib/holistic/ruby/scope/record.rb +20 -50
- data/lib/holistic/ruby/scope/repository.rb +29 -26
- data/lib/holistic/ruby/scope/store.rb +45 -0
- 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 +28 -0
- 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 +25 -69
- data/lib/holistic/ruby/type_inference/solve_pending_references.rb +3 -1
- data/lib/holistic/version.rb +1 -1
- metadata +21 -10
- data/lib/holistic/database/table.rb +0 -78
- data/lib/holistic/document/file.rb +0 -36
- data/lib/holistic/ruby/parser/table_of_contents.rb +0 -17
- data/lib/holistic/ruby/reference/register.rb +0 -15
- data/lib/holistic/ruby/reference/unregister.rb +0 -11
- data/lib/holistic/ruby/scope/register.rb +0 -31
- data/lib/holistic/ruby/scope/unregister.rb +0 -27
- data/lib/holistic/ruby/type_inference/conclusion.rb +0 -20
@@ -6,14 +6,22 @@ module Holistic::Ruby::Autocompletion
|
|
6
6
|
|
7
7
|
Suggestion = ::Data.define(:code, :kind)
|
8
8
|
|
9
|
+
StartsWithLowerCaseLetter = ->(code) do
|
10
|
+
return false if [".", ":", "@"].include?(code[0])
|
11
|
+
|
12
|
+
code[0] == code[0].downcase
|
13
|
+
end
|
14
|
+
|
9
15
|
def call(code:, scope:)
|
10
16
|
lookup_scope = scope
|
11
17
|
|
12
18
|
if code.start_with?("::")
|
13
|
-
lookup_scope = lookup_scope.
|
19
|
+
lookup_scope = lookup_scope.lexical_parent until lookup_scope.root?
|
14
20
|
end
|
15
21
|
|
16
|
-
if code
|
22
|
+
if StartsWithLowerCaseLetter[code]
|
23
|
+
suggest_local_methods_from_current_scope(code:, scope: lookup_scope)
|
24
|
+
elsif code.include?(".")
|
17
25
|
suggest_methods_from_scope(code:, scope: lookup_scope)
|
18
26
|
else
|
19
27
|
suggest_namespaces_from_scope(code:, scope: lookup_scope)
|
@@ -22,12 +30,32 @@ module Holistic::Ruby::Autocompletion
|
|
22
30
|
|
23
31
|
private
|
24
32
|
|
25
|
-
|
26
|
-
|
33
|
+
def suggest_local_methods_from_current_scope(code:, scope:)
|
34
|
+
suggestions = []
|
35
|
+
|
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}"
|
48
|
+
end
|
49
|
+
|
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
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
suggestions
|
57
|
+
end
|
27
58
|
|
28
|
-
# Payment. <--
|
29
|
-
# Payment::Notifications. <--
|
30
|
-
# current_user.a
|
31
59
|
def suggest_methods_from_scope(code:, scope:)
|
32
60
|
suggestions = []
|
33
61
|
|
@@ -41,7 +69,9 @@ module Holistic::Ruby::Autocompletion
|
|
41
69
|
return suggestions if scope.nil?
|
42
70
|
end
|
43
71
|
|
44
|
-
|
72
|
+
class_methods = ::Holistic::Ruby::Scope::ListClassMethods.call(scope:)
|
73
|
+
|
74
|
+
class_methods.each do |method_scope|
|
45
75
|
if method_scope.name.start_with?(method_to_autocomplete)
|
46
76
|
suggestions << Suggestion.new(code: method_scope.name, kind: method_scope.kind)
|
47
77
|
end
|
@@ -63,28 +93,18 @@ module Holistic::Ruby::Autocompletion
|
|
63
93
|
return suggestions if scope.nil?
|
64
94
|
end
|
65
95
|
|
66
|
-
# special case when user did not type :: at the end but the current word
|
67
|
-
# is matches an existing namespace. In this case, suggestions will start with ::.
|
68
|
-
# For example:
|
69
|
-
#
|
70
|
-
# \/ cursor here
|
71
|
-
# typing: "::MyApp::Payments"
|
72
|
-
# suggestions: ["::Record", "::SendReminder"]
|
73
|
-
resolve_scope(name: namespace_to_autocomplete, from_scope: scope)&.then do |fully_typed_scope|
|
74
|
-
scope = fully_typed_scope
|
75
|
-
namespace_to_autocomplete = ""
|
76
|
-
end
|
77
|
-
|
78
96
|
should_search_upwards = namespaces_to_resolve.empty?
|
79
97
|
|
80
98
|
search = ->(scope) do
|
81
|
-
scope.
|
99
|
+
scope.lexical_children.each do |child_scope|
|
100
|
+
next if child_scope.method?
|
101
|
+
|
82
102
|
if child_scope.name.start_with?(namespace_to_autocomplete)
|
83
103
|
suggestions << Suggestion.new(code: child_scope.name, kind: child_scope.kind)
|
84
104
|
end
|
85
105
|
end
|
86
106
|
|
87
|
-
search.(scope.
|
107
|
+
search.(scope.lexical_parent) if scope.lexical_parent.present? && should_search_upwards
|
88
108
|
end
|
89
109
|
|
90
110
|
search.(scope)
|
@@ -93,10 +113,10 @@ module Holistic::Ruby::Autocompletion
|
|
93
113
|
end
|
94
114
|
|
95
115
|
def resolve_scope(name:, from_scope:)
|
96
|
-
resolved_scope = from_scope.
|
116
|
+
resolved_scope = from_scope.lexical_children.find { |scope| scope.name == name }
|
97
117
|
|
98
|
-
if resolved_scope.nil? && from_scope.
|
99
|
-
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)
|
100
120
|
end
|
101
121
|
|
102
122
|
resolved_scope
|
@@ -9,9 +9,9 @@ module Holistic::Ruby::Parser
|
|
9
9
|
|
10
10
|
attr_reader :scope_repository, :scope, :method_registration_mode
|
11
11
|
|
12
|
-
def initialize(scope_repository
|
12
|
+
def initialize(scope_repository:)
|
13
13
|
@scope_repository = scope_repository
|
14
|
-
@scope =
|
14
|
+
@scope = scope_repository.root
|
15
15
|
@constant_resolution_possibilities = ["::"]
|
16
16
|
@method_registration_mode = MethodRegistrationMode::INSTANCE_METHODS
|
17
17
|
end
|
@@ -25,9 +25,9 @@ module Holistic::Ruby::Parser
|
|
25
25
|
|
26
26
|
nesting.each do |name|
|
27
27
|
@scope =
|
28
|
-
::Holistic::Ruby::Scope::
|
29
|
-
|
30
|
-
|
28
|
+
::Holistic::Ruby::Scope::Store.call(
|
29
|
+
database: @scope_repository.database,
|
30
|
+
lexical_parent: @scope,
|
31
31
|
kind: ::Holistic::Ruby::Scope::Kind::MODULE,
|
32
32
|
name:,
|
33
33
|
location:
|
@@ -52,9 +52,9 @@ module Holistic::Ruby::Parser
|
|
52
52
|
|
53
53
|
nesting.each do |name|
|
54
54
|
@scope =
|
55
|
-
::Holistic::Ruby::Scope::
|
56
|
-
|
57
|
-
|
55
|
+
::Holistic::Ruby::Scope::Store.call(
|
56
|
+
database: @scope_repository.database,
|
57
|
+
lexical_parent: @scope,
|
58
58
|
kind: ::Holistic::Ruby::Scope::Kind::CLASS,
|
59
59
|
name:,
|
60
60
|
location:
|
@@ -79,9 +79,9 @@ module Holistic::Ruby::Parser
|
|
79
79
|
|
80
80
|
nesting.each do |name|
|
81
81
|
@scope =
|
82
|
-
::Holistic::Ruby::Scope::
|
83
|
-
|
84
|
-
|
82
|
+
::Holistic::Ruby::Scope::Store.call(
|
83
|
+
database: @scope_repository.database,
|
84
|
+
lexical_parent: @scope,
|
85
85
|
kind:,
|
86
86
|
name:,
|
87
87
|
location:
|
@@ -4,59 +4,59 @@ module Holistic::Ruby::Parser
|
|
4
4
|
module LiveEditing::ProcessFileChanged
|
5
5
|
extend self
|
6
6
|
|
7
|
-
def call(application:,
|
7
|
+
def call(application:, file_path:, content:)
|
8
8
|
# TODO: do not build the AST twice
|
9
|
-
return unless HasValidSyntax[
|
9
|
+
return unless HasValidSyntax[content]
|
10
10
|
|
11
|
-
references_to_recalculate = identify_references_to_recalculate(application:,
|
11
|
+
references_to_recalculate = identify_references_to_recalculate(application:, file_path:)
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
delete_scopes_in_file(application:, file_path:)
|
14
|
+
delete_references_in_file(application:, file_path:)
|
15
15
|
|
16
|
-
parse_again(application:,
|
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
|
22
22
|
|
23
|
-
def identify_references_to_recalculate(application:,
|
23
|
+
def identify_references_to_recalculate(application:, file_path:)
|
24
24
|
# we need to reject references declared in the same because they're already going to be
|
25
25
|
# reparsed. If we don't do that, we'll end up with duplicated reference records.
|
26
26
|
|
27
27
|
application.references
|
28
|
-
.list_references_to_scopes_in_file(scopes: application.scopes, file_path:
|
29
|
-
.reject { _1.location.
|
28
|
+
.list_references_to_scopes_in_file(scopes: application.scopes, file_path: file_path)
|
29
|
+
.reject { _1.location.file.path == file_path }
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
33
|
-
application.scopes.list_scopes_in_file(
|
34
|
-
::Holistic::Ruby::Scope::
|
35
|
-
|
32
|
+
def delete_scopes_in_file(application:, file_path:)
|
33
|
+
application.scopes.list_scopes_in_file(file_path).each do |scope|
|
34
|
+
::Holistic::Ruby::Scope::Delete.call(
|
35
|
+
database: application.database,
|
36
36
|
fully_qualified_name: scope.fully_qualified_name,
|
37
|
-
file_path:
|
37
|
+
file_path:
|
38
38
|
)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
def
|
43
|
-
application.references.list_references_in_file(
|
44
|
-
::Holistic::Ruby::Reference::
|
45
|
-
|
42
|
+
def delete_references_in_file(application:, file_path:)
|
43
|
+
application.references.list_references_in_file(file_path).each do |reference|
|
44
|
+
::Holistic::Ruby::Reference::Delete.call(
|
45
|
+
database: application.database,
|
46
46
|
reference: reference
|
47
47
|
)
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
def parse_again(application:,
|
52
|
-
ParseFile.call(application:,
|
51
|
+
def parse_again(application:, file_path:, content:)
|
52
|
+
ParseFile.call(application:, file_path:, content:)
|
53
53
|
|
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
|
-
reference.
|
59
|
+
reference.relation(:referenced_scope).delete!(reference.referenced_scope)
|
60
60
|
|
61
61
|
::Holistic::Ruby::TypeInference::Solve.call(application:, reference:)
|
62
62
|
end
|
@@ -14,28 +14,43 @@ module Holistic::Ruby::Parser
|
|
14
14
|
|
15
15
|
visit_methods do
|
16
16
|
def visit_module(node)
|
17
|
-
declaration_node,
|
17
|
+
declaration_node, body_statements_node = node.child_nodes
|
18
18
|
|
19
19
|
nesting = NestingSyntax.build(declaration_node)
|
20
|
-
location = build_scope_location(declaration_node:, body_node:)
|
20
|
+
location = build_scope_location(declaration_node:, body_node: node)
|
21
21
|
|
22
22
|
@constant_resolution.register_child_module(nesting:, location:) do
|
23
|
-
visit(
|
23
|
+
visit(body_statements_node)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
27
|
def visit_class(node)
|
28
|
-
declaration_node, superclass_node,
|
29
|
-
|
30
|
-
if superclass_node
|
31
|
-
register_reference(nesting: NestingSyntax.build(superclass_node), location: build_location(superclass_node))
|
32
|
-
end
|
28
|
+
declaration_node, superclass_node, body_statements_node = node.child_nodes
|
33
29
|
|
34
30
|
nesting = NestingSyntax.build(declaration_node)
|
35
|
-
location = build_scope_location(declaration_node:, body_node:)
|
31
|
+
location = build_scope_location(declaration_node:, body_node: node)
|
36
32
|
|
37
33
|
class_scope = @constant_resolution.register_child_class(nesting:, location:) do
|
38
|
-
visit(
|
34
|
+
visit(body_statements_node)
|
35
|
+
end
|
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
|
+
)
|
39
54
|
end
|
40
55
|
|
41
56
|
@application.extensions.dispatch(:class_scope_registered, { class_scope:, location: })
|
@@ -50,14 +65,35 @@ 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
|
|
56
92
|
def visit_def(node)
|
57
|
-
instance_node, period_node, method_name_node, _params,
|
93
|
+
instance_node, period_node, method_name_node, _params, body_statements_node = node.child_nodes
|
58
94
|
|
59
95
|
nesting = NestingSyntax.new(method_name_node.value)
|
60
|
-
location = build_scope_location(declaration_node: method_name_node, body_node:)
|
96
|
+
location = build_scope_location(declaration_node: method_name_node, body_node: node)
|
61
97
|
|
62
98
|
kind =
|
63
99
|
if instance_node.present? && instance_node.child_nodes.first.value == "self"
|
@@ -69,7 +105,7 @@ module Holistic::Ruby::Parser
|
|
69
105
|
end
|
70
106
|
|
71
107
|
@constant_resolution.register_child_method(nesting:, location:, kind:) do
|
72
|
-
visit(
|
108
|
+
visit(body_statements_node)
|
73
109
|
end
|
74
110
|
end
|
75
111
|
|
@@ -80,8 +116,9 @@ module Holistic::Ruby::Parser
|
|
80
116
|
resolution_possibilities: @constant_resolution.current
|
81
117
|
)
|
82
118
|
|
83
|
-
::Holistic::Ruby::Reference::
|
84
|
-
|
119
|
+
::Holistic::Ruby::Reference::Store.call(
|
120
|
+
database: @application.database,
|
121
|
+
processing_queue: @application.type_inference_processing_queue,
|
85
122
|
scope: @constant_resolution.scope,
|
86
123
|
clues: [method_call_clue],
|
87
124
|
location: build_location(node)
|
@@ -114,8 +151,9 @@ module Holistic::Ruby::Parser
|
|
114
151
|
resolution_possibilities: @constant_resolution.current
|
115
152
|
)
|
116
153
|
|
117
|
-
::Holistic::Ruby::Reference::
|
118
|
-
|
154
|
+
::Holistic::Ruby::Reference::Store.call(
|
155
|
+
database: @application.database,
|
156
|
+
processing_queue: @application.type_inference_processing_queue,
|
119
157
|
scope: @constant_resolution.scope,
|
120
158
|
clues: [method_call_clue],
|
121
159
|
location: build_location(method_name_node || instance_node)
|
@@ -151,9 +189,9 @@ module Holistic::Ruby::Parser
|
|
151
189
|
location = build_scope_location(declaration_node: assign_node, body_node:)
|
152
190
|
|
153
191
|
lambda_scope =
|
154
|
-
::Holistic::Ruby::Scope::
|
155
|
-
|
156
|
-
|
192
|
+
::Holistic::Ruby::Scope::Store.call(
|
193
|
+
database: @application.database,
|
194
|
+
lexical_parent: @constant_resolution.scope,
|
157
195
|
kind: ::Holistic::Ruby::Scope::Kind::LAMBDA,
|
158
196
|
name: assign_node.child_nodes.first.value,
|
159
197
|
location:
|
@@ -185,8 +223,9 @@ module Holistic::Ruby::Parser
|
|
185
223
|
resolution_possibilities: @constant_resolution.current
|
186
224
|
)
|
187
225
|
|
188
|
-
::Holistic::Ruby::Reference::
|
189
|
-
|
226
|
+
::Holistic::Ruby::Reference::Store.call(
|
227
|
+
database: @application.database,
|
228
|
+
processing_queue: @application.type_inference_processing_queue,
|
190
229
|
scope: @constant_resolution.scope,
|
191
230
|
clues: [clue],
|
192
231
|
location:
|
@@ -209,13 +248,7 @@ module Holistic::Ruby::Parser
|
|
209
248
|
start_column = node.location.start_column
|
210
249
|
end_column = node.location.end_column
|
211
250
|
|
212
|
-
|
213
|
-
# It sets the end_column lower than the start_column.
|
214
|
-
if start_line == end_line && start_column > end_column
|
215
|
-
start_column, end_column = end_column, start_column
|
216
|
-
end
|
217
|
-
|
218
|
-
::Holistic::Document::Location.new(file_path: file.path, start_line:, start_column:, end_line:, end_column:)
|
251
|
+
::Holistic::Document::Location.new(file:, start_line:, start_column:, end_line:, end_column:)
|
219
252
|
end
|
220
253
|
end
|
221
254
|
end
|
data/lib/holistic/ruby/parser.rb
CHANGED
@@ -1,34 +1,74 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Holistic::Ruby::Parser
|
4
|
-
HasValidSyntax = ->(
|
5
|
-
::SyntaxTree.parse(
|
4
|
+
HasValidSyntax = ->(content) do
|
5
|
+
::SyntaxTree.parse(content)
|
6
6
|
|
7
7
|
true
|
8
8
|
rescue ::SyntaxTree::Parser::ParseError
|
9
9
|
false
|
10
10
|
end
|
11
11
|
|
12
|
-
ParseFile = ->(application:,
|
13
|
-
program = ::SyntaxTree.parse(
|
12
|
+
ParseFile = ->(application:, file_path:, content:) do
|
13
|
+
program = ::SyntaxTree.parse(content)
|
14
14
|
|
15
|
-
constant_resolution = ConstantResolution.new(
|
16
|
-
|
17
|
-
|
18
|
-
)
|
15
|
+
constant_resolution = ConstantResolution.new(scope_repository: application.scopes)
|
16
|
+
|
17
|
+
file = ::Holistic::Document::File::Store.call(database: application.database, file_path:)
|
19
18
|
|
20
19
|
visitor = ProgramVisitor.new(application:, constant_resolution:, file:)
|
21
20
|
|
22
21
|
visitor.visit(program)
|
23
22
|
rescue ::SyntaxTree::Parser::ParseError
|
24
|
-
::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
|
25
57
|
end
|
26
58
|
|
27
59
|
ParseDirectory = ->(application:, directory_path:) do
|
60
|
+
performance_metrics = PerformanceMetrics.new
|
61
|
+
|
28
62
|
::Dir.glob("#{directory_path}/**/*.rb").map do |file_path|
|
29
|
-
|
63
|
+
performance_metrics.start_file_read!
|
64
|
+
content = ::File.read(file_path)
|
65
|
+
performance_metrics.end_file_read!
|
30
66
|
|
31
|
-
|
67
|
+
performance_metrics.start_parse!
|
68
|
+
ParseFile.call(application:, file_path:, content:)
|
69
|
+
performance_metrics.end_parse!
|
32
70
|
end
|
71
|
+
|
72
|
+
performance_metrics
|
33
73
|
end
|
34
74
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Holistic::Ruby::Reference
|
4
|
+
module Delete
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def call(database:, reference:)
|
8
|
+
database.delete(reference.identifier)
|
9
|
+
|
10
|
+
reference.relation(:reference_defined_in_file).delete!(reference.location.file)
|
11
|
+
reference.relation(:located_in_scope).delete!(reference.located_in_scope)
|
12
|
+
|
13
|
+
if reference.referenced_scope
|
14
|
+
reference.relation(:referenced_scope).delete!(reference.referenced_scope)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -8,9 +8,9 @@ module Holistic::Ruby::Reference
|
|
8
8
|
reference = application.references.find_by_cursor(cursor)
|
9
9
|
|
10
10
|
return :not_found if reference.nil?
|
11
|
-
return :could_not_find_referenced_scope if reference.
|
11
|
+
return :could_not_find_referenced_scope if reference.referenced_scope.nil?
|
12
12
|
|
13
|
-
referenced_scope =
|
13
|
+
referenced_scope = reference.referenced_scope
|
14
14
|
|
15
15
|
[:referenced_scope_found, {reference:, referenced_scope:}]
|
16
16
|
end
|
@@ -1,13 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Holistic::Ruby::Reference
|
4
|
-
Record
|
5
|
-
:
|
6
|
-
:clues
|
7
|
-
:
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
class Record < ::Holistic::Database::Node
|
5
|
+
def identifier = attr(:identifier)
|
6
|
+
def clues = attr(:clues)
|
7
|
+
def location = attr(:location)
|
8
|
+
|
9
|
+
def referenced_scope = has_one(:referenced_scope)
|
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
|
12
19
|
end
|
13
20
|
end
|
@@ -2,68 +2,46 @@
|
|
2
2
|
|
3
3
|
module Holistic::Ruby::Reference
|
4
4
|
class Repository
|
5
|
-
attr_reader :
|
5
|
+
attr_reader :database
|
6
6
|
|
7
|
-
|
8
|
-
|
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
|
-
})
|
7
|
+
def initialize(database:)
|
8
|
+
@database = database
|
25
9
|
end
|
26
10
|
|
27
11
|
def find_by_cursor(cursor)
|
28
|
-
|
29
|
-
|
12
|
+
@database.find(cursor.file_path)&.then do |file|
|
13
|
+
file.defines_references.find do |reference|
|
14
|
+
reference.location.contains?(cursor)
|
15
|
+
end
|
30
16
|
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
17
|
end
|
38
18
|
|
39
19
|
def list_references_in_file(file_path)
|
40
|
-
|
20
|
+
@database.find(file_path)&.defines_references || []
|
41
21
|
end
|
42
22
|
|
43
23
|
def list_references_to_scopes_in_file(scopes:, file_path:)
|
44
|
-
|
45
|
-
|
24
|
+
references = @database.find(file_path)&.defines_scopes&.flat_map do |scope|
|
25
|
+
scope.referenced_by.to_a
|
46
26
|
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
27
|
|
53
|
-
|
54
|
-
table.delete(identifier)
|
28
|
+
references || []
|
55
29
|
end
|
56
30
|
|
57
31
|
concerning :TestHelpers do
|
32
|
+
def all
|
33
|
+
@database.all.filter { _1.is_a?(Record) }
|
34
|
+
end
|
35
|
+
|
58
36
|
def find_reference_to(scope_name)
|
59
|
-
|
60
|
-
|
37
|
+
all.find do |node|
|
38
|
+
node.referenced_scope&.fully_qualified_name == scope_name || node.clues&.find { _1.to_s == scope_name }
|
61
39
|
end
|
62
40
|
end
|
63
41
|
|
64
42
|
def find_by_code_content(code_content)
|
65
|
-
|
66
|
-
|
43
|
+
all.find do |node|
|
44
|
+
node.clues&.find { _1.to_s == code_content }
|
67
45
|
end
|
68
46
|
end
|
69
47
|
end
|