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