holistic-ruby 0.1.1 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 601bd799099f6615186f5d70dafab227a2f5d59e556aedec607784fc289b9f23
4
- data.tar.gz: 8684f179e1cf58b47d4cac81c75c10113c46f5578a9ab1ecd74eddcad2e20259
3
+ metadata.gz: abe4144d1f581c6151784152792604f799c0140d62c28f445f377f6039c40457
4
+ data.tar.gz: 6593b92046a8087f75c171280ae66401ff0bb1eb0f6dba9fa496df127bb39ee8
5
5
  SHA512:
6
- metadata.gz: 6cc033aaddc4b86f6b733c17c7cea974fabcc68132b7f7776dcbe9bdf4f6222ed52d6e58694e9988d624b693eab3d6abcf28389d5208b41221fee511e33c486e
7
- data.tar.gz: 54ebf824ec6771d1b58146a6c958a9271e229daa5c2d0c3a23830a873be8ffc523065f06a0240d821256712dd2c31c7136a7bf6e38fb7552eab4124bec24b5c8
6
+ metadata.gz: eb1f43eaa7f78c334da3f8d8f2fc782bfa662b94f8a5a6b5a8a24ab271cae26d62703bd722860dbecf637d9aae5d4595d1424257406d9d349f0d90a95ab702d4
7
+ data.tar.gz: f42ed13efc65779f8ef175c7127d03cf68bebd5df990a1744d714100efe639cad104b55592507d4737ecfa4d48e75d6b69da194a033f056740a16fde8bf3d65d
data/README.md CHANGED
@@ -32,4 +32,4 @@
32
32
 
33
33
  ## Why is it a toy language server?
34
34
 
35
- I use `holistic-ruby` on a daily basis in a faily large Ruby codebase. It seems stable and speedy. But... I built it for myself and I'm the only one using it :smile:
35
+ I use `holistic-ruby` on a daily basis while working in a fairly large Ruby codebase. It seems stable and speedy. But... I built it for myself and I'm the only one using it :smile:
@@ -5,6 +5,14 @@ class Holistic::Extensions::Events
5
5
  resolve_method_call_known_scope: {
6
6
  params: [:reference, :referenced_scope, :method_call_clue],
7
7
  output: ::Holistic::Ruby::Scope::Record
8
+ },
9
+ class_scope_registered: {
10
+ params: [:class_scope, :location],
11
+ output: nil
12
+ },
13
+ lambda_scope_registered: {
14
+ params: [:lambda_scope, :location],
15
+ output: nil
8
16
  }
9
17
  }.freeze
10
18
 
@@ -30,7 +38,7 @@ class Holistic::Extensions::Events
30
38
 
31
39
  result = @listeners[event].lazy.filter_map { |callback| callback.call(params) }.first
32
40
 
33
- raise UnexpectedOutput, result if result.present? && !result.is_a?(expected_output)
41
+ raise UnexpectedOutput, result if expected_output.present? && result.present? && !result.is_a?(expected_output)
34
42
 
35
43
  result
36
44
  end
@@ -16,28 +16,42 @@ module Holistic::Extensions::Ruby
16
16
  nil
17
17
  end
18
18
 
19
- ResolveStaticMethods = ->(application, params) do
20
- method_call_clue, referenced_scope = params[:method_call_clue], params[:referenced_scope]
21
-
22
- self_method_name = "#{referenced_scope.fully_qualified_name}#self.#{method_call_clue.method_name}"
23
-
24
- application.scopes.find_by_fully_qualified_name(self_method_name)
19
+ RegisterClassConstructor = ->(application, params) do
20
+ class_scope, location = params[:class_scope], params[:location]
21
+
22
+ has_overridden_new_method = class_scope.children.find { _1.class_method? && _1.name == "new" }
23
+
24
+ unless has_overridden_new_method
25
+ ::Holistic::Ruby::Scope::Register.call(
26
+ repository: application.scopes,
27
+ parent: class_scope,
28
+ kind: ::Holistic::Ruby::Scope::Kind::CLASS_METHOD,
29
+ name: "new",
30
+ location:
31
+ )
32
+ end
25
33
  end
