Add audit plan for slugify branch pass 3 and implement critical fixes

- Introduced a comprehensive audit plan for the slugify branch, detailing findings from a holistic review of 110 files, including 1 critical bug and 2 correctness issues.
- Completed todos addressing model JSON renaming, syncback updates, and test enhancements for roundtrip fidelity.
- Added tests for reserved names and ensured correct handling of slugified instances, preserving compound extensions in model files.
- Refactored change processing logic to correctly manage the `name` field in model files, enhancing metadata handling and ensuring compliance with round-trip identity standards.
This commit is contained in:
Joe 2026-02-11 01:31:09 -06:00
parent 2a917bf8e1
commit bb266789c0
14 changed files with 843 additions and 23 deletions

View file

@ -323,7 +323,9 @@ pub fn validate_file_name<S: AsRef<str>>(name: S) -> anyhow::Result<()> {
/// Known script suffixes that appear between the base name and file extension.
/// For example, in `MyScript.server.luau`, `.server` is the suffix.
const KNOWN_SCRIPT_SUFFIXES: &[&str] = &[".server", ".client", ".plugin", ".local", ".legacy"];
const KNOWN_SCRIPT_SUFFIXES: &[&str] = &[
".server", ".client", ".plugin", ".local", ".legacy", ".model",
];
/// Strips a known script suffix from a file stem.
///

View file

@ -46,6 +46,50 @@ pub enum RemoveNameOutcome {
FileDeleted,
}
/// Upsert the `name` field inside a `.model.json5` / `.model.json` file.
///
/// Parses the existing JSON, sets/replaces the `name` key, and rewrites.
/// Unlike `upsert_meta_name`, this modifies the model file in-place.
pub fn upsert_model_name(model_path: &Path, real_name: &str) -> anyhow::Result<()> {
let bytes = fs::read(model_path)?;
let mut obj = match crate::json::from_slice::<serde_json::Value>(&bytes) {
Ok(serde_json::Value::Object(map)) => map,
_ => anyhow::bail!("model file is not a JSON object: {}", model_path.display()),
};
obj.insert(
"name".to_string(),
serde_json::Value::String(real_name.to_string()),
);
let content = crate::json::to_vec_pretty_sorted(&serde_json::Value::Object(obj))?;
fs::write(model_path, &content)?;
Ok(())
}
/// Remove the `name` field from a `.model.json5` / `.model.json` file.
///
/// Unlike meta files, model files are never deleted when they become "empty"
/// (they always have at least `className`). Returns `RemoveNameOutcome` for
/// consistency with the meta helpers.
pub fn remove_model_name(model_path: &Path) -> anyhow::Result<RemoveNameOutcome> {
if !model_path.exists() {
return Ok(RemoveNameOutcome::NoOp);
}
let bytes = match fs::read(model_path) {
Ok(b) => b,
Err(_) => return Ok(RemoveNameOutcome::NoOp),
};
let mut obj = match crate::json::from_slice::<serde_json::Value>(&bytes) {
Ok(serde_json::Value::Object(map)) => map,
_ => return Ok(RemoveNameOutcome::NoOp),
};
if obj.remove("name").is_none() {
return Ok(RemoveNameOutcome::NoOp);
}
let content = crate::json::to_vec_pretty_sorted(&serde_json::Value::Object(obj))?;
fs::write(model_path, &content)?;
Ok(RemoveNameOutcome::FieldRemoved)
}
/// Remove the `name` field from a `.meta.json5` file.
///
/// If the file becomes an empty object after removal, deletes it entirely.