sorbet-baml 0.2.0 → 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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -0
  3. data/README.md +148 -6
  4. data/Rakefile +2 -2
  5. data/examples/description_parameters.rb +16 -16
  6. data/lib/sorbet_baml/comment_extractor.rb +31 -39
  7. data/lib/sorbet_baml/converter.rb +66 -32
  8. data/lib/sorbet_baml/dependency_resolver.rb +11 -11
  9. data/lib/sorbet_baml/description_extension.rb +5 -5
  10. data/lib/sorbet_baml/description_extractor.rb +8 -10
  11. data/lib/sorbet_baml/dspy_tool_converter.rb +97 -0
  12. data/lib/sorbet_baml/dspy_tool_extensions.rb +23 -0
  13. data/lib/sorbet_baml/enum_extensions.rb +2 -2
  14. data/lib/sorbet_baml/struct_extensions.rb +2 -2
  15. data/lib/sorbet_baml/tool_extensions.rb +23 -0
  16. data/lib/sorbet_baml/type_mapper.rb +35 -37
  17. data/lib/sorbet_baml/version.rb +1 -1
  18. data/lib/sorbet_baml.rb +43 -13
  19. metadata +6 -57
  20. data/.idea/.gitignore +0 -8
  21. data/.idea/inspectionProfiles/Project_Default.xml +0 -5
  22. data/.rspec +0 -3
  23. data/sorbet/config +0 -4
  24. data/sorbet/rbi/annotations/.gitattributes +0 -1
  25. data/sorbet/rbi/annotations/rainbow.rbi +0 -269
  26. data/sorbet/rbi/gems/.gitattributes +0 -1
  27. data/sorbet/rbi/gems/ast@2.4.3.rbi +0 -585
  28. data/sorbet/rbi/gems/benchmark@0.4.1.rbi +0 -619
  29. data/sorbet/rbi/gems/byebug@11.1.3.rbi +0 -37
  30. data/sorbet/rbi/gems/date@3.4.1.rbi +0 -75
  31. data/sorbet/rbi/gems/diff-lcs@1.6.2.rbi +0 -1134
  32. data/sorbet/rbi/gems/erb@5.0.2.rbi +0 -878
  33. data/sorbet/rbi/gems/erubi@1.13.1.rbi +0 -155
  34. data/sorbet/rbi/gems/io-console@0.8.1.rbi +0 -9
  35. data/sorbet/rbi/gems/json@2.13.2.rbi +0 -2087
  36. data/sorbet/rbi/gems/language_server-protocol@3.17.0.5.rbi +0 -9
  37. data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +0 -240
  38. data/sorbet/rbi/gems/logger@1.7.0.rbi +0 -963
  39. data/sorbet/rbi/gems/netrc@0.11.0.rbi +0 -159
  40. data/sorbet/rbi/gems/parallel@1.27.0.rbi +0 -291
  41. data/sorbet/rbi/gems/parser@3.3.9.0.rbi +0 -5535
  42. data/sorbet/rbi/gems/pp@0.6.2.rbi +0 -368
  43. data/sorbet/rbi/gems/prettyprint@0.2.0.rbi +0 -477
  44. data/sorbet/rbi/gems/prism@1.4.0.rbi +0 -41732
  45. data/sorbet/rbi/gems/psych@5.2.6.rbi +0 -2469
  46. data/sorbet/rbi/gems/racc@1.8.1.rbi +0 -164
  47. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +0 -403
  48. data/sorbet/rbi/gems/rake@13.3.0.rbi +0 -3031
  49. data/sorbet/rbi/gems/rbi@0.3.6.rbi +0 -6893
  50. data/sorbet/rbi/gems/rbs@3.9.4.rbi +0 -6976
  51. data/sorbet/rbi/gems/rdoc@6.14.2.rbi +0 -12688
  52. data/sorbet/rbi/gems/regexp_parser@2.11.2.rbi +0 -3845
  53. data/sorbet/rbi/gems/reline@0.6.2.rbi +0 -2441
  54. data/sorbet/rbi/gems/rexml@3.4.1.rbi +0 -5240
  55. data/sorbet/rbi/gems/rspec-core@3.13.5.rbi +0 -11250
  56. data/sorbet/rbi/gems/rspec-expectations@3.13.5.rbi +0 -8189
  57. data/sorbet/rbi/gems/rspec-mocks@3.13.5.rbi +0 -5350
  58. data/sorbet/rbi/gems/rspec-support@3.13.4.rbi +0 -1630
  59. data/sorbet/rbi/gems/rspec@3.13.1.rbi +0 -83
  60. data/sorbet/rbi/gems/rubocop-ast@1.46.0.rbi +0 -7764
  61. data/sorbet/rbi/gems/rubocop-sorbet@0.10.5.rbi +0 -2386
  62. data/sorbet/rbi/gems/rubocop@1.79.2.rbi +0 -63321
  63. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +0 -1318
  64. data/sorbet/rbi/gems/spoom@1.6.3.rbi +0 -6985
  65. data/sorbet/rbi/gems/stringio@3.1.7.rbi +0 -9
  66. data/sorbet/rbi/gems/tapioca@0.16.11.rbi +0 -3628
  67. data/sorbet/rbi/gems/thor@1.4.0.rbi +0 -4399
  68. data/sorbet/rbi/gems/unicode-display_width@3.1.5.rbi +0 -132
  69. data/sorbet/rbi/gems/unicode-emoji@4.0.4.rbi +0 -251
  70. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
  71. data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18379
  72. data/sorbet/tapioca/config.yml +0 -13
  73. data/sorbet/tapioca/require.rb +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6ebc9d978b525e29a1a19ef184f073393d6a588e54d84dc2b9fd7034d1fa40c
