tapioca 0.4.18 → 0.4.23

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ class String
5
+ extend T::Sig
6
+
7
+ sig { returns(String) }
8
+ def underscore
9
+ return self unless /[A-Z-]|::/.match?(self)
10
+
11
+ word = to_s.gsub("::", "/")
12
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
13
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
14
+ word.tr!("-", "_")
15
+ word.downcase!
16
+ word
17
+ end
18
+ end
@@ -108,8 +108,14 @@ module Tapioca
108
108
 
109
109
  sig { returns(T::Array[Pathname]) }
110
110
  def files
111
- @spec.full_require_paths.flat_map do |path|
112
- Pathname.glob((Pathname.new(path) / "**/*.rb").to_s)
111
+ if default_gem?
112
+ @spec.files.map do |file|
113
+ ruby_lib_dir.join(file)
114
+ end
115
+ else
116
+ @spec.full_require_paths.flat_map do |path|
117
+ Pathname.glob((Pathname.new(path) / "**/*.rb").to_s)
118
+ end
113
119
  end
114
120
  end
115
121
 
@@ -125,11 +131,25 @@ module Tapioca
125
131
 
126
132
  sig { params(path: String).returns(T::Boolean) }
127
133
  def contains_path?(path)
128
- to_realpath(path).start_with?(full_gem_path) || has_parent_gemspec?(path)
134
+ if default_gem?
135
+ files.any? { |file| file.to_s == to_realpath(path) }
136
+ else
137
+ to_realpath(path).start_with?(full_gem_path) || has_parent_gemspec?(path)
138
+ end
129
139
  end
130
140
 
131
141
  private
132
142
 
143
+ sig { returns(T::Boolean) }
144
+ def default_gem?
145
+ @spec.respond_to?(:default_gem?) && @spec.default_gem?
146
+ end
147
+
148
+ sig { returns(Pathname) }
149
+ def ruby_lib_dir
150
+ Pathname.new(RbConfig::CONFIG["rubylibdir"])
151
+ end
152
+
133
153
  sig { returns(String) }
134
154
  def version_string
135
155
  version = @spec.version.to_s
@@ -176,7 +196,7 @@ module Tapioca
176
196
 
177
197
  sig { returns(T::Boolean) }
178
198
  def gem_in_bundle_path?
179
- full_gem_path.start_with?(Bundler.bundle_path.to_s)
199
+ full_gem_path.start_with?(Bundler.bundle_path.to_s, Bundler.app_cache.to_s)
180
200
  end
181
201
  end
182
202
  end
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'pathname'
5
5
  require 'thor'
6
+ require "tapioca/core_ext/string"
6
7
 
7
8
  module Tapioca
8
9
  class Generator < ::Thor::Shell::Color
@@ -120,9 +121,10 @@ module Tapioca
120
121
  params(
121
122
  requested_constants: T::Array[String],
122
123
  should_verify: T::Boolean,
124
+ quiet: T::Boolean
123
125
  ).void
124
126
  end
125
- def build_dsl(requested_constants, should_verify: false)
127
+ def build_dsl(requested_constants, should_verify: false, quiet: false)
126
128
  load_application(eager_load: requested_constants.empty?)
127
129
  load_dsl_generators
128
130
 
@@ -133,7 +135,7 @@ module Tapioca
133
135
  end
134
136
  say("")
135
137
 
136
- outpath = should_verify ? Dir.mktmpdir : config.outpath
138
+ outpath = should_verify ? Pathname.new(Dir.mktmpdir) : config.outpath
137
139
  rbi_files_to_purge = existing_rbi_filenames(requested_constants)
138
140
 
139
141
  compiler = Compilers::DslCompiler.new(
@@ -145,8 +147,18 @@ module Tapioca
145
147
  )
146
148
 
147
149
  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
150
+ constant_name = Module.instance_method(:name).bind(constant).call
151
+
152
+ filename = compile_dsl_rbi(
153
+ constant_name,
154
+ contents,
155
+ outpath: outpath,
156
+ quiet: should_verify || quiet
157
+ )
158
+
159
+ if filename
160
+ rbi_files_to_purge.delete(filename)
161
+ end
150
162
  end
