From 1576301c12c1b17269e3a7061ab610e412bd6564 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sat, 22 Aug 2026 09:29:38 +0900 Subject: [PATCH 1/2] Name the assembly that declares an unresolved CS0234 namespace Unity reports a missing assembly reference without naming it. Look the namespace up in TypeCache and append the declaring assemblies so agents can update the failing asmdef without an extra round-trip. Co-authored-by: Cursor --- .../CompileErrorNextActionsComposerTests.cs | 272 ++++++++++++++++++ .../Compile/CompileErrorNextActionsBuilder.cs | 118 +++++++- .../CompileErrorNextActionsComposer.cs | 4 +- .../CompileErrorNextActionsConstants.cs | 14 + .../CompileMissingReferenceAssemblyLookup.cs | 81 ++++++ ...pileMissingReferenceAssemblyLookup.cs.meta | 11 + .../FirstPartyTools/Compile/CompileUseCase.cs | 3 + 7 files changed, 490 insertions(+), 13 deletions(-) create mode 100644 Packages/src/Editor/FirstPartyTools/Compile/CompileMissingReferenceAssemblyLookup.cs create mode 100644 Packages/src/Editor/FirstPartyTools/Compile/CompileMissingReferenceAssemblyLookup.cs.meta diff --git a/Assets/Tests/Editor/CompileErrorNextActionsComposerTests.cs b/Assets/Tests/Editor/CompileErrorNextActionsComposerTests.cs index dd6146d20..0e4df30f1 100644 --- a/Assets/Tests/Editor/CompileErrorNextActionsComposerTests.cs +++ b/Assets/Tests/Editor/CompileErrorNextActionsComposerTests.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using NUnit.Framework; +using UnityEditor; using UnityEditor.Compilation; using io.github.hatayama.UnityCliLoop.FirstPartyTools; @@ -47,6 +49,30 @@ public sealed class CompileErrorNextActionsComposerTests private const string ApiUpdaterNextAction = "Fix the obsolete API usages reported in Errors, or ask the user to accept the Script Updating Consent dialog in an interactive Unity session."; + private const string InputSystemCs0234Error = + "error CS0234: The type or namespace name 'InputSystem' does not exist in the namespace 'UnityEngine' (are you missing an assembly reference?)"; + + private const string PrefixlessInputSystemCs0234Error = + "CS0234: The type or namespace name 'InputSystem' does not exist in the namespace 'UnityEngine' (are you missing an assembly reference?)"; + + private const string InputSystemNextAction = + "error CS0234: 'UnityEngine.InputSystem' is declared in assembly 'Unity.InputSystem'. Add the assembly to the failing script's .asmdef references and run 'uloop compile' again. If the failing script has no .asmdef, the declaring assembly may have Auto Referenced disabled or its package may not be installed."; + + private const string DualAssemblyNextAction = + "error CS0234: 'UnityEngine.InputSystem' is declared in assemblies 'Alpha.Assembly', 'Zebra.Assembly'. Add the assembly to the failing script's .asmdef references and run 'uloop compile' again. If the failing script has no .asmdef, the declaring assembly may have Auto Referenced disabled or its package may not be installed."; + + private const string TripleAssemblyNextAction = + "error CS0234: 'UnityEngine.InputSystem' is declared in assemblies 'A.Assembly', 'B.Assembly', 'C.Assembly'. Add the assembly to the failing script's .asmdef references and run 'uloop compile' again. If the failing script has no .asmdef, the declaring assembly may have Auto Referenced disabled or its package may not be installed."; + + private const string Cs0246Error = + "error CS0246: The type or namespace name 'InputSystem' could not be found (are you missing a using directive or an assembly reference?)"; + + private const string NUnitCs0234Error = + "error CS0234: The type or namespace name 'Framework' does not exist in the namespace 'NUnit' (are you missing an assembly reference?)"; + + private const string UnknownCs0234Error = + "error CS0234: The type or namespace name 'NoSuchInner' does not exist in the namespace 'NoSuchOuter' (are you missing an assembly reference?)"; + /// /// What: a language-version error produces the pinned-rewrite NextAction as an exact literal. /// @@ -298,6 +324,252 @@ public void CreateResponse_WhenLanguageVersionErrorAndConsentDeclined_AppendsAft Is.EqualTo(new[] { ApiUpdaterNextAction, FileScopedNamespaceNextAction })); } + /// + /// What: a CS0234 error produces the declaring-assembly NextAction from the injected lookup. + /// + [Test] + public void Build_WhenCs0234Error_ReturnsDeclaringAssemblyAction() + { + string[] nextActions = CompileErrorNextActionsBuilder.Build( + new[] { InputSystemCs0234Error }, + searchName => searchName == "UnityEngine.InputSystem" + ? new[] { "Unity.InputSystem" } + : Array.Empty()); + + Assert.That(nextActions, Is.EqualTo(new[] { InputSystemNextAction })); + } + + /// + /// What: a prefix-less CS0234 message still produces the declaring-assembly NextAction. + /// + [Test] + public void Build_WhenPrefixlessCs0234Error_ReturnsDeclaringAssemblyAction() + { + string[] nextActions = CompileErrorNextActionsBuilder.Build( + new[] { PrefixlessInputSystemCs0234Error }, + searchName => new[] { "Unity.InputSystem" }); + + Assert.That(nextActions, Is.EqualTo(new[] { InputSystemNextAction })); + } + + /// + /// What: CS0246 never produces a missing-reference NextAction. + /// + [Test] + public void Build_WhenCs0246Error_ReturnsEmpty() + { + int lookupCalls = 0; + string[] nextActions = CompileErrorNextActionsBuilder.Build( + new[] { Cs0246Error }, + searchName => + { + lookupCalls++; + return new[] { "Unity.InputSystem" }; + }); + + Assert.That(nextActions, Is.EqualTo(Array.Empty())); + Assert.That(lookupCalls, Is.EqualTo(0)); + } + + /// + /// What: a CS0234 match with zero declaring assemblies stays fail-open. + /// + [Test] + public void Build_WhenCs0234LookupReturnsEmpty_ReturnsEmpty() + { + string[] nextActions = CompileErrorNextActionsBuilder.Build( + new[] { InputSystemCs0234Error }, + searchName => Array.Empty()); + + Assert.That(nextActions, Is.EqualTo(Array.Empty())); + } + + /// + /// What: multiple declaring assemblies are named in ordinal order. + /// + [Test] + public void Build_WhenCs0234HasMultipleAssemblies_NamesThemInOrdinalOrder() + { + string[] nextActions = CompileErrorNextActionsBuilder.Build( + new[] { InputSystemCs0234Error }, + searchName => new[] { "Zebra.Assembly", "Alpha.Assembly" }); + + Assert.That(nextActions, Is.EqualTo(new[] { DualAssemblyNextAction })); + } + + /// + /// What: more than three declaring assemblies are truncated after the first three sorted names. + /// + [Test] + public void Build_WhenCs0234HasMoreThanThreeAssemblies_NamesAtMostThree() + { + string[] nextActions = CompileErrorNextActionsBuilder.Build( + new[] { InputSystemCs0234Error }, + searchName => new[] { "D.Assembly", "C.Assembly", "B.Assembly", "A.Assembly" }); + + Assert.That(nextActions, Is.EqualTo(new[] { TripleAssemblyNextAction })); + } + + /// + /// What: identical CS0234 NextActions are appended only once. + /// + [Test] + public void Build_WhenDuplicateCs0234Actions_Dedups() + { + string[] nextActions = CompileErrorNextActionsBuilder.Build( + new[] { InputSystemCs0234Error, PrefixlessInputSystemCs0234Error }, + searchName => new[] { "Unity.InputSystem" }); + + Assert.That(nextActions, Is.EqualTo(new[] { InputSystemNextAction })); + } + + /// + /// What: language-version and CS0234 actions are appended in B-then-A order. + /// + [Test] + public void Build_WhenLanguageVersionAndCs0234_AppendsLanguageVersionFirst() + { + string[] nextActions = CompileErrorNextActionsBuilder.Build( + new[] { FileScopedNamespaceError, InputSystemCs0234Error }, + searchName => new[] { "Unity.InputSystem" }); + + Assert.That(nextActions, Is.EqualTo(new[] { FileScopedNamespaceNextAction, InputSystemNextAction })); + } + + /// + /// What: a CS0234 with no declaring assembly leaves existing NextActions unchanged. + /// + [Test] + public void Apply_WhenCs0234HasNoDeclaringAssembly_LeavesExistingNextActionsUnchanged() + { + CompileResponse response = CreateResponse(success: false); + response.NextActions = new[] { ExistingNextAction }; + + CompileErrorNextActionsComposer.Apply(response, new[] { CreateError(UnknownCs0234Error) }); + + Assert.That(response.NextActions, Is.EqualTo(new[] { ExistingNextAction })); + } + + /// + /// What: existing NextActions are kept and a resolved CS0234 action is appended at the end. + /// + [Test] + public void Apply_WhenExistingNextActionsAndResolvedCs0234_AppendsDeclaringAssemblyAction() + { + CompileResponse response = CreateResponse(success: false); + response.NextActions = new[] { ExistingNextAction }; + + CompileErrorNextActionsComposer.Apply(response, new[] { CreateError(NUnitCs0234Error) }); + + Assert.That(response.NextActions, Is.Not.Null); + Assert.That(response.NextActions, Has.Length.EqualTo(2)); + Assert.That(response.NextActions[0], Is.EqualTo(ExistingNextAction)); + Assert.That(response.NextActions[1], Does.Contain("nunit.framework")); + } + + /// + /// What: CreateResponse names nunit.framework for a real CS0234 against NUnit.Framework. + /// + [Test] + public void CreateResponse_WhenCs0234ForNUnitFramework_IncludesNunitFrameworkAssembly() + { + CompileResult result = CreateFailedResult(CreateError(NUnitCs0234Error)); + + CompileResponse response = CompileResponseFactory.CreateResponse( + result, + forceRecompile: false, + pausePointWarning: null); + + Assert.That(response.NextActions, Is.Not.Null); + Assert.That(response.NextActions, Has.Length.EqualTo(1)); + Assert.That(response.NextActions[0], Does.Contain("nunit.framework")); + } + + /// + /// What: CreateResponse stays fail-open when CS0234 names a namespace TypeCache does not declare. + /// + [Test] + public void CreateResponse_WhenCs0234HasNoDeclaringAssembly_ReturnsNoNextActions() + { + CompileResult result = CreateFailedResult(CreateError(UnknownCs0234Error)); + + CompileResponse response = CompileResponseFactory.CreateResponse( + result, + forceRecompile: false, + pausePointWarning: null); + + Assert.That(response.NextActions, Is.Null); + } + + /// + /// What: CreateResponse does not add a missing-reference NextAction for CS0246. + /// + [Test] + public void CreateResponse_WhenCs0246Error_ReturnsNoNextActions() + { + CompileResult result = CreateFailedResult(CreateError(Cs0246Error)); + + CompileResponse response = CompileResponseFactory.CreateResponse( + result, + forceRecompile: false, + pausePointWarning: null); + + Assert.That(response.NextActions, Is.Null); + } + + /// + /// What: CreateResponse appends the TypeCache CS0234 action after the API Updater action. + /// + [Test] + public void CreateResponse_WhenCs0234AndConsentDeclined_AppendsAfterExistingNextActions() + { + CompileResult result = new CompileResult( + success: false, + errorCount: 1, + warningCount: 0, + completedAt: DateTime.Now, + messages: Array.Empty(), + errors: new[] { CreateError(NUnitCs0234Error) }, + warnings: Array.Empty(), + apiUpdaterConsentDeclined: true); + + CompileResponse response = CompileResponseFactory.CreateResponse( + result, + forceRecompile: false, + pausePointWarning: null); + + Assert.That(response.NextActions, Is.Not.Null); + Assert.That(response.NextActions, Has.Length.EqualTo(2)); + Assert.That(response.NextActions[0], Is.EqualTo(ApiUpdaterNextAction)); + Assert.That(response.NextActions[1], Does.Contain("nunit.framework")); + } + + /// + /// What: TypeCache.GetTypesDerivedFrom(typeof(object)) lists NUnit.Framework types in nunit.framework. + /// + [Test] + public void TypeCache_GetTypesDerivedFromObject_IncludesNunitFrameworkForNUnitFrameworkNamespace() + { + List assemblyNames = new List(); + foreach (Type type in TypeCache.GetTypesDerivedFrom(typeof(object))) + { + if (type.Namespace != "NUnit.Framework") + { + continue; + } + + string assemblyName = type.Assembly.GetName().Name; + if (assemblyNames.Contains(assemblyName)) + { + continue; + } + + assemblyNames.Add(assemblyName); + } + + Assert.That(assemblyNames, Does.Contain("nunit.framework")); + } + private static CompileResponse CreateResponse(bool success) { return new CompileResponse( diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsBuilder.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsBuilder.cs index f7961ee0e..6ab6f067b 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsBuilder.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsBuilder.cs @@ -17,10 +17,16 @@ internal static class CompileErrorNextActionsBuilder CompileErrorNextActionsConstants.LanguageVersionFeaturePattern, RegexOptions.CultureInvariant); + private static readonly Regex MissingNamespaceRegex = new Regex( + CompileErrorNextActionsConstants.MissingNamespacePattern, + RegexOptions.CultureInvariant); + /// /// Returns up to three deduplicated NextActions for the first ten error messages. /// - internal static string[] Build(string[] errorMessages) + internal static string[] Build( + string[] errorMessages, + Func findAssemblyNames = null) { if (errorMessages == null) { @@ -36,21 +42,39 @@ internal static string[] Build(string[] errorMessages) break; } - string nextAction = TryBuildLanguageVersionNextAction(errorMessages[index]); - if (nextAction == null) - { - continue; - } + AppendFromMessage(additions, errorMessages[index], findAssemblyNames); + } - if (additions.Contains(nextAction)) - { - continue; - } + return additions.ToArray(); + } - additions.Add(nextAction); + private static void AppendFromMessage( + List additions, + string message, + Func findAssemblyNames) + { + TryAdd(additions, TryBuildLanguageVersionNextAction(message)); + if (additions.Count >= CompileErrorNextActionsConstants.MaxNextActionsToAppend) + { + return; } - return additions.ToArray(); + TryAdd(additions, TryBuildMissingReferenceNextAction(message, findAssemblyNames)); + } + + private static void TryAdd(List additions, string nextAction) + { + if (nextAction == null) + { + return; + } + + if (additions.Contains(nextAction)) + { + return; + } + + additions.Add(nextAction); } /// @@ -83,5 +107,75 @@ private static string TryBuildLanguageVersionNextAction(string message) featureMatch.Groups["feature"].Value, featureMatch.Groups["version"].Value); } + + /// + /// Why: Unity's "are you missing an assembly reference?" names the problem but not the + /// assembly; agents burned a round-trip discovering which asmdef reference to add. + /// + private static string TryBuildMissingReferenceNextAction( + string message, + Func findAssemblyNames) + { + if (findAssemblyNames == null || string.IsNullOrEmpty(message)) + { + return null; + } + + Match errorCodeMatch = ErrorCodeRegex.Match(message); + if (!errorCodeMatch.Success) + { + return null; + } + + if (errorCodeMatch.Groups[1].Value != CompileErrorNextActionsConstants.Cs0234ErrorCode) + { + return null; + } + + Match namespaceMatch = MissingNamespaceRegex.Match(message); + if (!namespaceMatch.Success) + { + return null; + } + + string searchName = namespaceMatch.Groups["outer"].Value + "." + namespaceMatch.Groups["inner"].Value; + string[] assemblyNames = findAssemblyNames(searchName); + string declaringAssemblies = FormatDeclaringAssemblies(assemblyNames); + if (declaringAssemblies == null) + { + return null; + } + + return string.Format( + CompileErrorNextActionsConstants.MissingAssemblyReferenceNextActionFormat, + errorCodeMatch.Groups[1].Value, + searchName, + declaringAssemblies); + } + + private static string FormatDeclaringAssemblies(string[] assemblyNames) + { + if (assemblyNames == null || assemblyNames.Length == 0) + { + return null; + } + + string[] sorted = new string[assemblyNames.Length]; + Array.Copy(assemblyNames, sorted, assemblyNames.Length); + Array.Sort(sorted, StringComparer.Ordinal); + int count = Math.Min(sorted.Length, CompileErrorNextActionsConstants.MaxDeclaringAssembliesToName); + string[] selected = new string[count]; + Array.Copy(sorted, selected, count); + if (selected.Length == 1) + { + return string.Format( + CompileErrorNextActionsConstants.SingleDeclaringAssemblyFormat, + selected[0]); + } + + return string.Format( + CompileErrorNextActionsConstants.MultipleDeclaringAssembliesFormat, + string.Join("', '", selected)); + } } } diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsComposer.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsComposer.cs index fff2a7439..ed9ccf892 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsComposer.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsComposer.cs @@ -31,7 +31,9 @@ internal static void Apply(CompileResponse response, CompilerMessage[] errors) messages[index] = errors[index].message; } - string[] additions = CompileErrorNextActionsBuilder.Build(messages); + string[] additions = CompileErrorNextActionsBuilder.Build( + messages, + CompileMissingReferenceAssemblyLookup.CreateLazyFinder()); if (additions.Length == 0) { return; diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsConstants.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsConstants.cs index 3d653debb..55396f627 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsConstants.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileErrorNextActionsConstants.cs @@ -13,8 +13,22 @@ internal static class CompileErrorNextActionsConstants public const string LanguageVersionFeaturePattern = @"Feature '(?[^']+)' is not available in C# (?[0-9]+(\.[0-9]+)?)"; + public const string MissingAssemblyReferenceNextActionFormat = + "error {0}: '{1}' is declared in {2}. Add the assembly to the failing script's .asmdef references and run 'uloop compile' again. If the failing script has no .asmdef, the declaring assembly may have Auto Referenced disabled or its package may not be installed."; + + public const string MissingNamespacePattern = + @"The type or namespace name '(?[^']+)' does not exist in the namespace '(?[^']+)'"; + + public const string Cs0234ErrorCode = "CS0234"; + + public const string SingleDeclaringAssemblyFormat = "assembly '{0}'"; + + public const string MultipleDeclaringAssembliesFormat = "assemblies '{0}'"; + public const int MaxErrorsToScan = 10; public const int MaxNextActionsToAppend = 3; + + public const int MaxDeclaringAssembliesToName = 3; } } diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileMissingReferenceAssemblyLookup.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileMissingReferenceAssemblyLookup.cs new file mode 100644 index 000000000..8d3b296c6 --- /dev/null +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileMissingReferenceAssemblyLookup.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using UnityEditor; + +using io.github.hatayama.UnityCliLoop.ToolContracts; + +namespace io.github.hatayama.UnityCliLoop.FirstPartyTools +{ + /// + /// Resolves declaring assembly names for a namespace by scanning TypeCache once per Apply. + /// + internal static class CompileMissingReferenceAssemblyLookup + { + /// + /// Why not scan eagerly: unmatched compiles must stay fail-open and skip TypeCache entirely. + /// Why TypeCache only: Assembly.GetTypes() needs try-catch for ReflectionTypeLoadException. + /// + internal static Func CreateLazyFinder() + { + Dictionary index = null; + return searchName => + { + if (index == null) + { + index = BuildIndex(); + } + + if (searchName == null) + { + return Array.Empty(); + } + + if (index.TryGetValue(searchName, out string[] assemblyNames)) + { + return assemblyNames; + } + + return Array.Empty(); + }; + } + + private static Dictionary BuildIndex() + { + Debug.Assert( + MainThreadSwitcher.IsMainThread, + "TypeCache.GetTypesDerivedFrom must run on the Unity main thread."); + + Dictionary> grouped = + new Dictionary>(StringComparer.Ordinal); + foreach (Type type in TypeCache.GetTypesDerivedFrom(typeof(object))) + { + string namespaceName = type.Namespace; + if (string.IsNullOrEmpty(namespaceName)) + { + continue; + } + + string assemblyName = type.Assembly.GetName().Name; + if (!grouped.TryGetValue(namespaceName, out SortedSet assemblyNames)) + { + assemblyNames = new SortedSet(StringComparer.Ordinal); + grouped.Add(namespaceName, assemblyNames); + } + + assemblyNames.Add(assemblyName); + } + + Dictionary index = + new Dictionary(grouped.Count, StringComparer.Ordinal); + foreach (KeyValuePair> pair in grouped) + { + string[] assemblyNames = new string[pair.Value.Count]; + pair.Value.CopyTo(assemblyNames); + index.Add(pair.Key, assemblyNames); + } + + return index; + } + } +} diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileMissingReferenceAssemblyLookup.cs.meta b/Packages/src/Editor/FirstPartyTools/Compile/CompileMissingReferenceAssemblyLookup.cs.meta new file mode 100644 index 000000000..b9dfdb98e --- /dev/null +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileMissingReferenceAssemblyLookup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c995cdd05dfc049baba04e9098f32998 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs index d439ea40d..1951e8465 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs @@ -175,6 +175,9 @@ public async Task CompileAsync(CompileSchema request, Cancellat activePausePointCountAtRequestStart); ct.ThrowIfCancellationRequested(); CompileResult result = await _executeCompilationAsync(request, pausePointWarning, ct).ConfigureAwait(false); + // Why: CreateResponse may query TypeCache for missing-reference NextActions, and + // TypeCache is a Unity Editor API that must run on the main thread. + await MainThreadSwitcher.SwitchToMainThread(ct); // 4. Result formatting CompileResponse successResponse = From 3d164096a475d15c5e6e01e6b3b9d210101a0ada Mon Sep 17 00:00:00 2001 From: hatayama Date: Sun, 23 Aug 2026 18:57:14 +0900 Subject: [PATCH 2/2] Require exact NextAction literals for NUnit CS0234 factory tests Substring checks would pass even if the recovery wording was missing or wrong. Pin the three live TypeCache response tests to one fixture. Co-authored-by: Cursor --- .../CompileErrorNextActionsComposerTests.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Assets/Tests/Editor/CompileErrorNextActionsComposerTests.cs b/Assets/Tests/Editor/CompileErrorNextActionsComposerTests.cs index 0e4df30f1..4640a5667 100644 --- a/Assets/Tests/Editor/CompileErrorNextActionsComposerTests.cs +++ b/Assets/Tests/Editor/CompileErrorNextActionsComposerTests.cs @@ -70,6 +70,9 @@ public sealed class CompileErrorNextActionsComposerTests private const string NUnitCs0234Error = "error CS0234: The type or namespace name 'Framework' does not exist in the namespace 'NUnit' (are you missing an assembly reference?)"; + private const string NUnitFrameworkNextAction = + "error CS0234: 'NUnit.Framework' is declared in assembly 'nunit.framework'. Add the assembly to the failing script's .asmdef references and run 'uloop compile' again. If the failing script has no .asmdef, the declaring assembly may have Auto Referenced disabled or its package may not be installed."; + private const string UnknownCs0234Error = "error CS0234: The type or namespace name 'NoSuchInner' does not exist in the namespace 'NoSuchOuter' (are you missing an assembly reference?)"; @@ -461,10 +464,9 @@ public void Apply_WhenExistingNextActionsAndResolvedCs0234_AppendsDeclaringAssem CompileErrorNextActionsComposer.Apply(response, new[] { CreateError(NUnitCs0234Error) }); - Assert.That(response.NextActions, Is.Not.Null); - Assert.That(response.NextActions, Has.Length.EqualTo(2)); - Assert.That(response.NextActions[0], Is.EqualTo(ExistingNextAction)); - Assert.That(response.NextActions[1], Does.Contain("nunit.framework")); + Assert.That( + response.NextActions, + Is.EqualTo(new[] { ExistingNextAction, NUnitFrameworkNextAction })); } /// @@ -480,9 +482,7 @@ public void CreateResponse_WhenCs0234ForNUnitFramework_IncludesNunitFrameworkAss forceRecompile: false, pausePointWarning: null); - Assert.That(response.NextActions, Is.Not.Null); - Assert.That(response.NextActions, Has.Length.EqualTo(1)); - Assert.That(response.NextActions[0], Does.Contain("nunit.framework")); + Assert.That(response.NextActions, Is.EqualTo(new[] { NUnitFrameworkNextAction })); } /// @@ -538,10 +538,9 @@ public void CreateResponse_WhenCs0234AndConsentDeclined_AppendsAfterExistingNext forceRecompile: false, pausePointWarning: null); - Assert.That(response.NextActions, Is.Not.Null); - Assert.That(response.NextActions, Has.Length.EqualTo(2)); - Assert.That(response.NextActions[0], Is.EqualTo(ApiUpdaterNextAction)); - Assert.That(response.NextActions[1], Does.Contain("nunit.framework")); + Assert.That( + response.NextActions, + Is.EqualTo(new[] { ApiUpdaterNextAction, NUnitFrameworkNextAction })); } ///