4
- data.tar.gz: f7d5e028fe65a4a4cc9b19c60dc17dd65fd13353634e55201db5189bb457447a
3
+ metadata.gz: 4de7b80da4ee748d5c22196c22f78ca92e855f9b4983437d4ea6ad02a5201fba
4
+ data.tar.gz: 22a81719e5d2021b52945d913e3134482ff999e917d655f67ec894a35fa2a40b
5
5
  SHA512:
6
- metadata.gz: 40b4736cc561b5135ee32e08543148c018ea9963e016c6cbb66697cd346ff7cf30f7438d370853c045b40d062a47f38d7aaa1a9c6c8e92f8e7f6721b495966e0
7
- data.tar.gz: 0caf0a19906655bbfae88723bbe5b38301601c1523c3cc3b8dc31a15bf6bdd02ceacb790cf0d4bddf718c1162ba51d7b0fcd08110578616cd7c7ac27272bcd42
6
+ metadata.gz: d64e1cef217666030c2f34cacd48333d6ce46d5fac40e2a2855c0f73fe43dadf39a620ad01b37df904ca5aa792a7d46cd3a1bdaf45ce797b34f3010ce4a2be12
7
+ data.tar.gz: 6131b255163a544f2ef7591f58e173ab0436c44426003513888b51ea4d3a35a5ff90d186b491d867035b9d0c86929b176c6ff8bfeb6de7106b87662546ea0c3e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,64 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2025-10-14
4
+
5
+ ### Changed
6
+
7
+ - **Dramatically reduced gem size from 1.46MB to 20KB (98.6% reduction)**
8
+ - Excluded development-only Sorbet RBI files (`sorbet/` directory - 11MB)
9
+ - Excluded documentation site source (`docs-site/` directory - 43MB)
10
+ - Excluded IDE configuration files (`.idea/` directory)
11
+ - Excluded documentation output (`docs-output/` directory)
12
+ - Excluded RSpec configuration files (`.rspec`)
13
+ - **File count reduced from 134 to 22 essential files**
14
+
15
+ ### Technical Details
16
+
17
+ The gem now only includes runtime-required files:
18
+ - Core library code (`lib/` directory)
19
+ - Documentation (README.md, LICENSE.txt, CHANGELOG.md)
20
+ - Examples (`examples/` directory)
21
+
22
+ All Sorbet type checking infrastructure and development tooling remains available in the repository for contributors but is no longer shipped to end users.
23
+
24
+ ### Impact
25
+
26
+ - **Faster gem installation** - 92% less data to download
27
+ - **Smaller disk footprint** - More efficient for deployed applications
28
+ - **No functionality changes** - All features work exactly as before
29
+ - **Zero breaking changes** - Full backward compatibility maintained
30
+
31
+ ## [0.3.0] - 2025-08-17
32
+
33
+ ### Added
34
+
35
+ - Comprehensive BAML tool type definitions for agentic workflows
36
+ - `.to_baml_tool()` method for T::Struct classes
37
+ - Automatic DSPy tool conversion when `dspy.rb` is available
38
+ - Tool metadata extraction (`tool_name`, `tool_description`)
39
+ - Support for function calling APIs (OpenAI, Anthropic, etc.)
40
+
41
+ ### Improved
42
+
43
+ - 80+ test cases with comprehensive coverage
44
+ - Full Sorbet type safety throughout
45
+ - Enhanced documentation with tool examples
46
+
47
+ ## [0.2.0] - 2025-08-16
48
+
49
+ ### Added
50
+
51
+ - Clean `description:` parameter for `const` and `prop` declarations
52
+ - Smart fallback chain: description parameter → Ruby comments → nil
53
+ - `DescriptionExtension` for T::Struct
54
+ - `DescriptionExtractor` for programmatic access
55
+
56
+ ### Improved
57
+
58
+ - Better LLM context with rich field descriptions
59
+ - Zero breaking changes to existing APIs
60
+ - Full backward compatibility
61
+
3
62
  ## [0.1.0] - 2025-08-16
