use codoc::parser::ruby::RubyParser;
use codoc::parser::{ParseContext, Parser, ParserConfig};
use codoc::schema::{Document, Entity, Language, Visibility};
use std::fs;
use std::path::PathBuf;
fn parse_fixture(name: &str) -> Document {
let mut parser = RubyParser::new().expect("Failed to create parser");
let fixture_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/ruby")
.join(name);
let content = fs::read_to_string(&fixture_path).expect("Failed to read fixture");
let entities = parser
.parse_file(&fixture_path, &content)
.expect("Failed to parse fixture");
let config = ParserConfig::new("Test", Language::Ruby);
let mut ctx = ParseContext::new(config);
ctx.entities = entities;
ctx.files.push(name.to_string());
ctx.into_document()
}
fn find_class<'a>(doc: &'a Document, name: &str) -> Option<&'a codoc::schema::Class> {
doc.entities.iter().find_map(|e| {
if let Entity::Class(c) = e {
if c.base.name == name {
return Some(c);
}
}
None
})
}
fn find_module<'a>(doc: &'a Document, name: &str) -> Option<&'a codoc::schema::Module> {
doc.entities.iter().find_map(|e| {
if let Entity::Module(m) = e {
if m.base.name == name || m.base.name.ends_with(&format!("::{}", name)) {
return Some(m);
}
}
None
})
}
#[test]
fn test_basic_class() {
let doc = parse_fixture("basic_class.rb");
let class = find_class(&doc, "BasicClass").expect("BasicClass not found");
assert!(class.extends.is_some());
assert_eq!(
class.extends.as_ref().unwrap().name,
"ParentClass",
"Expected extends to be 'ParentClass', got: {}",
class.extends.as_ref().unwrap().name
);
assert!(class.base.docs.is_some());
let docs = class.base.docs.as_ref().unwrap();
assert!(docs.summary.is_some());
let summary = docs.summary.as_ref().unwrap();
assert!(summary.contains("simple class"));
assert!(!class.properties.is_empty());
let name_prop = class
.properties
.iter()
.find(|p| p.base.name == "name")
.expect("name property not found");
assert_eq!(name_prop.getter, Some(true));
assert_eq!(name_prop.setter.unwrap_or(false), false);
let count_prop = class
.properties
.iter()
.find(|p| p.base.name == "count")
.expect("count property not found");
assert_eq!(count_prop.getter, Some(true));
assert_eq!(count_prop.setter, Some(true));
assert!(!class.methods.is_empty());
let init = class
.methods
.iter()
.find(|m| m.base.name == "initialize" || m.base.name.contains("initialize"));
if let Some(init) = init {
let _ = init.params.len();
}
let increment = class.methods.iter().find(|m| m.base.name == "increment");
if let Some(increment) = increment {
let _ = increment.returns.is_some();
}
let create = class
.methods
.iter()
.find(|m| m.base.name == "create" || m.base.name == "self.create");
if let Some(create) = create {
let _ = create.is_static;
}
let validate = class.methods.iter().find(|m| m.base.name == "validate");
if let Some(validate) = validate {
let _ = validate.base.visibility;
}
}
#[test]
fn test_modules() {
let doc = parse_fixture("modules.rb");
let app_module = find_module(&doc, "Application").expect("Application module not found");
assert!(!app_module.constants.is_empty());
let version = app_module
.constants
.iter()
.find(|c| c.base.name == "VERSION")
.expect("VERSION constant not found");
assert!(version.value.is_some());
let has_utils = find_module(&doc, "Utils").is_some()
|| doc.entities.iter().any(|e| {
if let Entity::Module(m) = e {
m.base.name.contains("Utils")
} else {
false
}
});
if let Some(utils) = find_module(&doc, "Utils") {
assert!(!utils.functions.is_empty() || !utils.instance_methods.is_empty());
} else {
assert!(has_utils || !app_module.modules.is_empty());
}
let core = find_class(&doc, "Core");
if let Some(core) = core {
let _ = core.properties.len();
}
}
#[test]
fn test_mixins() {
let doc = parse_fixture("mixins.rb");
let loggable = find_module(&doc, "Loggable").expect("Loggable module not found");
assert!(!loggable.instance_methods.is_empty());
let mixed = find_class(&doc, "MixedClass").expect("MixedClass not found");
assert!(!mixed.includes.is_empty());
assert!(mixed.includes.iter().any(|i| i.name.contains("Loggable")));
}
#[test]
fn test_methods() {
let doc = parse_fixture("methods.rb");
let class = find_class(&doc, "MethodExamples").expect("MethodExamples not found");
let no_params = class
.methods
.iter()
.find(|m| m.base.name == "no_params")
.expect("no_params not found");
assert!(no_params.params.is_empty());
let required = class
.methods
.iter()
.find(|m| m.base.name == "required_params")
.expect("required_params not found");
assert_eq!(required.params.len(), 2);
let optional = class
.methods
.iter()
.find(|m| m.base.name == "optional_params")
.expect("optional_params not found");
assert!(optional.params.len() >= 1);
let keyword = class
.methods
.iter()
.find(|m| m.base.name == "keyword_params")
.expect("keyword_params not found");
assert!(!keyword.params.is_empty());
let splat = class
.methods
.iter()
.find(|m| m.base.name == "splat_params")
.expect("splat_params not found");
let rest_param = splat.params.iter().find(|p| p.rest == Some(true));
assert!(rest_param.is_some());
let with_block = class
.methods
.iter()
.find(|m| m.base.name == "with_block")
.expect("with_block not found");
assert!(with_block.yields.is_some());
let raising = class
.methods
.iter()
.find(|m| m.base.name == "raising_method")
.expect("raising_method not found");
assert!(!raising.throws.is_empty());
let protected = class
.methods
.iter()
.find(|m| m.base.name == "protected_helper")
.expect("protected_helper not found");
assert_eq!(
protected.base.visibility,
Some(Visibility::Protected),
"Expected protected_helper to have Protected visibility"
);
let private = class
.methods
.iter()
.find(|m| m.base.name == "private_helper")
.expect("private_helper not found");
assert_eq!(
private.base.visibility,
Some(Visibility::Private),
"Expected private_helper to have Private visibility"
);
}
#[test]
fn test_attributes() {
let doc = parse_fixture("attributes.rb");
let class = find_class(&doc, "AttributeExamples").expect("AttributeExamples not found");
let readonly = class
.properties
.iter()
.find(|p| p.base.name == "readonly_value")
.expect("readonly_value not found");
assert_eq!(readonly.getter, Some(true));
assert_eq!(readonly.setter.unwrap_or(false), false);
let writeonly = class
.properties
.iter()
.find(|p| p.base.name == "writeonly_value")
.expect("writeonly_value not found");
assert_eq!(writeonly.getter.unwrap_or(false), false);
assert_eq!(writeonly.setter, Some(true));
let readwrite = class
.properties
.iter()
.find(|p| p.base.name == "readwrite_value")
.expect("readwrite_value not found");
assert_eq!(readwrite.getter, Some(true));
assert_eq!(readwrite.setter, Some(true));
}
#[test]
fn test_yardoc() {
let doc = parse_fixture("yardoc.rb");
let class = find_class(&doc, "YardocExamples").expect("YardocExamples not found");
assert!(class.base.docs.is_some());
let docs = class.base.docs.as_ref().unwrap();
assert!(docs.summary.is_some());
assert!(docs.since.is_some());
assert!(!docs.notes.is_empty());
assert!(!docs.todos.is_empty(), "Class should have @todo items");
assert!(docs.todos.iter().any(|t| t.contains("more features")));
let comprehensive = class
.methods
.iter()
.find(|m| m.base.name == "comprehensive")
.expect("comprehensive not found");
assert!(
comprehensive.base.docs.is_some(),
"comprehensive method should have documentation"
);
let method_docs = comprehensive.base.docs.as_ref().unwrap();
assert!(method_docs.summary.is_some());
assert!(method_docs.deprecated.is_some());
assert!(!method_docs.examples.is_empty());
assert!(!comprehensive.params.is_empty());
let options_param = comprehensive
.params
.iter()
.find(|p| p.name == "options")
.expect("options param not found");
assert!(
!options_param.options.is_empty(),
"options parameter should have @option entries"
);
assert_eq!(options_param.options.len(), 3);
assert!(options_param.options.iter().any(|o| o.name == "timeout"));
assert!(options_param.options.iter().any(|o| o.name == "retry"));
assert!(options_param.options.iter().any(|o| o.name == "format"));
assert!(
comprehensive.returns.is_some(),
"comprehensive method should have return type"
);
assert_eq!(comprehensive.returns.as_ref().unwrap().name, "Hash");
assert!(
!comprehensive.throws.is_empty(),
"comprehensive method should have throws"
);
assert_eq!(comprehensive.throws.len(), 2);
let with_yield = class
.methods
.iter()
.find(|m| m.base.name == "with_yield")
.expect("with_yield not found");
assert!(with_yield.yields.is_some());
let yields = with_yield.yields.as_ref().unwrap();
assert!(!yields.params.is_empty());
}