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
101 changes: 101 additions & 0 deletions src/SourceGenerators.Tests/CommentPropertyExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using UnionTypes.Toolkit.Generators;

namespace Tests;

[TestClass]
public class CommentPropertyExtensionsTests
{
[TestMethod]
public void TryGetCommentProperty_BareProperty_ReturnsTrueValue()
{
var declaration = GetClassDeclaration("// @union\nclass Example { }");

var found = declaration.TryGetCommentProperty("union", out var value);

Assert.IsTrue(found);
Assert.AreEqual("true", value);
Assert.IsTrue(declaration.HasCommentProperty("union"));
}

[TestMethod]
public void TryGetCommentProperty_ValueStopsAtCommentDelimiters()
{
var declaration = GetClassDeclaration("/* @name=example, @enabled */ class Example { }");

var found = declaration.TryGetCommentProperty("name", out var value);

Assert.IsTrue(found);
Assert.AreEqual("example", value);
Assert.IsTrue(declaration.HasCommentProperty("enabled"));
}

[TestMethod]
public void TryGetCommentProperty_MissingProperty_ReturnsFalse()
{
var declaration = GetClassDeclaration("// an unrelated comment\nclass Example { }");

var found = declaration.TryGetCommentProperty("union", out var value);

Assert.IsFalse(found);
Assert.IsNull(value);
Assert.IsFalse(declaration.HasCommentProperty("union"));
}

[TestMethod]
public void TryGetCommentProperty_Generic_ConvertsValueAndRejectsInvalidValue()
{
var declaration = GetClassDeclaration("// @count=42 @enabled\nclass Example { }");

var countFound = declaration.TryGetCommentProperty<int>("count", out var count);
var enabledFound = declaration.TryGetCommentProperty<bool>("enabled", out var enabled);
var invalidFound = declaration.TryGetCommentProperty<int>("enabled", out var invalid);

Assert.IsTrue(countFound);
Assert.AreEqual(42, count);
Assert.IsTrue(enabledFound);
Assert.IsTrue(enabled);
Assert.IsFalse(invalidFound);
Assert.AreEqual(0, invalid);
}

[TestMethod]
public void TryGetCommentProperty_Symbol_SearchesAllDeclarationNodes()
{
var compilation = TestHelpers.CreateCompilation(
"partial class Example { }",
"// @union\npartial class Example { }");
var symbol = compilation.GetTypeByMetadataName("Example");

Assert.IsNotNull(symbol);
var found = symbol!.TryGetCommentProperty("union", out var value);

Assert.IsTrue(found);
Assert.AreEqual("true", value);
Assert.IsTrue(symbol.HasCommentProperty("union"));
}

[TestMethod]
public void TryGetCommentProperty_GenericSymbol_ConvertsDeclarationValue()
{
var compilation = TestHelpers.CreateCompilation("// @priority=7\nclass Example { }");
var symbol = compilation.GetTypeByMetadataName("Example");

Assert.IsNotNull(symbol);
var found = symbol!.TryGetCommentProperty<int>("priority", out var priority);

Assert.IsTrue(found);
Assert.AreEqual(7, priority);
}

private static ClassDeclarationSyntax GetClassDeclaration(string source)
{
return CSharpSyntaxTree.ParseText(source)
.GetRoot()
.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.Single();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace UnionTypes.Toolkit.Generators;

Expand Down Expand Up @@ -34,36 +29,48 @@ public static bool HasCommentProperty(this ISymbol symbol, string propertyName)
public static bool TryGetCommentProperty(this SyntaxNode node, string propertyName, out string? value)
{
value = null;
var commentTrivia = node.GetLeadingTrivia().Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia) || t.IsKind(SyntaxKind.MultiLineCommentTrivia)).ToArray();
foreach (var trivia in commentTrivia)

foreach (var trivia in node.GetLeadingTrivia())
{
var text = trivia.ToString();
var prefix = "@" + propertyName;
var startIndex = text.IndexOf(prefix);
if (startIndex >= 0)
{
var endOfPrefix = startIndex + prefix.Length;

if (endOfPrefix < text.Length && text[endOfPrefix] == '=')
if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia)
|| trivia.IsKind(SyntaxKind.MultiLineCommentTrivia))
{
var text = trivia.ToString();
var prefix = "@" + propertyName;
var startIndex = text.IndexOf(prefix);
if (startIndex >= 0)
{
startIndex = endOfPrefix + 1;
var endIndex = text.IndexOfAny(new[] { ' ', '\t', '\r', '\n' }, startIndex);
if (endIndex < 0)
endIndex = text.Length;
value = text.Substring(startIndex, endIndex - startIndex);
return true;
}
else if (endOfPrefix == text.Length
|| text.IndexOfAny(new[] { ' ', '\t', '\r', '\n' }, endOfPrefix) >= endOfPrefix)
{
value = "true";
return true;
var endOfPrefix = startIndex + prefix.Length;

if (endOfPrefix < text.Length && text[endOfPrefix] == '=')
{
// determine value after =
startIndex = endOfPrefix + 1;
var endIndex = text.IndexOfAny(_commentPropertyEndingTokens, startIndex);
if (endIndex < 0)
endIndex = text.Length;
value = text.Substring(startIndex, endIndex - startIndex);
return true;
}
else if (endOfPrefix == text.Length
|| text.IndexOfAny(_commentPropertyEndingTokens, endOfPrefix) >= endOfPrefix)
{
// property without a value is considered to be "true"
value = "true";
return true;
}
}
}
}
}

return false;
}

/// <summary>
/// Any of these characters denotes the end of a comment property
/// </summary>
private static readonly char[] _commentPropertyEndingTokens = new[] { ' ', '\t', '\r', '\n', ',', ';', '|', ':', '(', ')', '[', ']', '{', '}' };

/// <summary>
/// Returns true if the comment property exists in the node's leading trivia, and outputs properties assigned value if present and convertible to the type T.
/// </summary>
Expand Down
Loading