Skip to content

Commit

Permalink
Run indentation tests on a part of the Helix source code.
Browse files Browse the repository at this point in the history
  • Loading branch information
Triton171 committed Aug 4, 2023
1 parent 8b77648 commit 523e66c
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 17 deletions.
159 changes: 142 additions & 17 deletions helix-core/tests/indent.rs
Original file line number Diff line number Diff line change
@@ -1,22 +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("commands.rs", "source.rust");
fn test_treesitter_indent_cpp() {
standard_treesitter_test("cpp.cpp", "source.cpp");
}

#[test]
fn test_treesitter_indent_cpp() {
test_treesitter_indent("cpp.cpp", "source.cpp");
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 = "09f02a6598bfb0da71bd2273e83e2f06f025bd77";
// let files = ["helix-term/src/commands.rs"];
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..2229,
],
"helix-term/src/commands/dap.rs" => vec![
// Complex `format!` macro
46..52,
],
"helix-term/src/commands/lsp.rs" => vec![
// Macro
657..660,
// Return type declaration of a closure. `cargo fmt` adds an additional space here,
// which we cannot (yet) model with our indent queries.
911..912,
// Same as in `helix-term/src/commands.rs`
1368..1376,
],
"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.
339..347,
],
"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`
435..441,
],
_ => 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]
Expand Down Expand Up @@ -52,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"));
Expand All @@ -81,6 +202,9 @@ 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_width: usize = 4;
let suggested_indent = treesitter_indent_for_pos(
Expand All @@ -97,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,
Expand Down
9 changes: 9 additions & 0 deletions runtime/queries/rust/indents.scm
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@
(#not-same-line? @indent @expr-start)
(#set! "scope" "all")
)
; Indent type aliases that span multiple lines, similar to
; regular assignment expressions
(type_item
.
(_) @expr-start
type: (_) @indent
(#not-same-line? @indent @expr-start)
(#set! "scope" "all")
)

; Some field expressions where the left part is a multiline expression are not
; indented by cargo fmt.
Expand Down

0 comments on commit 523e66c

Please sign in to comment.