lib/tapioca/generator.rb in tapioca-0.4.17 vs lib/tapioca/generator.rb in tapioca-0.4.18

- old
+ new

@@ -61,11 +61,11 @@ # it with the new one found in the client code and remove the old ones. File.delete(requires_path) if File.exist?(requires_path) content = String.new content << rbi_header( - config.generate_command, + "#{Config::DEFAULT_COMMAND} require", reason: "explicit gem requires", strictness: "false" ) content << rb_string @@ -74,12 +74,12 @@ File.write(requires_path, content) say("Done", :green) say("All requires from this application have been written to #{name}.", [:green, :bold]) - cmd = set_color("tapioca sync", :yellow, :bold) - say("Please review changes and commit them, then run #{cmd}.", [:green, :bold]) + cmd = set_color("#{Config::DEFAULT_COMMAND} sync", :yellow, :bold) + say("Please review changes and commit them, then run `#{cmd}`.", [:green, :bold]) end sig { void } def build_todos todos_path = config.todos_path @@ -97,11 +97,11 @@ return end content = String.new content << rbi_header( - config.generate_command, + "#{Config::DEFAULT_COMMAND} todo", reason: "unresolved constants", strictness: "false" ) content << rbi_string content << "\n" @@ -114,47 +114,54 @@ say("All unresolved constants have been written to #{name}.", [:green, :bold]) say("Please review changes and commit them.", [:green, :bold]) end - sig { params(requested_constants: T::Array[String]).void } - def build_dsl(requested_constants) + sig do + params( + requested_constants: T::Array[String], + should_verify: T::Boolean, + ).void + end + def build_dsl(requested_constants, should_verify: false) load_application(eager_load: requested_constants.empty?) load_dsl_generators - rbi_files_to_purge = existing_rbi_filenames(requested_constants) - - say("Compiling DSL RBI files...") + 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 + 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) } ) compiler.run do |constant, contents| - filename = compile_dsl_rbi(constant, contents) + filename = compile_dsl_rbi(constant, contents, outpath: Pathname.new(outpath)) rbi_files_to_purge.delete(filename) if filename end + say("") - unless rbi_files_to_purge.empty? - say("") - say("Removing stale RBI files...") + if should_verify + perform_dsl_verification(outpath) + else + purge_stale_dsl_rbi_files(rbi_files_to_purge) - rbi_files_to_purge.sort.each do |filename| - remove(filename) - end - end + say("Done", :green) - say("") - say("Done", :green) - - say("All operations performed in working directory.", [:green, :bold]) - say("Please review changes and commit them.", [:green, :bold]) + say("All operations performed in working directory.", [:green, :bold]) + say("Please review changes and commit them.", [:green, :bold]) + end end sig { void } def sync_rbis_with_gemfile anything_done = [ @@ -216,11 +223,11 @@ say_error("\n\nLoadError: #{error}", :bold, :red) say_error("\nTapioca could not load all the gems required by your application.", :yellow) say_error("If you populated ", :yellow) say_error("#{file} ", :bold, :blue) say_error("with ", :yellow) - say_error("tapioca require", :bold, :blue) + say_error("`#{Config::DEFAULT_COMMAND} require`", :bold, :blue) say_error("you should probably review it and remove the faulty line.", :yellow) end sig do params( @@ -284,14 +291,14 @@ end constant_map.values end - sig { params(requested_constants: T::Array[String]).returns(T::Set[Pathname]) } - def existing_rbi_filenames(requested_constants) + sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) } + def existing_rbi_filenames(requested_constants, path: config.outpath) filenames = if requested_constants.empty? - Pathname.glob(config.outpath / "**/*.rbi") + Pathname.glob(path / "**/*.rbi") else requested_constants.map do |constant_name| dsl_rbi_filename(constant_name) end end @@ -475,11 +482,11 @@ strictness = config.typed_overrides[gem.name] || "true" rbi_body_content = compiler.compile(gem) content = String.new content << rbi_header( - config.generate_command, + "#{Config::DEFAULT_COMMAND} sync", reason: "types exported from the `#{gem.name}` gem", strictness: strictness ) FileUtils.mkdir_p(config.outdir) @@ -492,35 +499,82 @@ content << rbi_body_content say("Done", :green) end File.write(filename.to_s, content) - Pathname.glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file| + 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).returns(T.nilable(Pathname)) } - def compile_dsl_rbi(constant, contents) + sig { params(constant: Module, contents: String, outpath: Pathname).returns(T.nilable(Pathname)) } + def compile_dsl_rbi(constant, contents, outpath: config.outpath) return if contents.nil? - command = format(config.generate_command, constant.name) constant_name = Module.instance_method(:name).bind(constant).call rbi_name = constant_name.underscore + ".rbi" - filename = config.outpath / rbi_name + filename = outpath / rbi_name out = String.new out << rbi_header( - command, + "#{Config::DEFAULT_COMMAND} dsl #{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) filename + end + + sig { params(tmp_dir: Pathname).returns(T.nilable(String)) } + 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 + + return "New file(s) introduced." if existing_rbis.length != new_rbis.length + + desynced_files = [] + + (0..existing_rbis.length - 1).each do |i| + desynced_files << new_rbis[i] unless FileUtils.identical?(existing_rbis[i], new_rbis[i]) + end + + unless desynced_files.empty? + filenames = desynced_files.map { |f| f.to_s.sub!(tmp_dir.to_s, "sorbet/rbi/dsl") }.join("\n - ") + + return "File(s) updated:\n - #{filenames}" + end + + nil + 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 + say("Nothing to do, all RBIs are up-to-date.") + end + ensure + FileUtils.remove_entry(dir) + end + + sig { params(files: T::Set[Pathname]).void } + def purge_stale_dsl_rbi_files(files) + if files.any? + say("Removing stale RBI files...") + + files.sort.each do |filename| + remove(filename) + end + say("") + end end end end