26
34
 
27
- LAMBDA_METHODS = ["call", "curry"]
35
+ LAMBDA_METHODS = ["call", "curry"].freeze
28
36
 
29
- ResolveCallToLambda = ->(application, params) do
30
- method_call_clue, referenced_scope = params[:method_call_clue], params[:referenced_scope]
37
+ RegisterLambdaMethods = ->(application, params) do
38
+ lambda_scope, location = params[:lambda_scope], params[:location]
31
39
 
32
- if LAMBDA_METHODS.include?(method_call_clue.method_name) && referenced_scope.lambda?
33
- return referenced_scope
40
+ LAMBDA_METHODS.each do |method_name|
41
+ ::Holistic::Ruby::Scope::Register.call(
42
+ repository: application.scopes,
43
+ parent: lambda_scope,
44
+ kind: ::Holistic::Ruby::Scope::Kind::CLASS_METHOD,
45
+ name: method_name,
46
+ location:
47
+ )
34
48
  end
35
49
  end
36
50
 
37
51
  def register(application)
38
52
  application.extensions.bind(:resolve_method_call_known_scope, &ResolveClassConstructor.curry[application])
39
- application.extensions.bind(:resolve_method_call_known_scope, &ResolveStaticMethods.curry[application])
40
- application.extensions.bind(:resolve_method_call_known_scope, &ResolveCallToLambda.curry[application])
53
+ application.extensions.bind(:class_scope_registered, &RegisterClassConstructor.curry[application])
54
+ application.extensions.bind(:lambda_scope_registered, &RegisterLambdaMethods.curry[application])
41
55
  end
42
56
  end
43
57
  end
@@ -19,10 +19,11 @@ module Holistic::LanguageServer
19
19
  end
20
20
 
21
21
  code = document.expand_code(cursor)
22
- scope = request.application.scopes.find_inner_most_scope_by_cursor(cursor) || request.application.root_scope
23
-
22
+
24
23
  return request.respond_with(nil) if code.blank?
25
24
 
25
+ scope = request.application.scopes.find_inner_most_scope_by_cursor(cursor) || request.application.root_scope
26
+
26
27
  suggestions = ::Holistic::Ruby::Autocompletion::Suggest.call(code:, scope:)
27
28
 
28
29
  respond_with_suggestions(request, suggestions)
@@ -40,11 +41,12 @@ module Holistic::LanguageServer
40
41
 
41
42
  module CompletionKind
42
43
  FROM_SCOPE_TO_COMPLETION = {
43
- ::Holistic::Ruby::Scope::Kind::CLASS => Protocol::COMPLETION_ITEM_KIND_CLASS,
44
- ::Holistic::Ruby::Scope::Kind::LAMBDA => Protocol::COMPLETION_ITEM_KIND_FUNCTION,
45
- ::Holistic::Ruby::Scope::Kind::METHOD => Protocol::COMPLETION_ITEM_KIND_METHOD,
46
- ::Holistic::Ruby::Scope::Kind::MODULE => Protocol::COMPLETION_ITEM_KIND_MODULE,
47
- ::Holistic::Ruby::Scope::Kind::ROOT => Protocol::COMPLETION_ITEM_KIND_MODULE
44
+ ::Holistic::Ruby::Scope::Kind::CLASS => Protocol::COMPLETION_ITEM_KIND_CLASS,
45
+ ::Holistic::Ruby::Scope::Kind::LAMBDA => Protocol::COMPLETION_ITEM_KIND_FUNCTION,
46
+ ::Holistic::Ruby::Scope::Kind::CLASS_METHOD => Protocol::COMPLETION_ITEM_KIND_METHOD,
47
+ ::Holistic::Ruby::Scope::Kind::INSTANCE_METHOD => Protocol::COMPLETION_ITEM_KIND_METHOD,
48
+ ::Holistic::Ruby::Scope::Kind::MODULE => Protocol::COMPLETION_ITEM_KIND_MODULE,
49
+ ::Holistic::Ruby::Scope::Kind::ROOT => Protocol::COMPLETION_ITEM_KIND_MODULE
48
50
  }.freeze
