Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Zomp.SyncMethodGenerator/AsyncToSyncRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,14 @@ bool RemoveType(TypeSyntax z, int index) =>
var originalName = @base.Identifier.Text;
var newName = RemoveAsync(originalName);

// A method which returned a task returns nothing once it is synchronized, so whatever
// its documentation said about the task it returned no longer describes anything. The
// documentation of a value which survives is left alone: it is imprecise about the task
// it mentions, but dropping it would say less than saying it imprecisely.
var returnsNothing = GetReturnType(@base.ReturnType, symbol)
is PredefinedTypeSyntax { Keyword.RawKind: (int)SyntaxKind.VoidKeyword }
or IdentifierNameSyntax { Identifier.ValueText: "void" };

// Documentation
var trivia = node.GetLeadingTrivia();
var newTriviaList = trivia;
Expand All @@ -982,6 +990,12 @@ bool RemoveType(TypeSyntax z, int index) =>
indicesToRemove.Add(i - 1); // preceding slashes
indicesToRemove.Add(i);
}
else if (returnsNothing
&& xes.StartTag.Name.LocalName.ValueText.Equals("returns", StringComparison.Ordinal))
{
indicesToRemove.Add(i - 1); // preceding slashes
indicesToRemove.Add(i);
}
}

++i;
Expand Down
4 changes: 4 additions & 0 deletions src/Zomp.SyncMethodGenerator/MethodToGenerate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
/// </summary>
/// <param name="Index">Index of the method in the source file.</param>
/// <param name="Namespaces">List of namespaces this method is under.</param>
/// <param name="OuterUsings">Using directives the source file declares outside its namespace.</param>
/// <param name="InnerUsings">Using directives the source file declares inside its namespace.</param>
/// <param name="IsNamespaceFileScoped">True if namespace is file scoped.</param>
/// <param name="IsCSharp14Extension">True if this is C# 14 extension.</param>
/// <param name="Parents">List of classes/structs/records this method belongs to starting from the outer-most class.</param>
Expand All @@ -17,6 +19,8 @@
internal sealed record MethodToGenerate(
int Index,
EquatableArray<string> Namespaces,
EquatableArray<string> OuterUsings,
EquatableArray<string> InnerUsings,
bool IsNamespaceFileScoped,
bool IsCSharp14Extension,
EquatableArray<MethodParentDeclaration> Parents,
Expand Down
39 changes: 38 additions & 1 deletion src/Zomp.SyncMethodGenerator/SourceGenerationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@ internal static string GenerateExtensionClass(MethodToGenerate methodToGenerate)
}
}

// Directives which were written inside the namespace are emitted inside it, at the
// innermost level. One which names a namespace relatively still resolves from there.
foreach (var @using in methodToGenerate.InnerUsings)
{
_ = sbBegin.Append($$"""
{{new string(' ', 4 * i)}}{{@using}}

""");
}

if (!methodToGenerate.InnerUsings.IsEmpty)
{
_ = sbBegin.Append("""


""");
}

// Handle classes
foreach (var parent in methodToGenerate.Parents)
{
Expand Down Expand Up @@ -136,11 +154,30 @@ internal static string GenerateExtensionClass(MethodToGenerate methodToGenerate)
++i;
}

var sbOuterUsings = new StringBuilder();
foreach (var @using in methodToGenerate.OuterUsings)
{
if (sbOuterUsings.Length == 0)
{
// Separates the directives from the header above them.
_ = sbOuterUsings.Append("""



""");
}

_ = sbOuterUsings.Append($$"""
{{@using}}

""");
}

var beforeNamespace = $"""
// <auto-generated/>{(methodToGenerate.DisableNullable ? string.Empty : """

#nullable enable
""")}
""")}{sbOuterUsings}
""";

return methodToGenerate.IsNamespaceFileScoped ? $$"""
Expand Down
33 changes: 32 additions & 1 deletion src/Zomp.SyncMethodGenerator/SyncMethodSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,15 @@ private static string Hash(string value)
var isNamespaceFileScoped = false;
var namespaces = ImmutableArray.CreateBuilder<string>();

