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.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +40 -0
- data/.yardopts +18 -0
- data/ACCESS_DOCS.md +116 -0
- data/ADVANCED_FEATURES_SUMMARY.md +245 -0
- data/CHANGELOG.md +498 -0
- data/CODE_OF_CONDUCT.md +122 -0
- data/CONTRIBUTING.md +307 -0
- data/DOCUMENTATION.md +204 -0
- data/DOCUMENTATION_GUIDE.md +194 -0
- data/LICENSE +22 -0
- data/README.md +1458 -0
- data/Rakefile +128 -0
- data/benchmark/README.md +591 -0
- data/benchmark/batch_generation.rb +63 -0
- data/benchmark/engine_comparison.rb +295 -0
- data/benchmark/single_record.rb +108 -0
- data/benchmark/streaming.rb +74 -0
- data/docs/ACTIVERECORD_INFERENCE.md +524 -0
- data/docs/API_SERVER.md +278 -0
- data/docs/CONFIG_FILE.md +315 -0
- data/docs/DATA_CONTRACTS.md +312 -0
- data/docs/ENHANCED_REPL.md +304 -0
- data/docs/FACTORY_BOT.md +271 -0
- data/docs/GITHUB_ACTION.md +420 -0
- data/docs/GRAPHQL_EXPORT.md +303 -0
- data/docs/GRAPHQL_FEDERATION.md +651 -0
- data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
- data/docs/LSP.md +189 -0
- data/docs/MIGRATION_GENERATOR.md +547 -0
- data/docs/MOCK_SERVER.md +280 -0
- data/docs/NATIVE_ENGINE.md +298 -0
- data/docs/OPENAPI_EXPORT.md +252 -0
- data/docs/PERFORMANCE_MODE.md +228 -0
- data/docs/PERSONAS.md +547 -0
- data/docs/PROPERTY_TESTING.md +284 -0
- data/docs/PROTOBUF_EXPORT.md +276 -0
- data/docs/QUALITY_METRICS.md +555 -0
- data/docs/QUICK_REFERENCE.md +88 -0
- data/docs/RAILS_ENGINE.md +404 -0
- data/docs/RAILS_INTEGRATION.md +454 -0
- data/docs/README.md +175 -0
- data/docs/REPL_QUICK_REFERENCE.md +149 -0
- data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
- data/docs/SCENARIOS.md +576 -0
- data/docs/SECURITY_TESTING.md +318 -0
- data/docs/SNAPSHOT_TESTING.md +308 -0
- data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
- data/docs/TERRAFORM_EXPORT.md +447 -0
- data/docs/TIME_TRAVEL.md +507 -0
- data/docs/USAGE_GUIDE.md +980 -0
- data/docs/WEBHOOK_SIMULATOR.md +579 -0
- data/docs/advanced_features.md +183 -0
- data/docs/api_reference.md +153 -0
- data/docs/behaviors.md +138 -0
- data/docs/best_practices.md +191 -0
- data/docs/dsl_reference.md +172 -0
- data/docs/examples.md +162 -0
- data/docs/generation_modes.md +125 -0
- data/docs/getting_started.md +92 -0
- data/docs/index.html +177 -0
- data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
- data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
- data/docs/troubleshooting.md +215 -0
- data/docs/type_reference.md +224 -0
- data/examples/hostile_mode_demo.rb +64 -0
- data/exe/synthra +6 -0
- data/ext/synthra_native/Cargo.lock +972 -0
- data/ext/synthra_native/Cargo.toml +46 -0
- data/ext/synthra_native/extconf.rb +41 -0
- data/ext/synthra_native/src/generator.rs +397 -0
- data/ext/synthra_native/src/lib.rs +229 -0
- data/ext/synthra_native/src/types.rs +1485 -0
- data/lib/generators/synthra/install_generator.rb +86 -0
- data/lib/generators/synthra/templates/api_response.dsl +15 -0
- data/lib/generators/synthra/templates/fake_data.rake +116 -0
- data/lib/generators/synthra/templates/synthra.yml +31 -0
- data/lib/generators/synthra/templates/synthra_support.rb +19 -0
- data/lib/generators/synthra/templates/user.dsl +11 -0
- data/lib/synthra/activerecord_inference.rb +394 -0
- data/lib/synthra/api.rb +381 -0
- data/lib/synthra/api_server.rb +718 -0
- data/lib/synthra/behaviors/applicator.rb +165 -0
- data/lib/synthra/behaviors/base.rb +155 -0
- data/lib/synthra/behaviors/close_connection.rb +60 -0
- data/lib/synthra/behaviors/deprecated.rb +100 -0
- data/lib/synthra/behaviors/failure.rb +69 -0
- data/lib/synthra/behaviors/latency.rb +103 -0
- data/lib/synthra/behaviors/partial_data.rb +83 -0
- data/lib/synthra/behaviors/randomize_order.rb +74 -0
- data/lib/synthra/behaviors/registry.rb +238 -0
- data/lib/synthra/behaviors/simulate_error.rb +79 -0
- data/lib/synthra/cli/commands/base.rb +105 -0
- data/lib/synthra/cli/commands/diff.rb +227 -0
- data/lib/synthra/cli/commands/docs.rb +67 -0
- data/lib/synthra/cli/commands/export.rb +150 -0
- data/lib/synthra/cli/commands/generate.rb +72 -0
- data/lib/synthra/cli/commands/import.rb +80 -0
- data/lib/synthra/cli/commands/lint.rb +53 -0
- data/lib/synthra/cli/commands/live.rb +57 -0
- data/lib/synthra/cli/commands/seed.rb +99 -0
- data/lib/synthra/cli/commands/validate.rb +28 -0
- data/lib/synthra/cli.rb +2471 -0
- data/lib/synthra/config_file.rb +129 -0
- data/lib/synthra/configuration.rb +281 -0
- data/lib/synthra/contracts_registry.rb +408 -0
- data/lib/synthra/database_seeder.rb +268 -0
- data/lib/synthra/deterministic_ids.rb +218 -0
- data/lib/synthra/documentation_generator.rb +414 -0
- data/lib/synthra/engine.rb +251 -0
- data/lib/synthra/errors.rb +1169 -0
- data/lib/synthra/export/base.rb +85 -0
- data/lib/synthra/export/csv.rb +101 -0
- data/lib/synthra/export/graphql.rb +266 -0
- data/lib/synthra/export/graphql_federation.rb +377 -0
- data/lib/synthra/export/graphviz.rb +258 -0
- data/lib/synthra/export/javascript.rb +327 -0
- data/lib/synthra/export/json_data.rb +61 -0
- data/lib/synthra/export/json_schema.rb +290 -0
- data/lib/synthra/export/openapi.rb +514 -0
- data/lib/synthra/export/protobuf.rb +483 -0
- data/lib/synthra/export/python.rb +560 -0
- data/lib/synthra/export/sql.rb +381 -0
- data/lib/synthra/export/sql_insert.rb +152 -0
- data/lib/synthra/export/terraform.rb +501 -0
- data/lib/synthra/export/type_mapping.rb +389 -0
- data/lib/synthra/export/typescript.rb +300 -0
- data/lib/synthra/export/xml_data.rb +104 -0
- data/lib/synthra/export/yaml_data.rb +56 -0
- data/lib/synthra/export.rb +404 -0
- data/lib/synthra/factory_bot_integration.rb +157 -0
- data/lib/synthra/field.rb +440 -0
- data/lib/synthra/functions/registry.rb +104 -0
- data/lib/synthra/generator/context.rb +372 -0
- data/lib/synthra/generator/engine.rb +336 -0
- data/lib/synthra/generator/faker_adapter.rb +425 -0
- data/lib/synthra/generator/modes.rb +444 -0
- data/lib/synthra/generator/resolver.rb +256 -0
- data/lib/synthra/generator/rng.rb +273 -0
- data/lib/synthra/generator/streamer.rb +63 -0
- data/lib/synthra/generator/uniqueness.rb +118 -0
- data/lib/synthra/initializer.rb +97 -0
- data/lib/synthra/limits.rb +279 -0
- data/lib/synthra/live_preview.rb +518 -0
- data/lib/synthra/loader_config.rb +78 -0
- data/lib/synthra/lsp/server.rb +689 -0
- data/lib/synthra/migration_generator.rb +301 -0
- data/lib/synthra/mixin.rb +174 -0
- data/lib/synthra/mock_server.rb +458 -0
- data/lib/synthra/native_engine.rb +304 -0
- data/lib/synthra/openapi_importer.rb +228 -0
- data/lib/synthra/output/json_formatter.rb +40 -0
- data/lib/synthra/output/ndjson_formatter.rb +55 -0
- data/lib/synthra/parser/ast.rb +1287 -0
- data/lib/synthra/parser/lexer.rb +1152 -0
- data/lib/synthra/parser/parser.rb +1664 -0
- data/lib/synthra/parser/tokens.rb +461 -0
- data/lib/synthra/performance_mode.rb +364 -0
- data/lib/synthra/personas.rb +397 -0
- data/lib/synthra/property_testing.rb +172 -0
- data/lib/synthra/quality_metrics.rb +405 -0
- data/lib/synthra/rails_test_helper.rb +248 -0
- data/lib/synthra/registry.rb +533 -0
- data/lib/synthra/relationships.rb +193 -0
- data/lib/synthra/repl/enhanced_repl.rb +607 -0
- data/lib/synthra/repl/formatter.rb +191 -0
- data/lib/synthra/scenarios.rb +423 -0
- data/lib/synthra/schema.rb +605 -0
- data/lib/synthra/schema_inheritance.rb +104 -0
- data/lib/synthra/schema_versioning.rb +212 -0
- data/lib/synthra/snapshot_testing.rb +199 -0
- data/lib/synthra/time_travel.rb +338 -0
- data/lib/synthra/type_definitions.rb +274 -0
- data/lib/synthra/types/address_location/addresses.rb +125 -0
- data/lib/synthra/types/address_location/airports.rb +200 -0
- data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
- data/lib/synthra/types/address_location/locations.rb +406 -0
- data/lib/synthra/types/base.rb +48 -0
- data/lib/synthra/types/commerce_products/commerce.rb +119 -0
- data/lib/synthra/types/commerce_products/companies.rb +71 -0
- data/lib/synthra/types/commerce_products/construction.rb +126 -0
- data/lib/synthra/types/commerce_products/products.rb +205 -0
- data/lib/synthra/types/core/collections.rb +513 -0
- data/lib/synthra/types/core/defaults.rb +76 -0
- data/lib/synthra/types/core/enums.rb +102 -0
- data/lib/synthra/types/core/identifiers.rb +445 -0
- data/lib/synthra/types/core/primitives.rb +586 -0
- data/lib/synthra/types/core/references.rb +466 -0
- data/lib/synthra/types/core/sequences.rb +304 -0
- data/lib/synthra/types/crypto/crypto.rb +162 -0
- data/lib/synthra/types/date_time/dates.rb +387 -0
- data/lib/synthra/types/finance_banking/banking.rb +424 -0
- data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
- data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
- data/lib/synthra/types/formula.rb +184 -0
- data/lib/synthra/types/health_medical/medical.rb +150 -0
- data/lib/synthra/types/hostile_payloads.rb +122 -0
- data/lib/synthra/types/json_array.rb +51 -0
- data/lib/synthra/types/media_entertainment/media.rb +62 -0
- data/lib/synthra/types/naughty_string.rb +30 -0
- data/lib/synthra/types/personal_names/chinese.rb +41 -0
- data/lib/synthra/types/personal_names/identifiers.rb +187 -0
- data/lib/synthra/types/personal_names/names.rb +246 -0
- data/lib/synthra/types/personal_names/national_id.rb +89 -0
- data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
- data/lib/synthra/types/regex.rb +248 -0
- data/lib/synthra/types/registry.rb +182 -0
- data/lib/synthra/types/repeating_element.rb +50 -0
- data/lib/synthra/types/scenario.rb +29 -0
- data/lib/synthra/types/technology_internet/apps.rb +67 -0
- data/lib/synthra/types/technology_internet/communication.rb +162 -0
- data/lib/synthra/types/technology_internet/devices.rb +80 -0
- data/lib/synthra/types/technology_internet/formats.rb +139 -0
- data/lib/synthra/types/technology_internet/networking.rb +143 -0
- data/lib/synthra/types/template.rb +92 -0
- data/lib/synthra/types/text_content/business.rb +128 -0
- data/lib/synthra/types/text_content/colors.rb +70 -0
- data/lib/synthra/types/text_content/misc.rb +237 -0
- data/lib/synthra/types/text_content/security.rb +130 -0
- data/lib/synthra/types/text_content/text_generation.rb +532 -0
- data/lib/synthra/types/travel/travel.rb +150 -0
- data/lib/synthra/utils/string_distance.rb +133 -0
- data/lib/synthra/validator/dsl_validator.rb +339 -0
- data/lib/synthra/validator/path_validator.rb +615 -0
- data/lib/synthra/version.rb +35 -0
- data/lib/synthra/webhook_simulator.rb +341 -0
- data/lib/synthra.rb +259 -0
- data/schemas/address.dsl +16 -0
- data/schemas/api_response.dsl +8 -0
- data/schemas/error_payload.dsl +9 -0
- data/schemas/order.dsl +10 -0
- data/schemas/order_item.dsl +8 -0
- data/schemas/payment.dsl +11 -0
- data/schemas/social_post.dsl +13 -0
- data/schemas/user.dsl +10 -0
- data/scripts/batch_fix_all.rb +96 -0
- data/scripts/delete_old_files.rb +32 -0
- data/scripts/fix_all_grouped_files.rb +137 -0
- data/scripts/fix_all_indentation.rb +48 -0
- data/scripts/fix_all_syntax.rb +124 -0
- data/scripts/fix_grouped_files.rb +184 -0
- data/scripts/fix_syntax_errors.rb +108 -0
- data/scripts/group_domain_types.rb +112 -0
- data/scripts/group_domain_types_fixed.rb +150 -0
- data/scripts/merge_domains_to_one_file.rb +68 -0
- data/scripts/move_existing_types.rb +130 -0
- data/scripts/split_grouped_types.rb +142 -0
- data/tech_docs/README.md +134 -0
- data/tech_docs/advanced/streaming.md +405 -0
- data/tech_docs/advanced/thread_safety.md +253 -0
- data/tech_docs/api/overview.md +485 -0
- data/tech_docs/appendices/type_chart.md +193 -0
- data/tech_docs/basic_concepts.md +383 -0
- data/tech_docs/behaviors/overview.md +346 -0
- data/tech_docs/dsl/complex_types.md +790 -0
- data/tech_docs/dsl/core_types.md +464 -0
- data/tech_docs/dsl/datetime_types.md +325 -0
- data/tech_docs/dsl/field_modifiers.md +414 -0
- data/tech_docs/dsl/grammar.md +431 -0
- data/tech_docs/dsl/schema_definition.md +399 -0
- data/tech_docs/export/README.md +276 -0
- data/tech_docs/installation.md +273 -0
- data/tech_docs/integration/ci_cd.md +707 -0
- data/tech_docs/integration/ci_cd_guide.md +579 -0
- data/tech_docs/integration/factory_bot.md +485 -0
- data/tech_docs/integration/rails.md +630 -0
- data/tech_docs/integration/rspec.md +449 -0
- data/tech_docs/modes/overview.md +350 -0
- data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
- data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
- data/tech_docs/quick_start.md +256 -0
- data/tech_docs/templates/README.md +805 -0
- data/tech_docs/tutorials/advanced.md +371 -0
- data/tech_docs/tutorials/getting_started.md +189 -0
- data/tech_docs/tutorials/intermediate.md +231 -0
- data/tech_docs/tutorials/template_gallery.md +569 -0
- data/tech_example/01_basic_usage.rb +238 -0
- data/tech_example/02_types_demo.rb +336 -0
- data/tech_example/04_cli_usage.md +429 -0
- data/tech_example/05_database_seeding.rb +283 -0
- data/tech_example/07_rspec_integration.rb +359 -0
- data/tech_example/10_custom_types.rb +387 -0
- data/tech_example/12_behaviors.rb +372 -0
- data/tech_example/13_twitter_dm_example.rb +120 -0
- data/tech_example/14_exact_json_structure.rb +253 -0
- data/tech_example/16_lsp_server.rb +56 -0
- data/tech_example/17_property_testing.rb +92 -0
- data/tech_example/18_enhanced_repl.rb +120 -0
- data/tech_example/19_security_fuzzing.rb +145 -0
- data/tech_example/NEW_FEATURES.md +169 -0
- data/tech_example/README.md +118 -0
- data/tech_example/schemas/api_response.dsl +123 -0
- data/tech_example/schemas/ecommerce.dsl +121 -0
- data/tech_example/schemas/twitter_dm.dsl +109 -0
- data/tech_example/schemas/user.dsl +36 -0
- data/vscode-extension/README.md +246 -0
- data/vscode-extension/language-configuration.json +31 -0
- data/vscode-extension/package.json +55 -0
- data/vscode-extension/snippets/fakedatadsl.json +198 -0
- data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
- 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
|