typespec_from_serializers 0.5.2 → 0.5.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46bcf5deebdc474b0f22abee212194c394fc811492bf55c9a3eaae01ed3976a0
4
- data.tar.gz: 8dc54c4ffb68a326c94c99f6ac778ebf9ee777e925e2bbe456c9256ed25abae6
3
+ metadata.gz: e442eede2713d04dc58848c414f8c97fb3131ec16aff96c33b31fc2b9bfe248b
4
+ data.tar.gz: 88424e945a35538e356adceaf9abdcb2ceb5997469a94efe9d661c31b228050a
5
5
  SHA512:
6
- metadata.gz: 0bccf7a80481a2c8b20157c9889ef18a89390854692017c559d16feb52c464ba9cf95d9a280078b203d626b4f46c0f720d3b8b46b72d8d8543a550b76e1d7e67
7
- data.tar.gz: 42c5a6028324542ecfaf6a79a5fea5fc635677688b960507cb8f4dc805ca7b7be164e3a5f39288dfbeea74f1e3af9b44f462f1d070eaed9cc947f4bee21c8ab0
6
+ metadata.gz: 960f08588e534643e0ecdb2cf4f3129d351995330f4474748c10ee8f7e844e202614c3e8ae391753c5b45b555831398aff2ffa3474af080ef9e5767b86f2f706
7
+ data.tar.gz: 32cd0ff2d5b1e339829a78f7150b3870a4ef954d61d12c8738b3ebe5f0d04b31ea7ba1988fc4efd7f5c46e436f56e96d52b2719f77b29a95231fdfaf13d07777
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # TypeSpec From Serializers Changelog
2
2
 
3
+ ## [0.5.4] - 2025-12-23
4
+
5
+ ### Fixed
6
+ - Transitive params extraction now follows method calls through intermediate methods
7
+ - Route-level `type:` declarations now only apply to actual path params (not inherited by collection routes)
8
+
9
+ ## [0.5.3] - 2025-12-23
10
+
11
+ ### Fixed
12
+ - Body params now scoped per action by analyzing which `*_params` methods are called
13
+
3
14
  ## [0.5.2] - 2025-12-22
4
15
 
5
16
  ### Fixed
@@ -847,7 +847,10 @@ module TypeSpecFromSerializers
847
847
 
848
848
  # Internal: Extracts all parameter types from route metadata and controller DSL
849
849
  def extract_all_param_types(controller, route)
850
+ # Route-level types are for path params only - filter to actual path params
851
+ path_param_names = route[:path].scan(/:([a-zA-Z_][a-zA-Z0-9_]*)/).flatten
850
852
  route_param_types = route[:param_types]
853
+ .select { |k, _| path_param_names.include?(k.to_s) }
851
854
  .transform_keys(&:to_s)
852
855
  .transform_values { |v| map_type_class_to_typespec(v) }
853
856
 
@@ -980,56 +983,19 @@ module TypeSpecFromSerializers
980
983
 
981
984
  # Internal: Extracts serializer class from controller method source using Prism AST
982
985
  def extract_serializer_from_controller_method(controller_class, action)
983
- return nil unless controller_class.method_defined?(action)
984
-
985
- method = controller_class.instance_method(action)
986
- source_location = method.source_location
987
- return nil unless source_location
988
-
989
- file_path, line_number = source_location
990
- return nil unless File.exist?(file_path)
991
-
992
- # Parse the file with Prism
993
- result = Prism.parse_file(file_path)
994
- return nil unless result.success?
995
-
996
- # Find the specific method definition node
997
- method_finder = MethodFinder.new(action.to_s, line_number)
998
- method_finder.visit(result.value)
999
- return nil unless method_finder.method_node
986
+ method_node = find_action_node(controller_class, action)
987
+ return unless method_node
1000
988
 
1001
989
  # Find serializer references only within this method
1002
990
  visitor = SerializerVisitor.new
1003
- visitor.visit(method_finder.method_node)
991
+ visitor.visit(method_node)
1004
992
 
1005
993
  # Try to constantize any found serializers and return the first valid one
1006
994
  visitor.serializer_names.filter_map(&:safe_constantize).first
1007
995
  rescue
1008
- # File read or parsing error - return nil
1009
996
  nil
1010
997
  end
1011
998
 
1012
- # Internal: Prism visitor to find a specific method definition by name and line
1013
- class MethodFinder < Prism::Visitor
1014
- attr_reader :method_node
1015
-
1016
- def initialize(method_name, line_number)
1017
- super()
1018
- @method_name = method_name
1019
- @line_number = line_number
1020
- @method_node = nil
1021
- end
1022
-
1023
- def visit_def_node(node)
1024
- # Match by method name and line number proximity
1025
- if node.name.to_s == @method_name &&
1026
- node.location.start_line <= @line_number &&
1027
- node.location.end_line >= @line_number
1028
- @method_node = node
1029
- end
1030
- super
1031
- end
1032
- end
1033
999
 
