ruby-lsp 0.27.0.beta2 → 0.27.0.beta4

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +0 -46
  4. data/exe/ruby-lsp-check +0 -15
  5. data/lib/ruby_lsp/base_server.rb +2 -0
  6. data/lib/ruby_lsp/global_state.rb +0 -5
  7. data/lib/ruby_lsp/internal.rb +2 -1
  8. data/lib/ruby_lsp/listeners/code_lens.rb +1 -1
  9. data/lib/ruby_lsp/listeners/completion.rb +244 -383
  10. data/lib/ruby_lsp/listeners/definition.rb +6 -9
  11. data/lib/ruby_lsp/listeners/hover.rb +11 -9
  12. data/lib/ruby_lsp/listeners/signature_help.rb +11 -12
  13. data/lib/ruby_lsp/listeners/test_discovery.rb +17 -1
  14. data/lib/ruby_lsp/listeners/test_style.rb +1 -1
  15. data/lib/ruby_lsp/requests/completion_resolve.rb +49 -29
  16. data/lib/ruby_lsp/requests/references.rb +21 -7
  17. data/lib/ruby_lsp/requests/rename.rb +1 -1
  18. data/lib/ruby_lsp/requests/support/common.rb +79 -68
  19. data/lib/ruby_lsp/ruby_document.rb +0 -73
  20. data/lib/ruby_lsp/rubydex/declaration.rb +128 -2
  21. data/lib/ruby_lsp/rubydex/definition.rb +16 -0
  22. data/lib/ruby_lsp/rubydex/signature.rb +107 -0
  23. data/lib/ruby_lsp/scripts/compose_bundle.rb +1 -1
  24. data/lib/ruby_lsp/server.rb +40 -168
  25. data/lib/ruby_lsp/setup_bundler.rb +1 -1
  26. data/lib/ruby_lsp/test_helper.rb +0 -1
  27. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +1 -1
  28. data/lib/ruby_lsp/type_inferrer.rb +58 -40
  29. metadata +14 -17
  30. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +0 -276
  31. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +0 -1101
  32. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +0 -44
  33. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +0 -605
  34. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +0 -1077
  35. data/lib/ruby_indexer/lib/ruby_indexer/location.rb +0 -37
  36. data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +0 -149
  37. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +0 -294
  38. data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +0 -32
  39. data/lib/ruby_indexer/ruby_indexer.rb +0 -19
  40. /data/lib/{ruby_indexer/lib/ruby_indexer → ruby_lsp}/uri.rb +0 -0
@@ -20,13 +20,67 @@ module RubyLsp
20
20
  when Prism::InstanceVariableReadNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableWriteNode,
21
21
  Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableTargetNode,
22
22
  Prism::SuperNode, Prism::ForwardingSuperNode, Prism::DefNode
23
- self_receiver_handling(node_context)
23
+ infer_self_type(node_context)
24
24
  when Prism::ClassVariableAndWriteNode, Prism::ClassVariableWriteNode, Prism::ClassVariableOperatorWriteNode,
25
25
  Prism::ClassVariableOrWriteNode, Prism::ClassVariableReadNode, Prism::ClassVariableTargetNode
26
26
  infer_receiver_for_class_variables(node_context)
27
27
  end
28
28
  end
29
29
 
