Skip to content

Commit f3c5b51

Browse files
committed
Display line diffs in dsl --verify error messages
When `dsl --verify` detects out of date RBI files, the error message lists which files were added, removed, or changed. We could make it easier to understand what has changed by including the line diff output in the error message as well This commit grabs the line diff output during RBI verification and includes it in the error message when the diff is 250 lines or less
1 parent b88908a commit f3c5b51

3 files changed

Lines changed: 333 additions & 10 deletions

File tree

lib/tapioca/commands/abstract_dsl.rb

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# typed: strict
22
# frozen_string_literal: true
33

4+
require "open3"
5+
46
module Tapioca
57
module Commands
68
# @abstract
@@ -240,9 +242,9 @@ def compile_dsl_rbi(constant_name, rbi, outpath: @outpath, quiet: false)
240242

241243
#: (Pathname dir) -> void
242244
def perform_dsl_verification(dir)
243-
diff = verify_dsl_rbi(tmp_dir: dir)
245+
diff, diff_output = verify_dsl_rbi(tmp_dir: dir)
244246

245-
report_diff_and_exit_if_out_of_date(diff, :dsl)
247+
report_diff_and_exit_if_out_of_date(diff, diff_output, :dsl)
246248
ensure
247249
FileUtils.remove_entry(dir)
248250
end
@@ -264,9 +266,10 @@ def dsl_rbi_filename(constant_name)
264266
@outpath / "#{underscore(constant_name)}.rbi"
265267
end
266268

267-
#: (tmp_dir: Pathname) -> Hash[String, Symbol]
269+
#: (tmp_dir: Pathname) -> [Hash[String, Symbol], String]
268270
def verify_dsl_rbi(tmp_dir:)
269271
diff = {}
272+
diff_output = String.new
270273

271274
existing_rbis = rbi_files_in(@outpath)
272275
new_rbis = rbi_files_in(tmp_dir)
@@ -275,12 +278,14 @@ def verify_dsl_rbi(tmp_dir:)
275278

276279
added_files.each do |file|
277280
diff[file] = :added
281+
diff_output << file_diff(file, File::NULL, tmp_dir / file)
278282
end
279283

280284
removed_files = (existing_rbis - new_rbis)
281285

282286
removed_files.each do |file|
283287
diff[file] = :removed
288+
diff_output << file_diff(file, @outpath / file, File::NULL)
284289
end
285290

286291
common_files = (existing_rbis & new_rbis)
@@ -291,9 +296,30 @@ def verify_dsl_rbi(tmp_dir:)
291296

292297
changed_files.each do |file|
293298
diff[file] = :changed
299+
diff_output << file_diff(file, @outpath / file, tmp_dir / file)
294300
end
295301

296-
diff
302+
[diff, diff_output]
303+
end
304+
305+
#: (Pathname filename, String | Pathname old_path, String | Pathname new_path) -> String
306+
def file_diff(filename, old_path, new_path)
307+
stdout, stderr, status = Open3.capture3(
308+
"diff",
309+
"-u",
310+
"--label",
311+
filename.to_s,
312+
"--label",
313+
filename.to_s,
314+
old_path.to_s,
315+
new_path.to_s,
316+
)
317+
raise stderr.chomp unless [0, 1].include?(status.exitstatus)
318+
319+
stdout
320+
rescue => e
321+
say_error("Failed to create #{filename} diff. #{e.message}", :yellow)
322+
""
297323
end
298324

299325
#: (Symbol cause, Array[String] files) -> String
@@ -305,16 +331,20 @@ def build_error_for_files(cause, files)
305331
" File(s) #{cause}:\n - #{filenames}"
306332
end
307333

308-
#: (Hash[String, Symbol] diff, Symbol command) -> void
309-
def report_diff_and_exit_if_out_of_date(diff, command)
334+
#: (Hash[String, Symbol] diff, String diff_output, Symbol command) -> void
335+
def report_diff_and_exit_if_out_of_date(diff, diff_output, command)
310336
if diff.empty?
311337
say("Nothing to do, all RBIs are up-to-date.")
312338
else
313339
reasons = diff.group_by(&:last).sort.map do |cause, diff_for_cause|
314340
build_error_for_files(cause, diff_for_cause.map(&:first))
315341
end.join("\n")
316342

