synthra 0.1.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 (302) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rubocop.yml +40 -0
  4. data/.yardopts +18 -0
  5. data/ACCESS_DOCS.md +116 -0
  6. data/ADVANCED_FEATURES_SUMMARY.md +245 -0
  7. data/CHANGELOG.md +498 -0
  8. data/CODE_OF_CONDUCT.md +122 -0
  9. data/CONTRIBUTING.md +307 -0
  10. data/DOCUMENTATION.md +204 -0
  11. data/DOCUMENTATION_GUIDE.md +194 -0
  12. data/LICENSE +22 -0
  13. data/README.md +1458 -0
  14. data/Rakefile +128 -0
  15. data/benchmark/README.md +591 -0
  16. data/benchmark/batch_generation.rb +63 -0
  17. data/benchmark/engine_comparison.rb +295 -0
  18. data/benchmark/single_record.rb +108 -0
  19. data/benchmark/streaming.rb +74 -0
  20. data/docs/ACTIVERECORD_INFERENCE.md +524 -0
  21. data/docs/API_SERVER.md +278 -0
  22. data/docs/CONFIG_FILE.md +315 -0
  23. data/docs/DATA_CONTRACTS.md +312 -0
  24. data/docs/ENHANCED_REPL.md +304 -0
  25. data/docs/FACTORY_BOT.md +271 -0
  26. data/docs/GITHUB_ACTION.md +420 -0
  27. data/docs/GRAPHQL_EXPORT.md +303 -0
  28. data/docs/GRAPHQL_FEDERATION.md +651 -0
  29. data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
  30. data/docs/LSP.md +189 -0
  31. data/docs/MIGRATION_GENERATOR.md +547 -0
  32. data/docs/MOCK_SERVER.md +280 -0
  33. data/docs/NATIVE_ENGINE.md +298 -0
  34. data/docs/OPENAPI_EXPORT.md +252 -0
  35. data/docs/PERFORMANCE_MODE.md +228 -0
  36. data/docs/PERSONAS.md +547 -0
  37. data/docs/PROPERTY_TESTING.md +284 -0
  38. data/docs/PROTOBUF_EXPORT.md +276 -0
  39. data/docs/QUALITY_METRICS.md +555 -0
  40. data/docs/QUICK_REFERENCE.md +88 -0
  41. data/docs/RAILS_ENGINE.md +404 -0
  42. data/docs/RAILS_INTEGRATION.md +454 -0
  43. data/docs/README.md +175 -0
  44. data/docs/REPL_QUICK_REFERENCE.md +149 -0
  45. data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
  46. data/docs/SCENARIOS.md +576 -0
  47. data/docs/SECURITY_TESTING.md +318 -0
  48. data/docs/SNAPSHOT_TESTING.md +308 -0
  49. data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
  50. data/docs/TERRAFORM_EXPORT.md +447 -0
  51. data/docs/TIME_TRAVEL.md +507 -0
  52. data/docs/USAGE_GUIDE.md +980 -0
  53. data/docs/WEBHOOK_SIMULATOR.md +579 -0
  54. data/docs/advanced_features.md +183 -0
  55. data/docs/api_reference.md +153 -0
  56. data/docs/behaviors.md +138 -0
  57. data/docs/best_practices.md +191 -0
  58. data/docs/dsl_reference.md +172 -0
  59. data/docs/examples.md +162 -0
  60. data/docs/generation_modes.md +125 -0
  61. data/docs/getting_started.md +92 -0
  62. data/docs/index.html +177 -0
  63. data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
  64. data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
  65. data/docs/troubleshooting.md +215 -0
  66. data/docs/type_reference.md +224 -0
  67. data/examples/hostile_mode_demo.rb +64 -0
  68. data/exe/synthra +6 -0
  69. data/ext/synthra_native/Cargo.lock +972 -0
  70. data/ext/synthra_native/Cargo.toml +46 -0
  71. data/ext/synthra_native/extconf.rb +41 -0
  72. data/ext/synthra_native/src/generator.rs +397 -0
  73. data/ext/synthra_native/src/lib.rs +229 -0
  74. data/ext/synthra_native/src/types.rs +1485 -0
  75. data/lib/generators/synthra/install_generator.rb +86 -0
  76. data/lib/generators/synthra/templates/api_response.dsl +15 -0
  77. data/lib/generators/synthra/templates/fake_data.rake +116 -0
  78. data/lib/generators/synthra/templates/synthra.yml +31 -0
  79. data/lib/generators/synthra/templates/synthra_support.rb +19 -0
  80. data/lib/generators/synthra/templates/user.dsl +11 -0
  81. data/lib/synthra/activerecord_inference.rb +394 -0
  82. data/lib/synthra/api.rb +381 -0
  83. data/lib/synthra/api_server.rb +718 -0
  84. data/lib/synthra/behaviors/applicator.rb +165 -0
  85. data/lib/synthra/behaviors/base.rb +155 -0
  86. data/lib/synthra/behaviors/close_connection.rb +60 -0
  87. data/lib/synthra/behaviors/deprecated.rb +100 -0
  88. data/lib/synthra/behaviors/failure.rb +69 -0
  89. data/lib/synthra/behaviors/latency.rb +103 -0
  90. data/lib/synthra/behaviors/partial_data.rb +83 -0
  91. data/lib/synthra/behaviors/randomize_order.rb +74 -0
  92. data/lib/synthra/behaviors/registry.rb +238 -0
  93. data/lib/synthra/behaviors/simulate_error.rb +79 -0
  94. data/lib/synthra/cli/commands/base.rb +105 -0
  95. data/lib/synthra/cli/commands/diff.rb +227 -0
  96. data/lib/synthra/cli/commands/docs.rb +67 -0
  97. data/lib/synthra/cli/commands/export.rb +150 -0
  98. data/lib/synthra/cli/commands/generate.rb +72 -0
  99. data/lib/synthra/cli/commands/import.rb +80 -0
  100. data/lib/synthra/cli/commands/lint.rb +53 -0
  101. data/lib/synthra/cli/commands/live.rb +57 -0
  102. data/lib/synthra/cli/commands/seed.rb +99 -0
  103. data/lib/synthra/cli/commands/validate.rb +28 -0
  104. data/lib/synthra/cli.rb +2471 -0
  105. data/lib/synthra/config_file.rb +129 -0
  106. data/lib/synthra/configuration.rb +281 -0
  107. data/lib/synthra/contracts_registry.rb +408 -0
  108. data/lib/synthra/database_seeder.rb +268 -0
  109. data/lib/synthra/deterministic_ids.rb +218 -0
  110. data/lib/synthra/documentation_generator.rb +414 -0
  111. data/lib/synthra/engine.rb +251 -0
  112. data/lib/synthra/errors.rb +1169 -0
  113. data/lib/synthra/export/base.rb +85 -0
  114. data/lib/synthra/export/csv.rb +101 -0
  115. data/lib/synthra/export/graphql.rb +266 -0
  116. data/lib/synthra/export/graphql_federation.rb +377 -0
  117. data/lib/synthra/export/graphviz.rb +258 -0
  118. data/lib/synthra/export/javascript.rb +327 -0
  119. data/lib/synthra/export/json_data.rb +61 -0
  120. data/lib/synthra/export/json_schema.rb +290 -0
  121. data/lib/synthra/export/openapi.rb +514 -0
  122. data/lib/synthra/export/protobuf.rb +483 -0
  123. data/lib/synthra/export/python.rb +560 -0
  124. data/lib/synthra/export/sql.rb +381 -0
  125. data/lib/synthra/export/sql_insert.rb +152 -0
  126. data/lib/synthra/export/terraform.rb +501 -0
  127. data/lib/synthra/export/type_mapping.rb +389 -0
  128. data/lib/synthra/export/typescript.rb +300 -0
  129. data/lib/synthra/export/xml_data.rb +104 -0
  130. data/lib/synthra/export/yaml_data.rb +56 -0
  131. data/lib/synthra/export.rb +404 -0
  132. data/lib/synthra/factory_bot_integration.rb +157 -0
  133. data/lib/synthra/field.rb +440 -0
  134. data/lib/synthra/functions/registry.rb +104 -0
  135. data/lib/synthra/generator/context.rb +372 -0
  136. data/lib/synthra/generator/engine.rb +336 -0
  137. data/lib/synthra/generator/faker_adapter.rb +425 -0
  138. data/lib/synthra/generator/modes.rb +444 -0
  139. data/lib/synthra/generator/resolver.rb +256 -0
  140. data/lib/synthra/generator/rng.rb +273 -0
  141. data/lib/synthra/generator/streamer.rb +63 -0
  142. data/lib/synthra/generator/uniqueness.rb +118 -0
  143. data/lib/synthra/initializer.rb +97 -0
  144. data/lib/synthra/limits.rb +279 -0
  145. data/lib/synthra/live_preview.rb +518 -0
  146. data/lib/synthra/loader_config.rb +78 -0
  147. data/lib/synthra/lsp/server.rb +689 -0
  148. data/lib/synthra/migration_generator.rb +301 -0
  149. data/lib/synthra/mixin.rb +174 -0
  150. data/lib/synthra/mock_server.rb +458 -0
  151. data/lib/synthra/native_engine.rb +304 -0
  152. data/lib/synthra/openapi_importer.rb +228 -0
  153. data/lib/synthra/output/json_formatter.rb +40 -0
  154. data/lib/synthra/output/ndjson_formatter.rb +55 -0
  155. data/lib/synthra/parser/ast.rb +1287 -0
  156. data/lib/synthra/parser/lexer.rb +1152 -0
  157. data/lib/synthra/parser/parser.rb +1664 -0
  158. data/lib/synthra/parser/tokens.rb +461 -0
  159. data/lib/synthra/performance_mode.rb +364 -0
  160. data/lib/synthra/personas.rb +397 -0
  161. data/lib/synthra/property_testing.rb +172 -0
  162. data/lib/synthra/quality_metrics.rb +405 -0
  163. data/lib/synthra/rails_test_helper.rb +248 -0
  164. data/lib/synthra/registry.rb +533 -0
  165. data/lib/synthra/relationships.rb +193 -0
  166. data/lib/synthra/repl/enhanced_repl.rb +607 -0
  167. data/lib/synthra/repl/formatter.rb +191 -0
  168. data/lib/synthra/scenarios.rb +423 -0
  169. data/lib/synthra/schema.rb +605 -0
  170. data/lib/synthra/schema_inheritance.rb +104 -0
  171. data/lib/synthra/schema_versioning.rb +212 -0
  172. data/lib/synthra/snapshot_testing.rb +199 -0
  173. data/lib/synthra/time_travel.rb +338 -0
  174. data/lib/synthra/type_definitions.rb +274 -0
  175. data/lib/synthra/types/address_location/addresses.rb +125 -0
  176. data/lib/synthra/types/address_location/airports.rb +200 -0
  177. data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
  178. data/lib/synthra/types/address_location/locations.rb +406 -0
  179. data/lib/synthra/types/base.rb +48 -0
  180. data/lib/synthra/types/commerce_products/commerce.rb +119 -0
  181. data/lib/synthra/types/commerce_products/companies.rb +71 -0
  182. data/lib/synthra/types/commerce_products/construction.rb +126 -0
  183. data/lib/synthra/types/commerce_products/products.rb +205 -0
  184. data/lib/synthra/types/core/collections.rb +513 -0
  185. data/lib/synthra/types/core/defaults.rb +76 -0
  186. data/lib/synthra/types/core/enums.rb +102 -0
  187. data/lib/synthra/types/core/identifiers.rb +445 -0
  188. data/lib/synthra/types/core/primitives.rb +586 -0
  189. data/lib/synthra/types/core/references.rb +466 -0
  190. data/lib/synthra/types/core/sequences.rb +304 -0
  191. data/lib/synthra/types/crypto/crypto.rb +162 -0
  192. data/lib/synthra/types/date_time/dates.rb +387 -0
  193. data/lib/synthra/types/finance_banking/banking.rb +424 -0
  194. data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
  195. data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
  196. data/lib/synthra/types/formula.rb +184 -0
  197. data/lib/synthra/types/health_medical/medical.rb +150 -0
  198. data/lib/synthra/types/hostile_payloads.rb +122 -0
  199. data/lib/synthra/types/json_array.rb +51 -0
  200. data/lib/synthra/types/media_entertainment/media.rb +62 -0
  201. data/lib/synthra/types/naughty_string.rb +30 -0
  202. data/lib/synthra/types/personal_names/chinese.rb +41 -0
  203. data/lib/synthra/types/personal_names/identifiers.rb +187 -0
  204. data/lib/synthra/types/personal_names/names.rb +246 -0
  205. data/lib/synthra/types/personal_names/national_id.rb +89 -0
  206. data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
  207. data/lib/synthra/types/regex.rb +248 -0
  208. data/lib/synthra/types/registry.rb +182 -0
  209. data/lib/synthra/types/repeating_element.rb +50 -0
  210. data/lib/synthra/types/scenario.rb +29 -0
  211. data/lib/synthra/types/technology_internet/apps.rb +67 -0
  212. data/lib/synthra/types/technology_internet/communication.rb +162 -0
  213. data/lib/synthra/types/technology_internet/devices.rb +80 -0
  214. data/lib/synthra/types/technology_internet/formats.rb +139 -0
  215. data/lib/synthra/types/technology_internet/networking.rb +143 -0
  216. data/lib/synthra/types/template.rb +92 -0
  217. data/lib/synthra/types/text_content/business.rb +128 -0
  218. data/lib/synthra/types/text_content/colors.rb +70 -0
  219. data/lib/synthra/types/text_content/misc.rb +237 -0
  220. data/lib/synthra/types/text_content/security.rb +130 -0
  221. data/lib/synthra/types/text_content/text_generation.rb +532 -0
  222. data/lib/synthra/types/travel/travel.rb +150 -0
  223. data/lib/synthra/utils/string_distance.rb +133 -0
  224. data/lib/synthra/validator/dsl_validator.rb +339 -0
  225. data/lib/synthra/validator/path_validator.rb +615 -0
  226. data/lib/synthra/version.rb +35 -0
  227. data/lib/synthra/webhook_simulator.rb +341 -0
  228. data/lib/synthra.rb +259 -0
  229. data/schemas/address.dsl +16 -0
  230. data/schemas/api_response.dsl +8 -0
  231. data/schemas/error_payload.dsl +9 -0
  232. data/schemas/order.dsl +10 -0
  233. data/schemas/order_item.dsl +8 -0
  234. data/schemas/payment.dsl +11 -0
  235. data/schemas/social_post.dsl +13 -0
  236. data/schemas/user.dsl +10 -0
  237. data/scripts/batch_fix_all.rb +96 -0
  238. data/scripts/delete_old_files.rb +32 -0
  239. data/scripts/fix_all_grouped_files.rb +137 -0
  240. data/scripts/fix_all_indentation.rb +48 -0
  241. data/scripts/fix_all_syntax.rb +124 -0
  242. data/scripts/fix_grouped_files.rb +184 -0
  243. data/scripts/fix_syntax_errors.rb +108 -0
  244. data/scripts/group_domain_types.rb +112 -0
  245. data/scripts/group_domain_types_fixed.rb +150 -0
  246. data/scripts/merge_domains_to_one_file.rb +68 -0
  247. data/scripts/move_existing_types.rb +130 -0
  248. data/scripts/split_grouped_types.rb +142 -0
  249. data/tech_docs/README.md +134 -0
  250. data/tech_docs/advanced/streaming.md +405 -0
  251. data/tech_docs/advanced/thread_safety.md +253 -0
  252. data/tech_docs/api/overview.md +485 -0
  253. data/tech_docs/appendices/type_chart.md +193 -0
  254. data/tech_docs/basic_concepts.md +383 -0
  255. data/tech_docs/behaviors/overview.md +346 -0
  256. data/tech_docs/dsl/complex_types.md +790 -0
  257. data/tech_docs/dsl/core_types.md +464 -0
  258. data/tech_docs/dsl/datetime_types.md +325 -0
  259. data/tech_docs/dsl/field_modifiers.md +414 -0
  260. data/tech_docs/dsl/grammar.md +431 -0
  261. data/tech_docs/dsl/schema_definition.md +399 -0
  262. data/tech_docs/export/README.md +276 -0
  263. data/tech_docs/installation.md +273 -0
  264. data/tech_docs/integration/ci_cd.md +707 -0
  265. data/tech_docs/integration/ci_cd_guide.md +579 -0
  266. data/tech_docs/integration/factory_bot.md +485 -0
  267. data/tech_docs/integration/rails.md +630 -0
  268. data/tech_docs/integration/rspec.md +449 -0
  269. data/tech_docs/modes/overview.md +350 -0
  270. data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
  271. data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
  272. data/tech_docs/quick_start.md +256 -0
  273. data/tech_docs/templates/README.md +805 -0
  274. data/tech_docs/tutorials/advanced.md +371 -0
  275. data/tech_docs/tutorials/getting_started.md +189 -0
  276. data/tech_docs/tutorials/intermediate.md +231 -0
  277. data/tech_docs/tutorials/template_gallery.md +569 -0
  278. data/tech_example/01_basic_usage.rb +238 -0
  279. data/tech_example/02_types_demo.rb +336 -0
  280. data/tech_example/04_cli_usage.md +429 -0
  281. data/tech_example/05_database_seeding.rb +283 -0
  282. data/tech_example/07_rspec_integration.rb +359 -0
  283. data/tech_example/10_custom_types.rb +387 -0
  284. data/tech_example/12_behaviors.rb +372 -0
  285. data/tech_example/13_twitter_dm_example.rb +120 -0
  286. data/tech_example/14_exact_json_structure.rb +253 -0
  287. data/tech_example/16_lsp_server.rb +56 -0
  288. data/tech_example/17_property_testing.rb +92 -0
  289. data/tech_example/18_enhanced_repl.rb +120 -0
  290. data/tech_example/19_security_fuzzing.rb +145 -0
  291. data/tech_example/NEW_FEATURES.md +169 -0
  292. data/tech_example/README.md +118 -0
  293. data/tech_example/schemas/api_response.dsl +123 -0
  294. data/tech_example/schemas/ecommerce.dsl +121 -0
  295. data/tech_example/schemas/twitter_dm.dsl +109 -0
  296. data/tech_example/schemas/user.dsl +36 -0
  297. data/vscode-extension/README.md +246 -0
  298. data/vscode-extension/language-configuration.json +31 -0
  299. data/vscode-extension/package.json +55 -0
  300. data/vscode-extension/snippets/fakedatadsl.json +198 -0
  301. data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
  302. metadata +478 -0