151
163
  say("")
152
164
 
@@ -274,7 +286,7 @@ module Tapioca
274
286
  def constantize(constant_names)
275
287
  constant_map = constant_names.map do |name|
276
288
  begin
277
- [name, name.constantize]
289
+ [name, Object.const_get(name)]
278
290
  rescue NameError
279
291
  [name, nil]
280
292
  end
@@ -506,60 +518,101 @@ module Tapioca
506
518
  end
507
519
  end
508
520
 
509
- sig { params(constant: Module, contents: String, outpath: Pathname).returns(T.nilable(Pathname)) }
510
- def compile_dsl_rbi(constant, contents, outpath: config.outpath)
521
+ sig do
522
+ params(constant_name: String, contents: String, outpath: Pathname, quiet: T::Boolean)
523
+ .returns(T.nilable(Pathname))
524
+ end
525
+ def compile_dsl_rbi(constant_name, contents, outpath: config.outpath, quiet: false)
511
526
  return if contents.nil?
512
527
 
513
- constant_name = Module.instance_method(:name).bind(constant).call
514
528
  rbi_name = constant_name.underscore + ".rbi"
515
529
  filename = outpath / rbi_name
516
530
 
517
531
  out = String.new
518
532
  out << rbi_header(
519
533
  "#{Config::DEFAULT_COMMAND} dsl #{constant_name}",
520
- reason: "dynamic methods in `#{constant.name}`"
534
+ reason: "dynamic methods in `#{constant_name}`"
521
535
  )
522
536
  out << contents
523
537
 
524
538
  FileUtils.mkdir_p(File.dirname(filename))
525
539
  File.write(filename, out)
526
- say("Wrote: ", [:green])
527
- say(filename)
540
+
541
+ unless quiet
542
+ say("Wrote: ", [:green])
543
+ say(filename)
544
+ end
528
545
 
529
546
  filename
530
547
  end
531
548
 
532
- sig { params(tmp_dir: Pathname).returns(T.nilable(String)) }
549
+ sig { params(tmp_dir: Pathname).returns(T::Hash[String, Symbol]) }
533
550
  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
551
+ diff = {}
536
552
 
537
- return "New file(s) introduced." if existing_rbis.length != new_rbis.length
553
+ existing_rbis = rbi_files_in(config.outpath)
554
+ new_rbis = rbi_files_in(tmp_dir)
538
555
 
539
- desynced_files = []
556
+ added_files = (new_rbis - existing_rbis)
540
557
 
541
- (0..existing_rbis.length - 1).each do |i|
542
- desynced_files << new_rbis[i] unless FileUtils.identical?(existing_rbis[i], new_rbis[i])
558
+ added_files.each do |file|
559
+ diff[file] = :added
543
560
  end
544
561
 
545
- unless desynced_files.empty?
546
- filenames = desynced_files.map { |f| f.to_s.sub!(tmp_dir.to_s, "sorbet/rbi/dsl") }.join("\n - ")
562
+ removed_files = (existing_rbis - new_rbis)
547
563
 
548
- return "File(s) updated:\n - #{filenames}"
564
+ removed_files.each do |file|
565
+ diff[file] = :removed
549
566
  end
550
567
 
551
- nil
568
+ common_files = (existing_rbis & new_rbis)
569
+
570
+ changed_files = common_files.map do |filename|
571
+ filename unless FileUtils.identical?(config.outpath / filename, tmp_dir / filename)
572
+ end.compact
573
+
574
+ changed_files.each do |file|
575
+ diff[file] = :changed
576
+ end
577
+
578
+ diff
579
+ end
580
+
581
+ sig { params(cause: Symbol, files: T::Array[String]).returns(String) }
582
+ def build_error_for_files(cause, files)
583
+ filenames = files.map do |file|
584
+ config.outpath / file
585
+ end.join("\n - ")
586
+
587
+ " File(s) #{cause}:\n - #{filenames}"
588
+ end
589
+
590
+ sig { params(path: Pathname).returns(T::Array[Pathname]) }
591
+ def rbi_files_in(path)
592
+ Pathname.glob(path / "**/*.rbi").map do |file|
593
+ file.relative_path_from(path)
594
+ end.sort
552
595
  end
