method-ray 0.1.1

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +21 -0
  4. data/README.md +39 -0
  5. data/exe/methodray +7 -0
  6. data/ext/Cargo.toml +24 -0
  7. data/ext/extconf.rb +40 -0
  8. data/ext/src/cli.rs +33 -0
  9. data/ext/src/lib.rs +79 -0
  10. data/lib/methodray/cli.rb +28 -0
  11. data/lib/methodray/commands.rb +78 -0
  12. data/lib/methodray/version.rb +5 -0
  13. data/lib/methodray.rb +9 -0
  14. data/rust/Cargo.toml +39 -0
  15. data/rust/src/analyzer/calls.rs +56 -0
  16. data/rust/src/analyzer/definitions.rs +70 -0
  17. data/rust/src/analyzer/dispatch.rs +134 -0
  18. data/rust/src/analyzer/install.rs +226 -0
  19. data/rust/src/analyzer/literals.rs +85 -0
  20. data/rust/src/analyzer/mod.rs +11 -0
  21. data/rust/src/analyzer/tests/integration_test.rs +136 -0
  22. data/rust/src/analyzer/tests/mod.rs +1 -0
  23. data/rust/src/analyzer/variables.rs +76 -0
  24. data/rust/src/cache/mod.rs +3 -0
  25. data/rust/src/cache/rbs_cache.rs +158 -0
  26. data/rust/src/checker.rs +139 -0
  27. data/rust/src/cli/args.rs +40 -0
  28. data/rust/src/cli/commands.rs +139 -0
  29. data/rust/src/cli/mod.rs +6 -0
  30. data/rust/src/diagnostics/diagnostic.rs +125 -0
  31. data/rust/src/diagnostics/formatter.rs +119 -0
  32. data/rust/src/diagnostics/mod.rs +5 -0
  33. data/rust/src/env/box_manager.rs +121 -0
  34. data/rust/src/env/global_env.rs +279 -0
  35. data/rust/src/env/local_env.rs +58 -0
  36. data/rust/src/env/method_registry.rs +63 -0
  37. data/rust/src/env/mod.rs +15 -0
  38. data/rust/src/env/scope.rs +330 -0
  39. data/rust/src/env/type_error.rs +23 -0
  40. data/rust/src/env/vertex_manager.rs +195 -0
  41. data/rust/src/graph/box.rs +157 -0
  42. data/rust/src/graph/change_set.rs +115 -0
  43. data/rust/src/graph/mod.rs +7 -0
  44. data/rust/src/graph/vertex.rs +167 -0
  45. data/rust/src/lib.rs +24 -0
  46. data/rust/src/lsp/diagnostics.rs +133 -0
  47. data/rust/src/lsp/main.rs +8 -0
  48. data/rust/src/lsp/mod.rs +4 -0
  49. data/rust/src/lsp/server.rs +138 -0
  50. data/rust/src/main.rs +46 -0
  51. data/rust/src/parser.rs +96 -0
  52. data/rust/src/rbs/converter.rs +82 -0
  53. data/rust/src/rbs/error.rs +37 -0
  54. data/rust/src/rbs/loader.rs +183 -0
  55. data/rust/src/rbs/mod.rs +15 -0
  56. data/rust/src/source_map.rs +102 -0
  57. data/rust/src/types.rs +75 -0
  58. metadata +119 -0