30
+ # Infers the type of `self` in the given context. Unlike `infer_receiver_type`, this does not depend on the node
31
+ # being completed — it's derived purely from the lexical nesting and the surrounding method's receiver. This matters
32
+ # because methods and instance variables are attached to the type of `self`, which doesn't always match the lexical
33
+ # scope (e.g. inside `def self.foo` or a class/module body).
34
+ #
35
+ #: (NodeContext node_context) -> Type?
36
+ def infer_self_type(node_context)
37
+ nesting = node_context.nesting
38
+ # If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
39
+ # inherits from Object
40
+ return Type.new("Object") if nesting.empty?
41
+
42
+ surrounding_method = node_context.surrounding_method
43
+
44
+ if surrounding_method
45
+ receiver_name = surrounding_method.receiver
46
+
47
+ case receiver_name
48
+ when "self"
49
+ # `def self.foo` — self is the singleton of the enclosing class/module
50
+ return resolve_singleton_type_from_nesting(nesting)
51
+ when "none"
52
+ # Instance method — self is an instance of the enclosing class/module
53
+ return resolve_type_from_nesting(nesting)
54
+ when nil
55
+ # Dynamic receiver that we cannot handle
56
+ return
57
+ else
58
+ # Explicit constant receiver (e.g. `def Bar.baz`) — self is that constant's singleton class
59
+ resolved = resolve_receiver_singleton_type(receiver_name, nesting)
60
+ return resolved if resolved
61
+
62
+ return resolve_type_from_nesting(nesting)
63
+ end
64
+ end
65
+
66
+ # If we're not inside a method, then we're inside the body of a class or module, which is a singleton
67
+ # context. Resolve through the graph to get the correct fully qualified name
68
+ resolve_singleton_type_from_nesting(nesting)
69
+ end
70
+
71
+ # The type of `self`, which may or may not match the current lexical scope. For example, if we have `def Bar.foo` or
72
+ # if something else mutates the type
73
+ #
74
+ #: (NodeContext) -> String?
75
+ def self_receiver_name(node_context)
76
+ return if node_context.nesting.empty?
77
+
78
+ name = infer_self_type(node_context)&.name
79
+ return unless name
80
+
81
+ name if @graph[name]
82
+ end
83
+
30
84
  private
31
85
 
32
86
  #: (Prism::CallNode node, NodeContext node_context) -> Type?
@@ -49,7 +103,7 @@ module RubyLsp
49
103
 
50
104
  case receiver
51
105
  when Prism::SelfNode, nil
52
- self_receiver_handling(node_context)
106
+ infer_self_type(node_context)
53
107
  when Prism::StringNode
54
108
  Type.new("String")
55
109
  when Prism::SymbolNode
@@ -78,7 +132,7 @@ module RubyLsp
78
132
  # When the receiver is a constant reference, we have to try to resolve it to figure out the right
79
133
  # receiver. But since the invocation is directly on the constant, that's the singleton context of that
80
134
  # class/module
81
- receiver_name = RubyIndexer::Index.constant_name(receiver)
135
+ receiver_name = Requests::Support::Common.constant_name(receiver)
82
136
  return unless receiver_name
83
137
 
84
138
  resolved_receiver = @graph.resolve_constant(receiver_name, node_context.nesting)
@@ -124,47 +178,11 @@ module RubyLsp
124
178
 
125
179
  declaration = @graph.resolve_constant(guessed_name, nesting)
126
180
  declaration ||= @graph.search(guessed_name).first
127
- return unless declaration
181
+ return unless declaration.is_a?(Rubydex::Namespace)
128
182
 
129
183
  GuessedType.new(declaration.name)
130
184
  end
131
185
 
132
- #: (NodeContext node_context) -> Type?
133
- def self_receiver_handling(node_context)
134
- nesting = node_context.nesting
135
- # If we're at the top level, then the invocation is happening on `<main>`, which is a special singleton that
136
- # inherits from Object
137
- return Type.new("Object") if nesting.empty?
138
-
139
- surrounding_method = node_context.surrounding_method
140
-
141
- if surrounding_method
142
- receiver_name = surrounding_method.receiver
143
-
144
- case receiver_name
145
- when "self"
146
- # `def self.foo` — self is the singleton of the enclosing class/module
147
- return resolve_singleton_type_from_nesting(nesting)
148
- when "none"
149
- # Instance method — self is an instance of the enclosing class/module
150
- return resolve_type_from_nesting(nesting)
151
- when nil
152
- # Dynamic receiver that we cannot handle
153
- return
154
- else
155
- # Explicit constant receiver (e.g. `def Bar.baz`) — self is that constant's singleton class
156
- resolved = resolve_receiver_singleton_type(receiver_name, nesting)
157
- return resolved if resolved
158
-
159
- return resolve_type_from_nesting(nesting)
160
- end
161
- end
162
-
163
- # If we're not inside a method, then we're inside the body of a class or module, which is a singleton
164
- # context. Resolve through the graph to get the correct fully qualified name
165
- resolve_singleton_type_from_nesting(nesting)
166
- end
167
-
168
186
  # Resolves the fully qualified name of the innermost constant from the nesting and returns it as a type.
