tapioca 0.4.19 → 0.4.20

Sign up to get free protection for your applications and to get access to all the features.
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