Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ast/src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ impl Repo {
Ok(())
}
fn start_lsp(root: &str, lang: &Lang, lsp: bool) -> Result<Option<CmdSender>> {
Ok(if lsp {
Ok(if lsp && lang.kind.has_lsp_support() {
let (tx, rx) = tokio::sync::mpsc::channel(10000);
spawn_analyzer(&root.into(), &lang.kind, rx)?;
Some(tx)
Expand Down
70 changes: 55 additions & 15 deletions ast/src/testing/annotations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,19 +108,34 @@ fn parse_meta_filter(s: &str) -> BTreeMap<String, String> {
map
}

fn strip_mode_annotation<'a>(
trimmed: &'a str,
prefix: &str,
keyword: &str,
use_lsp: bool,
) -> Option<&'a str> {
let common_prefix = format!("{}{}: ", prefix, keyword);
if let Some(rest) = trimmed.strip_prefix(common_prefix.as_str()) {
return Some(rest);
}

let mode = if use_lsp { "lsp" } else { "no_lsp" };
let mode_prefix = format!("{}{}_{}: ", prefix, keyword, mode);
trimmed.strip_prefix(mode_prefix.as_str())
}

#[derive(Debug, Clone)]
struct AbsentAnnotation {
node_type: NodeType,
name: String,
file_suffix: String,
}

fn parse_absent_annotations(source: &str, prefix: &str) -> Vec<AbsentAnnotation> {
let absent_prefix = format!("{}absent: ", prefix);
fn parse_absent_annotations(source: &str, prefix: &str, use_lsp: bool) -> Vec<AbsentAnnotation> {
let mut result = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix(absent_prefix.as_str()) {
if let Some(rest) = strip_mode_annotation(trimmed, prefix, "absent", use_lsp) {
let toks = parse_quoted_tokens(rest);
if toks.len() >= 3 {
if let Some(nt) = parse_node_type(&toks[0]) {
Expand All @@ -139,15 +154,16 @@ fn parse_absent_annotations(source: &str, prefix: &str) -> Vec<AbsentAnnotation>
fn parse_file_annotations(
source: &str,
prefix: &str,
use_lsp: bool,
) -> Vec<(NodeType, String, BTreeMap<String, String>, Vec<EdgeAnnotation>)> {
let mut result = Vec::new();
let mut current: Option<(NodeType, String, BTreeMap<String, String>, Vec<EdgeAnnotation>)> =
None;

for line in source.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix(prefix) {
if let Some(node_rest) = rest.strip_prefix("node: ") {
if trimmed.strip_prefix(prefix).is_some() {
if let Some(node_rest) = strip_mode_annotation(trimmed, prefix, "node", use_lsp) {
if let Some(prev) = current.take() {
result.push(prev);
}
Expand All @@ -158,7 +174,7 @@ fn parse_file_annotations(
current = Some((nt, toks[1].clone(), subject_meta, Vec::new()));
}
}
} else if let Some(edge_rest) = rest.strip_prefix("edge: ") {
} else if let Some(edge_rest) = strip_mode_annotation(trimmed, prefix, "edge", use_lsp) {
if let Some((_, _, _, ref mut edges)) = current {
let toks = parse_quoted_tokens(edge_rest);
if toks.len() >= 5 {
Expand Down Expand Up @@ -199,9 +215,9 @@ fn annotation_prefix_for_ext(ext: &str, default: &'static str) -> &'static str {
}
}

pub fn verify_file(source: &str, file_suffix: &str, graph: &impl Graph, prefix: &str) -> (Vec<String>, BTreeMap<NodeType, usize>) {
let groups = parse_file_annotations(source, prefix);
let absent = parse_absent_annotations(source, prefix);
pub fn verify_file(source: &str, file_suffix: &str, graph: &impl Graph, prefix: &str, use_lsp: bool) -> (Vec<String>, BTreeMap<NodeType, usize>) {
let groups = parse_file_annotations(source, prefix, use_lsp);
let absent = parse_absent_annotations(source, prefix, use_lsp);
let mut failures: Vec<String> = Vec::new();
let mut counts: BTreeMap<NodeType, usize> = BTreeMap::new();

Expand Down Expand Up @@ -296,17 +312,31 @@ pub fn verify_file(source: &str, file_suffix: &str, graph: &impl Graph, prefix:
(failures, counts)
}

pub fn walk_and_verify(fixture_dir: &Path, root: &Path, graph: &impl Graph, lang: &Language) -> Vec<String> {
fn node_file_is_in_fixture(file: &str, fixture_prefix: &str) -> bool {
let file = file.replace('\\', "/");
file == fixture_prefix
|| file.starts_with(&format!("{}/", fixture_prefix))
|| file.ends_with(&format!("/{}", fixture_prefix))
|| file.contains(&format!("/{}/", fixture_prefix))
}

pub fn walk_and_verify(fixture_dir: &Path, root: &Path, graph: &impl Graph, lang: &Language, use_lsp: bool) -> Vec<String> {
let mut failures = Vec::new();
let mut counts: BTreeMap<NodeType, usize> = BTreeMap::new();
let exts: Vec<&str> = lang.exts();
let skip_dirs: Vec<&str> = lang.skip_dirs();
walk_impl(fixture_dir, root, graph, &mut failures, &mut counts, &exts, &skip_dirs, lang);
let fixture_prefix = fixture_dir
.strip_prefix(root)
.unwrap_or(fixture_dir)
.to_string_lossy()
.replace('\\', "/");
walk_impl(fixture_dir, root, graph, &mut failures, &mut counts, &exts, &skip_dirs, lang, use_lsp);
for (node_type, expected) in &counts {
let actual = graph
.find_nodes_by_type(node_type.clone())
.iter()
.filter(|n| !n.name.contains('\n'))
.filter(|n| node_file_is_in_fixture(&n.file, &fixture_prefix))
.count();
if actual != *expected {
failures.push(format!(
Expand All @@ -327,6 +357,7 @@ fn walk_impl(
exts: &[&str],
skip_dirs: &[&str],
lang: &Language,
use_lsp: bool,
) {
let Ok(read) = std::fs::read_dir(dir) else {
return;
Expand All @@ -341,7 +372,7 @@ fn walk_impl(
if skip_dirs.contains(&dir_name) {
continue;
}
walk_impl(&path, root, graph, failures, counts, exts, skip_dirs, lang);
walk_impl(&path, root, graph, failures, counts, exts, skip_dirs, lang, use_lsp);
} else {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
if !exts.contains(&ext) {
Expand All @@ -358,7 +389,7 @@ fn walk_impl(
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| path.to_string_lossy().to_string());
let file_prefix = annotation_prefix_for_ext(ext, lang.annotation_prefix());
let (file_failures, file_counts) = verify_file(&src, &suffix, graph, file_prefix);
let (file_failures, file_counts) = verify_file(&src, &suffix, graph, file_prefix, use_lsp);
failures.extend(file_failures);
for (nt, n) in file_counts {
*counts.entry(nt).or_insert(0) += n;
Expand All @@ -371,11 +402,20 @@ pub async fn run_fixture_test<G: Graph + Sync>(
subdir: &str,
lang: &str,
annotation_lang: Language,
) -> Result<()> {
run_fixture_test_with_lsp::<G>(subdir, lang, annotation_lang, false).await
}

pub async fn run_fixture_test_with_lsp<G: Graph + Sync>(
subdir: &str,
lang: &str,
annotation_lang: Language,
use_lsp: bool,
) -> Result<()> {
let repo = Repo::new(
subdir,
Lang::from_str(lang).unwrap(),
false,
use_lsp,
Vec::new(),
Vec::new(),
)
Expand All @@ -385,7 +425,7 @@ pub async fn run_fixture_test<G: Graph + Sync>(
graph.analysis();
let fixture_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join(subdir);
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let failures = walk_and_verify(&fixture_dir, root, &graph, &annotation_lang);
let failures = walk_and_verify(&fixture_dir, root, &graph, &annotation_lang, use_lsp);
if !failures.is_empty() {
for f in &failures {
eprintln!("{}", f);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package com.kotlintestapp
// @ast node: Function "PersonItem"
// @ast node: Function "PersonList"
// @ast edge: Calls -> Function "PersonItem" "MainActivity.kt"
// @ast edge_lsp: Calls -> Function "updatePerson" "PersonViewModel.kt"
// @ast node: Function "UpdateProfileDialog"
// @ast node: Import "import-imports-srctestingkotlinappsrcmainjavacomkotlintestappmainactivitykt-0"

Expand Down Expand Up @@ -150,4 +151,3 @@ fun UpdateProfileDialog(
}
)
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package com.kotlintestapp.ui.viewmodels
// @ast edge: Operand -> Function "onUserClicked" "HomeViewModel.kt"
// @ast node: DataModel "HomeViewModel"
// @ast node: Function "fetchUsers"
// @ast edge_lsp: Calls -> Function "getUsers" "UserRepository.kt"
// @ast node: Function "onUserClicked"
// @ast node: Import "import-imports-srctestingkotlinappsrcmainjavacomkotlintestappuiviewmodelshomeviewmodelkt-0"

Expand Down
35 changes: 34 additions & 1 deletion ast/src/testing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::str::FromStr;
pub mod annotations;
pub mod bash_toml;

use annotations::run_fixture_test;
use annotations::{run_fixture_test, run_fixture_test_with_lsp};

#[cfg(test)]
pub mod builder;
Expand Down Expand Up @@ -220,6 +220,39 @@ async fn test_ruby() {
}
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_kotlin() {
let use_lsp = Language::Kotlin.default_do_lsp();

#[cfg(not(feature = "neo4j"))]
{
run_fixture_test_with_lsp::<ArrayGraph>(
"src/testing/kotlin",
"kotlin",
Language::Kotlin,
use_lsp,
).await.unwrap();
run_fixture_test_with_lsp::<BTreeMapGraph>(
"src/testing/kotlin",
"kotlin",
Language::Kotlin,
use_lsp,
).await.unwrap();
}
#[cfg(feature = "neo4j")]
{
use crate::{lang::graphs::Neo4jGraph, testing::annotations::run_fixture_test_with_lsp};
let graph = Neo4jGraph::default();
graph.clear().await.unwrap();
run_fixture_test_with_lsp::<Neo4jGraph>(
"src/testing/kotlin",
"kotlin",
Language::Kotlin,
use_lsp,
).await.unwrap();
}
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_c() {
#[cfg(not(feature = "neo4j"))]
Expand Down
5 changes: 4 additions & 1 deletion lsp/src/language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ impl Language {
pub fn default_do_lsp(&self) -> bool {
if let Ok(use_lsp) = std::env::var("USE_LSP") {
if use_lsp == "true" || use_lsp == "1" {
return matches!(self, Self::Rust | Self::Go | Self::Typescript | Self::Java);
return matches!(self, Self::Rust | Self::Go | Self::Typescript | Self::Java | Self::Kotlin);
}
}
false
Expand Down Expand Up @@ -445,6 +445,7 @@ mod tests {
assert!(Language::Go.default_do_lsp());
assert!(Language::Typescript.default_do_lsp());
assert!(Language::Java.default_do_lsp());
assert!(Language::Kotlin.default_do_lsp());
assert!(!Language::Python.default_do_lsp());
assert!(!Language::Ruby.default_do_lsp());
std::env::remove_var("USE_LSP");
Expand All @@ -457,6 +458,7 @@ mod tests {
assert!(Language::Go.default_do_lsp());
assert!(Language::Typescript.default_do_lsp());
assert!(Language::Java.default_do_lsp());
assert!(Language::Kotlin.default_do_lsp());
assert!(!Language::Python.default_do_lsp());
std::env::remove_var("USE_LSP");
}
Expand All @@ -468,6 +470,7 @@ mod tests {
assert!(!Language::Go.default_do_lsp());
assert!(!Language::Typescript.default_do_lsp());
assert!(!Language::Java.default_do_lsp());
assert!(!Language::Kotlin.default_do_lsp());
assert!(!Language::Python.default_do_lsp());
}
}
Loading