@@ -0,0 +1,689 @@
1
+ # frozen_string_literal: true
2
+
3
+ # =============================================================================
4
+ # Synthra Language Server Protocol (LSP) Implementation
5
+ # =============================================================================
6
+ #
7
+ # Provides IDE features for Synthra schema files:
8
+ # - Go to Definition for Ref(User.id) and schema references
9
+ # - Real-time error reporting as you type
10
+ # - Auto-completion for registered types, behaviors, and schemas
11
+ # - Hover documentation for types and behaviors
12
+ # - Code actions for common fixes
13
+ #
14
+ # Usage:
15
+ # # Start LSP server
16
+ # synthra lsp
17
+ #
18
+ # # Or use with language server client
19
+ # ruby -Ilib lib/synthra/lsp/server.rb
20
+ #
21
+ # =============================================================================
22
+
23
+ require "json"
24
+ require "uri"
25
+
26
+ module Synthra
27
+ module LSP
28
+ # Language Server Protocol server for Synthra
29
+ #
30
+ # Implements LSP 3.17 specification for Synthra schema files.
31
+ # Provides IDE features like go-to-definition, hover, completion, and diagnostics.
32
+ #
33
+ # @example Start server
34
+ # server = Server.new
35
+ # server.run
36
+ #
37
+ class Server
38
+ # Initialize LSP server
39
+ #
40
+ # @param input [IO] input stream (default: $stdin)
41
+ # @param output [IO] output stream (default: $stdout)
42
+ #
43
+ def initialize(input: $stdin, output: $stdout)
44
+ @input = input
45
+ @output = output
46
+ @registry = Registry.new
47
+ @workspace_roots = []
48
+ @documents = {} # URI => { content: String, version: Integer }
49
+ end
50
+
51
+ # Run the LSP server (main loop)
52
+ #
53
+ # Reads JSON-RPC messages from input and processes them.
54
+ #
55
+ def run
56
+ @running = true
57
+ begin
58
+ while @running
59
+ header = read_header
60
+ next unless header
61
+
62
+ content_length = header["Content-Length"]&.to_i
63
+ next unless content_length&.positive?
64
+
65
+ message = @input.read(content_length)
66
+ next unless message
67
+
68
+ process_message(JSON.parse(message))
69
+ end
70
+ rescue JSON::ParserError => e
71
+ send_error(-32700, "Parse error: #{e.message}")
72
+ rescue StandardError => e
73
+ send_error(-32603, "Internal error: #{e.message}")
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ # Read HTTP-style header from input
80
+ #
81
+ # @return [Hash, nil] header hash or nil if EOF
82
+ #
83
+ def read_header
84
+ header = {}
85
+ loop do
86
+ line = @input.gets
87
+ return nil if line.nil? || line.strip.empty?
88
+
89
+ key, value = line.split(":", 2)
90
+ header[key.strip] = value&.strip
91
+ end
92
+ end
93
+
94
+ # Process a JSON-RPC message
95
+ #
96
+ # @param message [Hash] parsed JSON-RPC message
97
+ #
98
+ def process_message(message)
99
+ id = message["id"]
100
+ method = message["method"]
101
+ params = message["params"] || {}
102
+
103
+ case method
104
+ when "initialize"
105
+ send_response(id, initialize_response(params))
106
+ when "initialized"
107
+ # Client is ready
108
+ when "textDocument/didOpen"
109
+ handle_did_open(params)
110
+ when "textDocument/didChange"
111
+ handle_did_change(params)
112
+ when "textDocument/didClose"
113
+ handle_did_close(params)
114
+ when "textDocument/definition"
115
+ send_response(id, handle_definition(params))
116
+ when "textDocument/hover"
117
+ send_response(id, handle_hover(params))
118
+ when "textDocument/completion"
119
+ send_response(id, handle_completion(params))
120
+ when "textDocument/diagnostic"
121
+ send_response(id, handle_diagnostic(params))
122
+ when "shutdown"
123
+ @running = false
124
+ send_response(id, nil)
125
+ when "exit"
126
+ @running = false
127
+ else
128
+ send_error(-32601, "Method not found: #{method}", id: id)
129
+ end
130
+ end
131
+
132
+ # Handle textDocument/didOpen notification
133
+ #
134
+ # @param params [Hash] notification parameters
135
+ #
136
+ def handle_did_open(params)
137
+ uri = params.dig("textDocument", "uri")
138
+ text = params.dig("textDocument", "text")
139
+ return unless uri && text
140
+
141
+ @documents[uri] = { content: text, version: 0 }
142
+ validate_document(uri)
143
+ end
144
+
145
+ # Handle textDocument/didChange notification
146
+ #
147
+ # @param params [Hash] notification parameters
148
+ #
149
+ def handle_did_change(params)
150
+ uri = params.dig("textDocument", "uri")
151
+ changes = params.dig("contentChanges") || []
152
+ version = params.dig("textDocument", "version")
153
+
154
+ return unless uri && @documents[uri]
155
+
156
+ # Apply changes (simplified - assumes full document replacement)
157
+ if changes.any?
158
+ @documents[uri][:content] = changes.last["text"]
159
+ @documents[uri][:version] = version if version
160
+ validate_document(uri)
161
+ end
162
+ end
163
+
164
+ # Handle textDocument/didClose notification
165
+ #
166
+ # @param params [Hash] notification parameters
167
+ #
168
+ def handle_did_close(params)
169
+ uri = params.dig("textDocument", "uri")
170
+ @documents.delete(uri) if uri
171
+ end
172
+
173
+ # Handle textDocument/definition request
174
+ #
175
+ # Returns location of definition for Ref(User.id) or schema references.
176
+ #
177
+ # @param params [Hash] request parameters
178
+ # @return [Hash, nil] location or nil if not found
179
+ #
180
+ def handle_definition(params)
181
+ uri = params.dig("textDocument", "uri")
182
+ position = params["position"]
183
+ return nil unless uri && position
184
+
185
+ doc = @documents[uri]
186
+ return nil unless doc
187
+
188
+ line = doc[:content].lines[position["line"]]
189
+ return nil unless line
190
+
191
+ # Find schema reference or Ref() call at cursor position
192
+ col = position["character"]
193
+
194
+ # Check for Ref(Schema.field) pattern
195
+ if line[0..col] =~ /Ref\(([A-Z]\w+)\.(\w+)\)/
196
+ schema_name = $1
197
+ field_name = $2
198
+
199
+ # Find schema definition in current or other files
200
+ schema_uri, schema_pos = find_schema_definition(schema_name, uri)
201
+ return nil unless schema_uri
202
+
203
+ # Find field definition within schema
204
+ field_pos = find_field_definition(schema_uri, schema_name, field_name)
205
+ return field_pos if field_pos
206
+
207
+ # Return schema definition if field not found
208
+ {
209
+ uri: schema_uri,
210
+ range: {
211
+ start: { line: schema_pos, character: 0 },
212
+ end: { line: schema_pos, character: schema_name.length }
213
+ }
214
+ }
215
+ # Check for schema type reference (e.g., "address: Address")
216
+ elsif line[0..col] =~ /:\s*([A-Z]\w+)(\?)?\s*$/
217
+ schema_name = $1
218
+ schema_uri, schema_pos = find_schema_definition(schema_name, uri)
219
+ return nil unless schema_uri
220
+
221
+ {
222
+ uri: schema_uri,
223
+ range: {
224
+ start: { line: schema_pos, character: 0 },
225
+ end: { line: schema_pos, character: schema_name.length }
226
+ }
227
+ }
228
+ else
229
+ nil
230
+ end
231
+ end
232
+
233
+ # Handle textDocument/hover request
234
+ #
235
+ # Returns documentation for types, behaviors, or schemas at cursor.
236
+ #
237
+ # @param params [Hash] request parameters
238
+ # @return [Hash, nil] hover information or nil
239
+ #
240
+ def handle_hover(params)
241
+ uri = params.dig("textDocument", "uri")
242
+ position = params["position"]
243
+ return nil unless uri && position
244
+
245
+ doc = @documents[uri]
246
+ return nil unless doc
247
+
248
+ line = doc[:content].lines[position["line"]]
249
+ return nil unless line
250
+
251
+ col = position["character"]
252
+ word = extract_word_at_position(line, col)
253
+
254
+ # Check for behavior annotation
255
+ if word.start_with?("@")
256
+ behavior_name = word[1..]
257
+ return hover_behavior(behavior_name)
258
+ end
259
+
260
+ # Check for type name
261
+ if Types::Registry.registered?(word)
262
+ return hover_type(word)
263
+ end
264
+
265
+ # Check for schema name
266
+ if word =~ /^[A-Z]/
267
+ schema_uri, _ = find_schema_definition(word, uri)
268
+ return hover_schema(word) if schema_uri
269
+ end
270
+
271
+ nil
272
+ end
273
+
274
+ # Handle textDocument/completion request
275
+ #
276
+ # Returns completion suggestions for types, behaviors, and schemas.
277
+ #
278
+ # @param params [Hash] request parameters
279
+ # @return [Hash] completion list
280
+ #
281
+ def handle_completion(params)
282
+ uri = params.dig("textDocument", "uri")
283
+ position = params["position"]
284
+ return { items: [] } unless uri && position
285
+
286
+ doc = @documents[uri]
287
+ return { items: [] } unless doc
288
+
289
+ line = doc[:content].lines[position["line"]]
290
+ return { items: [] } unless line
291
+
292
+ col = position["character"]
293
+ prefix = line[0..col]
294
+
295
+ items = []
296
+
297
+ # Behavior completions (@latency, @failure, etc.)
298
+ if prefix =~ /@(\w*)$/
299
+ prefix_text = $1
300
+ Behaviors::Registry.names.each do |name|
301
+ next unless name.to_s.start_with?(prefix_text)
302
+
303
+ items << {
304
+ label: "@#{name}",
305
+ kind: 14, # Keyword
306
+ documentation: behavior_documentation(name),
307
+ insertText: "@#{name} "
308
+ }
309
+ end
310
+ end
311
+
312
+ # Type completions
313
+ if prefix =~ /:\s*(\w*)$/
314
+ prefix_text = $1
315
+ Types::Registry.names.each do |name|
316
+ next unless name.start_with?(prefix_text)
317
+
318
+ items << {
319
+ label: name,
320
+ kind: 6, # Class
321
+ documentation: type_documentation(name),
322
+ insertText: name
323
+ }
324
+ end
325
+ end
326
+
327
+ # Schema completions (for Ref() or type references)
328
+ if prefix =~ /(Ref\(|:\s*)([A-Z]\w*)$/
329
+ prefix_text = $2
330
+ find_all_schemas(uri).each do |schema_name|
331
+ next unless schema_name.start_with?(prefix_text)
332
+
333
+ items << {
334
+ label: schema_name,
335
+ kind: 5, # Class
336
+ documentation: "Schema: #{schema_name}",
337
+ insertText: schema_name
338
+ }
339
+ end
340
+ end
341
+
342
+ { items: items }
343
+ end
344
+
345
+ # Handle textDocument/diagnostic request
346
+ #
347
+ # Returns validation errors and warnings for the document.
348
+ #
349
+ # @param params [Hash] request parameters
350
+ # @return [Hash] diagnostic list
351
+ #
352
+ def handle_diagnostic(params)
353
+ uri = params.dig("textDocument", "uri")
354
+ return { items: [] } unless uri
355
+
356
+ doc = @documents[uri]
357
+ return { items: [] } unless doc
358
+
359
+ diagnostics = validate_document(uri)
360
+ { items: diagnostics }
361
+ end
362
+
363
+ # Validate a document and return diagnostics
364
+ #
365
+ # @param uri [String] document URI
366
+ # @return [Array<Hash>] diagnostic items
367
+ #
368
+ def validate_document(uri)
369
+ doc = @documents[uri]
370
+ return [] unless doc
371
+
372
+ diagnostics = []
373
+
374
+ begin
375
+ # Parse the document
376
+ parser = Parser::Parser.new
377
+ ast_nodes = parser.parse(doc[:content])
378
+
379
+ # Load into registry for validation
380
+ temp_registry = Registry.new
381
+ ast_nodes.each do |node|
382
+ schema = node.to_schema
383
+ temp_registry.instance_variable_get(:@schemas)[schema.name] = schema
384
+ end
385
+
386
+ # Validate paths
387
+ validator = Validator::PathValidator.new(temp_registry)
388
+ errors = validator.validate
389
+
390
+ errors.each do |error|
391
+ diagnostics << {
392
+ range: {
393
+ start: { line: (error.line || 0) - 1, character: (error.column || 0) - 1 },
394
+ end: { line: (error.line || 0) - 1, character: (error.column || 0) }
395
+ },
396
+ severity: 1, # Error
397
+ message: error.message,
398
+ source: "Synthra"
399
+ }
400
+ end
401
+ rescue Errors::ParseError => e
402
+ diagnostics << {
403
+ range: {
404
+ start: { line: (e.line || 1) - 1, character: (e.column || 0) - 1 },
405
+ end: { line: (e.line || 1) - 1, character: (e.column || 0) }
406
+ },
407
+ severity: 1, # Error
408
+ message: e.message,
409
+ source: "Synthra"
410
+ }
411
+ rescue StandardError => e
412
+ diagnostics << {
413
+ range: {
414
+ start: { line: 0, character: 0 },
415
+ end: { line: 0, character: 0 }
416
+ },
417
+ severity: 1, # Error
418
+ message: "Parse error: #{e.message}",
419
+ source: "Synthra"
420
+ }
421
+ end
422
+
423
+ # Send diagnostics notification
424
+ send_notification("textDocument/publishDiagnostics", {
425
+ uri: uri,
426
+ diagnostics: diagnostics
427
+ })
428
+
429
+ diagnostics
430
+ end
431
+
432
+ # Find schema definition in workspace
433
+ #
434
+ # @param schema_name [String] schema name to find
435
+ # @param current_uri [String] current document URI
436
+ # @return [Array<String, Integer>, nil] [uri, line] or nil if not found
437
+ #
438
+ def find_schema_definition(schema_name, current_uri)
439
+ # Check current document first
440
+ doc = @documents[current_uri]
441
+ if doc
442
+ doc[:content].lines.each_with_index do |line, idx|
443
+ if line =~ /^#{Regexp.escape(schema_name)}:\s*$/
444
+ return [current_uri, idx]
445
+ end
446
+ end
447
+ end
448
+
449
+ # Check other documents in workspace
450
+ @documents.each do |uri, doc_data|
451
+ next if uri == current_uri
452
+
453
+ doc_data[:content].lines.each_with_index do |line, idx|
454
+ if line =~ /^#{Regexp.escape(schema_name)}:\s*$/
455
+ return [uri, idx]
456
+ end
457
+ end
458
+ end
459
+
460
+ nil
461
+ end
462
+
463
+ # Find field definition within a schema
464
+ #
465
+ # @param schema_uri [String] document URI containing schema
466
+ # @param schema_name [String] schema name
467
+ # @param field_name [String] field name to find
468
+ # @return [Hash, nil] location or nil if not found
469
+ #
470
+ def find_field_definition(schema_uri, schema_name, field_name)
471
+ doc = @documents[schema_uri]
472
+ return nil unless doc
473
+
474
+ in_schema = false
475
+ doc[:content].lines.each_with_index do |line, idx|
476
+ if line =~ /^#{Regexp.escape(schema_name)}:\s*$/
477
+ in_schema = true
478
+ next
479
+ end
480
+
481
+ break if in_schema && line !~ /^\s/ # End of schema
482
+
483
+ if in_schema && line =~ /^\s+#{Regexp.escape(field_name)}(\??):/
484
+ return {
485
+ uri: schema_uri,
486
+ range: {
487
+ start: { line: idx, character: line.index(field_name) },
488
+ end: { line: idx, character: line.index(field_name) + field_name.length }
489
+ }
490
+ }
491
+ end
492
+ end
493
+
494
+ nil
495
+ end
496
+
497
+ # Find all schema names in workspace
498
+ #
499
+ # @param current_uri [String] current document URI
500
+ # @return [Array<String>] schema names
501
+ #
502
+ def find_all_schemas(current_uri)
503
+ schemas = []
504
+
505
+ @documents.each do |uri, doc_data|
506
+ doc_data[:content].lines.each do |line|
507
+ if line =~ /^([A-Z]\w+):\s*$/
508
+ schemas << $1 unless schemas.include?($1)
509
+ end
510
+ end
511
+ end
512
+
513
+ schemas
514
+ end
515
+
516
+ # Extract word at cursor position
517
+ #
518
+ # @param line [String] line content
519
+ # @param col [Integer] column position
520
+ # @return [String] word at position
521
+ #
522
+ def extract_word_at_position(line, col)
523
+ # Find word boundaries
524
+ start = col
525
+ start -= 1 while start > 0 && line[start - 1] =~ /[\w@]/
526
+ finish = col
527
+ finish += 1 while finish < line.length && line[finish] =~ /[\w]/
528
+
529
+ line[start...finish] || ""
530
+ end
531
+
532
+ # Get hover information for a behavior
533
+ #
534
+ # @param name [String] behavior name
535
+ # @return [Hash, nil] hover information
536
+ #
537
+ def hover_behavior(name)
538
+ behavior_class = Behaviors::Registry.lookup(name.to_sym) rescue nil
539
+ return nil unless behavior_class
540
+
541
+ {
542
+ contents: {
543
+ kind: "markdown",
544
+ value: "**@#{name}**\n\nBehavior: #{behavior_class.name}"
545
+ }
546
+ }
547
+ end
548
+
549
+ # Get hover information for a type
550
+ #
551
+ # @param name [String] type name
552
+ # @return [Hash, nil] hover information
553
+ #
554
+ def hover_type(name)
555
+ {
556
+ contents: {
557
+ kind: "markdown",
558
+ value: "**#{name}**\n\nType: #{name}"
559
+ }
560
+ }
561
+ end
562
+
563
+ # Get hover information for a schema
564
+ #
565
+ # @param name [String] schema name
566
+ # @return [Hash, nil] hover information
567
+ #
568
+ def hover_schema(name)
569
+ {
570
+ contents: {
571
+ kind: "markdown",
572
+ value: "**#{name}**\n\nSchema definition"
573
+ }
574
+ }
575
+ end
576
+
577
+ # Get documentation for a behavior
578
+ #
579
+ # @param name [Symbol] behavior name
580
+ # @return [String] documentation string
581
+ #
582
+ def behavior_documentation(name)
583
+ "Behavior: @#{name}"
584
+ end
585
+
586
+ # Get documentation for a type
587
+ #
588
+ # @param name [String] type name
589
+ # @return [String] documentation string
590
+ #
591
+ def type_documentation(name)
592
+ "Type: #{name}"
593
+ end
594
+
595
+ # Initialize response
596
+ #
597
+ # @param params [Hash] initialize parameters
598
+ # @return [Hash] initialize response
599
+ #
600
+ def initialize_response(params)
601
+ @workspace_roots = [params.dig("workspaceFolders", 0, "uri")].compact
602
+
603
+ {
604
+ capabilities: {
605
+ textDocumentSync: {
606
+ openClose: true,
607
+ change: 1, # Full
608
+ willSave: false,
609
+ willSaveWaitUntil: false,
610
+ save: { includeText: false }
611
+ },
612
+ hoverProvider: true,
613
+ definitionProvider: true,
614
+ completionProvider: {
615
+ triggerCharacters: [":", "@", "(", "."]
616
+ },
617
+ diagnosticProvider: {
618
+ interFileDependencies: true,
619
+ workspaceDiagnostics: false
620
+ }
621
+ },
622
+ serverInfo: {
623
+ name: "Synthra LSP",
624
+ version: Synthra::VERSION
625
+ }
626
+ }
627
+ end
628
+
629
+ # Send JSON-RPC response
630
+ #
631
+ # @param id [Integer, String, nil] request ID
632
+ # @param result [Object] response result
633
+ #
634
+ def send_response(id, result)
635
+ message = {
636
+ jsonrpc: "2.0",
637
+ id: id,
638
+ result: result
639
+ }.compact
640
+
641
+ send_message(message)
642
+ end
643
+
644
+ # Send JSON-RPC error
645
+ #
646
+ # @param code [Integer] error code
647
+ # @param message [String] error message
648
+ # @param id [Integer, String, nil] request ID
649
+ #
650
+ def send_error(code, message, id: nil)
651
+ error = {
652
+ jsonrpc: "2.0",
653
+ id: id,
654
+ error: {
655
+ code: code,
656
+ message: message
657
+ }
658
+ }
659
+
660
+ send_message(error)
661
+ end
662
+
663
+ # Send JSON-RPC notification
664
+ #
665
+ # @param method [String] notification method
666
+ # @param params [Hash] notification parameters
667
+ #
668
+ def send_notification(method, params)
669
+ message = {
670
+ jsonrpc: "2.0",
671
+ method: method,
672
+ params: params
673
+ }
674
+
675
+ send_message(message)
676
+ end
677
+
678
+ # Send JSON-RPC message
679
+ #
680
+ # @param message [Hash] message to send
681
+ #
682
+ def send_message(message)
683
+ json = JSON.generate(message)
684
+ @output.write("Content-Length: #{json.bytesize}\r\n\r\n#{json}")
685
+ @output.flush
686
+ end
687
+ end
688
+ end
689
+ end