tapioca 0.4.19 → 0.4.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f64e1f934bd0528e93322b0ba116383fc7c053d1a0c35a3378664ecc3e08e69
4
- data.tar.gz: 609a1125626ba712e042e2fb3346df852508595bf22c0187e8ded67b3c66ecf2
3
+ metadata.gz: 567eff35f7ee24b52dafcd45ceb40ccaf042b6e0661c13cc5dcb87228c21c792
4
+ data.tar.gz: 0c5143d8407d5583ccd80edbb047fb0b7500b2de8bba1717723a92f97994c045
5
5
  SHA512:
6
- metadata.gz: 0ffc0244e7bd3d86ead0c42eb2cf9e940c84d4d838143b96247e908ded022477829c92c1838f2b933294b590eae49c2cd667c57bad6219be9b983ba3c556ddff
7
- data.tar.gz: 33f534d1521e209052a7edff35fe95979497c80e3fcf78b136c3bb13ea3471f8e1158db83cafff5f3aaf79f0cfa6a00129fb20fb2ce92a018883f94fe61e0264
6
+ metadata.gz: d82321ab3b2516a4410825a6dfe4b253d7cccf49abc8c62db0b2a8cdbe13f064aa43df759108685ba06c36073c7a59cfda4ff361cdad1bfc736d7e557ae3ada6
7
+ data.tar.gz: 0b9bd1ff91ca97cbbb1922e9915a8d046421ff40f3cd2774b4518c979f44635507b48fcce93a904770b1a65e7214a9897ce5444ce26ee6d532523a595e24ec96
@@ -66,9 +66,13 @@ module Tapioca
66
66
  type: :boolean,
67
67
  default: false,
68
68
  desc: "Verifies RBIs are up-to-date"
69
+ option :quiet,
70
+ aliases: ["-q"],
71
+ type: :boolean,
72
+ desc: "Supresses file creation output"
69
73
  def dsl(*constants)
70
74
  Tapioca.silence_warnings do
71
- generator.build_dsl(constants, should_verify: options[:verify])
75
+ generator.build_dsl(constants, should_verify: options[:verify], quiet: options[:quiet])
72
76
  end
73
77
  end
74
78
 
@@ -120,9 +120,10 @@ module Tapioca
120
120
  params(
121
121
  requested_constants: T::Array[String],
122
122
  should_verify: T::Boolean,
123
+ quiet: T::Boolean
123
124
  ).void
124
125
  end
125
- def build_dsl(requested_constants, should_verify: false)
126
+ def build_dsl(requested_constants, should_verify: false, quiet: false)
126
127
  load_application(eager_load: requested_constants.empty?)
127
128
  load_dsl_generators
128
129
 
@@ -133,7 +134,7 @@ module Tapioca
133
134
  end
134
135
  say("")
135
136
 
136
- outpath = should_verify ? Dir.mktmpdir : config.outpath
137
+ outpath = should_verify ? Pathname.new(Dir.mktmpdir) : config.outpath
137
138
  rbi_files_to_purge = existing_rbi_filenames(requested_constants)
138
139
 
139
140
  compiler = Compilers::DslCompiler.new(
@@ -144,14 +145,27 @@ module Tapioca
144
145
  }
145
146
  )
146
147
 
148
+ constant_lookup = {}
149
+
147
150
  compiler.run do |constant, contents|
148
- filename = compile_dsl_rbi(constant, contents, outpath: Pathname.new(outpath))
149
- rbi_files_to_purge.delete(filename) if filename
151
+ constant_name = Module.instance_method(:name).bind(constant).call
152
+
153
+ filename = compile_dsl_rbi(
154
+ constant_name,
155
+ contents,
156
+ outpath: outpath,
157
+ quiet: should_verify || quiet
158
+ )
159
+
160
+ if filename
161
+ rbi_files_to_purge.delete(filename)
162
+ constant_lookup[filename.relative_path_from(outpath)] = constant_name
163
+ end
150
164
  end
151
165
  say("")
152
166
 
153
167
  if should_verify
