Run indentation tests on a part of the Helix source code.
Add C++ indent test file.
This commit is contained in:
parent
155cedc5c8
commit
b315901cbb
4 changed files with 211 additions and 23 deletions
48
helix-core/tests/data/indent/cpp.cpp
Normal file
48
helix-core/tests/data/indent/cpp.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
std::vector<std::string>
|
||||
fn_with_many_parameters(int parm1, long parm2, float parm3, double parm4,
|
||||
char* parm5, bool parm6);
|
||||
|
||||
std::vector<std::string>
|
||||
fn_with_many_parameters(int parm1, long parm2, float parm3, double parm4,
|
||||
char* parm5, bool parm6) {
|
||||
auto lambda = []() {
|
||||
return 0;
|
||||
};
|
||||
auto lambda_with_a_really_long_name_that_uses_a_whole_line
|
||||
= [](int some_more_aligned_parameters,
|
||||
std::string parm2) {
|
||||
do_smth();
|
||||
};
|
||||
if (brace_on_same_line) {
|
||||
do_smth();
|
||||
} else if (brace_on_next_line)
|
||||
{
|
||||
do_smth();
|
||||
} else if (another_condition) {
|
||||
do_smth();
|
||||
}
|
||||
else {
|
||||
do_smth();
|
||||
}
|
||||
if (inline_if_statement)
|
||||
do_smth();
|
||||
if (another_inline_if_statement)
|
||||
return [](int parm1, char* parm2) {
|
||||
this_is_a_really_pointless_lambda();
|
||||
};
|
||||
|
||||
switch (var) {
|
||||
case true:
|
||||
return -1;
|
||||
case false:
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
|
||||
class MyClass : public MyBaseClass {
|
||||
public:
|
||||
MyClass();
|
||||
void public_fn();
|
||||
private:
|
||||
super_secret_private_fn();
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
../../../src/indent.rs
|
|
@ -11,3 +11,16 @@ indent = { tab-width = 4, unit = " " }
|
|||
[[grammar]]
|
||||
name = "rust"
|
||||
source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "0431a2c60828731f27491ee9fdefe25e250ce9c9" }
|
||||
|
||||
[[language]]
|
||||
name = "cpp"
|
||||
scope = "source.cpp"
|
||||
injection-regex = "cpp"
|
||||
file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino", "C", "H"]
|
||||
roots = []
|
||||
comment-token = "//"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
||||
[[grammar]]
|
||||
name = "cpp"
|
||||
source = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "2d2c4aee8672af4c7c8edff68e7dd4c07e88d2b1" }
|
||||
|
|
|
@ -1,20 +1,122 @@
|
|||
use helix_core::{
|
||||
indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
|
||||
syntax::Loader,
|
||||
syntax::{Configuration, Loader},
|
||||
Syntax,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use ropey::Rope;
|
||||
use std::{ops::Range, path::PathBuf, process::Command};
|
||||
|
||||
#[test]
|
||||
fn test_treesitter_indent_rust() {
|
||||
test_treesitter_indent("rust.rs", "source.rust");
|
||||
standard_treesitter_test("rust.rs", "source.rust");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_treesitter_indent_rust_2() {
|
||||
test_treesitter_indent("indent.rs", "source.rust");
|
||||
// TODO Use commands.rs as indentation test.
|
||||
// Currently this fails because we can't align the parameters of a closure yet
|
||||
// test_treesitter_indent("commands.rs", "source.rust");
|
||||
fn test_treesitter_indent_cpp() {
|
||||
standard_treesitter_test("cpp.cpp", "source.cpp");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_treesitter_indent_rust_helix() {
|
||||
// We pin a specific git revision to prevent unrelated changes from causing the indent tests to fail.
|
||||
// Ideally, someone updates this once in a while and fixes any errors that occur.
|
||||
let rev = "af382768cdaf89ff547dbd8f644a1bddd90e7c8f";
|
||||
let files = Command::new("git")
|
||||
.args([
|
||||
"ls-tree",
|
||||
"-r",
|
||||
"--name-only",
|
||||
"--full-tree",
|
||||
rev,
|
||||
"helix-term/src",
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
let files = String::from_utf8(files.stdout).unwrap();
|
||||
|
||||
let ignored_files = vec![
|
||||
// Contains many macros that tree-sitter does not parse in a meaningful way and is otherwise not very interesting
|
||||
"helix-term/src/health.rs",
|
||||
];
|
||||
|
||||
for file in files.split_whitespace() {
|
||||
if ignored_files.contains(&file) {
|
||||
continue;
|
||||
}
|
||||
let ignored_lines: Vec<Range<usize>> = match file {
|
||||
"helix-term/src/application.rs" => vec![
|
||||
// We can't handle complicated indent rules inside macros (`json!` in this case) since
|
||||
// the tree-sitter grammar only parses them as `token_tree` and `identifier` nodes.
|
||||
1045..1051,
|
||||
],
|
||||
"helix-term/src/commands.rs" => vec![
|
||||
// This is broken because of the current handling of `call_expression`
|
||||
// (i.e. having an indent query for it but outdenting again in specific cases).
|
||||
// The indent query is needed to correctly handle multi-line arguments in function calls
|
||||
// inside indented `field_expression` nodes (which occurs fairly often).
|
||||
//
|
||||
// Once we have the `@indent.always` capture type, it might be possible to just have an indent
|
||||
// capture for the `arguments` field of a call expression. That could enable us to correctly
|
||||
// handle this.
|
||||
2226..2230,
|
||||
],
|
||||
"helix-term/src/commands/dap.rs" => vec![
|
||||
// Complex `format!` macro
|
||||
46..52,
|
||||
],
|
||||
"helix-term/src/commands/lsp.rs" => vec![
|
||||
// Macro
|
||||
624..627,
|
||||
// Return type declaration of a closure. `cargo fmt` adds an additional space here,
|
||||
// which we cannot (yet) model with our indent queries.
|
||||
878..879,
|
||||
// Same as in `helix-term/src/commands.rs`
|
||||
1335..1343,
|
||||
],
|
||||
"helix-term/src/config.rs" => vec![
|
||||
// Multiline string
|
||||
146..152,
|
||||
],
|
||||
"helix-term/src/keymap.rs" => vec![
|
||||
// Complex macro (see above)
|
||||
456..470,
|
||||
// Multiline string without indent
|
||||
563..567,
|
||||
],
|
||||
"helix-term/src/main.rs" => vec![
|
||||
// Multiline string
|
||||
44..70,
|
||||
],
|
||||
"helix-term/src/ui/completion.rs" => vec![
|
||||
// Macro
|
||||
218..232,
|
||||
],
|
||||
"helix-term/src/ui/editor.rs" => vec![
|
||||
// The chained function calls here are not indented, probably because of the comment
|
||||
// in between. Since `cargo fmt` doesn't even attempt to format it, there's probably
|
||||
// no point in trying to indent this correctly.
|
||||
342..350,
|
||||
],
|
||||
"helix-term/src/ui/lsp.rs" => vec![
|
||||
// Macro
|
||||
56..61,
|
||||
],
|
||||
"helix-term/src/ui/statusline.rs" => vec![
|
||||
// Same as in `helix-term/src/commands.rs`
|
||||
436..442,
|
||||
450..456,
|
||||
],
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
let git_object = rev.to_string() + ":" + file;
|
||||
let content = Command::new("git")
|
||||
.args(["cat-file", "blob", &git_object])
|
||||
.output()
|
||||
.unwrap();
|
||||
let doc = Rope::from_reader(&mut content.stdout.as_slice()).unwrap();
|
||||
test_treesitter_indent(file, doc, "source.rust", ignored_lines);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -50,20 +152,41 @@ fn test_indent_level_for_line_with_spaces_and_tabs() {
|
|||
assert_eq!(indent_level, 2)
|
||||
}
|
||||
|
||||
fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
|
||||
fn indent_tests_dir() -> PathBuf {
|
||||
let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
test_dir.push("tests/data/indent");
|
||||
test_dir
|
||||
}
|
||||
|
||||
let mut test_file = test_dir.clone();
|
||||
test_file.push(file_name);
|
||||
let test_file = std::fs::File::open(test_file).unwrap();
|
||||
fn indent_test_path(name: &str) -> PathBuf {
|
||||
let mut path = indent_tests_dir();
|
||||
path.push(name);
|
||||
path
|
||||
}
|
||||
|
||||
fn indent_tests_config() -> Configuration {
|
||||
let mut config_path = indent_tests_dir();
|
||||
config_path.push("languages.toml");
|
||||
let config = std::fs::read_to_string(config_path).unwrap();
|
||||
toml::from_str(&config).unwrap()
|
||||
}
|
||||
|
||||
fn standard_treesitter_test(file_name: &str, lang_scope: &str) {
|
||||
let test_path = indent_test_path(file_name);
|
||||
let test_file = std::fs::File::open(test_path).unwrap();
|
||||
let doc = ropey::Rope::from_reader(test_file).unwrap();
|
||||
test_treesitter_indent(file_name, doc, lang_scope, Vec::new())
|
||||
}
|
||||
|
||||
let mut config_file = test_dir;
|
||||
config_file.push("languages.toml");
|
||||
let config = std::fs::read_to_string(config_file).unwrap();
|
||||
let config = toml::from_str(&config).unwrap();
|
||||
let loader = Loader::new(config);
|
||||
/// Test that all the lines in the given file are indented as expected.
|
||||
/// ignored_lines is a list of (1-indexed) line ranges that are excluded from this test.
|
||||
fn test_treesitter_indent(
|
||||
test_name: &str,
|
||||
doc: Rope,
|
||||
lang_scope: &str,
|
||||
ignored_lines: Vec<std::ops::Range<usize>>,
|
||||
) {
|
||||
let loader = Loader::new(indent_tests_config());
|
||||
|
||||
// set runtime path so we can find the queries
|
||||
let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
|
@ -71,6 +194,7 @@ fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
|
|||
std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
|
||||
|
||||
let language_config = loader.language_config_for_scope(lang_scope).unwrap();
|
||||
let indent_style = IndentStyle::from_str(&language_config.indent.as_ref().unwrap().unit);
|
||||
let highlight_config = language_config.highlight_config(&[]).unwrap();
|
||||
let text = doc.slice(..);
|
||||
let syntax = Syntax::new(text, highlight_config, std::sync::Arc::new(loader)).unwrap();
|
||||
|
@ -78,14 +202,17 @@ fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
|
|||
|
||||
for i in 0..doc.len_lines() {
|
||||
let line = text.line(i);
|
||||
if ignored_lines.iter().any(|range| range.contains(&(i + 1))) {
|
||||
continue;
|
||||
}
|
||||
if let Some(pos) = helix_core::find_first_non_whitespace_char(line) {
|
||||
let tab_and_indent_width: usize = 4;
|
||||
let tab_width: usize = 4;
|
||||
let suggested_indent = treesitter_indent_for_pos(
|
||||
indent_query,
|
||||
&syntax,
|
||||
&IndentStyle::Spaces(tab_and_indent_width as u8),
|
||||
tab_and_indent_width,
|
||||
tab_and_indent_width,
|
||||
&indent_style,
|
||||
tab_width,
|
||||
indent_style.indent_width(tab_width),
|
||||
text,
|
||||
i,
|
||||
text.line_to_char(i) + pos,
|
||||
|
@ -94,7 +221,8 @@ fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
|
|||
.unwrap();
|
||||
assert!(
|
||||
line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
|
||||
"Wrong indentation on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
|
||||
"Wrong indentation for file {:?} on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
|
||||
test_name,
|
||||
i+1,
|
||||
line.slice(..line.len_chars()-1),
|
||||
suggested_indent,
|
||||
|
|
Loading…
Reference in a new issue