Skip to content

Commit

Permalink
Support C# 10 'record struct'
Browse files Browse the repository at this point in the history
Fixes #3384
  • Loading branch information
sharwell committed Nov 10, 2021
1 parent 67850fe commit 88a4372
Show file tree
Hide file tree
Showing 29 changed files with 1,261 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ private Document CreateCodeFix(Document document, IndentationSettings indentatio
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.StructDeclaration:
case SyntaxKindEx.RecordDeclaration:
case SyntaxKindEx.RecordStructDeclaration:
case SyntaxKind.EnumDeclaration:
newSyntaxRoot = this.RegisterBaseTypeDeclarationCodeFix(syntaxRoot, (BaseTypeDeclarationSyntax)node, indentationSettings);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ private static Task<Document> GetTransformedDocumentAsync(Document document, Syn
break;

case SyntaxKindEx.RecordDeclaration:
case SyntaxKindEx.RecordStructDeclaration:
updatedDeclarationNode = HandleRecordDeclaration((RecordDeclarationSyntaxWrapper)declarationNode);
break;

Expand Down Expand Up @@ -378,6 +379,7 @@ private static SyntaxNode FindParentDeclarationNode(SyntaxNode node)
case SyntaxKind.EnumDeclaration:
case SyntaxKind.StructDeclaration:
case SyntaxKindEx.RecordDeclaration:
case SyntaxKindEx.RecordStructDeclaration:
case SyntaxKind.DelegateDeclaration:
case SyntaxKind.EventDeclaration:
case SyntaxKind.EventFieldDeclaration:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ private static TypeDeclarationSyntax ReplaceModifiers(TypeDeclarationSyntax node
case SyntaxKind.StructDeclaration:
return ((StructDeclarationSyntax)node).WithModifiers(modifiers);
case SyntaxKindEx.RecordDeclaration:
case SyntaxKindEx.RecordStructDeclaration:
return ((RecordDeclarationSyntaxWrapper)node).WithModifiers(modifiers);
}

Expand All @@ -132,6 +133,7 @@ private static TypeDeclarationSyntax ReplaceKeyword(TypeDeclarationSyntax node,
case SyntaxKind.StructDeclaration:
return ((StructDeclarationSyntax)node).WithKeyword(keyword);
case SyntaxKindEx.RecordDeclaration:
case SyntaxKindEx.RecordStructDeclaration:
return ((RecordDeclarationSyntaxWrapper)node).WithKeyword(keyword);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,50 @@

namespace StyleCop.Analyzers.Test.CSharp10.NamingRules
{
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using StyleCop.Analyzers.Test.CSharp9.NamingRules;
using StyleCop.Analyzers.Test.Verifiers;
using Xunit;
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
StyleCop.Analyzers.NamingRules.SA1313ParameterNamesMustBeginWithLowerCaseLetter,
StyleCop.Analyzers.NamingRules.RenameToLowerCaseCodeFixProvider>;

public class SA1313CSharp10UnitTests : SA1313CSharp9UnitTests
{
[Theory]
[WorkItem(3384, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3384")]
[InlineData("class")]
[InlineData("struct")]
public async Task TestRecordTypeAsync(string typeKind)
{
var testCode = $@"
public record {typeKind} R(int A)
{{
public R(int [|A|], int [|B|])
: this(A)
{{
}}
}}
";

var fixedCode = $@"
public record {typeKind} R(int A)
{{
public R(int a, int b)
: this(a)
{{
}}
}}
";

await new CSharpTest(LanguageVersion.CSharp10)
{
ReferenceAssemblies = GenericAnalyzerTest.ReferenceAssembliesNet60,
TestCode = testCode,
FixedCode = fixedCode,
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,22 @@ public static IEnumerable<object[]> Types
{
get
{
yield return new object[] { "class Foo<Ta, Tb> { }" };
yield return new object[] { "struct Foo<Ta, Tb> { }" };
yield return new object[] { "interface Foo<Ta, Tb> { }" };
yield return new object[] { "class Foo<Ta, T\\u0062> { }" };
yield return new object[] { "struct Foo<Ta, T\\u0062> { }" };
yield return new object[] { "interface Foo<Ta, T\\u0062> { }" };
yield return new object[] { "class Foo<{|#0:Ta|}, {|#1:Tb|}> { }" };
yield return new object[] { "struct Foo<{|#0:Ta|}, {|#1:Tb|}> { }" };
yield return new object[] { "interface Foo<{|#0:Ta|}, {|#1:Tb|}> { }" };
yield return new object[] { "class Foo<{|#0:Ta|}, {|#1:T\\u0062|}> { }" };
yield return new object[] { "struct Foo<{|#0:Ta|}, {|#1:T\\u0062|}> { }" };
yield return new object[] { "interface Foo<{|#0:Ta|}, {|#1:T\\u0062|}> { }" };
if (LightupHelpers.SupportsCSharp9)
{
yield return new object[] { "record Foo<Ta, Tb> { }" };
yield return new object[] { "record Foo<Ta, T\\u0062> { }" };
yield return new object[] { "record Foo<{|#0:Ta|}, {|#1:Tb|}> { }" };
yield return new object[] { "record Foo<{|#0:Ta|}, {|#1:T\\u0062|}> { }" };
}

if (LightupHelpers.SupportsCSharp10)
{
yield return new object[] { "record class Foo<{|#0:Ta|}, {|#1:Tb|}> { }" };
yield return new object[] { "record struct Foo<{|#0:Ta|}, {|#1:T\\u0062|}> { }" };
}
}
}
Expand Down Expand Up @@ -264,8 +270,8 @@ public async Task TestTypesWithMissingDocumentationAsync(string p)

var expected = new[]
{
Diagnostic().WithLocation(5, 22).WithArguments("Ta"),
Diagnostic().WithLocation(5, 26).WithArguments("Tb"),
Diagnostic().WithLocation(0).WithArguments("Ta"),
Diagnostic().WithLocation(1).WithArguments("Tb"),
};

await VerifyCSharpDiagnosticAsync(testCode.Replace("##", p), expected, CancellationToken.None).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,22 @@ public static IEnumerable<object[]> Types
{
get
{
yield return new object[] { "class Foo<Ta, Tb> { }" };
yield return new object[] { "struct Foo<Ta, Tb> { }" };
yield return new object[] { "interface Foo<Ta, Tb> { }" };
yield return new object[] { "class Foo<Ta, T\\u0062> { }" };
yield return new object[] { "struct Foo<Ta, T\\u0062> { }" };
yield return new object[] { "interface Foo<Ta, T\\u0062> { }" };
yield return new object[] { "class Foo<{|#0:Ta|}, {|#1:Tb|}> { }" };
yield return new object[] { "struct Foo<{|#0:Ta|}, {|#1:Tb|}> { }" };
yield return new object[] { "interface Foo<{|#0:Ta|}, {|#1:Tb|}> { }" };
yield return new object[] { "class Foo<{|#0:Ta|}, {|#1:T\\u0062|}> { }" };
yield return new object[] { "struct Foo<{|#0:Ta|}, {|#1:T\\u0062|}> { }" };
yield return new object[] { "interface Foo<{|#0:Ta|}, {|#1:T\\u0062|}> { }" };
if (LightupHelpers.SupportsCSharp9)
{
yield return new object[] { "record Foo<Ta, Tb> { }" };
yield return new object[] { "record Foo<Ta, T\\u0062> { }" };
yield return new object[] { "record Foo<{|#0:Ta|}, {|#1:Tb|}> { }" };
yield return new object[] { "record Foo<{|#0:Ta|}, {|#1:T\\u0062|}> { }" };
}

if (LightupHelpers.SupportsCSharp9)
{
yield return new object[] { "record class Foo<{|#0:Ta|}, {|#1:Tb|}> { }" };
yield return new object[] { "record struct Foo<{|#0:Ta|}, {|#1:T\\u0062|}> { }" };
}
}
}
Expand Down Expand Up @@ -120,8 +126,8 @@ public async Task TestPartialTypesWithMissingDocumentationAsync(string p)

var expected = new[]
{
Diagnostic().WithLocation(5, 30).WithArguments("Ta"),
Diagnostic().WithLocation(5, 34).WithArguments("Tb"),
Diagnostic().WithLocation(0).WithArguments("Ta"),
Diagnostic().WithLocation(1).WithArguments("Tb"),
};

await VerifyCSharpDiagnosticAsync(testCode.Replace("##", p), expected, CancellationToken.None).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public static IEnumerable<object[]> Members
{
yield return new[] { "public record Test { }" };
}

if (LightupHelpers.SupportsCSharp10)
{
yield return new[] { "public record class Test { }" };
yield return new[] { "public record struct Test { }" };
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ public static IEnumerable<object[]> DataTypeDeclarationKeywords
{
yield return new[] { "class" };
yield return new[] { "struct" };

if (LightupHelpers.SupportsCSharp9)
{
yield return new[] { "record" };
}

if (LightupHelpers.SupportsCSharp10)
{
yield return new[] { "record class" };
yield return new[] { "record struct" };
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ public static IEnumerable<object[]> ValidDeclarations
yield return new object[] { "internal sealed partial record" };
yield return new object[] { "record" };
}

if (LightupHelpers.SupportsCSharp10)
{
yield return new object[] { "public partial record class" };
yield return new object[] { "internal partial record class" };
yield return new object[] { "public sealed partial record class" };
yield return new object[] { "internal sealed partial record class" };
yield return new object[] { "record class" };

yield return new object[] { "public partial record struct" };
yield return new object[] { "internal partial record struct" };
yield return new object[] { "record struct" };
}
}
}

Expand All @@ -72,6 +85,14 @@ public static IEnumerable<object[]> InvalidDeclarations
yield return new object[] { "partial record" };
yield return new object[] { "sealed partial record" };
}

if (LightupHelpers.SupportsCSharp10)
{
yield return new object[] { "partial record class" };
yield return new object[] { "sealed partial record class" };

yield return new object[] { "partial record struct" };
}
}
}

Expand Down Expand Up @@ -113,6 +134,23 @@ public static IEnumerable<object[]> ValidNestedDeclarations
yield return new object[] { "private", "record" };
yield return new object[] { "private protected", "record" };
}

if (LightupHelpers.SupportsCSharp10)
{
yield return new object[] { "public", "record class" };
yield return new object[] { "protected", "record class" };
yield return new object[] { "internal", "record class" };
yield return new object[] { "protected internal", "record class" };
yield return new object[] { "private", "record class" };
yield return new object[] { "private protected", "record class" };

yield return new object[] { "public", "record struct" };
yield return new object[] { "protected", "record struct" };
yield return new object[] { "internal", "record struct" };
yield return new object[] { "protected internal", "record struct" };
yield return new object[] { "private", "record struct" };
yield return new object[] { "private protected", "record struct" };
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ internal static string GetNameOrIdentifier(MemberDeclarationSyntax member)
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.StructDeclaration:
case SyntaxKindEx.RecordDeclaration:
case SyntaxKindEx.RecordStructDeclaration:
return ((TypeDeclarationSyntax)member).Identifier.Text;

case SyntaxKind.EnumDeclaration:
Expand Down
6 changes: 4 additions & 2 deletions StyleCop.Analyzers/StyleCop.Analyzers/Helpers/SyntaxKinds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ internal static class SyntaxKinds
SyntaxKind.StructDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKind.EnumDeclaration,
SyntaxKindEx.RecordDeclaration);
SyntaxKindEx.RecordDeclaration,
SyntaxKindEx.RecordStructDeclaration);

/// <summary>
/// Gets a collection of <see cref="SyntaxKind"/> values which appear in the syntax tree as a
Expand All @@ -39,7 +40,8 @@ internal static class SyntaxKinds
SyntaxKind.ClassDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKindEx.RecordDeclaration);
SyntaxKindEx.RecordDeclaration,
SyntaxKindEx.RecordStructDeclaration);

/// <summary>
/// Gets a collection of <see cref="SyntaxKind"/> values which appear in the syntax tree as a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

namespace StyleCop.Analyzers.Lightup
{
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

internal readonly partial struct BaseExpressionColonSyntaxWrapper : ISyntaxWrapper<CSharpSyntaxNode>
{
internal const string WrappedTypeName = "Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax";
private static readonly Type WrappedType;

private static readonly Func<CSharpSyntaxNode, ExpressionSyntax> ExpressionAccessor;
private static readonly Func<CSharpSyntaxNode, SyntaxToken> ColonTokenAccessor;
private static readonly Func<CSharpSyntaxNode, ExpressionSyntax, CSharpSyntaxNode> WithExpressionAccessor;
private static readonly Func<CSharpSyntaxNode, SyntaxToken, CSharpSyntaxNode> WithColonTokenAccessor;

private readonly CSharpSyntaxNode node;

static BaseExpressionColonSyntaxWrapper()
{
WrappedType = SyntaxWrapperHelper.GetWrappedType(typeof(BaseExpressionColonSyntaxWrapper));
ExpressionAccessor = LightupHelpers.CreateSyntaxPropertyAccessor<CSharpSyntaxNode, ExpressionSyntax>(WrappedType, nameof(Expression));
ColonTokenAccessor = LightupHelpers.CreateSyntaxPropertyAccessor<CSharpSyntaxNode, SyntaxToken>(WrappedType, nameof(ColonToken));
WithExpressionAccessor = LightupHelpers.CreateSyntaxWithPropertyAccessor<CSharpSyntaxNode, ExpressionSyntax>(WrappedType, nameof(Expression));
WithColonTokenAccessor = LightupHelpers.CreateSyntaxWithPropertyAccessor<CSharpSyntaxNode, SyntaxToken>(WrappedType, nameof(ColonToken));
}

private BaseExpressionColonSyntaxWrapper(CSharpSyntaxNode node)
{
this.node = node;
}

public CSharpSyntaxNode SyntaxNode => this.node;

public ExpressionSyntax Expression
{
get
{
return ExpressionAccessor(this.SyntaxNode);
}
}

public SyntaxToken ColonToken
{
get
{
return ColonTokenAccessor(this.SyntaxNode);
}
}

public static explicit operator BaseExpressionColonSyntaxWrapper(SyntaxNode node)
{
if (node == null)
{
return default;
}

if (!IsInstance(node))
{
throw new InvalidCastException($"Cannot cast '{node.GetType().FullName}' to '{WrappedTypeName}'");
}

return new BaseExpressionColonSyntaxWrapper((CSharpSyntaxNode)node);
}

public static implicit operator CSharpSyntaxNode(BaseExpressionColonSyntaxWrapper wrapper)
{
return wrapper.node;
}

public static bool IsInstance(SyntaxNode node)
{
return node != null && LightupHelpers.CanWrapNode(node, WrappedType);
}

public BaseExpressionColonSyntaxWrapper WithExpression(ExpressionSyntax expression)
{
return new BaseExpressionColonSyntaxWrapper(WithExpressionAccessor(this.SyntaxNode, expression));
}

public BaseExpressionColonSyntaxWrapper WithColonToken(SyntaxToken colonToken)
{
return new BaseExpressionColonSyntaxWrapper(WithColonTokenAccessor(this.SyntaxNode, colonToken));
}

internal static BaseExpressionColonSyntaxWrapper FromUpcast(CSharpSyntaxNode node)
{
return new BaseExpressionColonSyntaxWrapper(node);
}
}
}
Loading

0 comments on commit 88a4372

Please sign in to comment.