yoda-language-server 0.4.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 (171) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +8 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +78 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +85 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/client/atom/main.js +27 -0
  13. data/client/vscode/.gitignore +4 -0
  14. data/client/vscode/.vscode/launch.json +28 -0
  15. data/client/vscode/.vscode/settings.json +9 -0
  16. data/client/vscode/.vscode/tasks.json +20 -0
  17. data/client/vscode/.vscodeignore +8 -0
  18. data/client/vscode/CHANGELOG.md +7 -0
  19. data/client/vscode/README.md +65 -0
  20. data/client/vscode/package-lock.json +2688 -0
  21. data/client/vscode/package.json +39 -0
  22. data/client/vscode/src/extension.ts +42 -0
  23. data/client/vscode/src/test/extension.test.ts +22 -0
  24. data/client/vscode/src/test/index.ts +22 -0
  25. data/client/vscode/tsconfig.json +16 -0
  26. data/client/vscode/vsc-extension-quickstart.md +33 -0
  27. data/exe/yoda +27 -0
  28. data/lib/yoda.rb +11 -0
  29. data/lib/yoda/evaluation.rb +9 -0
  30. data/lib/yoda/evaluation/code_completion.rb +65 -0
  31. data/lib/yoda/evaluation/code_completion/base_provider.rb +57 -0
  32. data/lib/yoda/evaluation/code_completion/const_provider.rb +90 -0
  33. data/lib/yoda/evaluation/code_completion/method_provider.rb +82 -0
  34. data/lib/yoda/evaluation/code_completion/variable_provider.rb +18 -0
  35. data/lib/yoda/evaluation/comment_completion.rb +70 -0
  36. data/lib/yoda/evaluation/comment_completion/base_provider.rb +64 -0
  37. data/lib/yoda/evaluation/comment_completion/param_provider.rb +18 -0
  38. data/lib/yoda/evaluation/comment_completion/tag_provider.rb +41 -0
  39. data/lib/yoda/evaluation/comment_completion/type_provider.rb +58 -0
  40. data/lib/yoda/evaluation/current_node_explain.rb +70 -0
  41. data/lib/yoda/evaluation/evaluator.rb +103 -0
  42. data/lib/yoda/evaluation/signature_discovery.rb +83 -0
  43. data/lib/yoda/model.rb +12 -0
  44. data/lib/yoda/model/completion_item.rb +56 -0
  45. data/lib/yoda/model/descriptions.rb +10 -0
  46. data/lib/yoda/model/descriptions/base.rb +26 -0
  47. data/lib/yoda/model/descriptions/function_description.rb +40 -0
  48. data/lib/yoda/model/descriptions/value_description.rb +33 -0
  49. data/lib/yoda/model/descriptions/word_description.rb +32 -0
  50. data/lib/yoda/model/function_signatures.rb +13 -0
  51. data/lib/yoda/model/function_signatures/base.rb +68 -0
  52. data/lib/yoda/model/function_signatures/constructor.rb +70 -0
  53. data/lib/yoda/model/function_signatures/formatter.rb +82 -0
  54. data/lib/yoda/model/function_signatures/method.rb +67 -0
  55. data/lib/yoda/model/function_signatures/overload.rb +79 -0
  56. data/lib/yoda/model/function_signatures/parameter_list.rb +108 -0
  57. data/lib/yoda/model/function_signatures/type_builder.rb +101 -0
  58. data/lib/yoda/model/node_signature.rb +28 -0
  59. data/lib/yoda/model/path.rb +96 -0
  60. data/lib/yoda/model/scoped_path.rb +44 -0
  61. data/lib/yoda/model/types.rb +84 -0
  62. data/lib/yoda/model/types/any_type.rb +32 -0
  63. data/lib/yoda/model/types/base.rb +37 -0
  64. data/lib/yoda/model/types/duck_type.rb +41 -0
  65. data/lib/yoda/model/types/function_type.rb +174 -0
  66. data/lib/yoda/model/types/generic_type.rb +66 -0
  67. data/lib/yoda/model/types/instance_type.rb +42 -0
  68. data/lib/yoda/model/types/module_type.rb +42 -0
  69. data/lib/yoda/model/types/sequence_type.rb +53 -0
  70. data/lib/yoda/model/types/union_type.rb +56 -0
  71. data/lib/yoda/model/types/unknown_type.rb +40 -0
  72. data/lib/yoda/model/types/value_type.rb +58 -0
  73. data/lib/yoda/model/values.rb +9 -0
  74. data/lib/yoda/model/values/base.rb +32 -0
  75. data/lib/yoda/model/values/instance_value.rb +65 -0
  76. data/lib/yoda/model/values/module_value.rb +72 -0
  77. data/lib/yoda/parsing.rb +15 -0
  78. data/lib/yoda/parsing/ast_traversable.rb +18 -0
  79. data/lib/yoda/parsing/comment_tokenizer.rb +59 -0
  80. data/lib/yoda/parsing/location.rb +101 -0
  81. data/lib/yoda/parsing/node_objects.rb +10 -0
  82. data/lib/yoda/parsing/node_objects/const_node.rb +52 -0
  83. data/lib/yoda/parsing/node_objects/method_definition.rb +46 -0
  84. data/lib/yoda/parsing/node_objects/namespace.rb +104 -0
  85. data/lib/yoda/parsing/node_objects/send_node.rb +72 -0
  86. data/lib/yoda/parsing/parser.rb +27 -0
  87. data/lib/yoda/parsing/query.rb +11 -0
  88. data/lib/yoda/parsing/query/current_comment_query.rb +80 -0
  89. data/lib/yoda/parsing/query/current_comment_token_query.rb +153 -0
  90. data/lib/yoda/parsing/query/current_commenting_node_query.rb +68 -0
  91. data/lib/yoda/parsing/query/current_location_node_query.rb +51 -0
  92. data/lib/yoda/parsing/query/current_node_comment_query.rb +40 -0
  93. data/lib/yoda/parsing/range.rb +41 -0
  94. data/lib/yoda/parsing/scopes.rb +15 -0
  95. data/lib/yoda/parsing/scopes/base.rb +78 -0
  96. data/lib/yoda/parsing/scopes/builder.rb +60 -0
  97. data/lib/yoda/parsing/scopes/class_definition.rb +47 -0
  98. data/lib/yoda/parsing/scopes/meta_class_definition.rb +44 -0
  99. data/lib/yoda/parsing/scopes/meta_method_definition.rb +70 -0
  100. data/lib/yoda/parsing/scopes/method_definition.rb +69 -0
  101. data/lib/yoda/parsing/scopes/module_definition.rb +36 -0
  102. data/lib/yoda/parsing/scopes/root.rb +25 -0
  103. data/lib/yoda/parsing/source_analyzer.rb +59 -0
  104. data/lib/yoda/parsing/source_cutter.rb +231 -0
  105. data/lib/yoda/parsing/type_parser.rb +141 -0
  106. data/lib/yoda/runner.rb +6 -0
  107. data/lib/yoda/runner/infer.rb +50 -0
  108. data/lib/yoda/runner/setup.rb +26 -0
  109. data/lib/yoda/server.rb +191 -0
  110. data/lib/yoda/server/client_info.rb +98 -0
  111. data/lib/yoda/server/completion_provider.rb +78 -0
  112. data/lib/yoda/server/definition_provider.rb +36 -0
  113. data/lib/yoda/server/deserializer.rb +27 -0
  114. data/lib/yoda/server/hover_provider.rb +38 -0
  115. data/lib/yoda/server/signature_provider.rb +46 -0
  116. data/lib/yoda/store.rb +13 -0
  117. data/lib/yoda/store/actions.rb +10 -0
  118. data/lib/yoda/store/actions/import_core_library.rb +30 -0
  119. data/lib/yoda/store/actions/import_gems.rb +91 -0
  120. data/lib/yoda/store/actions/read_file.rb +36 -0
  121. data/lib/yoda/store/actions/read_project_files.rb +29 -0
  122. data/lib/yoda/store/adapters.rb +14 -0
  123. data/lib/yoda/store/adapters/base.rb +58 -0
  124. data/lib/yoda/store/adapters/leveldb_adapter.rb +80 -0
  125. data/lib/yoda/store/adapters/lmdb_adapter.rb +113 -0
  126. data/lib/yoda/store/objects.rb +46 -0
  127. data/lib/yoda/store/objects/addressable.rb +25 -0
  128. data/lib/yoda/store/objects/base.rb +116 -0
  129. data/lib/yoda/store/objects/class_object.rb +51 -0
  130. data/lib/yoda/store/objects/merger.rb +94 -0
  131. data/lib/yoda/store/objects/meta_class_object.rb +41 -0
  132. data/lib/yoda/store/objects/method_object.rb +94 -0
  133. data/lib/yoda/store/objects/module_object.rb +11 -0
  134. data/lib/yoda/store/objects/namespace_object.rb +67 -0
  135. data/lib/yoda/store/objects/overload.rb +51 -0
  136. data/lib/yoda/store/objects/patch.rb +46 -0
  137. data/lib/yoda/store/objects/patch_set.rb +80 -0
  138. data/lib/yoda/store/objects/tag.rb +62 -0
  139. data/lib/yoda/store/objects/value_object.rb +45 -0
  140. data/lib/yoda/store/project.rb +159 -0
  141. data/lib/yoda/store/query.rb +12 -0
  142. data/lib/yoda/store/query/associators.rb +10 -0
  143. data/lib/yoda/store/query/associators/associate_ancestors.rb +103 -0
  144. data/lib/yoda/store/query/associators/associate_methods.rb +38 -0
  145. data/lib/yoda/store/query/base.rb +16 -0
  146. data/lib/yoda/store/query/find_constant.rb +150 -0
  147. data/lib/yoda/store/query/find_meta_class.rb +18 -0
  148. data/lib/yoda/store/query/find_method.rb +74 -0
  149. data/lib/yoda/store/query/find_signature.rb +43 -0
  150. data/lib/yoda/store/registry.rb +67 -0
  151. data/lib/yoda/store/yard_importer.rb +260 -0
  152. data/lib/yoda/typing.rb +10 -0
  153. data/lib/yoda/typing/context.rb +96 -0
  154. data/lib/yoda/typing/environment.rb +35 -0
  155. data/lib/yoda/typing/evaluator.rb +256 -0
  156. data/lib/yoda/typing/lexical_scope.rb +26 -0
  157. data/lib/yoda/typing/relation.rb +15 -0
  158. data/lib/yoda/typing/traces.rb +9 -0
  159. data/lib/yoda/typing/traces/base.rb +26 -0
  160. data/lib/yoda/typing/traces/normal.rb +22 -0
  161. data/lib/yoda/typing/traces/send.rb +26 -0
  162. data/lib/yoda/version.rb +3 -0
  163. data/lib/yoda/yard_extensions.rb +11 -0
  164. data/lib/yoda/yard_extensions/sig_directive.rb +40 -0
  165. data/lib/yoda/yard_extensions/type_tag.rb +10 -0
  166. data/package.json +76 -0
  167. data/scripts/benchmark.rb +6 -0
  168. data/scripts/build_core_index.sh +16 -0
  169. data/yarn.lock +13 -0
  170. data/yoda-language-server.gemspec +40 -0
  171. metadata +424 -0
