From a15d632f9938781e88184d0cfd76981380df638b Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Thu, 23 Jan 2025 16:56:04 -0500 Subject: [PATCH] Run URL helpers compiler when routes are modified --- lib/ruby_lsp/tapioca/addon.rb | 26 ++++++-- lib/ruby_lsp/tapioca/server_addon.rb | 10 ++- spec/tapioca/addon_spec.rb | 97 +++++++++++++++++++--------- 3 files changed, 94 insertions(+), 39 deletions(-) diff --git a/lib/ruby_lsp/tapioca/addon.rb b/lib/ruby_lsp/tapioca/addon.rb index b3d17f0c2..43140972c 100644 --- a/lib/ruby_lsp/tapioca/addon.rb +++ b/lib/ruby_lsp/tapioca/addon.rb @@ -79,11 +79,18 @@ def workspace_did_change_watched_files(changes) return unless T.must(@global_state).enabled_feature?(:tapiocaAddon) return unless @rails_runner_client # Client is not ready + has_route_change = T.let(false, T::Boolean) + constants = changes.flat_map do |change| path = URI(change[:uri]).to_standardized_path next if path.end_with?("_test.rb", "_spec.rb") next unless file_updated?(change, path) + if File.basename(path) == "routes.rb" + has_route_change = true + next + end + entries = T.must(@index).entries_for(change[:uri]) next unless entries @@ -92,14 +99,21 @@ def workspace_did_change_watched_files(changes) end end.compact - return if constants.empty? + return if constants.empty? && !has_route_change @rails_runner_client.trigger_reload - @rails_runner_client.delegate_notification( - server_addon_name: "Tapioca", - request_name: "dsl", - constants: constants, - ) + + if has_route_change + @rails_runner_client.delegate_notification(server_addon_name: "Tapioca", request_name: "route_dsl") + end + + if constants.any? + @rails_runner_client.delegate_notification( + server_addon_name: "Tapioca", + request_name: "dsl", + constants: constants, + ) + end end private diff --git a/lib/ruby_lsp/tapioca/server_addon.rb b/lib/ruby_lsp/tapioca/server_addon.rb index f1fdfb2af..362808e2b 100644 --- a/lib/ruby_lsp/tapioca/server_addon.rb +++ b/lib/ruby_lsp/tapioca/server_addon.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "tapioca/internal" +require "tapioca/dsl/compilers/url_helpers" module RubyLsp module Tapioca @@ -22,17 +23,20 @@ def execute(request, params) halt_upon_load_error: false, ).load_dsl_extensions_and_compilers when "dsl" + fork { dsl(params[:constants]) } + when "route_dsl" fork do - dsl(params) + constants = ::Tapioca::Dsl::Compilers::UrlHelpers.gather_constants + dsl(constants.map(&:name)) end end end private - def dsl(params) + def dsl(constants) load("tapioca/cli.rb") # Reload the CLI to reset thor defaults between requests - ::Tapioca::Cli.start(["dsl", "--lsp_addon", "--workers=1"] + params[:constants]) + ::Tapioca::Cli.start(["dsl", "--lsp_addon", "--workers=1"] + constants) end end end diff --git a/spec/tapioca/addon_spec.rb b/spec/tapioca/addon_spec.rb index 55864dd1d..08cca81f1 100644 --- a/spec/tapioca/addon_spec.rb +++ b/spec/tapioca/addon_spec.rb @@ -3,8 +3,9 @@ require "spec_helper" require "language_server-protocol" -require "ruby_lsp/utils" -require "ruby_lsp/ruby_lsp_rails/runner_client" +require "ruby_lsp/internal" +require "ruby_lsp/ruby_lsp_rails/addon" +require "ruby_lsp/tapioca/addon" require "minitest/hooks" module RubyLsp @@ -12,16 +13,68 @@ module Tapioca class AddonSpec < Minitest::HooksSpec # The approach here is based on tests within the Ruby LSP Rails gem - # TODO: Replace with `before(:all)` once Sorbet understands it - def initialize(*args) - super(*T.unsafe(args)) - @outgoing_queue = Thread::Queue.new - @client = T.let( - FileUtils.chdir("spec/dummy") do - RubyLsp::Rails::RunnerClient.new(@outgoing_queue) - end, - RubyLsp::Rails::RunnerClient, + it "generates DSL RBIs for a given constant" do + create_client + @client.delegate_notification( + server_addon_name: "Tapioca", + request_name: "dsl", + constants: ["NotifyUserJob"], ) + shutdown_client + + assert_path_exists("spec/dummy/sorbet/rbi/dsl/notify_user_job.rbi") + ensure + FileUtils.rm_r("spec/dummy/sorbet/rbi") + end + + it "triggers route DSL generation if routes.rb is modified" do + create_client + + File.write("spec/dummy/config/routes.rb", <<~RUBY) + Rails.application.routes.draw do + root "home#index" + end + RUBY + + global_state = RubyLsp::GlobalState.new + global_state.apply_options({ + initializationOptions: { + enabledFeatureFlags: { + tapiocaAddon: true, + }, + }, + }) + + addon = Addon.new + addon.instance_variable_set(:@rails_runner_client, @client) + addon.instance_variable_set(:@global_state, global_state) + addon.instance_variable_set(:@index, global_state.index) + addon.instance_variable_set(:@outgoing_queue, @outgoing_queue) + + addon.workspace_did_change_watched_files([{ + uri: "file://#{Dir.pwd}/spec/dummy/config/routes.rb", + type: Constant::FileChangeType::CREATED, + }]) + + shutdown_client + assert_path_exists("spec/dummy/sorbet/rbi/dsl/generated_url_helpers_module.rbi") + assert_path_exists("spec/dummy/sorbet/rbi/dsl/generated_path_helpers_module.rbi") + + assert_match("root_url", File.read("spec/dummy/sorbet/rbi/dsl/generated_url_helpers_module.rbi")) + assert_match("root_path", File.read("spec/dummy/sorbet/rbi/dsl/generated_path_helpers_module.rbi")) + ensure + FileUtils.rm_r("spec/dummy/sorbet/rbi") + FileUtils.rm("spec/dummy/config/routes.rb") + end + + private + + # Starts a new client + def create_client + @outgoing_queue = Thread::Queue.new + @client = FileUtils.chdir("spec/dummy") do + RubyLsp::Rails::RunnerClient.new(@outgoing_queue) + end addon_path = File.expand_path("lib/ruby_lsp/tapioca/server_addon.rb") @client.register_server_addon(File.expand_path(addon_path)) @@ -32,30 +85,14 @@ def initialize(*args) ) end - after(:all) do + # Triggers shutdown and waits for it to complete + def shutdown_client @client.shutdown + @client.instance_variable_get(:@wait_thread).join assert_predicate(@client, :stopped?) @outgoing_queue.close end - - it "generates DSL RBIs for a given constant" do - @client.delegate_notification( - server_addon_name: "Tapioca", - request_name: "dsl", - constants: ["NotifyUserJob"], - ) - - begin - Timeout.timeout(10) do - sleep(1) until File.exist?("spec/dummy/sorbet/rbi/dsl/notify_user_job.rbi") - end - rescue Timeout::Error - flunk("RBI file was not generated") - end - ensure - FileUtils.rm_rf("spec/dummy/sorbet/rbi") - end end end end