typespec_from_serializers 0.3.1 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/typespec_from_serializers/generator.rb +103 -4
- data/lib/typespec_from_serializers/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a1d95c0934cefa4c37d29af028dbd3677a3d07b5a6f8af804c206400091472d
|
|
4
|
+
data.tar.gz: 8ea38365134c4d22d0e60fb19886d6803e6ee9ad82b97a52c8e21decb4ee916c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2cfdedc3b5e45b9b9fac2e6abbfd215495787f9eb150d639091f3e5df9eaa4b4d7f84b5ba5fab3ede09876df871a08b11a3165eb358cc2788bac3be8ebbe41ea
|
|
7
|
+
data.tar.gz: 745708a0339c1cb4f7f45773eff7a2751826d734a9f6a252a6f275c050ad194b4b9c9464cd6f81e885575befda88345681db27389a980b9ae36ab64eadd51b27
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# TypeSpec From Serializers Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2025-12-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Linting system with 5 configurable rules to catch common issues during generation
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- Add `implicitOptionality: true` to PATCH decorators to suppress TypeSpec 1.0+ warnings
|
|
10
|
+
|
|
3
11
|
## [0.3.1] - 2025-12-22
|
|
4
12
|
|
|
5
13
|
### Fixed
|
|
@@ -26,6 +26,72 @@ module TypeSpecFromSerializers
|
|
|
26
26
|
MEMBER_ACTIONS = %w[show update destroy].freeze
|
|
27
27
|
SPECIAL_ACTIONS = %w[new edit].freeze
|
|
28
28
|
|
|
29
|
+
# Internal: Linting methods for TypeSpec generation
|
|
30
|
+
module Linting
|
|
31
|
+
extend self
|
|
32
|
+
|
|
33
|
+
# Internal: Lints for missing parameter types
|
|
34
|
+
def missing_param_types(controller, route, path_param_names, param_types, config)
|
|
35
|
+
return unless enabled?(config.linting, :missing_param_types)
|
|
36
|
+
|
|
37
|
+
missing_path_params = path_param_names.select { |name| !param_types.key?(name) }
|
|
38
|
+
|
|
39
|
+
unless missing_path_params.empty?
|
|
40
|
+
location = "#{controller.camelize}#{config.controller_suffix}##{route[:action]}"
|
|
41
|
+
warn "TypeSpec Lint: Missing type for path parameter(s) #{missing_path_params.join(', ')} in #{location} (#{route[:method]} #{route[:path]}). Defaulting to 'string'."
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Internal: Lints for unknown response types
|
|
46
|
+
def unknown_response_type(controller, route, response_type, config)
|
|
47
|
+
return unless enabled?(config.linting, :unknown_response_types)
|
|
48
|
+
|
|
49
|
+
if response_type == "unknown"
|
|
50
|
+
location = "#{controller.camelize}#{config.controller_suffix}##{route[:action]}"
|
|
51
|
+
warn "TypeSpec Lint: Unknown response type for #{location} (#{route[:method]} #{route[:path]}). Consider adding a serializer or explicit type annotation."
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Internal: Lints for missing documentation
|
|
56
|
+
def missing_documentation(controller, route, doc, config)
|
|
57
|
+
return unless enabled?(config.linting, :missing_documentation) && config.extract_docs
|
|
58
|
+
|
|
59
|
+
if doc.nil?
|
|
60
|
+
location = "#{controller.camelize}#{config.controller_suffix}##{route[:action]}"
|
|
61
|
+
warn "TypeSpec Lint: Missing documentation for #{location} (#{route[:method]} #{route[:path]}). Consider adding an RDoc comment."
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Internal: Lints for duplicate action names
|
|
66
|
+
def ambiguous_operations(name_counts, operations, config)
|
|
67
|
+
return unless enabled?(config.linting, :ambiguous_operations)
|
|
68
|
+
|
|
69
|
+
duplicates = name_counts.select { |_, count| count > 1 }
|
|
70
|
+
duplicates.each do |action, count|
|
|
71
|
+
ops = operations.select { |op| op.action == action }
|
|
72
|
+
methods = ops.map { |op| op.method }.join(', ')
|
|
73
|
+
warn "TypeSpec Lint: Action '#{action}' used for multiple routes (#{methods}). Using method suffix to differentiate."
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Internal: Lints for type inference failures
|
|
78
|
+
def type_inference_failure(property, explicit_type, serializer_name, config)
|
|
79
|
+
return unless enabled?(config.linting, :type_inference_failures)
|
|
80
|
+
|
|
81
|
+
if property.type.nil? && explicit_type.nil?
|
|
82
|
+
warn "TypeSpec Lint: Could not infer type for '#{property.name}' in #{serializer_name}. Consider adding explicit type annotation."
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
# Internal: Checks if a linting rule is enabled
|
|
89
|
+
def enabled?(linting_config, rule)
|
|
90
|
+
return false if linting_config == false
|
|
91
|
+
linting_config.is_a?(Hash) && linting_config[rule]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
29
95
|
# Internal: Extensions that simplify the implementation of the generator.
|
|
30
96
|
module SerializerRefinements
|
|
31
97
|
refine String do
|
|
@@ -96,7 +162,11 @@ module TypeSpecFromSerializers
|
|
|
96
162
|
column_name: options.fetch(:value_from),
|
|
97
163
|
doc: TypeSpecFromSerializers.config.extract_docs ? RDoc.method_doc(self, options.fetch(:value_from)) : nil,
|
|
98
164
|
).tap do |property|
|
|
165
|
+
explicit_type = property.type
|
|
99
166
|
property.infer_typespec_from(model_columns, model_enums, typespec_from, self, model_class)
|
|
167
|
+
|
|
168
|
+
# Linting
|
|
169
|
+
TypeSpecFromSerializers::Linting.type_inference_failure(property, explicit_type, self.name, TypeSpecFromSerializers.config)
|
|
100
170
|
end
|
|
101
171
|
end
|
|
102
172
|
}
|
|
@@ -138,6 +208,7 @@ module TypeSpecFromSerializers
|
|
|
138
208
|
:openapi_path,
|
|
139
209
|
:max_line_length,
|
|
140
210
|
:extract_docs,
|
|
211
|
+
:linting,
|
|
141
212
|
:root,
|
|
142
213
|
keyword_init: true,
|
|
143
214
|
) do
|
|
@@ -416,14 +487,21 @@ module TypeSpecFromSerializers
|
|
|
416
487
|
def build_single_line(tsp_method, operation_name)
|
|
417
488
|
params = params_typespec
|
|
418
489
|
params_str = params.empty? ? "()" : "(#{params})"
|
|
419
|
-
|
|
490
|
+
method_decorator = format_method_decorator(tsp_method)
|
|
491
|
+
"#{method_decorator} #{operation_name}#{params_str}: #{response_type.delete(":")};"
|
|
420
492
|
end
|
|
421
493
|
|
|
422
494
|
def multiline_format(route_line, tsp_method, operation_name)
|
|
423
495
|
params_indented = all_params.map { |p| "\n #{p}," }.join
|
|
424
496
|
return_type = response_type.delete(":")
|
|
497
|
+
method_decorator = format_method_decorator(tsp_method)
|
|
498
|
+
|
|
499
|
+
"#{route_line}#{method_decorator} #{operation_name}(#{params_indented}\n ): #{return_type};"
|
|
500
|
+
end
|
|
425
501
|
|
|
426
|
-
|
|
502
|
+
def format_method_decorator(tsp_method)
|
|
503
|
+
# PATCH operations need implicitOptionality flag in TypeSpec 1.0+
|
|
504
|
+
tsp_method == "patch" ? "@patch(\#{implicitOptionality: true})" : "@#{tsp_method}"
|
|
427
505
|
end
|
|
428
506
|
|
|
429
507
|
def operation_route_decorator(resource_path)
|
|
@@ -723,14 +801,23 @@ module TypeSpecFromSerializers
|
|
|
723
801
|
param_types = extract_all_param_types(controller, route)
|
|
724
802
|
controller_class = "#{controller.camelize}#{config.controller_suffix}".safe_constantize
|
|
725
803
|
|
|
804
|
+
# Linting
|
|
805
|
+
Linting.missing_param_types(controller, route, path_param_names, param_types, config)
|
|
806
|
+
|
|
807
|
+
response_type = infer_operation_response_type(route)
|
|
808
|
+
doc = config.extract_docs && controller_class ? RDoc.method_doc(controller_class, route[:action]) : nil
|
|
809
|
+
|
|
810
|
+
Linting.unknown_response_type(controller, route, response_type, config)
|
|
811
|
+
Linting.missing_documentation(controller, route, doc, config)
|
|
812
|
+
|
|
726
813
|
Operation.new(
|
|
727
814
|
method: route[:method],
|
|
728
815
|
action: simplify_operation_name(route),
|
|
729
816
|
path: route[:path],
|
|
730
817
|
path_params: build_path_params(path_param_names, param_types),
|
|
731
818
|
body_params: build_body_params(route[:method], path_param_names, param_types),
|
|
732
|
-
response_type:
|
|
733
|
-
doc:
|
|
819
|
+
response_type: response_type,
|
|
820
|
+
doc: doc,
|
|
734
821
|
)
|
|
735
822
|
end
|
|
736
823
|
|
|
@@ -786,6 +873,9 @@ module TypeSpecFromSerializers
|
|
|
786
873
|
name_counts = operations.group_by(&:action).transform_values(&:count)
|
|
787
874
|
name_usage = Hash.new(0)
|
|
788
875
|
|
|
876
|
+
# Linting
|
|
877
|
+
Linting.ambiguous_operations(name_counts, operations, config)
|
|
878
|
+
|
|
789
879
|
operations.map do |op|
|
|
790
880
|
next op unless name_counts[op.action] > 1
|
|
791
881
|
|
|
@@ -1295,6 +1385,15 @@ module TypeSpecFromSerializers
|
|
|
1295
1385
|
# Extract documentation from RDoc comments
|
|
1296
1386
|
extract_docs: true,
|
|
1297
1387
|
|
|
1388
|
+
# Linting configuration
|
|
1389
|
+
linting: {
|
|
1390
|
+
missing_param_types: true,
|
|
1391
|
+
unknown_response_types: true,
|
|
1392
|
+
missing_documentation: true,
|
|
1393
|
+
ambiguous_operations: true,
|
|
1394
|
+
type_inference_failures: true,
|
|
1395
|
+
},
|
|
1396
|
+
|
|
1298
1397
|
# Project root directory
|
|
1299
1398
|
root: root,
|
|
1300
1399
|
)
|