fix(template-engine): resolve TemplateEngine-015,016 — cascade-rename nested derived templates, correct composed-script ParentPath
This commit is contained in:
@@ -714,7 +714,7 @@ public class FlatteningService
|
||||
|
||||
foreach (var composition in compositions)
|
||||
ResolveComposedScriptsRecursive(
|
||||
composition, composition.InstanceName,
|
||||
composition, composition.InstanceName, parentPath: "",
|
||||
compositionMap, composedTemplateChains, scripts, scriptCanonicalById,
|
||||
new HashSet<int>());
|
||||
}
|
||||
@@ -723,11 +723,17 @@ public class FlatteningService
|
||||
/// <summary>
|
||||
/// Recursively resolves the scripts of a composed module and every module
|
||||
/// nested inside it, path-qualifying each canonical name with the
|
||||
/// accumulated <paramref name="prefix"/>.
|
||||
/// accumulated <paramref name="prefix"/>. <paramref name="parentPath"/> is
|
||||
/// the path of the enclosing module — empty for a depth-1 composition
|
||||
/// (parent is the root template) and the enclosing module's
|
||||
/// <c>prefix</c> for deeper nesting — and is carried into each script's
|
||||
/// <see cref="ScriptScope"/> so a nested script's <c>Parent.X</c>
|
||||
/// resolves against its real parent module.
|
||||
/// </summary>
|
||||
private static void ResolveComposedScriptsRecursive(
|
||||
TemplateComposition composition,
|
||||
string prefix,
|
||||
string parentPath,
|
||||
IReadOnlyDictionary<int, IReadOnlyList<TemplateComposition>> compositionMap,
|
||||
IReadOnlyDictionary<int, IReadOnlyList<Template>> composedTemplateChains,
|
||||
Dictionary<string, ResolvedScript> scripts,
|
||||
@@ -747,7 +753,7 @@ public class FlatteningService
|
||||
{
|
||||
CanonicalName = canonicalName,
|
||||
Source = "Composed",
|
||||
Scope = new Commons.Types.Scripts.ScriptScope(SelfPath: prefix, ParentPath: "")
|
||||
Scope = new Commons.Types.Scripts.ScriptScope(SelfPath: prefix, ParentPath: parentPath)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -762,7 +768,7 @@ public class FlatteningService
|
||||
|
||||
foreach (var nested in nestedCompositions)
|
||||
ResolveComposedScriptsRecursive(
|
||||
nested, $"{prefix}.{nested.InstanceName}",
|
||||
nested, $"{prefix}.{nested.InstanceName}", parentPath: prefix,
|
||||
compositionMap, composedTemplateChains, scripts, scriptCanonicalById, visited);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,12 +705,28 @@ public class TemplateService
|
||||
{
|
||||
var newDerivedName = $"{owner.Name}.{newInstanceName}";
|
||||
var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
|
||||
if (allTemplates.Any(t => t.Id != derived.Id && t.Name == newDerivedName))
|
||||
return Result<TemplateComposition>.Failure(
|
||||
$"Cannot rename derived template to '{newDerivedName}': a template with that name already exists.");
|
||||
|
||||
derived.Name = newDerivedName;
|
||||
await _repository.UpdateTemplateAsync(derived, cancellationToken);
|
||||
// The cascade of derived templates created by AddComposition follows a
|
||||
// dotted path (Pump.TempSensor and the nested Pump.TempSensor.Probe1).
|
||||
// Renaming the slot must rename every derived template in that cascade
|
||||
// so the dotted-path naming invariant holds — pre-check every new name
|
||||
// the cascade will introduce before any row mutates.
|
||||
var renames = new List<(Template Template, string NewName)>();
|
||||
await CollectCascadeRenamesAsync(derived, newDerivedName, renames, cancellationToken);
|
||||
|
||||
var renamedIds = renames.Select(r => r.Template.Id).ToHashSet();
|
||||
foreach (var (_, newName) in renames)
|
||||
{
|
||||
if (allTemplates.Any(t => !renamedIds.Contains(t.Id) && t.Name == newName))
|
||||
return Result<TemplateComposition>.Failure(
|
||||
$"Cannot rename derived template to '{newName}': a template with that name already exists.");
|
||||
}
|
||||
|
||||
foreach (var (template, newName) in renames)
|
||||
{
|
||||
template.Name = newName;
|
||||
await _repository.UpdateTemplateAsync(template, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
composition.InstanceName = newInstanceName;
|
||||
@@ -747,6 +763,30 @@ public class TemplateService
|
||||
return Result<bool>.Success(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively collects the (template, new name) pairs for a renamed derived
|
||||
/// template and every cascaded inner derived template beneath it. Each inner
|
||||
/// derived's new name is re-derived from its renamed parent and the slot's
|
||||
/// instance name (mirroring the cascade <see cref="CreateCascadedCompositionAsync"/>
|
||||
/// builds and the recursion in <see cref="CascadeDeleteDerivedAsync"/>).
|
||||
/// </summary>
|
||||
private async Task CollectCascadeRenamesAsync(
|
||||
Template derived,
|
||||
string newName,
|
||||
List<(Template Template, string NewName)> renames,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
renames.Add((derived, newName));
|
||||
|
||||
foreach (var child in derived.Compositions.ToList())
|
||||
{
|
||||
var childDerived = await _repository.GetTemplateByIdAsync(child.ComposedTemplateId, cancellationToken);
|
||||
if (childDerived != null && childDerived.IsDerived && childDerived.OwnerCompositionId == child.Id)
|
||||
await CollectCascadeRenamesAsync(
|
||||
childDerived, $"{newName}.{child.InstanceName}", renames, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively deletes a derived template along with the cascade of inner
|
||||
/// derived templates the compose flow created. Each composition row on the
|
||||
|
||||
Reference in New Issue
Block a user