Skip to content

Commit 14adc78

Browse files
committed
Added functionality for rename detection in verbs and props
1 parent 8e15112 commit 14adc78

11 files changed

Lines changed: 715 additions & 1 deletion

vcs-worker/src/object_diff.rs

Lines changed: 179 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::providers::objects::ObjectsProvider;
44
use crate::providers::refs::RefsProvider;
55
use crate::types::{Change, VcsObjectType};
66
use moor_compiler::ObjectDefinition;
7-
use moor_var::{Var, v_map, v_str};
7+
use moor_var::{Var, v_map, v_str, Sequence, Associative};
88
use serde::{Deserialize, Serialize};
99
use std::collections::{HashMap, HashSet};
1010

@@ -431,6 +431,10 @@ pub fn compare_object_definitions_with_meta(
431431
}
432432
}
433433

434+
// Detect verb renames: if a verb was deleted and another was added with the same content,
435+
// it's likely a rename rather than a delete+add
436+
detect_verb_renames(&baseline_verbs, &local_verbs, object_change);
437+
434438
// Compare property definitions
435439
let baseline_props: HashMap<String, &moor_compiler::ObjPropDef> = baseline
436440
.property_definitions
@@ -522,6 +526,180 @@ pub fn compare_object_definitions_with_meta(
522526
}
523527
}
524528
}
529+
530+
// Detect property renames: if a property was deleted and another was added with the same content,
531+
// it's likely a rename rather than a delete+add
532+
detect_property_renames(&baseline_props, &local_props, object_change);
533+
detect_property_override_renames(&baseline_overrides, &local_overrides, object_change);
534+
}
535+
536+
/// Detect verb renames by finding deleted verbs that match added verbs in content
537+
/// Also detects renames when verb names have overlapping aliases (space-delimited)
538+
fn detect_verb_renames(
539+
baseline_verbs: &HashMap<String, &moor_compiler::ObjVerbDef>,
540+
local_verbs: &HashMap<String, &moor_compiler::ObjVerbDef>,
541+
object_change: &mut ObjectChange,
542+
) {
543+
// Find potential renames: for each deleted verb, check if there's a matching added verb
544+
let mut renames_to_apply = Vec::new();
545+
546+
for deleted_name in &object_change.verbs_deleted.clone() {
547+
if let Some(baseline_verb) = baseline_verbs.get(deleted_name) {
548+
// Check if there's an added verb with the same content
549+
for added_name in &object_change.verbs_added.clone() {
550+
if let Some(local_verb) = local_verbs.get(added_name) {
551+
// Check if the verbs have the same content (everything except the name)
552+
if !verbs_differ(baseline_verb, local_verb) {
553+
// Check if the names have any overlapping elements (e.g. "look examine" vs "look inspect")
554+
// This helps confirm it's a rename vs coincidental identical code
555+
let has_overlap = verb_names_overlap(deleted_name, added_name);
556+
557+
// Accept the rename if:
558+
// 1. Names have overlapping aliases, OR
559+
// 2. Names are similar enough (could add Levenshtein distance check here)
560+
// For now, we'll accept any exact content match as a rename
561+
if has_overlap || true {
562+
// This is a rename!
563+
renames_to_apply.push((deleted_name.clone(), added_name.clone()));
564+
break; // Each deleted verb can only match one added verb
565+
}
566+
}
567+
}
568+
}
569+
}
570+
}
571+
572+
// Apply the renames: remove from added/deleted, add to renamed
573+
for (old_name, new_name) in renames_to_apply {
574+
object_change.verbs_deleted.remove(&old_name);
575+
object_change.verbs_added.remove(&new_name);
576+
object_change.verbs_renamed.insert(old_name, new_name);
577+
}
578+
}
579+
580+
/// Check if two verb names have any overlapping elements when split by spaces
581+
/// For example, "look examine" and "look inspect" overlap on "look"
582+
fn verb_names_overlap(name1: &str, name2: &str) -> bool {
583+
// Split both names by spaces and check for any common elements
584+
let names1: HashSet<&str> = name1.split_whitespace().collect();
585+
let names2: HashSet<&str> = name2.split_whitespace().collect();
586+
587+
// If either name has no elements after splitting, fall back to exact comparison
588+
if names1.is_empty() || names2.is_empty() {
589+
return name1 == name2;
590+
}
591+
592+
// Check if there's any intersection
593+
!names1.is_disjoint(&names2)
594+
}
595+
596+
/// Detect property definition renames by finding deleted properties that match added properties in content
597+
/// Skip rename detection for properties with falsy values (empty strings, lists, maps) to avoid false positives
598+
fn detect_property_renames(
599+
baseline_props: &HashMap<String, &moor_compiler::ObjPropDef>,
600+
local_props: &HashMap<String, &moor_compiler::ObjPropDef>,
601+
object_change: &mut ObjectChange,
602+
) {
603+
let mut renames_to_apply = Vec::new();
604+
605+
for deleted_name in &object_change.props_deleted.clone() {
606+
if let Some(baseline_prop) = baseline_props.get(deleted_name) {
607+
// Skip rename detection if the property value is falsy (empty/default)
608+
if let Some(value) = &baseline_prop.value {
609+
if is_property_value_falsy(value) {
610+
continue;
611+
}
612+
}
613+
614+
// Check if there's an added property with the same content
615+
for added_name in &object_change.props_added.clone() {
616+
if let Some(local_prop) = local_props.get(added_name) {
617+
// Skip if the new property value is also falsy
618+
if let Some(value) = &local_prop.value {
619+
if is_property_value_falsy(value) {
620+
continue;
621+
}
622+
}
623+
624+
// Check if the properties have the same content (everything except the name)
625+
if !property_definitions_differ(baseline_prop, local_prop) {
626+
// This is a rename!
627+
renames_to_apply.push((deleted_name.clone(), added_name.clone()));
628+
break;
629+
}
630+
}
631+
}
632+
}
633+
}
634+
635+
// Apply the renames
636+
for (old_name, new_name) in renames_to_apply {
637+
object_change.props_deleted.remove(&old_name);
638+
object_change.props_added.remove(&new_name);
639+
object_change.props_renamed.insert(old_name, new_name);
640+
}
641+
}
642+
643+
/// Detect property override renames by finding deleted overrides that match added overrides in content
644+
/// Skip rename detection for properties with falsy values (empty strings, lists, maps) to avoid false positives
645+
fn detect_property_override_renames(
646+
baseline_overrides: &HashMap<String, &moor_compiler::ObjPropOverride>,
647+
local_overrides: &HashMap<String, &moor_compiler::ObjPropOverride>,
648+
object_change: &mut ObjectChange,
649+
) {
650+
let mut renames_to_apply = Vec::new();
651+
652+
for deleted_name in &object_change.props_deleted.clone() {
653+
if let Some(baseline_override) = baseline_overrides.get(deleted_name) {
654+
// Skip rename detection if the property value is falsy (empty/default)
655+
if let Some(value) = &baseline_override.value {
656+
if is_property_value_falsy(value) {
657+
continue;
658+
}
659+
}
660+
661+
// Check if there's an added property override with the same content
662+
for added_name in &object_change.props_added.clone() {
663+
if let Some(local_override) = local_overrides.get(added_name) {
664+
// Skip if the new property value is also falsy
665+
if let Some(value) = &local_override.value {
666+
if is_property_value_falsy(value) {
667+
continue;
668+
}
669+
}
670+
671+
// Check if the property overrides have the same content (everything except the name)
672+
if !property_overrides_differ(baseline_override, local_override) {
673+
// This is a rename!
674+
renames_to_apply.push((deleted_name.clone(), added_name.clone()));
675+
break;
676+
}
677+
}
678+
}
679+
}
680+
}
681+
682+
// Apply the renames
683+
for (old_name, new_name) in renames_to_apply {
684+
object_change.props_deleted.remove(&old_name);
685+
object_change.props_added.remove(&new_name);
686+
object_change.props_renamed.insert(old_name, new_name);
687+
}
688+
}
689+
690+
/// Check if a property value is "falsy" (empty/default) to avoid false positives in rename detection
691+
fn is_property_value_falsy(value: &moor_var::Var) -> bool {
692+
use moor_var::Variant;
693+
694+
match value.variant() {
695+
Variant::Str(s) => s.is_empty(),
696+
Variant::List(l) => l.is_empty(),
697+
Variant::Map(m) => m.is_empty(),
698+
Variant::Int(i) => *i == 0,
699+
Variant::Float(f) => *f == 0.0,
700+
Variant::None => true,
701+
_ => false,
702+
}
525703
}
526704

527705
/// Check if two verb definitions differ

vcs-worker/tests/operations/object/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//! - delete_tests: Edge case tests for object/delete operation
99
//! - get_tests: Edge case tests for object/get operation
1010
//! - rename_edge_cases_tests: Edge case tests for object/rename operation
11+
//! - verb_property_rename_tests: Tests for verb and property rename detection
1112
1213
mod crud;
1314
mod delete_tests;
@@ -16,3 +17,4 @@ mod lifecycle;
1617
mod list;
1718
mod rename_edge_cases_tests;
1819
mod rename_update_integration;
20+
mod verb_property_rename_tests;

0 commit comments

Comments
 (0)