169
187
  # For instance methods, the nesting won't have singleton markers, so the result is an instance type.
170
188
  # For `def self.` methods, the nesting includes a singleton marker, which is preserved in the result.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.27.0.beta2
4
+ version: 0.27.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -67,16 +67,22 @@ dependencies:
67
67
  name: rubydex
68
68
  requirement: !ruby/object:Gem::Requirement
69
69
  requirements:
70
- - - "~>"
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 0.2.7
73
+ - - "<"
71
74
  - !ruby/object:Gem::Version
72
- version: 0.1.0.beta1
75
+ version: 0.3.0
73
76
  type: :runtime
74
77
  prerelease: false
75
78
  version_requirements: !ruby/object:Gem::Requirement
76
79
  requirements:
77
- - - "~>"
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.2.7
83
+ - - "<"
78
84
  - !ruby/object:Gem::Version
79
- version: 0.1.0.beta1
85
+ version: 0.3.0
80
86
  description: An opinionated language server for Ruby
81
87
  email:
82
88
  - ruby@shopify.com
@@ -98,17 +104,6 @@ files:
98
104
  - lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
99
105
  - lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
100
106
  - lib/ruby-lsp.rb
101
- - lib/ruby_indexer/lib/ruby_indexer/configuration.rb
102
- - lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb
103
- - lib/ruby_indexer/lib/ruby_indexer/enhancement.rb
104
- - lib/ruby_indexer/lib/ruby_indexer/entry.rb
105
- - lib/ruby_indexer/lib/ruby_indexer/index.rb
106
- - lib/ruby_indexer/lib/ruby_indexer/location.rb
107
- - lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb
108
- - lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb
109
- - lib/ruby_indexer/lib/ruby_indexer/uri.rb
110
- - lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb
111
- - lib/ruby_indexer/ruby_indexer.rb
112
107
  - lib/ruby_lsp/addon.rb
113
108
  - lib/ruby_lsp/base_server.rb
114
109
  - lib/ruby_lsp/client_capabilities.rb
@@ -184,6 +179,7 @@ files:
184
179
  - lib/ruby_lsp/rubydex/declaration.rb
185
180
  - lib/ruby_lsp/rubydex/definition.rb
186
181
  - lib/ruby_lsp/rubydex/reference.rb
182
+ - lib/ruby_lsp/rubydex/signature.rb
187
183
  - lib/ruby_lsp/scope.rb
188
184
  - lib/ruby_lsp/scripts/compose_bundle.rb
189
185
  - lib/ruby_lsp/scripts/compose_bundle_windows.rb
@@ -195,6 +191,7 @@ files:
195
191
  - lib/ruby_lsp/test_reporters/minitest_reporter.rb
196
192
  - lib/ruby_lsp/test_reporters/test_unit_reporter.rb
197
193
  - lib/ruby_lsp/type_inferrer.rb
194
+ - lib/ruby_lsp/uri.rb
198
195
  - lib/ruby_lsp/utils.rb
199
196
  homepage: https://github.com/Shopify/ruby-lsp
200
197
  licenses:
@@ -216,7 +213,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
216
213
  - !ruby/object:Gem::Version
217
214
  version: '0'
218
215
  requirements: []
219
- rubygems_version: 4.0.3
216
+ rubygems_version: 4.0.10
220
217
  specification_version: 4
221
218
  summary: An opinionated language server for Ruby
222
219
  test_files: []