@@ -0,0 +1,115 @@
1
+ use super::VertexId;
2
+
3
+ /// Manages edge changes for type propagation
4
+ #[derive(Debug, Clone)]
5
+ pub struct ChangeSet {
6
+ new_edges: Vec<(VertexId, VertexId)>,
7
+ edges: Vec<(VertexId, VertexId)>,
8
+ }
9
+
10
+ impl ChangeSet {
11
+ pub fn new() -> Self {
12
+ Self {
13
+ new_edges: Vec::new(),
14
+ edges: Vec::new(),
15
+ }
16
+ }
17
+
18
+ /// Add edge
19
+ pub fn add_edge(&mut self, src: VertexId, dst: VertexId) {
20
+ self.new_edges.push((src, dst));
21
+ }
22
+
23
+ /// Commit changes and return list of added/removed edges
24
+ pub fn reinstall(&mut self) -> Vec<EdgeUpdate> {
25
+ // Remove duplicates
26
+ self.new_edges.sort_by_key(|&(src, dst)| (src.0, dst.0));
27
+ self.new_edges.dedup();
28
+
29
+ let mut updates = Vec::new();
30
+
31
+ // New edges
32
+ for &(src, dst) in &self.new_edges {
33
+ if !self.edges.contains(&(src, dst)) {
34
+ updates.push(EdgeUpdate::Add { src, dst });
35
+ }
36
+ }
37
+
38
+ // Removed edges
39
+ for &(src, dst) in &self.edges {
40
+ if !self.new_edges.contains(&(src, dst)) {
41
+ updates.push(EdgeUpdate::Remove { src, dst });
42
+ }
43
+ }
44
+
45
+ // Commit edges
46
+ std::mem::swap(&mut self.edges, &mut self.new_edges);
47
+ self.new_edges.clear();
48
+
49
+ updates
50
+ }
51
+ }
52
+
53
+ /// Edge update type
54
+ #[derive(Debug, Clone, PartialEq, Eq)]
55
+ pub enum EdgeUpdate {
56
+ Add { src: VertexId, dst: VertexId },
57
+ Remove { src: VertexId, dst: VertexId },
58
+ }
59
+
60
+ #[cfg(test)]
61
+ mod tests {
62
+ use super::*;
63
+
64
+ #[test]
65
+ fn test_change_set_add() {
66
+ let mut cs = ChangeSet::new();
67
+
68
+ cs.add_edge(VertexId(1), VertexId(2));
69
+ cs.add_edge(VertexId(2), VertexId(3));
70
+
71
+ let updates = cs.reinstall();
72
+
73
+ assert_eq!(updates.len(), 2);
74
+ assert!(updates.contains(&EdgeUpdate::Add {
75
+ src: VertexId(1),
76
+ dst: VertexId(2)
77
+ }));
78
+ assert!(updates.contains(&EdgeUpdate::Add {
79
+ src: VertexId(2),
80
+ dst: VertexId(3)
81
+ }));
82
+ }
83
+
84
+ #[test]
85
+ fn test_change_set_dedup() {
86
+ let mut cs = ChangeSet::new();
87
+
88
+ cs.add_edge(VertexId(1), VertexId(2));
89
+ cs.add_edge(VertexId(1), VertexId(2)); // Duplicate
90
+
91
+ let updates = cs.reinstall();
92
+
93
+ assert_eq!(updates.len(), 1); // Duplicates removed
94
+ }
95
+
96
+ #[test]
97
+ fn test_change_set_remove() {
98
+ let mut cs = ChangeSet::new();
99
+
100
+ // First commit
101
+ cs.add_edge(VertexId(1), VertexId(2));
102
+ cs.add_edge(VertexId(2), VertexId(3));
103
+ cs.reinstall();
104
+
105
+ // Second time: keep only (1,2)
106
+ cs.add_edge(VertexId(1), VertexId(2));
107
+ let updates = cs.reinstall();
108
+
109
+ assert_eq!(updates.len(), 1);
110
+ assert!(updates.contains(&EdgeUpdate::Remove {
111
+ src: VertexId(2),
112
+ dst: VertexId(3)
113
+ }));
114
+ }
115
+ }
@@ -0,0 +1,7 @@
1
+ pub mod r#box;
2
+ pub mod change_set;
3
+ pub mod vertex;
4
+
5
+ pub use change_set::{ChangeSet, EdgeUpdate};
6
+ pub use r#box::{BoxId, BoxTrait, MethodCallBox};
7
+ pub use vertex::{Source, Vertex, VertexId};
@@ -0,0 +1,167 @@
1
+ use crate::types::Type;
2
+ use std::collections::{HashMap, HashSet};
3
+
4
+ /// Vertex ID (uniquely identifies a vertex in the graph)
5
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
6
+ pub struct VertexId(pub usize);
7
+
8
+ /// Source: Vertex with fixed type (e.g., literals)
9
+ #[derive(Debug, Clone)]
10
+ pub struct Source {
11
+ pub ty: Type,
12
+ }
13
+
14
+ impl Source {
15
+ pub fn new(ty: Type) -> Self {
16
+ Self { ty }
17
+ }
18
+ }
19
+
20
+ /// Vertex: Vertex that dynamically accumulates types (e.g., variables)
21
+ #[derive(Debug, Clone)]
22
+ pub struct Vertex {
23
+ /// Type -> Sources (set of Source IDs that provided this type)
24
+ pub types: HashMap<Type, HashSet<VertexId>>,
25
+ /// Set of connected Vertex IDs
26
+ pub next_vtxs: HashSet<VertexId>,
27
+ }
28
+
29
+ impl Vertex {
30
+ pub fn new() -> Self {
31
+ Self {
32
+ types: HashMap::new(),
33
+ next_vtxs: HashSet::new(),
34
+ }
35
+ }
36
+
37
+ /// Add connection destination
38
+ pub fn add_next(&mut self, next_id: VertexId) {
39
+ self.next_vtxs.insert(next_id);
40
+ }
41
+
42
+ /// Add type (core of type propagation)
43
+ /// Returns: list of newly added types and destinations to propagate to
44
+ pub fn on_type_added(
45
+ &mut self,
46
+ src_id: VertexId,
47
+ added_types: Vec<Type>,
48
+ ) -> Vec<(VertexId, Vec<Type>)> {
49
+ let mut new_added_types = Vec::new();
50
+
51
+ for ty in added_types {
52
+ if let Some(sources) = self.types.get_mut(&ty) {
53
+ // Type already exists: add Source
54
+ sources.insert(src_id);
55
+ } else {
56
+ // New type: add type and record Source
57
+ let mut sources = HashSet::new();
58
+ sources.insert(src_id);
59
+ self.types.insert(ty.clone(), sources);
60
+ new_added_types.push(ty);
61
+ }
62
+ }
63
+
64
+ // If no new types, don't propagate anything
65
+ if new_added_types.is_empty() {
66
+ return vec![];
67
+ }
68
+
69
+ // Propagate to connections
70
+ self.next_vtxs
71
+ .iter()
72
+ .map(|&next_id| (next_id, new_added_types.clone()))
73
+ .collect()
74
+ }
75
+
76
+ /// Convert type to string representation
77
+ pub fn show(&self) -> String {
78
+ if self.types.is_empty() {
79
+ return "untyped".to_string();
80
+ }
81
+
82
+ let mut type_strs: Vec<_> = self.types.keys().map(|t| t.show()).collect();
83
+ type_strs.sort();
84
+ type_strs.dedup();
85
+
86
+ if type_strs.len() == 1 {
87
+ type_strs[0].clone()
88
+ } else {
89
+ format!("({})", type_strs.join(" | "))
90
+ }
91
+ }
92
+ }
93
+
94
+ impl Default for Vertex {
95
+ fn default() -> Self {
96
+ Self::new()
97
+ }
98
+ }
99
+
100
+ #[cfg(test)]
101
+ mod tests {
102
+ use super::*;
103
+
104
+ #[test]
105
+ fn test_source_type() {
106
+ let src = Source::new(Type::string());
107
+ assert_eq!(src.ty.show(), "String");
108
+ }
109
+
110
+ #[test]
111
+ fn test_vertex_empty() {
112
+ let vtx = Vertex::new();
113
+ assert_eq!(vtx.show(), "untyped");
114
+ }
115
+
116
+ #[test]
117
+ fn test_vertex_single_type() {
118
+ let mut vtx = Vertex::new();
119
+
120
+ // Add String type
121
+ let propagations = vtx.on_type_added(VertexId(1), vec![Type::string()]);
122
+ assert_eq!(vtx.show(), "String");
123
+ assert_eq!(propagations.len(), 0); // No propagation since no connections
124
+ }
125
+
126
+ #[test]
127
+ fn test_vertex_union_type() {
128
+ let mut vtx = Vertex::new();
129
+
130
+ // Add String type
131
+ vtx.on_type_added(VertexId(1), vec![Type::string()]);
132
+ assert_eq!(vtx.show(), "String");
133
+
134
+ // Add Integer type → becomes Union type
135
+ vtx.on_type_added(VertexId(1), vec![Type::integer()]);
136
+ assert_eq!(vtx.show(), "(Integer | String)");
137
+ }
138
+
139
+ #[test]
140
+ fn test_vertex_propagation() {
141
+ let mut vtx = Vertex::new();
142
+
143
+ // Add connections
144
+ vtx.add_next(VertexId(3));
145
+ vtx.add_next(VertexId(4));
146
+
147
+ // Add type → propagated to connections
148
+ let propagations = vtx.on_type_added(VertexId(1), vec![Type::string()]);
149
+
150
+ assert_eq!(propagations.len(), 2);
151
+ assert!(propagations.contains(&(VertexId(3), vec![Type::string()])));
152
+ assert!(propagations.contains(&(VertexId(4), vec![Type::string()])));
153
+ }
154
+
155
+ #[test]
156
+ fn test_vertex_no_duplicate_propagation() {
157
+ let mut vtx = Vertex::new();
158
+ vtx.add_next(VertexId(3));
159
+
160
+ // Add same type twice → only first time propagates
161
+ let prop1 = vtx.on_type_added(VertexId(1), vec![Type::string()]);
162
+ assert_eq!(prop1.len(), 1);
163
+
164
+ let prop2 = vtx.on_type_added(VertexId(1), vec![Type::string()]);
165
+ assert_eq!(prop2.len(), 0); // No propagation since already exists
166
+ }
167
+ }
data/rust/src/lib.rs ADDED
@@ -0,0 +1,24 @@
1
+ //! Method-Ray Core - Static type checker for Ruby
2
+ //!
3
+ //! This crate provides the core type inference engine.
4
+
5
+ pub mod analyzer;
6
+ pub mod cache;
7
+ pub mod diagnostics;
8
+ pub mod env;
9
+ pub mod graph;
10
+ pub mod parser;
11
+ pub mod source_map;
12
+ pub mod types;
13
+
14
+ #[cfg(feature = "ruby-ffi")]
15
+ pub mod rbs;
16
+
17
+ #[cfg(any(feature = "cli", feature = "lsp"))]
18
+ pub mod checker;
19
+
20
+ #[cfg(feature = "cli")]
21
+ pub mod cli;
22
+
23
+ #[cfg(feature = "lsp")]
24
+ pub mod lsp;
@@ -0,0 +1,133 @@
1
+ use crate::diagnostics::{Diagnostic as MethodRayDiagnostic, DiagnosticLevel};
2
+ use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range};
3
+
4
+ /// Extract method name length from error message
5
+ /// Supports messages like:
6
+ /// - "undefined method `downcase` for Integer"
7
+ /// - "method `upcase` is defined for ..."
8
+ fn extract_method_name_length(message: &str) -> Option<u32> {
9
+ // Pattern: `method_name`
10
+ if let Some(start) = message.find('`') {
11
+ if let Some(end) = message[start + 1..].find('`') {
12
+ let method_name = &message[start + 1..start + 1 + end];
13
+ return Some(method_name.len() as u32);
14
+ }
15
+ }
16
+ None
17
+ }
18
+
19
+ /// Convert MethodRay Diagnostic to LSP Diagnostic
20
+ pub fn to_lsp_diagnostic(diag: &MethodRayDiagnostic) -> Diagnostic {
21
+ let severity = match diag.level {
22
+ DiagnosticLevel::Error => DiagnosticSeverity::ERROR,
23
+ DiagnosticLevel::Warning => DiagnosticSeverity::WARNING,
24
+ };
25
+
26
+ let start_line = if diag.location.line > 0 {
27
+ (diag.location.line - 1) as u32
28
+ } else {
29
+ 0
30
+ };
31
+
32
+ let start_char = if diag.location.column > 0 {
33
+ (diag.location.column - 1) as u32
34
+ } else {
35
+ 0
36
+ };
37
+
38
+ // Use actual source length if available, otherwise extract from message
39
+ let highlight_length = diag
40
+ .location
41
+ .length
42
+ .map(|len| len as u32)
43
+ .or_else(|| extract_method_name_length(&diag.message))
44
+ .unwrap_or(5);
45
+ let end_char = start_char + highlight_length;
46
+
47
+ Diagnostic {
48
+ range: Range {
49
+ start: Position {
50
+ line: start_line,
51
+ character: start_char,
52
+ },
53
+ end: Position {
54
+ line: start_line,
55
+ character: end_char,
56
+ },
57
+ },
58
+ severity: Some(severity),
59
+ code: None,
60
+ code_description: None,
61
+ source: Some("methodray".to_string()),
62
+ message: diag.message.clone(),
63
+ related_information: None,
64
+ tags: None,
65
+ data: None,
66
+ }
67
+ }
68
+
69
+ #[cfg(test)]
70
+ mod tests {
71
+ use super::*;
72
+ use crate::diagnostics::Location;
73
+
74
+ #[test]
75
+ fn test_to_lsp_diagnostic() {
76
+ use std::path::PathBuf;
77
+
78
+ let methodray_diag = MethodRayDiagnostic {
79
+ level: DiagnosticLevel::Error,
80
+ location: Location {
81
+ file: PathBuf::from("test.rb"),
82
+ line: 5,
83
+ column: 10,
84
+ length: Some(6), // "upcase".len()
85
+ },
86
+ message: "undefined method `upcase` for Integer".to_string(),
87
+ code: None,
88
+ };
89
+
90
+ let lsp_diag = to_lsp_diagnostic(&methodray_diag);
91
+
92
+ assert_eq!(lsp_diag.range.start.line, 4); // 0-indexed
93
+ assert_eq!(lsp_diag.range.start.character, 9); // 0-indexed
94
+ assert_eq!(lsp_diag.range.end.character, 15); // start(9) + length(6)
95
+ assert_eq!(lsp_diag.severity, Some(DiagnosticSeverity::ERROR));
96
+ assert_eq!(lsp_diag.message, "undefined method `upcase` for Integer");
97
+ }
98
+
99
+ #[test]
100
+ fn test_extract_method_name_length() {
101
+ assert_eq!(
102
+ extract_method_name_length("undefined method `downcase` for Integer"),
103
+ Some(8)
104
+ );
105
+ assert_eq!(
106
+ extract_method_name_length("method `upcase` is defined for String"),
107
+ Some(6)
108
+ );
109
+ assert_eq!(extract_method_name_length("no method name here"), None);
110
+ }
111
+
112
+ #[test]
113
+ fn test_highlight_length_for_downcase() {
114
+ use std::path::PathBuf;
115
+
116
+ let methodray_diag = MethodRayDiagnostic {
117
+ level: DiagnosticLevel::Error,
118
+ location: Location {
119
+ file: PathBuf::from("test.rb"),
120
+ line: 2,
121
+ column: 5,
122
+ length: Some(8), // "downcase".len()
123
+ },
124
+ message: "undefined method `downcase` for Integer".to_string(),
125
+ code: None,
126
+ };
127
+
128
+ let lsp_diag = to_lsp_diagnostic(&methodray_diag);
129
+
130
+ assert_eq!(lsp_diag.range.start.character, 4); // column 5 -> 0-indexed = 4
131
+ assert_eq!(lsp_diag.range.end.character, 12); // start(4) + length(8)
132
+ }
133
+ }
@@ -0,0 +1,8 @@
1
+ //! LSP server binary entry point
2
+
3
+ use methodray_core::lsp;
4
+
5
+ #[tokio::main]
6
+ async fn main() {
7
+ lsp::run_server().await;
8
+ }
@@ -0,0 +1,4 @@
1
+ pub mod diagnostics;
2
+ pub mod server;
3
+
4
+ pub use server::run_server;
@@ -0,0 +1,138 @@
1
+ use anyhow::Context;
2
+ use std::collections::HashMap;
3
+ use tokio::sync::RwLock;
4
+ use tower_lsp::jsonrpc::Result;
5
+ use tower_lsp::lsp_types::*;
6
+ use tower_lsp::{Client, LanguageServer, LspService, Server};
7
+
8
+ use super::diagnostics::to_lsp_diagnostic;
9
+ use crate::checker::FileChecker;
10
+
11
+ pub struct MethodRayServer {
12
+ client: Client,
13
+ documents: RwLock<HashMap<Url, String>>,
14
+ }
15
+
16
+ impl MethodRayServer {
17
+ pub fn new(client: Client) -> Self {
18
+ Self {
19
+ client,
20
+ documents: RwLock::new(HashMap::new()),
21
+ }
22
+ }
23
+
24
+ async fn check_document(&self, uri: Url) {
25
+ let documents = self.documents.read().await;
26
+
27
+ if let Some(source) = documents.get(&uri) {
28
+ match self.run_type_check(&uri, source).await {
29
+ Ok(diagnostics) => {
30
+ self.client
31
+ .publish_diagnostics(uri.clone(), diagnostics, None)
32
+ .await;
33
+ }
34
+ Err(e) => {
35
+ self.client
36
+ .log_message(MessageType::ERROR, format!("Type check failed: {}", e))
37
+ .await;
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ async fn run_type_check(&self, uri: &Url, source: &str) -> anyhow::Result<Vec<Diagnostic>> {
44
+ // Convert URI to file path
45
+ let file_path = uri
46
+ .to_file_path()
47
+ .map_err(|_| anyhow::anyhow!("Invalid file URI: {}", uri))?;
48
+
49
+ // Create a temporary file for checking
50
+ let temp_dir = tempfile::tempdir()?;
51
+ let temp_file = temp_dir.path().join("temp.rb");
52
+ std::fs::write(&temp_file, source)?;
53
+
54
+ // Run type check using FileChecker
55
+ let checker = FileChecker::new().with_context(|| "Failed to create FileChecker")?;
56
+
57
+ let methodray_diagnostics = checker
58
+ .check_file(&temp_file)
59
+ .with_context(|| format!("Failed to check file: {}", file_path.display()))?;
60
+
61
+ // Convert to LSP diagnostics
62
+ let lsp_diagnostics = methodray_diagnostics
63
+ .iter()
64
+ .map(to_lsp_diagnostic)
65
+ .collect();
66
+
67
+ Ok(lsp_diagnostics)
68
+ }
69
+ }
70
+
71
+ #[tower_lsp::async_trait]
72
+ impl LanguageServer for MethodRayServer {
73
+ async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
74
+ Ok(InitializeResult {
75
+ capabilities: ServerCapabilities {
76
+ text_document_sync: Some(TextDocumentSyncCapability::Kind(
77
+ TextDocumentSyncKind::FULL,
78
+ )),
79
+ ..Default::default()
80
+ },
81
+ ..Default::default()
82
+ })
83
+ }
84
+
85
+ async fn initialized(&self, _: InitializedParams) {
86
+ self.client
87
+ .log_message(MessageType::INFO, "MethodRay LSP server initialized")
88
+ .await;
89
+ }
90
+
91
+ async fn did_open(&self, params: DidOpenTextDocumentParams) {
92
+ let uri = params.text_document.uri;
93
+ let text = params.text_document.text;
94
+
95
+ self.documents.write().await.insert(uri.clone(), text);
96
+ self.check_document(uri).await;
97
+ }
98
+
99
+ async fn did_change(&self, params: DidChangeTextDocumentParams) {
100
+ let uri = params.text_document.uri;
101
+
102
+ if let Some(change) = params.content_changes.first() {
103
+ let text = change.text.clone();
104
+ self.documents.write().await.insert(uri.clone(), text);
105
+ // Note: In production, we'd want debouncing here
106
+ // self.check_document(uri).await;
107
+ }
108
+ }
109
+
110
+ async fn did_save(&self, params: DidSaveTextDocumentParams) {
111
+ self.check_document(params.text_document.uri).await;
112
+ }
113
+
114
+ async fn did_close(&self, params: DidCloseTextDocumentParams) {
115
+ self.documents
116
+ .write()
117
+ .await
118
+ .remove(&params.text_document.uri);
119
+
120
+ // Clear diagnostics
121
+ self.client
122
+ .publish_diagnostics(params.text_document.uri, vec![], None)
123
+ .await;
124
+ }
125
+
126
+ async fn shutdown(&self) -> Result<()> {
127
+ Ok(())
128
+ }
129
+ }
130
+
131
+ pub async fn run_server() {
132
+ let stdin = tokio::io::stdin();
133
+ let stdout = tokio::io::stdout();
134
+
135
+ let (service, socket) = LspService::new(|client| MethodRayServer::new(client));
136
+
137
+ Server::new(stdin, stdout, socket).serve(service).await;
138
+ }
data/rust/src/main.rs ADDED
@@ -0,0 +1,46 @@
1
+ //! MethodRay CLI - Fast Ruby type checker
2
+
3
+ use anyhow::Result;
4
+ use clap::Parser;
5
+
6
+ mod analyzer;
7
+ mod cache;
8
+ mod checker;
9
+ mod cli;
10
+ mod diagnostics;
11
+ mod env;
12
+ mod graph;
13
+ mod parser;
14
+ mod rbs;
15
+ mod source_map;
16
+ mod types;
17
+
18
+ use cli::{commands, Cli, Commands};
19
+
20
+ fn main() -> Result<()> {
21
+ let cli = Cli::parse();
22
+
23
+ match cli.command {
24
+ Commands::Check { file, verbose } => {
25
+ if let Some(file_path) = file {
26
+ let success = commands::check_single_file(&file_path, verbose)?;
27
+ if !success {
28
+ std::process::exit(1);
29
+ }
30
+ } else {
31
+ commands::check_project(verbose)?;
32
+ }
33
+ }
34
+ Commands::Watch { file } => {
35
+ commands::watch_file(&file)?;
36
+ }
37
+ Commands::Version => {
38
+ commands::print_version();
39
+ }
40
+ Commands::ClearCache => {
41
+ commands::clear_cache()?;
42
+ }
43
+ }
44
+
45
+ Ok(())
46
+ }