lib/tapioca/generator.rb in tapioca-0.4.19 vs lib/tapioca/generator.rb in tapioca-0.4.20

- old
+ new

@@ -118,42 +118,56 @@ sig do params( requested_constants: T::Array[String], should_verify: T::Boolean, + quiet: T::Boolean ).void end - def build_dsl(requested_constants, should_verify: false) + def build_dsl(requested_constants, should_verify: false, quiet: false) load_application(eager_load: requested_constants.empty?) load_dsl_generators if should_verify say("Checking for out-of-date RBIs...") else say("Compiling DSL RBI files...") end say("") - outpath = should_verify ? Dir.mktmpdir : config.outpath + outpath = should_verify ? Pathname.new(Dir.mktmpdir) : config.outpath rbi_files_to_purge = existing_rbi_filenames(requested_constants) compiler = Compilers::DslCompiler.new( requested_constants: constantize(requested_constants), requested_generators: config.generators, error_handler: ->(error) { say_error(error, :bold, :red) } ) + constant_lookup = {} + compiler.run do |constant, contents| - filename = compile_dsl_rbi(constant, contents, outpath: Pathname.new(outpath)) - rbi_files_to_purge.delete(filename) if filename + constant_name = Module.instance_method(:name).bind(constant).call + + filename = compile_dsl_rbi( + constant_name, + contents, + outpath: outpath, + quiet: should_verify || quiet + ) + + if filename + rbi_files_to_purge.delete(filename) + constant_lookup[filename.relative_path_from(outpath)] = constant_name + end end say("") if should_verify - perform_dsl_verification(outpath) + perform_dsl_verification(outpath, constant_lookup) else purge_stale_dsl_rbi_files(rbi_files_to_purge) say("Done", :green) @@ -504,63 +518,105 @@ T.unsafe(Pathname).glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file| remove(file) unless file.basename.to_s == gem.rbi_file_name end end - sig { params(constant: Module, contents: String, outpath: Pathname).returns(T.nilable(Pathname)) } - def compile_dsl_rbi(constant, contents, outpath: config.outpath) + sig do + params(constant_name: String, contents: String, outpath: Pathname, quiet: T::Boolean) + .returns(T.nilable(Pathname)) + end + def compile_dsl_rbi(constant_name, contents, outpath: config.outpath, quiet: false) return if contents.nil? - constant_name = Module.instance_method(:name).bind(constant).call rbi_name = constant_name.underscore + ".rbi" filename = outpath / rbi_name out = String.new out << rbi_header( "#{Config::DEFAULT_COMMAND} dsl #{constant_name}", - reason: "dynamic methods in `#{constant.name}`" + reason: "dynamic methods in `#{constant_name}`" ) out << contents FileUtils.mkdir_p(File.dirname(filename)) File.write(filename, out) - say("Wrote: ", [:green]) - say(filename) + unless quiet + say("Wrote: ", [:green]) + say(filename) + end + filename end - sig { params(tmp_dir: Pathname).returns(T.nilable(String)) } + sig { params(tmp_dir: Pathname).returns(T::Hash[String, Symbol]) } def verify_dsl_rbi(tmp_dir:) - existing_rbis = existing_rbi_filenames([]).sort - new_rbis = existing_rbi_filenames([], path: tmp_dir).grep_v(/gem|shim/).sort + diff = {} - return "New file(s) introduced." if existing_rbis.length != new_rbis.length + existing_rbis = rbi_files_in(config.outpath) + new_rbis = rbi_files_in(tmp_dir) - desynced_files = [] + added_files = (new_rbis - existing_rbis) - (0..existing_rbis.length - 1).each do |i| - desynced_files << new_rbis[i] unless FileUtils.identical?(existing_rbis[i], new_rbis[i]) + added_files.each do |file| + diff[file] = :added end - unless desynced_files.empty? - filenames = desynced_files.map { |f| f.to_s.sub!(tmp_dir.to_s, "sorbet/rbi/dsl") }.join("\n - ") + removed_files = (existing_rbis - new_rbis) - return "File(s) updated:\n - #{filenames}" + removed_files.each do |file| + diff[file] = :removed end - nil + common_files = (existing_rbis & new_rbis) + + changed_files = common_files.map do |filename| + filename unless FileUtils.identical?(config.outpath / filename, tmp_dir / filename) + end.compact + + changed_files.each do |file| + diff[file] = :changed + end + + diff end - sig { params(dir: String).void } - def perform_dsl_verification(dir) - if (error = verify_dsl_rbi(tmp_dir: Pathname.new(dir))) - say("RBI files are out-of-date, please run `#{Config::DEFAULT_COMMAND} dsl` to update.") - say("Reason: ", [:red]) - say(error) - exit(1) - else + sig { params(cause: Symbol, files: T::Array[String]).returns(String) } + def build_error_for_files(cause, files) + filenames = files.map do |file| + config.outpath / file + end.join("\n - ") + + " File(s) #{cause}:\n - #{filenames}" + end + + sig { params(path: Pathname).returns(T::Array[Pathname]) } + def rbi_files_in(path) + Pathname.glob(path / "**/*.rbi").map do |file| + file.relative_path_from(path) + end.sort + end + + sig { params(dir: Pathname, constant_lookup: T::Hash[String, String]).void } + def perform_dsl_verification(dir, constant_lookup) + diff = verify_dsl_rbi(tmp_dir: dir) + + if diff.empty? say("Nothing to do, all RBIs are up-to-date.") + else + constants = T.unsafe(constant_lookup).values_at(*diff.keys).join(" ") + + say("RBI files are out-of-date, please run:") + say(" `#{Config::DEFAULT_COMMAND} dsl #{constants}`") + + say("") + + say("Reason:", [:red]) + diff.group_by(&:last).sort.each do |cause, diff_for_cause| + say(build_error_for_files(cause, diff_for_cause.map(&:first))) + end + + exit(1) end ensure FileUtils.remove_entry(dir) end