weavatrix-parse 0.3.4

Dependency-free source tokenizer and structural extractor for repository intelligence
Documentation
use super::*;

#[test]
fn nested_call_arguments_are_not_object_methods() {
    let source = r"function build(makeClient, session) {
  return {
make: () => makeClient({ timeoutMs: Math.max(100, remaining(session)) }),
  }
}
";
    let facts = extract(source, Language::JavaScript);
    let remaining = facts
        .references
        .iter()
        .filter(|reference| reference.name == "remaining" && reference.kind == ReferenceKind::Call)
        .collect::<Vec<_>>();
    assert_eq!(remaining.len(), 1, "got {:?}", facts.references);
    assert_eq!(remaining[0].span.line, 3);
    assert!(
        !facts.declarations.iter().any(|declaration| {
            declaration.name == "remaining" && declaration.kind == DeclarationKind::Method
        }),
        "a nested argument is not an object method: {:?}",
        facts.declarations
    );
}

#[test]
fn default_object_parameter_is_not_the_function_body() {
    let source = r"export function runCommand(command, args = [], options = {}) {
  return spawn(command, args, { env: childProcessEnv(options.env || {}) })
}
";
    let facts = extract(source, Language::JavaScript);
    let declaration = facts
        .declarations
        .iter()
        .find(|declaration| declaration.name == "runCommand")
        .expect("exported function declaration");
    assert_eq!(declaration.kind, DeclarationKind::Function);
    assert!(declaration.exported);
    let environment = facts
        .references
        .iter()
        .find(|reference| reference.name == "childProcessEnv")
        .expect("call inside function body");
    assert_eq!(environment.owner.as_deref(), Some("runCommand"));
}

#[test]
fn call_in_returned_object_property_is_retained() {
    let source = r"function withGraph(graph) {
  const root = mkdtempSync('prefix')
  const graphPath = join(root, 'graph.json')
  return {root, graphPath, graph: loadGraph(graphPath)}
}
";
    let facts = extract(source, Language::JavaScript);
    let load = facts
        .references
        .iter()
        .find(|reference| reference.name == "loadGraph" && reference.kind == ReferenceKind::Call)
        .unwrap_or_else(|| panic!("missing loadGraph, got {facts:?}"));
    assert_eq!(load.owner.as_deref(), Some("withGraph"));
    assert_eq!(load.span.line, 4);
}

#[test]
fn object_method_names_are_not_calls_but_their_bodies_are() {
    let source = r"function wrap(client) {
  return Object.freeze({
fromUri(uri) { return client.normalizer.fromUri(uri) },
kill() { client.kill() },
  })
}
";
    let facts = extract(source, Language::JavaScript);
    let calls = facts
        .references
        .iter()
        .filter(|reference| reference.kind == ReferenceKind::Call)
        .collect::<Vec<_>>();
    assert_eq!(
        calls
            .iter()
            .filter(|reference| reference.name == "fromUri")
            .count(),
        1,
        "only the body call is a reference, got {calls:?}"
    );
    let from_uri = calls
        .iter()
        .find(|reference| reference.name == "fromUri")
        .expect("body call");
    assert_eq!(from_uri.receiver.as_deref(), Some("normalizer"));
    assert_eq!(from_uri.span.line, 3);
    assert!(
        from_uri.span.column > 30,
        "the call must point inside the body, got {:?}",
        from_uri.span
    );
    assert_eq!(
        calls
            .iter()
            .filter(|reference| reference.name == "kill")
            .count(),
        1,
        "only client.kill() is a call, got {calls:?}"
    );
}

#[test]
fn typescript_generic_calls_keep_their_call_fact() {
    let facts = extract(
        "export async function loadUser() { return get<User>('/users/1'); }\n",
        Language::TypeScript,
    );
    let call = facts
        .references
        .iter()
        .find(|reference| reference.name == "get" && reference.kind == ReferenceKind::Call)
        .expect("generic call");
    assert_eq!(call.string_arguments, ["/users/1"]);
}

#[test]
fn calls_inside_object_fields_and_nested_arguments_are_all_retained() {
    let source = "function score(entry, count, total) {\n\
         \x20 return {...entry, hotspotScore: round(Math.sqrt(entry.value))}\n\
         }\n\
         function pair(pairs, count, total) {\n\
         \x20 pairs.push({jaccard: round(count / total), lift: round(Math.max(count, total))})\n\
         }\n";
    let facts = extract(source, Language::JavaScript);
    let calls = facts
        .references
        .iter()
        .filter(|reference| reference.kind == ReferenceKind::Call)
        .map(|reference| (reference.name.as_str(), reference.span.line))
        .collect::<Vec<_>>();
    assert_eq!(
        calls.iter().filter(|(name, _)| *name == "round").count(),
        3,
        "every aliased round call must survive, got {calls:?}"
    );
    for expected in [
        ("round", 2),
        ("sqrt", 2),
        ("push", 5),
        ("round", 5),
        ("max", 5),
    ] {
        assert!(
            calls.contains(&expected),
            "missing {expected:?}; got {calls:?}"
        );
    }
    for false_method in ["round", "sqrt", "max"] {
        assert!(
            !facts.declarations.iter().any(|declaration| {
                declaration.name == false_method && declaration.kind == DeclarationKind::Method
            }),
            "a call used as an object value is not a method declaration"
        );
    }
}