duplicate_code 0.2.0

A tool for parsing directories scanning all the files within to find duplicate segments of code across files.
use crate::model::*;

pub fn get_all_duplicates(files: Files) -> Duplicates {
    let mut duplicates = vec![];

    let number_of_files = files.len();

    for file_index_1 in 0..number_of_files {
        for file_index_2 in (file_index_1 + 1)..number_of_files {
            duplicates.extend(get_duplicates(
                files.get(file_index_1).unwrap(),
                files.get(file_index_2).unwrap(),
            ));
        }
    }

    duplicates
}

const MINIMUM_SUCCESSIVE_LINES: usize = 3;

fn get_duplicates(file_1: &FileContent, file_2: &FileContent) -> Duplicates {
    let mut duplicates = vec![];
    let mut file_1_line_number = 1;
    let file_1_line_number_upper_range = file_1.number_of_lines + 1;
    let file_2_line_number_upper_range = file_2.number_of_lines + 1;

    while file_1_line_number < file_1_line_number_upper_range {
        let file_1_line_number_hash = file_1.line_number_to_hash.get(&file_1_line_number).unwrap();

        if file_2
            .hash_to_line_number
            .contains_key(file_1_line_number_hash)
        {
            let file_1_start = file_1_line_number;
            let file_2_start = *file_2
                .hash_to_line_number
                .get(file_1_line_number_hash)
                .unwrap();
            let mut successive_file_1_line_number = file_1_start + 1;
            let mut successive_file_2_line_number = file_2_start + 1;

            'successive: while (successive_file_1_line_number < file_1_line_number_upper_range)
                && (successive_file_2_line_number < file_2_line_number_upper_range)
            {
                let successive_file_1_line_number_hash = file_1
                    .line_number_to_hash
                    .get(&successive_file_1_line_number)
                    .unwrap();
                let successive_file_2_line_number_hash = file_2
                    .line_number_to_hash
                    .get(&successive_file_2_line_number)
                    .unwrap();

                if successive_file_1_line_number_hash.ne(successive_file_2_line_number_hash) {
                    break 'successive;
                }

                successive_file_1_line_number += 1;
                successive_file_2_line_number += 1;
            }

            if (successive_file_1_line_number - file_1_start) >= MINIMUM_SUCCESSIVE_LINES {
                // We have a successive match of at least MINIMUM_SUCCESSIVE_LINES
                let file_1_end = successive_file_1_line_number - 1;
                let file_1_line_range = LineRange {
                    start: file_1_start,
                    end: file_1_end,
                };
                let file_2_line_range = LineRange {
                    start: file_2_start,
                    end: successive_file_2_line_number - 1,
                };

                let duplicate = Duplicate {
                    file_1: file_1.filename.clone(),
                    file_1_line_range,
                    file_2: file_2.filename.clone(),
                    file_2_line_range,
                };

                duplicates.push(duplicate);

                // Jump over successive patch.
                file_1_line_number = file_1_end;
            }
        }

        file_1_line_number += 1;
    }

    duplicates
}