Merge pull request #87137 from /dotnet/nullable-godot-tools

This commit is contained in:
Rémi Verschelde 2024-02-17 18:31:37 +01:00
commit ccd9cefa8e
No known key found for this signature in database
GPG Key ID: C3336907360768E1
47 changed files with 349 additions and 194 deletions

View File

@ -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)

View File

@ -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" />

View File

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

View File

@ -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);
}

View File

@ -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('\\', '/');

View File

@ -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" />

View File

@ -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!);
}
};

View File

@ -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)

View File

@ -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;

View File

@ -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!);
}
};
}

View File

@ -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; }
}
}

View File

@ -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;
}

View File

@ -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 />

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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();
}
}
}

View File

@ -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; }

View File

@ -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 });
}

View File

@ -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)

View File

@ -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>

View File

@ -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)]

View File

@ -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)

View File

@ -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" />

View File

@ -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;

View File

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

View File

@ -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 &&

View File

@ -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;
public static DateTime LastValidBuildDateTime { get; private set; }
@ -274,8 +274,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
)
@ -294,10 +294,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
)
{
@ -319,20 +319,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,

View File

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

View File

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

View File

@ -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;

View File

@ -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))
{

View File

@ -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)
{

View File

@ -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
@ -183,7 +181,7 @@ namespace GodotTools.Build
}
}
private void StdOutputReceived(string text)
private void StdOutputReceived(string? text)
{
lock (_pendingBuildLogTextLock)
{
@ -193,7 +191,7 @@ namespace GodotTools.Build
}
}
private void StdErrorReceived(string text)
private void StdErrorReceived(string? text)
{
lock (_pendingBuildLogTextLock)
{

View File

@ -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)
{

View File

@ -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

View File

@ -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;
@ -67,29 +69,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);
@ -342,7 +338,7 @@ namespace GodotTools
}
}
args.Add(Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath));
args.Add(Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath)!);
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
@ -459,7 +455,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}");
@ -694,8 +690,9 @@ namespace GodotTools
}
// Singleton
#nullable disable
public static GodotSharpEditor Instance { get; private set; }
#nullable enable
[UsedImplicitly]
private static IntPtr InternalCreateInstance(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize)

View File

@ -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>

View File

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

View File

@ -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:

View File

@ -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");

View File

@ -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>();
}
}
}

View File

@ -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
}

View File

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

View File

@ -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
{

View File

@ -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;
}

View File

@ -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;

View File

@ -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();