// Documentation comments are copied across verbatim, and a cref in one is resolved
// against the file it lands in rather than the file it was written in. Without the
// using directives which were in scope where it was written, every cref which relied on
// one goes unresolved, which a project building with warnings as errors reads as a
// build failure. The directives are kept on the side of the namespace they were
// declared on, since one declared inside a namespace may name it only relatively.
var outerUsings = ImmutableArray.CreateBuilder<string>();
var innerUsings = ImmutableArray.CreateBuilder<string>();

if (!hasErrors)
{
while (node is not null and not CompilationUnitSyntax)
Expand All @@ -380,9 +389,11 @@ private static string Hash(string value)
{
case NamespaceDeclarationSyntax nds:
namespaces.Insert(0, nds.Name.ToString());
InsertUsings(innerUsings, nds.Usings);
break;
case FileScopedNamespaceDeclarationSyntax file:
namespaces.Add(file.Name.ToString());
InsertUsings(innerUsings, file.Usings);
isNamespaceFileScoped = true;
break;
default:
Expand All @@ -391,6 +402,11 @@ private static string Hash(string value)

node = node.Parent;
}

if (node is CompilationUnitSyntax compilationUnit)
{
InsertUsings(outerUsings, compilationUnit.Usings);
}
}

#if ROSLYN_5_0_OR_GREATER
Expand All @@ -400,11 +416,26 @@ private static string Hash(string value)
#endif
var signature = BuildSignature(sn, namespaces, classes, methodDeclarationSyntax);

var result = new MethodToGenerate(index, namespaces.ToImmutable(), isNamespaceFileScoped, isCSharp14Extension, classes.ToImmutable(), methodDeclarationSyntax.Identifier.ValueText, content, disableNullable, rewriter.Diagnostics, hasErrors, signature);
var result = new MethodToGenerate(index, namespaces.ToImmutable(), outerUsings.ToImmutable(), innerUsings.ToImmutable(), isNamespaceFileScoped, isCSharp14Extension, classes.ToImmutable(), methodDeclarationSyntax.Identifier.ValueText, content, disableNullable, rewriter.Diagnostics, hasErrors, signature);

return result;
}

/// <summary>
/// Records the directives, innermost first, so that walking outwards from the method builds
/// them up in the order they were written.
/// </summary>
/// <param name="destination">Collected directives.</param>
/// <param name="usings">Directives declared at one level.</param>
private static void InsertUsings(ImmutableArray<string>.Builder destination, SyntaxList<UsingDirectiveSyntax> usings)
{
var index = 0;
foreach (var @using in usings)
{
destination.Insert(index++, @using.ToString());
}
}

