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 +4 -4
- data/CHANGELOG.md +11 -0
- data/lib/typespec_from_serializers/generator.rb +87 -44
- data/lib/typespec_from_serializers/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e442eede2713d04dc58848c414f8c97fb3131ec16aff96c33b31fc2b9bfe248b
|
|
4
|
+
data.tar.gz: 88424e945a35538e356adceaf9abdcb2ceb5997469a94efe9d661c31b228050a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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(
|
|
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
|
-
#
|
|
1128
|
-
|
|
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 = {}
|
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.
|
|
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-
|
|
12
|
+
date: 2025-12-23 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: railties
|