-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchecker.rs
More file actions
136 lines (111 loc) · 4.03 KB
/
checker.rs
File metadata and controls
136 lines (111 loc) · 4.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use crate::analyzer::AstInstaller;
use crate::diagnostics::Diagnostic;
use crate::env::{GlobalEnv, LocalEnv};
use crate::parser::ParseSession;
use anyhow::{Context, Result};
use std::path::Path;
/// File type checker
pub struct FileChecker {
// No state - creates fresh GlobalEnv for each check
}
impl FileChecker {
/// Create new FileChecker
/// Note: This is for standalone CLI usage (no Ruby runtime)
pub fn new() -> Result<Self> {
// Just verify cache exists
use crate::cache::RbsCache;
RbsCache::load().context(
"Failed to load RBS cache.",
)?;
Ok(Self {})
}
/// Check a single Ruby file
pub fn check_file(&self, file_path: &Path) -> Result<Vec<Diagnostic>> {
// Read source code
let source = std::fs::read_to_string(file_path)
.with_context(|| format!("Failed to read {}", file_path.display()))?;
// Create ParseSession (arena allocator) for this check
let session = ParseSession::new();
// Parse file
let parse_result = session
.parse_source(&source, &file_path.to_string_lossy())
.with_context(|| format!("Failed to parse {}", file_path.display()))?;
// Create fresh GlobalEnv for this analysis
let mut genv = GlobalEnv::new();
load_rbs_from_cache(&mut genv)?;
let mut lenv = LocalEnv::new();
let mut installer = AstInstaller::new(&mut genv, &mut lenv, &source);
// Process AST
let root = parse_result.node();
if let Some(program_node) = root.as_program_node() {
let statements = program_node.statements();
for stmt in &statements.body() {
installer.install_node(&stmt);
}
}
installer.finish();
// Collect diagnostics
let diagnostics = collect_diagnostics(&genv, file_path);
Ok(diagnostics)
} // session drops here, freeing arena memory
}
/// Load RBS methods from cache (CLI mode without Ruby runtime)
fn load_rbs_from_cache(genv: &mut GlobalEnv) -> Result<()> {
use crate::cache::RbsCache;
use crate::rbs::converter::RbsTypeConverter;
use crate::types::Type;
let cache = RbsCache::load().context(
"Failed to load RBS cache.",
)?;
let methods = cache.methods();
for method_info in methods {
let receiver_type = Type::instance(&method_info.receiver_class);
// Convert block param type strings to Type enums
let block_param_types = method_info.block_param_types.as_ref().map(|types| {
types
.iter()
.map(|s| RbsTypeConverter::parse(s))
.collect()
});
genv.register_builtin_method_with_block(
receiver_type,
&method_info.method_name,
RbsTypeConverter::parse(&method_info.return_type_str),
block_param_types,
);
}
Ok(())
}
/// Collect type error diagnostics from GlobalEnv
fn collect_diagnostics(genv: &GlobalEnv, file_path: &Path) -> Vec<Diagnostic> {
use crate::diagnostics::{Diagnostic, Location};
use std::path::PathBuf;
let mut diagnostics = Vec::new();
// Convert TypeErrors to Diagnostics
for type_error in &genv.type_errors {
// Use actual location from TypeError if available
let location = if let Some(source_loc) = &type_error.location {
Location {
file: PathBuf::from(file_path),
line: source_loc.line,
column: source_loc.column,
length: Some(source_loc.length),
}
} else {
// Fallback to placeholder
Location {
file: PathBuf::from(file_path),
line: 1,
column: 1,
length: None,
}
};
let diagnostic = Diagnostic::undefined_method(
location,
&type_error.receiver_type.show(),
&type_error.method_name,
);
diagnostics.push(diagnostic);
}
diagnostics
}