Auto stash before rebase of "relpace_lsp_with_rust" onto "origin/relpace_lsp_with_rust"

This commit is contained in:
2025-11-16 20:55:25 +01:00
parent 7a3e3ae9f7
commit f44539ec2f
8 changed files with 220 additions and 1 deletions
+10
View File
@@ -20,6 +20,16 @@ dependencies = [
"serde",
"serde_json",
"tree-sitter",
"tree-sitter-tcl",
]
[[package]]
name = "analysis-tests"
version = "0.1.0"
dependencies = [
"analysis",
"tree-sitter",
"tree-sitter-tcl",
]
[[package]]
+1
View File
@@ -4,6 +4,7 @@ members = [
"crates/text",
"crates/analysis",
"crates/features",
"tests/analysis-tests",
]
resolver = "2"
+1
View File
@@ -7,6 +7,7 @@ edition = "2021"
anyhow = { workspace = true }
lsp-types = { workspace = true }
tree-sitter = { workspace = true }
tree-sitter-tcl = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
+22
View File
@@ -0,0 +1,22 @@
use tree_sitter::Parser;
use analysis::collect_procs;
fn main() {
let src = r#"namespace eval foo {
proc bar {x y} { return $x }
}
proc top {} {}
"#;
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_tcl::LANGUAGE.into())
.expect("load tcl grammar");
let tree = parser.parse(src, None).expect("parse");
println!("tree: {}", tree.root_node().to_sexp());
let procs = collect_procs(&tree, src);
println!("found {} procs via collect_procs", procs.len());
for p in procs {
println!("- {} {:?}", p.name, p.params);
}
}
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "analysis-tests"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
tree-sitter = { workspace = true }
tree-sitter-tcl = { workspace = true }
[dev-dependencies]
analysis = { path = "../../crates/analysis" }
+28
View File
@@ -0,0 +1,28 @@
//! Helpers for workspace-level tests. Provides simple Tree-sitter Tcl utilities
//! so you can inspect the parsed tree output while writing tests.
use tree_sitter::{Parser, Tree};
/// Parse the given Tcl source and return the syntax tree.
pub fn parse_tcl(src: &str) -> Tree {
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_tcl::LANGUAGE.into())
.expect("load tcl grammar");
parser.parse(src, None).expect("parse")
}
/// Return the s-expression (to_sexp) of the root node for quick inspection.
pub fn tcl_to_sexp(src: &str) -> String {
let tree = parse_tcl(src);
tree.root_node().to_sexp()
}
/// Print the s-expression to stdout. Run tests with `-- --nocapture` to see it.
///
/// Example:
/// cargo test -p analysis-tests -- --nocapture
pub fn print_tcl_sexp(src: &str) {
println!("{}", tcl_to_sexp(src));
}
+32
View File
@@ -0,0 +1,32 @@
//! Small CLI to inspect Tree-sitter Tcl output.
//! Examples:
//! cargo run -p analysis-tests -- "proc hello {a} { puts $a }"
//! cargo run -p analysis-tests -- --file path/to/file.tcl
use std::{ env, fs };
fn print_usage() {
eprintln!(
"Usage:\n cargo run -p analysis-tests -- [--file PATH] [SRC]\n\nExamples:\n cargo run -p analysis-tests -- \"proc hello {{a b c {{optArg 0}} {{ puts $a }}\"\n cargo run -p analysis-tests -- --file script.tcl\n"
);
}
fn main() {
let args: Vec<String> = env::args().skip(1).collect();
if args.iter().any(|a| (a == "-h" || a == "--help")) {
print_usage();
return;
}
let src = if args.len() >= 2 && args[0] == "--file" {
fs::read_to_string(&args[1]).expect("read file")
} else if !args.is_empty() {
args.join(" ")
} else {
// Default sample
String::from("proc hello {a} { puts $a }")
};
// Print S-expression of the parsed tree
analysis_tests::print_tcl_sexp(&src);
}
@@ -0,0 +1,113 @@
//! Grammar-focused tests: exercise Tree-sitter Tcl directly.
//! These do not rely on the `analysis` crate — just the parser/AST.
use tree_sitter::{ Node, Parser };
fn parse_tcl(src: &str) -> tree_sitter::Tree {
let mut parser = Parser::new();
parser.set_language(&tree_sitter_tcl::LANGUAGE.into()).expect("load tcl grammar");
parser.parse(src, None).expect("parse")
}
fn text<'a>(node: Node<'a>, src: &'a str) -> &'a str {
node.utf8_text(src.as_bytes()).expect("utf8 text")
}
#[test]
fn procedure_node_and_fields() {
let src = r#"proc hello {a b} { return $a }"#;
let tree = parse_tcl(src);
let root = tree.root_node();
// The top-level child for a proc is a builtin `procedure` node
// (per grammar.js: `procedure: seq('proc', field('name', _word), field('arguments', arguments), field('body', _word))`).
// The source_file is a list of (optional _command, terminator). The first named child
// should be the `_command` node itself; for a `proc` that is `procedure`.
let proc = root.named_child(0).expect("first child");
assert_eq!(proc.kind(), "procedure");
// Fields: name, arguments, body
let name = proc.child_by_field_name("name").expect("name field");
assert_eq!(text(name, src), "hello");
let args = proc.child_by_field_name("arguments").expect("args field");
assert_eq!(args.kind(), "arguments");
// Arguments contain one or more `argument` nodes when braced.
let mut cursor = args.walk();
let mut arg_names = vec![];
for ch in args.named_children(&mut cursor) {
if ch.kind() == "argument" {
if let Some(n) = ch.child_by_field_name("name") {
arg_names.push(text(n, src).to_string());
}
}
}
assert_eq!(arg_names, vec!["a", "b"]);
let body = proc.child_by_field_name("body").expect("body field");
// A braced block body is parsed as `braced_word`.
assert_eq!(body.kind(), "braced_word");
}
#[test]
fn generic_command_and_arguments() {
let src = "puts hello";
let tree = parse_tcl(src);
let root = tree.root_node();
let cmd = root.named_child(0).expect("first child");
assert_eq!(cmd.kind(), "command");
let name = cmd.child_by_field_name("name").expect("name field");
assert_eq!(text(name, src), "puts");
let args = cmd.child_by_field_name("arguments").expect("arguments field (word_list)");
assert_eq!(args.kind(), "word_list");
// `word_list` has word-like children; collect their surface text.
let mut cursor = args.walk();
let words: Vec<_> = args
.named_children(&mut cursor)
.map(|w| text(w, src).to_string())
.collect();
assert_eq!(words, vec!["hello"]);
}
#[test]
fn namespace_eval_contains_nested_proc() {
let src = r#"
namespace eval foo {
proc bar {x y} { return $x }
}
"#;
let tree = parse_tcl(src);
let root = tree.root_node();
let ns = root.named_child(0).expect("first child");
assert_eq!(ns.kind(), "namespace");
// The `namespace`'s `word_list` contains: `eval`, `foo`, and a braced block.
let mut cursor = ns.walk();
let mut braced_block: Option<Node> = None;
for ch in ns.named_children(&mut cursor) {
if ch.kind() == "word_list" {
let mut cur2 = ch.walk();
for w in ch.named_children(&mut cur2) {
if w.kind() == "braced_word" {
braced_block = Some(w);
}
}
}
}
let block = braced_block.expect("braced block in namespace eval");
// Inside the braced block, we expect a `procedure` command for `proc bar ...`.
let mut cur = block.walk();
let mut found_proc = false;
for n in block.named_children(&mut cur) {
if n.kind() == "procedure" {
found_proc = true;
let name = n.child_by_field_name("name").expect("proc name");
assert_eq!(text(name, src), "bar");
}
}
assert!(found_proc, "expected nested procedure inside namespace block");
}