49
51
 
50
52
  DEFAULT = Protocol::COMPLETION_ITEM_KIND_MODULE
@@ -7,9 +7,7 @@ module Holistic::LanguageServer
7
7
  def call(request)
8
8
  path = Format::FileUri.extract_path(request.message.param("textDocument", "uri"))
9
9
 
10
- unsaved_document = request.application.unsaved_documents.find(path)
11
-
12
- if unsaved_document.present?
10
+ request.application.unsaved_documents.find(path)&.then do |unsaved_document|
13
11
  request.application.unsaved_documents.delete(path)
14
12
 
15
13
  if unsaved_document.has_unsaved_changes?
@@ -13,12 +13,42 @@ module Holistic::Ruby::Autocompletion
13
13
  lookup_scope = lookup_scope.parent until lookup_scope.root?
14
14
  end
15
15
 
16
- suggest_namespaces_from_scope(code:, scope: lookup_scope)
16
+ if code.include?(".")
17
+ suggest_methods_from_scope(code:, scope: lookup_scope)
18
+ else
19
+ suggest_namespaces_from_scope(code:, scope: lookup_scope)
20
+ end
17
21
  end
18
22
 
19
23
  private
20
24
 
21
- NonMethods = ->(scope) { !scope.method? }
25
+ NonMethods = ->(scope) { !Methods[scope] }
26
+ Methods = ->(scope) { scope.instance_method? || scope.class_method? }
27
+
28
+ # Payment. <--
29
+ # Payment::Notifications. <--
30
+ # current_user.a
31
+ def suggest_methods_from_scope(code:, scope:)
32
+ suggestions = []
33
+
34
+ partial_namespaces = code.split(/(::|\.)/).compact_blank
35
+ method_to_autocomplete = partial_namespaces.pop.then { _1 == "." ? "" : _1 }
36
+ namespaces_to_resolve = partial_namespaces.reject { _1 == "::" || _1 == "." }
37
+
38
+ namespaces_to_resolve.each do |namespace_name|
39
+ scope = resolve_scope(name: namespace_name, from_scope: scope)
40
+
41
+ return suggestions if scope.nil?
42
+ end
43
+
44
+ scope.children.filter(&:class_method?).each do |method_scope|
45
+ if method_scope.name.start_with?(method_to_autocomplete)
46
+ suggestions << Suggestion.new(code: method_scope.name, kind: method_scope.kind)
47
+ end
48
+ end
49
+
50
+ suggestions
51
+ end
22
52
 
23
53
  def suggest_namespaces_from_scope(code:, scope:)
24
54
  suggestions = []
@@ -2,12 +2,18 @@
2
2
 
3
3
  module Holistic::Ruby::Parser
4
4
  class ConstantResolution
5
- attr_reader :scope_repository, :scope
5
+ module MethodRegistrationMode
6
+ INSTANCE_METHODS = :instance_methods
7
+ CLASS_METHODS = :class_methods
8
+ end
9
+
10
+ attr_reader :scope_repository, :scope, :method_registration_mode
6
11
 
7
12
  def initialize(scope_repository:, root_scope:)
8
13
  @scope_repository = scope_repository
9
14
  @scope = root_scope
10
15
  @constant_resolution_possibilities = ["::"]
16
+ @method_registration_mode = MethodRegistrationMode::INSTANCE_METHODS
11
17
  end
12
18
 
13
19
  def current
@@ -28,12 +34,17 @@ module Holistic::Ruby::Parser
28
34
  )
29
35
  end
30
36
 
37
+ registered_module_scope = @scope
38
+
31
39
  @constant_resolution_possibilities.unshift(@scope.fully_qualified_name)
32
40
 
33
41
  block.call
34
42
 
35
- @scope = starting_scope
43
+ change_method_registration_mode_to_instance_methods!
36
44
  @constant_resolution_possibilities.shift
