use std::env;
use std::ffi::OsStr;
use std::path::PathBuf;
use tsgo_client::Api;
use tsgo_client::client::{Client, Options};
use tsgo_client::symbolflags::SymbolFlags;
use serde::Serialize;
fn get_tsgo_path() -> Option<PathBuf> {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let repo_root = manifest_dir
.parent()
.and_then(|p| p.parent())
.expect("Could not find repo root");
let tsgo_output = repo_root.join("target/tsgo");
let cmd_tsgo_dir = repo_root.join("cmd/tsgo");
if cmd_tsgo_dir.exists() {
eprintln!("Building tsgo from cmd/tsgo...");
let status = std::process::Command::new("go")
.args(["build", "-o"])
.arg(&tsgo_output)
.arg("./cmd/tsgo")
.current_dir(repo_root)
.status();
if status.is_ok() && tsgo_output.exists() {
eprintln!("✓ Built tsgo successfully");
return Some(tsgo_output);
}
}
let possible_paths = ["target/tsgo", "bin/tsgo"];
for path in &possible_paths {
let full_path = repo_root.join(path);
if full_path.exists() {
return Some(full_path);
}
}
None
}
fn get_fixtures_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
}
#[test]
fn test_tsgo_integration_simple_project() {
let tsgo_path = get_tsgo_path().expect(
"Could not find tsgo executable. \
Please build tsgo first or ensure it's in your PATH.",
);
let fixture_dir = get_fixtures_dir().join("simple-project");
let config_file = fixture_dir.join("tsconfig.json");
assert!(
fixture_dir.exists(),
"Test fixture directory does not exist: {fixture_dir:?}"
);
assert!(
config_file.exists(),
"tsconfig.json does not exist: {config_file:?}"
);
let options = Options {
cwd: Some(fixture_dir.clone()),
log_file: None,
config_file: config_file.to_string_lossy().to_string(),
};
let uninitialized_client = Client::builder(OsStr::new(&tsgo_path), options)
.build()
.expect("Failed to build client");
let api =
Api::with_uninitialized_client(uninitialized_client).expect("Failed to initialize API");
let mut buffer = Vec::new();
let project = api
.load_project(&mut buffer)
.expect("Failed to load project");
println!("Root files: {:?}", project.root_files);
println!("Number of source files: {}", project.source_files.len());
println!("Number of modules: {}", project.module_list.len());
println!("Number of diagnostics: {}", project.diagnostics.len());
assert!(
!project.source_files.is_empty(),
"Expected at least one source file"
);
assert!(
!project.module_list.is_empty(),
"Expected at least one module"
);
assert!(
!project.semantic.symtab.is_empty(),
"Expected symbols in symbol table"
);
assert!(
!project.semantic.typetab.is_empty(),
"Expected types in type table"
);
assert_ne!(project.semantic.primtypes.string, 0);
assert_ne!(project.semantic.primtypes.number, 0);
assert_ne!(project.semantic.primtypes.any, 0);
println!("\n✓ Integration test passed!");
println!(" - Loaded {} source files", project.source_files.len());
println!(" - Found {} symbols", project.semantic.symtab.len());
println!(" - Found {} types", project.semantic.typetab.len());
}
#[test]
fn test_tsgo_client_builder() {
let tsgo_path = get_tsgo_path().expect("Could not find tsgo executable");
let fixture_dir = get_fixtures_dir().join("simple-project");
let config_file = fixture_dir.join("tsconfig.json");
let options = Options {
cwd: Some(fixture_dir.clone()),
log_file: None,
config_file: config_file.to_string_lossy().to_string(),
};
let client = Client::builder(OsStr::new(&tsgo_path), options)
.log_file("test.log".to_string())
.build();
assert!(client.is_ok(), "Failed to build client with builder");
}
#[test]
fn test_fixture_structure() {
let fixture_dir = get_fixtures_dir().join("simple-project");
assert!(fixture_dir.exists(), "Fixture directory should exist");
let tsconfig = fixture_dir.join("tsconfig.json");
assert!(tsconfig.exists(), "tsconfig.json should exist");
let src_dir = fixture_dir.join("src");
assert!(src_dir.exists(), "src directory should exist");
let index_ts = src_dir.join("index.ts");
assert!(index_ts.exists(), "index.ts should exist");
let utils_ts = src_dir.join("utils.ts");
assert!(utils_ts.exists(), "utils.ts should exist");
let shorthand_ts = src_dir.join("shorthand.ts");
assert!(shorthand_ts.exists(), "shorthand.ts should exist");
}
#[derive(Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)]
struct ShorthandSymbolMapping {
source_node_span: String,
target_symbol_name: String,
target_decl_span: String,
}
#[test]
fn test_get_shorthand_assignment_value_symbol() {
let tsgo_path = get_tsgo_path().expect(
"Could not find tsgo executable. \
Please build tsgo first or ensure it's in your PATH.",
);
let fixture_dir = get_fixtures_dir().join("simple-project");
let config_file = fixture_dir.join("tsconfig.json");
let options = Options {
cwd: Some(fixture_dir.clone()),
log_file: None,
config_file: config_file.to_string_lossy().to_string(),
};
let uninitialized_client = Client::builder(OsStr::new(&tsgo_path), options)
.build()
.expect("Failed to build client");
let api =
Api::with_uninitialized_client(uninitialized_client).expect("Failed to initialize API");
let mut buffer = Vec::new();
let project = api
.load_project(&mut buffer)
.expect("Failed to load project");
let semantic = &project.semantic;
let mut shorthand_mappings = Vec::new();
for (node_ref, _source_symbol_id) in &semantic.node2sym {
if let Some(target_symbol_id) = semantic.get_shorthand_assignment_value_symbol(node_ref) {
if let Some((_, target_symbol_data)) = semantic
.symtab
.iter()
.find(|(id, _)| *id == target_symbol_id)
{
let flags = SymbolFlags::from_bits_truncate(target_symbol_data.flags);
assert!(
flags.intersects(SymbolFlags::VALUE | SymbolFlags::ALIAS),
"Shorthand value symbol should have VALUE or ALIAS flags, got: {flags:?}"
);
let target_symbol_name =
String::from_utf8_lossy(&target_symbol_data.name).to_string();
if [
"name", "age", "username", "userAge", "isActive", "id", "email",
]
.contains(&target_symbol_name.as_str())
{
let source_span = format!(
"{}:{}..{}",
node_ref.sourcefile_id, node_ref.start, node_ref.end
);
let target_decl_span = if let Some(decl) = &target_symbol_data.decl {
format!("{}:{}..{}", decl.sourcefile_id, decl.start, decl.end)
} else {
"unknown".to_string()
};
shorthand_mappings.push(ShorthandSymbolMapping {
source_node_span: source_span,
target_symbol_name: target_symbol_name.clone(),
target_decl_span,
});
}
}
}
}
shorthand_mappings.sort();
println!("\n✓ Shorthand assignment test passed!");
println!(
" - Found {} unique shorthand symbol mappings",
shorthand_mappings.len()
);
println!(
" - Symbols: {:?}",
shorthand_mappings
.iter()
.map(|m| &m.target_symbol_name)
.collect::<Vec<_>>()
);
assert!(
shorthand_mappings.len() >= 7,
"Expected at least 7 shorthand symbols, found {}",
shorthand_mappings.len()
);
insta::assert_json_snapshot!(shorthand_mappings);
}