Preserve source identity ownership across prepared model commands
This commit is contained in:
@@ -261,6 +261,16 @@ pub fn inspect_includes(
|
||||
|
||||
type Edits = BTreeMap<LogicalPath, Vec<(Span, String)>>;
|
||||
|
||||
/// Diagnose the exact editable bytes, without inserting omitted UUIDs. Locations
|
||||
/// refer to the supplied bundle, including when successful compilation would
|
||||
/// otherwise stabilize identities before returning its index and diagnostics.
|
||||
pub fn lint_bundle(bundle: &SourceBundle, limits: ProjectLimits) -> Vec<SourceDiagnostic> {
|
||||
match compile_once(bundle, limits) {
|
||||
Ok((compiled, _)) => compiled.diagnostics,
|
||||
Err(diagnostics) => diagnostics,
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile all reachable files as one source3 model and stabilize omitted UUIDs.
|
||||
/// Missing/unreachable files and every admission or engineering error leave the
|
||||
/// input untouched. V1/V2 single-file migration remains in `language::parse`.
|
||||
@@ -727,6 +737,28 @@ pub fn update_bundle(
|
||||
current: &CompiledSourceProject,
|
||||
project: &Project,
|
||||
limits: ProjectLimits,
|
||||
) -> Result<CompiledSourceProject, Vec<SourceDiagnostic>> {
|
||||
let identities = current
|
||||
.decoded
|
||||
.project
|
||||
.identities()
|
||||
.into_iter()
|
||||
.filter(|(id, _)| project.contains(*id))
|
||||
.map(|(id, _)| (id, id))
|
||||
.collect();
|
||||
update_bundle_with_identity_map(current, project, &identities, limits)
|
||||
}
|
||||
|
||||
/// Preserve declaration and reference-comment ownership through identity edits.
|
||||
/// `identities` maps every surviving original identity to its candidate identity;
|
||||
/// new records have no entry. Use the map from `prepare_command`, including for
|
||||
/// nested batches and identity swaps. An arbitrary model replacement without
|
||||
/// command provenance can use `update_bundle`'s ordinary same-UUID matching.
|
||||
pub fn update_bundle_with_identity_map(
|
||||
current: &CompiledSourceProject,
|
||||
project: &Project,
|
||||
identities: &BTreeMap<Id, Id>,
|
||||
limits: ProjectLimits,
|
||||
) -> Result<CompiledSourceProject, Vec<SourceDiagnostic>> {
|
||||
// Public document/index fields are convenient for callers, but cannot become
|
||||
// an unchecked editing authority if a caller has changed either of them.
|
||||
@@ -760,26 +792,68 @@ pub fn update_bundle(
|
||||
)]
|
||||
})?;
|
||||
let old = ¤t.decoded.project;
|
||||
// A supplied map is editing intent, never permission to create duplicate
|
||||
// records, refer to absent objects, or move syntax between record classes.
|
||||
let category = |model: &Project, id: Id| {
|
||||
if model.id == id {
|
||||
0
|
||||
} else if model.elements.contains_key(&id) {
|
||||
1
|
||||
} else if model.relations.contains_key(&id) {
|
||||
2
|
||||
} else if model.diagrams.contains_key(&id) {
|
||||
3
|
||||
} else if model.artifact_links.contains_key(&id) {
|
||||
4
|
||||
} else if model.traces.contains_key(&id) {
|
||||
5
|
||||
} else {
|
||||
6 // Node placements; existence is checked separately.
|
||||
}
|
||||
};
|
||||
if identities.values().collect::<BTreeSet<_>>().len() != identities.len()
|
||||
|| identities.iter().any(|(before, after)| {
|
||||
!old.contains(*before)
|
||||
|| !project.contains(*after)
|
||||
|| category(old, *before) != category(project, *after)
|
||||
})
|
||||
{
|
||||
return Err(vec![problem(
|
||||
¤t.bundle,
|
||||
¤t.bundle.entry,
|
||||
Span::default(),
|
||||
"invalid_identity_map",
|
||||
"Source identity correspondence must map existing records one-to-one within their record class.",
|
||||
)]);
|
||||
}
|
||||
let origins: BTreeMap<_, _> = identities.iter().map(|(old, new)| (*new, *old)).collect();
|
||||
let before: BTreeMap<_, _> = language::format_declarations(old).into_iter().collect();
|
||||
let after: BTreeMap<_, _> = language::format_declarations(project).into_iter().collect();
|
||||
let old_aliases: BTreeMap<_, _> = old
|
||||
.identities()
|
||||
.into_iter()
|
||||
.map(|(id, alias)| (alias.to_owned(), id))
|
||||
.map(|(id, alias)| (alias.to_owned(), format!("original:{id}")))
|
||||
.collect();
|
||||
let new_aliases: BTreeMap<_, _> = project
|
||||
.identities()
|
||||
.into_iter()
|
||||
.map(|(id, alias)| (alias.to_owned(), id))
|
||||
.map(|(id, alias)| {
|
||||
let identity = origins.get(&id).map_or_else(
|
||||
|| format!("new:{id}"),
|
||||
|origin| format!("original:{origin}"),
|
||||
);
|
||||
(alias.to_owned(), identity)
|
||||
})
|
||||
.collect();
|
||||
let mut bundle = current.bundle.clone();
|
||||
let mut edits: Edits = BTreeMap::new();
|
||||
for (id, old_text) in &before {
|
||||
let location = ¤t.index.declarations[id];
|
||||
if after.get(id) != Some(old_text) {
|
||||
let new_text = identities.get(id).and_then(|id| after.get(id));
|
||||
if new_text != Some(old_text) {
|
||||
let source = &bundle.files[&location.path][location.span.start..location.span.end];
|
||||
let mut local = Vec::new();
|
||||
if let Some(new_text) = after.get(id) {
|
||||
if let Some(new_text) = new_text {
|
||||
let parse = |text| {
|
||||
language::edit_declaration(text).map_err(|error| {
|
||||
vec![problem(
|
||||
@@ -906,8 +980,7 @@ pub fn update_bundle(
|
||||
.index
|
||||
.declarations
|
||||
.iter()
|
||||
.filter(|(id, _)| project.contains(**id))
|
||||
.map(|(id, location)| (*id, location.path.clone()))
|
||||
.filter_map(|(id, location)| identities.get(id).map(|new| (*new, location.path.clone())))
|
||||
.collect();
|
||||
let mut chosen_defaults = BTreeMap::new();
|
||||
let mut used_paths: BTreeSet<_> = bundle
|
||||
@@ -919,7 +992,7 @@ pub fn update_bundle(
|
||||
let aliases: BTreeMap<_, _> = project.identities().into_iter().collect();
|
||||
let mut new_ids: Vec<_> = after
|
||||
.keys()
|
||||
.filter(|id| !before.contains_key(*id))
|
||||
.filter(|id| !origins.contains_key(*id))
|
||||
.copied()
|
||||
.collect();
|
||||
new_ids.sort_by_key(|id| declaration_order(project, *id, &aliases));
|
||||
@@ -1089,8 +1162,8 @@ struct SyntaxUpdate<'a> {
|
||||
source: &'a str,
|
||||
before: &'a str,
|
||||
after: &'a str,
|
||||
old_aliases: &'a BTreeMap<String, Id>,
|
||||
new_aliases: &'a BTreeMap<String, Id>,
|
||||
old_aliases: &'a BTreeMap<String, String>,
|
||||
new_aliases: &'a BTreeMap<String, String>,
|
||||
}
|
||||
impl SyntaxUpdate<'_> {
|
||||
fn header(
|
||||
@@ -1420,7 +1493,7 @@ impl SyntaxUpdate<'_> {
|
||||
fn item_key(
|
||||
value: &language::EditValue,
|
||||
source: &str,
|
||||
aliases: &BTreeMap<String, Id>,
|
||||
aliases: &BTreeMap<String, String>,
|
||||
) -> Option<String> {
|
||||
use language::EditKind;
|
||||
match &value.kind {
|
||||
|
||||
Reference in New Issue
Block a user