553
596
 
554
- sig { params(dir: String).void }
597
+ sig { params(dir: Pathname).void }
555
598
  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
599
+ diff = verify_dsl_rbi(tmp_dir: dir)
600
+
601
+ if diff.empty?
562
602
  say("Nothing to do, all RBIs are up-to-date.")
603
+ else
604
+ say("RBI files are out-of-date. In your development environment, please run:", :green)
605
+ say(" `#{Config::DEFAULT_COMMAND} dsl`", [:green, :bold])
606
+ say("Once it is complete, be sure to commit and push any changes", :green)
607
+
608
+ say("")
609
+
610
+ say("Reason:", [:red])
611
+ diff.group_by(&:last).sort.each do |cause, diff_for_cause|
612
+ say(build_error_for_files(cause, diff_for_cause.map(&:first)))
613
+ end
614
+
615
+ exit(1)
563
616
  end
564
617
  ensure
565
618
  FileUtils.remove_entry(dir)
@@ -51,14 +51,12 @@ module Tapioca
51
51
  type_list = types.map { |type| T::Utils.coerce(type).name }.join(", ")
52
52
  name = "#{name_of(constant)}[#{type_list}]"
53
53
 
54
- # Create a clone of the constant with an overridden `name`
54
+ # Create a generic type with an overridden `name`
55
55
  # method that returns the name we constructed above.
56
56
  #
57
- # Also, we try to memoize the clone based on the name, so that
58
- # we don't have to keep recreating clones all the time.
59
- @generic_instances[name] ||= constant.clone.tap do |clone|
60
- clone.define_singleton_method(:name) { name }
61
- end
57
+ # Also, we try to memoize the generic type based on the name, so that
58
+ # we don't have to keep recreating them all the time.
59
+ @generic_instances[name] ||= create_generic_type(constant, name)
62
60
  end
63
61
 
64
62
  sig do
@@ -94,6 +92,31 @@ module Tapioca
94
92
 
95
93
  private
96
94
 
95
+ sig { params(constant: Module, name: String).returns(Module) }
96
+ def create_generic_type(constant, name)
97
+ generic_type = case constant
98
+ when Class
99
+ # For classes, we want to create a subclass, so that an instance of
100
+ # the generic class `Foo[Bar]` is still a `Foo`. That is:
101
+ # `Foo[Bar].new.is_a?(Foo)` should be true, which isn't the case
102
+ # if we just clone the class. But subclassing works just fine.
103
+ create_safe_subclass(constant)
104
+ else
105
+ # This can only be a module and it is fine to just clone modules
106
+ # since they can't have instances and will not have `is_a?` relationships.
107
+ # Moreover, we never `include`/`extend` any generic modules into the
108
+ # ancestor tree, so this doesn't become a problem with checking the
109
+ # instance of a class being `is_a?` of a module type.
110
+ constant.clone
111
+ end
112
+
113
+ # Let's set the `name` method to return the proper generic name
114
+ generic_type.define_singleton_method(:name) { name }
115
+
116
+ # Return the generic type we created
117
+ generic_type
118
+ end
119
+
97
120
  # This method is called from intercepted calls to `type_member` and `type_template`.
98
121
  # We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
99
122
  # instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
@@ -127,6 +150,35 @@ module Tapioca
127
150
  )
128
151
  end
129
152
 
153
+ sig { params(constant: Class).returns(Class) }
154
+ def create_safe_subclass(constant)
155
+ # Lookup the "inherited" class method
156
+ inherited_method = constant.method(:inherited)
157
+ # and the module that defines it
158
+ owner = inherited_method.owner
159
+
160
+ # If no one has overriden the inherited method yet, just subclass
161
+ return Class.new(constant) if Class == owner
162
+
163
+ begin
164
+ # Otherwise, some inherited method could be preventing us
165
+ # from creating subclasses, so let's override it and rescue
166
+ owner.send(:define_method, :inherited) do |s|
167
+ begin
168
+ inherited_method.call(s)
169
+ rescue
170
+ # Ignoring errors
171
+ end
172
+ end
173
+
174
+ # return a subclass
175
+ Class.new(constant)
176
+ ensure
177
+ # Reinstate the original inherited method back.
178
+ owner.send(:define_method, :inherited, inherited_method)
179
+ end
180
+ end
181
+
130
182
  sig { params(constant: Module).returns(T::Hash[Integer, String]) }
