-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'dansiegel:master' into master
- Loading branch information
Showing
7 changed files
with
325 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
using System; | ||
using System.Linq; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
||
namespace AvantiPoint.CodeGenHelpers.Extensions; | ||
|
||
public static class RoslynExtensions | ||
{ | ||
/// <summary> | ||
/// Method to get the <see cref="ISymbol"/> from a <see cref="BaseTypeDeclarationSyntax"/> | ||
/// </summary> | ||
/// <typeparam name="TSymbol">The symbol that you want that derives from <see cref="ISymbol"/></typeparam> | ||
/// <param name="compilation">The <see cref="Compilation"/></param> | ||
/// <param name="declarationSyntax">The <see cref="BaseTypeDeclarationSyntax"/>that you want to convert to symbol.</param> | ||
/// <returns>The desired Symbol.</returns> | ||
public static TSymbol? GetSymbol<TSymbol>(this Compilation compilation, BaseTypeDeclarationSyntax declarationSyntax) | ||
where TSymbol : ISymbol | ||
{ | ||
var model = compilation.GetSemanticModel(declarationSyntax.SyntaxTree); | ||
return (TSymbol?)model.GetDeclaredSymbol(declarationSyntax); | ||
} | ||
|
||
/// <summary> | ||
/// Returns the value of a named attribute as a <see cref="TypedConstant"/>. If no attribute with the given name is found, this method returns <c>null</c>. | ||
/// </summary> | ||
/// <param name="attribute">The attribute whose value is to be returned.</param> | ||
/// <param name="name">The name of the attribute to be returned.</param> | ||
/// <returns>The value of the named attribute, or <c>null</c> if no such attribute is found.</returns> | ||
public static TypedConstant GetAttributeValueByName(this AttributeData attribute, string name) | ||
{ | ||
return attribute.NamedArguments.SingleOrDefault(arg => arg.Key == name).Value; | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Returns the value of an attribute with the given name as a string. If no attribute with the specified name is found, the default value of "null" is returned. | ||
/// </summary> | ||
/// <param name="attribute">The attribute to inspect.</param> | ||
/// <param name="name">The name of the attribute value to retrieve.</param> | ||
/// <param name="placeholder">The default value to return if the attribute value is null.</param> | ||
/// <returns>The attribute value as a string, or the specified default value if the attribute is not found or its value is null.</returns> | ||
public static string GetAttributeValueByNameAsString(this AttributeData attribute, string name, string placeholder = "null") | ||
{ | ||
var data = attribute.NamedArguments.SingleOrDefault(kvp => kvp.Key == name).Value; | ||
|
||
return data.Value is null ? placeholder : data.Value.ToString(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
tests/CodeGenHelpers.Tests/SampleCode/SampleClassWithPropertiesNotSorted.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
//------------------------------------------------------------------------------ | ||
// <auto-generated> | ||
// This code was generated. | ||
// | ||
// Changes to this file may cause incorrect behavior and will be lost if | ||
// the code is regenerated. | ||
// </auto-generated> | ||
//------------------------------------------------------------------------------ | ||
|
||
using System; | ||
|
||
namespace CodeGenHelpers.SampleCode | ||
{ | ||
partial class SampleClassWithPropertiesNotSorted | ||
{ | ||
public string PropZ; | ||
|
||
public string PropC; | ||
|
||
public string PropB; | ||
|
||
public string PropA; | ||
|
||
public string PropD; | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
tests/CodeGenHelpers.Tests/SampleCode/SampleClassWithPropertiesSorted.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
//------------------------------------------------------------------------------ | ||
// <auto-generated> | ||
// This code was generated. | ||
// | ||
// Changes to this file may cause incorrect behavior and will be lost if | ||
// the code is regenerated. | ||
// </auto-generated> | ||
//------------------------------------------------------------------------------ | ||
|
||
using System; | ||
|
||
namespace CodeGenHelpers.SampleCode | ||
{ | ||
partial class SampleClassWithPropertiesSorted | ||
{ | ||
public string PropA; | ||
|
||
public string PropB; | ||
|
||
public string PropC; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
182 changes: 182 additions & 0 deletions
182
tests/CodeGenHelpers.Tests/Tests/RoslynExtensionTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
using System.Linq; | ||
using AvantiPoint.CodeGenHelpers.Extensions; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Xunit; | ||
|
||
namespace CodeGenHelpers.Tests; | ||
|
||
public class RoslynExtensionTests | ||
{ | ||
readonly string _placeholder = "null"; | ||
readonly string _notFoundName = "NotFoundAttribute"; | ||
|
||
[Fact] | ||
public void GetSymbol_ReturnsSymbol_ForGivenDeclarationSyntax() | ||
{ | ||
// Arrange | ||
var text = @" | ||
namespace MyNamespace | ||
{ | ||
public class MyClass {} | ||
}"; | ||
var tree = CSharpSyntaxTree.ParseText(text); | ||
var compilation = CSharpCompilation.Create("MyCompilation", | ||
references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }, | ||
syntaxTrees: new[] { tree } | ||
); | ||
|
||
var root = tree.GetCompilationUnitRoot(); | ||
var classDeclaration = root.DescendantNodes().OfType<ClassDeclarationSyntax>().Single(); | ||
var expected = compilation.GetTypeByMetadataName("MyNamespace.MyClass"); | ||
|
||
// Act | ||
var actual = compilation.GetSymbol<INamedTypeSymbol>(classDeclaration); | ||
|
||
// Assert | ||
Assert.Equal(expected, actual); | ||
} | ||
|
||
[Fact] | ||
public void GetAttributeValueByName_ReturnsTypedConstant() | ||
{ | ||
// arrange | ||
var syntaxTree = SyntaxFactory.ParseSyntaxTree(@" | ||
using System; | ||
using System.Collections.Generic; | ||
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] | ||
sealed class MyAttribute : Attribute | ||
{ | ||
public MyAttribute(string arg1, string arg2) | ||
{ | ||
} | ||
}"); | ||
|
||
var compilation = CSharpCompilation.Create("MyCompilation", | ||
references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }, | ||
syntaxTrees: new[] { syntaxTree } | ||
); | ||
var root = syntaxTree.GetCompilationUnitRoot(); | ||
var @class = root.Members[0] as ClassDeclarationSyntax; | ||
var symbol = compilation.GetSemanticModel(syntaxTree).GetDeclaredSymbol(@class!)!; | ||
var attribute = symbol.GetAttributes().First(); | ||
|
||
var expected = typeof(TypedConstant); | ||
|
||
// act | ||
var actual = RoslynExtensions.GetAttributeValueByName(attribute, "Inherited"); | ||
|
||
//// assert | ||
Assert.IsType(expected, actual); | ||
Assert.NotNull(actual.Value); | ||
} | ||
|
||
[Fact] | ||
public void GetAttributeValueByName_ReturnsValueNullIfNotFound() | ||
{ | ||
// arrange | ||
var syntaxTree = SyntaxFactory.ParseSyntaxTree(@" | ||
using System; | ||
using System.Collections.Generic; | ||
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] | ||
sealed class MyAttribute : Attribute | ||
{ | ||
public MyAttribute(string arg1, string arg2) | ||
{ | ||
} | ||
}"); | ||
|
||
var compilation = CSharpCompilation.Create("MyCompilation", | ||
references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }, | ||
syntaxTrees: new[] { syntaxTree } | ||
); | ||
|
||
var root = syntaxTree.GetCompilationUnitRoot(); | ||
var @class = root.DescendantNodes()?.OfType<ClassDeclarationSyntax>().FirstOrDefault(); | ||
var symbol = compilation.GetSemanticModel(syntaxTree).GetDeclaredSymbol(@class); | ||
var attribute = symbol.GetAttributes().First(); | ||
|
||
// act | ||
var actual = RoslynExtensions.GetAttributeValueByName(attribute, _notFoundName); | ||
|
||
// assert | ||
Assert.Null(actual.Value); | ||
} | ||
|
||
[Fact] | ||
public void GetAttributeValueByNameAsString_ReturnsString() | ||
{ | ||
// arrange | ||
var syntaxTree = SyntaxFactory.ParseSyntaxTree(@" | ||
using System; | ||
using System.Collections.Generic; | ||
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] | ||
sealed class MyAttribute : Attribute | ||
{ | ||
public MyAttribute(Type type, string name) | ||
{ | ||
Name = name; | ||
Type = type; | ||
} | ||
public Type Type { get; } | ||
public string Name { get; } | ||
}"); | ||
|
||
var compilation = CSharpCompilation.Create("MyCompilation", | ||
references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }, | ||
syntaxTrees: new[] { syntaxTree } | ||
); | ||
var root = syntaxTree.GetCompilationUnitRoot(); | ||
var @class = root.DescendantNodes()?.OfType<ClassDeclarationSyntax>() | ||
.Single(x => x.AttributeLists.Count > 0); | ||
|
||
var symbol = compilation.GetSemanticModel(syntaxTree).GetDeclaredSymbol(@class); | ||
var attribute = symbol.GetAttributes().First(); | ||
|
||
var expected = "True"; | ||
|
||
// act | ||
var actual = RoslynExtensions.GetAttributeValueByNameAsString(attribute, "AllowMultiple", "SomeDefaultValue"); | ||
|
||
// assert | ||
Assert.Equal(expected, actual); | ||
} | ||
|
||
[Fact] | ||
public void GetAttributeValueByNameAsString_ReturnsNullIfNotFound() | ||
{ | ||
// arrange | ||
var syntaxTree = SyntaxFactory.ParseSyntaxTree(@" | ||
using System; | ||
using System.Collections.Generic; | ||
[ExcludeFromCodeCoverage] | ||
[AttributeUsage(AttributeTargets.All)] | ||
sealed class MyAttribute : Attribute | ||
{ | ||
public MyAttribute() | ||
{ | ||
} | ||
}"); | ||
|
||
var compilation = CSharpCompilation.Create("MyCompilation", | ||
references: new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }, | ||
syntaxTrees: new[] { syntaxTree }); | ||
var root = syntaxTree.GetCompilationUnitRoot(); | ||
var @class = root.DescendantNodes()?.OfType<ClassDeclarationSyntax>().FirstOrDefault(); | ||
var symbol = compilation.GetSemanticModel(syntaxTree).GetDeclaredSymbol(@class); | ||
var attribute = symbol.GetAttributes().First(); | ||
|
||
//// act | ||
var actual = RoslynExtensions.GetAttributeValueByNameAsString(attribute, _notFoundName); | ||
|
||
//// assert | ||
Assert.Equal(_placeholder, actual); | ||
} | ||
} |