/// <summary>
/// Describes the method which is about to be emitted, precisely enough to tell whether two
/// of them would declare the same member. The rewritten declaration is used rather than the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#nullable enable
namespace Callers
{
using Helpers;

public static partial class StreamCallers
{
extension(global::System.IO.Stream stream)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#nullable enable
namespace Callers
{
using Helpers;

public static partial class StreamCallers
{
extension(global::System.IO.Stream stream)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
//HintName: Zomp.SyncMethodGenerator.IntegrationTests.EntityFrameworkQuery_0f26cde8.QueryableExtensionAsync.g.cs
// <auto-generated/>
#nullable enable

using System.Threading;
using System.Threading.Tasks;

namespace Zomp.SyncMethodGenerator.IntegrationTests
{
using Microsoft.EntityFrameworkCore;

public partial class EntityFrameworkQueryableExtensions
{
public int QueryableExtension(global::Microsoft.EntityFrameworkCore.DbContext dbContext)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
//HintName: Zomp.SyncMethodGenerator.IntegrationTests.Extensions.HasGeneric2ExtensionAsync.g.cs
// <auto-generated/>
#nullable enable
namespace Zomp.SyncMethodGenerator.IntegrationTests
{
partial class Extensions
{
public static void HasGeneric2Extension(object o)
{
var z = global::Extensi.ons123.MyExtensionClass.TryGetValue<global::System.Drawing.Point, global::System.Drawing.PointF>(o, out var _, out var _1);
}
}
}
//HintName: Zomp.SyncMethodGenerator.IntegrationTests.Extensions.HasGeneric2ExtensionAsync.g.cs
// <auto-generated/>
#nullable enable

using System.Drawing;
using System.Threading;
using System.Threading.Tasks;

namespace Zomp.SyncMethodGenerator.IntegrationTests
{
using Extensi.ons123;

partial class Extensions
{
public static void HasGeneric2Extension(object o)
{
var z = global::Extensi.ons123.MyExtensionClass.TryGetValue<global::System.Drawing.Point, global::System.Drawing.PointF>(o, out var _, out var _1);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
//HintName: Zomp.SyncMethodGenerator.IntegrationTests.Extensions.HasGenericExtensionAsync.g.cs
// <auto-generated/>
#nullable enable
namespace Zomp.SyncMethodGenerator.IntegrationTests
{
partial class Extensions
{
public static void HasGenericExtension(object o)
{
var z = global::Extensi.ons123.MyExtensionClass.TryGetValue<global::System.Drawing.Point>(o, out var item);
}
}
}
//HintName: Zomp.SyncMethodGenerator.IntegrationTests.Extensions.HasGenericExtensionAsync.g.cs
// <auto-generated/>
#nullable enable

using System.Drawing;
using System.Threading;
using System.Threading.Tasks;

namespace Zomp.SyncMethodGenerator.IntegrationTests
{
using Extensi.ons123;

partial class Extensions
{
public static void HasGenericExtension(object o)
{
var z = global::Extensi.ons123.MyExtensionClass.TryGetValue<global::System.Drawing.Point>(o, out var item);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
//HintName: N1.Class.FillAsync.g.cs
// <auto-generated/>
#nullable enable
namespace N1
{
partial class Class
{
void Fill(global::System.Span<global::N2.C1.C2.Accelerometer> accelerometers)
{
global::N2.C1.C2.Accelerometer a = new(1, 2, 3);
accelerometers[0] = a;
}
}
}
//HintName: N1.Class.FillAsync.g.cs
// <auto-generated/>
#nullable enable

using N2;
using static N2.C1;

namespace N1
{
partial class Class
{
void Fill(global::System.Span<global::N2.C1.C2.Accelerometer> accelerometers)
{
global::N2.C1.C2.Accelerometer a = new(1, 2, 3);
accelerometers[0] = a;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
//HintName: Test.Extensions.GetConfiguredAsyncEnumerator.g.cs
// <auto-generated/>
#nullable enable
namespace Test;
internal static partial class Extensions
{
public static System.Collections.Generic.IEnumerator<int> GetConfiguredEnumerator(global::System.Collections.Generic.IEnumerable<int> enumerable)
{
return enumerable.GetEnumerator();
}
}
//HintName: Test.Extensions.GetConfiguredAsyncEnumerator.g.cs
// <auto-generated/>
#nullable enable

using static System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable<int>;

namespace Test;
internal static partial class Extensions
{
public static System.Collections.Generic.IEnumerator<int> GetConfiguredEnumerator(global::System.Collections.Generic.IEnumerable<int> enumerable)
{
return enumerable.GetEnumerator();
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
//HintName: Test.Class.MethodAsync.g.cs
_ = global::System.Data.ConnectionState.Closed is global::System.Data.ConnectionState.Closed;
// <auto-generated/>
#nullable enable

using static System.Data.ConnectionState;

namespace Test;
partial class Class
{
public void Method()
{
_ = global::System.Data.ConnectionState.Closed is global::System.Data.ConnectionState.Closed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//HintName: Test.Class.CountAsync.g.cs
/// <summary>
/// Counts the values.
/// </summary>
/// <param name="input">Where to read from.</param>
/// <returns>A <see cref="Task"/> whose result is the number of values.</returns>
public int Count(global::System.IO.Stream input)
{
input.Flush();
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//HintName: Test.Class.ReadAsync.g.cs
/// <summary>
/// Reads a value.
/// </summary>
/// <param name="input">Where to read from.</param>
public void Read(global::System.IO.Stream input)
=> input.Flush();
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#nullable enable
namespace M
{
using N;

public partial class B
{
public void DoSomething(object arg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#nullable enable
namespace M
{
using N;

public partial class B
{
public void DoSomething()
Expand Down
Loading
Loading