45
+ @scope = starting_scope
46
+
47
+ registered_module_scope
37
48
  end
38
49
 
39
50
  def register_child_class(nesting:, location:, &block)
@@ -50,12 +61,52 @@ module Holistic::Ruby::Parser
50
61
  )
51
62
  end
52
63
 
64
+ registered_class_scope = @scope
65
+
53
66
  @constant_resolution_possibilities.unshift(@scope.fully_qualified_name)
54
67
 
55
68
  block.call
56
69
 
57
- @scope = starting_scope
70
+ change_method_registration_mode_to_instance_methods!
58
71
  @constant_resolution_possibilities.shift
72
+ @scope = starting_scope
73
+
74
+ registered_class_scope
75
+ end
76
+
77
+ def register_child_method(nesting:, location:, kind:, &block)
78
+ starting_scope = @scope
79
+
80
+ nesting.each do |name|
81
+ @scope =
82
+ ::Holistic::Ruby::Scope::Register.call(
83
+ repository: @scope_repository,
84
+ parent: @scope,
85
+ kind:,
86
+ name:,
87
+ location:
88
+ )
89
+ end
90
+
91
+ registered_method_scope = @scope
92
+
93
+ block.call
94
+
95
+ @scope = starting_scope
96
+
97
+ registered_method_scope
98
+ end
99
+
100
+ def method_registration_class_methods?
101
+ method_registration_mode == MethodRegistrationMode::CLASS_METHODS
102
+ end
103
+
104
+ def change_method_registration_mode_to_class_methods!
105
+ @method_registration_mode = MethodRegistrationMode::CLASS_METHODS
106
+ end
107
+
108
+ def change_method_registration_mode_to_instance_methods!
109
+ @method_registration_mode = MethodRegistrationMode::INSTANCE_METHODS
59
110
  end
60
111
  end
61
112
  end
@@ -5,7 +5,10 @@ module Holistic::Ruby::Parser
5
5
  extend self
6
6
 
7
7
  def call(application:, file:)
8
- references_to_recalculate = identify_references_to_recalculate_type_inference(application:, file:)
8
+ # TODO: do not build the AST twice
9
+ return unless HasValidSyntax[file:]
10
+
11
+ references_to_recalculate = identify_references_to_recalculate(application:, file:)
9
12
 
10
13
  unregister_scopes_in_file(application:, file:)
11
14
  unregsiter_references_in_file(application:, file:)
@@ -17,9 +20,9 @@ module Holistic::Ruby::Parser
17
20
 
18
21
  private
19
22
 
20
- def identify_references_to_recalculate_type_inference(application:, file:)
23
+ def identify_references_to_recalculate(application:, file:)
21
24
  # we need to reject references declared in the same because they're already going to be
22
- # unregistered and reparsed. If we don't do that, we'll end up with duplicated reference records.
25
+ # reparsed. If we don't do that, we'll end up with duplicated reference records.
23
26
 
24
27
  application.references
25
28
  .list_references_to_scopes_in_file(scopes: application.scopes, file_path: file.path)
@@ -14,6 +14,7 @@ module Holistic::Ruby::Parser
14
14
  when ::SyntaxTree::Const then nesting_syntax << node.value
15
15
  when ::SyntaxTree::VCall then append.(node.child_nodes.first)
16
16
  when ::SyntaxTree::Ident then nesting_syntax << node.value
17
+ when ::SyntaxTree::Kw then nesting_syntax << node.value
17
18
  when ::SyntaxTree::IVar then nesting_syntax << node.value
18
19
  when ::SyntaxTree::Period then nesting_syntax << "."
19
20
  when ::SyntaxTree::Paren then append.(node.child_nodes[1]) # node.child_nodes[0] is ::SyntaxTree::LParen
@@ -34,32 +34,43 @@ module Holistic::Ruby::Parser
34
34
  nesting = NestingSyntax.build(declaration_node)
35
35
  location = build_scope_location(declaration_node:, body_node:)
36
36
 
