rucoa 0.4.0 → 0.5.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +11 -2
  5. data/README.md +36 -5
  6. data/data/definitions_ruby_3_1 +0 -0
  7. data/images/diagnostics.gif +0 -0
  8. data/images/document-formatting.gif +0 -0
  9. data/images/document-symbol.gif +0 -0
  10. data/images/selection-ranges.gif +0 -0
  11. data/lib/rucoa/configuration.rb +58 -32
  12. data/lib/rucoa/definition_archiver.rb +29 -0
  13. data/lib/rucoa/definition_builders/rbs_constant_definition_builder.rb +44 -0
  14. data/lib/rucoa/definition_builders/rbs_method_definition_builder.rb +106 -0
  15. data/lib/rucoa/definition_builders/yard_method_definition_builder.rb +191 -0
  16. data/lib/rucoa/definition_builders.rb +9 -0
  17. data/lib/rucoa/definition_store.rb +63 -0
  18. data/lib/rucoa/definitions/base.rb +12 -0
  19. data/lib/rucoa/definitions/constant_definition.rb +51 -0
  20. data/lib/rucoa/definitions/method_definition.rb +126 -0
  21. data/lib/rucoa/definitions/method_parameter_definition.rb +30 -0
  22. data/lib/rucoa/definitions.rb +9 -0
  23. data/lib/rucoa/handlers/base.rb +8 -0
  24. data/lib/rucoa/handlers/exit_handler.rb +11 -0
  25. data/lib/rucoa/handlers/initialize_handler.rb +6 -0
  26. data/lib/rucoa/handlers/initialized_handler.rb +10 -0
  27. data/lib/rucoa/handlers/shutdown_handler.rb +12 -0
  28. data/lib/rucoa/handlers/text_document_did_change_handler.rb +1 -20
  29. data/lib/rucoa/handlers/text_document_did_open_handler.rb +11 -4
  30. data/lib/rucoa/handlers/text_document_document_symbol_handler.rb +1 -1
  31. data/lib/rucoa/handlers/text_document_range_formatting_handler.rb +1 -1
  32. data/lib/rucoa/handlers/text_document_signature_help_handler.rb +68 -0
  33. data/lib/rucoa/handlers.rb +3 -0
  34. data/lib/rucoa/node_inspector.rb +109 -0
  35. data/lib/rucoa/nodes/base.rb +30 -0
  36. data/lib/rucoa/nodes/def_node.rb +59 -0
  37. data/lib/rucoa/nodes/lvar_node.rb +25 -0
  38. data/lib/rucoa/nodes/send_node.rb +43 -0
  39. data/lib/rucoa/nodes.rb +1 -0
  40. data/lib/rucoa/parser_builder.rb +1 -0
  41. data/lib/rucoa/range.rb +64 -14
  42. data/lib/rucoa/rbs_document_loader.rb +43 -0
  43. data/lib/rucoa/rubocop_autocorrector.rb +1 -1
  44. data/lib/rucoa/rubocop_investigator.rb +1 -1
  45. data/lib/rucoa/server.rb +19 -1
  46. data/lib/rucoa/source.rb +54 -4
  47. data/lib/rucoa/source_store.rb +6 -10
  48. data/lib/rucoa/types/method_type.rb +23 -0
  49. data/lib/rucoa/types.rb +7 -0
  50. data/lib/rucoa/version.rb +1 -1
  51. data/lib/rucoa/yard_glob_document_loader.rb +47 -0
  52. data/lib/rucoa/yard_string_document_loader.rb +70 -0
  53. data/lib/rucoa.rb +9 -0
  54. data/rucoa.gemspec +1 -0
  55. metadata +42 -2
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ class DefinitionStore
5
+ # @return [Array<Rucoa::Definition::Base>]
6
+ attr_accessor :definitions
7
+
8
+ def initialize
9
+ @definitions = []
10
+ end
11
+
12
+ # @param source_path [String]
13
+ # @return [Array<Rucoa::Definition::Base>]
14
+ def update_definitions_defined_in(source_path, definitions:)
15
+ delete_definitions_defined_in(source_path)
16
+ @definitions += definitions
17
+ end
18
+
19
+ # @param full_qualified_name [String]
20
+ # @return [Array<Rucoa::Definitions::Base>]
21
+ def select_by_full_qualified_name(full_qualified_name)
22
+ @definitions.select do |definition|
23
+ definition.full_qualified_name == full_qualified_name
24
+ end
25
+ end
26
+
27
+ # @param namespace [String]
28
+ # @return [Array<Rucoa::Definitions::MethodDefinition>]
29
+ def method_definitions_of(namespace)
30
+ method_definitions.select do |method_definition|
31
+ method_definition.namespace == namespace
32
+ end
33
+ end
34
+
35
+ # @param namespace [String]
36
+ # @return [Array<Rucoa::Definitions::ConstantDefinition>] e.g. File::Separator, File::SEPARATOR, etc.
37
+ def constant_definitions_under(namespace)
38
+ constant_definitions.select do |constant_definition|
39
+ constant_definition.namespace == namespace
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Array<Rucoa::Definition::ConstantDefinition>]
46
+ def constant_definitions
47
+ @definitions.grep(Definitions::ConstantDefinition)
48
+ end
49
+
50
+ # @return [Array<Rucoa::Definition::MethodDefinition>]
51
+ def method_definitions
52
+ @definitions.grep(Definitions::MethodDefinition)
53
+ end
54
+
55
+ # @param source_path [String]
56
+ # @return [Array<Rucoa::Definition::Base>]
57
+ def delete_definitions_defined_in(source_path)
58
+ @definitions.delete_if do |definition|
59
+ definition.source_path == source_path
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Definitions
5
+ class Base
6
+ # @return [String]
7
+ def source_path
8
+ raise ::NotImplementedError
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Definitions
5
+ # Represents class definition, module definition, or constant assignment.
6
+ class ConstantDefinition < Base
7
+ # @return [String]
8
+ attr_reader :full_qualified_name
9
+
10
+ # @return [String]
11
+ attr_reader :source_path
12
+
13
+ # @param full_qualified_name [String]
14
+ # @param source_path [String]
15
+ def initialize(full_qualified_name:, source_path:)
16
+ super()
17
+ @full_qualified_name = full_qualified_name
18
+ @source_path = source_path
19
+ end
20
+
21
+ # @return [String]
22
+ # @example returns non-full-qualified name
23
+ # definition = Rucoa::Definitions::ConstantDefinition.new(
24
+ # full_qualified_name: 'Foo::Bar::Baz',
25
+ # source_path: '/path/to/foo/bar/baz.rb'
26
+ # )
27
+ # expect(definition.name).to eq('Baz')
28
+ def name
29
+ names.last
30
+ end
31
+
32
+ # @return [String]
33
+ # @example returns namespace
34
+ # definition = Rucoa::Definitions::ConstantDefinition.new(
35
+ # full_qualified_name: 'Foo::Bar::Baz',
36
+ # source_path: '/path/to/foo/bar/baz.rb'
37
+ # )
38
+ # expect(definition.namespace).to eq('Foo::Bar')
39
+ def namespace
40
+ names[..-2].join('::')
41
+ end
42
+
43
+ private
44
+
45
+ # @return [Array<String>]
46
+ def names
47
+ @names ||= full_qualified_name.split('::')
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Definitions
5
+ class MethodDefinition < Base
6
+ # @return [String, nil]
7
+ attr_reader :description
8
+
9
+ # @return [String]
10
+ attr_reader :method_name
11
+
12
+ # @return [String]
13
+ attr_reader :namespace
14
+
15
+ # @return [String]
16
+ attr_reader :source_path
17
+
18
+ # @param description [String, nil]
19
+ # @param kind [Symbol]
20
+ # @param method_name [String]
21
+ # @param namespace [String]
22
+ # @param source_path [String]
23
+ # @param types [Array<Rucoa::Types::MethodType>]
24
+ def initialize(
25
+ description:,
26
+ kind:,
27
+ method_name:,
28
+ namespace:,
29
+ source_path:,
30
+ types:
31
+ )
32
+ super()
33
+ @description = description
34
+ @kind = kind
35
+ @method_name = method_name
36
+ @namespace = namespace
37
+ @source_path = source_path
38
+ @types = types
39
+ end
40
+
41
+ # @return [String]
42
+ # @example returns qualified name of method
43
+ # method_definition = Rucoa::Definitions::MethodDefinition.new(
44
+ # description: nil,
45
+ # kind: :instance,
46
+ # method_name: 'foo',
47
+ # namespace: 'Foo::Bar',
48
+ # source_path: '/path/to/foo/bar.rb',
49
+ # types: []
50
+ # )
51
+ # expect(method_definition.full_qualified_name).to eq('Foo::Bar#foo')
52
+ def full_qualified_name
53
+ [
54
+ @namespace,
55
+ method_kind_symbol,
56
+ @method_name
57
+ ].join
58
+ end
59
+
60
+ # @todo
61
+ # @return [Array<Rucoa::Definitions::MethodParameter>]
62
+ def parameters
63
+ []
64
+ end
65
+
66
+ # @return [Array<String>]
67
+ # @example returns return types
68
+ # method_definition = Rucoa::Definitions::MethodDefinition.new(
69
+ # description: nil,
70
+ # kind: :instance,
71
+ # method_name: 'foo',
72
+ # namespace: 'Foo::Bar',
73
+ # source_path: '/path/to/foo/bar.rb',
74
+ # types: [
75
+ # Rucoa::Types::MethodType.new(
76
+ # parameters_string: '',
77
+ # return_type: 'String'
78
+ # )
79
+ # ]
80
+ # )
81
+ # expect(method_definition.return_types).to eq(%w[String])
82
+ def return_types
83
+ @types.map(&:return_type)
84
+ end
85
+
86
+ # @return [Array<String>]
87
+ # @example returns signature
88
+ # method_definition = Rucoa::Definitions::MethodDefinition.new(
89
+ # description: nil,
90
+ # kind: :instance,
91
+ # method_name: 'foo',
92
+ # namespace: 'Foo::Bar',
93
+ # source_path: '/path/to/foo/bar.rb',
94
+ # types: [
95
+ # Rucoa::Types::MethodType.new(
96
+ # parameters_string: '?::int base',
97
+ # return_type: 'String'
98
+ # )
99
+ # ]
100
+ # )
101
+ # expect(method_definition.signatures).to eq(['Foo::Bar#foo(?::int base) -> String'])
102
+ def signatures
103
+ @types.map do |type|
104
+ format(
105
+ '%<full_qualified_name>s(%<parameters>s) -> %<return_types>s',
106
+ full_qualified_name: full_qualified_name,
107
+ parameters: type.parameters_string,
108
+ return_types: type.return_type
109
+ )
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ # @return [String]
116
+ def method_kind_symbol
117
+ case @kind
118
+ when :instance
119
+ '#'
120
+ else
121
+ '.'
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Definitions
5
+ # Represents method parameter definition.
6
+ class MethodParameterDefinition < Base
7
+ # @return [String]
8
+ attr_reader :name
9
+
10
+ # @return [Array<String>]
11
+ attr_reader :types
12
+
13
+ # @param name [String]
14
+ # @param types [Array<String>]
15
+ def initialize(name:, types:)
16
+ super()
17
+ @name = name
18
+ @types = types
19
+ end
20
+
21
+ # @return [Hash{Symbol => Object}]
22
+ def to_hash
23
+ {
24
+ name: name,
25
+ types: types
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Definitions
5
+ autoload :Base, 'rucoa/definitions/base'
6
+ autoload :ConstantDefinition, 'rucoa/definitions/constant_definition'
7
+ autoload :MethodDefinition, 'rucoa/definitions/method_definition'
8
+ end
9
+ end
@@ -32,6 +32,9 @@ module Rucoa
32
32
  # @return [Hash]
33
33
  attr_reader :request
34
34
 
35
+ # @return [Rucoa::Server]
36
+ attr_reader :server
37
+
35
38
  # @param message [Hash]
36
39
  # @return [void]
37
40
  def respond(message)
@@ -46,6 +49,11 @@ module Rucoa
46
49
  @server.configuration
47
50
  end
48
51
 
52
+ # @return [Rucoa::DefinitionStore]
53
+ def definition_store
54
+ @server.definition_store
55
+ end
56
+
49
57
  # @return [Rucoa::SourceStore]
50
58
  def source_store
51
59
  @server.source_store
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class ExitHandler < Base
6
+ def call
7
+ server.finish
8
+ end
9
+ end
10
+ end
11
+ end
@@ -11,6 +11,12 @@ module Rucoa
11
11
  documentRangeFormattingProvider: true,
12
12
  documentSymbolProvider: true,
13
13
  selectionRangeProvider: true,
14
+ signatureHelpProvider: {
15
+ triggerCharacters: %w[
16
+ (
17
+ ,
18
+ ]
19
+ },
14
20
  textDocumentSync: {
15
21
  change: 1, # Full
16
22
  openClose: true
@@ -7,6 +7,16 @@ module Rucoa
7
7
 
8
8
  def call
9
9
  request_workspace_configuration
10
+ load_definitions
11
+ end
12
+
13
+ private
14
+
15
+ # @return [void]
16
+ def load_definitions
17
+ YardGlobDocumentLoader.call(
18
+ globs: ::YARD::Parser::SourceParser::DEFAULT_PATH_GLOB
19
+ )
10
20
  end
11
21
  end
12
22
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class ShutdownHandler < Base
6
+ def call
7
+ server.shutting_down = true
8
+ respond(nil)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -2,30 +2,11 @@
2
2
 
3
3
  module Rucoa
4
4
  module Handlers
5
- class TextDocumentDidChangeHandler < Base
6
- include HandlerConcerns::DiagnosticsPublishable
7
-
8
- def call
9
- update_source
10
- publish_diagnostics_on(uri)
11
- end
12
-
13
- private
14
-
15
- # @return [void]
16
- def update_source
17
- source_store.set(uri, text)
18
- end
19
-
5
+ class TextDocumentDidChangeHandler < TextDocumentDidOpenHandler
20
6
  # @return [String]
21
7
  def text
22
8
  request.dig('params', 'contentChanges')[0]['text']
23
9
  end
24
-
25
- # @return [String]
26
- def uri
27
- @uri ||= request.dig('params', 'textDocument', 'uri')
28
- end
29
10
  end
30
11
  end
31
12
  end
@@ -6,15 +6,22 @@ module Rucoa
6
6
  include HandlerConcerns::DiagnosticsPublishable
7
7
 
8
8
  def call
9
- update_source
9
+ source_store.update(source)
10
+ definition_store.update_definitions_defined_in(
11
+ source.path,
12
+ definitions: source.definitions
13
+ )
10
14
  publish_diagnostics_on(uri)
11
15
  end
12
16
 
13
17
  private
14
18
 
15
- # @return [void]
16
- def update_source
17
- source_store.set(uri, text)
19
+ # @return [Rucoa::Source]
20
+ def source
21
+ @source ||= Source.new(
22
+ content: text,
23
+ uri: uri
24
+ )
18
25
  end
19
26
 
20
27
  # @return [String]
@@ -212,7 +212,7 @@ module Rucoa
212
212
 
213
213
  # @return [Rucoa::Source]
214
214
  def source
215
- @source ||= @server.source_store.get(uri)
215
+ @source ||= source_store.get(uri)
216
216
  end
217
217
 
218
218
  # @return [String]
@@ -53,7 +53,7 @@ module Rucoa
53
53
  # @return [Array(Rucoa::Range, String)]
54
54
  def correctable_replacements
55
55
  replacements.select do |replacement_range, _|
56
- range.contains?(replacement_range)
56
+ range.contain?(replacement_range)
57
57
  end
58
58
  end
59
59
 
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rucoa
4
+ module Handlers
5
+ class TextDocumentSignatureHelpHandler < Base
6
+ def call
7
+ respond(signature_help)
8
+ end
9
+
10
+ private
11
+
12
+ # @return [Hash]
13
+ def signature_help
14
+ return unless respondable?
15
+
16
+ {
17
+ signatures: signature_informations
18
+ }
19
+ end
20
+
21
+ # @return [Boolean]
22
+ def respondable?
23
+ configuration.enables_signature_help? &&
24
+ node.is_a?(Nodes::SendNode)
25
+ end
26
+
27
+ # @return [Array<Hash>]
28
+ def signature_informations
29
+ method_definitions.map do |method_definition|
30
+ {
31
+ documentation: method_definition.description,
32
+ label: method_definition.signatures.join("\n")
33
+ }
34
+ end
35
+ end
36
+
37
+ # @return [Array<Rucoa::Definitions::MethodDefinition>]
38
+ def method_definitions
39
+ NodeInspector.new(
40
+ definition_store: definition_store,
41
+ node: node
42
+ ).method_definitions
43
+ end
44
+
45
+ # @return [Rucoa::Nodes::Base, nil]
46
+ def node
47
+ @node ||= source.node_at(position)
48
+ end
49
+
50
+ # @return [Rucoa::Position]
51
+ def position
52
+ Position.from_vscode_position(
53
+ request.dig('params', 'position')
54
+ )
55
+ end
56
+
57
+ # @return [Rucoa::Source]
58
+ def source
59
+ source_store.get(uri)
60
+ end
61
+
62
+ # @return [String]
63
+ def uri
64
+ request.dig('params', 'textDocument', 'uri')
65
+ end
66
+ end
67
+ end
68
+ end
@@ -3,8 +3,10 @@
3
3
  module Rucoa
4
4
  module Handlers
5
5
  autoload :Base, 'rucoa/handlers/base'
6
+ autoload :ExitHandler, 'rucoa/handlers/exit_handler'
6
7
  autoload :InitializeHandler, 'rucoa/handlers/initialize_handler'
7
8
  autoload :InitializedHandler, 'rucoa/handlers/initialized_handler'
9
+ autoload :ShutdownHandler, 'rucoa/handlers/shutdown_handler'
8
10
  autoload :TextDocumentCodeActionHandler, 'rucoa/handlers/text_document_code_action_handler'
9
11
  autoload :TextDocumentDidChangeHandler, 'rucoa/handlers/text_document_did_change_handler'
10
12
  autoload :TextDocumentDidOpenHandler, 'rucoa/handlers/text_document_did_open_handler'
@@ -12,6 +14,7 @@ module Rucoa
12
14
  autoload :TextDocumentFormattingHandler, 'rucoa/handlers/text_document_formatting_handler'
13
15
  autoload :TextDocumentRangeFormattingHandler, 'rucoa/handlers/text_document_range_formatting_handler'
14
16
  autoload :TextDocumentSelectionRangeHandler, 'rucoa/handlers/text_document_selection_range_handler'
17
+ autoload :TextDocumentSignatureHelpHandler, 'rucoa/handlers/text_document_signature_help_handler'
15
18
  autoload :WorkspaceDidChangeConfigurationHandler, 'rucoa/handlers/workspace_did_change_configuration_handler'
16
19
  end
17
20
  end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Rucoa
6
+ class NodeInspector
7
+ # @param definition_store [Rucoa::DefinitionStore]
8
+ # @param node [Rucoa::Node]
9
+ def initialize(definition_store:, node:)
10
+ @definition_store = definition_store
11
+ @node = node
12
+ end
13
+
14
+ # @return [Array<String>, nil]
15
+ def method_definitions
16
+ method_full_qualified_name&.flat_map do |full_qualified_name|
17
+ @definition_store.select_by_full_qualified_name(full_qualified_name)
18
+ end
19
+ end
20
+
21
+ # @return [Array<String>, nil]
22
+ def method_receiver_types
23
+ return unless @node.is_a?(Nodes::SendNode)
24
+
25
+ if @node.receiver
26
+ self.class.new(
27
+ definition_store: @definition_store,
28
+ node: @node.receiver
29
+ ).return_types
30
+ else
31
+ [@node.namespace]
32
+ end
33
+ end
34
+
35
+ # @return [Array<String>, nil]
36
+ def return_types
37
+ case @node.type
38
+ when :const
39
+ [@node.name]
40
+ when :lvar
41
+ return_types_for_lvar
42
+ when :send
43
+ return_types_for_send
44
+ when :array
45
+ %w[Array]
46
+ when :class, :module, :nil
47
+ %w[NilClass]
48
+ when :complex
49
+ %w[Complex]
50
+ when :def, :sym
51
+ %w[Symbol]
52
+ when :dstr, :str, :xstr
53
+ %w[String]
54
+ when :erange, :irange
55
+ %w[Range]
56
+ when false
57
+ %w[FalseClass]
58
+ when :float
59
+ %w[Float]
60
+ when :hash, :pair
61
+ %w[Hash]
62
+ when :int
63
+ %w[Integer]
64
+ when :rational
65
+ %w[Rational]
66
+ when :regexp, :regopt
67
+ %w[Regexp]
68
+ when true
69
+ %w[TrueClass]
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ # @return [Array<String>, nil]
76
+ def method_full_qualified_name
77
+ method_receiver_types&.map do |type|
78
+ [
79
+ type,
80
+ @node.name
81
+ ].join('#')
82
+ end
83
+ end
84
+
85
+ # @return [String, nil]
86
+ def nearest_def_full_qualified_name
87
+ @node.each_ancestor(:def).first&.full_qualified_name
88
+ end
89
+
90
+ # @return [Array<String>]
91
+ def return_types_for_lvar
92
+ full_qualified_name = nearest_def_full_qualified_name
93
+ return [] unless full_qualified_name
94
+
95
+ @definition_store.select_by_full_qualified_name(full_qualified_name).flat_map do |definition|
96
+ definition.parameters.select do |parameter|
97
+ parameter.name == @node.name
98
+ end.flat_map(&:types)
99
+ end
100
+ end
101
+
102
+ # @return [Array<String>]
103
+ def return_types_for_send
104
+ method_full_qualified_name.flat_map do |full_qualified_name|
105
+ @definition_store.select_by_full_qualified_name(full_qualified_name).flat_map(&:return_types)
106
+ end.uniq
107
+ end
108
+ end
109
+ end
@@ -85,6 +85,36 @@ module Rucoa
85
85
  Range.from_parser_range(location.expression).include?(position)
86
86
  end
87
87
 
88
+ # @note namespace is a String representation of `Module.nesting`.
89
+ # @return [String]
90
+ # @example returns namespace
91
+ # node = Rucoa::Source.new(
92
+ # content: <<~RUBY
93
+ # module Foo
94
+ # class Bar
95
+ # def baz
96
+ # end
97
+ # end
98
+ # end
99
+ # RUBY
100
+ # ).node_at(
101
+ # Rucoa::Position.new(
102
+ # column: 4,
103
+ # line: 3
104
+ # )
105
+ # )
106
+ # expect(node.namespace).to eq('Foo::Bar')
107
+ # @example returns "Object" when the node is not in a namespace
108
+ # node = Rucoa::Parser.call(
109
+ # <<~RUBY
110
+ # foo
111
+ # RUBY
112
+ # )
113
+ # expect(node.namespace).to eq('Object')
114
+ def namespace
115
+ each_ancestor(:module, :class).first&.full_qualified_name || 'Object'
116
+ end
117
+
88
118
  protected
89
119
 
90
120
  # Visit all descendants.