154
- perform_dsl_verification(outpath)
168
+ perform_dsl_verification(outpath, constant_lookup)
155
169
  else
156
170
  purge_stale_dsl_rbi_files(rbi_files_to_purge)
157
171
 
@@ -506,60 +520,102 @@ module Tapioca
506
520
  end
507
521
  end
508
522
 
509
- sig { params(constant: Module, contents: String, outpath: Pathname).returns(T.nilable(Pathname)) }
510
- def compile_dsl_rbi(constant, contents, outpath: config.outpath)
523
+ sig do
524
+ params(constant_name: String, contents: String, outpath: Pathname, quiet: T::Boolean)
525
+ .returns(T.nilable(Pathname))
526
+ end
527
+ def compile_dsl_rbi(constant_name, contents, outpath: config.outpath, quiet: false)
511
528
  return if contents.nil?
512
529
 
513
- constant_name = Module.instance_method(:name).bind(constant).call
514
530
  rbi_name = constant_name.underscore + ".rbi"
515
531
  filename = outpath / rbi_name
516
532
 
517
533
  out = String.new
518
534
  out << rbi_header(
519
535
  "#{Config::DEFAULT_COMMAND} dsl #{constant_name}",
520
- reason: "dynamic methods in `#{constant.name}`"
536
+ reason: "dynamic methods in `#{constant_name}`"
521
537
  )
522
538
  out << contents
523
539
 
524
540
  FileUtils.mkdir_p(File.dirname(filename))
525
541
  File.write(filename, out)
526
- say("Wrote: ", [:green])
527
- say(filename)
542
+
543
+ unless quiet
544
+ say("Wrote: ", [:green])
545
+ say(filename)
546
+ end
528
547
 
529
548
  filename
530
549
  end
531
550
 
532
- sig { params(tmp_dir: Pathname).returns(T.nilable(String)) }
551
+ sig { params(tmp_dir: Pathname).returns(T::Hash[String, Symbol]) }
533
552
  def verify_dsl_rbi(tmp_dir:)
534
- existing_rbis = existing_rbi_filenames([]).sort
535
- new_rbis = existing_rbi_filenames([], path: tmp_dir).grep_v(/gem|shim/).sort
553
+ diff = {}
536
554
 
537
- return "New file(s) introduced." if existing_rbis.length != new_rbis.length
555
+ existing_rbis = rbi_files_in(config.outpath)
556
+ new_rbis = rbi_files_in(tmp_dir)
538
557
 
539
- desynced_files = []
558
+ added_files = (new_rbis - existing_rbis)
540
559
 
541
- (0..existing_rbis.length - 1).each do |i|
542
- desynced_files << new_rbis[i] unless FileUtils.identical?(existing_rbis[i], new_rbis[i])
560
+ added_files.each do |file|
561
+ diff[file] = :added
543
562
  end
544
563
 
545
- unless desynced_files.empty?
546
- filenames = desynced_files.map { |f| f.to_s.sub!(tmp_dir.to_s, "sorbet/rbi/dsl") }.join("\n - ")
564
+ removed_files = (existing_rbis - new_rbis)
547
565
 
548
- return "File(s) updated:\n - #{filenames}"
566
+ removed_files.each do |file|
567
+ diff[file] = :removed
549
568
  end
550
569
 
551
- nil
570
+ common_files = (existing_rbis & new_rbis)
571
+
572
+ changed_files = common_files.map do |filename|
573
+ filename unless FileUtils.identical?(config.outpath / filename, tmp_dir / filename)
574
+ end.compact
575
+
576
+ changed_files.each do |file|
577
+ diff[file] = :changed
578
+ end
579
+
580
+ diff
552
581
  end
553
582
 