131
183
  def lookup_or_initialize_type_variables(constant)
132
184
  @type_variables[object_id_of(constant)] ||= {}
@@ -6,12 +6,20 @@ require "tapioca/loader"
6
6
  require "tapioca/constant_locator"
7
7
  require "tapioca/generic_type_registry"
8
8
  require "tapioca/sorbet_ext/generic_name_patch"
9
+ require "tapioca/sorbet_ext/fixed_hash_patch"
9
10
  require "tapioca/config"
10
11
  require "tapioca/config_builder"
11
12
  require "tapioca/generator"
12
13
  require "tapioca/cli"
13
14
  require "tapioca/cli/main"
14
15
  require "tapioca/gemfile"
16
+ require "tapioca/rbi/model"
17
+ require "tapioca/rbi/visitor"
18
+ require "tapioca/rbi/rewriters/nest_singleton_methods"
19
+ require "tapioca/rbi/rewriters/nest_non_public_methods"
20
+ require "tapioca/rbi/rewriters/group_nodes"
21
+ require "tapioca/rbi/rewriters/sort_nodes"
22
+ require "tapioca/rbi/printer"
15
23
  require "tapioca/compilers/sorbet"
16
24
  require "tapioca/compilers/requires_compiler"
17
25
  require "tapioca/compilers/symbol_table_compiler"
