use pretty::BoxDoc;
use crate::import::ImportRef;
use crate::lang::CodeLang;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TypePresentation<'a> {
GenericWrap {
name: &'a str,
},
Prefix {
prefix: &'a str,
},
Postfix {
suffix: &'a str,
},
Surround {
prefix: &'a str,
suffix: &'a str,
},
Delimited {
open: &'a str,
sep: &'a str,
close: &'a str,
},
Infix {
sep: &'a str,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum GenericApplicationStyle {
Delimited,
PrefixJuxtaposition,
PostfixJuxtaposition,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FunctionPresentation<'a> {
pub keyword: &'a str,
pub params_open: &'a str,
pub params_sep: &'a str,
pub params_close: &'a str,
pub arrow: &'a str,
pub return_first: bool,
pub curried: bool,
pub wrapper_open: &'a str,
pub wrapper_close: &'a str,
}
impl Default for FunctionPresentation<'_> {
fn default() -> Self {
Self {
keyword: "",
params_open: "(",
params_sep: ", ",
params_close: ")",
arrow: " => ",
return_first: false,
curried: false,
wrapper_open: "",
wrapper_close: "",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssociatedTypeStyle<'a> {
QualifiedPath {
open: &'a str,
as_kw: &'a str,
close_sep: &'a str,
simple_sep: &'a str,
},
DotAccess,
IndexAccess {
open: &'a str,
close: &'a str,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BoundsPresentation<'a> {
pub keyword: &'a str,
pub separator: &'a str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WildcardPresentation<'a> {
pub unbounded: &'a str,
pub upper_keyword: &'a str,
pub lower_keyword: &'a str,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum TypeName {
Importable {
module: String,
name: String,
is_type_only: bool,
alias: Option<String>,
#[serde(default)]
qualified: bool,
},
Primitive(String),
Array(Box<TypeName>),
ReadonlyArray(Box<TypeName>),
Generic {
base: Box<TypeName>,
params: Vec<TypeName>,
},
Union(Vec<TypeName>),
Intersection(Vec<TypeName>),
Pointer(Box<TypeName>),
Slice(Box<TypeName>),
Map {
key: Box<TypeName>,
value: Box<TypeName>,
},
Optional(Box<TypeName>),
Tuple(Vec<TypeName>),
Reference {
inner: Box<TypeName>,
mutable: bool,
#[serde(default)]
lifetime: Option<String>,
},
AssociatedType {
base: Box<TypeName>,
qualifier: Option<Box<TypeName>>,
member: String,
},
ImplTrait {
bounds: Vec<TypeName>,
},
DynTrait {
bounds: Vec<TypeName>,
},
Wildcard {
upper_bound: Option<Box<TypeName>>,
lower_bound: Option<Box<TypeName>>,
},
Function {
params: Vec<TypeName>,
return_type: Box<TypeName>,
},
Raw(String),
}
impl TypeName {
pub fn importable(module: &str, name: &str) -> Self {
TypeName::Importable {
module: module.to_string(),
name: name.to_string(),
is_type_only: false,
alias: None,
qualified: false,
}
}
pub fn importable_type(module: &str, name: &str) -> Self {
TypeName::Importable {
module: module.to_string(),
name: name.to_string(),
is_type_only: true,
alias: None,
qualified: false,
}
}
pub fn primitive(name: &str) -> Self {
TypeName::Primitive(name.to_string())
}
pub fn qualified(module: &str, name: &str) -> Self {
TypeName::Importable {
module: module.to_string(),
name: name.to_string(),
is_type_only: false,
alias: None,
qualified: true,
}
}
pub fn is_empty(&self) -> bool {
matches!(self, TypeName::Primitive(s) | TypeName::Raw(s) if s.is_empty())
}
pub fn with_alias(mut self, alias: &str) -> Self {
if let TypeName::Importable {
alias: ref mut a, ..
} = self
{
*a = Some(alias.to_string());
}
self
}
pub fn qualify(mut self) -> Self {
if let TypeName::Importable {
qualified: ref mut q,
..
} = self
{
*q = true;
}
self
}
pub fn array(inner: TypeName) -> Self {
TypeName::Array(Box::new(inner))
}
pub fn readonly_array(inner: TypeName) -> Self {
TypeName::ReadonlyArray(Box::new(inner))
}
pub fn generic(base: TypeName, params: Vec<TypeName>) -> Self {
TypeName::Generic {
base: Box::new(base),
params,
}
}
pub fn union(members: Vec<TypeName>) -> Self {
TypeName::Union(members)
}
pub fn intersection(members: Vec<TypeName>) -> Self {
TypeName::Intersection(members)
}
pub fn pointer(inner: TypeName) -> Self {
TypeName::Pointer(Box::new(inner))
}
pub fn slice(inner: TypeName) -> Self {
TypeName::Slice(Box::new(inner))
}
pub fn map(key: TypeName, value: TypeName) -> Self {
TypeName::Map {
key: Box::new(key),
value: Box::new(value),
}
}
pub fn optional(inner: TypeName) -> Self {
TypeName::Optional(Box::new(inner))
}
pub fn tuple(elements: Vec<TypeName>) -> Self {
TypeName::Tuple(elements)
}
pub fn unit() -> Self {
TypeName::Tuple(Vec::new())
}
pub fn reference(inner: TypeName) -> Self {
TypeName::Reference {
inner: Box::new(inner),
mutable: false,
lifetime: None,
}
}
pub fn reference_mut(inner: TypeName) -> Self {
TypeName::Reference {
inner: Box::new(inner),
mutable: true,
lifetime: None,
}
}
pub fn reference_with_lifetime(inner: TypeName, lifetime: &str) -> Self {
TypeName::Reference {
inner: Box::new(inner),
mutable: false,
lifetime: Some(lifetime.to_string()),
}
}
pub fn reference_mut_with_lifetime(inner: TypeName, lifetime: &str) -> Self {
TypeName::Reference {
inner: Box::new(inner),
mutable: true,
lifetime: Some(lifetime.to_string()),
}
}
pub fn function(params: Vec<TypeName>, return_type: TypeName) -> Self {
TypeName::Function {
params,
return_type: Box::new(return_type),
}
}
pub fn raw(s: &str) -> Self {
TypeName::Raw(s.to_string())
}
pub fn associated_type(base: TypeName, qualifier: Option<TypeName>, member: &str) -> Self {
TypeName::AssociatedType {
base: Box::new(base),
qualifier: qualifier.map(Box::new),
member: member.to_string(),
}
}
pub fn member_type(base: TypeName, member: &str) -> Self {
TypeName::AssociatedType {
base: Box::new(base),
qualifier: None,
member: member.to_string(),
}
}
pub fn impl_trait(bounds: Vec<TypeName>) -> Self {
TypeName::ImplTrait { bounds }
}
pub fn dyn_trait(bounds: Vec<TypeName>) -> Self {
TypeName::DynTrait { bounds }
}
pub fn wildcard() -> Self {
TypeName::Wildcard {
upper_bound: None,
lower_bound: None,
}
}
pub fn wildcard_extends(bound: TypeName) -> Self {
TypeName::Wildcard {
upper_bound: Some(Box::new(bound)),
lower_bound: None,
}
}
pub fn wildcard_super(bound: TypeName) -> Self {
TypeName::Wildcard {
upper_bound: None,
lower_bound: Some(Box::new(bound)),
}
}
pub fn simple_name(&self) -> Option<&str> {
match self {
TypeName::Importable { name, .. } => Some(name),
TypeName::Primitive(name) => Some(name),
TypeName::Generic { base, .. } => base.simple_name(),
TypeName::Raw(s) => Some(s),
_ => None,
}
}
pub(crate) fn collect_imports(&self, out: &mut Vec<ImportRef>) {
crate::type_name_import::collect_imports(self, out)
}
pub fn to_doc<F>(&self, resolve: &F) -> BoxDoc<'static, ()>
where
F: Fn(&str, &str) -> String,
{
crate::type_name_render::to_doc(self, resolve)
}
pub fn render<F>(
&self,
width: usize,
resolve: &F,
) -> Result<String, crate::error::SigilStitchError>
where
F: Fn(&str, &str) -> String,
{
crate::type_name_render::render(self, width, resolve)
}
pub fn to_doc_with_lang<F>(&self, resolve: &F, lang: &dyn CodeLang) -> BoxDoc<'static, ()>
where
F: Fn(&str, &str) -> String,
{
crate::type_name_render::to_doc_with_lang(self, resolve, lang)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lang::typescript::TypeScript;
use crate::type_name_render::is_compound_type;
fn identity_resolve(module: &str, name: &str) -> String {
let _ = module;
name.to_string()
}
macro_rules! test_lang {
($name:ident, $base:ty { $($override:tt)* }) => {
#[derive(Debug, Clone)]
struct $name(pub $base);
impl $name {
fn new() -> Self { Self(<$base>::new()) }
}
impl crate::lang::CodeLang for $name {
fn file_extension(&self) -> &str { self.0.file_extension() }
fn reserved_words(&self) -> &[&str] { self.0.reserved_words() }
fn render_imports(&self, imports: &crate::import::ImportGroup) -> String { self.0.render_imports(imports) }
fn render_string_literal(&self, s: &str) -> String { self.0.render_string_literal(s) }
fn render_doc_comment(&self, lines: &[&str]) -> String { self.0.render_doc_comment(lines) }
fn line_comment_prefix(&self) -> &str { self.0.line_comment_prefix() }
fn render_visibility(&self, vis: crate::spec::modifiers::Visibility, ctx: crate::spec::modifiers::DeclarationContext) -> &str { self.0.render_visibility(vis, ctx) }
fn function_keyword(&self, ctx: crate::spec::modifiers::DeclarationContext) -> &str { self.0.function_keyword(ctx) }
fn type_keyword(&self, kind: crate::spec::modifiers::TypeKind) -> &str { self.0.type_keyword(kind) }
fn methods_inside_type_body(&self, kind: crate::spec::modifiers::TypeKind) -> bool { self.0.methods_inside_type_body(kind) }
fn type_presentation(&self) -> crate::lang::config::TypePresentationConfig<'_> { self.0.type_presentation() }
fn block_syntax(&self) -> crate::lang::config::BlockSyntaxConfig<'_> { self.0.block_syntax() }
fn function_syntax(&self) -> crate::lang::config::FunctionSyntaxConfig<'_> { self.0.function_syntax() }
fn type_decl_syntax(&self) -> crate::lang::config::TypeDeclSyntaxConfig<'_> { self.0.type_decl_syntax() }
fn enum_and_annotation(&self) -> crate::lang::config::EnumAndAnnotationConfig<'_> { self.0.enum_and_annotation() }
$($override)*
}
};
}
test_lang!(PrefixLang, TypeScript {
fn generic_syntax(&self) -> crate::lang::config::GenericSyntaxConfig<'_> {
crate::lang::config::GenericSyntaxConfig {
application_style: GenericApplicationStyle::PrefixJuxtaposition,
..self.0.generic_syntax()
}
}
});
test_lang!(PostfixLang, TypeScript {
fn generic_syntax(&self) -> crate::lang::config::GenericSyntaxConfig<'_> {
crate::lang::config::GenericSyntaxConfig {
application_style: GenericApplicationStyle::PostfixJuxtaposition,
..self.0.generic_syntax()
}
}
});
#[test]
fn test_primitive() {
let t = TypeName::primitive("number");
assert_eq!(t.render(80, &identity_resolve).unwrap(), "number");
}
#[test]
fn test_importable() {
let t = TypeName::importable("./models", "User");
assert_eq!(t.render(80, &identity_resolve).unwrap(), "User");
}
#[test]
fn test_importable_with_alias() {
let t = TypeName::importable("./other", "User");
let resolve = |module: &str, name: &str| {
if module == "./other" && name == "User" {
"OtherUser".to_string()
} else {
name.to_string()
}
};
assert_eq!(t.render(80, &resolve).unwrap(), "OtherUser");
}
#[test]
fn test_array() {
let t = TypeName::array(TypeName::primitive("string"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "string[]");
}
#[test]
fn test_generic() {
let t = TypeName::generic(
TypeName::primitive("Promise"),
vec![TypeName::importable("./models", "User")],
);
assert_eq!(t.render(80, &identity_resolve).unwrap(), "Promise<User>");
}
#[test]
fn test_generic_multiline() {
let t = TypeName::generic(
TypeName::primitive("Map"),
vec![
TypeName::primitive("VeryLongKeyTypeName"),
TypeName::primitive("VeryLongValueTypeName"),
],
);
let output = t.render(20, &identity_resolve).unwrap();
assert!(output.contains('\n'));
assert!(output.contains("VeryLongKeyTypeName"));
assert!(output.contains("VeryLongValueTypeName"));
}
#[test]
fn test_union() {
let t = TypeName::union(vec![
TypeName::primitive("string"),
TypeName::primitive("number"),
TypeName::primitive("boolean"),
]);
assert_eq!(
t.render(80, &identity_resolve).unwrap(),
"string | number | boolean"
);
}
#[test]
fn test_union_multiline() {
let t = TypeName::union(vec![
TypeName::primitive("VeryLongTypeName1"),
TypeName::primitive("VeryLongTypeName2"),
TypeName::primitive("VeryLongTypeName3"),
]);
let output = t.render(30, &identity_resolve).unwrap();
assert!(output.contains('\n'));
}
#[test]
fn test_pointer() {
let t = TypeName::pointer(TypeName::primitive("User"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "*User");
}
#[test]
fn test_slice() {
let t = TypeName::slice(TypeName::primitive("User"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "[]User");
}
#[test]
fn test_map() {
let t = TypeName::map(TypeName::primitive("string"), TypeName::primitive("User"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "map[string]User");
}
#[test]
fn test_optional() {
let t = TypeName::optional(TypeName::primitive("string"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "string | null");
}
#[test]
fn test_function_type() {
let t = TypeName::function(
vec![TypeName::primitive("string"), TypeName::primitive("number")],
TypeName::primitive("boolean"),
);
assert_eq!(
t.render(80, &identity_resolve).unwrap(),
"(string, number) => boolean"
);
}
#[test]
fn test_deeply_nested() {
let inner = TypeName::generic(
TypeName::primitive("Array"),
vec![TypeName::importable("./models", "User")],
);
let outer = TypeName::generic(TypeName::primitive("Promise"), vec![inner]);
assert_eq!(
outer.render(80, &identity_resolve).unwrap(),
"Promise<Array<User>>"
);
}
#[test]
fn test_collect_imports() {
let t = TypeName::generic(
TypeName::importable("./base", "Base"),
vec![
TypeName::importable("./models", "User"),
TypeName::array(TypeName::importable("./models", "Tag")),
],
);
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 3);
assert_eq!(imports[0].name, "Base");
assert_eq!(imports[1].name, "User");
assert_eq!(imports[2].name, "Tag");
}
#[test]
fn test_raw_no_imports() {
let t = TypeName::raw("any");
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert!(imports.is_empty());
assert_eq!(t.render(80, &identity_resolve).unwrap(), "any");
}
#[test]
fn test_with_alias_on_importable() {
let t = TypeName::importable("./models", "User").with_alias("MyUser");
if let TypeName::Importable { alias, .. } = &t {
assert_eq!(alias.as_deref(), Some("MyUser"));
} else {
panic!("Expected Importable variant");
}
}
#[test]
fn test_with_alias_propagates_to_import_ref() {
let t = TypeName::importable("./models", "User").with_alias("MyUser");
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].name, "User");
assert_eq!(imports[0].alias.as_deref(), Some("MyUser"));
}
#[test]
fn test_with_alias_noop_on_primitive() {
let t = TypeName::primitive("number").with_alias("MyNumber");
assert_eq!(t.render(80, &identity_resolve).unwrap(), "number");
}
#[test]
fn test_with_alias_renders_alias_name() {
let t = TypeName::importable("./models", "User").with_alias("MyUser");
let resolve = |_module: &str, _name: &str| "MyUser".to_string();
assert_eq!(t.render(80, &resolve).unwrap(), "MyUser");
}
#[test]
fn test_tuple() {
let t = TypeName::tuple(vec![
TypeName::primitive("string"),
TypeName::primitive("number"),
]);
assert_eq!(t.render(80, &identity_resolve).unwrap(), "(string, number)");
}
#[test]
fn test_unit_tuple() {
let t = TypeName::unit();
assert_eq!(t.render(80, &identity_resolve).unwrap(), "()");
}
#[test]
fn test_tuple_collect_imports() {
let t = TypeName::tuple(vec![
TypeName::importable("./models", "User"),
TypeName::importable("./models", "Tag"),
]);
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 2);
assert_eq!(imports[0].name, "User");
assert_eq!(imports[1].name, "Tag");
}
#[test]
fn test_tuple_with_lang_ts() {
let lang = TypeScript::new();
let t = TypeName::tuple(vec![
TypeName::primitive("string"),
TypeName::primitive("number"),
]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "[string, number]");
}
#[test]
fn test_tuple_with_lang_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::tuple(vec![
TypeName::primitive("String"),
TypeName::primitive("i32"),
]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "(String, i32)");
}
#[test]
fn test_tuple_with_lang_python() {
use crate::lang::python::Python;
let lang = Python::new();
let t = TypeName::tuple(vec![TypeName::primitive("str"), TypeName::primitive("int")]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "tuple[str, int]");
}
#[test]
fn test_tuple_with_lang_cpp() {
use crate::lang::cpp_lang::CppLang;
let lang = CppLang::new();
let t = TypeName::tuple(vec![
TypeName::primitive("int"),
TypeName::primitive("std::string"),
]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(
String::from_utf8(buf).unwrap(),
"std::tuple<int, std::string>"
);
}
#[test]
fn test_unit_tuple_with_lang_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::unit();
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "()");
}
#[test]
fn test_reference() {
let t = TypeName::reference(TypeName::primitive("str"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "&str");
}
#[test]
fn test_reference_mut() {
let t = TypeName::reference_mut(TypeName::primitive("Vec<i32>"));
assert_eq!(t.render(80, &identity_resolve).unwrap(), "&mut Vec<i32>");
}
#[test]
fn test_reference_collect_imports() {
let t = TypeName::reference(TypeName::importable("./models", "User"));
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 1);
assert_eq!(imports[0].name, "User");
}
#[test]
fn test_reference_with_lang_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::reference(TypeName::primitive("String"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "&String");
}
#[test]
fn test_reference_mut_with_lang_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::reference_mut(TypeName::primitive("String"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "&mut String");
}
#[test]
fn test_reference_with_lang_cpp() {
use crate::lang::cpp_lang::CppLang;
let lang = CppLang::new();
let t = TypeName::reference(TypeName::primitive("std::string"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "const std::string&");
}
#[test]
fn test_reference_mut_with_lang_cpp() {
use crate::lang::cpp_lang::CppLang;
let lang = CppLang::new();
let t = TypeName::reference_mut(TypeName::primitive("std::string"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "std::string&");
}
#[test]
fn test_reference_with_lang_ts() {
let lang = TypeScript::new();
let t = TypeName::reference(TypeName::primitive("string"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "string");
}
#[test]
fn test_reference_mut_with_lang_go() {
use crate::lang::go_lang::GoLang;
let lang = GoLang::new();
let t = TypeName::reference_mut(TypeName::primitive("int"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "*int");
}
#[test]
fn test_reference_with_lang_go() {
use crate::lang::go_lang::GoLang;
let lang = GoLang::new();
let t = TypeName::reference(TypeName::primitive("int"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "int");
}
#[test]
fn test_reference_with_lang_c() {
use crate::lang::c_lang::CLang;
let lang = CLang::new();
let t = TypeName::reference(TypeName::primitive("int"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "const int*");
}
#[test]
fn test_reference_mut_with_lang_c() {
use crate::lang::c_lang::CLang;
let lang = CLang::new();
let t = TypeName::reference_mut(TypeName::primitive("int"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "int*");
}
#[test]
fn test_generic_prefix_juxtaposition() {
let lang = PrefixLang::new();
let t = TypeName::generic(
TypeName::primitive("Maybe"),
vec![TypeName::primitive("Int")],
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "Maybe Int");
}
#[test]
fn test_generic_prefix_juxtaposition_compound_param() {
let lang = PrefixLang::new();
let inner = TypeName::generic(
TypeName::primitive("Maybe"),
vec![TypeName::primitive("Int")],
);
let t = TypeName::generic(
TypeName::primitive("Either"),
vec![TypeName::primitive("String"), inner],
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "Either String (Maybe Int)");
}
#[test]
fn test_generic_postfix_juxtaposition_single() {
let lang = PostfixLang::new();
let t = TypeName::generic(
TypeName::primitive("option"),
vec![TypeName::primitive("int")],
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "int option");
}
#[test]
fn test_generic_postfix_juxtaposition_multi() {
let lang = PostfixLang::new();
let t = TypeName::generic(
TypeName::primitive("result"),
vec![TypeName::primitive("int"), TypeName::primitive("string")],
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "(int, string) result");
}
#[test]
fn test_is_compound_type() {
assert!(is_compound_type(&TypeName::generic(
TypeName::primitive("A"),
vec![TypeName::primitive("B")],
)));
assert!(is_compound_type(&TypeName::union(vec![
TypeName::primitive("A"),
TypeName::primitive("B"),
])));
assert!(is_compound_type(&TypeName::intersection(vec![
TypeName::primitive("A"),
TypeName::primitive("B"),
])));
assert!(is_compound_type(&TypeName::function(
vec![TypeName::primitive("A")],
TypeName::primitive("B"),
)));
assert!(is_compound_type(&TypeName::tuple(vec![
TypeName::primitive("A"),
TypeName::primitive("B"),
])));
assert!(!is_compound_type(&TypeName::primitive("Int")));
assert!(!is_compound_type(&TypeName::array(TypeName::primitive(
"Int"
),)));
}
#[test]
fn test_associated_type_rust_qualified() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::associated_type(
TypeName::primitive("T"),
Some(TypeName::primitive("Iterator")),
"Item",
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "<T as Iterator>::Item");
}
#[test]
fn test_associated_type_rust_simple() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::member_type(TypeName::primitive("Self"), "Output");
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "Self::Output");
}
#[test]
fn test_associated_type_ts_index_access() {
let lang = TypeScript::new();
let t = TypeName::associated_type(
TypeName::primitive("T"),
Some(TypeName::primitive("Qual")),
"key",
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "T[\"key\"]");
}
#[test]
fn test_associated_type_java_dot() {
use crate::lang::java_lang::JavaLang;
let lang = JavaLang::new();
let t = TypeName::member_type(TypeName::primitive("Map"), "Entry");
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "Map.Entry");
}
#[test]
fn test_associated_type_collect_imports() {
let t = TypeName::associated_type(
TypeName::importable("./models", "User"),
Some(TypeName::importable("./traits", "Serializable")),
"Output",
);
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 2);
}
#[test]
fn test_impl_trait_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::impl_trait(vec![
TypeName::primitive("Display"),
TypeName::primitive("Debug"),
]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "impl Display + Debug");
}
#[test]
fn test_dyn_trait_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::dyn_trait(vec![TypeName::primitive("Error")]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "dyn Error");
}
#[test]
fn test_impl_trait_ts_intersection() {
let lang = TypeScript::new();
let t = TypeName::impl_trait(vec![
TypeName::primitive("Serializable"),
TypeName::primitive("Loggable"),
]);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "Serializable & Loggable");
}
#[test]
fn test_wildcard_java_unbounded() {
use crate::lang::java_lang::JavaLang;
let lang = JavaLang::new();
let t = TypeName::wildcard();
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "?");
}
#[test]
fn test_wildcard_java_extends() {
use crate::lang::java_lang::JavaLang;
let lang = JavaLang::new();
let t = TypeName::wildcard_extends(TypeName::primitive("Comparable"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "? extends Comparable");
}
#[test]
fn test_wildcard_java_super() {
use crate::lang::java_lang::JavaLang;
let lang = JavaLang::new();
let t = TypeName::wildcard_super(TypeName::primitive("Number"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "? super Number");
}
#[test]
fn test_wildcard_kotlin() {
use crate::lang::kotlin::Kotlin;
let lang = Kotlin::new();
let t = TypeName::wildcard();
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "*");
}
#[test]
fn test_wildcard_kotlin_out() {
use crate::lang::kotlin::Kotlin;
let lang = Kotlin::new();
let t = TypeName::wildcard_extends(TypeName::primitive("Number"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "out Number");
}
#[test]
fn test_wildcard_kotlin_in() {
use crate::lang::kotlin::Kotlin;
let lang = Kotlin::new();
let t = TypeName::wildcard_super(TypeName::primitive("Number"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "in Number");
}
#[test]
fn test_wildcard_go() {
use crate::lang::go_lang::GoLang;
let lang = GoLang::new();
let t = TypeName::wildcard();
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "any");
}
#[test]
fn test_wildcard_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::wildcard();
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "_");
}
#[test]
fn test_impl_trait_collect_imports() {
let t = TypeName::impl_trait(vec![
TypeName::importable("./traits", "Serializable"),
TypeName::primitive("Debug"),
]);
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 1);
}
#[test]
fn test_dyn_trait_collect_imports() {
let t = TypeName::dyn_trait(vec![TypeName::importable("./errors", "AppError")]);
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 1);
}
#[test]
fn test_wildcard_collect_imports() {
let t = TypeName::wildcard_extends(TypeName::importable("./models", "User"));
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert_eq!(imports.len(), 1);
}
#[test]
fn test_associated_type_default_rendering() {
let t = TypeName::associated_type(
TypeName::primitive("T"),
Some(TypeName::primitive("Iter")),
"Item",
);
assert_eq!(
t.render(80, &identity_resolve).unwrap(),
"<T as Iter>::Item"
);
}
#[test]
fn test_member_type_default_rendering() {
let t = TypeName::member_type(TypeName::primitive("Self"), "Output");
assert_eq!(t.render(80, &identity_resolve).unwrap(), "Self::Output");
}
#[test]
fn test_impl_trait_default_rendering() {
let t = TypeName::impl_trait(vec![TypeName::primitive("Display")]);
assert_eq!(t.render(80, &identity_resolve).unwrap(), "impl Display");
}
#[test]
fn test_dyn_trait_default_rendering() {
let t = TypeName::dyn_trait(vec![
TypeName::primitive("Error"),
TypeName::primitive("Send"),
]);
assert_eq!(t.render(80, &identity_resolve).unwrap(), "dyn Error + Send");
}
#[test]
fn test_wildcard_default_rendering() {
assert_eq!(
TypeName::wildcard().render(80, &identity_resolve).unwrap(),
"?"
);
assert_eq!(
TypeName::wildcard_extends(TypeName::primitive("T"))
.render(80, &identity_resolve)
.unwrap(),
"? extends T"
);
assert_eq!(
TypeName::wildcard_super(TypeName::primitive("T"))
.render(80, &identity_resolve)
.unwrap(),
"? super T"
);
}
#[test]
fn test_reference_with_lifetime_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::reference_with_lifetime(TypeName::primitive("str"), "'a");
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "&'a str");
}
#[test]
fn test_reference_mut_with_lifetime_rust() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::reference_mut_with_lifetime(TypeName::primitive("String"), "'a");
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "&'a mut String");
}
#[test]
fn test_reference_with_lifetime_default_rendering() {
let t = TypeName::reference_with_lifetime(TypeName::primitive("str"), "'a");
assert_eq!(t.render(80, &identity_resolve).unwrap(), "&'a str");
}
#[test]
fn test_reference_without_lifetime_unchanged() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::reference(TypeName::primitive("String"));
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "&String");
}
#[test]
fn test_qualified_renders_with_rust_separator() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::qualified("serde_json", "Value");
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "serde_json::Value");
}
#[test]
fn test_qualified_renders_with_go_separator() {
use crate::lang::go_lang::GoLang;
let lang = GoLang::new();
let t = TypeName::qualified("net/http", "Server");
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "net/http.Server");
}
#[test]
fn test_qualified_skips_import_collection() {
let t = TypeName::qualified("serde_json", "Value");
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert!(imports.is_empty());
}
#[test]
fn test_qualify_modifier() {
let t = TypeName::importable("serde_json", "Value").qualify();
let mut imports = Vec::new();
t.collect_imports(&mut imports);
assert!(imports.is_empty());
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "serde_json::Value");
}
#[test]
fn test_qualified_in_generic() {
use crate::lang::rust_lang::RustLang;
let lang = RustLang::new();
let t = TypeName::generic(
TypeName::qualified("std::collections", "HashMap"),
vec![
TypeName::primitive("String"),
TypeName::qualified("serde_json", "Value"),
],
);
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(
String::from_utf8(buf).unwrap(),
"std::collections::HashMap<String, serde_json::Value>"
);
}
#[test]
fn test_qualified_fallback_unsupported_lang() {
let lang = TypeScript::new();
let t = TypeName::qualified("serde_json", "Value");
let doc = t.to_doc_with_lang(&identity_resolve, &lang);
let mut buf = Vec::new();
doc.render(80, &mut buf).unwrap();
assert_eq!(String::from_utf8(buf).unwrap(), "Value");
}
}