4
63
 
5
64
  - Initial release
data/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # sorbet-baml
2
2
 
3
+ [![Gem Version](https://img.shields.io/gem/v/sorbet-baml)](https://rubygems.org/gems/sorbet-baml)
4
+ [![Total Downloads](https://img.shields.io/gem/dt/sorbet-baml)](https://rubygems.org/gems/sorbet-baml)
5
+ [![License](https://img.shields.io/github/license/vicentereig/sorbet-baml)](https://github.com/vicentereig/sorbet-baml/blob/main/LICENSE.txt)
6
+ [![Sorbet Compatible](https://img.shields.io/badge/Sorbet-compatible-blue)](https://sorbet.org)
7
+
3
8
  Ruby-idiomatic conversion from Sorbet types to BAML (Boundary AI Markup Language) for efficient LLM prompting.
4
9
 
5
10
  ## What is this?
@@ -142,7 +147,29 @@ SorbetBaml.from_structs([ResearchSynthesis, ResearchFindings])
142
147
 
143
148
  ## 🎯 Field Descriptions for LLM Context
144
149
 
145
- Add crucial context to your BAML types by documenting fields with comments - essential for autonomous agents and complex workflows:
150
+ Add crucial context to your BAML types by documenting fields - essential for autonomous agents and complex workflows. The gem supports **two ways** to provide field descriptions:
151
+
152
+ ### Method 1: Comments (Traditional)
153
+ ```ruby
154
+ class ResearchTask < T::Struct
155
+ # Clear description of the research objective
156
+ const :objective, String
157
+ # Strategic priority ranking (1-5 scale)
158
+ const :priority, Integer
159
+ end
160
+ ```
161
+
162
+ ### Method 2: Description Parameter (New in v0.2.0+)
163
+ ```ruby
164
+ class ResearchTask < T::Struct
165
+ const :objective, String, description: "Clear description of the research objective"
166
+ const :priority, Integer, description: "Strategic priority ranking (1-5 scale)"
167
+ end
168
+ ```
169
+
170
+ Both methods produce identical BAML output with `@description` annotations. The `description:` parameter provides a more explicit, Sorbet-native way to document fields.
171
+
172
+ ### Complete Example
146
173
 
147
174
  ```ruby
148
175
  class TaskType < T::Enum
@@ -193,6 +220,112 @@ ResearchSubtask.to_baml
193
220
 
194
221
  **Why descriptions matter**: LLMs use field descriptions to understand context and generate more accurate, meaningful data. This is crucial for complex domains where field names alone aren't sufficient.
195
222
 
223
+ ## 🛠️ Tool Type Definitions
224
+
225
+ Generate BAML tool specifications for agentic workflows, function calling, and structured LLM interactions:
226
+
227
+ ### T::Struct-based Tools
228
+
229
+ ```ruby
230
+ class ReplyTool < T::Struct
231
+ # The response message to send back to the user
232
+ const :response, String
233
+ end
234
+
235
+ class SearchTool < T::Struct
236
+ # Using description: parameter (both methods work)
237
+ const :query, String, description: "The search query to execute"
238
+ const :limit, T.nilable(Integer), description: "Maximum number of results to return"
239
+ end
240
+
241
+ # Generate BAML tool definitions
242
+ ReplyTool.to_baml_tool
243
+ # =>
244
+ # class ReplyTool {
245
+ # response string @description("The response message to send back to the user")
246
+ # }
247
+
248
+ SearchTool.to_baml_tool
249
+ # =>
250
+ # class SearchTool {
251
+ # query string @description("The search query to execute")
252
+ # limit int? @description("Maximum number of results to return")
253
+ # }
254
+
255
+ # Module API also available
256
+ SorbetBaml.from_tool(ReplyTool)
257
+ ```
258
+
259
+ ### DSPy-style Tools (Optional)
260
+
261
+ When `dspy.rb` is available, automatically convert DSPy tools with rich metadata:
262
+
263
+ ```ruby
264
+ class CalculatorTool < DSPy::Tools::Base
265
+ extend T::Sig
266
+
267
+ tool_name 'calculator'
268
+ tool_description 'Performs basic arithmetic operations'
269
+
270
+ sig { params(operation: String, num1: Float, num2: Float).returns(T.any(Float, String)) }
271
+ def call(operation:, num1:, num2:)
272
+ case operation.downcase
273
+ when 'add' then num1 + num2
274
+ when 'subtract' then num1 - num2
275
+ when 'multiply' then num1 * num2
276
+ when 'divide'
277
+ return "Error: Cannot divide by zero" if num2 == 0
278
+ num1 / num2
279
+ else
280
+ "Error: Unknown operation '#{operation}'. Use add, subtract, multiply, or divide"
281
+ end
282
+ end
283
+ end
284
+
285
+ # Automatic extraction of tool metadata and parameter types
286
+ CalculatorTool.to_baml
287
+ # =>
288
+ # // Performs basic arithmetic operations
289
+ # class calculator {
290
+ # operation string @description("Parameter operation")
291
+ # num1 float @description("Parameter num1")
292
+ # num2 float @description("Parameter num2")
293
+ # }
294
+
295
+ # Optional parameters handled correctly
296
+ class SearchTool < DSPy::Tools::Base
297
+ extend T::Sig
298
+
299
+ tool_name 'search'
300
+ tool_description 'Search for information'
301
+
302
+ sig { params(query: String, limit: T.nilable(Integer)).returns(T::Array[String]) }
303
+ def call(query:, limit: nil)
304
+ # Implementation...
305
+ end
306
+ end
307
+
308
+ SearchTool.to_baml
309
+ # =>
310
+ # // Search for information
311
+ # class search {
312
+ # query string @description("Parameter query")
313
+ # limit int? @description("Parameter limit (optional)")
314
+ # }
315
+
316
+ # Module API also available
317
+ SorbetBaml.from_dspy_tool(CalculatorTool)
318
+ ```
319
+
320
+ **Tool Features:**
321
+ - ✅ **T::Struct tools**: Convert any struct to BAML tool definition
322
+ - ✅ **DSPy integration**: Automatic extraction from DSPy::Tools::Base classes
323
+ - ✅ **Parameter types**: Full Sorbet type support (string, int, float, arrays, maps, etc.)
324
+ - ✅ **Optional parameters**: Automatically detect and mark with `?`
325
+ - ✅ **Descriptions**: Extract from comments or `description:` parameter (T::Struct) or automatic generation (DSPy)
326
+ - ✅ **Tool metadata**: Names, descriptions, and parameter documentation
327
+ - ✅ **Ruby-idiomatic**: `.to_baml_tool()` and `.to_baml()` methods
328
+
196
329
  ## 🎯 Complete Type Support
197
330
 
198
331
  ### ✅ Fully Supported
@@ -222,8 +355,10 @@ ResearchSubtask.to_baml
222
355
  ### 🚀 Advanced Features
223
356
 
224
357
  - **Ruby-idiomatic API**: Every T::Struct and T::Enum gets `.to_baml` method
358
+ - **Tool definitions**: Generate BAML tool specs for function calling and agentic workflows
359
+ - **DSPy integration**: Automatic tool conversion from DSPy::Tools::Base classes
225
360
  - **Smart defaults**: Field descriptions and dependencies included automatically
226
- - **Field descriptions**: Extracts comments from source code for LLM context
361
+ - **Field descriptions**: Extracts from comments or `description:` parameter for LLM context
227
362
  - **Dependency management**: Automatically includes all referenced types
228
363
  - **Proper ordering**: Dependencies are sorted topologically (no forward references needed)
229
364
  - **Circular reference handling**: Won't get stuck in infinite loops
@@ -249,29 +384,34 @@ ResearchSubtask.to_baml
249
384
 
250
385
  ## 🏁 Production Ready
251
386
 
252
- This gem has reached **feature completeness** for core BAML conversion needs. The Ruby-idiomatic API is stable and thoroughly tested with **50+ test cases** covering all type combinations and edge cases.
387
+ This gem has reached **feature completeness** for core BAML conversion needs. The Ruby-idiomatic API is stable and thoroughly tested with **80 test cases** covering all type combinations, tool definitions, and edge cases.
253
388
 
254
389
  ### 📊 Quality Metrics
255
390
 
256
- - ✅ **100% Test Coverage** - All features comprehensively tested
391
+ - ✅ **Comprehensive Test Coverage** - All features thoroughly tested
257
392
  - ✅ **Full Sorbet Type Safety** - Zero type errors throughout codebase
258
- - ✅ **50+ Test Cases** - Covering basic types, complex combinations, and edge cases
393
+ - ✅ **80 Test Cases** - Covering basic types, complex combinations, tool definitions, and edge cases
259
394
  - ✅ **TDD Development** - All features built test-first
260
395
  - ✅ **Field Descriptions** - Automatic comment extraction for LLM context
396
+ - ✅ **Tool Definitions** - BAML tool specifications for function calling and agentic workflows
397
+ - ✅ **DSPy Integration** - Automatic tool conversion from DSPy::Tools::Base classes
261
398
  - ✅ **Smart Defaults** - Dependencies and descriptions included by default
262
399
  - ✅ **Zero Breaking Changes** - Maintains backward compatibility
263
400
 
264
401
  ### ✅ Complete Feature Set
265
402
 
266
403
  - ✅ **Ruby-idiomatic API**: Every T::Struct and T::Enum gets `.to_baml` method
404
+ - ✅ **Tool definitions**: Generate BAML tool specifications from T::Struct classes
405
+ - ✅ **DSPy integration**: Automatic tool conversion from DSPy::Tools::Base classes
267
406
  - ✅ **Smart defaults**: Field descriptions and dependencies included automatically
268
- - ✅ **Field descriptions**: Extract documentation from comments for LLM context
407
+ - ✅ **Field descriptions**: Extract documentation from comments or `description:` parameter for LLM context
269
408
  - ✅ **Dependency management**: Automatically includes all referenced types
270
409
  - ✅ **Proper ordering**: Dependencies are sorted topologically
271
410
  - ✅ **Type safety**: Full Sorbet type checking throughout
272
411
 
273
412
  ### 🗺️ Future Enhancements (Optional)
274
413
 
414
+ - [ ] **DSPy-independent tool API**: Tools shouldn't require DSPy, just follow the same API pattern
275
415
  - [ ] **Type aliases**: `T.type_alias { String }` → `type Alias = string`
276
416
  - [ ] **Custom naming**: Convert between snake_case ↔ camelCase
277
417
  - [ ] **CLI tool**: `sorbet-baml convert MyStruct` command
@@ -282,6 +422,8 @@ This gem has reached **feature completeness** for core BAML conversion needs. Th
282
422
 
283
423
  - **v0.0.1** - Initial implementation with basic type support
284
424
  - **v0.1.0** - Complete type system + Ruby-idiomatic API + field descriptions + smart defaults
425
+ - **v0.2.0** - Description parameter support and enhanced field extraction
426
+ - **v0.3.0** - Tool type definitions + DSPy integration + 80+ test cases + comprehensive documentation
285
427
 
286
428
  ## 🌟 Real-World Usage: Autonomous Research Agents
287
429
 
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
@@ -3,15 +3,15 @@
3
3
 
4
4
  require_relative '../lib/sorbet_baml'
5
5
 
6
- puts "🎯 Description Parameter Support Demo"
7
- puts "=" * 50
6
+ puts '🎯 Description Parameter Support Demo'
7
+ puts '=' * 50
8
8
 
9
9
  # Example 1: Basic description parameters
10
10
  class User < T::Struct
11
11
  const :name, String, description: "User's full legal name"
12
- prop :age, Integer, description: "Age in years"
13
- const :email, T.nilable(String), description: "Optional email address for notifications"
14
- const :interests, T::Array[String], description: "List of user hobbies and interests"
12
+ prop :age, Integer, description: 'Age in years'
13
+ const :email, T.nilable(String), description: 'Optional email address for notifications'
14
+ const :interests, T::Array[String], description: 'List of user hobbies and interests'
15
15
  end
16
16
 
17
17
  puts "\n1. Basic T::Struct with description parameters:"
@@ -21,13 +21,13 @@ puts User.to_baml
21
21
  class Product < T::Struct
22
22
  # This comment will be used as fallback
23
23
  const :id, String
24
-
25
- const :name, String, description: "Product name for display"
26
-
24
+
25
+ const :name, String, description: 'Product name for display'
26
+
27
27
  # Price in USD cents
28
28
  prop :price_cents, Integer
29
-
30
- const :category, String, description: "Product category classification"
29
+
30
+ const :category, String, description: 'Product category classification'
31
31
  end
32
32
 
33
33
  puts "\n2. Mixed description sources (parameters take priority):"
@@ -35,15 +35,15 @@ puts Product.to_baml
35
35
 
36
36
  # Example 3: Complex nested types with descriptions
37
37
  class Order < T::Struct
38
- const :id, String, description: "Unique order identifier"
39
- const :customer, User, description: "Customer who placed the order"
40
- const :items, T::Array[Product], description: "List of ordered products"
41
- const :total_cents, Integer, description: "Total order value in USD cents"
42
- const :status, String, description: "Current order processing status"
38
+ const :id, String, description: 'Unique order identifier'
39
+ const :customer, User, description: 'Customer who placed the order'
40
+ const :items, T::Array[Product], description: 'List of ordered products'
41
+ const :total_cents, Integer, description: 'Total order value in USD cents'
42
+ const :status, String, description: 'Current order processing status'
43
43
  end
44
44
 
45
45
  puts "\n3. Complex nested types with dependencies:"
46
46
  puts Order.to_baml
47
47
 
48
48
  puts "\n✨ Beautiful, readable, and LLM-friendly!"
49
- puts "🚀 Perfect for DSPy.rb, autonomous agents, and structured LLM outputs"
49
+ puts '🚀 Perfect for DSPy.rb, autonomous agents, and structured LLM outputs'
@@ -10,67 +10,63 @@ module SorbetBaml
10
10
  def self.extract_field_comments(klass)
11
11
  # First try to get descriptions from the description extractor (extra field)
12
12
  descriptions = DescriptionExtractor.extract_prop_descriptions(klass)
13
-
13
+
14
14
  # Then fall back to comment-based extraction for any missing descriptions
15
15
  comments = {}
16
16
  source_file = find_source_file(klass)
17
-
17
+
18
18
  if source_file && File.exist?(source_file)
19
19
  lines = File.readlines(source_file)
20
20
  extract_comments_from_lines(lines, T.must(T.must(klass.name).split('::').last), comments)
21
21
  end
22
-
22
+
23
23
  # Merge with priority: description parameters > comments
24
- descriptions.merge(comments) { |key, desc_param, comment| desc_param }
24
+ descriptions.merge(comments) { |_key, desc_param, _comment| desc_param }
25
25
  end
26
26
 
27
27
  sig { params(klass: T.class_of(T::Enum)).returns(T::Hash[String, T.nilable(String)]) }
28
28
  def self.extract_enum_comments(klass)
29
29
  comments = {}
30
30
  source_file = find_source_file(klass)
31
-
31
+
32
32
  return comments unless source_file && File.exist?(source_file)
33
-
33
+
34
34
  lines = File.readlines(source_file)
35
35
  extract_enum_comments_from_lines(lines, T.must(T.must(klass.name).split('::').last), comments)
36
-
36
+
37
37
  comments
38
38
  end
39
39
 
40
- private
41
-
42
40
  sig { params(klass: T::Class[T.anything]).returns(T.nilable(String)) }
43
41
  def self.find_source_file(klass)
44
42
  # Try to find where the class was defined
45
43
  # This is a heuristic approach since Ruby doesn't provide reliable source location for classes
46
-
44
+
47
45
  # Method 1: Check if any methods have source location
48
46
  begin
49
47
  if klass.respond_to?(:new) && klass.method(:new).respond_to?(:source_location)
50
48
  location = klass.method(:new).source_location
51
49
  return location[0] if location
52
50
  end
53
- rescue
51
+ rescue StandardError
54
52
  # Ignore errors
55
53
  end
56
-
54
+
57
55
  # Method 2: Look at the current call stack for files that might contain the class
58
56
  caller_locations.each do |location|
59
57
  file_path = location.absolute_path || location.path
60
58
  next unless file_path && File.exist?(file_path)
61
-
59
+
62
60
  # Read the file and check if it contains the class definition
63
61
  begin
64
62
  content = File.read(file_path)
65
63
  class_name = T.must(klass.name).split('::').last
66
- if content.match(/class\s+#{Regexp.escape(T.must(class_name))}\s*</)
67
- return file_path
68
- end
69
- rescue
64
+ return file_path if content.match(/class\s+#{Regexp.escape(T.must(class_name))}\s*</)
65
+ rescue StandardError
70
66
  # Ignore file read errors
71
67
  end
72
68
  end
73
-
69
+
74
70
  nil
75
71
  end
76
72
 
@@ -79,28 +75,26 @@ module SorbetBaml
79
75
  in_target_class = T.let(false, T::Boolean)
80
76
  current_comment = T.let(nil, T.nilable(String))
81
77
  brace_depth = 0
82
-
78
+
83
79
  lines.each do |line|
84
80
  stripped = line.strip
85
-
81
+
86
82
  # Check if we're entering the target class
87
83
  if stripped.match(/^class\s+#{Regexp.escape(class_name)}\s*<\s*T::Struct/)
88
84
  in_target_class = true
89
85
  brace_depth = 0
90
86
  next
91
87
  end
92
-
88
+
93
89
  next unless in_target_class
94
-
90
+
95
91
  # Track brace depth to handle nested classes
96
92
  brace_depth += stripped.count('{')
97
93
  brace_depth -= stripped.count('}')
98
-
94
+
99
95
  # Exit when we reach the end of the class
100
- if stripped == 'end' && brace_depth == 0
101
- break
102
- end
103
-
96
+ break if stripped == 'end' && brace_depth == 0
97
+
104
98
  # Extract comment
105
99
  if stripped.start_with?('#')
106
100
  comment_text = T.must(stripped[1..-1]).strip
@@ -121,37 +115,35 @@ module SorbetBaml
121
115
  in_target_class = T.let(false, T::Boolean)
122
116
  in_enums_block = T.let(false, T::Boolean)
123
117
  current_comment = T.let(nil, T.nilable(String))
124
-
118
+
125
119
  lines.each do |line|
126
120
  stripped = line.strip
127
-
121
+
128
122
  # Check if we're entering the target enum class
129
123
  if stripped.match(/^class\s+#{Regexp.escape(class_name)}\s*<\s*T::Enum/)
130
124
  in_target_class = true
131
125
  next
132
126
  end
133
-
127
+
134
128
  next unless in_target_class
135
-
129
+
136
130
  # Check if we're in the enums block
137
131
  if stripped == 'enums do'
138
132
  in_enums_block = true
139
133
  next
140
134
  end
141
-
135
+
142
136
  # Exit enums block
143
137
  if in_enums_block && stripped == 'end'
144
138
  in_enums_block = false
145
139
  next
146
140
  end
147
-
141
+
148
142
  # Exit class
149
- if stripped == 'end' && !in_enums_block
150
- break
151
- end
152
-
143
+ break if stripped == 'end' && !in_enums_block
144
+
153
145
  next unless in_enums_block
154
-
146
+
155
147
  # Extract comment
156
148
  if stripped.start_with?('#')
157
149
  comment_text = T.must(stripped[1..-1]).strip
@@ -167,4 +159,4 @@ module SorbetBaml
167
159
  end
168
160
  end
169
161
  end
170
- end
162
+ end