@@ -0,0 +1,405 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module RBI
6
+ class Node
7
+ extend T::Sig
8
+ extend T::Helpers
9
+
10
+ abstract!
11
+
12
+ sig { returns(T.nilable(Tree)) }
13
+ attr_accessor :parent_tree
14
+
15
+ sig { void }
16
+ def initialize
17
+ @parent_tree = nil
18
+ end
19
+
20
+ sig { void }
21
+ def detach
22
+ tree = parent_tree
23
+ return unless tree
24
+ tree.nodes.delete(self)
25
+ self.parent_tree = nil
26
+ end
27
+ end
28
+
29
+ class Tree < Node
30
+ extend T::Sig
31
+
32
+ sig { returns(T::Array[Node]) }
33
+ attr_reader :nodes
34
+
35
+ sig { void }
36
+ def initialize
37
+ super()
38
+ @nodes = T.let([], T::Array[Node])
39
+ end
40
+
41
+ sig { params(node: Node).void }
42
+ def <<(node)
43
+ node.parent_tree = self
44
+ @nodes << node
45
+ end
46
+
47
+ sig { returns(T::Boolean) }
48
+ def empty?
49
+ nodes.empty?
50
+ end
51
+ end
52
+
53
+ # Scopes
54
+
55
+ class Scope < Tree
56
+ extend T::Helpers
57
+
58
+ abstract!
59
+ end
60
+
61
+ class Module < Scope
62
+ extend T::Sig
63
+
64
+ sig { returns(String) }
65
+ attr_accessor :name
66
+
67
+ sig { params(name: String).void }
68
+ def initialize(name)
69
+ super()
70
+ @name = name
71
+ end
72
+ end
73
+
74
+ class Class < Scope
75
+ extend T::Sig
76
+
77
+ sig { returns(String) }
78
+ attr_accessor :name
79
+
80
+ sig { returns(T.nilable(String)) }
81
+ attr_accessor :superclass_name
82
+
83
+ sig { params(name: String, superclass_name: T.nilable(String)).void }
84
+ def initialize(name, superclass_name: nil)
85
+ super()
86
+ @name = name
87
+ @superclass_name = superclass_name
88
+ end
89
+ end
90
+
91
+ class SingletonClass < Scope
92
+ extend T::Sig
93
+
94
+ sig { void }
95
+ def initialize
96
+ super()
97
+ end
98
+ end
99
+
100
+ # Consts
101
+
102
+ class Const < Node
103
+ extend T::Sig
104
+
105
+ sig { returns(String) }
106
+ attr_reader :name, :value
107
+
108
+ sig { params(name: String, value: String).void }
109
+ def initialize(name, value)
110
+ super()
111
+ @name = name
112
+ @value = value
113
+ end
114
+ end
115
+
116
+ # Methods and args
117
+
118
+ class Method < Node
119
+ extend T::Sig
120
+
121
+ sig { returns(String) }
122
+ attr_accessor :name
123
+
124
+ sig { returns(T::Array[Param]) }
125
+ attr_reader :params
126
+
127
+ sig { returns(T::Boolean) }
128
+ attr_accessor :is_singleton
129
+
130
+ sig { returns(Visibility) }
131
+ attr_accessor :visibility
132
+
133
+ sig { returns(T::Array[Sig]) }
134
+ attr_accessor :sigs
135
+
136
+ sig do
137
+ params(
138
+ name: String,
139
+ params: T::Array[Param],
140
+ is_singleton: T::Boolean,
141
+ visibility: Visibility,
142
+ sigs: T::Array[Sig]
143
+ ).void
144
+ end
145
+ def initialize(name, params: [], is_singleton: false, visibility: Visibility::Public, sigs: [])
146
+ super()
147
+ @name = name
148
+ @params = params
149
+ @is_singleton = is_singleton
150
+ @visibility = visibility
151
+ @sigs = sigs
152
+ end
153
+
154
+ sig { params(param: Param).void }
155
+ def <<(param)
156
+ @params << param
157
+ end
158
+ end
159
+
160
+ class Param < Node
161
+ extend T::Sig
162
+
163
+ sig { returns(String) }
164
+ attr_reader :name
165
+
166
+ sig { params(name: String).void }
167
+ def initialize(name)
168
+ super()
169
+ @name = name
170
+ end
171
+ end
172
+
173
+ class OptParam < Param
174
+ extend T::Sig
175
+
176
+ sig { returns(String) }
177
+ attr_reader :value
178
+
179
+ sig { params(name: String, value: String).void }
180
+ def initialize(name, value)
181
+ super(name)
182
+ @value = value
183
+ end
184
+ end
185
+
186
+ class RestParam < Param; end
187
+ class KwParam < Param; end
188
+ class KwOptParam < OptParam; end
189
+ class KwRestParam < Param; end
190
+ class BlockParam < Param; end
191
+
192
+ # Mixins
193
+
194
+ class Mixin < Node
195
+ extend T::Sig
196
+ extend T::Helpers
197
+
198
+ abstract!
199
+
200
+ sig { returns(String) }
201
+ attr_reader :name
202
+
203
+ sig { params(name: String).void }
204
+ def initialize(name)
205
+ super()
206
+ @name = name
207
+ end
208
+ end
209
+
210
+ class Include < Mixin; end
211
+ class Extend < Mixin; end
212
+
213
+ # Visibility
214
+
215
+ class Visibility < Node
216
+ extend T::Sig
217
+ extend T::Helpers
218
+
219
+ abstract!
220
+
221
+ sig { returns(Symbol) }
222
+ attr_reader :visibility
223
+
224
+ sig { params(visibility: Symbol).void }
225
+ def initialize(visibility)
226
+ super()
227
+ @visibility = visibility
228
+ end
229
+
230
+ sig { returns(T::Boolean) }
231
+ def public?
232
+ visibility == :public
233
+ end
234
+
235
+ Public = T.let(Visibility.new(:public), Visibility)
236
+ Protected = T.let(Visibility.new(:protected), Visibility)
237
+ Private = T.let(Visibility.new(:private), Visibility)
238
+ end
239
+
240
+ # Sorbet's sigs
241
+
242
+ class Sig < Node
243
+ extend T::Sig
244
+
245
+ sig { returns(T::Array[SigParam]) }
246
+ attr_reader :params
247
+
248
+ sig { returns(T.nilable(String)) }
249
+ attr_accessor :return_type
250
+
251
+ sig { returns(T::Boolean) }
252
+ attr_accessor :is_abstract, :is_override, :is_overridable
253
+
254
+ sig { returns(T::Array[String]) }
255
+ attr_reader :type_params
256
+
257
+ sig do
258
+ params(
259
+ params: T::Array[SigParam],
260
+ return_type: T.nilable(String),
261
+ is_abstract: T::Boolean,
262
+ is_override: T::Boolean,
263
+ is_overridable: T::Boolean,
264
+ type_params: T::Array[String]
265
+ ).void
266
+ end
267
+ def initialize(
268
+ params: [],
269
+ return_type: nil,
270
+ is_abstract: false,
271
+ is_override: false,
272
+ is_overridable: false,
273
+ type_params: []
274
+ )
275
+ super()
276
+ @params = params
277
+ @return_type = return_type
278
+ @is_abstract = is_abstract
279
+ @is_override = is_override
280
+ @is_overridable = is_overridable
281
+ @type_params = type_params
282
+ end
283
+
284
+ sig { params(param: SigParam).void }
285
+ def <<(param)
286
+ @params << param
287
+ end
288
+ end
289
+
290
+ class SigParam < Node
291
+ extend T::Sig
292
+
293
+ sig { returns(String) }
294
+ attr_reader :name, :type
295
+
296
+ sig { params(name: String, type: String).void }
297
+ def initialize(name, type)
298
+ super()
299
+ @name = name
300
+ @type = type
301
+ end
302
+ end
303
+
304
+ # Sorbet's T::Struct
305
+
306
+ class TStruct < Class
307
+ extend T::Sig
308
+
309
+ sig { params(name: String).void }
310
+ def initialize(name)
311
+ super(name, superclass_name: "::T::Struct")
312
+ end
313
+ end
314
+
315
+ class TStructField < Node
316
+ extend T::Sig
317
+ extend T::Helpers
318
+
319
+ abstract!
320
+
321
+ sig { returns(String) }
322
+ attr_accessor :name, :type
323
+
324
+ sig { returns(T.nilable(String)) }
325
+ attr_accessor :default
326
+
327
+ sig do
328
+ params(
329
+ name: String,
330
+ type: String,
331
+ default: T.nilable(String)
332
+ ).void
333
+ end
334
+ def initialize(name, type, default: nil)
335
+ super()
336
+ @name = name
337
+ @type = type
338
+ @default = default
339
+ end
340
+ end
341
+
342
+ class TStructProp < TStructField; end
343
+ class TStructConst < TStructField; end
344
+
345
+ # Sorbet's T::Enum
346
+
347
+ class TEnum < Class
348
+ extend T::Sig
349
+
350
+ sig { params(name: String).void }
351
+ def initialize(name)
352
+ super(name, superclass_name: "::T::Enum")
353
+ end
354
+ end
355
+
356
+ class TEnumBlock < Node
357
+ extend T::Sig
358
+
359
+ sig { returns(T::Array[String]) }
360
+ attr_reader :names
361
+
362
+ sig { params(names: T::Array[String]).void }
363
+ def initialize(names = [])
364
+ super()
365
+ @names = names
366
+ end
367
+
368
+ sig { returns(T::Boolean) }
369
+ def empty?
370
+ names.empty?
371
+ end
372
+ end
373
+
374
+ # Sorbet's misc.
375
+
376
+ class Helper < Node
377
+ extend T::Helpers
378
+
379
+ sig { returns(String) }
380
+ attr_reader :name
381
+
382
+ sig { params(name: String).void }
383
+ def initialize(name)
384
+ super()
385
+ @name = name
386
+ end
387
+ end
388
+
389
+ class TypeMember < Node
390
+ extend T::Sig
391
+
392
+ sig { returns(String) }
393
+ attr_reader :name, :value
394
+
395
+ sig { params(name: String, value: String).void }
396
+ def initialize(name, value)
397
+ super()
398
+ @name = name
399
+ @value = value
400
+ end
401
+ end
402
+
403
+ class MixesInClassMethods < Mixin; end
404
+ end
405
+ end