rails-openapi-gen 0.0.2 → 0.0.3

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/CLAUDE.md +17 -5
  4. data/README.md +25 -0
  5. data/lib/rails-openapi-gen/ast_nodes/array_node.rb +101 -0
  6. data/lib/rails-openapi-gen/ast_nodes/base_node.rb +139 -0
  7. data/lib/rails-openapi-gen/ast_nodes/comment_data.rb +180 -0
  8. data/lib/rails-openapi-gen/ast_nodes/node_factory.rb +206 -0
  9. data/lib/rails-openapi-gen/ast_nodes/object_node.rb +129 -0
  10. data/lib/rails-openapi-gen/ast_nodes/partial_node.rb +111 -0
  11. data/lib/rails-openapi-gen/ast_nodes/property_node.rb +74 -0
  12. data/lib/rails-openapi-gen/ast_nodes.rb +129 -0
  13. data/lib/rails-openapi-gen/configuration.rb +154 -22
  14. data/lib/rails-openapi-gen/debug_helpers.rb +185 -0
  15. data/lib/rails-openapi-gen/engine.rb +1 -1
  16. data/lib/rails-openapi-gen/generators/yaml_generator.rb +242 -27
  17. data/lib/rails-openapi-gen/generators.rb +5 -0
  18. data/lib/rails-openapi-gen/importer.rb +164 -145
  19. data/lib/rails-openapi-gen/parsers/comment_parser.rb +1 -1
  20. data/lib/rails-openapi-gen/parsers/comment_parsers/attribute_parser.rb +7 -7
  21. data/lib/rails-openapi-gen/parsers/comment_parsers/base_attribute_parser.rb +5 -9
  22. data/lib/rails-openapi-gen/parsers/comment_parsers/body_parser.rb +6 -6
  23. data/lib/rails-openapi-gen/parsers/comment_parsers/conditional_parser.rb +1 -1
  24. data/lib/rails-openapi-gen/parsers/comment_parsers/operation_parser.rb +5 -5
  25. data/lib/rails-openapi-gen/parsers/comment_parsers/param_parser.rb +6 -6
  26. data/lib/rails-openapi-gen/parsers/comment_parsers/query_parser.rb +6 -6
  27. data/lib/rails-openapi-gen/parsers/controller_parser.rb +64 -20
  28. data/lib/rails-openapi-gen/parsers/jbuilder/ast_parser.rb +914 -0
  29. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/array_call_detector.rb +103 -0
  30. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/base_detector.rb +107 -0
  31. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/cache_call_detector.rb +112 -0
  32. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/json_call_detector.rb +91 -0
  33. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/key_format_detector.rb +27 -0
  34. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/null_handling_detector.rb +27 -0
  35. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/object_manipulation_detector.rb +27 -0
  36. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors/partial_call_detector.rb +125 -0
  37. data/lib/rails-openapi-gen/parsers/jbuilder/call_detectors.rb +95 -0
  38. data/lib/rails-openapi-gen/parsers/jbuilder/jbuilder_parser.rb +39 -0
  39. data/lib/rails-openapi-gen/parsers/jbuilder/operation_comment_parser.rb +26 -0
  40. data/lib/rails-openapi-gen/parsers/jbuilder/processors/array_processor.rb +266 -0
  41. data/lib/rails-openapi-gen/parsers/jbuilder/processors/base_processor.rb +235 -0
  42. data/lib/rails-openapi-gen/parsers/jbuilder/processors/composite_processor.rb +97 -0
  43. data/lib/rails-openapi-gen/parsers/jbuilder/processors/object_processor.rb +176 -0
  44. data/lib/rails-openapi-gen/parsers/jbuilder/processors/partial_processor.rb +69 -0
  45. data/lib/rails-openapi-gen/parsers/jbuilder/processors/property_processor.rb +68 -0
  46. data/lib/rails-openapi-gen/parsers/jbuilder/processors.rb +10 -0
  47. data/lib/rails-openapi-gen/parsers/jbuilder/property_comment_parser.rb +26 -0
  48. data/lib/rails-openapi-gen/parsers/jbuilder.rb +10 -0
  49. data/lib/rails-openapi-gen/parsers/routes_parser.rb +83 -9
  50. data/lib/rails-openapi-gen/parsers/template_processors/jbuilder_template_processor.rb +125 -131
  51. data/lib/rails-openapi-gen/parsers/template_processors/response_template_processor.rb +8 -12
  52. data/lib/rails-openapi-gen/parsers/template_processors.rb +6 -0
  53. data/lib/rails-openapi-gen/parsers.rb +9 -0
  54. data/lib/rails-openapi-gen/processors/ast_to_schema_processor.rb +226 -0
  55. data/lib/rails-openapi-gen/processors/base_processor.rb +124 -0
  56. data/lib/rails-openapi-gen/processors/component_schema_processor.rb +35 -0
  57. data/lib/rails-openapi-gen/processors/openapi_schema_processor.rb +218 -0
  58. data/lib/rails-openapi-gen/processors.rb +7 -0
  59. data/lib/rails-openapi-gen/railtie.rb +1 -1
  60. data/lib/rails-openapi-gen/tasks/openapi.rake +4 -4
  61. data/lib/rails-openapi-gen/version.rb +1 -1
  62. data/lib/rails-openapi-gen.rb +169 -196
  63. data/lib/tasks/openapi_import.rake +35 -36
  64. data/rails-openapi-gen.gemspec +6 -5
  65. metadata +62 -23
  66. data/lib/rails-openapi-gen/parsers/jbuilder_parser.rb +0 -529
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_detector'
4
+
5
+ module RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
6
+ # Detects array calls (json.array!)
7
+ # Handles array generation in Jbuilder templates
8
+ class ArrayCallDetector < BaseDetector
9
+ class << self
10
+ # Check if this detector handles the method call
11
+ # @param receiver [Parser::AST::Node, nil] Method receiver node
12
+ # @param method_name [Symbol] Method name
13
+ # @param args [Array<Parser::AST::Node>] Method arguments
14
+ # @return [Boolean] True if this is an array call
15
+ def handles?(receiver, method_name, _args = [])
16
+ result = array_call?(receiver, method_name)
17
+ if ENV['RAILS_OPENAPI_DEBUG']
18
+ puts "🔍 DEBUG: ArrayCallDetector.handles? for #{method_name}"
19
+ puts " receiver: #{receiver.inspect}"
20
+ puts " method_name: #{method_name.inspect}"
21
+ puts " method_name == :array!: #{method_name == :array!}"
22
+ puts " json_receiver?: #{json_receiver?(receiver)}"
23
+ puts " result: #{result}"
24
+ end
25
+ result
26
+ end
27
+
28
+ # High priority for array calls
29
+ # @return [Integer] Priority value
30
+ def priority
31
+ 20
32
+ end
33
+
34
+ # @return [Symbol] Detector category
35
+ def category
36
+ :array
37
+ end
38
+
39
+ # @return [String] Description
40
+ def description
41
+ "Array calls (json.array!)"
42
+ end
43
+
44
+ # Check if this is an array call
45
+ # @param receiver [Parser::AST::Node, nil] Method receiver
46
+ # @param method_name [Symbol] Method name
47
+ # @return [Boolean] True if this is an array call
48
+ def array_call?(receiver, method_name)
49
+ method_name == :array! && json_receiver?(receiver)
50
+ end
51
+
52
+ # Check if arguments contain partial key in hash
53
+ # @param hash_node [Parser::AST::Node] Hash node to check
54
+ # @return [Boolean] True if hash contains partial key
55
+ def has_partial_key?(hash_node)
56
+ return false unless hash_node.type == :hash
57
+
58
+ hash_node.children.any? do |pair|
59
+ next false unless pair.type == :pair
60
+
61
+ key, _value = pair.children
62
+ key.type == :sym && key.children.first == :partial
63
+ end
64
+ end
65
+
66
+ # Check if array call has collection argument
67
+ # @param args [Array<Parser::AST::Node>] Method arguments
68
+ # @return [Boolean] True if array has collection
69
+ def has_collection?(args)
70
+ args.any? && !args.first.type == :hash
71
+ end
72
+
73
+ # Check if array call has options hash
74
+ # @param args [Array<Parser::AST::Node>] Method arguments
75
+ # @return [Boolean] True if array has options
76
+ def has_options?(args)
77
+ args.any? { |arg| arg.type == :hash }
78
+ end
79
+
80
+ # Extract collection from array arguments
81
+ # @param args [Array<Parser::AST::Node>] Method arguments
82
+ # @return [Parser::AST::Node, nil] Collection node
83
+ def extract_collection(args)
84
+ args.find { |arg| arg.type != :hash }
85
+ end
86
+
87
+ # Extract options hash from array arguments
88
+ # @param args [Array<Parser::AST::Node>] Method arguments
89
+ # @return [Parser::AST::Node, nil] Options hash node
90
+ def extract_options(args)
91
+ args.find { |arg| arg.type == :hash }
92
+ end
93
+
94
+ # Check if array call uses partial rendering
95
+ # @param args [Array<Parser::AST::Node>] Method arguments
96
+ # @return [Boolean] True if array uses partial
97
+ def uses_partial?(args)
98
+ options = extract_options(args)
99
+ options && has_partial_key?(options)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
4
+ # Base class for all call detectors
5
+ # Provides common interface and utility methods for detecting Jbuilder method calls
6
+ class BaseDetector
7
+ class << self
8
+ # Check if a method call matches this detector's patterns
9
+ # @param receiver [Parser::AST::Node, nil] Method receiver node
10
+ # @param method_name [Symbol] Method name
11
+ # @param args [Array<Parser::AST::Node>] Method arguments
12
+ # @return [Boolean] True if this detector handles the method call
13
+ def handles?(_receiver, _method_name, _args = [])
14
+ raise NotImplementedError, "#{self} must implement #handles?"
15
+ end
16
+
17
+ # Get the detection priority for this detector
18
+ # Higher priority detectors are checked first
19
+ # @return [Integer] Priority value (higher = more priority)
20
+ def priority
21
+ 0
22
+ end
23
+
24
+ # Get the category of this detector for organization
25
+ # @return [Symbol] Detector category
26
+ def category
27
+ :general
28
+ end
29
+
30
+ # Get human-readable description of what this detector handles
31
+ # @return [String] Description
32
+ def description
33
+ "Base detector"
34
+ end
35
+
36
+ protected
37
+
38
+ # Check if receiver is json object
39
+ # @param receiver [Parser::AST::Node, nil] Receiver node
40
+ # @return [Boolean] True if receiver is json
41
+ def json_receiver?(receiver)
42
+ # Handle implicit json calls (receiver is nil in top-level context)
43
+ return true if receiver.nil?
44
+
45
+ # Handle explicit json calls (json.property_name)
46
+ return true if receiver.type == :send && receiver.children[0].nil? && receiver.children[1] == :json
47
+
48
+ false
49
+ end
50
+
51
+ # Check if method name matches any of the given patterns
52
+ # @param method_name [Symbol, String] Method name to check
53
+ # @param patterns [Array<Symbol, String, Regexp>] Patterns to match against
54
+ # @return [Boolean] True if method name matches any pattern
55
+ def method_matches?(method_name, patterns)
56
+ patterns.any? do |pattern|
57
+ case pattern
58
+ when Symbol, String
59
+ method_name.to_sym == pattern.to_sym
60
+ when Regexp
61
+ pattern.match?(method_name.to_s)
62
+ else
63
+ false
64
+ end
65
+ end
66
+ end
67
+
68
+ # Check if arguments contain a hash with specific keys
69
+ # @param args [Array<Parser::AST::Node>] Arguments to check
70
+ # @param keys [Array<Symbol>] Keys to look for
71
+ # @return [Boolean] True if hash argument contains any of the keys
72
+ def args_contain_hash_with_keys?(args, keys)
73
+ args.any? do |arg|
74
+ next false unless arg.type == :hash
75
+
76
+ hash_keys = arg.children.map do |pair|
77
+ key_node = pair.children.first
78
+ key_node.type == :sym ? key_node.children.first : nil
79
+ end.compact
80
+
81
+ (keys & hash_keys).any?
82
+ end
83
+ end
84
+
85
+ # Extract string value from a node
86
+ # @param node [Parser::AST::Node] Node to extract value from
87
+ # @return [String, nil] String value or nil
88
+ def extract_string_value(node)
89
+ return nil unless node
90
+
91
+ case node.type
92
+ when :str
93
+ node.children.first
94
+ when :sym
95
+ node.children.first.to_s
96
+ end
97
+ end
98
+
99
+ # Check if node represents a literal value
100
+ # @param node [Parser::AST::Node] Node to check
101
+ # @return [Boolean] True if node is a literal
102
+ def literal_node?(node)
103
+ %i[str int float true false nil sym].include?(node.type)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_detector'
4
+
5
+ module RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
6
+ # Detects cache calls (json.cache!, json.cache_if!)
7
+ # Handles caching directives in Jbuilder templates
8
+ class CacheCallDetector < BaseDetector
9
+ # Cache methods that this detector handles
10
+ CACHE_METHODS = %w[cache! cache_if!].freeze
11
+
12
+ class << self
13
+ # Check if this detector handles the method call
14
+ # @param receiver [Parser::AST::Node, nil] Method receiver node
15
+ # @param method_name [Symbol] Method name
16
+ # @param args [Array<Parser::AST::Node>] Method arguments
17
+ # @return [Boolean] True if this is a cache call
18
+ def handles?(receiver, method_name, _args = [])
19
+ cache_call?(receiver, method_name) || cache_if_call?(receiver, method_name)
20
+ end
21
+
22
+ # Medium priority - caching doesn't affect schema structure
23
+ # @return [Integer] Priority value
24
+ def priority
25
+ 5
26
+ end
27
+
28
+ # @return [Symbol] Detector category
29
+ def category
30
+ :meta
31
+ end
32
+
33
+ # @return [String] Description
34
+ def description
35
+ "Cache calls (json.cache!, json.cache_if!)"
36
+ end
37
+
38
+ # Check if this is a cache call
39
+ # @param receiver [Parser::AST::Node, nil] Method receiver
40
+ # @param method_name [Symbol] Method name
41
+ # @return [Boolean] True if this is a cache call
42
+ def cache_call?(receiver, method_name)
43
+ method_name == :cache! && json_receiver?(receiver)
44
+ end
45
+
46
+ # Check if this is a conditional cache call
47
+ # @param receiver [Parser::AST::Node, nil] Method receiver
48
+ # @param method_name [Symbol] Method name
49
+ # @return [Boolean] True if this is a cache_if call
50
+ def cache_if_call?(receiver, method_name)
51
+ method_name == :cache_if! && json_receiver?(receiver)
52
+ end
53
+
54
+ # Check if method is any cache-related method
55
+ # @param method_name [Symbol] Method name
56
+ # @return [Boolean] True if method is cache-related
57
+ def cache_method?(method_name)
58
+ CACHE_METHODS.include?(method_name.to_s)
59
+ end
60
+
61
+ # Extract cache key from arguments
62
+ # @param args [Array<Parser::AST::Node>] Method arguments
63
+ # @return [String, nil] Cache key
64
+ def extract_cache_key(args)
65
+ return nil if args.empty?
66
+
67
+ first_arg = args.first
68
+ extract_string_value(first_arg)
69
+ end
70
+
71
+ # Extract cache condition from cache_if! arguments
72
+ # @param args [Array<Parser::AST::Node>] Method arguments
73
+ # @return [Parser::AST::Node, nil] Condition node
74
+ def extract_cache_condition(args)
75
+ return nil if args.length < 2
76
+
77
+ args.first # First argument is the condition
78
+ end
79
+
80
+ # Check if cache call has block
81
+ # @param node [Parser::AST::Node] The cache call node
82
+ # @return [Boolean] True if cache has block
83
+ def has_block?(node)
84
+ node.type == :block
85
+ end
86
+
87
+ # Extract cache options from arguments
88
+ # @param args [Array<Parser::AST::Node>] Method arguments
89
+ # @return [Hash] Cache options
90
+ def extract_cache_options(args)
91
+ options = {}
92
+
93
+ args.each do |arg|
94
+ next unless arg.type == :hash
95
+
96
+ arg.children.each do |pair|
97
+ next unless pair.type == :pair
98
+
99
+ key_node, value_node = pair.children
100
+ next unless key_node.type == :sym
101
+
102
+ key = key_node.children.first
103
+ value = extract_string_value(value_node) || value_node
104
+ options[key] = value
105
+ end
106
+ end
107
+
108
+ options
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_detector'
4
+
5
+ module RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
6
+ # Detects JSON property calls (json.property_name)
7
+ # Handles the most common Jbuilder pattern for setting properties
8
+ class JsonCallDetector < BaseDetector
9
+ # JSON methods that should not be treated as properties
10
+ SPECIAL_METHODS = %w[array! partial! cache! cache_if! key_format! null! ignore_nil!].freeze
11
+
12
+ class << self
13
+ # Check if this detector handles the method call
14
+ # @param receiver [Parser::AST::Node, nil] Method receiver node
15
+ # @param method_name [Symbol] Method name
16
+ # @param args [Array<Parser::AST::Node>] Method arguments
17
+ # @return [Boolean] True if this is a JSON property call
18
+ def handles?(receiver, method_name, _args = [])
19
+ json_property?(receiver, method_name)
20
+ end
21
+
22
+ # Higher priority since this is the most common case
23
+ # @return [Integer] Priority value
24
+ def priority
25
+ 10
26
+ end
27
+
28
+ # @return [Symbol] Detector category
29
+ def category
30
+ :property
31
+ end
32
+
33
+ # @return [String] Description
34
+ def description
35
+ "JSON property calls (json.property_name)"
36
+ end
37
+
38
+ # Checks if this is a json property call (json.property_name)
39
+ # @param receiver [Parser::AST::Node, nil] The receiver of the method call
40
+ # @param method_name [Symbol] The method name being called
41
+ # @return [Boolean] True if this is a json property call
42
+ def json_property?(receiver, method_name)
43
+ return false if method_name.nil?
44
+ return false if SPECIAL_METHODS.include?(method_name.to_s)
45
+
46
+ # Don't handle bare "json" calls - these are receivers, not properties
47
+ return false if receiver.nil? && method_name == :json
48
+
49
+ # Must be called on json (implicit or explicit)
50
+ json_receiver?(receiver)
51
+ end
52
+
53
+ # Checks if this is an array-related call (json.array!)
54
+ # @param receiver [Parser::AST::Node, nil] The receiver of the method call
55
+ # @param method_name [Symbol] The method name being called
56
+ # @return [Boolean] True if this is an array call
57
+ def array_call?(receiver, method_name)
58
+ method_name == :array! && json_receiver?(receiver)
59
+ end
60
+
61
+ # Checks if this is a partial call (json.partial!)
62
+ # @param receiver [Parser::AST::Node, nil] The receiver of the method call
63
+ # @param method_name [Symbol] The method name being called
64
+ # @return [Boolean] True if this is a partial call
65
+ def partial_call?(receiver, method_name)
66
+ method_name == :partial! && json_receiver?(receiver)
67
+ end
68
+
69
+ # Check if method call represents a simple property assignment
70
+ # @param receiver [Parser::AST::Node, nil] Method receiver
71
+ # @param method_name [Symbol] Method name
72
+ # @param args [Array<Parser::AST::Node>] Method arguments
73
+ # @return [Boolean] True if this is a simple property assignment
74
+ def simple_property?(receiver, method_name, args)
75
+ return false unless json_property?(receiver, method_name)
76
+
77
+ # Simple property has exactly one argument and no block
78
+ args.length == 1
79
+ end
80
+
81
+ # Check if method call represents a property with block (object)
82
+ # @param receiver [Parser::AST::Node, nil] Method receiver
83
+ # @param method_name [Symbol] Method name
84
+ # @param has_block [Boolean] Whether the call has a block
85
+ # @return [Boolean] True if this is a property with block
86
+ def property_with_block?(receiver, method_name, has_block)
87
+ json_property?(receiver, method_name) && has_block
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
4
+ class KeyFormatDetector
5
+ class << self
6
+ # Detects if node represents a key formatting method
7
+ # @param receiver [Parser::AST::Node] Receiver node
8
+ # @param method_name [Symbol] Method name
9
+ # @return [Boolean] True if key format method
10
+ def key_format?(receiver, method_name)
11
+ return false unless json_receiver?(receiver)
12
+
13
+ key_format_methods = %i[key_format! deep_format_keys!]
14
+ key_format_methods.include?(method_name)
15
+ end
16
+
17
+ private
18
+
19
+ # Checks if receiver is a json object
20
+ # @param receiver [Parser::AST::Node] Receiver node
21
+ # @return [Boolean] True if json receiver
22
+ def json_receiver?(receiver)
23
+ receiver && receiver.type == :send && receiver.children[1] == :json
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
4
+ class NullHandlingDetector
5
+ class << self
6
+ # Detects if node represents a null/nil handling method
7
+ # @param receiver [Parser::AST::Node] Receiver node
8
+ # @param method_name [Symbol] Method name
9
+ # @return [Boolean] True if null handling method
10
+ def null_handling?(receiver, method_name)
11
+ return false unless json_receiver?(receiver)
12
+
13
+ null_handling_methods = %i[ignore_nil! nil! null!]
14
+ null_handling_methods.include?(method_name)
15
+ end
16
+
17
+ private
18
+
19
+ # Checks if receiver is a json object
20
+ # @param receiver [Parser::AST::Node] Receiver node
21
+ # @return [Boolean] True if json receiver
22
+ def json_receiver?(receiver)
23
+ receiver && receiver.type == :send && receiver.children[1] == :json
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
4
+ class ObjectManipulationDetector
5
+ class << self
6
+ # Detects if node represents an object manipulation method
7
+ # @param receiver [Parser::AST::Node] Receiver node
8
+ # @param method_name [Symbol] Method name
9
+ # @return [Boolean] True if object manipulation method
10
+ def object_manipulation?(receiver, method_name)
11
+ return false unless json_receiver?(receiver)
12
+
13
+ object_manipulation_methods = %i[merge! set! child! cache_root!]
14
+ object_manipulation_methods.include?(method_name)
15
+ end
16
+
17
+ private
18
+
19
+ # Checks if receiver is a json object
20
+ # @param receiver [Parser::AST::Node] Receiver node
21
+ # @return [Boolean] True if json receiver
22
+ def json_receiver?(receiver)
23
+ receiver && receiver.type == :send && receiver.children[1] == :json
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_detector'
4
+
5
+ module RailsOpenapiGen::Parsers::Jbuilder::CallDetectors
6
+ # Detects partial calls (json.partial!)
7
+ # Handles partial template rendering in Jbuilder
8
+ class PartialCallDetector < BaseDetector
9
+ class << self
10
+ # Check if this detector handles the method call
11
+ # @param receiver [Parser::AST::Node, nil] Method receiver node
12
+ # @param method_name [Symbol] Method name
13
+ # @param args [Array<Parser::AST::Node>] Method arguments
14
+ # @return [Boolean] True if this is a partial call
15
+ def handles?(receiver, method_name, _args = [])
16
+ partial_call?(receiver, method_name)
17
+ end
18
+
19
+ # High priority for partial calls
20
+ # @return [Integer] Priority value
21
+ def priority
22
+ 20
23
+ end
24
+
25
+ # @return [Symbol] Detector category
26
+ def category
27
+ :partial
28
+ end
29
+
30
+ # @return [String] Description
31
+ def description
32
+ "Partial calls (json.partial!)"
33
+ end
34
+
35
+ # Check if this is a partial call
36
+ # @param receiver [Parser::AST::Node, nil] Method receiver
37
+ # @param method_name [Symbol] Method name
38
+ # @return [Boolean] True if this is a partial call
39
+ def partial_call?(receiver, method_name)
40
+ method_name == :partial! && json_receiver?(receiver)
41
+ end
42
+
43
+ # Extract partial path from arguments
44
+ # @param args [Array<Parser::AST::Node>] Method arguments
45
+ # @return [String, nil] Partial path
46
+ def extract_partial_path(args)
47
+ return nil if args.empty?
48
+
49
+ first_arg = args.first
50
+ return extract_string_value(first_arg) if literal_node?(first_arg)
51
+
52
+ # Check if it's in a hash under :partial key
53
+ if first_arg.type == :hash
54
+ partial_pair = first_arg.children.find do |pair|
55
+ key_node = pair.children.first
56
+ key_node.type == :sym && key_node.children.first == :partial
57
+ end
58
+
59
+ if partial_pair
60
+ value_node = partial_pair.children.last
61
+ return extract_string_value(value_node)
62
+ end
63
+ end
64
+
65
+ nil
66
+ end
67
+
68
+ # Extract local variables from arguments
69
+ # @param args [Array<Parser::AST::Node>] Method arguments
70
+ # @return [Hash] Local variables hash
71
+ def extract_locals(args)
72
+ locals = {}
73
+
74
+ args.each do |arg|
75
+ next unless arg.type == :hash
76
+
77
+ arg.children.each do |pair|
78
+ next unless pair.type == :pair
79
+
80
+ key_node, value_node = pair.children
81
+ next unless key_node.type == :sym
82
+
83
+ key = key_node.children.first
84
+ next if key == :partial # Skip partial path
85
+
86
+ # For now, just store the key, value extraction would need evaluation
87
+ locals[key] = value_node
88
+ end
89
+ end
90
+
91
+ locals
92
+ end
93
+
94
+ # Check if partial has locals
95
+ # @param args [Array<Parser::AST::Node>] Method arguments
96
+ # @return [Boolean] True if partial has locals
97
+ def has_locals?(args)
98
+ !extract_locals(args).empty?
99
+ end
100
+
101
+ # Check if partial path is a string literal
102
+ # @param args [Array<Parser::AST::Node>] Method arguments
103
+ # @return [Boolean] True if path is literal
104
+ def literal_path?(args)
105
+ return false if args.empty?
106
+
107
+ literal_node?(args.first)
108
+ end
109
+
110
+ # Check if partial is called with collection
111
+ # @param args [Array<Parser::AST::Node>] Method arguments
112
+ # @return [Boolean] True if partial has collection
113
+ def has_collection?(args)
114
+ args.any? do |arg|
115
+ next false unless arg.type == :hash
116
+
117
+ arg.children.any? do |pair|
118
+ key_node = pair.children.first
119
+ key_node.type == :sym && key_node.children.first == :collection
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end