Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support C# 10 'record struct' #3401

Merged
merged 1 commit into from
Nov 10, 2021
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
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.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 @@ -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