diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7b3e1ff00..018a58e3a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -41,6 +41,10 @@ jobs: run: | cd rust cargo test --verbose + - name: Test publish crates + run: | + cd rust + cargo publish --dry-run lint: name: cargo:lint diff --git a/rust/ruby-rbs/src/node/mod.rs b/rust/ruby-rbs/src/node/mod.rs index f78daa0d9..28d09e5ef 100644 --- a/rust/ruby-rbs/src/node/mod.rs +++ b/rust/ruby-rbs/src/node/mod.rs @@ -16,11 +16,12 @@ pub fn parse(rbs_code: &[u8]) -> Result, String> { unsafe { let start_ptr = rbs_code.as_ptr() as *const i8; let end_ptr = start_ptr.add(rbs_code.len()); + let bytes = rbs_code.len() as i32; let raw_rbs_string_value = rbs_string_new(start_ptr, end_ptr); let encoding_ptr = &rbs_encodings[RBS_ENCODING_UTF_8 as usize] as *const rbs_encoding_t; - let parser = rbs_parser_new(raw_rbs_string_value, encoding_ptr, 0, rbs_code.len() as i32); + let parser = rbs_parser_new(raw_rbs_string_value, encoding_ptr, 0, bytes); let mut signature: *mut rbs_signature_t = std::ptr::null_mut(); let result = rbs_parse_signature(parser, &mut signature); @@ -34,7 +35,18 @@ pub fn parse(rbs_code: &[u8]) -> Result, String> { if result { Ok(signature_node) } else { - Err(String::from("Failed to parse RBS signature")) + let error_message = (*parser) + .error + .as_ref() + .filter(|error| !error.message.is_null()) + .map(|error| { + std::ffi::CStr::from_ptr(error.message) + .to_string_lossy() + .into_owned() + }) + .unwrap_or_else(|| String::from("Failed to parse RBS signature")); + + Err(error_message) } } } @@ -267,6 +279,14 @@ impl SymbolNode<'_> { mod tests { use super::*; + #[test] + fn test_parse_error_contains_actual_message() { + let rbs_code = "class { end"; + let result = parse(rbs_code.as_bytes()); + let error_message = result.unwrap_err(); + assert_eq!(error_message, "expected one of class/module/constant name"); + } + #[test] fn test_parse() { let rbs_code = r#"type foo = "hello""#; diff --git a/rust/ruby-rbs/tests/sanity.rs b/rust/ruby-rbs/tests/sanity.rs new file mode 100644 index 000000000..8ed5c1900 --- /dev/null +++ b/rust/ruby-rbs/tests/sanity.rs @@ -0,0 +1,47 @@ +use std::path::Path; + +use ruby_rbs::node::parse; + +fn collect_rbs_files(dir: &Path) -> Vec { + let mut files = Vec::new(); + + for entry in std::fs::read_dir(dir).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + + if path.is_dir() { + files.extend(collect_rbs_files(&path)); + } else if path.extension().is_some_and(|ext| ext == "rbs") { + files.push(path); + } + } + + files +} + +#[test] +fn all_included_rbs_can_be_parsed() { + let repo_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let dirs = [repo_root.join("core"), repo_root.join("stdlib")]; + + let mut files: Vec<_> = dirs.iter().flat_map(|d| collect_rbs_files(d)).collect(); + files.sort(); + assert!(!files.is_empty()); + + let mut failures = Vec::new(); + + for file in &files { + let content = std::fs::read_to_string(file).unwrap(); + + if let Err(e) = parse(content.as_bytes()) { + failures.push(format!("{}: {}", file.display(), e)); + } + } + + assert!( + failures.is_empty(), + "Failed to parse {} RBS file(s):\n{}", + failures.len(), + failures.join("\n") + ); +}