diff --git a/lib/tapioca/dsl/pipeline.rb b/lib/tapioca/dsl/pipeline.rb index 1a7bd2f11..33ff96c05 100644 --- a/lib/tapioca/dsl/pipeline.rb +++ b/lib/tapioca/dsl/pipeline.rb @@ -64,6 +64,9 @@ def run(&blk) # It's OK if there are no constants to process if we received a valid file/path. if constants_to_process.empty? && requested_paths.none? { |p| File.exist?(p) } + # When running within the add-on, return early so this expected case is not logged as an error + return [] if @lsp_addon + report_error(<<~ERROR) No classes/modules can be matched for RBI generation. Please check that the requested classes/modules include processable DSL methods. diff --git a/spec/dummy/app/models/foo.rb b/spec/dummy/app/models/foo.rb new file mode 100644 index 000000000..9d430d21a --- /dev/null +++ b/spec/dummy/app/models/foo.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# Plain class with no processable DSL methods, used by addon_spec. +class Foo +end diff --git a/spec/tapioca/addon_spec.rb b/spec/tapioca/addon_spec.rb index 38b2cadcc..ac61b5379 100644 --- a/spec/tapioca/addon_spec.rb +++ b/spec/tapioca/addon_spec.rb @@ -28,6 +28,25 @@ class AddonSpec < Minitest::HooksSpec FileUtils.rm_rf("spec/dummy/sorbet/rbi") end + it "does not error when a constant has no processable DSL" do + create_client(supports_progress: true) + + @client.delegate_notification( + server_addon_name: "Tapioca", + request_name: "dsl", + constants: ["Foo"], + ) + + messages = wait_for_dsl_completion + + shutdown_client + + assert_empty(messages.select { |message| error_message?(message) }) + refute_path_exists("spec/dummy/sorbet/rbi/dsl/foo.rbi") + ensure + FileUtils.rm_rf("spec/dummy/sorbet/rbi") + end + it "triggers route DSL generation if routes.rb is modified" do create_client @@ -150,9 +169,12 @@ def decorate private # Starts a new client - def create_client + def create_client(supports_progress: false) @outgoing_queue = Thread::Queue.new global_state = GlobalState.new + if supports_progress + global_state.apply_options(capabilities: { window: { workDoneProgress: true } }) + end @client = FileUtils.chdir("spec/dummy") do RubyLsp::Rails::RunnerClient.new(@outgoing_queue, global_state) end @@ -182,6 +204,39 @@ def wait_until_exists(path) rescue Timeout::Error flunk("#{path} was not created in time") end + + def wait_for_dsl_completion(token: "dsl") + messages = [] + Timeout.timeout(10) do + loop do + notification = @outgoing_queue.pop + messages << notification + break if dsl_progress_end?(notification, token) + end + end + messages + rescue Timeout::Error + flunk("dsl request for #{token} did not complete in time") + end + + def dsl_progress_end?(notification, token) + return false unless notification.is_a?(Hash) + + params = notification[:params] + return false unless params + + params[:token] == token && params.dig(:value, :kind) == "end" + end + + def error_message?(notification) + message = if notification.is_a?(Hash) + notification.dig(:params, :message).to_s + else + notification.params&.message.to_s + end + + message.include?("Tapioca::Error") || message.include?("failed with StandardError") + end end end end