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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.IO;
using System.Threading.Tasks;
using Microsoft.Macios.Generator.Availability;
using Xamarin.Utils;

Expand Down Expand Up @@ -54,4 +55,50 @@ public static TabbedWriter<StringWriter> AppendMemberAvailability (this TabbedWr

return self;
}

public static async Task<TabbedWriter<StreamWriter>> AppendMemberAvailabilityAsync (this TabbedWriter<StreamWriter> self, SymbolAvailability allPlatformsAvailability)
{
foreach (var availability in allPlatformsAvailability.PlatformAvailabilities) {
var platformName = availability.Platform.AsString ().ToLower ();
if (availability.SupportedVersion is not null) {
var versionStr = (PlatformAvailability.IsDefaultVersion (availability.SupportedVersion))
? string.Empty
: availability.SupportedVersion.ToString ();
await self.WriteLineAsync ($"[SupportedOSPlatform (\"{platformName}{versionStr}\")]");
}

// loop over the unsupported versions of the platform
foreach (var (version, message) in availability.UnsupportedVersions) {
var versionStr = (PlatformAvailability.IsDefaultVersion (version)) ? string.Empty : version.ToString ();
if (message is null) {
await self.WriteLineAsync ($"[UnsupportedOSPlatform (\"{platformName}{versionStr}\")]");
} else {
await self.WriteLineAsync ($"[UnsupportedOSPlatform (\"{platformName}{versionStr}\", \"{message}\")]");
}
}

// loop over the obsolete versions of the platform
foreach (var (version, obsoleteInfo) in availability.ObsoletedVersions) {
var versionStr = (PlatformAvailability.IsDefaultVersion (version)) ? string.Empty : version.ToString ();

switch (obsoleteInfo) {
case (null, null):
await self.WriteLineAsync ($"[ObsoletedOSPlatform (\"{platformName}{versionStr}\")]");
break;
case (not null, null):
await self.WriteLineAsync ($"[ObsoletedOSPlatform (\"{platformName}{versionStr}\", \"{obsoleteInfo.Message}\")]");
break;
case (null, not null):
await self.WriteLineAsync ($"[ObsoletedOSPlatform (\"{platformName}{versionStr}\", Url=\"{obsoleteInfo.Url}\")]");
break;
case (not null, not null):
await self.WriteLineAsync (
$"[ObsoletedOSPlatform (\"{platformName}{versionStr}\", \"{obsoleteInfo.Message}\", Url=\"{obsoleteInfo.Url}\")]");
break;
}
}
}

return self;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

namespace Microsoft.Macios.Generator.IO;

static class TabbedStreamWriterExtensions {

/// <summary>
/// Appends the comments and doc comments trivia to the writer.
/// </summary>
/// <param name="self">The current writer.</param>
/// <param name="leadingTrivia">The leading trivia of a syntax node to append.</param>
/// <returns>The writer itself.</returns>
public static async Task<TabbedWriter<StreamWriter>> AppendCommentsTrivia (
this TabbedWriter<StreamWriter> self, SyntaxTriviaList leadingTrivia)
{
// loop over the trivia and append only the doc comments or the comments
foreach (var trivia in leadingTrivia) {
// leading a single line
if (trivia.IsKind (SyntaxKind.SingleLineCommentTrivia)
|| trivia.IsKind (SyntaxKind.SingleLineDocumentationCommentTrivia)) {
// This is a regular comment
await self.WriteLineAsync (trivia.ToFullString ().Trim ());
}

if (trivia.IsKind (SyntaxKind.MultiLineCommentTrivia)
|| trivia.IsKind (SyntaxKind.MultiLineDocumentationCommentTrivia)) {
// multilines, we want to split them and write line by line so that we
// can keep the correct indentation
var triviaText = trivia.ToString ();
var lines = triviaText.Split (['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);

for (var index = 0; index < lines.Length - 1; index++) {
await self.WriteLineAsync (lines [index].Trim ());
}
// add the last line without trimming, since it might be important and we do not want to
// mess with the formatting
await self.WriteAsync (lines [^1]);
}
}
return self;
}

/// <summary>
/// Appends the using directives to the writer.
/// </summary>
/// <param name="self">The current writer.</param>
/// <param name="usingDirectives">The collection of using directives to append.</param>
/// <returns>The writer itself.</returns>
public static async Task<TabbedWriter<StreamWriter>> AppendUsingDirectives (
this TabbedWriter<StreamWriter> self, IReadOnlySet<string> usingDirectives)
{
// we want to create a hashset with the directives to avoid duplicates and we want to make sure
// that the ObCBindings and the System.Versioning are always present
HashSet<string> directives = [
.. usingDirectives,
"ObjCRuntime",
"ObjCBindings",
"System.Runtime.Versioning"
];
foreach (var directive in directives.OrderBy (d => d)) {
await self.WriteLineAsync ($"using {directive};");
}
return self;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
<Compile Include="../Microsoft.Macios.Generator/IO/TabbedStringBuilder.cs">
<Link>IO/TabbedStringBuilder.cs</Link>
</Compile>
<Compile Include="../Microsoft.Macios.Generator/IO/TabbedStringBuilderAvailability.cs">
<Link>IO/TabbedStringBuilderAvailability.cs</Link>
</Compile>
<Compile Include="../Microsoft.Macios.Generator/Attributes/ObsoletedOSPlatformData.cs">
<Link>Attributes/ObsoletedOSPlatformData.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// Licensed under the MIT License.

using Microsoft.Macios.Generator.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Macios.Generator.Availability;
using Microsoft.Macios.Generator.Attributes;

namespace Microsoft.Macios.Transformer.Tests.IO;

Expand Down Expand Up @@ -118,4 +122,154 @@ public async Task ConstructorBlockNestedAsyncTest ()
";
Assert.Equal (expectedResult, ReadFile ());
}

public static IEnumerable<object []> AppendCommentsTriviaTestData ()
{
// Case 1: Single line comment
var trivia1 = SyntaxFactory.ParseLeadingTrivia ("// A single line comment");
var expected1 = "// A single line comment\n";
yield return [trivia1, expected1];

// Case 2: Multi-line comment
var trivia2 = SyntaxFactory.ParseLeadingTrivia ("/* A multi-line\n comment */");
var expected2 = "/* A multi-line\n comment */";
yield return [trivia2, expected2];

// Case 3: Single line doc comment
var trivia3 = SyntaxFactory.ParseLeadingTrivia ("/// A single line doc comment");
var expected3 = "/// A single line doc comment\n";
yield return [trivia3, expected3];

// Case 4: Multi-line doc comment
var trivia4 = SyntaxFactory.ParseLeadingTrivia (
"""
/*
* A multi-line doc comment
*/
""");
var expected4 = """
/*
* A multi-line doc comment
*/
""";
yield return [trivia4, expected4];

// Case 5: Mix of comments and other trivia
var trivia5 = SyntaxFactory.ParseLeadingTrivia (@"
// A comment
/// A doc comment
");
var expected5 = "// A comment\n/// A doc comment\n";
yield return [trivia5, expected5];

// Case 6: No comments, only other trivia
var trivia6 = SyntaxFactory.ParseLeadingTrivia ("\n ");
var expected6 = "";
yield return [trivia6, expected6];

// Case 7: Empty trivia list
var trivia7 = new SyntaxTriviaList ();
var expected7 = "";
yield return [trivia7, expected7];
}

[Theory]
[MemberData (nameof (AppendCommentsTriviaTestData))]
public async Task AppendCommentsTrivia (SyntaxTriviaList trivia, string expected)
{
await using (var writer = new TabbedStreamWriter (tempFile)) {
await writer.AppendCommentsTrivia (trivia);
}
Assert.Equal (expected, ReadFile ());
}

public static IEnumerable<object []> AppendUsingDirectivesTestData ()
{
// Case 1: Empty set
yield return new object [] {
new HashSet<string>(),
"using ObjCBindings;\nusing ObjCRuntime;\nusing System.Runtime.Versioning;\n"
};

// Case 2: With some directives
yield return new object [] {
new HashSet<string> { "System", "System.Collections.Generic" },
"using ObjCBindings;\nusing ObjCRuntime;\nusing System;\nusing System.Collections.Generic;\nusing System.Runtime.Versioning;\n"
};

// Case 3: With duplicates of default directives
yield return new object [] {
new HashSet<string> { "ObjCRuntime", "System" },
"using ObjCBindings;\nusing ObjCRuntime;\nusing System;\nusing System.Runtime.Versioning;\n"
};

// Case 4: With all default directives present
yield return new object [] {
new HashSet<string> { "ObjCRuntime", "ObjCBindings", "System.Runtime.Versioning" },
"using ObjCBindings;\nusing ObjCRuntime;\nusing System.Runtime.Versioning;\n"
};
}

[Theory]
[MemberData (nameof (AppendUsingDirectivesTestData))]
public async Task AppendUsingDirectives (IReadOnlySet<string> directives, string expected)
{
await using (var writer = new TabbedStreamWriter (tempFile)) {
await writer.AppendUsingDirectives (directives);
}
Assert.Equal (expected, ReadFile ());
}

public static IEnumerable<object []> AppendMemberAvailabilityTestData ()
{
var builder = SymbolAvailability.CreateBuilder ();

// single platform, available no version
builder.Add (new SupportedOSPlatformData ("ios"));
yield return [builder.ToImmutable (), "[SupportedOSPlatform (\"ios\")]\n"];
builder.Clear ();

// single platform available with version
builder.Add (new SupportedOSPlatformData ("macos13.0"));
yield return [builder.ToImmutable (), "[SupportedOSPlatform (\"macos13.0\")]\n"];
builder.Clear ();

// single platform available with version, unavailable with version
builder.Add (new SupportedOSPlatformData ("ios"));
builder.Add (new UnsupportedOSPlatformData ("ios13.0"));
yield return [builder.ToImmutable (), "[SupportedOSPlatform (\"ios\")]\n[UnsupportedOSPlatform (\"ios13.0\")]\n"];
builder.Clear ();

// several platforms available no version
builder.Add (new SupportedOSPlatformData ("ios"));
builder.Add (new SupportedOSPlatformData ("tvos"));
builder.Add (new SupportedOSPlatformData ("macos"));
yield return [builder.ToImmutable (), "[SupportedOSPlatform (\"macos\")]\n[SupportedOSPlatform (\"ios\")]\n[SupportedOSPlatform (\"tvos\")]\n"];
builder.Clear ();

// several platforms available with version
builder.Add (new SupportedOSPlatformData ("ios12.0"));
builder.Add (new SupportedOSPlatformData ("tvos12.0"));
builder.Add (new SupportedOSPlatformData ("macos10.0"));
yield return [builder.ToImmutable (), "[SupportedOSPlatform (\"macos10.0\")]\n[SupportedOSPlatform (\"ios12.0\")]\n[SupportedOSPlatform (\"tvos12.0\")]\n"];
builder.Clear ();

// several platforms unsupported
// several platforms available with version
builder.Add (new UnsupportedOSPlatformData ("ios12.0"));
builder.Add (new UnsupportedOSPlatformData ("tvos12.0"));
builder.Add (new UnsupportedOSPlatformData ("macos"));
yield return [builder.ToImmutable (), "[UnsupportedOSPlatform (\"macos\")]\n[UnsupportedOSPlatform (\"ios12.0\")]\n[UnsupportedOSPlatform (\"tvos12.0\")]\n"];
builder.Clear ();
}

[Theory]
[MemberData (nameof (AppendMemberAvailabilityTestData))]
async Task AppendMemberAvailabilityAsyncTest (SymbolAvailability availability, string expected)
{
await using (var writer = new TabbedStreamWriter (tempFile)) {
await writer.AppendMemberAvailabilityAsync (availability);
}
Assert.Equal (expected, ReadFile ());
}
}
Loading