rbs_activerecord 1.0.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 +7 -0
- data/.rubocop.yml +22 -0
- data/.vscode/extensions.json +6 -0
- data/.vscode/settings.json +12 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +49 -0
- data/Rakefile +14 -0
- data/Steepfile +9 -0
- data/lib/generators/rbs_activerecord/install_generator.rb +21 -0
- data/lib/rbs_activerecord/generator/active_storage/instance_methods.rb +57 -0
- data/lib/rbs_activerecord/generator/active_storage/scopes.rb +39 -0
- data/lib/rbs_activerecord/generator/associations.rb +118 -0
- data/lib/rbs_activerecord/generator/attributes.rb +95 -0
- data/lib/rbs_activerecord/generator/delegated_type/instance_methods.rb +66 -0
- data/lib/rbs_activerecord/generator/delegated_type/scopes.rb +44 -0
- data/lib/rbs_activerecord/generator/enum/base.rb +63 -0
- data/lib/rbs_activerecord/generator/enum/instance_methods.rb +51 -0
- data/lib/rbs_activerecord/generator/enum/scopes.rb +51 -0
- data/lib/rbs_activerecord/generator/pluck_overloads.rb +41 -0
- data/lib/rbs_activerecord/generator/scopes.rb +99 -0
- data/lib/rbs_activerecord/generator/secure_password.rb +54 -0
- data/lib/rbs_activerecord/generator.rb +143 -0
- data/lib/rbs_activerecord/model.rb +33 -0
- data/lib/rbs_activerecord/parser/evaluator.rb +48 -0
- data/lib/rbs_activerecord/parser/visitor.rb +67 -0
- data/lib/rbs_activerecord/parser.rb +30 -0
- data/lib/rbs_activerecord/rake_task.rb +61 -0
- data/lib/rbs_activerecord/utils.rb +58 -0
- data/lib/rbs_activerecord/version.rb +5 -0
- data/lib/rbs_activerecord.rb +27 -0
- data/rbs_collection.lock.yaml +352 -0
- data/rbs_collection.yaml +18 -0
- data/sig/generators/rbs_activerecord/install_generator.rbs +7 -0
- data/sig/rbs_activerecord/generator/active_storage/instance_methods.rbs +23 -0
- data/sig/rbs_activerecord/generator/active_storage/scopes.rbs +23 -0
- data/sig/rbs_activerecord/generator/associations.rbs +29 -0
- data/sig/rbs_activerecord/generator/attributes.rbs +28 -0
- data/sig/rbs_activerecord/generator/delegated_type/instance_methods.rbs +31 -0
- data/sig/rbs_activerecord/generator/delegated_type/scopes.rbs +26 -0
- data/sig/rbs_activerecord/generator/enum/base.rbs +19 -0
- data/sig/rbs_activerecord/generator/enum/instance_methods.rbs +26 -0
- data/sig/rbs_activerecord/generator/enum/scopes.rbs +26 -0
- data/sig/rbs_activerecord/generator/pluck_overloads.rbs +23 -0
- data/sig/rbs_activerecord/generator/scopes.rbs +27 -0
- data/sig/rbs_activerecord/generator/secure_password.rbs +22 -0
- data/sig/rbs_activerecord/generator.rbs +34 -0
- data/sig/rbs_activerecord/model.rbs +26 -0
- data/sig/rbs_activerecord/parser/evaluator.rbs +12 -0
- data/sig/rbs_activerecord/parser/visitor.rbs +27 -0
- data/sig/rbs_activerecord/parser.rbs +15 -0
- data/sig/rbs_activerecord/rake_task.rbs +19 -0
- data/sig/rbs_activerecord/utils.rbs +14 -0
- data/sig/rbs_activerecord/version.rbs +5 -0
- data/sig/rbs_activerecord.rbs +6 -0
- metadata +142 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
module DelegatedType
|
6
|
+
class Scopes
|
7
|
+
attr_reader :model #: RbsActiverecord::Model
|
8
|
+
attr_reader :declarations #: Array[Prism::CallNode]
|
9
|
+
|
10
|
+
# @rbs model: RbsActiverecord::Model
|
11
|
+
# @rbs declarations: Hash[String, Array[Prism::CallNode]]
|
12
|
+
def initialize(model, declarations) #: void
|
13
|
+
@model = model
|
14
|
+
@declarations = declarations.fetch(model.klass.name.to_s, [])
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate #: String
|
18
|
+
<<~RBS.strip
|
19
|
+
module GeneratedDelegatedTypeScopeMethods[Relation]
|
20
|
+
#{delegated_types.map { |node| delegated_type(node) }.join("\n")}
|
21
|
+
end
|
22
|
+
RBS
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def delegated_types #: Array[Prism::CallNode]
|
28
|
+
declarations.select { |node| node.name == :delegated_type }
|
29
|
+
end
|
30
|
+
|
31
|
+
# @rbs node: Prism::CallNode
|
32
|
+
def delegated_type(node) #: String
|
33
|
+
arguments = node.arguments&.arguments || []
|
34
|
+
name, options = arguments.map { |arg| Parser.eval_node(arg) } #: [String?, Hash[Symbol, untyped]]
|
35
|
+
return "" unless name
|
36
|
+
|
37
|
+
options[:types].map do |type|
|
38
|
+
"def #{type.tableize}: () -> Relation"
|
39
|
+
end.join("\n")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
module Enum
|
6
|
+
class Base
|
7
|
+
private
|
8
|
+
|
9
|
+
# @rbs node: Prism::CallNode
|
10
|
+
def parse_arguments(node) #: [String?, Array[String | Symbol], Hash[Symbol, untyped]] # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
11
|
+
arguments = node.arguments&.arguments || []
|
12
|
+
args = arguments.map { |arg| Parser.eval_node(arg) }
|
13
|
+
return nil, [], {} if args.empty?
|
14
|
+
|
15
|
+
name = args[0] #: String?
|
16
|
+
|
17
|
+
case args[1]
|
18
|
+
when Array
|
19
|
+
values = args[1]
|
20
|
+
options = args[2] || {}
|
21
|
+
when Hash
|
22
|
+
values = args[1].keys
|
23
|
+
options = args[2] || {}
|
24
|
+
else
|
25
|
+
values = []
|
26
|
+
options = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
[name, values, options]
|
30
|
+
end
|
31
|
+
|
32
|
+
# @rbs name: String
|
33
|
+
# @rbs value: untyped
|
34
|
+
# @rbs options: Hash[Symbol, untyped]
|
35
|
+
def enum_method_name(name, value, options) #: String
|
36
|
+
components = [] #: Array[String | Symbol]
|
37
|
+
|
38
|
+
case options[:prefix]
|
39
|
+
when true
|
40
|
+
components << name
|
41
|
+
when String, Symbol
|
42
|
+
components << options[:prefix]
|
43
|
+
end
|
44
|
+
|
45
|
+
components << value
|
46
|
+
|
47
|
+
case options[:suffix]
|
48
|
+
when true
|
49
|
+
components << name
|
50
|
+
when String, Symbol
|
51
|
+
components << options[:suffix]
|
52
|
+
end
|
53
|
+
|
54
|
+
enum_method_name = components.join("_")
|
55
|
+
|
56
|
+
# Make enum methods friendly
|
57
|
+
# refs: https://github.com/rails/rails/blob/v8.0.0/activerecord/lib/active_record/enum.rb#L270
|
58
|
+
enum_method_name.gsub(/[\W&&[:ascii:]]+/, "_")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
module Enum
|
6
|
+
class InstanceMethods < Base
|
7
|
+
attr_reader :model #: RbsActiverecord::Model
|
8
|
+
attr_reader :declarations #: Array[Prism::CallNode]
|
9
|
+
|
10
|
+
# @rbs model: RbsActiverecord::Model
|
11
|
+
# @rbs declarations: Hash[String, Array[Prism::CallNode]]
|
12
|
+
def initialize(model, declarations) #: void
|
13
|
+
@model = model
|
14
|
+
@declarations = declarations.fetch(model.klass.name.to_s, [])
|
15
|
+
|
16
|
+
super()
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate #: String
|
20
|
+
<<~RBS.strip
|
21
|
+
module GeneratedEnumInstanceMethods
|
22
|
+
#{enums.map { |node| enum(node) }.join("\n")}
|
23
|
+
end
|
24
|
+
RBS
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def enums #: Array[Prism::CallNode]
|
30
|
+
declarations.select { |node| node.name == :enum }
|
31
|
+
end
|
32
|
+
|
33
|
+
# @rbs node: Prism::CallNode
|
34
|
+
def enum(node) #: String
|
35
|
+
name, values, options = parse_arguments(node)
|
36
|
+
|
37
|
+
return "" unless name
|
38
|
+
return "" unless options.fetch(:instance_methods, true)
|
39
|
+
|
40
|
+
values.map do |value|
|
41
|
+
method_name = enum_method_name(name, value, options)
|
42
|
+
<<~RBS
|
43
|
+
def #{method_name}!: () -> void
|
44
|
+
def #{method_name}?: () -> bool
|
45
|
+
RBS
|
46
|
+
end.join("\n")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
module Enum
|
6
|
+
class Scopes < Base
|
7
|
+
attr_reader :model #: RbsActiverecord::Model
|
8
|
+
attr_reader :declarations #: Array[Prism::CallNode]
|
9
|
+
|
10
|
+
# @rbs model: RbsActiverecord::Model
|
11
|
+
# @rbs declarations: Hash[String, Array[Prism::CallNode]]
|
12
|
+
def initialize(model, declarations) #: void
|
13
|
+
@model = model
|
14
|
+
@declarations = declarations.fetch(model.klass.name.to_s, [])
|
15
|
+
|
16
|
+
super()
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate #: String
|
20
|
+
<<~RBS.strip
|
21
|
+
module GeneratedEnumScopeMethods[Relation]
|
22
|
+
#{enums.map { |node| enum(node) }.join("\n")}
|
23
|
+
end
|
24
|
+
RBS
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def enums #: Array[Prism::CallNode]
|
30
|
+
declarations.select { |node| node.name == :enum }
|
31
|
+
end
|
32
|
+
|
33
|
+
# @rbs node: Prism::CallNode
|
34
|
+
def enum(node) #: String
|
35
|
+
name, values, options = parse_arguments(node)
|
36
|
+
|
37
|
+
return "" unless name
|
38
|
+
return "" unless options.fetch(:scopes, true)
|
39
|
+
|
40
|
+
values.map do |value|
|
41
|
+
method_name = enum_method_name(name, value, options)
|
42
|
+
<<~RBS
|
43
|
+
def #{method_name}: () -> Relation
|
44
|
+
def not_#{method_name}: () -> Relation
|
45
|
+
RBS
|
46
|
+
end.join("\n")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
class PluckOverloads
|
6
|
+
include Utils
|
7
|
+
|
8
|
+
attr_reader :model #: RbsActiverecord::Model
|
9
|
+
|
10
|
+
# @rbs model: RbsActiverecord::Model
|
11
|
+
def initialize(model) #: void
|
12
|
+
@model = model
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate #: String
|
16
|
+
<<~RBS.strip
|
17
|
+
module GeneratedPluckOverloads
|
18
|
+
def pluck: #{overloads.join(" | ")}
|
19
|
+
| (::Symbol | ::String | ::Arel::Nodes::t column) -> ::Array[untyped]
|
20
|
+
| (*::Symbol | ::String | ::Arel::Nodes::t columns) -> ::Array[::Array[untyped]]
|
21
|
+
end
|
22
|
+
RBS
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def overloads #: Array[String]
|
28
|
+
model.columns.map do |col|
|
29
|
+
base_type = enum?(col) ? "::String" : sql_type_to_class(col.type)
|
30
|
+
column_type = col.null ? "#{base_type}?" : base_type
|
31
|
+
"(:#{col.name} | \"#{col.name}\") -> ::Array[#{column_type}]"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @rbs column: untyped
|
36
|
+
def enum?(column) #: bool
|
37
|
+
model.defined_enums.key? column.name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
class Scopes
|
6
|
+
attr_reader :model #: RbsActiverecord::Model
|
7
|
+
attr_reader :declarations #: Array[Prism::CallNode]
|
8
|
+
|
9
|
+
# @rbs model: RbsActiverecord::Model
|
10
|
+
# @rbs delarations: Hash[String, Array[Prism::CallNode]]
|
11
|
+
def initialize(model, declarations) #: void
|
12
|
+
@model = model
|
13
|
+
@declarations = declarations.fetch(model.klass.name, [])
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate #: String
|
17
|
+
<<~RBS.strip
|
18
|
+
module GeneratedScopeMethods[Relation]
|
19
|
+
#{scopes.map { |node| scope(node) }.join("\n")}
|
20
|
+
end
|
21
|
+
RBS
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def scopes #: Array[Prism::CallNode]
|
27
|
+
declarations.select { |node| node.name == :scope }
|
28
|
+
end
|
29
|
+
|
30
|
+
# @rbs node: Prism::CallNode
|
31
|
+
def scope(node) #: String # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
32
|
+
args = node.arguments&.arguments
|
33
|
+
return "" unless args
|
34
|
+
|
35
|
+
name = args[0] #: Prism::SymbolNode
|
36
|
+
body = args[1]
|
37
|
+
|
38
|
+
case body
|
39
|
+
when Prism::LambdaNode
|
40
|
+
params = lambda_params(body)
|
41
|
+
"def #{name.value}: (#{params}) -> Relation"
|
42
|
+
when Prism::CallNode
|
43
|
+
if (body.name == :lambda || body.name == :proc) && body.block
|
44
|
+
block = body.block #: Prism::BlockNode
|
45
|
+
params = lambda_params(block)
|
46
|
+
"def #{name.value}: (#{params}) -> Relation"
|
47
|
+
else
|
48
|
+
"def #{name.value}: (?) -> Relation"
|
49
|
+
end
|
50
|
+
else
|
51
|
+
"def #{name.value}: (?) -> Relation"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @rbs node: Prism::LambdaNode | Prism::BlockNode
|
56
|
+
def lambda_params(node) #: String # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
57
|
+
# @type var params: Prism::ParametersNode?
|
58
|
+
params = node.parameters&.parameters # steep:ignore
|
59
|
+
return "" unless params
|
60
|
+
|
61
|
+
args = [] #: Array[String]
|
62
|
+
|
63
|
+
params.requireds.each do |required|
|
64
|
+
case required
|
65
|
+
when Prism::RequiredParameterNode
|
66
|
+
args << "untyped #{required.name}"
|
67
|
+
when Prism::MultiTargetNode
|
68
|
+
args << "untyped"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
params.optionals.each do |optional|
|
72
|
+
args << "?untyped #{optional.name}"
|
73
|
+
end
|
74
|
+
case params.rest
|
75
|
+
when Prism::RestParameterNode
|
76
|
+
args << "*untyped #{params.rest.name}"
|
77
|
+
when Prism::ImplicitRestNode
|
78
|
+
args << "*untyped"
|
79
|
+
end
|
80
|
+
params.keywords.each do |keyword|
|
81
|
+
case keyword
|
82
|
+
when Prism::RequiredKeywordParameterNode
|
83
|
+
args << "#{keyword.name}: untyped"
|
84
|
+
when Prism::OptionalKeywordParameterNode
|
85
|
+
args << "?#{keyword.name}: untyped"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
case params.keyword_rest
|
89
|
+
when Prism::KeywordRestParameterNode
|
90
|
+
args << "**untyped #{params.keyword_rest.name}"
|
91
|
+
when Prism::NoKeywordsParameterNode
|
92
|
+
args << "**untyped"
|
93
|
+
end
|
94
|
+
|
95
|
+
args.join(", ")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
class Generator
|
5
|
+
class SecurePassword
|
6
|
+
attr_reader :model #: RbsActiverecord::Model
|
7
|
+
|
8
|
+
# @rbs model: RbsActiverecord::Model
|
9
|
+
def initialize(model) #: void
|
10
|
+
@model = model
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate #: String
|
14
|
+
<<~RBS
|
15
|
+
module GeneratedSecurePasswordMethods
|
16
|
+
#{methods}
|
17
|
+
end
|
18
|
+
RBS
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def methods #: String
|
24
|
+
secure_password_attributes.map do |name|
|
25
|
+
<<~RBS
|
26
|
+
attr_reader #{name}: String?
|
27
|
+
attr_accessor #{name}_confirmation: String
|
28
|
+
attr_accessor #{name}_challenge: String
|
29
|
+
|
30
|
+
def #{name}=: (String) -> String
|
31
|
+
def #{name}_salt: () -> String
|
32
|
+
def authenticate_#{name}: (String) -> (instance | false)
|
33
|
+
|
34
|
+
#{"alias authenticate authenticate_password" if name == "password"}
|
35
|
+
RBS
|
36
|
+
end.join("\n")
|
37
|
+
end
|
38
|
+
|
39
|
+
def secure_password_attributes #: Array[String]
|
40
|
+
return [] unless secure_password?
|
41
|
+
|
42
|
+
model.klass.instance_methods.grep(/authenticate_/).filter_map do |method|
|
43
|
+
next if model.klass.instance_methods.include?(:"#{method}_confirmation")
|
44
|
+
|
45
|
+
method.to_s.split("_", 2).last
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def secure_password? #: bool
|
50
|
+
model.klass.ancestors.any?(::ActiveModel::SecurePassword::InstanceMethodsOnActivation)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
|
5
|
+
module RbsActiverecord
|
6
|
+
class Generator
|
7
|
+
include Utils
|
8
|
+
|
9
|
+
attr_reader :klass #: singleton(ActiveRecord::Base)
|
10
|
+
attr_reader :klass_name #: String
|
11
|
+
attr_reader :model #: RbsActiverecord::Model
|
12
|
+
|
13
|
+
# @rbs klass: singleton(ActiveRecord::Base)
|
14
|
+
def initialize(klass) #: void
|
15
|
+
@klass = klass
|
16
|
+
@klass_name = klass.name || ""
|
17
|
+
@model = Model.new(klass)
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate #: String # rubocop:disable Metrics/AbcSize
|
21
|
+
format <<~RBS
|
22
|
+
#{header}
|
23
|
+
#{Attributes.new(model).generate}
|
24
|
+
#{Associations.new(model).generate}
|
25
|
+
|
26
|
+
#{ActiveStorage::InstanceMethods.new(model).generate}
|
27
|
+
#{ActiveStorage::Scopes.new(model).generate}
|
28
|
+
#{PluckOverloads.new(model).generate}
|
29
|
+
#{SecurePassword.new(model).generate}
|
30
|
+
|
31
|
+
#{DelegatedType::InstanceMethods.new(model, declarations).generate}
|
32
|
+
#{DelegatedType::Scopes.new(model, declarations).generate}
|
33
|
+
#{Enum::InstanceMethods.new(model, declarations).generate}
|
34
|
+
#{Enum::Scopes.new(model, declarations).generate}
|
35
|
+
#{Scopes.new(model, declarations).generate}
|
36
|
+
|
37
|
+
class ActiveRecord_Relation < ::ActiveRecord::Relation
|
38
|
+
include ::Enumerable[#{klass_name}]
|
39
|
+
include ::ActiveRecord::Relation::Methods[#{klass_name}, #{primary_key_type}]
|
40
|
+
#{relation_methods}
|
41
|
+
include GeneratedPluckOverloads
|
42
|
+
end
|
43
|
+
|
44
|
+
class ActiveRecord_Associations_CollectionProxy < ::ActiveRecord::Associations::CollectionProxy
|
45
|
+
include ::Enumerable[#{klass_name}]
|
46
|
+
include ::ActiveRecord::Relation::Methods[#{klass_name}, #{primary_key_type}]
|
47
|
+
#{relation_methods}
|
48
|
+
include GeneratedPluckOverloads
|
49
|
+
end
|
50
|
+
|
51
|
+
extend ::ActiveRecord::Base::ClassMethods[#{klass_name}, #{klass_name}::ActiveRecord_Relation, #{primary_key_type}]
|
52
|
+
#{scope_class_methods}
|
53
|
+
extend GeneratedPluckOverloads
|
54
|
+
|
55
|
+
include GeneratedActiveStorageInstanceMethods
|
56
|
+
include GeneratedAttributeMethods
|
57
|
+
include GeneratedAssociationMethods
|
58
|
+
include GeneratedDelegatedTypeInstanceMethods
|
59
|
+
include GeneratedEnumInstanceMethods
|
60
|
+
include GeneratedSecurePasswordMethods
|
61
|
+
#{footer}
|
62
|
+
RBS
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def primary_key_type #: String
|
68
|
+
primary_key_type_for(klass)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @rbs @declarations: Hash[String, Array[Prism::CallNode]]
|
72
|
+
|
73
|
+
def declarations #: Hash[String, Array[Prism::CallNode]]
|
74
|
+
@declarations ||= begin
|
75
|
+
filename = Rails.root.join(model.filename)
|
76
|
+
if filename.exist?
|
77
|
+
Parser.parse(filename.to_s)
|
78
|
+
else
|
79
|
+
{}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def header #: String
|
85
|
+
namespace = +""
|
86
|
+
klass_name.split("::").map do |mod_name|
|
87
|
+
namespace += "::#{mod_name}"
|
88
|
+
mod_object = Object.const_get(namespace)
|
89
|
+
case mod_object
|
90
|
+
when Class
|
91
|
+
superclass = mod_object.superclass
|
92
|
+
superclass_name = superclass&.name || "Object"
|
93
|
+
|
94
|
+
"class #{mod_name} < ::#{superclass_name}"
|
95
|
+
when Module
|
96
|
+
"module #{mod_name}"
|
97
|
+
else
|
98
|
+
raise "unreachable"
|
99
|
+
end
|
100
|
+
end.join("\n")
|
101
|
+
end
|
102
|
+
|
103
|
+
def footer #: String
|
104
|
+
"end\n" * klass.module_parents.size
|
105
|
+
end
|
106
|
+
|
107
|
+
def relation_methods #: String
|
108
|
+
methods = <<~RBS
|
109
|
+
include GeneratedActiveStorageScopeMethods[ActiveRecord_Relation]
|
110
|
+
include GeneratedDelegatedTypeScopeMethods[ActiveRecord_Relation]
|
111
|
+
include GeneratedEnumScopeMethods[ActiveRecord_Relation]
|
112
|
+
include GeneratedScopeMethods[ActiveRecord_Relation]
|
113
|
+
RBS
|
114
|
+
model.parents.each do |cls|
|
115
|
+
methods += <<~RBS.strip
|
116
|
+
include ::#{cls.name}::GeneratedActiveStorageScopeMethods[ActiveRecord_Relation]
|
117
|
+
include ::#{cls.name}::GeneratedDelegatedTypeScopeMethods[ActiveRecord_Relation]
|
118
|
+
include ::#{cls.name}::GeneratedEnumScopeMethods[ActiveRecord_Relation]
|
119
|
+
include ::#{cls.name}::GeneratedScopeMethods[ActiveRecord_Relation]
|
120
|
+
RBS
|
121
|
+
end
|
122
|
+
methods
|
123
|
+
end
|
124
|
+
|
125
|
+
def scope_class_methods #: String
|
126
|
+
methods = <<~RBS
|
127
|
+
extend GeneratedActiveStorageScopeMethods[ActiveRecord_Relation]
|
128
|
+
extend GeneratedDelegatedTypeScopeMethods[ActiveRecord_Relation]
|
129
|
+
extend GeneratedEnumScopeMethods[ActiveRecord_Relation]
|
130
|
+
extend GeneratedScopeMethods[ActiveRecord_Relation]
|
131
|
+
RBS
|
132
|
+
model.parents.each do |cls|
|
133
|
+
methods += <<~RBS.strip
|
134
|
+
extend ::#{cls.name}::GeneratedActiveStorageScopeMethods[ActiveRecord_Relation]
|
135
|
+
extend ::#{cls.name}::GeneratedDelegatedTypeScopeMethods[ActiveRecord_Relation]
|
136
|
+
extend ::#{cls.name}::GeneratedEnumScopeMethods[ActiveRecord_Relation]
|
137
|
+
extend ::#{cls.name}::GeneratedScopeMethods[ActiveRecord_Relation]
|
138
|
+
RBS
|
139
|
+
end
|
140
|
+
methods
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module RbsActiverecord
|
6
|
+
class Model
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_reader :klass #: singleton(ActiveRecord::Base)
|
10
|
+
|
11
|
+
# @rbs!
|
12
|
+
# def attribute_types: () -> Hash[String, untyped]
|
13
|
+
# def attribute_aliases: () -> Hash[String, String]
|
14
|
+
# def columns: () -> Array[untyped]
|
15
|
+
# def defined_enums: () -> Hash[String, untyped]
|
16
|
+
# def reflect_on_all_associations: (Symbol) -> Array[untyped]
|
17
|
+
def_delegators :klass, :attribute_aliases, :attribute_types, :columns, :defined_enums, :reflect_on_all_associations
|
18
|
+
|
19
|
+
# @rbs klass: singleton(ActiveRecord::Base)
|
20
|
+
def initialize(klass) #: void
|
21
|
+
@klass = klass
|
22
|
+
end
|
23
|
+
|
24
|
+
def filename #: String
|
25
|
+
"app/models/#{klass.name.to_s.underscore}.rb"
|
26
|
+
end
|
27
|
+
|
28
|
+
def parents #: Array[singleton(ActiveRecord::Base)]
|
29
|
+
ancestors = klass.ancestors #: Array[singleton(ActiveRecord::Base)]
|
30
|
+
ancestors.select { |cls| cls < ActiveRecord::Base && cls != klass && !cls.abstract_class? }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
module Parser
|
5
|
+
module Evaluator
|
6
|
+
# @rbs! def self.eval_node: (Prism::Node node) -> untyped
|
7
|
+
|
8
|
+
# @rbs node: Prism::Node
|
9
|
+
def eval_node(node) #: untyped # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
10
|
+
case node
|
11
|
+
when Prism::NilNode
|
12
|
+
nil
|
13
|
+
when Prism::TrueNode
|
14
|
+
true
|
15
|
+
when Prism::FalseNode
|
16
|
+
false
|
17
|
+
when Prism::SymbolNode
|
18
|
+
node.value.to_s.to_sym
|
19
|
+
when Prism::IntegerNode
|
20
|
+
node.value
|
21
|
+
when Prism::StringNode
|
22
|
+
node.unescaped
|
23
|
+
when Prism::ArrayNode
|
24
|
+
node.elements.map { |e| eval_node(e) }
|
25
|
+
when Prism::HashNode
|
26
|
+
node.elements.filter_map do |assoc|
|
27
|
+
case assoc
|
28
|
+
when Prism::AssocNode
|
29
|
+
key = eval_node(assoc.key)
|
30
|
+
value = eval_node(assoc.value)
|
31
|
+
[key, value]
|
32
|
+
end
|
33
|
+
end.to_h
|
34
|
+
when Prism::KeywordHashNode
|
35
|
+
node.elements.filter_map do |assoc|
|
36
|
+
case assoc
|
37
|
+
when Prism::AssocNode
|
38
|
+
key = eval_node(assoc.key)
|
39
|
+
value = eval_node(assoc.value)
|
40
|
+
[key, value]
|
41
|
+
end
|
42
|
+
end.to_h
|
43
|
+
end
|
44
|
+
end
|
45
|
+
module_function :eval_node
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsActiverecord
|
4
|
+
module Parser
|
5
|
+
class Visitor < Prism::Visitor
|
6
|
+
attr_reader :context #: Array[Prism::ClassNode | Prism::ModuleNode]
|
7
|
+
attr_reader :decls #: Hash[String, Array[Prism::CallNode]]
|
8
|
+
|
9
|
+
def initialize #: void
|
10
|
+
super
|
11
|
+
|
12
|
+
@context = []
|
13
|
+
@decls = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_namespace #: String
|
17
|
+
context.flat_map do |node|
|
18
|
+
namespace = [] #: Array[Symbol?]
|
19
|
+
path = node.constant_path #: Prism::Node?
|
20
|
+
loop do
|
21
|
+
case path
|
22
|
+
when Prism::ConstantPathNode
|
23
|
+
namespace << path.name
|
24
|
+
path = path.parent
|
25
|
+
when Prism::ConstantReadNode
|
26
|
+
namespace << path.name
|
27
|
+
break
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
namespace.compact.reverse
|
32
|
+
end.join("::")
|
33
|
+
end
|
34
|
+
|
35
|
+
# @rbs override
|
36
|
+
def visit_module_node(node)
|
37
|
+
context << node
|
38
|
+
begin
|
39
|
+
super
|
40
|
+
ensure
|
41
|
+
context.pop
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @rbs override
|
46
|
+
def visit_class_node(node)
|
47
|
+
context << node
|
48
|
+
begin
|
49
|
+
super
|
50
|
+
ensure
|
51
|
+
context.pop
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @rbs override
|
56
|
+
def visit_call_node(node)
|
57
|
+
decls[current_namespace] ||= []
|
58
|
+
decls[current_namespace] << node
|
59
|
+
end
|
60
|
+
|
61
|
+
# @rbs override
|
62
|
+
def visit_def_node(node)
|
63
|
+
# ignore
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|