holistic-ruby 0.1.1 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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