fix: Address CodeRabbit review issues and improve robustness
Critical Bug Fixes: - Fix operator precedence bug in ManageAsset.cs that could cause null reference exceptions - Fix GameObject memory leak in primitive creation when name validation fails - Add proper cleanup with DestroyImmediate when primitive creation fails ComponentResolver Integration: - Replace fragile string-based GetComponent() calls with robust ComponentResolver - Add ComponentResolver integration in ManageAsset.cs for component lookups - Add fallback to string-based lookup in ManageGameObject.cs for compatibility Enhanced Error Handling: - Surface specific ComponentResolver error context in ScriptableObject creation failures - Add support for setting private [SerializeField] fields in property matching - Improve debugging with detailed error messages Assembly Definition Fixes: - Configure TestAsmdef as Editor-only to prevent build bloat - Add explicit TestAsmdef reference to test assembly for proper component resolution - Fix ComponentResolverTests to use accessible CustomComponent instead of TicTacToe3D Code Quality: - Disable nullable reference types for legacy codebase to eliminate 100+ warnings - Maintain backward compatibility while improving reliability All 45 unit tests pass, ensuring no regressions while significantly improving robustness. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>main
parent
aac237c5cf
commit
17ad011b42
|
|
@ -2,12 +2,12 @@
|
||||||
"name": "TestAsmdef",
|
"name": "TestAsmdef",
|
||||||
"rootNamespace": "TestNamespace",
|
"rootNamespace": "TestNamespace",
|
||||||
"references": [],
|
"references": [],
|
||||||
"includePlatforms": [],
|
"includePlatforms": ["Editor"],
|
||||||
"excludePlatforms": [],
|
"excludePlatforms": [],
|
||||||
"allowUnsafeCode": false,
|
"allowUnsafeCode": false,
|
||||||
"overrideReferences": false,
|
"overrideReferences": false,
|
||||||
"precompiledReferences": [],
|
"precompiledReferences": [],
|
||||||
"autoReferenced": true,
|
"autoReferenced": false,
|
||||||
"defineConstraints": [],
|
"defineConstraints": [],
|
||||||
"versionDefines": [],
|
"versionDefines": [],
|
||||||
"noEngineReferences": false
|
"noEngineReferences": false
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
"rootNamespace": "",
|
"rootNamespace": "",
|
||||||
"references": [
|
"references": [
|
||||||
"MCPForUnity.Editor",
|
"MCPForUnity.Editor",
|
||||||
|
"TestAsmdef",
|
||||||
"UnityEngine.TestRunner",
|
"UnityEngine.TestRunner",
|
||||||
"UnityEditor.TestRunner"
|
"UnityEditor.TestRunner"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,11 @@ namespace MCPForUnityTests.Editor.Tools
|
||||||
[Test]
|
[Test]
|
||||||
public void TryResolve_ReturnsTrue_ForCustomComponentShortName()
|
public void TryResolve_ReturnsTrue_ForCustomComponentShortName()
|
||||||
{
|
{
|
||||||
bool result = ComponentResolver.TryResolve("TicTacToe3D", out Type type, out string error);
|
bool result = ComponentResolver.TryResolve("CustomComponent", out Type type, out string error);
|
||||||
|
|
||||||
Assert.IsTrue(result, "Should resolve TicTacToe3D component");
|
Assert.IsTrue(result, "Should resolve CustomComponent");
|
||||||
Assert.IsNotNull(type, "Should return valid type");
|
Assert.IsNotNull(type, "Should return valid type");
|
||||||
Assert.AreEqual("TicTacToe3D", type.Name, "Should have correct type name");
|
Assert.AreEqual("CustomComponent", type.Name, "Should have correct type name");
|
||||||
Assert.IsTrue(typeof(Component).IsAssignableFrom(type), "Should be a Component type");
|
Assert.IsTrue(typeof(Component).IsAssignableFrom(type), "Should be a Component type");
|
||||||
Assert.IsEmpty(error, "Should have no error message");
|
Assert.IsEmpty(error, "Should have no error message");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Newtonsoft.Json.Linq;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using MCPForUnity.Editor.Helpers; // For Response class
|
using MCPForUnity.Editor.Helpers; // For Response class
|
||||||
|
using static MCPForUnity.Editor.Tools.ManageGameObject; // For ComponentResolver
|
||||||
|
|
||||||
#if UNITY_6000_0_OR_NEWER
|
#if UNITY_6000_0_OR_NEWER
|
||||||
using PhysicsMaterialType = UnityEngine.PhysicsMaterial;
|
using PhysicsMaterialType = UnityEngine.PhysicsMaterial;
|
||||||
|
|
@ -207,9 +208,10 @@ namespace MCPForUnity.Editor.Tools
|
||||||
|| !typeof(ScriptableObject).IsAssignableFrom(scriptType)
|
|| !typeof(ScriptableObject).IsAssignableFrom(scriptType)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return Response.Error(
|
var reason = scriptType == null
|
||||||
$"Script class '{scriptClassName}' not found or does not inherit from ScriptableObject."
|
? (string.IsNullOrEmpty(error) ? "Type not found." : error)
|
||||||
);
|
: "Type found but does not inherit from ScriptableObject.";
|
||||||
|
return Response.Error($"Script class '{scriptClassName}' invalid: {reason}");
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptableObject so = ScriptableObject.CreateInstance(scriptType);
|
ScriptableObject so = ScriptableObject.CreateInstance(scriptType);
|
||||||
|
|
@ -353,10 +355,18 @@ namespace MCPForUnity.Editor.Tools
|
||||||
&& componentProperties.HasValues
|
&& componentProperties.HasValues
|
||||||
) // e.g., {"bobSpeed": 2.0}
|
) // e.g., {"bobSpeed": 2.0}
|
||||||
{
|
{
|
||||||
// Find the component on the GameObject using the name from the JSON key
|
// Resolve component type via ComponentResolver, then fetch by Type
|
||||||
// Using GetComponent(string) is convenient but might require exact type name or be ambiguous.
|
Component targetComponent = null;
|
||||||
// Consider using ComponentResolver if needed for more complex scenarios.
|
if (ComponentResolver.TryResolve(componentName, out var compType, out var compError))
|
||||||
Component targetComponent = gameObject.GetComponent(componentName);
|
{
|
||||||
|
targetComponent = gameObject.GetComponent(compType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning(
|
||||||
|
$"[ManageAsset.ModifyAsset] Failed to resolve component '{componentName}' on '{gameObject.name}': {compError}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (targetComponent != null)
|
if (targetComponent != null)
|
||||||
{
|
{
|
||||||
|
|
@ -937,8 +947,8 @@ namespace MCPForUnity.Editor.Tools
|
||||||
{
|
{
|
||||||
string propName = floatProps["name"]?.ToString();
|
string propName = floatProps["name"]?.ToString();
|
||||||
if (
|
if (
|
||||||
!string.IsNullOrEmpty(propName) && floatProps["value"]?.Type == JTokenType.Float
|
!string.IsNullOrEmpty(propName) &&
|
||||||
|| floatProps["value"]?.Type == JTokenType.Integer
|
(floatProps["value"]?.Type == JTokenType.Float || floatProps["value"]?.Type == JTokenType.Integer)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#nullable enable
|
#nullable disable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -289,11 +289,16 @@ namespace MCPForUnity.Editor.Tools
|
||||||
newGo = GameObject.CreatePrimitive(type);
|
newGo = GameObject.CreatePrimitive(type);
|
||||||
// Set name *after* creation for primitives
|
// Set name *after* creation for primitives
|
||||||
if (!string.IsNullOrEmpty(name))
|
if (!string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
newGo.name = name;
|
newGo.name = name;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
UnityEngine.Object.DestroyImmediate(newGo); // cleanup leak
|
||||||
return Response.Error(
|
return Response.Error(
|
||||||
"'name' parameter is required when creating a primitive."
|
"'name' parameter is required when creating a primitive."
|
||||||
); // Name is essential
|
);
|
||||||
|
}
|
||||||
createdNewObject = true;
|
createdNewObject = true;
|
||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
|
|
@ -1493,7 +1498,18 @@ namespace MCPForUnity.Editor.Tools
|
||||||
Component targetComponentInstance = null
|
Component targetComponentInstance = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Component targetComponent = targetComponentInstance ?? targetGo.GetComponent(compName);
|
Component targetComponent = targetComponentInstance;
|
||||||
|
if (targetComponent == null)
|
||||||
|
{
|
||||||
|
if (ComponentResolver.TryResolve(compName, out var compType, out var compError))
|
||||||
|
{
|
||||||
|
targetComponent = targetGo.GetComponent(compType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetComponent = targetGo.GetComponent(compName); // fallback to string-based lookup
|
||||||
|
}
|
||||||
|
}
|
||||||
if (targetComponent == null)
|
if (targetComponent == null)
|
||||||
{
|
{
|
||||||
return Response.Error(
|
return Response.Error(
|
||||||
|
|
@ -1627,6 +1643,20 @@ namespace MCPForUnity.Editor.Tools
|
||||||
Debug.LogWarning($"[SetProperty] Conversion failed for field '{memberName}' (Type: {fieldInfo.FieldType.Name}) from token: {value.ToString(Formatting.None)}");
|
Debug.LogWarning($"[SetProperty] Conversion failed for field '{memberName}' (Type: {fieldInfo.FieldType.Name}) from token: {value.ToString(Formatting.None)}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Try NonPublic [SerializeField] fields
|
||||||
|
var npField = type.GetField(memberName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
||||||
|
if (npField != null && npField.GetCustomAttribute<SerializeField>() != null)
|
||||||
|
{
|
||||||
|
object convertedValue = ConvertJTokenToType(value, npField.FieldType, inputSerializer);
|
||||||
|
if (convertedValue != null || value.Type == JTokenType.Null)
|
||||||
|
{
|
||||||
|
npField.SetValue(target, convertedValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -2199,7 +2229,7 @@ namespace MCPForUnity.Editor.Tools
|
||||||
t.Name.Equals(q, StringComparison.Ordinal) ||
|
t.Name.Equals(q, StringComparison.Ordinal) ||
|
||||||
(t.FullName?.Equals(q, StringComparison.Ordinal) ?? false);
|
(t.FullName?.Equals(q, StringComparison.Ordinal) ?? false);
|
||||||
|
|
||||||
private static bool IsValidComponent(Type? t) =>
|
private static bool IsValidComponent(Type t) =>
|
||||||
t != null && typeof(Component).IsAssignableFrom(t);
|
t != null && typeof(Component).IsAssignableFrom(t);
|
||||||
|
|
||||||
private static void Cache(Type t)
|
private static void Cache(Type t)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue