similarity-core 0.5.2

Core library for code similarity detection using AST-based comparison
Documentation
use std::rc::Rc;

use similarity_core::{
    find_function_overlaps, parse_and_convert_to_tree, OverlapOptions, TreeNode,
};

fn contains_value(node: &Rc<TreeNode>, value: &str) -> bool {
    node.value == value || node.children.iter().any(|child| contains_value(child, value))
}

fn contains_label(node: &Rc<TreeNode>, label: &str) -> bool {
    node.label == label || node.children.iter().any(|child| contains_label(child, label))
}

#[test]
fn parse_exported_function_declaration_preserves_function_shape() {
    let code = r#"
export function greet(name) {
    return name + "!";
}
"#;

    let tree = parse_and_convert_to_tree("test.ts", code).unwrap();
    let function = tree.children.first().expect("expected exported function");

    assert_eq!(function.label, "greet");
    assert_eq!(function.value, "FunctionDeclaration");
    assert_eq!(function.children.first().map(|child| child.label.as_str()), Some("name"));
    assert!(contains_value(function, "ReturnStatement"));
}

#[test]
fn parse_export_default_class_preserves_methods() {
    let code = r#"
export default class Greeter {
    greet(name) {
        return `Hello, ${name}`;
    }
}
"#;

    let tree = parse_and_convert_to_tree("test.ts", code).unwrap();
    let class = tree.children.first().expect("expected exported class");

    assert_eq!(class.label, "Greeter");
    assert_eq!(class.value, "ClassDeclaration");
    assert!(contains_label(class, "greet"));
    assert!(contains_value(class, "ReturnStatement"));
}

#[test]
fn parse_exported_variable_arrow_function_preserves_body() {
    let code = r#"
export const loadValue = () => {
    return 42;
};
"#;

    let tree = parse_and_convert_to_tree("test.ts", code).unwrap();
    let declaration = tree.children.first().expect("expected exported variable declaration");
    let variable = declaration.children.first().expect("expected exported variable");

    assert_eq!(declaration.value, "VariableDeclaration");
    assert_eq!(variable.label, "loadValue");
    assert!(contains_value(variable, "ArrowFunctionExpression"));
    assert!(contains_value(variable, "ReturnStatement"));
}

#[test]
fn overlap_detection_handles_exported_functions() {
    let code = r#"
export function validateUser(user) {
    if (!user.email) {
        throw new Error("Email is required");
    }
    if (!user.email.includes("@")) {
        throw new Error("Invalid email format");
    }
    return user.email.toLowerCase();
}

export function validateAdmin(admin) {
    if (!admin.email) {
        throw new Error("Email is required");
    }
    if (!admin.email.includes("@")) {
        throw new Error("Invalid email format");
    }
    return admin.email.toLowerCase();
}
"#;

    let options = OverlapOptions {
        min_window_size: 3,
        max_window_size: 25,
        threshold: 0.5,
        size_tolerance: 0.5,
    };

    let overlaps = find_function_overlaps(code, code, &options).unwrap();

    assert!(
        overlaps.iter().any(|overlap| overlap.similarity > 0.9),
        "expected exported functions to be compared with their real ASTs"
    );
}