tapioca 0.6.1 → 0.7.0
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 +4 -4
- data/Gemfile +13 -2
- data/README.md +79 -25
- data/Rakefile +10 -14
- data/lib/tapioca/cli.rb +66 -80
- data/lib/tapioca/{generators/base.rb → commands/command.rb} +17 -10
- data/lib/tapioca/{generators → commands}/dsl.rb +59 -45
- data/lib/tapioca/{generators → commands}/gem.rb +93 -30
- data/lib/tapioca/{generators → commands}/init.rb +9 -13
- data/lib/tapioca/{generators → commands}/require.rb +8 -10
- data/lib/tapioca/commands/todo.rb +84 -0
- data/lib/tapioca/commands.rb +13 -0
- data/lib/tapioca/dsl/compiler.rb +185 -0
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/aasm.rb +12 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_controller_helpers.rb +13 -20
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/action_mailer.rb +10 -8
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_job.rb +11 -9
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_attributes.rb +32 -24
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_model_secure_password.rb +10 -12
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_associations.rb +29 -35
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_columns.rb +26 -24
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_enum.rb +14 -12
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_fixtures.rb +10 -8
- data/lib/tapioca/dsl/compilers/active_record_relations.rb +712 -0
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_scope.rb +21 -20
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_record_typed_store.rb +12 -17
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_resource.rb +10 -8
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_storage.rb +11 -11
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_concern.rb +19 -14
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/active_support_current_attributes.rb +16 -21
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/config.rb +10 -8
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/frozen_record.rb +13 -11
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/identity_cache.rb +28 -25
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/mixed_in_class_attributes.rb +12 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/protobuf.rb +10 -8
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/rails_generators.rb +13 -14
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/sidekiq_worker.rb +14 -13
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/smart_properties.rb +12 -13
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/state_machines.rb +12 -10
- data/lib/tapioca/{compilers/dsl → dsl/compilers}/url_helpers.rb +16 -14
- data/lib/tapioca/dsl/compilers.rb +31 -0
- data/lib/tapioca/{compilers/dsl → dsl}/extensions/frozen_record.rb +2 -2
- data/lib/tapioca/dsl/helpers/active_record_column_type_helper.rb +114 -0
- data/lib/tapioca/dsl/helpers/active_record_constants_helper.rb +29 -0
- data/lib/tapioca/{compilers/dsl → dsl/helpers}/param_helper.rb +2 -2
- data/lib/tapioca/{compilers/dsl_compiler.rb → dsl/pipeline.rb} +41 -33
- data/lib/tapioca/gem/events.rb +120 -0
- data/lib/tapioca/gem/listeners/base.rb +48 -0
- data/lib/tapioca/gem/listeners/dynamic_mixins.rb +32 -0
- data/lib/tapioca/gem/listeners/methods.rb +183 -0
- data/lib/tapioca/gem/listeners/mixins.rb +101 -0
- data/lib/tapioca/gem/listeners/remove_empty_payload_scopes.rb +21 -0
- data/lib/tapioca/gem/listeners/sorbet_enums.rb +26 -0
- data/lib/tapioca/gem/listeners/sorbet_helpers.rb +29 -0
- data/lib/tapioca/gem/listeners/sorbet_props.rb +33 -0
- data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +23 -0
- data/lib/tapioca/gem/listeners/sorbet_signatures.rb +79 -0
- data/lib/tapioca/gem/listeners/sorbet_type_variables.rb +51 -0
- data/lib/tapioca/gem/listeners/subconstants.rb +37 -0
- data/lib/tapioca/gem/listeners/yard_doc.rb +96 -0
- data/lib/tapioca/gem/listeners.rb +16 -0
- data/lib/tapioca/gem/pipeline.rb +365 -0
- data/lib/tapioca/gemfile.rb +44 -20
- data/lib/tapioca/helpers/cli_helper.rb +16 -8
- data/lib/tapioca/helpers/config_helper.rb +113 -0
- data/lib/tapioca/helpers/rbi_helper.rb +17 -0
- data/lib/tapioca/helpers/shims_helper.rb +87 -0
- data/lib/tapioca/helpers/sorbet_helper.rb +57 -0
- data/lib/tapioca/helpers/test/dsl_compiler.rb +118 -0
- data/lib/tapioca/helpers/test/isolation.rb +1 -1
- data/lib/tapioca/helpers/test/template.rb +13 -2
- data/lib/tapioca/internal.rb +17 -10
- data/lib/tapioca/rbi_ext/model.rb +2 -48
- data/lib/tapioca/rbi_formatter.rb +37 -0
- data/lib/tapioca/runtime/dynamic_mixin_compiler.rb +227 -0
- data/lib/tapioca/runtime/generic_type_registry.rb +166 -0
- data/lib/tapioca/runtime/loader.rb +123 -0
- data/lib/tapioca/runtime/reflection.rb +153 -0
- data/lib/tapioca/runtime/trackers/autoload.rb +72 -0
- data/lib/tapioca/runtime/trackers/constant_definition.rb +44 -0
- data/lib/tapioca/runtime/trackers/mixin.rb +80 -0
- data/lib/tapioca/runtime/trackers/required_ancestor.rb +50 -0
- data/lib/tapioca/{trackers.rb → runtime/trackers.rb} +4 -3
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +110 -54
- data/lib/tapioca/sorbet_ext/name_patch.rb +7 -1
- data/lib/tapioca/{compilers → static}/requires_compiler.rb +5 -12
- data/lib/tapioca/static/symbol_loader.rb +83 -0
- data/lib/tapioca/static/symbol_table_parser.rb +63 -0
- data/lib/tapioca/version.rb +1 -1
- data/lib/tapioca.rb +2 -7
- metadata +82 -62
- data/lib/tapioca/compilers/dsl/active_record_relations.rb +0 -711
- data/lib/tapioca/compilers/dsl/base.rb +0 -179
- data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +0 -27
- data/lib/tapioca/compilers/dynamic_mixin_compiler.rb +0 -198
- data/lib/tapioca/compilers/sorbet.rb +0 -59
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +0 -780
- data/lib/tapioca/compilers/symbol_table/symbol_loader.rb +0 -90
- data/lib/tapioca/compilers/symbol_table_compiler.rb +0 -17
- data/lib/tapioca/compilers/todos_compiler.rb +0 -32
- data/lib/tapioca/generators/todo.rb +0 -76
- data/lib/tapioca/generators.rb +0 -9
- data/lib/tapioca/generic_type_registry.rb +0 -149
- data/lib/tapioca/helpers/active_record_column_type_helper.rb +0 -98
- data/lib/tapioca/loader.rb +0 -119
- data/lib/tapioca/reflection.rb +0 -151
- data/lib/tapioca/trackers/autoload.rb +0 -70
- data/lib/tapioca/trackers/constant_definition.rb +0 -42
- data/lib/tapioca/trackers/mixin.rb +0 -78
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Tapioca
|
|
5
|
+
module ShimsHelper
|
|
6
|
+
extend T::Sig
|
|
7
|
+
extend T::Helpers
|
|
8
|
+
|
|
9
|
+
requires_ancestor { Thor::Shell }
|
|
10
|
+
|
|
11
|
+
sig { params(index: RBI::Index, kind: String, dir: String).void }
|
|
12
|
+
def index_rbis(index, kind, dir)
|
|
13
|
+
return unless Dir.exist?(dir) && !Dir.empty?(dir)
|
|
14
|
+
|
|
15
|
+
say("Loading #{kind} RBIs from #{dir}... ")
|
|
16
|
+
files = Dir.glob("#{dir}/**/*.rbi").sort
|
|
17
|
+
|
|
18
|
+
trees = files.map do |file|
|
|
19
|
+
RBI::Parser.parse_file(file)
|
|
20
|
+
rescue RBI::ParseError => e
|
|
21
|
+
say_error("\nWarning: #{e} (#{e.location})", :yellow)
|
|
22
|
+
end.compact
|
|
23
|
+
|
|
24
|
+
index.visit_all(trees)
|
|
25
|
+
say(" Done", :green)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
sig { params(index: RBI::Index, shim_rbi_dir: String).returns(T::Hash[String, T::Array[RBI::Node]]) }
|
|
29
|
+
def duplicated_nodes_from_index(index, shim_rbi_dir)
|
|
30
|
+
duplicates = {}
|
|
31
|
+
say("Looking for duplicates... ")
|
|
32
|
+
index.keys.each do |key|
|
|
33
|
+
nodes = index[key]
|
|
34
|
+
next unless shims_have_duplicates?(nodes, shim_rbi_dir)
|
|
35
|
+
duplicates[key] = nodes
|
|
36
|
+
end
|
|
37
|
+
say(" Done", :green)
|
|
38
|
+
duplicates
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String).returns(T::Boolean) }
|
|
44
|
+
def shims_have_duplicates?(nodes, shim_rbi_dir)
|
|
45
|
+
return false if nodes.size == 1
|
|
46
|
+
|
|
47
|
+
shims = extract_shims(nodes, shim_rbi_dir)
|
|
48
|
+
return false if shims.empty?
|
|
49
|
+
|
|
50
|
+
props = extract_methods_and_attrs(shims)
|
|
51
|
+
return false if props.empty?
|
|
52
|
+
|
|
53
|
+
shims_with_sigs = extract_nodes_with_sigs(props)
|
|
54
|
+
shims_with_sigs.each do |shim|
|
|
55
|
+
shim_sigs = shim.sigs
|
|
56
|
+
|
|
57
|
+
extract_methods_and_attrs(nodes).each do |node|
|
|
58
|
+
next if node == shim
|
|
59
|
+
return true if shim_sigs.all? { |sig| node.sigs.include?(sig) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
return false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String).returns(T::Array[RBI::Node]) }
|
|
69
|
+
def extract_shims(nodes, shim_rbi_dir)
|
|
70
|
+
nodes.select do |node|
|
|
71
|
+
node.loc&.file&.start_with?(shim_rbi_dir)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
sig { params(nodes: T::Array[RBI::Node]).returns(T::Array[T.any(RBI::Method, RBI::Attr)]) }
|
|
76
|
+
def extract_methods_and_attrs(nodes)
|
|
77
|
+
T.cast(nodes.select do |node|
|
|
78
|
+
node.is_a?(RBI::Method) || node.is_a?(RBI::Attr)
|
|
79
|
+
end, T::Array[T.any(RBI::Method, RBI::Attr)])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
sig { params(nodes: T::Array[T.any(RBI::Method, RBI::Attr)]).returns(T::Array[T.any(RBI::Method, RBI::Attr)]) }
|
|
83
|
+
def extract_nodes_with_sigs(nodes)
|
|
84
|
+
nodes.reject { |node| node.sigs.empty? }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "pathname"
|
|
5
|
+
require "shellwords"
|
|
6
|
+
|
|
7
|
+
module Tapioca
|
|
8
|
+
module SorbetHelper
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
SORBET_GEM_SPEC = T.let(
|
|
12
|
+
::Gem::Specification.find_by_name("sorbet-static"),
|
|
13
|
+
::Gem::Specification
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
SORBET_BIN = T.let(
|
|
17
|
+
Pathname.new(SORBET_GEM_SPEC.full_gem_path) / "libexec" / "sorbet",
|
|
18
|
+
Pathname
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
SORBET_EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
|
|
22
|
+
|
|
23
|
+
FEATURE_REQUIREMENTS = T.let({
|
|
24
|
+
# First tag that includes https://github.com/sorbet/sorbet/pull/4706
|
|
25
|
+
to_ary_nil_support: ::Gem::Requirement.new(">= 0.5.9220"),
|
|
26
|
+
}.freeze, T::Hash[Symbol, ::Gem::Requirement])
|
|
27
|
+
|
|
28
|
+
class CmdResult < T::Struct
|
|
29
|
+
const :out, String
|
|
30
|
+
const :err, String
|
|
31
|
+
const :status, T::Boolean
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
sig { params(sorbet_args: String).returns(CmdResult) }
|
|
35
|
+
def sorbet(*sorbet_args)
|
|
36
|
+
out, err, status = Open3.capture3([sorbet_path, *sorbet_args].join(" "))
|
|
37
|
+
CmdResult.new(out: out, err: err, status: status.success? || false)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
sig { returns(String) }
|
|
41
|
+
def sorbet_path
|
|
42
|
+
sorbet_path = ENV.fetch(SORBET_EXE_PATH_ENV_VAR, SORBET_BIN)
|
|
43
|
+
sorbet_path = SORBET_BIN if sorbet_path.empty?
|
|
44
|
+
sorbet_path.to_s.shellescape
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
sig { params(feature: Symbol, version: T.nilable(::Gem::Version)).returns(T::Boolean) }
|
|
48
|
+
def sorbet_supports?(feature, version: nil)
|
|
49
|
+
version = SORBET_GEM_SPEC.version unless version
|
|
50
|
+
requirement = FEATURE_REQUIREMENTS[feature]
|
|
51
|
+
|
|
52
|
+
Kernel.raise "Invalid Sorbet feature #{feature}" unless requirement
|
|
53
|
+
|
|
54
|
+
requirement.satisfied_by?(version)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "tapioca/helpers/test/content"
|
|
5
|
+
require "tapioca/helpers/test/isolation"
|
|
6
|
+
require "tapioca/helpers/test/template"
|
|
7
|
+
|
|
8
|
+
module Tapioca
|
|
9
|
+
module Helpers
|
|
10
|
+
module Test
|
|
11
|
+
module DslCompiler
|
|
12
|
+
extend T::Sig
|
|
13
|
+
extend T::Helpers
|
|
14
|
+
|
|
15
|
+
include Isolation
|
|
16
|
+
include Content
|
|
17
|
+
include Template
|
|
18
|
+
|
|
19
|
+
requires_ancestor { Kernel }
|
|
20
|
+
|
|
21
|
+
sig { params(compiler_class: T.class_of(Tapioca::Dsl::Compiler)).void }
|
|
22
|
+
def use_dsl_compiler(compiler_class)
|
|
23
|
+
@context = T.let(CompilerContext.new(compiler_class), T.nilable(CompilerContext))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
sig { params(compiler_classes: T.class_of(Tapioca::Dsl::Compiler)).void }
|
|
27
|
+
def activate_other_dsl_compilers(*compiler_classes)
|
|
28
|
+
context.activate_other_dsl_compilers(compiler_classes)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sig { params(constant_name: T.any(Symbol, String)).returns(String) }
|
|
32
|
+
def rbi_for(constant_name)
|
|
33
|
+
context.rbi_for(constant_name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
sig { returns(T::Array[String]) }
|
|
37
|
+
def gathered_constants
|
|
38
|
+
context.gathered_constants
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
sig { returns(T::Array[String]) }
|
|
42
|
+
def generated_errors
|
|
43
|
+
context.errors
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
sig { returns(CompilerContext) }
|
|
47
|
+
def context
|
|
48
|
+
raise "Please call `use_dsl_compiler` before" unless @context
|
|
49
|
+
@context
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class CompilerContext
|
|
53
|
+
extend T::Sig
|
|
54
|
+
|
|
55
|
+
sig { returns(T.class_of(Tapioca::Dsl::Compiler)) }
|
|
56
|
+
attr_reader :compiler_class
|
|
57
|
+
|
|
58
|
+
sig { returns(T::Array[T.class_of(Tapioca::Dsl::Compiler)]) }
|
|
59
|
+
attr_reader :other_compiler_classes
|
|
60
|
+
|
|
61
|
+
sig { params(compiler_class: T.class_of(Tapioca::Dsl::Compiler)).void }
|
|
62
|
+
def initialize(compiler_class)
|
|
63
|
+
@compiler_class = compiler_class
|
|
64
|
+
@other_compiler_classes = T.let([], T::Array[T.class_of(Tapioca::Dsl::Compiler)])
|
|
65
|
+
@pipeline = T.let(nil, T.nilable(Tapioca::Dsl::Pipeline))
|
|
66
|
+
@errors = T.let([], T::Array[String])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
sig { params(compiler_classes: T::Array[T.class_of(Tapioca::Dsl::Compiler)]).void }
|
|
70
|
+
def activate_other_dsl_compilers(compiler_classes)
|
|
71
|
+
@other_compiler_classes = compiler_classes
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
sig { returns(T::Array[T.class_of(Tapioca::Dsl::Compiler)]) }
|
|
75
|
+
def activated_compiler_classes
|
|
76
|
+
[compiler_class, *other_compiler_classes]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
sig { returns(T::Array[String]) }
|
|
80
|
+
def gathered_constants
|
|
81
|
+
compiler_class.processable_constants.map(&:name).compact.sort
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
sig { params(constant_name: T.any(Symbol, String)).returns(String) }
|
|
85
|
+
def rbi_for(constant_name)
|
|
86
|
+
# Make sure this is a constant that we can handle.
|
|
87
|
+
unless gathered_constants.include?(constant_name.to_s)
|
|
88
|
+
raise "`#{constant_name}` is not processable by the `#{compiler_class}` compiler."
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
file = RBI::File.new(strictness: "strong")
|
|
92
|
+
constant = Object.const_get(constant_name)
|
|
93
|
+
|
|
94
|
+
compiler = compiler_class.new(pipeline, file.root, constant)
|
|
95
|
+
compiler.decorate
|
|
96
|
+
|
|
97
|
+
Tapioca::DEFAULT_RBI_FORMATTER.print_file(file)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
sig { returns(T::Array[String]) }
|
|
101
|
+
def errors
|
|
102
|
+
pipeline.errors
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
sig { returns(Tapioca::Dsl::Pipeline) }
|
|
108
|
+
def pipeline
|
|
109
|
+
@pipeline ||= Tapioca::Dsl::Pipeline.new(
|
|
110
|
+
requested_constants: [],
|
|
111
|
+
requested_compilers: activated_compiler_classes
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -100,7 +100,7 @@ module Tapioca
|
|
|
100
100
|
load_path_args << File.expand_path(p)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
child = IO.popen([env, Gem.ruby, *load_path_args, $PROGRAM_NAME, *ORIG_ARGV, test_opts])
|
|
103
|
+
child = IO.popen([env, ::Gem.ruby, *load_path_args, $PROGRAM_NAME, *ORIG_ARGV, test_opts])
|
|
104
104
|
|
|
105
105
|
begin
|
|
106
106
|
Process.wait(child.pid)
|
|
@@ -7,15 +7,18 @@ module Tapioca
|
|
|
7
7
|
module Helpers
|
|
8
8
|
module Test
|
|
9
9
|
module Template
|
|
10
|
-
include Kernel
|
|
11
10
|
extend T::Sig
|
|
11
|
+
extend T::Helpers
|
|
12
|
+
|
|
13
|
+
requires_ancestor { Kernel }
|
|
14
|
+
|
|
12
15
|
ERB_SUPPORTS_KVARGS = T.let(
|
|
13
16
|
::ERB.instance_method(:initialize).parameters.assoc(:key), T.nilable([Symbol, Symbol])
|
|
14
17
|
)
|
|
15
18
|
|
|
16
19
|
sig { params(selector: String).returns(T::Boolean) }
|
|
17
20
|
def ruby_version(selector)
|
|
18
|
-
Gem::Requirement.new(selector).satisfied_by?(Gem::Version.new(RUBY_VERSION))
|
|
21
|
+
::Gem::Requirement.new(selector).satisfied_by?(::Gem::Version.new(RUBY_VERSION))
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
sig { params(src: String).returns(String) }
|
|
@@ -28,6 +31,14 @@ module Tapioca
|
|
|
28
31
|
|
|
29
32
|
erb.result(binding)
|
|
30
33
|
end
|
|
34
|
+
|
|
35
|
+
sig { params(str: String, indent: Integer).returns(String) }
|
|
36
|
+
def indented(str, indent)
|
|
37
|
+
str.lines.map! do |line|
|
|
38
|
+
next line if line.chomp.empty?
|
|
39
|
+
(" " * indent) + line
|
|
40
|
+
end.join
|
|
41
|
+
end
|
|
31
42
|
end
|
|
32
43
|
end
|
|
33
44
|
end
|
data/lib/tapioca/internal.rb
CHANGED
|
@@ -2,20 +2,27 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "tapioca"
|
|
5
|
-
require "tapioca/
|
|
5
|
+
require "tapioca/runtime/reflection"
|
|
6
|
+
require "tapioca/runtime/trackers"
|
|
7
|
+
require "tapioca/runtime/dynamic_mixin_compiler"
|
|
8
|
+
require "tapioca/runtime/loader"
|
|
6
9
|
require "tapioca/sorbet_ext/generic_name_patch"
|
|
7
10
|
require "tapioca/sorbet_ext/fixed_hash_patch"
|
|
8
|
-
require "tapioca/generic_type_registry"
|
|
11
|
+
require "tapioca/runtime/generic_type_registry"
|
|
9
12
|
require "tapioca/helpers/cli_helper"
|
|
10
13
|
require "tapioca/helpers/config_helper"
|
|
11
|
-
require "tapioca/
|
|
14
|
+
require "tapioca/helpers/rbi_helper"
|
|
15
|
+
require "tapioca/helpers/shims_helper"
|
|
16
|
+
require "tapioca/helpers/sorbet_helper"
|
|
17
|
+
require "tapioca/commands"
|
|
12
18
|
require "tapioca/cli"
|
|
13
19
|
require "tapioca/gemfile"
|
|
14
20
|
require "tapioca/executor"
|
|
15
|
-
require "tapioca/
|
|
16
|
-
require "tapioca/
|
|
17
|
-
require "tapioca/
|
|
18
|
-
require "tapioca/
|
|
19
|
-
require "tapioca/
|
|
20
|
-
require "tapioca/
|
|
21
|
-
require "tapioca/
|
|
21
|
+
require "tapioca/static/symbol_table_parser"
|
|
22
|
+
require "tapioca/static/symbol_loader"
|
|
23
|
+
require "tapioca/gem/events"
|
|
24
|
+
require "tapioca/gem/listeners"
|
|
25
|
+
require "tapioca/gem/pipeline"
|
|
26
|
+
require "tapioca/dsl/compiler"
|
|
27
|
+
require "tapioca/dsl/pipeline"
|
|
28
|
+
require "tapioca/static/requires_compiler"
|
|
@@ -1,59 +1,13 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "rbi"
|
|
5
|
-
|
|
6
4
|
module RBI
|
|
7
|
-
class File
|
|
8
|
-
extend T::Sig
|
|
9
|
-
|
|
10
|
-
sig { returns(String) }
|
|
11
|
-
def transformed_string
|
|
12
|
-
transform_rbi!
|
|
13
|
-
string
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
sig { void }
|
|
17
|
-
def transform_rbi!
|
|
18
|
-
root.nest_singleton_methods!
|
|
19
|
-
root.nest_non_public_methods!
|
|
20
|
-
root.group_nodes!
|
|
21
|
-
root.sort_nodes!
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
sig do
|
|
25
|
-
params(
|
|
26
|
-
command: String,
|
|
27
|
-
reason: T.nilable(String),
|
|
28
|
-
display_heading: T::Boolean
|
|
29
|
-
).void
|
|
30
|
-
end
|
|
31
|
-
def set_file_header(command, reason: nil, display_heading: true)
|
|
32
|
-
return unless display_heading
|
|
33
|
-
comments << RBI::Comment.new("DO NOT EDIT MANUALLY")
|
|
34
|
-
comments << RBI::Comment.new("This is an autogenerated file for #{reason}.") unless reason.nil?
|
|
35
|
-
comments << RBI::Comment.new("Please instead update this file by running `#{command}`.")
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
sig { void }
|
|
39
|
-
def set_empty_body_content
|
|
40
|
-
comments << RBI::BlankLine.new unless comments.empty?
|
|
41
|
-
comments << RBI::Comment.new("THIS IS AN EMPTY RBI FILE.")
|
|
42
|
-
comments << RBI::Comment.new("see https://github.com/Shopify/tapioca/wiki/Manual-Gem-Requires")
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
sig { returns(T::Boolean) }
|
|
46
|
-
def empty?
|
|
47
|
-
root.empty?
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
5
|
class Tree
|
|
52
6
|
extend T::Sig
|
|
53
7
|
|
|
54
8
|
sig { params(constant: ::Module, block: T.nilable(T.proc.params(scope: Scope).void)).void }
|
|
55
9
|
def create_path(constant, &block)
|
|
56
|
-
constant_name = Tapioca::Reflection.name_of(constant)
|
|
10
|
+
constant_name = Tapioca::Runtime::Reflection.name_of(constant)
|
|
57
11
|
raise "given constant does not have a name" unless constant_name
|
|
58
12
|
|
|
59
13
|
instance = ::Module.const_get(constant_name)
|
|
@@ -137,7 +91,7 @@ module RBI
|
|
|
137
91
|
|
|
138
92
|
SPECIAL_METHOD_NAMES = T.let(
|
|
139
93
|
["!", "~", "+@", "**", "-@", "*", "/", "%", "+", "-", "<<", ">>", "&", "|", "^", "<", "<=", "=>", ">", ">=",
|
|
140
|
-
"==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`"].freeze,
|
|
94
|
+
"==", "===", "!=", "=~", "!~", "<=>", "[]", "[]=", "`",].freeze,
|
|
141
95
|
T::Array[String]
|
|
142
96
|
)
|
|
143
97
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Tapioca
|
|
5
|
+
class RBIFormatter < RBI::Formatter
|
|
6
|
+
extend T::Sig
|
|
7
|
+
|
|
8
|
+
sig do
|
|
9
|
+
params(
|
|
10
|
+
file: RBI::File,
|
|
11
|
+
command: String,
|
|
12
|
+
reason: T.nilable(String)
|
|
13
|
+
).void
|
|
14
|
+
end
|
|
15
|
+
def write_header!(file, command, reason: nil)
|
|
16
|
+
file.comments << RBI::Comment.new("DO NOT EDIT MANUALLY")
|
|
17
|
+
file.comments << RBI::Comment.new("This is an autogenerated file for #{reason}.") unless reason.nil?
|
|
18
|
+
file.comments << RBI::Comment.new("Please instead update this file by running `#{command}`.")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
sig { params(file: RBI::File).void }
|
|
22
|
+
def write_empty_body_comment!(file)
|
|
23
|
+
file.comments << RBI::BlankLine.new unless file.comments.empty?
|
|
24
|
+
file.comments << RBI::Comment.new("THIS IS AN EMPTY RBI FILE.")
|
|
25
|
+
file.comments << RBI::Comment.new("see https://github.com/Shopify/tapioca/wiki/Manual-Gem-Requires")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
DEFAULT_RBI_FORMATTER = T.let(RBIFormatter.new(
|
|
30
|
+
add_sig_templates: false,
|
|
31
|
+
group_nodes: true,
|
|
32
|
+
max_line_length: nil,
|
|
33
|
+
nest_singleton_methods: true,
|
|
34
|
+
nest_non_public_methods: true,
|
|
35
|
+
sort_nodes: true
|
|
36
|
+
), RBIFormatter)
|
|
37
|
+
end
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module Tapioca
|
|
5
|
+
module Runtime
|
|
6
|
+
class DynamicMixinCompiler
|
|
7
|
+
extend T::Sig
|
|
8
|
+
include Runtime::Reflection
|
|
9
|
+
|
|
10
|
+
sig { returns(T::Array[Module]) }
|
|
11
|
+
attr_reader :dynamic_extends, :dynamic_includes
|
|
12
|
+
|
|
13
|
+
sig { returns(T::Array[Symbol]) }
|
|
14
|
+
attr_reader :class_attribute_readers, :class_attribute_writers, :class_attribute_predicates
|
|
15
|
+
|
|
16
|
+
sig { returns(T::Array[Symbol]) }
|
|
17
|
+
attr_reader :instance_attribute_readers, :instance_attribute_writers, :instance_attribute_predicates
|
|
18
|
+
|
|
19
|
+
sig { params(constant: Module).void }
|
|
20
|
+
def initialize(constant)
|
|
21
|
+
@constant = constant
|
|
22
|
+
mixins_from_modules = {}.compare_by_identity
|
|
23
|
+
class_attribute_readers = T.let([], T::Array[Symbol])
|
|
24
|
+
class_attribute_writers = T.let([], T::Array[Symbol])
|
|
25
|
+
class_attribute_predicates = T.let([], T::Array[Symbol])
|
|
26
|
+
|
|
27
|
+
instance_attribute_readers = T.let([], T::Array[Symbol])
|
|
28
|
+
instance_attribute_writers = T.let([], T::Array[Symbol])
|
|
29
|
+
instance_attribute_predicates = T.let([], T::Array[Symbol])
|
|
30
|
+
|
|
31
|
+
Class.new do
|
|
32
|
+
# Override the `self.include` method
|
|
33
|
+
define_singleton_method(:include) do |mod|
|
|
34
|
+
# Take a snapshot of the list of singleton class ancestors
|
|
35
|
+
# before the actual include
|
|
36
|
+
before = singleton_class.ancestors
|
|
37
|
+
# Call the actual `include` method with the supplied module
|
|
38
|
+
super(mod).tap do
|
|
39
|
+
# Take a snapshot of the list of singleton class ancestors
|
|
40
|
+
# after the actual include
|
|
41
|
+
after = singleton_class.ancestors
|
|
42
|
+
# The difference is the modules that are added to the list
|
|
43
|
+
# of ancestors of the singleton class. Those are all the
|
|
44
|
+
# modules that were `extend`ed due to the `include` call.
|
|
45
|
+
#
|
|
46
|
+
# We record those modules on our lookup table keyed by
|
|
47
|
+
# the included module with the values being all the modules
|
|
48
|
+
# that that module pulls into the singleton class.
|
|
49
|
+
#
|
|
50
|
+
# We need to reverse the order, since the extend order should
|
|
51
|
+
# be the inverse of the ancestor order. That is, earlier
|
|
52
|
+
# extended modules would be later in the ancestor chain.
|
|
53
|
+
mixins_from_modules[mod] = (after - before).reverse!
|
|
54
|
+
end
|
|
55
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
|
56
|
+
# this is a best effort, bail if we can't perform this
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
define_singleton_method(:class_attribute) do |*attrs, **kwargs|
|
|
60
|
+
class_attribute_readers.concat(attrs)
|
|
61
|
+
class_attribute_writers.concat(attrs)
|
|
62
|
+
|
|
63
|
+
instance_predicate = kwargs.fetch(:instance_predicate, true)
|
|
64
|
+
instance_accessor = kwargs.fetch(:instance_accessor, true)
|
|
65
|
+
instance_reader = kwargs.fetch(:instance_reader, instance_accessor)
|
|
66
|
+
instance_writer = kwargs.fetch(:instance_writer, instance_accessor)
|
|
67
|
+
|
|
68
|
+
if instance_reader
|
|
69
|
+
instance_attribute_readers.concat(attrs)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
if instance_writer
|
|
73
|
+
instance_attribute_writers.concat(attrs)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if instance_predicate
|
|
77
|
+
class_attribute_predicates.concat(attrs)
|
|
78
|
+
|
|
79
|
+
if instance_reader
|
|
80
|
+
instance_attribute_predicates.concat(attrs)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
super(*attrs, **kwargs) if defined?(super)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# rubocop:disable Style/MissingRespondToMissing
|
|
88
|
+
T::Sig::WithoutRuntime.sig { params(symbol: Symbol, args: T.untyped).returns(T.untyped) }
|
|
89
|
+
def method_missing(symbol, *args)
|
|
90
|
+
# We need this here so that we can handle any random instance
|
|
91
|
+
# method calls on the fake including class that may be done by
|
|
92
|
+
# the included module during the `self.included` hook.
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class << self
|
|
96
|
+
extend T::Sig
|
|
97
|
+
|
|
98
|
+
T::Sig::WithoutRuntime.sig { params(symbol: Symbol, args: T.untyped).returns(T.untyped) }
|
|
99
|
+
def method_missing(symbol, *args)
|
|
100
|
+
# Similarly, we need this here so that we can handle any
|
|
101
|
+
# random class method calls on the fake including class
|
|
102
|
+
# that may be done by the included module during the
|
|
103
|
+
# `self.included` hook.
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
# rubocop:enable Style/MissingRespondToMissing
|
|
107
|
+
end.include(constant)
|
|
108
|
+
|
|
109
|
+
# The value that corresponds to the original included constant
|
|
110
|
+
# is the list of all dynamically extended modules because of that
|
|
111
|
+
# constant. We grab that value by deleting the key for the original
|
|
112
|
+
# constant.
|
|
113
|
+
@dynamic_extends = T.let(mixins_from_modules.delete(constant) || [], T::Array[Module])
|
|
114
|
+
|
|
115
|
+
# Since we deleted the original constant from the list of keys, all
|
|
116
|
+
# the keys that remain are the ones that are dynamically included modules
|
|
117
|
+
# during the include of the original constant.
|
|
118
|
+
@dynamic_includes = T.let(mixins_from_modules.keys, T::Array[Module])
|
|
119
|
+
|
|
120
|
+
@class_attribute_readers = T.let(class_attribute_readers, T::Array[Symbol])
|
|
121
|
+
@class_attribute_writers = T.let(class_attribute_writers, T::Array[Symbol])
|
|
122
|
+
@class_attribute_predicates = T.let(class_attribute_predicates, T::Array[Symbol])
|
|
123
|
+
|
|
124
|
+
@instance_attribute_readers = T.let(instance_attribute_readers, T::Array[Symbol])
|
|
125
|
+
@instance_attribute_writers = T.let(instance_attribute_writers, T::Array[Symbol])
|
|
126
|
+
@instance_attribute_predicates = T.let(instance_attribute_predicates, T::Array[Symbol])
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
sig { returns(T::Boolean) }
|
|
130
|
+
def empty_attributes?
|
|
131
|
+
@class_attribute_readers.empty? && @class_attribute_writers.empty?
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
sig { params(tree: RBI::Tree).void }
|
|
135
|
+
def compile_class_attributes(tree)
|
|
136
|
+
return if empty_attributes?
|
|
137
|
+
|
|
138
|
+
# Create a synthetic module to hold the generated class methods
|
|
139
|
+
tree << RBI::Module.new("GeneratedClassMethods") do |mod|
|
|
140
|
+
class_attribute_readers.each do |attribute|
|
|
141
|
+
mod << RBI::Method.new(attribute.to_s)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
class_attribute_writers.each do |attribute|
|
|
145
|
+
mod << RBI::Method.new("#{attribute}=") do |method|
|
|
146
|
+
method << RBI::Param.new("value")
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
class_attribute_predicates.each do |attribute|
|
|
151
|
+
mod << RBI::Method.new("#{attribute}?")
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Create a synthetic module to hold the generated instance methods
|
|
156
|
+
tree << RBI::Module.new("GeneratedInstanceMethods") do |mod|
|
|
157
|
+
instance_attribute_readers.each do |attribute|
|
|
158
|
+
mod << RBI::Method.new(attribute.to_s)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
instance_attribute_writers.each do |attribute|
|
|
162
|
+
mod << RBI::Method.new("#{attribute}=") do |method|
|
|
163
|
+
method << RBI::Param.new("value")
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
instance_attribute_predicates.each do |attribute|
|
|
168
|
+
mod << RBI::Method.new("#{attribute}?")
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Add a mixes_in_class_methods and include for the generated modules
|
|
173
|
+
tree << RBI::MixesInClassMethods.new("GeneratedClassMethods")
|
|
174
|
+
tree << RBI::Include.new("GeneratedInstanceMethods")
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
sig { params(tree: RBI::Tree).returns([T::Array[Module], T::Array[Module]]) }
|
|
178
|
+
def compile_mixes_in_class_methods(tree)
|
|
179
|
+
includes = dynamic_includes.map do |mod|
|
|
180
|
+
qname = qualified_name_of(mod)
|
|
181
|
+
|
|
182
|
+
next if qname.nil? || qname.empty?
|
|
183
|
+
next if filtered_mixin?(qname)
|
|
184
|
+
|
|
185
|
+
tree << RBI::Include.new(qname)
|
|
186
|
+
|
|
187
|
+
mod
|
|
188
|
+
end.compact
|
|
189
|
+
|
|
190
|
+
# If we can generate multiple mixes_in_class_methods, then we want to use all dynamic extends that are not the
|
|
191
|
+
# constant itself
|
|
192
|
+
mixed_in_class_methods = dynamic_extends.select do |mod|
|
|
193
|
+
mod != @constant && !module_included_by_another_dynamic_extend?(mod, dynamic_extends)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
return [[], []] if mixed_in_class_methods.empty?
|
|
197
|
+
|
|
198
|
+
mixed_in_class_methods.each do |mod|
|
|
199
|
+
qualified_name = qualified_name_of(mod)
|
|
200
|
+
|
|
201
|
+
next if qualified_name.nil? || qualified_name.empty?
|
|
202
|
+
next if filtered_mixin?(qualified_name)
|
|
203
|
+
|
|
204
|
+
tree << RBI::MixesInClassMethods.new(qualified_name)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
[mixed_in_class_methods, includes]
|
|
208
|
+
rescue
|
|
209
|
+
[[], []] # silence errors
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
sig { params(mod: Module, dynamic_extends: T::Array[Module]).returns(T::Boolean) }
|
|
213
|
+
def module_included_by_another_dynamic_extend?(mod, dynamic_extends)
|
|
214
|
+
dynamic_extends.any? do |dynamic_extend|
|
|
215
|
+
mod != dynamic_extend && ancestors_of(dynamic_extend).include?(mod)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
sig { params(qualified_mixin_name: String).returns(T::Boolean) }
|
|
220
|
+
def filtered_mixin?(qualified_mixin_name)
|
|
221
|
+
# filter T:: namespace mixins that aren't T::Props
|
|
222
|
+
# T::Props and subconstants have semantic value
|
|
223
|
+
qualified_mixin_name.start_with?("::T::") && !qualified_mixin_name.start_with?("::T::Props")
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|