37
- @constant_resolution.register_child_class(nesting:, location:) do
37
+ class_scope = @constant_resolution.register_child_class(nesting:, location:) do
38
38
  visit(body_node)
39
39
  end
40
+
41
+ @application.extensions.dispatch(:class_scope_registered, { class_scope:, location: })
42
+ end
43
+
44
+ def visit_command(node)
45
+ command_name_node, args_node = node.child_nodes
46
+
47
+ if command_name_node.value == "extend"
48
+ is_extending_self = args_node.child_nodes.size == 1 && NestingSyntax.build(args_node.child_nodes.first).to_s == "self"
49
+
50
+ @constant_resolution.change_method_registration_mode_to_class_methods! if is_extending_self
51
+ end
52
+
53
+ visit(args_node)
40
54
  end
41
55
 
42
56
  def visit_def(node)
43
57
  instance_node, period_node, method_name_node, _params, body_node = node.child_nodes
44
58
 
45
- method_name =
46
- if instance_node.present? && period_node.present?
47
- instance_node.child_nodes.first.value + period_node.value + method_name_node.value
48
- else
49
- method_name_node.value
50
- end
51
-
59
+ nesting = NestingSyntax.new(method_name_node.value)
52
60
  location = build_scope_location(declaration_node: method_name_node, body_node:)
53
61
 
54
- ::Holistic::Ruby::Scope::Register.call(
55
- repository: @application.scopes,
56
- parent: @constant_resolution.scope,
57
- kind: ::Holistic::Ruby::Scope::Kind::METHOD,
58
- name: method_name,
59
- location:
60
- )
62
+ kind =
63
+ if instance_node.present? && instance_node.child_nodes.first.value == "self"
64
+ ::Holistic::Ruby::Scope::Kind::CLASS_METHOD
65
+ elsif @constant_resolution.method_registration_class_methods?
66
+ ::Holistic::Ruby::Scope::Kind::CLASS_METHOD
67
+ else
68
+ ::Holistic::Ruby::Scope::Kind::INSTANCE_METHOD
69
+ end
61
70
 
62
- visit(body_node)
71
+ @constant_resolution.register_child_method(nesting:, location:, kind:) do
72
+ visit(body_node)
73
+ end
63
74
  end
64
75
 
65
76
  def visit_vcall(node)
@@ -128,22 +139,27 @@ module Holistic::Ruby::Parser
128
139
  nesting = NestingSyntax.build(assign_node)
129
140
  location = build_scope_location(declaration_node: assign_node, body_node: block_node)
130
141
 
131
- @constant_resolution.register_child_class(nesting:, location:) do
142
+ class_scope = @constant_resolution.register_child_class(nesting:, location:) do
132
143
  visit(block_node)
133
144
  end
134
145
 
146
+ @application.extensions.dispatch(:class_scope_registered, { class_scope:, location: })
147
+
135
148
  return
136
149
  end
137
150
 
138
151
  location = build_scope_location(declaration_node: assign_node, body_node:)
139
152
 
140
- ::Holistic::Ruby::Scope::Register.call(
141
- repository: @application.scopes,
142
- parent: @constant_resolution.scope,
143
- kind: ::Holistic::Ruby::Scope::Kind::LAMBDA,
144
- name: assign_node.child_nodes.first.value,
145
- location:
146
- )
153
+ lambda_scope =
154
+ ::Holistic::Ruby::Scope::Register.call(
155
+ repository: @application.scopes,
156
+ parent: @constant_resolution.scope,
157
+ kind: ::Holistic::Ruby::Scope::Kind::LAMBDA,
158
+ name: assign_node.child_nodes.first.value,
159
+ location:
160
+ )
161
+
162
+ @application.extensions.dispatch(:lambda_scope_registered, { lambda_scope:, location: })
147
163
 
148
164
  visit(body_node)
149
165
  end
@@ -1,6 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Holistic::Ruby::Parser
4
+ HasValidSyntax = ->(file:) do
5
+ ::SyntaxTree.parse(file.read)
6
+
7
+ true
8
+ rescue ::SyntaxTree::Parser::ParseError
9
+ false
10
+ end
11
+
4
12
  ParseFile = ->(application:, file:) do
