diff --git a/InterfaceGenerator.Tests/AccessorsGenerationTests.cs b/InterfaceGenerator.Tests/AccessorsGenerationTests.cs index dfe68e8..75b0c25 100644 --- a/InterfaceGenerator.Tests/AccessorsGenerationTests.cs +++ b/InterfaceGenerator.Tests/AccessorsGenerationTests.cs @@ -32,7 +32,7 @@ namespace InterfaceGenerator.Tests public void PublicProperty_IsImplemented() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.PublicProperty)); + .GetProperty(nameof(IAccessorsTestsService.PublicProperty))!; prop.Should().NotBeNull(); @@ -47,14 +47,14 @@ namespace InterfaceGenerator.Tests public void InitProperty_IsImplemented() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.InitOnlyProperty)); + .GetProperty(nameof(IAccessorsTestsService.InitOnlyProperty))!; prop.Should().NotBeNull(); prop.GetMethod.Should().NotBeNull(); prop.SetMethod.Should().NotBeNull(); - prop.SetMethod.ReturnParameter.GetRequiredCustomModifiers().Should().Contain(typeof(IsExternalInit)); + prop.SetMethod!.ReturnParameter!.GetRequiredCustomModifiers().Should().Contain(typeof(IsExternalInit)); string _ = _sut.InitOnlyProperty; } @@ -63,7 +63,7 @@ namespace InterfaceGenerator.Tests public void PrivateSetter_IsOmitted() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.PropertyWithPrivateSetter)); + .GetProperty(nameof(IAccessorsTestsService.PropertyWithPrivateSetter))!; prop.Should().NotBeNull(); @@ -77,7 +77,7 @@ namespace InterfaceGenerator.Tests public void PrivateGetter_IsOmitted() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.PropertyWithPrivateGetter)); + .GetProperty(nameof(IAccessorsTestsService.PropertyWithPrivateGetter))!; prop.Should().NotBeNull(); @@ -91,7 +91,7 @@ namespace InterfaceGenerator.Tests public void ProtectedSetter_IsOmitted() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.PropertyWithProtectedSetter)); + .GetProperty(nameof(IAccessorsTestsService.PropertyWithProtectedSetter))!; prop.Should().NotBeNull(); @@ -105,7 +105,7 @@ namespace InterfaceGenerator.Tests public void ProtectedGetter_IsOmitted() { var prop = typeof(IAccessorsTestsService) - .GetProperty(nameof(IAccessorsTestsService.PropertyWithProtectedGetter)); + .GetProperty(nameof(IAccessorsTestsService.PropertyWithProtectedGetter))!; prop.Should().NotBeNull(); diff --git a/InterfaceGenerator.Tests/MethodGenerationTests.cs b/InterfaceGenerator.Tests/MethodGenerationTests.cs index 53aec89..05edbd9 100644 --- a/InterfaceGenerator.Tests/MethodGenerationTests.cs +++ b/InterfaceGenerator.Tests/MethodGenerationTests.cs @@ -20,7 +20,7 @@ namespace InterfaceGenerator.Tests public void VoidMethod_IsImplemented() { var method = typeof(IMethodsTestService).GetMethod( - nameof(MethodsTestService.VoidMethod)); + nameof(MethodsTestService.VoidMethod))!; method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -31,12 +31,30 @@ namespace InterfaceGenerator.Tests _sut.VoidMethod(); } + [Fact] + public void VoidMethodWithKeywordParam_IsImplemented() + { + var method = typeof(IMethodsTestService).GetMethod( + nameof(MethodsTestService.VoidMethodWithKeywordParam))!; + + method.Should().NotBeNull(); + method.ReturnType.Should().Be(typeof(void)); + + var parameters = method.GetParameters(); + parameters.Select(x => x.ParameterType).Should().AllBeEquivalentTo(typeof(string)); + parameters.Should().HaveCount(1); + + parameters[0].Name.Should().Be("void"); + + _sut.VoidMethodWithKeywordParam(""); + } + [Fact] public void VoidMethodWithParams_IsImplemented() { var method = typeof(IMethodsTestService).GetMethod( nameof(MethodsTestService.VoidMethodWithParams), - new[] { typeof(string), typeof(string) }); + new[] { typeof(string), typeof(string) })!; method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -53,7 +71,7 @@ namespace InterfaceGenerator.Tests { var method = typeof(IMethodsTestService).GetMethod( nameof(MethodsTestService.VoidMethodWithOutParam), - new[] { typeof(string).MakeByRefType() }); + new[] { typeof(string).MakeByRefType() })!; method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -71,7 +89,7 @@ namespace InterfaceGenerator.Tests { var method = typeof(IMethodsTestService).GetMethod( nameof(MethodsTestService.VoidMethodWithInParam), - new[] { typeof(string).MakeByRefType() }); + new[] { typeof(string).MakeByRefType() })!; method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -90,7 +108,7 @@ namespace InterfaceGenerator.Tests { var method = typeof(IMethodsTestService).GetMethod( nameof(MethodsTestService.VoidMethodWithRefParam), - new[] { typeof(string).MakeByRefType() }); + new[] { typeof(string).MakeByRefType() })!; method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -109,7 +127,7 @@ namespace InterfaceGenerator.Tests public void StringMethod_IsImplemented() { var method = typeof(IMethodsTestService).GetMethod( - nameof(MethodsTestService.StringMethod)); + nameof(MethodsTestService.StringMethod))!; method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(string)); @@ -125,7 +143,7 @@ namespace InterfaceGenerator.Tests { var method = typeof(IMethodsTestService) .GetMethods() - .FirstOrDefault(x => x.Name == nameof(MethodsTestService.GenericVoidMethod)); + .First(x => x.Name == nameof(MethodsTestService.GenericVoidMethod)); method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -143,7 +161,7 @@ namespace InterfaceGenerator.Tests { var method = typeof(IMethodsTestService) .GetMethods() - .FirstOrDefault(x => x.Name == nameof(MethodsTestService.GenericVoidMethodWithGenericParam)); + .First(x => x.Name == nameof(MethodsTestService.GenericVoidMethodWithGenericParam)); method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -163,7 +181,7 @@ namespace InterfaceGenerator.Tests { var method = typeof(IMethodsTestService) .GetMethods() - .FirstOrDefault(x => x.Name == nameof(MethodsTestService.GenericVoidMethodWithConstraints)); + .First(x => x.Name == nameof(MethodsTestService.GenericVoidMethodWithConstraints)); method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -188,7 +206,7 @@ namespace InterfaceGenerator.Tests { var method = typeof(IMethodsTestService) .GetMethods() - .FirstOrDefault(x => x.Name == nameof(MethodsTestService.VoidMethodWithOptionalParams)); + .First(x => x.Name == nameof(MethodsTestService.VoidMethodWithOptionalParams)); method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); @@ -213,9 +231,8 @@ namespace InterfaceGenerator.Tests { var method = typeof(IMethodsTestService) .GetMethods() - .FirstOrDefault(x => x.Name == nameof(MethodsTestService.VoidMethodWithExpandingParam)); + .First(x => x.Name == nameof(MethodsTestService.VoidMethodWithExpandingParam)); - method.Should().NotBeNull(); method.ReturnType.Should().Be(typeof(void)); var parameters = method.GetParameters(); @@ -257,6 +274,10 @@ namespace InterfaceGenerator.Tests public void VoidMethodWithParams(string a, string b) { } + + public void VoidMethodWithKeywordParam(string @void) + { + } public void VoidMethodWithOutParam(out string a) { diff --git a/InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs b/InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs index b69bc15..161bd80 100644 --- a/InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs +++ b/InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs @@ -17,13 +17,13 @@ namespace InterfaceGenerator.Tests public void RecordProperty_IsGenerated() { var prop = typeof(ITestRecord) - .GetProperty(nameof(TestRecord.RecordProperty)); + .GetProperty(nameof(TestRecord.RecordProperty))!; prop.Should().NotBeNull(); prop.GetMethod.Should().NotBeNull(); prop.SetMethod.Should().NotBeNull(); - prop.SetMethod.ReturnParameter.GetRequiredCustomModifiers().Should().Contain(typeof(IsExternalInit)); + prop.SetMethod!.ReturnParameter!.GetRequiredCustomModifiers().Should().Contain(typeof(IsExternalInit)); _sut.RecordProperty.Should().Be(420); } @@ -35,7 +35,7 @@ namespace InterfaceGenerator.Tests nameof(TestRecord.RecordMethod)); method.Should().NotBeNull(); - method.ReturnType.Should().Be(typeof(void)); + method!.ReturnType.Should().Be(typeof(void)); var parameters = method.GetParameters(); parameters.Should().BeEmpty(); @@ -50,7 +50,7 @@ namespace InterfaceGenerator.Tests nameof(TestRecord.Deconstruct)); method.Should().NotBeNull(); - method.ReturnType.Should().Be(typeof(void)); + method!.ReturnType.Should().Be(typeof(void)); var parameters = method.GetParameters(); parameters.Length.Should().Be(1); diff --git a/InterfaceGenerator/AttributeDataExtensions.cs b/InterfaceGenerator/AttributeDataExtensions.cs new file mode 100644 index 0000000..084842a --- /dev/null +++ b/InterfaceGenerator/AttributeDataExtensions.cs @@ -0,0 +1,14 @@ +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace InterfaceGenerator +{ + internal static class AttributeDataExtensions + { + public static string? GetNamedParamValue(this AttributeData attributeData, string paramName) + { + var pair = attributeData.NamedArguments.FirstOrDefault(x => x.Key == paramName); + return pair.Value.Value?.ToString(); + } + } +} \ No newline at end of file diff --git a/InterfaceGenerator/AutoInterfaceGenerator.cs b/InterfaceGenerator/AutoInterfaceGenerator.cs index b06f513..376448b 100644 --- a/InterfaceGenerator/AutoInterfaceGenerator.cs +++ b/InterfaceGenerator/AutoInterfaceGenerator.cs @@ -8,7 +8,6 @@ using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace InterfaceGenerator @@ -47,7 +46,9 @@ namespace InterfaceGenerator private void GenerateInterfaces(GeneratorExecutionContext context) { if (context.SyntaxReceiver is not SyntaxReceiver receiver) + { return; + } var compilation = GetCompilation(context); InitAttributes(compilation); @@ -67,50 +68,41 @@ namespace InterfaceGenerator } } - private string GetVisibilityModifier(INamedTypeSymbol implTypeSymbol, AttributeData attributeData) + private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeData attributeData) { - var pair = attributeData.NamedArguments.FirstOrDefault(x => x.Key == Attributes.VisibilityModifierPropName); - string? result = pair.Value.Value?.ToString(); - + string? result = attributeData.GetNamedParamValue(Attributes.VisibilityModifierPropName); if (!string.IsNullOrEmpty(result)) { - return result; + return result!; } return implTypeSymbol.DeclaredAccessibility switch { Accessibility.Public => "public", - var _ => "internal", + _ => "internal", }; } - private string GetInterfaceName(INamedTypeSymbol implTypeSymbol, AttributeData attributeData) + private static string InferInterfaceName(ISymbol implTypeSymbol, AttributeData attributeData) { - var pair = attributeData.NamedArguments.FirstOrDefault(x => x.Key == Attributes.InterfaceNamePropName); - string? result = pair.Value.Value?.ToString(); - - if (!string.IsNullOrEmpty(result)) - { - return result; - } - - return "I" + implTypeSymbol.Name; + return attributeData.GetNamedParamValue(Attributes.InterfaceNamePropName) ?? $"I{implTypeSymbol.Name}"; } private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeData attributeData) { using var stream = new MemoryStream(); var streamWriter = new StreamWriter(stream, Encoding.UTF8); - var codeWriter = new IndentedTextWriter(streamWriter, GeneratorConsts.Indent); + var codeWriter = new IndentedTextWriter(streamWriter, " "); var namespaceName = implTypeSymbol.ContainingNamespace.ToDisplayString(); - var interfaceName = GetInterfaceName(implTypeSymbol, attributeData); - var visibilityModifier = GetVisibilityModifier(implTypeSymbol, attributeData); + var interfaceName = InferInterfaceName(implTypeSymbol, attributeData); + var visibilityModifier = InferVisibilityModifier(implTypeSymbol, attributeData); codeWriter.WriteLine("namespace {0}", namespaceName); codeWriter.WriteLine("{"); ++codeWriter.Indent; + WriteSymbolDocsIfPresent(codeWriter, implTypeSymbol); codeWriter.Write("{0} partial interface {1}", visibilityModifier, interfaceName); WriteTypeGenericsIfNeeded(codeWriter, implTypeSymbol); codeWriter.WriteLine(); @@ -131,7 +123,7 @@ namespace InterfaceGenerator return reader.ReadToEnd(); } - private void WriteTypeGenericsIfNeeded(IndentedTextWriter writer, INamedTypeSymbol implTypeSymbol) + private static void WriteTypeGenericsIfNeeded(TextWriter writer, INamedTypeSymbol implTypeSymbol) { if (!implTypeSymbol.IsGenericType) { @@ -145,16 +137,12 @@ namespace InterfaceGenerator WriteTypeParameterConstraints(writer, implTypeSymbol.TypeParameters); } - private void GenerateInterfaceMemberDefinitions(IndentedTextWriter writer, INamedTypeSymbol implTypeSymbol) + private void GenerateInterfaceMemberDefinitions(TextWriter writer, INamespaceOrTypeSymbol implTypeSymbol) { foreach (var member in implTypeSymbol.GetMembers()) { - if (member.DeclaredAccessibility != Accessibility.Public) - { - continue; - } - - if (member.HasAttribute(_ignoreAttribute)) + if (member.DeclaredAccessibility != Accessibility.Public || + member.HasAttribute(_ignoreAttribute)) { continue; } @@ -163,7 +151,7 @@ namespace InterfaceGenerator } } - private static void GenerateInterfaceMemberDefinition(IndentedTextWriter writer, ISymbol member) + private static void GenerateInterfaceMemberDefinition(TextWriter writer, ISymbol member) { switch (member) { @@ -176,9 +164,9 @@ namespace InterfaceGenerator } } - private static void WriteMemberDocs(IndentedTextWriter writer, ISymbol member) + private static void WriteSymbolDocsIfPresent(TextWriter writer, ISymbol symbol) { - var xml = member.GetDocumentationCommentXml(); + var xml = symbol.GetDocumentationCommentXml(); if (string.IsNullOrWhiteSpace(xml)) { return; @@ -202,18 +190,17 @@ namespace InterfaceGenerator for (int i = 1; i < lines.Count - 1; i++) { - var line = lines[i]; + var line = lines[i].TrimStart(); // for some reason, 4 spaces are inserted to the beginning of the line writer.WriteLine("/// {0}", line); } } - private static bool IsPublicOrInternal(IMethodSymbol methodSymbol) + private static bool IsPublicOrInternal(ISymbol symbol) { - return methodSymbol.DeclaredAccessibility == Accessibility.Public || - methodSymbol.DeclaredAccessibility == Accessibility.Internal; + return symbol.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal; } - private static void GeneratePropertyDefinition(IndentedTextWriter writer, IPropertySymbol propertySymbol) + private static void GeneratePropertyDefinition(TextWriter writer, IPropertySymbol propertySymbol) { if (propertySymbol.IsStatic) { @@ -231,7 +218,7 @@ namespace InterfaceGenerator return; } - WriteMemberDocs(writer, propertySymbol); + WriteSymbolDocsIfPresent(writer, propertySymbol); if (propertySymbol.IsIndexer) { @@ -266,7 +253,7 @@ namespace InterfaceGenerator writer.WriteLine("}"); } - private static void GenerateMethodDefinition(IndentedTextWriter writer, IMethodSymbol methodSymbol) + private static void GenerateMethodDefinition(TextWriter writer, IMethodSymbol methodSymbol) { if (methodSymbol.MethodKind != MethodKind.Ordinary || methodSymbol.IsStatic) { @@ -280,7 +267,7 @@ namespace InterfaceGenerator return; } - WriteMemberDocs(writer, methodSymbol); + WriteSymbolDocsIfPresent(writer, methodSymbol); writer.Write("{0} {1}", methodSymbol.ReturnType, methodSymbol.Name); // ex. int Foo @@ -323,8 +310,16 @@ namespace InterfaceGenerator writer.Write("in "); break; } + + writer.Write(param.Type); + writer.Write(" "); - writer.Write("{0} {1}", param.Type, param.Name); + if (StringExtensions.IsCSharpKeyword(param.Name)) + { + writer.Write("@"); + } + + writer.Write(param.Name); if (param.HasExplicitDefaultValue) { @@ -365,7 +360,7 @@ namespace InterfaceGenerator } private static void WriteTypeParameterConstraints( - IndentedTextWriter writer, + TextWriter writer, IEnumerable typeParameters) { foreach (var typeParameter in typeParameters) @@ -395,10 +390,10 @@ namespace InterfaceGenerator return receiver.CandidateTypes.Select(candidate => GetTypeSymbol(compilation, candidate)); } - private static INamedTypeSymbol GetTypeSymbol(Compilation compilation, TypeDeclarationSyntax type) + private static INamedTypeSymbol GetTypeSymbol(Compilation compilation, SyntaxNode type) { var model = compilation.GetSemanticModel(type.SyntaxTree); - var typeSymbol = ModelExtensions.GetDeclaredSymbol(model, type)!; + var typeSymbol = model.GetDeclaredSymbol(type)!; return (INamedTypeSymbol)typeSymbol; } diff --git a/InterfaceGenerator/GeneratorConsts.cs b/InterfaceGenerator/GeneratorConsts.cs deleted file mode 100644 index a4b56f3..0000000 --- a/InterfaceGenerator/GeneratorConsts.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace InterfaceGenerator -{ - internal class GeneratorConsts - { - public const string Indent = " "; - } -} \ No newline at end of file diff --git a/InterfaceGenerator/InterfaceGenerator.csproj b/InterfaceGenerator/InterfaceGenerator.csproj index e30788b..3ba41c3 100644 --- a/InterfaceGenerator/InterfaceGenerator.csproj +++ b/InterfaceGenerator/InterfaceGenerator.csproj @@ -3,7 +3,7 @@ netstandard2 9.0 enable - 1.0.7 + 1.0.8 true false diff --git a/InterfaceGenerator/StringExtensions.cs b/InterfaceGenerator/StringExtensions.cs new file mode 100644 index 0000000..8859a69 --- /dev/null +++ b/InterfaceGenerator/StringExtensions.cs @@ -0,0 +1,125 @@ +namespace InterfaceGenerator +{ + internal static class StringExtensions + { + public static bool IsCSharpKeyword(string? name) + { + switch (name) + { + case "abstract": + case "add": + case "alias": + case "as": + case "ascending": + case "async": + case "await": + case "base": + case "bool": + case "break": + case "by": + case "byte": + case "case": + case "catch": + case "char": + case "checked": + case "class": + case "const": + case "continue": + case "decimal": + case "default": + case "delegate": + case "descending": + case "do": + case "double": + case "dynamic": + case "else": + case "enum": + case "equals": + case "event": + case "explicit": + case "extern": + case "false": + case "finally": + case "fixed": + case "float": + case "for": + case "foreach": + case "from": + case "get": + case "global": + case "goto": + // `group` is a contextual to linq queries that we don't generate + //case "group": + case "if": + case "implicit": + case "in": + case "int": + case "interface": + case "internal": + case "into": + case "is": + case "join": + case "let": + case "lock": + case "long": + case "nameof": + case "namespace": + case "new": + case "null": + case "object": + case "on": + case "operator": + // `orderby` is a contextual to linq queries that we don't generate + //case "orderby": + case "out": + case "override": + case "params": + case "partial": + case "private": + case "protected": + case "public": + case "readonly": + case "ref": + case "remove": + case "return": + case "sbyte": + case "sealed": + // `select` is a contextual to linq queries that we don't generate + // case "select": + case "set": + case "short": + case "sizeof": + case "stackalloc": + case "static": + case "string": + case "struct": + case "switch": + case "this": + case "throw": + case "true": + case "try": + case "typeof": + case "uint": + case "ulong": + case "unchecked": + case "unmanaged": + case "unsafe": + case "ushort": + case "using": + // `value` is a contextual to getters that we don't generate + // case "value": + case "var": + case "virtual": + case "void": + case "volatile": + case "when": + case "where": + case "while": + case "yield": + return true; + default: + return false; + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index d55192b..582cc62 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # InterfaceGenerator [![NuGet Version](http://img.shields.io/nuget/v/InterfaceGenerator.svg?style=flat)](https://www.nuget.org/packages/InterfaceGenerator/) -A simple source generator that creates interfaces by implementations. Useful for when you need an interface purely for the sake of mocking - or maybe you're not sure if you'll need the abstraction later on. +A simple source generator that creates interfaces by implementations. Useful for when you need an interface purely for the sake of mocking, or maybe you're not sure if you'll need the abstraction later on. Inspired by [net_automatic_interface](https://github.com/codecentric/net_automatic_interface)