use std::path::PathBuf;
use cpp_code_analysis::file::coordinates::{FileOffset, ReadingOffset};
use cpp_code_analysis::{DiskFiles, OpenDocuments, Session, SessionFiles, WatchFilter};
#[test]
fn a_member_of_a_class_template_instantiation_is_named() {
let root: PathBuf = std::env::temp_dir().join("cppls-hover-template");
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).expect("a project directory");
let file = root.join("a.cpp");
let source = "template <typename T, typename U = int>\n\
struct Pair {\n\
T first;\n\
U second;\n\
T sum() const { return first + second; }\n\
};\n\
void f() {\n\
int a = Pair<int, int>{1, 2}.sum();\n\
}\n";
std::fs::write(&file, source).expect("the fixture writes");
let mut session = Session::open(
&root,
SessionFiles::new(OpenDocuments::new(), DiskFiles),
WatchFilter::new(&root),
);
session.add_project_files([file.clone()]);
session.did_open(&file, source);
session.catch_up(&file);
let _ = session.cook(&file);
let _ = session.index_everything();
let view = session.view_of_the_file(&file).expect("the written text");
let at =
source.find("Pair<int, int>{1, 2}.sum").expect("the use") + "Pair<int, int>{1, 2}.".len();
let hover = cpp_ls::handlers::hover::hover(
&session,
&view,
ReadingOffset::new(at),
FileOffset::new(at),
)
.expect("a member has a card");
let text = match hover.contents {
lsp_types::HoverContents::Markup(markup) => markup.value,
other => format!("{other:?}"),
};
assert!(
!text.contains("has no member"),
"the member is resolved through the instantiation: {text}"
);
assert!(text.contains("sum"), "the card names the member: {text}");
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn a_member_of_an_instance_answers_in_the_instance_terms() {
let root: PathBuf = std::env::temp_dir().join("cppls-hover-instance");
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).expect("a project directory");
let file = root.join("a.cpp");
let source = "template <class V>\n\
struct Box {\n\
std::optional<V> query(int key);\n\
};\n\
struct C {\n\
Box<int> box;\n\
void f() {\n\
auto x = box.query(1);\n\
}\n\
};\n";
std::fs::write(&file, source).expect("the fixture writes");
let mut session = Session::open(
&root,
SessionFiles::new(OpenDocuments::new(), DiskFiles),
WatchFilter::new(&root),
);
session.add_project_files([file.clone()]);
session.did_open(&file, source);
session.catch_up(&file);
let _ = session.cook(&file);
let _ = session.index_everything();
let view = session.view_of_the_file(&file).expect("the written text");
let at = source.find("box.query").expect("the use") + "box.".len();
let hover = cpp_ls::handlers::hover::hover(
&session,
&view,
ReadingOffset::new(at),
FileOffset::new(at),
)
.expect("a member has a card");
let text = match hover.contents {
lsp_types::HoverContents::Markup(markup) => markup.value,
other => format!("{other:?}"),
};
assert!(
text.contains("std::optional<int>"),
"the arguments are substituted into the member's return type: {text}"
);
assert!(
!text.contains("std::optional<V>"),
"and the class's own parameter is not what the reader sees: {text}"
);
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn a_member_declared_twice_shows_both_declarations_and_their_clauses() {
let root: PathBuf = std::env::temp_dir().join("cppls-hover-qualifiers");
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).expect("a project directory");
let file = root.join("a.cpp");
let source = "struct Foo {\n\
template <typename Fn> void call(Fn &&fn) {}\n\
template <typename Fn> void call(Fn &&fn) const {}\n\
};\n\
struct Bar {\n\
template <typename Fn> void call(Fn &&fn) requires requires { fn(*this); } {}\n\
template <typename Fn> void call(Fn &&fn) const requires requires { fn(*this); } {}\n\
};\n\
void f() {\n\
const Foo cfoo;\n\
cfoo.call([](auto &&x) {});\n\
const Bar cbar;\n\
cbar.call([](auto &&x) {});\n\
}\n";
std::fs::write(&file, source).expect("the fixture writes");
let mut session = Session::open(
&root,
SessionFiles::new(OpenDocuments::new(), DiskFiles),
WatchFilter::new(&root),
);
session.add_project_files([file.clone()]);
session.did_open(&file, source);
session.catch_up(&file);
let _ = session.cook(&file);
let _ = session.index_everything();
let view = session.view_of_the_file(&file).expect("the written text");
for (needle, must_say) in [
("cfoo.call", "void call(Fn&& fn) const"),
("cbar.call", "const requires requires"),
] {
let line = source.find(needle).expect("the use");
let at = line + needle.find("call").expect("the member name");
let hover = cpp_ls::handlers::hover::hover(
&session,
&view,
ReadingOffset::new(at + 1),
FileOffset::new(at + 1),
)
.expect("a member has a card");
let text = match hover.contents {
lsp_types::HoverContents::Markup(markup) => markup.value,
other => format!("{other:?}"),
};
assert!(
text.contains(must_say),
"the card must say {must_say:?}: {text}"
);
assert!(
text.contains("const Foo")
|| text.contains("const Bar")
|| text.contains("`const` object"),
"and it must say which one a const object may call: {text}"
);
}
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn a_deduced_local_shows_its_type() {
let root: PathBuf = std::env::temp_dir().join("cppls-hover-deduced");
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).expect("a project directory");
let file = root.join("a.cpp");
let source = "struct LuaSyntaxTree {\n\
bool HasError() const { return false; }\n\
};\n\
struct Holder {\n\
LuaSyntaxTree& value();\n\
};\n\
void f() {\n\
Holder op;\n\
auto &tree = op.value();\n\
bool bad = tree.HasError();\n\
}\n";
std::fs::write(&file, source).expect("the fixture writes");
let mut session = Session::open(
&root,
SessionFiles::new(OpenDocuments::new(), DiskFiles),
WatchFilter::new(&root),
);
session.add_project_files([file.clone()]);
session.did_open(&file, source);
session.catch_up(&file);
let _ = session.cook(&file);
let _ = session.index_everything();
let view = session.view_of_the_file(&file).expect("the written text");
let at = source.find("tree.HasError").expect("the use");
let hover = cpp_ls::handlers::hover::hover(
&session,
&view,
ReadingOffset::new(at),
FileOffset::new(at),
)
.expect("a local has a card");
let text = match hover.contents {
lsp_types::HoverContents::Markup(markup) => markup.value,
other => format!("{other:?}"),
};
assert!(
text.contains("LuaSyntaxTree&"),
"the card shows the deduced type: {text}"
);
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn a_member_through_a_smart_pointers_arrow_is_named() {
let root: PathBuf = std::env::temp_dir().join("cppls-hover-arrow");
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).expect("a project directory");
let file = root.join("a.cpp");
let source = "namespace std {\n\
template <typename T, int Lp>\n\
class __shared_ptr;\n\
template <typename T>\n\
class shared_ptr : public __shared_ptr<T, 2> {\n\
public:\n\
using element_type = T;\n\
shared_ptr() = default;\n\
};\n\
}\n\
struct State {\n\
bool called;\n\
};\n\
std::shared_ptr<State> state_;\n\
void f() {\n\
state_->called = true;\n\
}\n";
std::fs::write(&file, source).expect("the fixture writes");
let mut session = Session::open(
&root,
SessionFiles::new(OpenDocuments::new(), DiskFiles),
WatchFilter::new(&root),
);
session.add_project_files([file.clone()]);
session.did_open(&file, source);
session.catch_up(&file);
let _ = session.cook(&file);
let _ = session.index_everything();
let view = session.view_of_the_file(&file).expect("the written text");
let at = source.find("called = true").expect("the member");
let hover = cpp_ls::handlers::hover::hover(
&session,
&view,
ReadingOffset::new(at),
FileOffset::new(at),
)
.expect("a member has a card");
let text = match hover.contents {
lsp_types::HoverContents::Markup(markup) => markup.value,
other => format!("{other:?}"),
};
assert!(
!text.contains("has no member"),
"the member is reached through the handle: {text}"
);
assert!(
text.contains("State") && text.contains("called"),
"the card says the member belongs to the pointee: {text}"
);
assert!(text.contains("called"), "the card names the member: {text}");
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn a_local_is_not_answered_by_a_same_named_declaration_from_the_index() {
let root: PathBuf = std::env::temp_dir().join("cppls-hover-local");
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).expect("a project directory");
let header = root.join("standard.h");
std::fs::write(
&header,
"namespace std {\nstruct codecvt_base {\n using result = int;\n};\n}\n",
)
.expect("the header writes");
let file = root.join("a.cpp");
let source = "#include \"standard.h\"\nvoid f() {\n auto result = 1;\n}\n";
std::fs::write(&file, source).expect("the fixture writes");
let mut session = Session::open(
&root,
SessionFiles::new(OpenDocuments::new(), DiskFiles),
WatchFilter::new(&root),
);
session.add_project_files([file.clone(), header.clone()]);
session.did_open(&file, source);
session.catch_up(&file);
let _ = session.cook(&file);
let _ = session.index_everything();
let view = session.view_of_the_file(&file).expect("the written text");
let at = source.find("result").expect("the local") + 1;
let text = cpp_ls::handlers::hover::hover_text(
&session,
&view,
ReadingOffset::new(at),
FileOffset::new(at),
)
.expect("the local has a card");
assert!(
!text.contains("codecvt_base"),
"the card is about the local, not about the standard library's `result`: {text}"
);
assert!(
text.contains("a variable"),
"and it says what the local is: {text}"
);
assert!(text.contains("int"), "and what it deduced to: {text}");
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn a_documentation_links_target_has_a_card() {
let root: PathBuf = std::env::temp_dir().join("cppls-hover-doc-link");
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).expect("a project directory");
let file = root.join("a.cpp");
let source = "\
struct Widget {
/// See [`Widget::grow`] for the rule.
void grow(int by);
};
";
std::fs::write(&file, source).expect("the fixture writes");
let mut session = Session::open(
&root,
SessionFiles::new(OpenDocuments::new(), DiskFiles),
WatchFilter::new(&root),
);
session.add_project_files([file.clone()]);
session.did_open(&file, source);
session.catch_up(&file);
let _ = session.cook(&file);
let _ = session.index_everything();
let view = session.view_of_the_file(&file).expect("the written text");
let at = source.find("See [`").expect("the fixture") + "See [`Widget::".len();
let hover = cpp_ls::handlers::hover::hover(
&session,
&view,
ReadingOffset::new(at),
FileOffset::new(at),
)
.expect("a link has a card");
let text = match hover.contents {
lsp_types::HoverContents::Markup(markup) => markup.value,
other => format!("{other:?}"),
};
assert!(
text.contains("void grow(int by)"),
"the card is the declaration the link names: {text}"
);
let _ = std::fs::remove_dir_all(&root);
}