rbs_activerecord 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +22 -0
  3. data/.vscode/extensions.json +6 -0
  4. data/.vscode/settings.json +12 -0
  5. data/CODE_OF_CONDUCT.md +132 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +49 -0
  8. data/Rakefile +14 -0
  9. data/Steepfile +9 -0
  10. data/lib/generators/rbs_activerecord/install_generator.rb +21 -0
  11. data/lib/rbs_activerecord/generator/active_storage/instance_methods.rb +57 -0
  12. data/lib/rbs_activerecord/generator/active_storage/scopes.rb +39 -0
  13. data/lib/rbs_activerecord/generator/associations.rb +118 -0
  14. data/lib/rbs_activerecord/generator/attributes.rb +95 -0
  15. data/lib/rbs_activerecord/generator/delegated_type/instance_methods.rb +66 -0
  16. data/lib/rbs_activerecord/generator/delegated_type/scopes.rb +44 -0
  17. data/lib/rbs_activerecord/generator/enum/base.rb +63 -0
  18. data/lib/rbs_activerecord/generator/enum/instance_methods.rb +51 -0
  19. data/lib/rbs_activerecord/generator/enum/scopes.rb +51 -0
  20. data/lib/rbs_activerecord/generator/pluck_overloads.rb +41 -0
  21. data/lib/rbs_activerecord/generator/scopes.rb +99 -0
  22. data/lib/rbs_activerecord/generator/secure_password.rb +54 -0
  23. data/lib/rbs_activerecord/generator.rb +143 -0
  24. data/lib/rbs_activerecord/model.rb +33 -0
  25. data/lib/rbs_activerecord/parser/evaluator.rb +48 -0
  26. data/lib/rbs_activerecord/parser/visitor.rb +67 -0
  27. data/lib/rbs_activerecord/parser.rb +30 -0
  28. data/lib/rbs_activerecord/rake_task.rb +61 -0
  29. data/lib/rbs_activerecord/utils.rb +58 -0
  30. data/lib/rbs_activerecord/version.rb +5 -0
  31. data/lib/rbs_activerecord.rb +27 -0
  32. data/rbs_collection.lock.yaml +352 -0
  33. data/rbs_collection.yaml +18 -0
  34. data/sig/generators/rbs_activerecord/install_generator.rbs +7 -0
  35. data/sig/rbs_activerecord/generator/active_storage/instance_methods.rbs +23 -0
  36. data/sig/rbs_activerecord/generator/active_storage/scopes.rbs +23 -0
  37. data/sig/rbs_activerecord/generator/associations.rbs +29 -0
  38. data/sig/rbs_activerecord/generator/attributes.rbs +28 -0
  39. data/sig/rbs_activerecord/generator/delegated_type/instance_methods.rbs +31 -0
  40. data/sig/rbs_activerecord/generator/delegated_type/scopes.rbs +26 -0
  41. data/sig/rbs_activerecord/generator/enum/base.rbs +19 -0
  42. data/sig/rbs_activerecord/generator/enum/instance_methods.rbs +26 -0
  43. data/sig/rbs_activerecord/generator/enum/scopes.rbs +26 -0
  44. data/sig/rbs_activerecord/generator/pluck_overloads.rbs +23 -0
  45. data/sig/rbs_activerecord/generator/scopes.rbs +27 -0
  46. data/sig/rbs_activerecord/generator/secure_password.rbs +22 -0
  47. data/sig/rbs_activerecord/generator.rbs +34 -0
  48. data/sig/rbs_activerecord/model.rbs +26 -0
  49. data/sig/rbs_activerecord/parser/evaluator.rbs +12 -0
  50. data/sig/rbs_activerecord/parser/visitor.rbs +27 -0
  51. data/sig/rbs_activerecord/parser.rbs +15 -0
  52. data/sig/rbs_activerecord/rake_task.rbs +19 -0
  53. data/sig/rbs_activerecord/utils.rbs +14 -0
  54. data/sig/rbs_activerecord/version.rbs +5 -0
  55. data/sig/rbs_activerecord.rbs +6 -0
  56. 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