5
13
  program = ::SyntaxTree.parse(file.read)
6
14
 
@@ -2,10 +2,11 @@
2
2
 
3
3
  module Holistic::Ruby::Scope
4
4
  module Kind
5
- ROOT = :root
6
- MODULE = :module
7
- CLASS = :class
8
- METHOD = :method
9
- LAMBDA = :lambda
5
+ ROOT = :root
6
+ MODULE = :module
7
+ CLASS = :class
8
+ INSTANCE_METHOD = :instance_method
9
+ CLASS_METHOD = :class_method
10
+ LAMBDA = :lambda
10
11
  end
11
12
  end
@@ -16,10 +16,10 @@ module Holistic::Ruby::Scope
16
16
  return "" if root?
17
17
 
18
18
  separator =
19
- if kind == Kind::METHOD
20
- "#"
21
- else
22
- "::"
19
+ case kind
20
+ when Kind::INSTANCE_METHOD then "#"
21
+ when Kind::CLASS_METHOD then "."
22
+ else "::"
23
23
  end
24
24
 
25
25
  "#{parent.fully_qualified_name}#{separator}#{name}"
@@ -41,8 +41,12 @@ module Holistic::Ruby::Scope
41
41
  kind == Kind::MODULE
42
42
  end
43
43
 
44
- def method?
45
- kind == Kind::METHOD
44
+ def instance_method?
45
+ kind == Kind::INSTANCE_METHOD
46
+ end
47
+
48
+ def class_method?
49
+ kind == Kind::CLASS_METHOD
46
50
  end
47
51
 
48
52
  def descendant?(other)
@@ -37,7 +37,12 @@ module Holistic::Ruby::TypeInference
37
37
  end
38
38
 
39
39
  SolveMethodCallInCurrentScope = ->(application:, reference:, method_call_clue:) do
40
- referenced_method = resolve_method(application:, scope: reference.scope, method_name: method_call_clue.method_name)
40
+ referenced_method =
41
+ if reference.scope.class_method?
42
+ resolve_class_method(application:, scope: reference.scope.parent, method_name: method_call_clue.method_name)
43
+ elsif reference.scope.instance_method? && reference.scope.parent.present?
44
+ resolve_instance_method(application:, scope: reference.scope.parent, method_name: method_call_clue.method_name)
45
+ end
41
46
 
42
47
  return if referenced_method.nil?
43
48
 
@@ -53,8 +58,8 @@ module Holistic::Ruby::TypeInference
53
58
 
54
59
  return if referenced_scope.nil?
55
60
 
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: })
61
+ referenced_method = application.extensions.dispatch(:resolve_method_call_known_scope, { reference:, referenced_scope:, method_call_clue: })
62
+ referenced_method ||= resolve_class_method(application:, scope: referenced_scope, method_name: method_call_clue.method_name)
58
63
 
59
64
  Conclusion.done(referenced_method.fully_qualified_name) if referenced_method.present?
60
65
  end
@@ -101,10 +106,16 @@ module Holistic::Ruby::TypeInference
101
106
  nil
102
107
  end
103
108
 
104
- def resolve_method(application:, scope:, method_name:)
109
+ def resolve_instance_method(application:, scope:, method_name:)
105
110
  method_fully_qualified_name = "#{scope.fully_qualified_name}##{method_name}"
106
111
 
107
112
  application.scopes.find_by_fully_qualified_name(method_fully_qualified_name)
108
113
  end
114
+
115
+ def resolve_class_method(application:, scope:, method_name:)
116
+ method_fully_qualified_name = "#{scope.fully_qualified_name}.#{method_name}"
117
+
118
+ application.scopes.find_by_fully_qualified_name(method_fully_qualified_name)
119
+ end
109
120
  end
110
121
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Holistic
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: holistic-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luiz Vasconcellos
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-19 00:00:00.000000000 Z
11
+ date: 2023-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: syntax_tree