gobby-code 1.3.3

Fast Rust CLI for Gobby's code index — AST-aware search, symbol navigation, and dependency graph
Documentation
use super::common::{parse_scala, parse_source};

macro_rules! assert_scala_local_import {
    ($parsed:expr, $callee_name:expr, $expected_file:expr) => {{
        let callee_name: &str = $callee_name;
        let call = $parsed
            .calls
            .iter()
            .find(|call| {
                call.callee_target_kind.as_str() == "local_import"
                    && call.callee_name == callee_name
            })
            .unwrap_or_else(|| panic!("missing local_import call for {callee_name}"));
        let candidates = call.local_import_candidate_files();
        assert!(
            candidates.iter().any(|file| file == $expected_file),
            "candidate files {candidates:?} did not contain {}",
            $expected_file
        );
    }};
}

#[test]
fn extracts_scala_symbols_imports_and_calls() {
    let parsed = parse_scala(
        r#"
package app

import scala.concurrent.Future

trait Service {
  def run(): Unit
}

class Runner extends Service {
  def run(): Unit = {
    Future.successful(())
  }
}

object Main {
  def main(): Unit = {
    Runner()
  }
}
"#,
        &[],
    );

    assert!(
        parsed
            .symbols
            .iter()
            .any(|symbol| symbol.name == "Service" && symbol.kind == "type")
    );
    assert!(
        parsed
            .symbols
            .iter()
            .any(|symbol| symbol.name == "Runner" && symbol.kind == "class")
    );
    assert!(
        parsed
            .symbols
            .iter()
            .any(|symbol| symbol.name == "Main" && symbol.kind == "class")
    );
    assert!(
        parsed
            .symbols
            .iter()
            .any(|symbol| symbol.name == "run" && symbol.kind == "method")
    );
    assert!(
        parsed
            .imports
            .iter()
            .any(|import| import.module_name == "scala.concurrent.Future")
    );
    let future_call = parsed
        .calls
        .iter()
        .find(|call| call.callee_name == "successful")
        .expect("Future.successful call");
    assert_eq!(future_call.callee_target_kind.as_str(), "external");
    assert_eq!(
        future_call.callee_external_module.as_deref(),
        Some("scala.concurrent.Future")
    );
}

#[test]
fn resolves_scala_single_and_grouped_local_import_calls() {
    let parsed = parse_scala(
        r#"
package app

import app.helpers.render
import app.widgets.{Widget, buildWidget}

object Main {
  def run(): Unit = {
    render()
    Widget.build()
    new Widget()
    buildWidget()
  }
}
"#,
        &[
            (
                "src/main/scala/helpers/Helpers.scala",
                "package app.helpers\n\ndef render(): Unit = {}\n",
            ),
            (
                "src/main/scala/widgets/Widget.scala",
                "package app.widgets\n\nclass Widget {\n  def build(): Unit = {}\n}\ndef buildWidget(): Widget = new Widget()\n",
            ),
        ],
    );

    assert!(
        parsed
            .imports
            .iter()
            .any(|import| import.module_name == "app.helpers.render")
    );
    assert!(
        parsed
            .imports
            .iter()
            .any(|import| import.module_name == "app.widgets.Widget")
    );
    assert!(
        parsed
            .imports
            .iter()
            .any(|import| import.module_name == "app.widgets.buildWidget")
    );
    assert_scala_local_import!(&parsed, "render", "src/main/scala/helpers/Helpers.scala");
    assert_scala_local_import!(&parsed, "build", "src/main/scala/widgets/Widget.scala");
    assert_scala_local_import!(&parsed, "Widget", "src/main/scala/widgets/Widget.scala");
    assert_scala_local_import!(
        &parsed,
        "buildWidget",
        "src/main/scala/widgets/Widget.scala"
    );
}

#[test]
fn resolves_aliased_scala_imports_without_phantom_wildcard_edges() {
    let parsed = parse_scala(
        r#"
package app

import app.widgets.{Widget => W, buildWidget as makeWidget, _}

object Main {
  def run(): Unit = {
    W.build()
    new W()
    makeWidget()
    wildcardOnly()
  }
}
"#,
        &[(
            "src/main/scala/widgets/Widget.scala",
            "package app.widgets\n\nclass Widget {\n  def build(): Unit = {}\n}\ndef buildWidget(): Widget = new Widget()\ndef wildcardOnly(): Unit = {}\n",
        )],
    );

    assert_scala_local_import!(&parsed, "build", "src/main/scala/widgets/Widget.scala");
    assert_scala_local_import!(&parsed, "Widget", "src/main/scala/widgets/Widget.scala");
    assert_scala_local_import!(
        &parsed,
        "buildWidget",
        "src/main/scala/widgets/Widget.scala"
    );
    assert!(
        parsed.calls.iter().any(|call| {
            call.callee_name == "wildcardOnly" && call.callee_target_kind.as_str() == "unresolved"
        }),
        "wildcard-only import must not create a local_import edge: {:?}",
        parsed.calls
    );
}

#[test]
fn detects_scala_script_extension() {
    let parsed = parse_source(
        "scripts/build.sc",
        r#"
def compile(): Unit = println("compile")
compile()
"#,
        &[],
    );

    assert!(
        parsed
            .symbols
            .iter()
            .any(|symbol| symbol.name == "compile" && symbol.kind == "function")
    );
    assert!(
        parsed
            .calls
            .iter()
            .any(|call| call.callee_name == "compile")
    );
}