@@ -0,0 +1,72 @@
1
+ module Yoda
2
+ module Model
3
+ module Values
4
+ class ModuleValue < Base
5
+ attr_reader :registry, :namespace_object
6
+
7
+ # @param registry [Registry]
8
+ # @param namespace_object [::YARD::CodeObjects::NamespaceObject, ::YARD::CodeObjects::Proxy]
9
+ def initialize(registry, namespace_object)
10
+ fail ArgumentError, registry unless registry.is_a?(Registry)
11
+ fail ArgumentError, namespace_object unless namespace_object.is_a?(::YARD::CodeObjects::NamespaceObject) || namespace_object.is_a?(::YARD::CodeObjects::Proxy)
12
+ @registry = registry
13
+ @namespace_object = namespace_object
14
+ end
15
+
16
+ # @return [Array<Functions::Base>]
17
+ def methods(visibility: nil)
18
+ return [] if namespace_object.type == :proxy
19
+ @methods ||= begin
20
+ opts = { scope: :class, visibility: visibility }.compact
21
+ class_methods = namespace_object.meths(opts).map { |meth| Functions::Method.new(meth) } + constructors
22
+ class_method_names = Set.new(class_methods.map(&:name))
23
+ parent_meths = parent_methods(visibility: visibility).reject { |m| class_method_names.include?(m.name) }
24
+ class_methods + parent_meths
25
+ end
26
+ end
27
+
28
+ # @return [String]
29
+ def path
30
+ "#{namespace.path}.#{namespace.type == :class ? 'class' : 'module'}"
31
+ end
32
+
33
+ def namespace
34
+ namespace_object
35
+ end
36
+
37
+ # @param [String]
38
+ def docstring
39
+ namespace.docstring
40
+ end
41
+
42
+ # @return [Array<[String, Integer]>]
43
+ def defined_files
44
+ namespace.files
45
+ end
46
+
47
+ private
48
+
49
+ # @return [Array<Functions::Constructor>]
50
+ def constructors
51
+ [] unless namespace_object.type == :class
52
+ [] if namespace.child(name: :new, scope: :class)
53
+ [namespace.child(name: :initialize, scope: :instance)].map do |method_object|
54
+ Functions::Constructor.new(method_object)
55
+ end
56
+ end
57
+
58
+ # @return [Array<Functions::Base>]
59
+ def parent_methods(visibility: nil)
60
+ case namespace_object.type
61
+ when :class
62
+ InstanceValue.new(registry, registry.find_or_proxy('::Class')).methods(visibility: visibility)
63
+ when :module
64
+ InstanceValue.new(registry, registry.find_or_proxy('::Module')).methods(visibility: visibility)
65
+ else
66
+ []
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,15 @@
1
+ module Yoda
2
+ module Parsing
3
+ require 'yoda/parsing/ast_traversable'
4
+ require 'yoda/parsing/comment_tokenizer'
5
+ require 'yoda/parsing/parser'
6
+ require 'yoda/parsing/source_analyzer'
7
+ require 'yoda/parsing/node_objects'
8
+ require 'yoda/parsing/location'
9
+ require 'yoda/parsing/scopes'
10
+ require 'yoda/parsing/source_cutter'
11
+ require 'yoda/parsing/range'
12
+ require 'yoda/parsing/query'
13
+ require 'yoda/parsing/type_parser'
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module Yoda
2
+ module Parsing
3
+ module AstTraversable
4
+ # @param root_node [Array<::Parser::AST::Node>]
5
+ # @param current_location [Parser::Source::Map]
6
+ # @return [Array<::Parser::AST::Node>]
7
+ def calc_nodes_to_current_location(root_node, current_location)
8
+ nodes = [root_node]
9
+ node = root_node
10
+ while node && !node.children.empty?
11
+ node = node.children.find { |n| n.respond_to?(:location) && current_location.included?(n.location) }
12
+ nodes << node if node && node.is_a?(::Parser::AST::Node)
13
+ end
14
+ nodes
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,59 @@
1
+ require 'parslet'
2
+
3
+ module Yoda
4
+ module Parsing
5
+ class CommentTokenizer
6
+ # @return [Sequence]
7
+ def parse(str)
8
+ Generator.new.apply(Tokenizer.new.parse(str))
9
+ end
10
+
11
+ class Tokenizer < Parslet::Parser
12
+ rule(:space) { match('\s').repeat(1) }
13
+ rule(:space?) { space.maybe }
14
+
15
+ rule(:comment_begin) { str('#') }
16
+ rule(:tag) { str('@') >> (str('!').maybe >> match('[a-zA-Z0-9_-]').repeat) }
17
+
18
+ rule(:name) { (sign.absent? >> match['[:graph:]']).repeat(1) }
19
+ rule(:sign) { match['\[\]<>,{}\(\)'] }
20
+
21
+ rule(:comment_token) { sign | name }
22
+
23
+ rule(:base) { comment_begin.maybe >> space? >> tag.maybe.as(:tag) >> space? >> (comment_token.as(:token) >> space?).repeat.as(:tokens) }
24
+ root :base
25
+ end
26
+
27
+ class Generator < Parslet::Transform
28
+ rule(token: simple(:token)) { token }
29
+ rule(tag: simple(:tag), tokens: sequence(:tokens)) { Sequence.new(tag: tag, tokens: tokens) }
30
+ rule(tag: simple(:tag), tokens: simple(:token)) { Sequence.new(tag: tag, tokens: [token]) }
31
+ end
32
+
33
+ class Sequence
34
+ # @type Parslet::Slice | nil
35
+ attr_reader :tag
36
+
37
+ # @param tag [Parslet::Slice, nil]
38
+ # @param tokens [Array<Parslet::Slice>]
39
+ def initialize(tag: nil, tokens: [])
40
+ fail ArgumentError, tag if tag && !tag.is_a?(Parslet::Slice)
41
+ fail ArgumentError, tokens unless tokens.all? { |token| token.is_a?(Parslet::Slice) }
42
+
43
+ @tag = tag
44
+ @tokens = tokens
45
+ end
46
+
47
+ # @return [Array<Parslet::Slice>]
48
+ def all_tokens
49
+ @all_tokens ||= [@tag, *parameter_tokens].compact
50
+ end
51
+
52
+ # @return [Array<Parslet::Slice>]
53
+ def parameter_tokens
54
+ @tokens
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,101 @@
1
+ module Yoda
2
+ module Parsing
3
+ class Location
4
+ include Comparable
5
+
6
+ # @todo Make this 0-indexed.
7
+ # @return [Integer] 0-indexed column number.
8
+ attr_reader :row
9
+
10
+ # @return [Integer] 0-indexed column number.
11
+ attr_reader :column
12
+
13
+ # @param row [Integer] 1-indexed row number.
14
+ # @param column [Integer] 0-indexed column number.
15
+ def initialize(row:, column:)
16
+ @row = row
17
+ @column = column
18
+ end
19
+
20
+ # @param ast_location [Parser::Source::Map, Parser::Source::Range]
21
+ # @return [Location, nil]
22
+ def self.of_ast_location(ast_location)
23
+ return nil unless valid_location?(ast_location)
24
+ Location.new(row: ast_location.line, column: ast_location.column)
25
+ end
26
+
27
+ # @param line [Integer]
28
+ # @param character [Integer]
29
+ # @return [Location]
30
+ def self.of_language_server_protocol_position(line:, character:)
31
+ new(row: line + 1, column: character)
32
+ end
33
+
34
+ # @param location [Parser::Source::Range, Parser::Source::Map, Object]
35
+ def self.valid_location?(location)
36
+ return false if !location.is_a?(::Parser::Source::Range) && !location.is_a?(::Parser::Source::Map)
37
+ return false if location.is_a?(::Parser::Source::Map) && !location.expression
38
+ true
39
+ end
40
+
41
+ # @param location [Parser::Source::Range, Parser::Source::Map]
42
+ def index_of(source)
43
+ (source.split("\n").slice(0, row - 1) || []).map(&:length).reduce(0, &:+) + column
44
+ end
45
+
46
+ # @param location [Parser::Source::Range, Parser::Source::Map]
47
+ def included?(location)
48
+ return false unless self.class.valid_location?(location)
49
+ after_begin(location) && before_last(location)
50
+ end
51
+
52
+ # @param location [Parser::Source::Range, Parser::Source::Map]
53
+ def later_than?(location)
54
+ move(row: 0, column: -1).after_begin(location)
55
+ end
56
+
57
+ # @param location [Parser::Source::Range, Parser::Source::Map]
58
+ def after_begin(location)
59
+ return false unless self.class.valid_location?(location)
60
+ (location.line == row && location.column <= column) || location.line < row
61
+ end
62
+
63
+ # @param location [Parser::Source::Range, Parser::Source::Map]
64
+ def before_last(location)
65
+ return false unless self.class.valid_location?(location)
66
+ (location.last_line == row && column <= location.last_column ) || row < location.last_line
67
+ end
68
+
69
+ # @param location [Parser::Source::Range, Parser::Source::Map]
70
+ # @return [{Symbol => Numerical}]
71
+ def offset_from_begin(location)
72
+ fail ArgumentError, location unless self.class.valid_location?(location)
73
+ { line: row - location.line, column: column - location.column }
74
+ end
75
+
76
+ # @param row [Integer]
77
+ # @param column [Integer]
78
+ # @return [Location]
79
+ def move(row:, column:)
80
+ self.class.new(row: @row + row, column: @column + column)
81
+ end
82
+
83
+ # @return [{Symbol => Integer}]
84
+ def to_language_server_protocol_range
85
+ { line: row - 1, character: column }
86
+ end
87
+
88
+ def to_s
89
+ "(#{row}, #{column})"
90
+ end
91
+
92
+ # @param another [Location]
93
+ # @return [Integer]
94
+ def <=>(another)
95
+ return 0 if row == another.row && column == another.column
96
+ return 1 if (row == another.row && column >= another.column) || row > another.row
97
+ -1
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,10 @@
1
+ module Yoda
2
+ module Parsing
3
+ module NodeObjects
4
+ require 'yoda/parsing/node_objects/const_node'
5
+ require 'yoda/parsing/node_objects/send_node'
6
+ require 'yoda/parsing/node_objects/method_definition'
7
+ require 'yoda/parsing/node_objects/namespace'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,52 @@
1
+ module Yoda
2
+ module Parsing
3
+ module NodeObjects
4
+ class ConstNode
5
+ # @param node [::AST::Node]
6
+ attr_reader :node
7
+
8
+ # @param node [::AST::Node]
9
+ def initialize(node)
10
+ fail ArgumentError, node unless node.is_a?(::AST::Node) && node.type == :const
11
+ @node = node
12
+ end
13
+
14
+ # @return [ConstNode, nil]
15
+ def parent_const
16
+ node.children.first && node.children.first.type == :const ? ConstNode.new(node.children.first) : nil
17
+ end
18
+
19
+ # @return [true, false]
20
+ def absolute?
21
+ node.children.first == :cbase
22
+ end
23
+
24
+ # @param location [Location]
25
+ # @return [true, false]
26
+ def just_after_separator?(location)
27
+ return false unless node.location.double_colon
28
+ location == Location.of_ast_location(node.location.double_colon.end)
29
+ end
30
+
31
+ # @return [Model::Path]
32
+ def to_path
33
+ Model::Path.new(to_s)
34
+ end
35
+
36
+ # @param base [String, Symbol, nil]
37
+ # @return [String]
38
+ def to_s(base = nil)
39
+ fail ArgumentError, base unless !base || base.is_a?(String) || base.is_a?(Symbol)
40
+ paths = []
41
+ looking_node = node
42
+ while true
43
+ return (base ? base.to_s + '::' : '') + paths.join('::') unless looking_node
44
+ return '::' + paths.join('::') if looking_node.type == :cbase
45
+ paths.unshift(looking_node.children[1])
46
+ looking_node = looking_node.children[0]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,46 @@
1
+ module Yoda
2
+ module Parsing
3
+ module NodeObjects
4
+ class MethodDefinition
5
+
6
+ # @return [::Parser::AST::Node]
7
+ attr_reader :node
8
+
9
+ # @return [Namespace]
10
+ attr_reader :namespace
11
+
12
+ # @param node [::Parser::AST::Node]
13
+ # @param namespace [Namespace]
14
+ def initialize(node, namespace)
15
+ fail ArgumentError, node unless node.is_a?(::Parser::AST::Node)
16
+ fail ArgumentError, namespace unless namespace.is_a?(Namespace)
17
+ @node = node
18
+ @namespace = namespace
19
+ end
20
+
21
+ # @return [Symbol]
22
+ def name
23
+ node.children[-3]
24
+ end
25
+
26
+ def arg_node
27
+ node.children[-2]
28
+ end
29
+
30
+ def body_node
31
+ node.children[-1]
32
+ end
33
+
34
+ # @return [String]
35
+ def full_name
36
+ "#{namespace.full_name}##{name}"
37
+ end
38
+
39
+ # @return [String]
40
+ def namespace_name
41
+ namespace.full_name
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,104 @@
1
+ module Yoda
2
+ module Parsing
3
+ module NodeObjects
4
+ class Namespace
5
+ include AstTraversable
6
+
7
+ # @return [::Parser::AST::Node]
8
+ attr_reader :node
9
+
10
+ # @return [Namespace, nil]
11
+ attr_reader :parent
12
+
13
+ # @param node [::Parser::AST::Node]
14
+ # @param parent [Namespace, nil]
15
+ def initialize(node, parent = nil)
16
+ fail ArgumentError, node unless node.is_a?(::Parser::AST::Node)
17
+ fail ArgumentError, parent unless !parent || parent.is_a?(Namespace)
18
+ @node = node
19
+ @parent = parent
20
+ end
21
+
22
+ # @return [::Parser::AST::Node]
23
+ def body_node
24
+ return node if type == :root
25
+ return node.children[2] if type == :class
26
+ node.children[1]
27
+ end
28
+
29
+ # @return [::Parser::AST::Node, nil]
30
+ def const_node
31
+ %i(root sclass).include?(type) ? nil : node.children[0]
32
+ end
33
+
34
+ # @return [Namespace]
35
+ def child_namespaces
36
+ @child_namespaces ||= child_nodes_of(body_node).select { |node| %i(module class sclass).include?(node.type) }.map { |node| self.class.new(node, self) }
37
+ end
38
+
39
+ # @return [Wrappers::MethodNodeWrapper]
40
+ def child_methods
41
+ @child_methods ||= child_nodes_of(body_node).select { |node| %i(def defs).include?(node.type) }.map { |node| MethodDefinition.new(node, self) }
42
+ end
43
+
44
+ def type
45
+ @type ||= begin
46
+ return node.type if %i(module class sclass).include?(node.type)
47
+ :root
48
+ end
49
+ end
50
+
51
+ # @return [String]
52
+ def path
53
+ name = full_name
54
+ name == :root ? '' : name
55
+ end
56
+
57
+ # @return [true, false]
58
+ def root?
59
+ type == :root
60
+ end
61
+
62
+ # @return [String, Symbol]
63
+ def full_name
64
+ return :root if type == :root
65
+ parent_name = parent && !parent.root? ? parent.full_name : ''
66
+ const_node ? ConstNode.new(const_node).to_s(parent_name) : parent_name
67
+ end
68
+
69
+ # @return [Array<String>]
70
+ def paths_from_root
71
+ if root?
72
+ [path]
73
+ else
74
+ parent ? parent.paths_from_root + [path] : ['', path]
75
+ end
76
+ end
77
+
78
+ # @param location [Location]
79
+ # @return [Namespace, nil]
80
+ def calc_current_location_namespace(location)
81
+ return nil unless location.included?(node.location)
82
+ including_child_namespace = child_namespaces.find { |namespace| location.included?(namespace.node.location) }
83
+ including_child_namespace ? including_child_namespace.calc_current_location_namespace(location) : self
84
+ end
85
+
86
+ # @param location [Location]
87
+ # @return [MethodNodeWrapper, nil]
88
+ def calc_current_location_method(location)
89
+ namespace = calc_current_location_namespace(location)
90
+ namespace && namespace.child_methods.find { |method| location.included?(method.node.location) }
91
+ end
92
+
93
+ private
94
+
95
+ def child_nodes_of(node)
96
+ # @todo evaluate nodes in the namespace
97
+ return [] unless node
98
+ return node.children.map { |child| child_nodes_of(child) }.flatten.compact if node.type == :begin
99
+ [node]
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end