1034
1000
  # Internal: Prism visitor to extract serializer class names from AST
1035
1001
  class SerializerVisitor < Prism::Visitor
@@ -1124,10 +1090,8 @@ module TypeSpecFromSerializers
1124
1090
  def extract_param_types_from_controller(controller_class, action)
1125
1091
  param_types = {}
1126
1092
 
1127
- # Extract from *_params methods with type DSL declarations
1128
- # Include private methods since *_params methods are typically private
1129
- param_methods = (controller_class.instance_methods(false) | controller_class.private_instance_methods(false))
1130
- .select { |m| m.to_s.end_with?(config.param_method_suffix) }
1093
+ # Find which *_params methods are called by this action
1094
+ param_methods = find_params_methods_called_by_action(controller_class, action)
1131
1095
 
1132
1096
  param_methods.each do |method_name|
1133
1097
  type_found = false
@@ -1172,6 +1136,85 @@ module TypeSpecFromSerializers
1172
1136
  {}
1173
1137
  end
1174
1138
 
1139
+ # Internal: Finds *_params methods that are called by a specific action method (transitively)
1140
+ def find_params_methods_called_by_action(controller_class, action)
1141
+ method_node = find_action_node(controller_class, action)
1142
+ return [] unless method_node
1143
+
1144
+ method = controller_class.instance_method(action)
1145
+ file_path, = method.source_location
1146
+ suffix = config.param_method_suffix
1147
+
1148
+ find_params_methods_transitively(file_path, method_node, suffix)
1149
+ rescue
1150
+ []
1151
+ end
1152
+
1153
+ # Internal: Recursively finds *_params methods through intermediate method calls
1154
+ def find_params_methods_transitively(file_path, node, suffix, visited = Set.new)
1155
+ calls = find_method_calls(node)
1156
+ params_methods = calls.select { |name| name.end_with?(suffix) }.map(&:to_sym)
1157
+
1158
+ # Follow non-params method calls defined in the same file
1159
+ calls.each do |call_name|
1160
+ next if call_name.end_with?(suffix)
1161
+ next if visited.include?(call_name)
1162
+
1163
+ visited << call_name
1164
+ called_node = find_def_node(file_path) { |n| n.name.to_s == call_name }
1165
+ next unless called_node
1166
+
1167
+ params_methods.concat(find_params_methods_transitively(file_path, called_node, suffix, visited))
1168
+ end
1169
+
1170
+ params_methods.uniq
1171
+ end
1172
+
1173
+ # Internal: Finds the AST node for a controller action method.
1174
+ def find_action_node(controller_class, action)
1175
+ return unless controller_class.method_defined?(action)
1176
+
1177
+ method = controller_class.instance_method(action)
1178
+ file_path, line_number = method.source_location
1179
+ return unless file_path && File.exist?(file_path)
1180
+
1181
+ find_def_node(file_path) { |node| node.location.start_line == line_number }
1182
+ end
1183
+
1184
+ # Internal: Parses a Ruby file and caches the result.
1185
+ def parse_file_cached(file_path)
1186
+ @parsed_files ||= {}
1187
+ @parsed_files[file_path] ||= begin
1188
+ result = Prism.parse_file(file_path)
1189
+ result.success? ? result.value : nil
1190
+ end
1191
+ end
1192
+
1193
+ # Internal: Traverses all descendant nodes in BFS order.
1194
+ def each_descendant(node)
1195
+ return enum_for(:each_descendant, node) unless block_given?
1196
+ queue = [node]
1197
+ while (current = queue.shift)
1198
+ yield current
1199
+ queue.concat(current.compact_child_nodes)
1200
+ end
1201
+ end
1202
+
1203
+ # Internal: Finds a DefNode matching the given condition.
1204
+ def find_def_node(file_path)
1205
+ ast = parse_file_cached(file_path)
1206
+ return unless ast
1207
+ each_descendant(ast).find { |node| node.is_a?(Prism::DefNode) && yield(node) }
1208
+ end
1209
+
1210
+ # Internal: Finds all unqualified method call names in an AST node.
1211
+ def find_method_calls(node)
1212
+ each_descendant(node)
1213
+ .select { |n| n.is_a?(Prism::CallNode) && n.receiver.nil? }
1214
+ .map { |n| n.name.to_s }
1215
+ .uniq
1216
+ end
1217
+
1175
1218
  # Internal: Extracts key-value types from Sorbet hash type
1176
1219
  def extract_hash_types_from_sorbet(sorbet_type)
1177
1220
  param_types = {}
@@ -2,5 +2,5 @@
2
2
 
3
3
  module TypeSpecFromSerializers
4
4
  # Public: This library adheres to semantic versioning.
5
- VERSION = "0.5.2"
5
+ VERSION = "0.5.4"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typespec_from_serializers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danila Poyarkov
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-12-22 00:00:00.000000000 Z
12
+ date: 2025-12-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: railties