From 3314f8cc6532af5057738d1059876706ee7062af Mon Sep 17 00:00:00 2001 From: Thaddeus Crews Date: Fri, 12 Jan 2024 22:24:12 -0600 Subject: [PATCH] C#: Enable nullable environment for `GodotTools` --- .../GodotBuildLogger.cs | 6 +- .../GodotTools.BuildLogger.csproj | 1 + .../GodotTools.Core/GodotTools.Core.csproj | 1 + .../GodotTools.Core/ProcessExtensions.cs | 2 +- .../GodotTools.Core/StringExtensions.cs | 5 +- .../GodotTools.IdeMessaging.CLI.csproj | 1 + .../GodotTools.IdeMessaging.CLI/Program.cs | 18 +-- .../GodotTools.IdeMessaging/Client.cs | 14 +- .../ClientHandshake.cs | 3 +- .../ClientMessageHandler.cs | 2 +- .../CodeAnalysisAttributes.cs | 142 ++++++++++++++++++ .../GodotIdeMetadata.cs | 4 +- .../GodotTools.IdeMessaging.csproj | 5 +- .../GodotTools.IdeMessaging/IHandshake.cs | 4 +- .../GodotTools.IdeMessaging/MessageDecoder.cs | 4 +- .../GodotTools.IdeMessaging/Peer.cs | 28 ++-- .../Requests/Requests.cs | 11 +- .../ResponseAwaiter.cs | 2 +- .../Utils/NotifyAwaiter.cs | 11 +- .../GodotTools.OpenVisualStudio.csproj | 1 + .../GodotTools.OpenVisualStudio/Program.cs | 16 +- .../DotNetSolution.cs | 24 +-- .../GodotTools.ProjectEditor.csproj | 1 + .../GodotTools.ProjectEditor/ProjectUtils.cs | 2 +- .../GodotTools/Build/BuildDiagnostic.cs | 2 - .../GodotTools/GodotTools/Build/BuildInfo.cs | 5 +- .../GodotTools/Build/BuildManager.cs | 38 ++--- .../GodotTools/Build/BuildOutputView.cs | 2 - .../GodotTools/Build/BuildProblemsFilter.cs | 2 - .../GodotTools/Build/BuildProblemsView.cs | 6 +- .../GodotTools/Build/BuildSystem.cs | 24 +-- .../GodotTools/Build/DotNetFinder.cs | 14 +- .../GodotTools/Build/MSBuildPanel.cs | 6 +- .../GodotTools/Export/ExportPlugin.cs | 9 +- .../GodotTools/Export/XcodeHelper.cs | 12 +- .../GodotTools/GodotTools/GodotSharpEditor.cs | 29 ++-- .../GodotTools/GodotTools/GodotTools.csproj | 1 + .../GodotTools/HotReloadAssemblyWatcher.cs | 2 + .../GodotTools/Ides/GodotIdeManager.cs | 8 +- .../GodotTools/Ides/MessagingServer.cs | 17 ++- .../GodotTools/Ides/MonoDevelop/Instance.cs | 6 +- .../Ides/Rider/RiderLocatorEnvironment.cs | 10 +- .../GodotTools/Ides/Rider/RiderPathManager.cs | 2 - .../GodotTools/Internals/GodotSharpDirs.cs | 12 +- .../GodotTools/Utils/CollectionExtensions.cs | 8 +- .../GodotTools/Utils/FsPathUtils.cs | 3 +- .../editor/GodotTools/GodotTools/Utils/OS.cs | 17 +-- 47 files changed, 349 insertions(+), 194 deletions(-) create mode 100644 modules/mono/editor/GodotTools/GodotTools.IdeMessaging/CodeAnalysisAttributes.cs diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index 01aa65bfc3e..3c46079414a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/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) diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj index d0972f1eaee..fd836f9ad24 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj @@ -3,6 +3,7 @@ {6CE9A984-37B1-4F8A-8FE9-609F05F071B3} net6.0 10 + enable diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj index cfd5c88a58e..07554844658 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj @@ -3,5 +3,6 @@ {639E48BD-44E5-4091-8EDD-22D36DC0768D} net6.0 10 + enable diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs index a4d7dedbd55..4dd18b4c03d 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.Core/ProcessExtensions.cs @@ -11,7 +11,7 @@ namespace GodotTools.Core { var tcs = new TaskCompletionSource(); - void ProcessExited(object sender, EventArgs e) + void ProcessExited(object? sender, EventArgs e) { tcs.TrySetResult(true); } diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs index d86a77d222d..d5c3a923515 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs +++ b/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('\\', '/'); diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj index eb6ac8bafc8..3bf678e9f9e 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj @@ -4,6 +4,7 @@ Exe net6.0 10 + enable diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs index 450c4bf0cb2..e344aa4a375 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs +++ b/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 SendRequest(Client client, string id, MessageContent content) + private static async Task SendRequest(Client client, string id, MessageContent content) { - var handlers = new Dictionary>> + var handlers = new Dictionary>> { [PlayRequest.Id] = async () => { var request = JsonConvert.DeserializeObject(content.Body); - return await client.SendRequest(request); + return await client.SendRequest(request!); }, [DebugPlayRequest.Id] = async () => { var request = JsonConvert.DeserializeObject(content.Body); - return await client.SendRequest(request); + return await client.SendRequest(request!); }, [ReloadScriptsRequest.Id] = async () => { var request = JsonConvert.DeserializeObject(content.Body); - return await client.SendRequest(request); + return await client.SendRequest(request!); }, [CodeCompletionRequest.Id] = async () => { var request = JsonConvert.DeserializeObject(content.Body); - return await client.SendRequest(request); + return await client.SendRequest(request!); } }; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs index 72e2a1fc0da..7bfa07be0b7 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs +++ b/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> clientConnectedAwaiters = new Queue>(); @@ -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 SendRequest(Request request) + public async Task SendRequest(Request request) where TResponse : Response, new() { if (!IsConnected) @@ -346,7 +348,7 @@ namespace GodotTools.IdeMessaging return await peer.SendRequest(request.Id, body); } - public async Task SendRequest(string id, string body) + public async Task SendRequest(string id, string body) where TResponse : Response, new() { if (!IsConnected) diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs index 43041be7bec..7fef628c793 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs +++ b/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; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs index 64bcfd824c4..0321aa1b57a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs @@ -42,7 +42,7 @@ namespace GodotTools.IdeMessaging [OpenFileRequest.Id] = async (peer, content) => { var request = JsonConvert.DeserializeObject(content.Body); - return await HandleOpenFile(request); + return await HandleOpenFile(request!); } }; } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/CodeAnalysisAttributes.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/CodeAnalysisAttributes.cs new file mode 100644 index 00000000000..a4a7ee82df8 --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/CodeAnalysisAttributes.cs @@ -0,0 +1,142 @@ +// ReSharper disable once CheckNamespace +namespace System.Diagnostics.CodeAnalysis +{ + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class AllowNullAttribute : Attribute + { } + + /// Specifies that null is disallowed as an input even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + internal sealed class DisallowNullAttribute : Attribute + { } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class MaybeNullAttribute : Attribute + { } + + /// 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. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class NotNullAttribute : Attribute + { } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class MaybeNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that the output will be non-null if the named parameter is non-null. + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + internal sealed class NotNullIfNotNullAttribute : Attribute + { + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } + } + + /// Applied to a method that will never return under any circumstance. + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + internal sealed class DoesNotReturnAttribute : Attribute + { } + + /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class DoesNotReturnIfAttribute : Attribute + { + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } + } + + /// Specifies that the method or property will ensure that the listed field and property members have not-null values. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullAttribute : Attribute + { + /// Initializes the attribute with a field or property member. + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullAttribute(string member) => Members = new[] { member }; + + /// Initializes the attribute with the list of field and property members. + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullAttribute(params string[] members) => Members = members; + + /// Gets field or property member names. + public string[] Members { get; } + } + + /// 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. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new[] { member }; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + /// Gets the return value condition. + public bool ReturnValue { get; } + + /// Gets field or property member names. + public string[] Members { get; } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs index 2448a2953b7..f11a7cc1495 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs +++ b/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; } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj index 02f1764f320..be6af398e22 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj @@ -2,9 +2,10 @@ {92600954-25F0-4291-8E11-1FEE9FC4BE20} netstandard2.0 - 7.2 + 9 + enable GodotTools.IdeMessaging - 1.1.1 + 1.1.2 $(Version) Godot Engine contributors diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs index 6387145a28d..8ec60876ccb 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs +++ b/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); } } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs index a00575a2a1d..caf1ae2705a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs +++ b/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; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs index dd3913b4f3a..5e453dcfabd 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs +++ b/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 ReadLine() + private async Task ReadLine() { try { @@ -216,7 +216,7 @@ namespace GodotTools.IdeMessaging return WriteLine(builder.ToString()); } - public async Task SendRequest(string id, string body) + public async Task SendRequest(string id, string body) where TResponse : Response, new() { ResponseAwaiter responseAwaiter; @@ -243,7 +243,7 @@ namespace GodotTools.IdeMessaging private async Task 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(); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs index e93db9377b0..452593b6734 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs +++ b/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(); } 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; } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs index a57c82b6089..b09575d5d2e 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs +++ b/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(content.Body)); + SetResult(JsonConvert.DeserializeObject(content.Body)!); else SetResult(new T { Status = content.Status }); } diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs index a285d5fa974..6dde1828a96 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs +++ b/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 : 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) diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj index 22778f21cba..c335e7b09f6 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj @@ -4,6 +4,7 @@ Exe net6.0-windows 10 + enable $(SolutionDir)/../../../../bin/GodotSharp/Tools diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs index 8e8eaa79b82..3072ca2857b 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs +++ b/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)] diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs index 355b21d63a5..ff15b96c1cf 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs +++ b/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 _projects = new Dictionary(); 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 Configs = new List(); + public string Guid { get; } + public string PathRelativeToSolution { get; } + public List Configs { get; } + + public ProjectInfo(string guid, string pathRelativeToSolution, List 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) diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index bde14b2b40c..623475e11a3 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -3,6 +3,7 @@ {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984} net6.0 10 + enable diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index 1d17f3d4f5c..794443a69c1 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/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; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs index 6e0c63dd433..79eb9e2ce2e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildDiagnostic.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace GodotTools.Build { public class BuildDiagnostic diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs index 7c02f296068..be0b7d32221 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs +++ b/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 && diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 7cf98b8f1f1..42250438186 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/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 BuildStarted; - public static event Action BuildFinished; - public static event Action StdOutputReceived; - public static event Action StdErrorReceived; + public static event BuildLaunchFailedEventHandler? BuildLaunchFailed; + public static event Action? BuildStarted; + public static event Action? BuildFinished; + public static event Action? StdOutputReceived; + public static event Action? 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, diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index f9e85c36e56..5cf6581fc49 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs +++ b/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 diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs index 9c165e57674..2071f687b38 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsFilter.cs @@ -1,7 +1,5 @@ using Godot; -#nullable enable - namespace GodotTools.Build { public class BuildProblemsFilter diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs index 93b5a6c7f81..5d11a96cd8c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildProblemsView.cs +++ b/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; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index 57b5598a787..5ae4dfa076d 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/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 stdOutHandler, - Action stdErrHandler) + private static Process LaunchBuild(BuildInfo buildInfo, Action? stdOutHandler, + Action? 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 stdOutHandler, Action stdErrHandler) + public static int Build(BuildInfo buildInfo, Action? stdOutHandler, Action? stdErrHandler) { using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) { @@ -77,8 +77,8 @@ namespace GodotTools.Build } } - public static async Task BuildAsync(BuildInfo buildInfo, Action stdOutHandler, - Action stdErrHandler) + public static async Task BuildAsync(BuildInfo buildInfo, Action? stdOutHandler, + Action? stdErrHandler) { using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) { @@ -88,10 +88,10 @@ namespace GodotTools.Build } } - private static Process LaunchPublish(BuildInfo buildInfo, Action stdOutHandler, - Action stdErrHandler) + private static Process LaunchPublish(BuildInfo buildInfo, Action? stdOutHandler, + Action? 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 stdOutHandler, Action stdErrHandler) + public static int Publish(BuildInfo buildInfo, Action? stdOutHandler, Action? stdErrHandler) { using (var process = LaunchPublish(buildInfo, stdOutHandler, stdErrHandler)) { @@ -297,7 +297,7 @@ namespace GodotTools.Build } private static Process DoGenerateXCFramework(List outputPaths, string xcFrameworkPath, - Action stdOutHandler, Action stdErrHandler) + Action? stdOutHandler, Action? stdErrHandler) { if (Directory.Exists(xcFrameworkPath)) { @@ -341,7 +341,7 @@ namespace GodotTools.Build return process; } - public static int GenerateXCFramework(List outputPaths, string xcFrameworkPath, Action stdOutHandler, Action stdErrHandler) + public static int GenerateXCFramework(List outputPaths, string xcFrameworkPath, Action? stdOutHandler, Action? stdErrHandler) { using (var process = DoGenerateXCFramework(outputPaths, xcFrameworkPath, stdOutHandler, stdErrHandler)) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs index cfe79cf3e1f..4d1456b70ca 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs +++ b/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) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index bae87dd1ddf..99b23f82d59 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/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) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index c634d9c1acf..f0b2b5d617f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/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 features, out string platform) + private static bool DeterminePlatformFromFeatures(IEnumerable features, [NotNullWhen(true)] out string? platform) { foreach (var feature in features) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs index 4f5bebfb42a..023f46b6851 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs +++ b/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 diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 5408b9b13e2..46c0322755b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/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 { "Debug", "ExportDebug", "ExportRelease" } - }; + var projectInfo = new DotNetSolution.ProjectInfo(guid, + Path.GetRelativePath(slnDir, GodotSharpDirs.ProjectCsProjPath), + new List { "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) diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index b5567ea3c79..ff14ee7b5d4 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -4,6 +4,7 @@ net6.0 true 10 + enable Debug $(SolutionDir)/../../../../ diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs index eb42f01b3a9..fb14afb5d7a 100644 --- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs +++ b/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) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 65b77112aab..6563bfbb06c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/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: diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs index 51c7a8aa226..c5acc5e2db8 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs +++ b/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(content.Body); - return await HandleDebugPlay(request); + return await HandleDebugPlay(request!); }, [StopPlayRequest.Id] = async (peer, content) => { var request = JsonConvert.DeserializeObject(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(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"); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs index 7a0983a8cb3..66983156d00 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs +++ b/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 command; + string? command; if (OS.IsMacOS) { @@ -136,6 +136,8 @@ namespace GodotTools.Ides.MonoDevelop {EditorId.MonoDevelop, "monodevelop"} }; } + ExecutableNames ??= new Dictionary(); + BundleIds ??= new Dictionary(); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs index 61c1581281f..77e89dfb7fc 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs @@ -20,12 +20,12 @@ public class RiderLocatorEnvironment : IRiderLocatorEnvironment } } - public T FromJson(string json) + public T? FromJson(string json) { return JsonConvert.DeserializeObject(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 } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index a0ab381b9bc..5c54d2d9ba8 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/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 diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs index 67891a0594b..94499c4f380 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs +++ b/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 { diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs index e3c2c822a57..2abca985da3 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/CollectionExtensions.cs +++ b/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(this IEnumerable enumerable, Func predicate, T orElse = null) + [return: NotNullIfNotNull("orElse")] + public static T? SelectFirstNotNull(this IEnumerable enumerable, Func 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 EnumerateLines(this TextReader textReader) { - string line; + string? line; while ((line = textReader.ReadLine()) != null) yield return line; } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs index 89bda704bb4..fee96469317 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs +++ b/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; diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index c24b730c89a..d5d8de93d2f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/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[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); + string[]? pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); char[] invalidPathChars = Path.GetInvalidPathChars(); var searchDirs = new List(); @@ -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(); @@ -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();