Browse Source

C#: Enable nullable environment for `GodotTools`

Thaddeus Crews 1 year ago
parent
commit
3314f8cc65
47 changed files with 349 additions and 194 deletions
  1. 3 3
      modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
  2. 1 0
      modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
  3. 1 0
      modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
  4. 1 1
      modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs
  5. 1 4
      modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
  6. 1 0
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj
  7. 9 9
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs
  8. 8 6
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs
  9. 2 1
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs
  10. 1 1
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs
  11. 142 0
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/CodeAnalysisAttributes.cs
  12. 3 1
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs
  13. 3 2
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj
  14. 3 1
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs
  15. 2 2
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs
  16. 14 14
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs
  17. 6 5
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs
  18. 1 1
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs
  19. 7 4
      modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs
  20. 1 0
      modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj
  21. 11 5
      modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs
  22. 13 11
      modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs
  23. 1 0
      modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
  24. 1 1
      modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
  25. 0 2
      modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs
  26. 2 3
      modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
  27. 19 19
      modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
  28. 0 2
      modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
  29. 0 2
      modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs
  30. 3 3
      modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs
  31. 12 12
      modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
  32. 6 8
      modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs
  33. 2 4
      modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
  34. 5 4
      modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
  35. 6 6
      modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs
  36. 13 16
      modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
  37. 1 0
      modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
  38. 2 0
      modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
  39. 4 4
      modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
  40. 9 8
      modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs
  41. 4 2
      modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
  42. 5 5
      modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs
  43. 0 2
      modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
  44. 7 5
      modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs
  45. 5 3
      modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs
  46. 1 2
      modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs
  47. 7 10
      modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs

+ 3 - 3
modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs

@@ -7,11 +7,11 @@ namespace GodotTools.BuildLogger
 {
     public class GodotBuildLogger : ILogger
     {
-        public string Parameters { get; set; }
+        public string? Parameters { get; set; }
         public LoggerVerbosity Verbosity { get; set; }
 
-        private StreamWriter _logStreamWriter;
-        private StreamWriter _issuesStreamWriter;
+        private StreamWriter _logStreamWriter = StreamWriter.Null;
+        private StreamWriter _issuesStreamWriter = StreamWriter.Null;
         private int _indent;
 
         public void Initialize(IEventSource eventSource)

+ 1 - 0
modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj

@@ -3,6 +3,7 @@
     <ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid>
     <TargetFramework>net6.0</TargetFramework>
     <LangVersion>10</LangVersion>
+    <Nullable>enable</Nullable>
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" ExcludeAssets="runtime" />

+ 1 - 0
modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj

@@ -3,5 +3,6 @@
     <ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid>
     <TargetFramework>net6.0</TargetFramework>
     <LangVersion>10</LangVersion>
+    <Nullable>enable</Nullable>
   </PropertyGroup>
 </Project>

+ 1 - 1
modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs

@@ -11,7 +11,7 @@ namespace GodotTools.Core
         {
             var tcs = new TaskCompletionSource<bool>();
 
-            void ProcessExited(object sender, EventArgs e)
+            void ProcessExited(object? sender, EventArgs e)
             {
                 tcs.TrySetResult(true);
             }

+ 1 - 4
modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs

@@ -7,7 +7,7 @@ namespace GodotTools.Core
 {
     public static class StringExtensions
     {
-        private static readonly string _driveRoot = Path.GetPathRoot(Environment.CurrentDirectory);
+        private static readonly string _driveRoot = Path.GetPathRoot(Environment.CurrentDirectory)!;
 
         public static string RelativeToPath(this string path, string dir)
         {
@@ -26,9 +26,6 @@ namespace GodotTools.Core
 
         public static string NormalizePath(this string path)
         {
-            if (string.IsNullOrEmpty(path))
-                return path;
-
             bool rooted = path.IsAbsolutePath();
 
             path = path.Replace('\\', '/');

+ 1 - 0
modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj

@@ -4,6 +4,7 @@
     <OutputType>Exe</OutputType>
     <TargetFramework>net6.0</TargetFramework>
     <LangVersion>10</LangVersion>
+    <Nullable>enable</Nullable>
   </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" />

+ 9 - 9
modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs

@@ -64,14 +64,14 @@ namespace GodotTools.IdeMessaging.CLI
 
                     while (!fwdClient.IsDisposed)
                     {
-                        string firstLine = await inputReader.ReadLineAsync();
+                        string? firstLine = await inputReader.ReadLineAsync();
 
                         if (firstLine == null || firstLine == "QUIT")
                             goto ExitMainLoop;
 
                         string messageId = firstLine;
 
-                        string messageArgcLine = await inputReader.ReadLineAsync();
+                        string? messageArgcLine = await inputReader.ReadLineAsync();
 
                         if (messageArgcLine == null)
                         {
@@ -89,7 +89,7 @@ namespace GodotTools.IdeMessaging.CLI
 
                         for (int i = 0; i < messageArgc; i++)
                         {
-                            string bodyLine = await inputReader.ReadLineAsync();
+                            string? bodyLine = await inputReader.ReadLineAsync();
 
                             if (bodyLine == null)
                             {
@@ -126,29 +126,29 @@ namespace GodotTools.IdeMessaging.CLI
             }
         }
 
-        private static async Task<Response> SendRequest(Client client, string id, MessageContent content)
+        private static async Task<Response?> SendRequest(Client client, string id, MessageContent content)
         {
-            var handlers = new Dictionary<string, Func<Task<Response>>>
+            var handlers = new Dictionary<string, Func<Task<Response?>>>
             {
                 [PlayRequest.Id] = async () =>
                 {
                     var request = JsonConvert.DeserializeObject<PlayRequest>(content.Body);
-                    return await client.SendRequest<PlayResponse>(request);
+                    return await client.SendRequest<PlayResponse>(request!);
                 },
                 [DebugPlayRequest.Id] = async () =>
                 {
                     var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body);
-                    return await client.SendRequest<DebugPlayResponse>(request);
+                    return await client.SendRequest<DebugPlayResponse>(request!);
                 },
                 [ReloadScriptsRequest.Id] = async () =>
                 {
                     var request = JsonConvert.DeserializeObject<ReloadScriptsRequest>(content.Body);
-                    return await client.SendRequest<ReloadScriptsResponse>(request);
+                    return await client.SendRequest<ReloadScriptsResponse>(request!);
                 },
                 [CodeCompletionRequest.Id] = async () =>
                 {
                     var request = JsonConvert.DeserializeObject<CodeCompletionRequest>(content.Body);
-                    return await client.SendRequest<CodeCompletionResponse>(request);
+                    return await client.SendRequest<CodeCompletionResponse>(request!);
                 }
             };
 

+ 8 - 6
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics.CodeAnalysis;
 using System.Collections.Generic;
 using System.IO;
 using System.Net;
@@ -27,7 +28,7 @@ namespace GodotTools.IdeMessaging
 
         private readonly IMessageHandler messageHandler;
 
-        private Peer peer;
+        private Peer? peer;
         private readonly SemaphoreSlim connectionSem = new SemaphoreSlim(1);
 
         private readonly Queue<NotifyAwaiter<bool>> clientConnectedAwaiters = new Queue<NotifyAwaiter<bool>>();
@@ -53,6 +54,7 @@ namespace GodotTools.IdeMessaging
         public bool IsDisposed { get; private set; }
 
         // ReSharper disable once MemberCanBePrivate.Global
+        [MemberNotNullWhen(true, "peer")]
         public bool IsConnected => peer != null && !peer.IsDisposed && peer.IsTcpClientConnected;
 
         // ReSharper disable once EventNeverSubscribedTo.Global
@@ -111,7 +113,7 @@ namespace GodotTools.IdeMessaging
             if (disposing)
             {
                 peer?.Dispose();
-                fsWatcher?.Dispose();
+                fsWatcher.Dispose();
             }
         }
 
@@ -215,12 +217,12 @@ namespace GodotTools.IdeMessaging
             using (var fileStream = new FileStream(MetaFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
             using (var reader = new StreamReader(fileStream))
             {
-                string portStr = reader.ReadLine();
+                string? portStr = reader.ReadLine();
 
                 if (portStr == null)
                     return null;
 
-                string editorExecutablePath = reader.ReadLine();
+                string? editorExecutablePath = reader.ReadLine();
 
                 if (editorExecutablePath == null)
                     return null;
@@ -333,7 +335,7 @@ namespace GodotTools.IdeMessaging
             }
         }
 
-        public async Task<TResponse> SendRequest<TResponse>(Request request)
+        public async Task<TResponse?> SendRequest<TResponse>(Request request)
             where TResponse : Response, new()
         {
             if (!IsConnected)
@@ -346,7 +348,7 @@ namespace GodotTools.IdeMessaging
             return await peer.SendRequest<TResponse>(request.Id, body);
         }
 
-        public async Task<TResponse> SendRequest<TResponse>(string id, string body)
+        public async Task<TResponse?> SendRequest<TResponse>(string id, string body)
             where TResponse : Response, new()
         {
             if (!IsConnected)

+ 2 - 1
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs

@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
 using System.Text.RegularExpressions;
 
 namespace GodotTools.IdeMessaging
@@ -9,7 +10,7 @@ namespace GodotTools.IdeMessaging
 
         public string GetHandshakeLine(string identity) => $"{ClientHandshakeBase},{identity}";
 
-        public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger)
+        public bool IsValidPeerHandshake(string handshake, [NotNullWhen(true)] out string? identity, ILogger logger)
         {
             identity = null;
 

+ 1 - 1
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs

@@ -42,7 +42,7 @@ namespace GodotTools.IdeMessaging
                 [OpenFileRequest.Id] = async (peer, content) =>
                 {
                     var request = JsonConvert.DeserializeObject<OpenFileRequest>(content.Body);
-                    return await HandleOpenFile(request);
+                    return await HandleOpenFile(request!);
                 }
             };
         }

+ 142 - 0
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/CodeAnalysisAttributes.cs

@@ -0,0 +1,142 @@
+// ReSharper disable once CheckNamespace
+namespace System.Diagnostics.CodeAnalysis
+{
+    /// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
+    internal sealed class AllowNullAttribute : Attribute
+    { }
+
+    /// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
+    internal sealed class DisallowNullAttribute : Attribute
+    { }
+
+    /// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
+    internal sealed class MaybeNullAttribute : Attribute
+    { }
+
+    /// <summary>Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
+    internal sealed class NotNullAttribute : Attribute
+    { }
+
+    /// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
+    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+    internal sealed class MaybeNullWhenAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the specified return value condition.</summary>
+        /// <param name="returnValue">
+        /// The return value condition. If the method returns this value, the associated parameter may be null.
+        /// </param>
+        public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
+
+        /// <summary>Gets the return value condition.</summary>
+        public bool ReturnValue { get; }
+    }
+
+    /// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
+    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+    internal sealed class NotNullWhenAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the specified return value condition.</summary>
+        /// <param name="returnValue">
+        /// The return value condition. If the method returns this value, the associated parameter will not be null.
+        /// </param>
+        public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
+
+        /// <summary>Gets the return value condition.</summary>
+        public bool ReturnValue { get; }
+    }
+
+    /// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
+    [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
+    internal sealed class NotNullIfNotNullAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the associated parameter name.</summary>
+        /// <param name="parameterName">
+        /// The associated parameter name.  The output will be non-null if the argument to the parameter specified is non-null.
+        /// </param>
+        public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
+
+        /// <summary>Gets the associated parameter name.</summary>
+        public string ParameterName { get; }
+    }
+
+    /// <summary>Applied to a method that will never return under any circumstance.</summary>
+    [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+    internal sealed class DoesNotReturnAttribute : Attribute
+    { }
+
+    /// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
+    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+    internal sealed class DoesNotReturnIfAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the specified parameter value.</summary>
+        /// <param name="parameterValue">
+        /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
+        /// the associated parameter matches this value.
+        /// </param>
+        public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
+
+        /// <summary>Gets the condition parameter value.</summary>
+        public bool ParameterValue { get; }
+    }
+
+    /// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values.</summary>
+    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
+    internal sealed class MemberNotNullAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with a field or property member.</summary>
+        /// <param name="member">
+        /// The field or property member that is promised to be not-null.
+        /// </param>
+        public MemberNotNullAttribute(string member) => Members = new[] { member };
+
+        /// <summary>Initializes the attribute with the list of field and property members.</summary>
+        /// <param name="members">
+        /// The list of field and property members that are promised to be not-null.
+        /// </param>
+        public MemberNotNullAttribute(params string[] members) => Members = members;
+
+        /// <summary>Gets field or property member names.</summary>
+        public string[] Members { get; }
+    }
+
+    /// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition.</summary>
+    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
+    internal sealed class MemberNotNullWhenAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
+        /// <param name="returnValue">
+        /// The return value condition. If the method returns this value, the associated parameter will not be null.
+        /// </param>
+        /// <param name="member">
+        /// The field or property member that is promised to be not-null.
+        /// </param>
+        public MemberNotNullWhenAttribute(bool returnValue, string member)
+        {
+            ReturnValue = returnValue;
+            Members = new[] { member };
+        }
+
+        /// <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary>
+        /// <param name="returnValue">
+        /// The return value condition. If the method returns this value, the associated parameter will not be null.
+        /// </param>
+        /// <param name="members">
+        /// The list of field and property members that are promised to be not-null.
+        /// </param>
+        public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
+        {
+            ReturnValue = returnValue;
+            Members = members;
+        }
+
+        /// <summary>Gets the return value condition.</summary>
+        public bool ReturnValue { get; }
+
+        /// <summary>Gets field or property member names.</summary>
+        public string[] Members { get; }
+    }
+}

+ 3 - 1
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs

@@ -1,3 +1,5 @@
+using System.Diagnostics.CodeAnalysis;
+
 namespace GodotTools.IdeMessaging
 {
     public readonly struct GodotIdeMetadata
@@ -23,7 +25,7 @@ namespace GodotTools.IdeMessaging
             return !(a == b);
         }
 
-        public override bool Equals(object obj)
+        public override bool Equals([NotNullWhen(true)] object? obj)
         {
             return obj is GodotIdeMetadata metadata && metadata == this;
         }

+ 3 - 2
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj

@@ -2,9 +2,10 @@
   <PropertyGroup>
     <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid>
     <TargetFramework>netstandard2.0</TargetFramework>
-    <LangVersion>7.2</LangVersion>
+    <LangVersion>9</LangVersion>
+    <Nullable>enable</Nullable>
     <PackageId>GodotTools.IdeMessaging</PackageId>
-    <Version>1.1.1</Version>
+    <Version>1.1.2</Version>
     <AssemblyVersion>$(Version)</AssemblyVersion>
     <Authors>Godot Engine contributors</Authors>
     <Company />

+ 3 - 1
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs

@@ -1,8 +1,10 @@
+using System.Diagnostics.CodeAnalysis;
+
 namespace GodotTools.IdeMessaging
 {
     public interface IHandshake
     {
         string GetHandshakeLine(string identity);
-        bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger);
+        bool IsValidPeerHandshake(string handshake, [NotNullWhen(true)] out string? identity, ILogger logger);
     }
 }

+ 2 - 2
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs

@@ -8,7 +8,7 @@ namespace GodotTools.IdeMessaging
         private class DecodedMessage
         {
             public MessageKind? Kind;
-            public string Id;
+            public string? Id;
             public MessageStatus? Status;
             public readonly StringBuilder Body = new StringBuilder();
             public uint? PendingBodyLines;
@@ -41,7 +41,7 @@ namespace GodotTools.IdeMessaging
 
         private readonly DecodedMessage decodingMessage = new DecodedMessage();
 
-        public State Decode(string messageLine, out Message decodedMessage)
+        public State Decode(string messageLine, out Message? decodedMessage)
         {
             decodedMessage = null;
 

+ 14 - 14
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs

@@ -46,11 +46,11 @@ namespace GodotTools.IdeMessaging
 
         private readonly SemaphoreSlim writeSem = new SemaphoreSlim(1);
 
-        private string remoteIdentity = string.Empty;
-        public string RemoteIdentity => remoteIdentity;
+        private string? remoteIdentity;
+        public string RemoteIdentity => remoteIdentity ??= string.Empty;
 
-        public event Action Connected;
-        public event Action Disconnected;
+        public event Action? Connected;
+        public event Action? Disconnected;
 
         private ILogger Logger { get; }
 
@@ -87,7 +87,7 @@ namespace GodotTools.IdeMessaging
             {
                 var decoder = new MessageDecoder();
 
-                string messageLine;
+                string? messageLine;
                 while ((messageLine = await ReadLine()) != null)
                 {
                     var state = decoder.Decode(messageLine, out var msg);
@@ -105,7 +105,7 @@ namespace GodotTools.IdeMessaging
 
                     try
                     {
-                        if (msg.Kind == MessageKind.Request)
+                        if (msg!.Kind == MessageKind.Request)
                         {
                             var responseContent = await messageHandler.HandleRequest(this, msg.Id, msg.Content, Logger);
                             await WriteMessage(new Message(MessageKind.Response, msg.Id, responseContent));
@@ -163,9 +163,9 @@ namespace GodotTools.IdeMessaging
                 return false;
             }
 
-            string peerHandshake = await readHandshakeTask;
+            string? peerHandshake = await readHandshakeTask;
 
-            if (handshake == null || !handshake.IsValidPeerHandshake(peerHandshake, out remoteIdentity, Logger))
+            if (peerHandshake == null || !handshake.IsValidPeerHandshake(peerHandshake, out remoteIdentity, Logger))
             {
                 Logger.LogError("Received invalid handshake: " + peerHandshake);
                 return false;
@@ -179,7 +179,7 @@ namespace GodotTools.IdeMessaging
             return true;
         }
 
-        private async Task<string> ReadLine()
+        private async Task<string?> ReadLine()
         {
             try
             {
@@ -216,7 +216,7 @@ namespace GodotTools.IdeMessaging
             return WriteLine(builder.ToString());
         }
 
-        public async Task<TResponse> SendRequest<TResponse>(string id, string body)
+        public async Task<TResponse?> SendRequest<TResponse>(string id, string body)
             where TResponse : Response, new()
         {
             ResponseAwaiter responseAwaiter;
@@ -243,7 +243,7 @@ namespace GodotTools.IdeMessaging
 
         private async Task<bool> WriteLine(string text)
         {
-            if (clientWriter == null || IsDisposed || !IsTcpClientConnected)
+            if (IsDisposed || !IsTcpClientConnected)
                 return false;
 
             using (await writeSem.UseAsync())
@@ -290,9 +290,9 @@ namespace GodotTools.IdeMessaging
                     Disconnected?.Invoke();
             }
 
-            clientReader?.Dispose();
-            clientWriter?.Dispose();
-            ((IDisposable)tcpClient)?.Dispose();
+            clientReader.Dispose();
+            clientWriter.Dispose();
+            ((IDisposable)tcpClient).Dispose();
         }
     }
 }

+ 6 - 5
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs

@@ -2,6 +2,7 @@
 // ReSharper disable UnusedMember.Global
 // ReSharper disable UnusedAutoPropertyAccessor.Global
 
+using System;
 using Newtonsoft.Json;
 
 namespace GodotTools.IdeMessaging.Requests
@@ -38,7 +39,7 @@ namespace GodotTools.IdeMessaging.Requests
         }
 
         public CompletionKind Kind { get; set; }
-        public string ScriptFile { get; set; }
+        public string ScriptFile { get; set; } = string.Empty;
 
         public new const string Id = "CodeCompletion";
 
@@ -50,8 +51,8 @@ namespace GodotTools.IdeMessaging.Requests
     public sealed class CodeCompletionResponse : Response
     {
         public CodeCompletionRequest.CompletionKind Kind;
-        public string ScriptFile { get; set; }
-        public string[] Suggestions { get; set; }
+        public string ScriptFile { get; set; } = string.Empty;
+        public string[] Suggestions { get; set; } = Array.Empty<string>();
     }
 
     public sealed class PlayRequest : Request
@@ -82,7 +83,7 @@ namespace GodotTools.IdeMessaging.Requests
 
     public sealed class DebugPlayRequest : Request
     {
-        public string DebuggerHost { get; set; }
+        public string DebuggerHost { get; set; } = string.Empty;
         public int DebuggerPort { get; set; }
         public bool? BuildBeforePlaying { get; set; }
 
@@ -99,7 +100,7 @@ namespace GodotTools.IdeMessaging.Requests
 
     public sealed class OpenFileRequest : Request
     {
-        public string File { get; set; }
+        public string File { get; set; } = string.Empty;
         public int? Line { get; set; }
         public int? Column { get; set; }
 

+ 1 - 1
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs

@@ -15,7 +15,7 @@ namespace GodotTools.IdeMessaging
         public override void SetResult(MessageContent content)
         {
             if (content.Status == MessageStatus.Ok)
-                SetResult(JsonConvert.DeserializeObject<T>(content.Body));
+                SetResult(JsonConvert.DeserializeObject<T>(content.Body)!);
             else
                 SetResult(new T { Status = content.Status });
         }

+ 7 - 4
modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs

@@ -1,3 +1,6 @@
+// ReSharper disable ParameterHidesMember
+// ReSharper disable UnusedMember.Global
+
 using System;
 using System.Runtime.CompilerServices;
 
@@ -5,9 +8,9 @@ namespace GodotTools.IdeMessaging.Utils
 {
     public class NotifyAwaiter<T> : INotifyCompletion
     {
-        private Action continuation;
-        private Exception exception;
-        private T result;
+        private Action? continuation;
+        private Exception? exception;
+        private T? result;
 
         public bool IsCompleted { get; private set; }
 
@@ -15,7 +18,7 @@ namespace GodotTools.IdeMessaging.Utils
         {
             if (exception != null)
                 throw exception;
-            return result;
+            return result!;
         }
 
         public void OnCompleted(Action continuation)

+ 1 - 0
modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj

@@ -4,6 +4,7 @@
     <OutputType>Exe</OutputType>
     <TargetFramework>net6.0-windows</TargetFramework>
     <LangVersion>10</LangVersion>
+    <Nullable>enable</Nullable>
   </PropertyGroup>
   <PropertyGroup Condition="Exists('$(SolutionDir)/../../../../bin/GodotSharp/Api/Debug/GodotSharp.dll') And ('$(GodotPlatform)' == 'windows' Or ('$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT'))">
     <OutputPath>$(SolutionDir)/../../../../bin/GodotSharp/Tools</OutputPath>

+ 11 - 5
modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs

@@ -53,6 +53,12 @@ namespace GodotTools.OpenVisualStudio
                 {
                     // Launch of VS 2022 failed, fallback to 2019
                     dte = TryVisualStudioLaunch("VisualStudio.DTE.16.0");
+
+                    if (dte == null)
+                    {
+                        Console.Error.WriteLine("Visual Studio not found");
+                        return 1;
+                    }
                 }
 
                 dte.UserControl = true;
@@ -137,12 +143,12 @@ namespace GodotTools.OpenVisualStudio
             return 0;
         }
 
-        private static DTE TryVisualStudioLaunch(string version)
+        private static DTE? TryVisualStudioLaunch(string version)
         {
             try
             {
                 var visualStudioDteType = Type.GetTypeFromProgID(version, throwOnError: true);
-                var dte = (DTE)Activator.CreateInstance(visualStudioDteType);
+                var dte = (DTE?)Activator.CreateInstance(visualStudioDteType!);
 
                 return dte;
             }
@@ -152,7 +158,7 @@ namespace GodotTools.OpenVisualStudio
             }
         }
 
-        private static DTE FindInstanceEditingSolution(string solutionPath)
+        private static DTE? FindInstanceEditingSolution(string solutionPath)
         {
             if (GetRunningObjectTable(0, out IRunningObjectTable pprot) != 0)
                 return null;
@@ -218,7 +224,7 @@ namespace GodotTools.OpenVisualStudio
             // Class containing the IOleMessageFilter
             // thread error-handling functions
 
-            private static IOleMessageFilter _oldFilter;
+            private static IOleMessageFilter? _oldFilter;
 
             // Start the filter
             public static void Register()
@@ -268,7 +274,7 @@ namespace GodotTools.OpenVisualStudio
 
             // Implement the IOleMessageFilter interface
             [DllImport("ole32.dll")]
-            private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter);
+            private static extern int CoRegisterMessageFilter(IOleMessageFilter? newFilter, out IOleMessageFilter? oldFilter);
         }
 
         [ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]

+ 13 - 11
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs

@@ -34,22 +34,23 @@ EndProject";
 @"		{{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU
 		{{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU";
 
-        private string _directoryPath;
         private readonly Dictionary<string, ProjectInfo> _projects = new Dictionary<string, ProjectInfo>();
 
         public string Name { get; }
-
-        public string DirectoryPath
-        {
-            get => _directoryPath;
-            set => _directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value);
-        }
+        public string DirectoryPath { get; }
 
         public class ProjectInfo
         {
-            public string Guid;
-            public string PathRelativeToSolution;
-            public List<string> Configs = new List<string>();
+            public string Guid { get; }
+            public string PathRelativeToSolution { get; }
+            public List<string> Configs { get; }
+
+            public ProjectInfo(string guid, string pathRelativeToSolution, List<string> configs)
+            {
+                Guid = guid;
+                PathRelativeToSolution = pathRelativeToSolution;
+                Configs = configs;
+            }
         }
 
         public void AddNewProject(string name, ProjectInfo projectInfo)
@@ -117,9 +118,10 @@ EndProject";
             File.WriteAllText(solutionPath, content, Encoding.UTF8); // UTF-8 with BOM
         }
 
-        public DotNetSolution(string name)
+        public DotNetSolution(string name, string directoryPath)
         {
             Name = name;
+            DirectoryPath = directoryPath.IsAbsolutePath() ? directoryPath : Path.GetFullPath(directoryPath);
         }
 
         public static void MigrateFromOldConfigNames(string slnPath)

+ 1 - 0
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj

@@ -3,6 +3,7 @@
     <ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid>
     <TargetFramework>net6.0</TargetFramework>
     <LangVersion>10</LangVersion>
+    <Nullable>enable</Nullable>
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.Build" Version="15.1.548" ExcludeAssets="runtime" />

+ 1 - 1
modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs

@@ -34,7 +34,7 @@ namespace GodotTools.ProjectEditor
         public static void MSBuildLocatorRegisterMSBuildPath(string msbuildPath)
             => MSBuildLocator.RegisterMSBuildPath(msbuildPath);
 
-        public static MSBuildProject Open(string path)
+        public static MSBuildProject? Open(string path)
         {
             var root = ProjectRootElement.Open(path);
             return root != null ? new MSBuildProject(root) : null;

+ 0 - 2
modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs

@@ -1,5 +1,3 @@
-#nullable enable
-
 namespace GodotTools.Build
 {
     public class BuildDiagnostic

+ 2 - 3
modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs

@@ -1,11 +1,10 @@
 using System;
+using System.Diagnostics.CodeAnalysis;
 using Godot;
 using Godot.Collections;
 using GodotTools.Internals;
 using Path = System.IO.Path;
 
-#nullable enable
-
 namespace GodotTools.Build
 {
     [Serializable]
@@ -25,7 +24,7 @@ namespace GodotTools.Build
 
         public string LogsDirPath => GodotSharpDirs.LogsDirPathFor(Solution, Configuration);
 
-        public override bool Equals(object? obj)
+        public override bool Equals([NotNullWhen(true)] object? obj)
         {
             return obj is BuildInfo other &&
                 other.Solution == Solution &&

+ 19 - 19
modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs

@@ -11,18 +11,18 @@ namespace GodotTools.Build
 {
     public static class BuildManager
     {
-        private static BuildInfo _buildInProgress;
+        private static BuildInfo? _buildInProgress;
 
         public const string MsBuildIssuesFileName = "msbuild_issues.csv";
         private const string MsBuildLogFileName = "msbuild_log.txt";
 
         public delegate void BuildLaunchFailedEventHandler(BuildInfo buildInfo, string reason);
 
-        public static event BuildLaunchFailedEventHandler BuildLaunchFailed;
-        public static event Action<BuildInfo> BuildStarted;
-        public static event Action<BuildResult> BuildFinished;
-        public static event Action<string> StdOutputReceived;
-        public static event Action<string> StdErrorReceived;
+        public static event BuildLaunchFailedEventHandler? BuildLaunchFailed;
+        public static event Action<BuildInfo>? BuildStarted;
+        public static event Action<BuildResult>? BuildFinished;
+        public static event Action<string?>? StdOutputReceived;
+        public static event Action<string?>? StdErrorReceived;
 
         private static void RemoveOldIssuesFile(BuildInfo buildInfo)
         {
@@ -260,8 +260,8 @@ namespace GodotTools.Build
         }
 
         private static BuildInfo CreateBuildInfo(
-            [DisallowNull] string configuration,
-            [AllowNull] string platform = null,
+            string configuration,
+            string? platform = null,
             bool rebuild = false,
             bool onlyClean = false
         )
@@ -280,10 +280,10 @@ namespace GodotTools.Build
         }
 
         private static BuildInfo CreatePublishBuildInfo(
-            [DisallowNull] string configuration,
-            [DisallowNull] string platform,
-            [DisallowNull] string runtimeIdentifier,
-            [DisallowNull] string publishOutputDir,
+            string configuration,
+            string platform,
+            string runtimeIdentifier,
+            string publishOutputDir,
             bool includeDebugSymbols = true
         )
         {
@@ -305,20 +305,20 @@ namespace GodotTools.Build
         }
 
         public static bool BuildProjectBlocking(
-            [DisallowNull] string configuration,
-            [AllowNull] string platform = null,
+            string configuration,
+            string? platform = null,
             bool rebuild = false
         ) => BuildProjectBlocking(CreateBuildInfo(configuration, platform, rebuild));
 
         public static bool CleanProjectBlocking(
-            [DisallowNull] string configuration,
-            [AllowNull] string platform = null
+            string configuration,
+            string? platform = null
         ) => CleanProjectBlocking(CreateBuildInfo(configuration, platform, rebuild: false, onlyClean: true));
 
         public static bool PublishProjectBlocking(
-            [DisallowNull] string configuration,
-            [DisallowNull] string platform,
-            [DisallowNull] string runtimeIdentifier,
+            string configuration,
+            string platform,
+            string runtimeIdentifier,
             string publishOutputDir,
             bool includeDebugSymbols = true
         ) => PublishProjectBlocking(CreatePublishBuildInfo(configuration,

+ 0 - 2
modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs

@@ -1,8 +1,6 @@
 using Godot;
 using static GodotTools.Internals.Globals;
 
-#nullable enable
-
 namespace GodotTools.Build
 {
     public partial class BuildOutputView : HBoxContainer

+ 0 - 2
modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs

@@ -1,7 +1,5 @@
 using Godot;
 
-#nullable enable
-
 namespace GodotTools.Build
 {
     public class BuildProblemsFilter

+ 3 - 3
modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs

@@ -8,8 +8,6 @@ using GodotTools.Internals;
 using static GodotTools.Internals.Globals;
 using FileAccess = Godot.FileAccess;
 
-#nullable enable
-
 namespace GodotTools.Build
 {
     public partial class BuildProblemsView : HBoxContainer
@@ -244,7 +242,9 @@ namespace GodotTools.Build
             if (string.IsNullOrEmpty(projectDir))
                 return;
 
-            string file = Path.Combine(projectDir.SimplifyGodotPath(), diagnostic.File.SimplifyGodotPath());
+            string? file = !string.IsNullOrEmpty(diagnostic.File) ?
+                Path.Combine(projectDir.SimplifyGodotPath(), diagnostic.File.SimplifyGodotPath()) :
+                null;
 
             if (!File.Exists(file))
                 return;

+ 12 - 12
modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs

@@ -17,10 +17,10 @@ namespace GodotTools.Build
 {
     public static class BuildSystem
     {
-        private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler,
-            Action<string> stdErrHandler)
+        private static Process LaunchBuild(BuildInfo buildInfo, Action<string?>? stdOutHandler,
+            Action<string?>? stdErrHandler)
         {
-            string dotnetPath = DotNetFinder.FindDotNetExe();
+            string? dotnetPath = DotNetFinder.FindDotNetExe();
 
             if (dotnetPath == null)
                 throw new FileNotFoundException("Cannot find the dotnet executable.");
@@ -67,7 +67,7 @@ namespace GodotTools.Build
             return process;
         }
 
-        public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
+        public static int Build(BuildInfo buildInfo, Action<string?>? stdOutHandler, Action<string?>? stdErrHandler)
         {
             using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
             {
@@ -77,8 +77,8 @@ namespace GodotTools.Build
             }
         }
 
-        public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler,
-            Action<string> stdErrHandler)
+        public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string?>? stdOutHandler,
+            Action<string?>? stdErrHandler)
         {
             using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
             {
@@ -88,10 +88,10 @@ namespace GodotTools.Build
             }
         }
 
-        private static Process LaunchPublish(BuildInfo buildInfo, Action<string> stdOutHandler,
-            Action<string> stdErrHandler)
+        private static Process LaunchPublish(BuildInfo buildInfo, Action<string?>? stdOutHandler,
+            Action<string?>? stdErrHandler)
         {
-            string dotnetPath = DotNetFinder.FindDotNetExe();
+            string? dotnetPath = DotNetFinder.FindDotNetExe();
 
             if (dotnetPath == null)
                 throw new FileNotFoundException("Cannot find the dotnet executable.");
@@ -137,7 +137,7 @@ namespace GodotTools.Build
             return process;
         }
 
-        public static int Publish(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
+        public static int Publish(BuildInfo buildInfo, Action<string?>? stdOutHandler, Action<string?>? stdErrHandler)
         {
             using (var process = LaunchPublish(buildInfo, stdOutHandler, stdErrHandler))
             {
@@ -297,7 +297,7 @@ namespace GodotTools.Build
         }
 
         private static Process DoGenerateXCFramework(List<string> outputPaths, string xcFrameworkPath,
-            Action<string> stdOutHandler, Action<string> stdErrHandler)
+            Action<string?>? stdOutHandler, Action<string?>? stdErrHandler)
         {
             if (Directory.Exists(xcFrameworkPath))
             {
@@ -341,7 +341,7 @@ namespace GodotTools.Build
             return process;
         }
 
-        public static int GenerateXCFramework(List<string> outputPaths, string xcFrameworkPath, Action<string> stdOutHandler, Action<string> stdErrHandler)
+        public static int GenerateXCFramework(List<string> outputPaths, string xcFrameworkPath, Action<string?>? stdOutHandler, Action<string?>? stdErrHandler)
         {
             using (var process = DoGenerateXCFramework(outputPaths, xcFrameworkPath, stdOutHandler, stdErrHandler))
             {

+ 6 - 8
modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs

@@ -5,15 +5,13 @@ using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Runtime.InteropServices;
 using System.Text;
-using JetBrains.Annotations;
 using OS = GodotTools.Utils.OS;
 
 namespace GodotTools.Build
 {
     public static class DotNetFinder
     {
-        [CanBeNull]
-        public static string FindDotNetExe()
+        public static string? FindDotNetExe()
         {
             // In the future, this method may do more than just search in PATH. We could look in
             // known locations or use Godot's linked nethost to search from the hostfxr location.
@@ -40,14 +38,14 @@ namespace GodotTools.Build
 
         public static bool TryFindDotNetSdk(
             Version expectedVersion,
-            [NotNullWhen(true)] out Version version,
-            [NotNullWhen(true)] out string path
+            [NotNullWhen(true)] out Version? version,
+            [NotNullWhen(true)] out string? path
         )
         {
             version = null;
             path = null;
 
-            string dotNetExe = FindDotNetExe();
+            string? dotNetExe = FindDotNetExe();
 
             if (string.IsNullOrEmpty(dotNetExe))
                 return false;
@@ -86,8 +84,8 @@ namespace GodotTools.Build
             process.BeginOutputReadLine();
             process.WaitForExit();
 
-            Version latestVersionMatch = null;
-            string matchPath = null;
+            Version? latestVersionMatch = null;
+            string? matchPath = null;
 
             foreach (var line in lines)
             {

+ 2 - 4
modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs

@@ -5,8 +5,6 @@ using GodotTools.Internals;
 using static GodotTools.Internals.Globals;
 using File = GodotTools.Utils.File;
 
-#nullable enable
-
 namespace GodotTools.Build
 {
     public partial class MSBuildPanel : MarginContainer, ISerializationListener
@@ -177,7 +175,7 @@ namespace GodotTools.Build
             }
         }
 
-        private void StdOutputReceived(string text)
+        private void StdOutputReceived(string? text)
         {
             lock (_pendingBuildLogTextLock)
             {
@@ -187,7 +185,7 @@ namespace GodotTools.Build
             }
         }
 
-        private void StdErrorReceived(string text)
+        private void StdErrorReceived(string? text)
         {
             lock (_pendingBuildLogTextLock)
             {

+ 5 - 4
modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs

@@ -1,6 +1,7 @@
 using Godot;
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
 using System.Security.Cryptography;
@@ -73,7 +74,7 @@ namespace GodotTools.Export
             };
         }
 
-        private string _maybeLastExportError;
+        private string? _maybeLastExportError;
 
         // With this method we can override how a file is exported in the PCK
         public override void _ExportFile(string path, string type, string[] features)
@@ -135,7 +136,7 @@ namespace GodotTools.Export
             if (!ProjectContainsDotNet())
                 return;
 
-            if (!DeterminePlatformFromFeatures(features, out string platform))
+            if (!DeterminePlatformFromFeatures(features, out string? platform))
                 throw new NotSupportedException("Target platform not supported.");
 
             if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS }
@@ -327,7 +328,7 @@ namespace GodotTools.Export
                                         AddSharedObject(path, tags: null,
                                             Path.Join(projectDataDirName,
                                                 Path.GetRelativePath(publishOutputDir,
-                                                    Path.GetDirectoryName(path))));
+                                                    Path.GetDirectoryName(path)!)));
                                     }
                                 }
                             }
@@ -450,7 +451,7 @@ namespace GodotTools.Export
             }
         }
 
-        private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, out string platform)
+        private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, [NotNullWhen(true)] out string? platform)
         {
             foreach (var feature in features)
             {

+ 6 - 6
modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs

@@ -5,7 +5,7 @@ namespace GodotTools.Export
 {
     public static class XcodeHelper
     {
-        private static string _XcodePath = null;
+        private static string? _XcodePath = null;
 
         public static string XcodePath
         {
@@ -23,7 +23,7 @@ namespace GodotTools.Export
             }
         }
 
-        private static string FindSelectedXcode()
+        private static string? FindSelectedXcode()
         {
             var outputWrapper = new Godot.Collections.Array();
 
@@ -40,9 +40,9 @@ namespace GodotTools.Export
             return null;
         }
 
-        public static string FindXcode()
+        public static string? FindXcode()
         {
-            string selectedXcode = FindSelectedXcode();
+            string? selectedXcode = FindSelectedXcode();
             if (selectedXcode != null)
             {
                 if (Directory.Exists(Path.Combine(selectedXcode, "Contents", "Developer")))
@@ -50,10 +50,10 @@ namespace GodotTools.Export
 
                 // The path already pointed to Contents/Developer
                 var dirInfo = new DirectoryInfo(selectedXcode);
-                if (dirInfo.Name != "Developer" || dirInfo.Parent.Name != "Contents")
+                if (dirInfo is not { Parent.Name: "Contents", Name: "Developer" })
                 {
                     Console.WriteLine(Path.GetDirectoryName(selectedXcode));
-                    Console.WriteLine(System.IO.Directory.GetParent(selectedXcode).Name);
+                    Console.WriteLine(System.IO.Directory.GetParent(selectedXcode)?.Name);
                     Console.Error.WriteLine("Unrecognized path for selected Xcode");
                 }
                 else

+ 13 - 16
modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs

@@ -34,6 +34,7 @@ namespace GodotTools
             public const string ProblemsLayout = "dotnet/build/problems_layout";
         }
 
+#nullable disable
         private EditorSettings _editorSettings;
 
         private PopupMenu _menuPopup;
@@ -51,6 +52,7 @@ namespace GodotTools
         public GodotIdeManager GodotIdeManager { get; private set; }
 
         public MSBuildPanel MSBuildPanel { get; private set; }
+#nullable enable
 
         public bool SkipBuildBeforePlaying { get; set; } = false;
 
@@ -69,29 +71,23 @@ namespace GodotTools
 
         private bool CreateProjectSolution()
         {
-            string errorMessage = null;
+            string? errorMessage = null;
             using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2))
             {
                 pr.Step("Generating C# project...".TTR());
 
-                string csprojDir = Path.GetDirectoryName(GodotSharpDirs.ProjectCsProjPath);
-                string slnDir = Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath);
+                string csprojDir = Path.GetDirectoryName(GodotSharpDirs.ProjectCsProjPath)!;
+                string slnDir = Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath)!;
                 string name = GodotSharpDirs.ProjectAssemblyName;
                 string guid = CsProjOperations.GenerateGameProject(csprojDir, name);
 
                 if (guid.Length > 0)
                 {
-                    var solution = new DotNetSolution(name)
-                    {
-                        DirectoryPath = slnDir
-                    };
+                    var solution = new DotNetSolution(name, slnDir);
 
-                    var projectInfo = new DotNetSolution.ProjectInfo
-                    {
-                        Guid = guid,
-                        PathRelativeToSolution = Path.GetRelativePath(slnDir, GodotSharpDirs.ProjectCsProjPath),
-                        Configs = new List<string> { "Debug", "ExportDebug", "ExportRelease" }
-                    };
+                    var projectInfo = new DotNetSolution.ProjectInfo(guid,
+                        Path.GetRelativePath(slnDir, GodotSharpDirs.ProjectCsProjPath),
+                        new List<string> { "Debug", "ExportDebug", "ExportRelease" });
 
                     solution.AddNewProject(name, projectInfo);
 
@@ -344,7 +340,7 @@ namespace GodotTools
                         }
                     }
 
-                    args.Add(Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath));
+                    args.Add(Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath)!);
 
                     string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
 
@@ -478,7 +474,7 @@ namespace GodotTools
 
             // First we try to find the .NET Sdk ourselves to make sure we get the
             // correct version first, otherwise pick the latest.
-            if (DotNetFinder.TryFindDotNetSdk(dotNetSdkSearchVersion, out var sdkVersion, out string sdkPath))
+            if (DotNetFinder.TryFindDotNetSdk(dotNetSdkSearchVersion, out var sdkVersion, out string? sdkPath))
             {
                 if (Godot.OS.IsStdOutVerbose())
                     Console.WriteLine($"Found .NET Sdk version '{sdkVersion}': {sdkPath}");
@@ -715,8 +711,9 @@ namespace GodotTools
         }
 
         // Singleton
-
+#nullable disable
         public static GodotSharpEditor Instance { get; private set; }
+#nullable enable
 
         [UsedImplicitly]
         private static IntPtr InternalCreateInstance(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)

+ 1 - 0
modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj

@@ -4,6 +4,7 @@
     <TargetFramework>net6.0</TargetFramework>
     <EnableDynamicLoading>true</EnableDynamicLoading>
     <LangVersion>10</LangVersion>
+    <Nullable>enable</Nullable>
     <!-- The Godot editor uses the Debug Godot API assemblies -->
     <GodotApiConfiguration>Debug</GodotApiConfiguration>
     <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>

+ 2 - 0
modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs

@@ -7,7 +7,9 @@ namespace GodotTools
 {
     public partial class HotReloadAssemblyWatcher : Node
     {
+#nullable disable
         private Timer _watchTimer;
+#nullable enable
 
         public override void _Notification(int what)
         {

+ 4 - 4
modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs

@@ -10,10 +10,10 @@ namespace GodotTools.Ides
 {
     public sealed partial class GodotIdeManager : Node, ISerializationListener
     {
-        private MessagingServer _messagingServer;
+        private MessagingServer? _messagingServer;
 
-        private MonoDevelop.Instance _monoDevelInstance;
-        private MonoDevelop.Instance _vsForMacInstance;
+        private MonoDevelop.Instance? _monoDevelInstance;
+        private MonoDevelop.Instance? _vsForMacInstance;
 
         private MessagingServer GetRunningOrNewServer()
         {
@@ -59,7 +59,7 @@ namespace GodotTools.Ides
             switch (editorId)
             {
                 case ExternalEditorId.None:
-                    return null;
+                    return string.Empty;
                 case ExternalEditorId.VisualStudio:
                     return "VisualStudio";
                 case ExternalEditorId.VsCode:

+ 9 - 8
modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Linq;
 using System.Net;
@@ -97,9 +98,9 @@ namespace GodotTools.Ides
                 foreach (var connection in Peers)
                     connection.Dispose();
                 Peers.Clear();
-                _listener?.Stop();
+                _listener.Stop();
 
-                _metaFile?.Dispose();
+                _metaFile.Dispose();
 
                 File.Delete(_metaFilePath);
             }
@@ -122,7 +123,7 @@ namespace GodotTools.Ides
             _listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0));
             _listener.Start();
 
-            int port = ((IPEndPoint)_listener.Server.LocalEndPoint).Port;
+            int port = ((IPEndPoint?)_listener.Server.LocalEndPoint)?.Port ?? 0;
             using (var metaFileWriter = new StreamWriter(_metaFile, Encoding.UTF8))
             {
                 metaFileWriter.WriteLine(port);
@@ -235,7 +236,7 @@ namespace GodotTools.Ides
 
             public string GetHandshakeLine(string identity) => $"{_serverHandshakeBase},{identity}";
 
-            public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger)
+            public bool IsValidPeerHandshake(string handshake, [NotNullWhen(true)] out string? identity, ILogger logger)
             {
                 identity = null;
 
@@ -311,12 +312,12 @@ namespace GodotTools.Ides
                     [DebugPlayRequest.Id] = async (peer, content) =>
                     {
                         var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body);
-                        return await HandleDebugPlay(request);
+                        return await HandleDebugPlay(request!);
                     },
                     [StopPlayRequest.Id] = async (peer, content) =>
                     {
                         var request = JsonConvert.DeserializeObject<StopPlayRequest>(content.Body);
-                        return await HandleStopPlay(request);
+                        return await HandleStopPlay(request!);
                     },
                     [ReloadScriptsRequest.Id] = async (peer, content) =>
                     {
@@ -326,7 +327,7 @@ namespace GodotTools.Ides
                     [CodeCompletionRequest.Id] = async (peer, content) =>
                     {
                         var request = JsonConvert.DeserializeObject<CodeCompletionRequest>(content.Body);
-                        return await HandleCodeCompletionRequest(request);
+                        return await HandleCodeCompletionRequest(request!);
                     }
                 };
             }
@@ -383,7 +384,7 @@ namespace GodotTools.Ides
             {
                 // This is needed if the "resource path" part of the path is case insensitive.
                 // However, it doesn't fix resource loading if the rest of the path is also case insensitive.
-                string scriptFileLocalized = FsPathUtils.LocalizePathWithCaseChecked(request.ScriptFile);
+                string? scriptFileLocalized = FsPathUtils.LocalizePathWithCaseChecked(request.ScriptFile);
 
                 // The node API can only be called from the main thread.
                 await Godot.Engine.GetMainLoop().ToSignal(Godot.Engine.GetMainLoop(), "process_frame");

+ 4 - 2
modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs

@@ -13,7 +13,7 @@ namespace GodotTools.Ides.MonoDevelop
         private readonly string _solutionFile;
         private readonly EditorId _editorId;
 
-        private Process _process;
+        private Process? _process;
 
         public bool IsRunning => _process != null && !_process.HasExited;
         public bool IsDisposed { get; private set; }
@@ -24,7 +24,7 @@ namespace GodotTools.Ides.MonoDevelop
 
             var args = new List<string>();
 
-            string command;
+            string? command;
 
             if (OS.IsMacOS)
             {
@@ -136,6 +136,8 @@ namespace GodotTools.Ides.MonoDevelop
                     {EditorId.MonoDevelop, "monodevelop"}
                 };
             }
+            ExecutableNames ??= new Dictionary<EditorId, string>();
+            BundleIds ??= new Dictionary<EditorId, string>();
         }
     }
 }

+ 5 - 5
modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs

@@ -20,12 +20,12 @@ public class RiderLocatorEnvironment : IRiderLocatorEnvironment
         }
     }
 
-    public T FromJson<T>(string json)
+    public T? FromJson<T>(string json)
     {
         return JsonConvert.DeserializeObject<T>(json);
     }
 
-    public void Info(string message, Exception e = null)
+    public void Info(string message, Exception? e = null)
     {
         if (e == null)
             GD.Print(message);
@@ -33,7 +33,7 @@ public class RiderLocatorEnvironment : IRiderLocatorEnvironment
             GD.Print(message, e);
     }
 
-    public void Warn(string message, Exception e = null)
+    public void Warn(string message, Exception? e = null)
     {
         if (e == null)
             GD.PushWarning(message);
@@ -41,7 +41,7 @@ public class RiderLocatorEnvironment : IRiderLocatorEnvironment
             GD.PushWarning(message, e);
     }
 
-    public void Error(string message, Exception e = null)
+    public void Error(string message, Exception? e = null)
     {
         if (e == null)
             GD.PushError(message);
@@ -49,7 +49,7 @@ public class RiderLocatorEnvironment : IRiderLocatorEnvironment
             GD.PushError(message, e);
     }
 
-    public void Verbose(string message, Exception e = null)
+    public void Verbose(string message, Exception? e = null)
     {
         // do nothing, since IDK how to write only to the log, without spamming the output
     }

+ 0 - 2
modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs

@@ -5,8 +5,6 @@ using Godot;
 using GodotTools.Internals;
 using JetBrains.Rider.PathLocator;
 
-#nullable enable
-
 namespace GodotTools.Ides.Rider
 {
     public static class RiderPathManager

+ 7 - 5
modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs

@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using Godot;
 using Godot.NativeInterop;
@@ -59,16 +60,17 @@ namespace GodotTools.Internals
             }
         }
 
+        [MemberNotNull("_projectAssemblyName", "_projectSlnPath", "_projectCsProjPath")]
         public static void DetermineProjectLocation()
         {
-            _projectAssemblyName = (string)ProjectSettings.GetSetting("dotnet/project/assembly_name");
+            _projectAssemblyName = (string?)ProjectSettings.GetSetting("dotnet/project/assembly_name");
             if (string.IsNullOrEmpty(_projectAssemblyName))
             {
                 _projectAssemblyName = CSharpProjectName;
                 ProjectSettings.SetSetting("dotnet/project/assembly_name", _projectAssemblyName);
             }
 
-            string slnParentDir = (string)ProjectSettings.GetSetting("dotnet/project/solution_directory");
+            string? slnParentDir = (string?)ProjectSettings.GetSetting("dotnet/project/solution_directory");
             if (string.IsNullOrEmpty(slnParentDir))
                 slnParentDir = "res://";
             else if (!slnParentDir.StartsWith("res://"))
@@ -84,9 +86,9 @@ namespace GodotTools.Internals
                 string.Concat(_projectAssemblyName, ".csproj"));
         }
 
-        private static string _projectAssemblyName;
-        private static string _projectSlnPath;
-        private static string _projectCsProjPath;
+        private static string? _projectAssemblyName;
+        private static string? _projectSlnPath;
+        private static string? _projectCsProjPath;
 
         public static string ProjectAssemblyName
         {

+ 5 - 3
modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs

@@ -1,17 +1,19 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 
 namespace GodotTools.Utils
 {
     public static class CollectionExtensions
     {
-        public static T SelectFirstNotNull<T>(this IEnumerable<T> enumerable, Func<T, T> predicate, T orElse = null)
+        [return: NotNullIfNotNull("orElse")]
+        public static T? SelectFirstNotNull<T>(this IEnumerable<T> enumerable, Func<T, T?> predicate, T? orElse = null)
             where T : class
         {
             foreach (T elem in enumerable)
             {
-                T result = predicate(elem);
+                T? result = predicate(elem);
                 if (result != null)
                     return result;
             }
@@ -21,7 +23,7 @@ namespace GodotTools.Utils
 
         public static IEnumerable<string> EnumerateLines(this TextReader textReader)
         {
-            string line;
+            string? line;
             while ((line = textReader.ReadLine()) != null)
                 yield return line;
         }

+ 1 - 2
modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs

@@ -30,8 +30,7 @@ namespace GodotTools.Utils
             return childPathNorm.PathStartsWithAlreadyNorm(parentPathNorm);
         }
 
-        [return: MaybeNull]
-        public static string LocalizePathWithCaseChecked(string path)
+        public static string? LocalizePathWithCaseChecked(string path)
         {
             string pathNorm = path.NormalizePath() + Path.DirectorySeparatorChar;
             string resourcePathNorm = ResourcePath.NormalizePath() + Path.DirectorySeparatorChar;

+ 7 - 10
modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs

@@ -150,8 +150,7 @@ namespace GodotTools.Utils
 
         public static char PathSep => IsWindows ? ';' : ':';
 
-        [return: MaybeNull]
-        public static string PathWhich([NotNull] string name)
+        public static string? PathWhich(string name)
         {
             if (IsWindows)
                 return PathWhichWindows(name);
@@ -159,12 +158,11 @@ namespace GodotTools.Utils
             return PathWhichUnix(name);
         }
 
-        [return: MaybeNull]
-        private static string PathWhichWindows([NotNull] string name)
+        private static string? PathWhichWindows(string name)
         {
             string[] windowsExts =
                 Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? Array.Empty<string>();
-            string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
+            string[]? pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
             char[] invalidPathChars = Path.GetInvalidPathChars();
 
             var searchDirs = new List<string>();
@@ -196,10 +194,9 @@ namespace GodotTools.Utils
                     select path + ext).FirstOrDefault(File.Exists);
         }
 
-        [return: MaybeNull]
-        private static string PathWhichUnix([NotNull] string name)
+        private static string? PathWhichUnix(string name)
         {
-            string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
+            string[]? pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
             char[] invalidPathChars = Path.GetInvalidPathChars();
 
             var searchDirs = new List<string>();
@@ -238,7 +235,7 @@ namespace GodotTools.Utils
             foreach (string arg in arguments)
                 startInfo.ArgumentList.Add(arg);
 
-            using Process process = Process.Start(startInfo);
+            using Process? process = Process.Start(startInfo);
 
             if (process == null)
                 throw new InvalidOperationException("No process was started.");
@@ -315,7 +312,7 @@ namespace GodotTools.Utils
 
         public static StringBuilder GetCommandLineDisplay(
             this ProcessStartInfo startInfo,
-            StringBuilder optionalBuilder = null
+            StringBuilder? optionalBuilder = null
         )
         {
             var builder = optionalBuilder ?? new StringBuilder();