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.
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