317-
raise Tapioca::Error, <<~ERROR
343+
file_diff = if diff_output.lines.count.between?(1, 250)
344+
"#{set_color("Diff:", :red)}\n#{diff_output.chomp}"
345+
end
346+
347+
raise Tapioca::Error, <<~ERROR.chomp
318348
#{set_color("RBI files are out-of-date. In your development environment, please run:", :green)}
319349
#{set_color("`#{default_command(command)}`", :green, :bold)}
320350
#{set_color("Once it is complete, be sure to commit and push any changes", :green)}
@@ -323,6 +353,8 @@ def report_diff_and_exit_if_out_of_date(diff, command)
323353
324354
#{set_color("Reason:", :red)}
325355
#{reasons}
356+
357+
#{file_diff}
326358
ERROR
327359
end
328360
end

spec/tapioca/cli/dsl_spec.rb

Lines changed: 198 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,13 +1934,28 @@ def perform(foo, bar)
19341934

19351935
after do
19361936
@project.remove!("sorbet/rbi/dsl")
1937+
@project.remove!("lib/image.rb")
1938+
@project.write!("lib/post.rb", <<~RB)
1939+
require "smart_properties"
1940+
1941+
class Post
1942+
include SmartProperties
1943+
property :title, accepts: String
1944+
end
1945+
RB
19371946
end
19381947

19391948
it "does nothing and returns exit status 0 with no changes" do
19401949
@project.tapioca("dsl")
19411950
result = @project.tapioca("dsl --verify")
19421951

1943-
assert_stdout_includes(result, <<~OUT)
1952+
assert_stdout_equals(<<~OUT, result)
1953+
Loading DSL extension classes... Done
1954+
Loading Rails application... Done
1955+
Loading DSL compiler classes... Done
1956+
Checking for out-of-date RBIs...
1957+
1958+
19441959
Nothing to do, all RBIs are up-to-date.
19451960
OUT
19461961

@@ -1971,6 +1986,29 @@ def perform(foo, bar)
19711986
Reason:
19721987
File(s) removed:
19731988
- sorbet/rbi/dsl/post.rbi
1989+
1990+
Diff:
1991+
--- post.rbi
1992+
+++ post.rbi
1993+
@@ -1,18 +0,0 @@
1994+
-# typed: true
1995+
-
1996+
-# DO NOT EDIT MANUALLY
1997+
-# This is an autogenerated file for dynamic methods in `Post`.
1998+
-# Please instead update this file by running `bin/tapioca dsl Post`.
1999+
-
2000+
-
2001+
-class Post
2002+
- include SmartPropertiesGeneratedMethods
2003+
-
2004+
- module SmartPropertiesGeneratedMethods
2005+
- sig { returns(T.nilable(::String)) }
2006+
- def title; end
2007+
-
2008+
- sig { params(title: T.nilable(::String)).returns(T.nilable(::String)) }
2009+
- def title=(title); end
2010+
- end
2011+
-end
19742012
ERROR
19752013

19762014
refute_success_status(result)
@@ -2010,11 +2048,32 @@ class Image
20102048
Reason:
20112049
File(s) added:
20122050
- sorbet/rbi/dsl/image.rbi
2051+
2052+
Diff:
2053+
--- image.rbi
2054+
+++ image.rbi
2055+
@@ -0,0 +1,18 @@
2056+
+# typed: true
2057+
+
2058+
+# DO NOT EDIT MANUALLY
2059+
+# This is an autogenerated file for dynamic methods in `Image`.
2060+
+# Please instead update this file by running `bin/tapioca dsl Image`.
2061+
+
2062+
+
2063+
+class Image
2064+
+ include SmartPropertiesGeneratedMethods
2065+
+
2066+
+ module SmartPropertiesGeneratedMethods
2067+
+ sig { returns(T.nilable(::String)) }
2068+
+ def title; end
2069+
+
2070+
+ sig { params(title: T.nilable(::String)).returns(T.nilable(::String)) }
2071+
+ def title=(title); end
2072+
+ end
2073+
+end
20132074
ERROR
20142075

20152076
refute_success_status(result)
2016-
2017-
@project.remove!("lib/image.rb")
20182077
end
20192078

20202079
it "advises of modified file(s) and returns exit status 1 with modified file" do
@@ -2060,6 +2119,142 @@ class Post
20602119
Reason:
20612120
File(s) changed:
20622121
- sorbet/rbi/dsl/post.rbi
2122+
2123+
Diff:
2124+
--- post.rbi
2125+
+++ post.rbi
2126+
@@ -10,6 +10,12 @@
2127+
#{" "}
2128+
module SmartPropertiesGeneratedMethods
2129+
sig { returns(T.nilable(::String)) }
2130+
+ def desc; end
2131+
+
2132+
+ sig { params(desc: T.nilable(::String)).returns(T.nilable(::String)) }
2133+
+ def desc=(desc); end
2134+
+
2135+
+ sig { returns(T.nilable(::String)) }
2136+
def title; end
2137+
#{" "}
2138+
sig { params(title: T.nilable(::String)).returns(T.nilable(::String)) }
2139+
ERROR
2140+
2141+
refute_success_status(result)
2142+
end
2143+
2144+
it "advises of added and changed files and returns exit status 1" do
2145+
@project.tapioca("dsl")
2146+
2147+
@project.write!("lib/post.rb", <<~RB)
2148+
require "smart_properties"
2149+
2150+
class Post
2151+
include SmartProperties
2152+
property :title, accepts: String
2153+
property :body, accepts: String
2154+
end
2155+
RB
2156+
2157+
@project.write!("lib/image.rb", <<~RB)
2158+
require "smart_properties"
2159+
2160+
class Image
2161+
include(SmartProperties)
2162+
2163+
property :title, accepts: String
2164+
end
2165+
RB
2166+
2167+
result = @project.tapioca("dsl --verify")
2168+
2169+
assert_stdout_equals(<<~OUT, result)
2170+
Loading DSL extension classes... Done
2171+
Loading Rails application... Done
2172+
Loading DSL compiler classes... Done
2173+
Checking for out-of-date RBIs...
2174+
2175+
2176+
OUT
2177+
2178+
assert_stderr_equals(<<~ERROR, result)
2179+
RBI files are out-of-date. In your development environment, please run:
2180+
`bin/tapioca dsl`
2181+
Once it is complete, be sure to commit and push any changes
2182+
If you don't observe any changes after running the command locally, ensure your database is in a good
2183+
state e.g. run `bin/rails db:reset`
2184+
2185+
Reason:
2186+
File(s) added:
2187+
- sorbet/rbi/dsl/image.rbi
2188+
File(s) changed:
2189+
- sorbet/rbi/dsl/post.rbi
2190+
2191+
Diff:
2192+
--- image.rbi
2193+
+++ image.rbi
2194+
@@ -0,0 +1,18 @@
2195+
+# typed: true
2196+
+
2197+
+# DO NOT EDIT MANUALLY
2198+
+# This is an autogenerated file for dynamic methods in `Image`.
2199+
+# Please instead update this file by running `bin/tapioca dsl Image`.
2200+
+
2201+
+
2202+
+class Image
2203+
+ include SmartPropertiesGeneratedMethods
2204+
+
2205+
+ module SmartPropertiesGeneratedMethods
2206+
+ sig { returns(T.nilable(::String)) }
2207+
+ def title; end
2208+
+
2209+
+ sig { params(title: T.nilable(::String)).returns(T.nilable(::String)) }
2210+
+ def title=(title); end
2211+
+ end
2212+
+end
2213+
--- post.rbi
2214+
+++ post.rbi
2215+
@@ -10,6 +10,12 @@
2216+
#{" "}
2217+
module SmartPropertiesGeneratedMethods
2218+
sig { returns(T.nilable(::String)) }
2219+
+ def body; end
2220+
+
2221+
+ sig { params(body: T.nilable(::String)).returns(T.nilable(::String)) }
2222+
+ def body=(body); end
2223+
+
2224+
+ sig { returns(T.nilable(::String)) }
2225+
def title; end
2226+
#{" "}
2227+
sig { params(title: T.nilable(::String)).returns(T.nilable(::String)) }
2228+
ERROR
2229+
2230+
refute_success_status(result)
2231+
end
2232+
2233+
it "omits diff output when it exceeds 250 lines" do
2234+
@project.tapioca("dsl")
2235+
2236+
@project.write!("lib/post.rb", <<~RB)
2237+
require "smart_properties"
2238+
2239+
class Post
2240+
include SmartProperties
2241+
#{(1..250).map { |i| "property :p#{i}, accepts: String" }.join("\n")}
2242+
end
2243+
RB
2244+
2245+
result = @project.tapioca("dsl --verify")
2246+
2247+
assert_stderr_equals(<<~ERROR, result)
2248+
RBI files are out-of-date. In your development environment, please run:
2249+
`bin/tapioca dsl`
2250+
Once it is complete, be sure to commit and push any changes
2251+
If you don't observe any changes after running the command locally, ensure your database is in a good
2252+
state e.g. run `bin/rails db:reset`
2253+
2254+
Reason:
2255+
File(s) changed:
2256+
- sorbet/rbi/dsl/post.rbi
2257+
20632258
ERROR
20642259

20652260
refute_success_status(result)

0 commit comments

Comments
 (0)