Skip to content

Commit

Permalink
Fixes and inprovements
Browse files Browse the repository at this point in the history
  • Loading branch information
sungaila committed Mar 10, 2023
1 parent e6eae19 commit 6fabf1d
Show file tree
Hide file tree
Showing 15 changed files with 569 additions and 24 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The source generator will produce classes containing the matching unit tests.

```csharp
// shortened code for readability
[GeneratedCode("Sungaila.InlineTest", "0.9.0-preview+17ac90a4b0b471c88edc5fcedee4124a7cbbac28")]
[GeneratedCode("Sungaila.InlineTest", "1.0.0+17ac90a4b0b471c88edc5fcedee4124a7cbbac28")]
[TestClass]
public partial class ReadmeExampleTests
{
Expand Down Expand Up @@ -71,7 +71,8 @@ public partial class ReadmeExampleTests
## Restrictions
1. The same Attribute rules apply here. Your parameters have to be either
- a constant value
- a `System.Type`
- a `System.Type` (defined at compile-time)
- a single-dimensional array of the above
2. The annotated method must be `static` or the declaring class must have a parameterless constructor.
3. The annotated method must not have more than 16 parameters.
2. The method must not have more than 15 parameters.
3. The method must be defined inside a `class` or `struct`.
4. For classes without a parameterless constructor the method must be `static`.
3 changes: 2 additions & 1 deletion src/InlineTest.Tests/InlineTest.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
<Nullable>enable</Nullable>
<Features>strict</Features>
<WarningsAsErrors>nullable</WarningsAsErrors>
<NoWarn>CA1822,CA2208,IDE0060</NoWarn>
</PropertyGroup>

<!-- Project references -->
<ItemGroup>
<ProjectReference Include="..\InlineTest\InlineTest.csproj" OutputItemType="Analyzer"/>
<ProjectReference Include="..\InlineTest\InlineTest.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="True"/>
</ItemGroup>
</Project>
17 changes: 17 additions & 0 deletions src/InlineTest.Tests/MethodOverloadTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Sungaila.InlineTest.Tests
{
public class MethodOverloadTests
{
[AreEqual(Expected = (string?)null)]
public static string? EchoValue() => null;

[AreEqual("Moin Moin", Expected = "Moin Moin")]
public static string? EchoValue(string input) => input;

[AreEqual("Moin Moin", Expected = "Moin Moin")]
public static object? EchoValue(object input) => input;

[AreEqual(1994, Expected = 1994)]
public static int EchoValue(int input) => input;
}
}
2 changes: 1 addition & 1 deletion src/InlineTest.Tests/ReadmeExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public int Divide(int dividend, int divisor)
public void CheckForGeneratedTests()
{
// find the generated test class
var type = typeof(ReadmeExample).Assembly.GetType("Sungaila.InlineTest.Generated.ReadmeExampleTests", true) ?? throw new NullReferenceException();
var type = typeof(ReadmeExample).Assembly.GetType("Sungaila.InlineTest.Generated.Sungaila_InlineTest_Tests_ReadmeExample", true) ?? throw new NullReferenceException();

// there must be a TestClassAttribute
Assert.IsNotNull(type.GetCustomAttribute<TestClassAttribute>());
Expand Down
51 changes: 51 additions & 0 deletions src/InlineTest.Tests/StructTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace Sungaila.InlineTest.Tests
{
public struct StructTests
{
// structs must have a parameterless constructor
// so this dummy constructor should be fine
public StructTests(object? something) { }

[AreEqual("Moin Moin", Expected = "Moin Moin")]
public string? EchoValueInstance(string input) => input;

[AreEqual("Moin Moin", Expected = "Moin Moin")]
public static string? EchoValueStatic(string input) => input;

public struct SubStructDepth1
{
[AreEqual("Moin Moin", Expected = "Moin Moin")]
public string? EchoValueInstance(string input) => input;

[AreEqual("Moin Moin", Expected = "Moin Moin")]
public static string? EchoValueStatic(string input) => input;

public struct SubStructDepth2
{
[AreEqual("Moin Moin", Expected = "Moin Moin")]
public string? EchoValueInstance(string input) => input;

[AreEqual("Moin Moin", Expected = "Moin Moin")]
public static string? EchoValueStatic(string input) => input;

public struct SubStructDepth3
{
[AreEqual("Moin Moin", Expected = "Moin Moin")]
public string? EchoValueInstance(string input) => input;

[AreEqual("Moin Moin", Expected = "Moin Moin")]
public static string? EchoValueStatic(string input) => input;

public struct SubStructDepth4
{
[AreEqual("Moin Moin", Expected = "Moin Moin")]
public string? EchoValueInstance(string input) => input;

[AreEqual("Moin Moin", Expected = "Moin Moin")]
public static string? EchoValueStatic(string input) => input;
}
}
}
}
}
}
47 changes: 47 additions & 0 deletions src/InlineTest.Tests/SubClassTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace Sungaila.InlineTest.Tests
{
public class SubClassTests
{
[AreEqual(Expected = 0.0d)]
public double GetRealInstance() => 0.0d;

[AreEqual(Expected = 0.0d)]
public static double GetRealStatic() => 0.0d;

public class SubClassDepth1
{
[AreEqual(Expected = 0.0d)]
public double GetRealInstance() => 0.0d;

[AreEqual(Expected = 0.0d)]
public static double GetRealStatic() => 0.0d;

public class SubClassDepth2
{
[AreEqual(Expected = 0.0d)]
public double GetRealInstance() => 0.0d;

[AreEqual(Expected = 0.0d)]
public static double GetRealStatic() => 0.0d;

public class SubClassDepth3
{
[AreEqual(Expected = 0.0d)]
public double GetRealInstance() => 0.0d;

[AreEqual(Expected = 0.0d)]
public static double GetRealStatic() => 0.0d;

public class SubClassDepth4
{
[AreEqual(Expected = 0.0d)]
public double GetRealInstance() => 0.0d;

[AreEqual(Expected = 0.0d)]
public static double GetRealStatic() => 0.0d;
}
}
}
}
}
}
4 changes: 2 additions & 2 deletions src/InlineTest.Tests/ThrowsExceptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class ThrowsExceptionTests
}

[ThrowsException<ArgumentException>("Null")]
[ThrowsException<InvalidOperationException>]
[ThrowsException<InvalidOperationException>("Something")]
public object? Throw(string input)
{
if (input == "Null")
Expand All @@ -28,7 +28,7 @@ public class ThrowsExceptionTests
}

[ThrowsException<ArgumentException>("Null")]
[ThrowsException<InvalidOperationException>]
[ThrowsException<InvalidOperationException>("Something")]
public object? Throw2(string input)
{
if (input == "Null")
Expand Down
15 changes: 15 additions & 0 deletions src/InlineTest/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
; Shipped analyzer releases
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

## Release 1.0.0

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
IT0000 | Method | Error | InlineTestAnalyzer, [Documentation](https://github.com/sungaila/InlineTest)
IT0001 | Method | Error | InlineTestAnalyzer, [Documentation](https://github.com/sungaila/InlineTest)
IT0002 | Method | Error | InlineTestAnalyzer, [Documentation](https://github.com/sungaila/InlineTest)
IT0003 | Method | Warning | InlineTestAnalyzer, [Documentation](https://github.com/sungaila/InlineTest)
IT0004 | Style | Warning | InlineTestAnalyzer, [Documentation](https://github.com/sungaila/InlineTest)
IT0005 | Style | Warning | InlineTestAnalyzer, [Documentation](https://github.com/sungaila/InlineTest)
3 changes: 3 additions & 0 deletions src/InlineTest/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Sungaila.InlineTest.CodeFixProviders
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AreEqualNoExpectedValueCodeFixProvider)), Shared]
internal class AreEqualNoExpectedValueCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(InlineTestAnalyzer.AreEqualNoExpectedValueId);

public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
#if FALSE && DEBUG
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Launch();
}
#endif
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

var diagnostic = context.Diagnostics.Single();
var diagnosticSpan = diagnostic.Location.SourceSpan;
var declaration = root!.FindToken(diagnosticSpan.Start).Parent!.FirstAncestorOrSelf<AttributeSyntax>();

context.RegisterCodeFix(
CodeAction.Create(
"Add Expected parameter",
c => AddExpectedParameterAsync(context.Document, declaration!, c),
"Add Expected parameter"
),
diagnostic);
}

private async Task<Document> AddExpectedParameterAsync(Document document, AttributeSyntax attributeSyntax, CancellationToken cancellationToken)
{
var newArgument = SyntaxFactory.AttributeArgument(
SyntaxFactory.NameEquals(nameof(AreEqualAttribute.Expected)),
null,
SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression)
);

var oldRoot = await document.GetSyntaxRootAsync(cancellationToken);
SyntaxNode newRoot;

if (attributeSyntax.ArgumentList == null)
{
newRoot = oldRoot!.ReplaceNode(attributeSyntax,
attributeSyntax.WithArgumentList(SyntaxFactory.AttributeArgumentList(SyntaxFactory.SeparatedList(new[] { newArgument }))));
}
else
{
newRoot = oldRoot!.ReplaceNode(attributeSyntax, attributeSyntax.AddArgumentListArguments(newArgument));
}

return document.WithSyntaxRoot(newRoot);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Sungaila.InlineTest.CodeFixProviders
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AreEqualNoExpectedValueCodeFixProvider)), Shared]
internal class AreNotEqualNoNotExpectedValueCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(InlineTestAnalyzer.AreNotEqualNoNotExpectedValueId);

public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
#if FALSE && DEBUG
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Launch();
}
#endif
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

var diagnostic = context.Diagnostics.Single();
var diagnosticSpan = diagnostic.Location.SourceSpan;
var declaration = root!.FindToken(diagnosticSpan.Start).Parent!.FirstAncestorOrSelf<AttributeSyntax>();

context.RegisterCodeFix(
CodeAction.Create(
"Add NotExpected parameter",
c => AddExpectedParameterAsync(context.Document, declaration!, c),
"Add NotExpected parameter"
),
diagnostic);
}

private async Task<Document> AddExpectedParameterAsync(Document document, AttributeSyntax attributeSyntax, CancellationToken cancellationToken)
{
var newArgument = SyntaxFactory.AttributeArgument(
SyntaxFactory.NameEquals(nameof(AreNotEqualAttribute.NotExpected)),
null,
SyntaxFactory.LiteralExpression(SyntaxKind.DefaultLiteralExpression)
);

var oldRoot = await document.GetSyntaxRootAsync(cancellationToken);
SyntaxNode newRoot;

if (attributeSyntax.ArgumentList == null)
{
newRoot = oldRoot!.ReplaceNode(attributeSyntax,
attributeSyntax.WithArgumentList(SyntaxFactory.AttributeArgumentList(SyntaxFactory.SeparatedList(new[] { newArgument }))));
}
else
{
newRoot = oldRoot!.ReplaceNode(attributeSyntax, attributeSyntax.AddArgumentListArguments(newArgument));
}

return document.WithSyntaxRoot(newRoot);
}
}
}
30 changes: 26 additions & 4 deletions src/InlineTest/GeneratorContextHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Threading.Tasks;

namespace Sungaila.InlineTest
{
Expand All @@ -20,6 +18,24 @@ public static string GetFullyQualifiedMetadataName(this ISymbol namedTypeSymbol)
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters));
}

public static string GetFullyQualifiedMetadataNameWithoutGlobal(this ISymbol namedTypeSymbol)
{
return namedTypeSymbol.ToDisplayString(new SymbolDisplayFormat(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
memberOptions: SymbolDisplayMemberOptions.IncludeContainingType,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters));
}

public static string GetFullyQualifiedMetadataNameWithoutTypeParameters(this ISymbol namedTypeSymbol)
{
return namedTypeSymbol.ToDisplayString(new SymbolDisplayFormat(
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
memberOptions: SymbolDisplayMemberOptions.IncludeContainingType,
genericsOptions: SymbolDisplayGenericsOptions.None));
}

public static Microsoft.CodeAnalysis.TypeInfo GetTypeInfo(this SyntaxNode source, GeneratorExecutionContext context)
{
return context.Compilation.GetSemanticModel(source.SyntaxTree, true).GetTypeInfo(source, context.CancellationToken);
Expand Down Expand Up @@ -53,10 +69,16 @@ public static string InvokeMethod(this MethodDeclarationSyntax methodDef, Genera
return $"{(isAsyncMethod ? "await " : string.Empty)}{methodDef.GetDeclaredSymbol(context).GetFullyQualifiedMetadataName()}({paramWithoutTypeList}){(withSemicolon ? ";" : string.Empty)}";
}

if (methodDef.FirstAncestorOrSelf<ClassDeclarationSyntax>() is not ClassDeclarationSyntax classDef)
TypeDeclarationSyntax? classOrStructDef;

if (methodDef.FirstAncestorOrSelf<ClassDeclarationSyntax>() is ClassDeclarationSyntax classDef)
classOrStructDef = classDef;
else if (methodDef.FirstAncestorOrSelf<StructDeclarationSyntax>() is StructDeclarationSyntax structDef)
classOrStructDef = structDef;
else
throw new InvalidOperationException();

return $"{(isAsyncMethod ? "await " : string.Empty)}new {classDef.GetDeclaredSymbol(context).GetFullyQualifiedMetadataName()}().{methodDef.Identifier.ValueText}({paramWithoutTypeList}){(withSemicolon ? ";" : string.Empty)}";
return $"{(isAsyncMethod ? "await " : string.Empty)}new {classOrStructDef.GetDeclaredSymbol(context).GetFullyQualifiedMetadataName()}().{methodDef.Identifier.ValueText}({paramWithoutTypeList}){(withSemicolon ? ";" : string.Empty)}";
}

public static string InvokeMethodWithResult(this MethodDeclarationSyntax methodDef, GeneratorExecutionContext context)
Expand Down
Loading

0 comments on commit 6fabf1d

Please sign in to comment.