554
- sig { params(dir: String).void }
555
- def perform_dsl_verification(dir)
556
- if (error = verify_dsl_rbi(tmp_dir: Pathname.new(dir)))
557
- say("RBI files are out-of-date, please run `#{Config::DEFAULT_COMMAND} dsl` to update.")
558
- say("Reason: ", [:red])
559
- say(error)
560
- exit(1)
561
- else
583
+ sig { params(cause: Symbol, files: T::Array[String]).returns(String) }
584
+ def build_error_for_files(cause, files)
585
+ filenames = files.map do |file|
586
+ config.outpath / file
587
+ end.join("\n - ")
588
+
589
+ " File(s) #{cause}:\n - #{filenames}"
590
+ end
591
+
592
+ sig { params(path: Pathname).returns(T::Array[Pathname]) }
593
+ def rbi_files_in(path)
594
+ Pathname.glob(path / "**/*.rbi").map do |file|
595
+ file.relative_path_from(path)
596
+ end.sort
597
+ end
598
+
599
+ sig { params(dir: Pathname, constant_lookup: T::Hash[String, String]).void }
600
+ def perform_dsl_verification(dir, constant_lookup)
601
+ diff = verify_dsl_rbi(tmp_dir: dir)
602
+
603
+ if diff.empty?
562
604
  say("Nothing to do, all RBIs are up-to-date.")
605
+ else
606
+ constants = T.unsafe(constant_lookup).values_at(*diff.keys).join(" ")
607
+
608
+ say("RBI files are out-of-date, please run:")
609
+ say(" `#{Config::DEFAULT_COMMAND} dsl #{constants}`")
610
+
611
+ say("")
612
+
613
+ say("Reason:", [:red])
614
+ diff.group_by(&:last).sort.each do |cause, diff_for_cause|
615
+ say(build_error_for_files(cause, diff_for_cause.map(&:first)))
616
+ end
617
+
618
+ exit(1)
563
619
  end
564
620
  ensure
565
621
  FileUtils.remove_entry(dir)
@@ -100,7 +100,7 @@ module Tapioca
100
100
  # the generic class `Foo[Bar]` is still a `Foo`. That is:
101
101
  # `Foo[Bar].new.is_a?(Foo)` should be true, which isn't the case
102
102
  # if we just clone the class. But subclassing works just fine.
103
- Class.new(constant)
103
+ create_sealed_safe_subclass(constant)
104
104
  else
105
105
  # This can only be a module and it is fine to just clone modules
106
106
  # since they can't have instances and will not have `is_a?` relationships.
@@ -150,6 +150,32 @@ module Tapioca
150
150
  )
151
151
  end
152
152
 
153
+ sig { params(constant: Class).returns(Class) }
154
+ def create_sealed_safe_subclass(constant)
155
+ # If the constant is not sealed let's just bail early.
156
+ # We just return a subclass of the constant.
157
+ return Class.new(constant) unless T::Private::Sealed.sealed_module?(constant)
158
+
159
+ # Since sealed classes can normally not be subclassed, we need to trick
160
+ # sealed classes into thinking that the generic type we are
161
+ # creating by subclassing is actually safe for sealed types.
162
+ #
163
+ # Get the filename the sealed class was declared in
164
+ decl_file = constant.instance_variable_get(:@sorbet_sealed_module_decl_file)
165
+ begin
166
+ # Clear the current declaration filename on the class
167
+ constant.remove_instance_variable(:@sorbet_sealed_module_decl_file)
168
+ # Make this file be the declaration filename so that Sorbet runtime
169
+ # does not shout at us for an invalid subclassing.
170
+ T.cast(constant, T::Helpers).sealed!
171
+ # return a subclass
172
+ Class.new(constant)
173
+ ensure
174
+ # Reinstate the original declaration filename
175
+ constant.instance_variable_set(:@sorbet_sealed_module_decl_file, decl_file)
176
+ end
177
+ end
178
+
153
179
  sig { params(constant: Module).returns(T::Hash[Integer, String]) }
154
180
  def lookup_or_initialize_type_variables(constant)
155
181
  @type_variables[object_id_of(constant)] ||= {}
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.4.19"
5
+ VERSION = "0.4.20"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tapioca
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.19
4
+ version: 0.4.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ufuk Kayserilioglu
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2021-03-24 00:00:00.000000000 Z
14
+ date: 2021-04-01 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler