Skip to content

Commit 384c457

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 864141f commit 384c457

8 files changed

Lines changed: 442 additions & 23 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,8 @@ Options:
507507
-w, [--workers=N] # Number of parallel workers to use when generating RBIs (default: auto)
508508
[--rbi-max-line-length=N] # Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped
509509
# Default: 120
510+
[--max-diff-lines=N] # Max number of diff lines to include in the `dsl --verify` output
511+
# Default: 250
510512
-e, [--environment=ENVIRONMENT] # The Rack/Rails environment to use when generating RBIs
511513
# Default: development
512514
-l, [--list-compilers], [--no-list-compilers], [--skip-list-compilers] # List all loaded compilers
@@ -1002,6 +1004,7 @@ dsl:
10021004
quiet: false
10031005
workers: 1
10041006
rbi_max_line_length: 120
1007+
max_diff_lines: 250
10051008
environment: development
10061009
list_compilers: false
10071010
app_root: "."

lib/tapioca.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class Error < StandardError; end
3232

3333
DEFAULT_RBI_MAX_LINE_LENGTH = 120
3434
DEFAULT_ENVIRONMENT = "development"
35+
DEFAULT_MAX_DIFF_LINES = 250
3536

3637
CENTRAL_REPO_ROOT_URI = "https://raw.githubusercontent.com/Shopify/rbi-central/main"
3738
CENTRAL_REPO_INDEX_PATH = "index.json"

lib/tapioca/cli.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ def todo
120120
type: :numeric,
121121
desc: "Set the max line length of generated RBIs. Signatures longer than the max line length will be wrapped",
122122
default: DEFAULT_RBI_MAX_LINE_LENGTH
123+
option :max_diff_lines,
124+
type: :numeric,
125+
desc: "Max number of diff lines to include in the `dsl --verify` output",
126+
default: DEFAULT_MAX_DIFF_LINES
123127
option :environment,
124128
aliases: ["-e"],
125129
type: :string,
@@ -159,6 +163,10 @@ def dsl(*constant_or_paths)
159163
# Assume anything starting with a capital letter or colon is a class, otherwise a path
160164
constants, paths = constant_or_paths.partition { |c| c =~ /\A[A-Z:]/ }
161165

166+
unless options[:max_diff_lines].positive?
167+
raise MalformattedArgumentError, "Option '--max-diff-lines' must be a positive number"
168+
end
169+
162170
command_args = {
163171
requested_constants: constants,
164172
requested_paths: paths.map { |p| Pathname.new(p) },
@@ -176,6 +184,7 @@ def dsl(*constant_or_paths)
176184
halt_upon_load_error: options[:halt_upon_load_error],
177185
compiler_options: options[:compiler_options],
178186
lsp_addon: self.class.addon_mode,
187+
max_diff_lines: options[:max_diff_lines],
179188
}
180189

181190
command = if options[:verify]

lib/tapioca/commands/abstract_dsl.rb

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module Tapioca
55
module Commands
66
# @abstract
77
class AbstractDsl < CommandWithoutTracker
8+
include FileHelper
89
include SorbetHelper
910
include RBIFilesHelper
1011

@@ -26,7 +27,8 @@ class AbstractDsl < CommandWithoutTracker
2627
#| ?app_root: String,
2728
#| ?halt_upon_load_error: bool,
2829
#| ?compiler_options: Hash[String, untyped],
29-
#| ?lsp_addon: bool
30+
#| ?lsp_addon: bool,
31+
#| ?max_diff_lines: Integer
3032
#| ) -> void
3133
def initialize(
3234
requested_constants:,
@@ -46,7 +48,8 @@ def initialize(
4648
app_root: ".",
4749
halt_upon_load_error: true,
4850
compiler_options: {},
49-
lsp_addon: false
51+
lsp_addon: false,
52+
max_diff_lines: DEFAULT_MAX_DIFF_LINES
5053
)
5154
@requested_constants = requested_constants
5255
@requested_paths = requested_paths
@@ -66,6 +69,7 @@ def initialize(
6669
@skip_constant = skip_constant
6770
@compiler_options = compiler_options
6871
@lsp_addon = lsp_addon
72+
@max_diff_lines = max_diff_lines
6973

7074
super()
7175
end
@@ -247,7 +251,7 @@ def compile_dsl_rbi(constant_name, rbi, outpath: @outpath, quiet: false)
247251
def perform_dsl_verification(dir)
248252
diff = verify_dsl_rbi(tmp_dir: dir)
249253

250-
report_diff_and_exit_if_out_of_date(diff, :dsl)
254+
report_diff_and_exit_if_out_of_date(diff, tmp_dir: dir, command: :dsl)
251255
ensure
252256
FileUtils.remove_entry(dir)
253257
end
@@ -269,7 +273,7 @@ def dsl_rbi_filename(constant_name)
269273
@outpath / "#{underscore(constant_name)}.rbi"
270274
end
271275

272-
#: (tmp_dir: Pathname) -> Hash[String, Symbol]
276+
#: (tmp_dir: Pathname) -> Hash[Pathname, Symbol]
273277
def verify_dsl_rbi(tmp_dir:)
274278
diff = {}
275279

@@ -301,7 +305,7 @@ def verify_dsl_rbi(tmp_dir:)
301305
diff
302306
end
303307

304-
#: (Symbol cause, Array[String] files) -> String
308+
#: (Symbol cause, Array[Pathname] files) -> String
305309
def build_error_for_files(cause, files)
306310
filenames = files.map do |file|
307311
@outpath / file
@@ -310,26 +314,66 @@ def build_error_for_files(cause, files)
310314
" File(s) #{cause}:\n - #{filenames}"
311315
end
312316

313-
#: (Hash[String, Symbol] diff, Symbol command) -> void
314-
def report_diff_and_exit_if_out_of_date(diff, command)
317+
#: (Hash[Pathname, Symbol] diff, tmp_dir: Pathname, command: Symbol) -> void
318+
def report_diff_and_exit_if_out_of_date(diff, tmp_dir:, command:)
315319
if diff.empty?
316320
say("Nothing to do, all RBIs are up-to-date.")
317-
else
318-
reasons = diff.group_by(&:last).sort.map do |cause, diff_for_cause|
319-
build_error_for_files(cause, diff_for_cause.map(&:first))
320-
end.join("\n")
321+
return
322+
end
321323

322-
raise Tapioca::Error, <<~ERROR
323-
#{set_color("RBI files are out-of-date. In your development environment, please run:", :green)}
324-
#{set_color("`#{default_command(command)}`", :green, :bold)}
325-
#{set_color("Once it is complete, be sure to commit and push any changes", :green)}
326-
If you don't observe any changes after running the command locally, ensure your database is in a good
327-
state e.g. run `bin/rails db:reset`
324+
reasons = diff.group_by(&:last).sort.map do |cause, diff_for_cause|
325+
build_error_for_files(cause, diff_for_cause.map(&:first))
326+
end.join("\n")
327+
328+
diff_output = build_diff_output(diff, tmp_dir)
329+
diff_lines = diff_output.count("\n")
330+
331+
diff_section =
332+
if diff_lines.between?(1, @max_diff_lines)
333+
"#{set_color("Diff:", :red)}\n#{diff_output.chomp}"
334+
elsif diff_lines > @max_diff_lines
335+
truncated_output = diff_output.lines.first(@max_diff_lines).join
336+
"#{set_color("Diff truncated to #{@max_diff_lines} lines.\nChange the number of displayed lines using the `--max-diff-lines` flag.", :red)}\n#{truncated_output.rstrip}"
337+
else
338+
""
339+
end
328340

329-
#{set_color("Reason:", :red)}
330-
#{reasons}
331-
ERROR
341+
raise Tapioca::Error, <<~ERROR.rstrip
342+
#{set_color("RBI files are out-of-date. In your development environment, please run:", :green)}
343+
#{set_color("`#{default_command(command)}`", :green, :bold)}
344+
#{set_color("Once it is complete, be sure to commit and push any changes", :green)}
345+
If you don't observe any changes after running the command locally, ensure your database is in a good
346+
state e.g. run `bin/rails db:reset`
347+
348+
#{set_color("Reason:", :red)}
349+
#{reasons}
350+
351+
#{diff_section}
352+
ERROR
353+
end
354+
355+
#: (Hash[Pathname, Symbol] diff, Pathname tmp_dir) -> String
356+
def build_diff_output(diff, tmp_dir)
357+
out = String.new
358+
line_count = 0
359+
360+
diff.each do |file, status|
361+
old_path = (@outpath / file)
362+
new_path = (tmp_dir / file)
363+
364+
chunk = case status
365+
when :added then file_diff(file, File::NULL, new_path)
366+
when :removed then file_diff(file, old_path, File::NULL)
367+
when :changed then file_diff(file, old_path, new_path)
368+
else ""
369+
end
370+
371+
out << chunk
372+
line_count += chunk.count("\n")
373+
break if line_count > @max_diff_lines
332374
end
375+
376+
out
333377
end
334378

335379
#: (Pathname path) -> Array[Pathname]

lib/tapioca/helpers/file_helper.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# typed: strict
2+
# frozen_string_literal: true
3+
4+
require "open3"
5+
6+
module Tapioca
7+
module FileHelper
8+
#: (Pathname filename, Pathname | String old_path, Pathname | String new_path) -> String
9+
def file_diff(filename, old_path, new_path)
10+
filename = filename.to_s
11+
stdout, stderr, status = Open3.capture3(
12+
"diff",
13+
"-u",
14+
"--label=Current #{filename}",
15+
old_path.to_s,
16+
"--label=Correct #{filename} (After running `bin/tapioca dsl`)",
17+
new_path.to_s,
18+
)
19+
20+
unless [0, 1].include?(status.exitstatus)
21+
error_msg("Failed to create #{filename} diff. #{stderr.chomp}")
22+
return ""
23+
end
24+
25+
stdout
26+
rescue SystemCallError => e
27+
error_msg("Failed to create #{filename} diff. #{e.message}")
28+
""
29+
end
30+
31+
private
32+
33+
RED = "\e[31m" #: String
34+
CLEAR = "\e[0m" #: String
35+
36+
#: (String message) -> void
37+
def error_msg(message)
38+
message = "#{RED}#{message}#{CLEAR}"
39+
Kernel.warn(message)
40+
end
41+
end
42+
end

lib/tapioca/internal.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
require "tapioca/helpers/package_url"
5555
require "tapioca/helpers/cli_helper"
5656
require "tapioca/helpers/config_helper"
57+
require "tapioca/helpers/file_helper"
5758
require "tapioca/helpers/rbi_files_helper"
5859
require "tapioca/helpers/env_helper"
5960

0 commit comments

Comments
 (0)