@@ -1,276 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module RubyIndexer
5
- class Configuration
6
- CONFIGURATION_SCHEMA = {
7
- "excluded_gems" => Array,
8
- "included_gems" => Array,
9
- "excluded_patterns" => Array,
10
- "included_patterns" => Array,
11
- "excluded_magic_comments" => Array,
12
- }.freeze #: Hash[String, untyped]
13
-
14
- #: String
15
- attr_writer :workspace_path
16
-
17
- #: Encoding
18
- attr_accessor :encoding
19
-
20
- #: -> void
21
- def initialize
22
- @workspace_path = Dir.pwd #: String
23
- @encoding = Encoding::UTF_8 #: Encoding
24
- @excluded_gems = initial_excluded_gems #: Array[String]
25
- @included_gems = [] #: Array[String]
26
-
27
- @excluded_patterns = [
28
- "**/{test,spec}/**/{*_test.rb,test_*.rb,*_spec.rb}",
29
- "**/fixtures/**/*",
30
- ] #: Array[String]
31
-
32
- path = Bundler.settings["path"]
33
- if path
34
- # Substitute Windows backslashes into forward slashes, which are used in glob patterns
35
- glob = path.gsub(/[\\]+/, "/")
36
- glob.delete_suffix!("/")
37
- @excluded_patterns << "#{glob}/**/*.rb"
38
- end
39
-
40
- # We start the included patterns with only the non excluded directories so that we can avoid paying the price of
41
- # traversing large directories that don't include Ruby files like `node_modules`
42
- @included_patterns = ["{#{top_level_directories.join(",")}}/**/*.rb", "*.rb"] #: Array[String]
43
- @excluded_magic_comments = [
44
- "frozen_string_literal:",
45
- "typed:",
46
- "compiled:",
47
- "encoding:",
48
- "shareable_constant_value:",
49
- "warn_indent:",
50
- "rubocop:",
51
- "nodoc:",
52
- "doc:",
53
- "coding:",
54
- "warn_past_scope:",
55
- ] #: Array[String]
56
- end
57
-
58
- #: -> Array[URI::Generic]
59
- def indexable_uris
60
- excluded_gems = @excluded_gems - @included_gems
61
- locked_gems = Bundler.locked_gems&.specs
62
-
63
- # NOTE: indexing the patterns (both included and excluded) needs to happen before indexing gems, otherwise we risk
64
- # having duplicates if BUNDLE_PATH is set to a folder inside the project structure
65
-
66
- flags = File::FNM_PATHNAME | File::FNM_EXTGLOB
67
-
68
- uris = @included_patterns.flat_map do |pattern|
69
- load_path_entry = nil #: String?
70
-
71
- Dir.glob(File.join(@workspace_path, pattern), flags).map! do |path|
72
- # All entries for the same pattern match the same $LOAD_PATH entry. Since searching the $LOAD_PATH for every
73
- # entry is expensive, we memoize it until we find a path that doesn't belong to that $LOAD_PATH. This happens
74
- # on repositories that define multiple gems, like Rails. All frameworks are defined inside the current
75
- # workspace directory, but each one of them belongs to a different $LOAD_PATH entry
76
- if load_path_entry.nil? || !path.start_with?(load_path_entry)
77
- load_path_entry = $LOAD_PATH.find { |load_path| path.start_with?(load_path) }
78
- end
79
-
80
- URI::Generic.from_path(path: path, load_path_entry: load_path_entry)
81
- end
82
- end
83
-
84
- # If the patterns are relative, we make it relative to the workspace path. If they are absolute, then we shouldn't
85
- # concatenate anything
86
- excluded_patterns = @excluded_patterns.map do |pattern|
87
- if File.absolute_path?(pattern)
88
- pattern
89
- else
90
- File.join(@workspace_path, pattern)
91
- end
92
- end
93
-
94
- # Remove user specified patterns
95
- bundle_path = Bundler.settings["path"]&.gsub(/[\\]+/, "/")
96
- uris.reject! do |indexable|
97
- path = indexable.full_path #: as !nil
98
- next false if test_files_ignored_from_exclusion?(path, bundle_path)
99
-
100
- excluded_patterns.any? { |pattern| File.fnmatch?(pattern, path, flags) }
101
- end
102
-
103
- # Add default gems to the list of files to be indexed
104
- Dir.glob(File.join(RbConfig::CONFIG["rubylibdir"], "*")).each do |default_path|
105
- # The default_path might be a Ruby file or a folder with the gem's name. For example:
106
- # bundler/
107
- # bundler.rb
108
- # psych/
109
- # psych.rb
110
- pathname = Pathname.new(default_path)
111
- short_name = pathname.basename.to_s.delete_suffix(".rb")
112
-
113
- # If the gem name is excluded, then we skip it
114
- next if excluded_gems.include?(short_name)
115
-
116
- # If the default gem is also a part of the bundle, we skip indexing the default one and index only the one in
117
- # the bundle, which won't be in `default_path`, but will be in `Bundler.bundle_path` instead
118
- next if locked_gems&.any? do |locked_spec|
119
- locked_spec.name == short_name &&
120
- !Gem::Specification.find_by_name(short_name).full_gem_path.start_with?(RbConfig::CONFIG["rubylibprefix"])
121
- rescue Gem::MissingSpecError
122
- # If a default gem is scoped to a specific platform, then `find_by_name` will raise. We want to skip those
123
- # cases
124
- true
125
- end
126
-
127
- if pathname.directory?
128
- # If the default_path is a directory, we index all the Ruby files in it
129
- uris.concat(
130
- Dir.glob(File.join(default_path, "**", "*.rb"), File::FNM_PATHNAME | File::FNM_EXTGLOB).map! do |path|
131
- URI::Generic.from_path(path: path, load_path_entry: RbConfig::CONFIG["rubylibdir"])
132
- end,
133
- )
134
- elsif pathname.extname == ".rb"
135
- # If the default_path is a Ruby file, we index it
136
- uris << URI::Generic.from_path(path: default_path, load_path_entry: RbConfig::CONFIG["rubylibdir"])
137
- end
138
- end
139
-
140
- # Add the locked gems to the list of files to be indexed
141
- locked_gems&.each do |lazy_spec|
142
- next if excluded_gems.include?(lazy_spec.name)
143
-
144
- spec = Gem::Specification.find_by_name(lazy_spec.name)
145
-
146
- # When working on a gem, it will be included in the locked_gems list. Since these are the project's own files,
147
- # we have already included and handled exclude patterns for it and should not re-include or it'll lead to
148
- # duplicates or accidentally ignoring exclude patterns
149
- next if spec.full_gem_path == @workspace_path
150
-
151
- uris.concat(
152
- spec.require_paths.flat_map do |require_path|
153
- load_path_entry = File.join(spec.full_gem_path, require_path)
154
- Dir.glob(File.join(load_path_entry, "**", "*.rb")).map! do |path|
155
- URI::Generic.from_path(path: path, load_path_entry: load_path_entry)
156
- end
157
- end,
158
- )
159
- rescue Gem::MissingSpecError
160
- # If a gem is scoped only to some specific platform, then its dependencies may not be installed either, but they
161
- # are still listed in locked_gems. We can't index them because they are not installed for the platform, so we
162
- # just ignore if they're missing
163
- end
164
-
165
- uris.uniq!(&:to_s)
166
- uris
167
- end
168
-
169
- #: -> Regexp
170
- def magic_comment_regex
171
- @magic_comment_regex ||= /^#\s*#{@excluded_magic_comments.join("|")}/ #: Regexp?
172
- end
173
-
174
- #: (Hash[String, untyped] config) -> void
175
- def apply_config(config)
176
- validate_config!(config)
177
-
178
- @excluded_gems.concat(config["excluded_gems"]) if config["excluded_gems"]
179
- @included_gems.concat(config["included_gems"]) if config["included_gems"]
180
- @excluded_patterns.concat(config["excluded_patterns"]) if config["excluded_patterns"]
181
- @included_patterns.concat(config["included_patterns"]) if config["included_patterns"]
182
- @excluded_magic_comments.concat(config["excluded_magic_comments"]) if config["excluded_magic_comments"]
183
- end
184
-
185
- private
186
-
187
- #: (Hash[String, untyped] config) -> void
188
- def validate_config!(config)
189
- errors = config.filter_map do |key, value|
190
- type = CONFIGURATION_SCHEMA[key]
191
-
192
- if type.nil?
193
- "Unknown configuration option: #{key}"
194
- elsif !value.is_a?(type)
195
- "Expected #{key} to be a #{type}, but got #{value.class}"
196
- end
197
- end
198
-
199
- raise ArgumentError, errors.join("\n") if errors.any?
200
- end
201
-
202
- #: -> Array[String]
203
- def initial_excluded_gems
204
- excluded, others = Bundler.definition.dependencies.partition do |dependency|
205
- dependency.groups == [:development]
206
- end
207
-
208
- # When working on a gem, we need to make sure that its gemspec dependencies can't be excluded. This is necessary
209
- # because Bundler doesn't assign groups to gemspec dependencies
210
- #
211
- # If the dependency is prerelease, `to_spec` may return `nil` due to a bug in older version of Bundler/RubyGems:
212
- # https://github.com/Shopify/ruby-lsp/issues/1246
213
- this_gem = Bundler.definition.dependencies.find do |d|
214
- d.to_spec&.full_gem_path == @workspace_path
215
- rescue Gem::MissingSpecError
216
- false
217
- end
218
-
219
- others.concat(this_gem.to_spec.dependencies) if this_gem
220
- others.concat(
221
- others.filter_map do |d|
222
- d.to_spec&.dependencies
223
- rescue Gem::MissingSpecError
224
- nil
225
- end.flatten,
226
- )
227
- others.uniq!
228
- others.map!(&:name)
229
-
230
- transitive_excluded = excluded.each_with_object([]) do |dependency, acc|
231
- next unless dependency.runtime?
232
-
233
- spec = dependency.to_spec
234
- next unless spec
235
-
236
- spec.dependencies.each do |transitive_dependency|
237
- next if others.include?(transitive_dependency.name)
238
-
239
- acc << transitive_dependency
240
- end
241
- rescue Gem::MissingSpecError
242
- # If a gem is scoped only to some specific platform, then its dependencies may not be installed either, but they
243
- # are still listed in dependencies. We can't index them because they are not installed for the platform, so we
244
- # just ignore if they're missing
245
- end
246
-
247
- excluded.concat(transitive_excluded)
248
- excluded.uniq!
249
- excluded.map(&:name)
250
- rescue Bundler::GemfileNotFound
251
- []
252
- end
253
-
254
- # Checks if the test file is never supposed to be ignored from indexing despite matching exclusion patterns, like
255
- # `test_helper.rb` or `test_case.rb`. Also takes into consideration the possibility of finding these files under
256
- # fixtures or inside gem source code if the bundle path points to a directory inside the workspace
257
- #: (String path, String? bundle_path) -> bool
258
- def test_files_ignored_from_exclusion?(path, bundle_path)
259
- ["test_case.rb", "test_helper.rb"].include?(File.basename(path)) &&
260
- !File.fnmatch?("**/fixtures/**/*", path, File::FNM_PATHNAME | File::FNM_EXTGLOB) &&
261
- (!bundle_path || !path.start_with?(bundle_path))
262
- end
263
-
264
- #: -> Array[String]
265
- def top_level_directories
266
- excluded_directories = ["tmp", "node_modules", "sorbet"]
267
-
268
- Dir.glob("#{Dir.pwd}/*").filter_map do |path|
269
- dir_name = File.basename(path)
270
- next unless File.directory?(path) && !excluded_directories.include?(dir_name)
271
-
272
- dir_name
273
- end
274
- end
275
- end
276
- end