Skip to content

Commit

Permalink
Record-structs: address misc other IDE scenarios (#52464)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv authored Apr 9, 2021
1 parent 7f3ffe4 commit 99ee4b4
Show file tree
Hide file tree
Showing 69 changed files with 607 additions and 156 deletions.
1 change: 1 addition & 0 deletions src/CodeStyle/Core/Analyzers/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.Parameter = 20 -> Micros
Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.Property = 13 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind
Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.RaiseAccessor = 28 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind
Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.RecordClass = 29 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind
Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.RecordStruct = 30 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind
Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.RemoveAccessor = 27 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind
Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.SetAccessor = 25 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind
Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind.Struct = 3 -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationKind
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,87 +348,6 @@ private static bool IsInAStringOrCharacter(SyntaxNode currentNode, SnapshotPoint
&& caret.Position < currentNode.Span.End
&& caret.Position > currentNode.SpanStart;

private static bool SemicolonIsMissing(SyntaxNode currentNode)
{
switch (currentNode.Kind())
{
case SyntaxKind.LocalDeclarationStatement:
return ((LocalDeclarationStatementSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.ReturnStatement:
return ((ReturnStatementSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.VariableDeclaration:
return SemicolonIsMissing(currentNode.Parent);
case SyntaxKind.ThrowStatement:
return ((ThrowStatementSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.DoStatement:
return ((DoStatementSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.GetAccessorDeclaration:
case SyntaxKind.SetAccessorDeclaration:
return ((AccessorDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.FieldDeclaration:
return ((FieldDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.ForStatement:
return ((ForStatementSyntax)currentNode).FirstSemicolonToken.IsMissing;
case SyntaxKind.ExpressionStatement:
return ((ExpressionStatementSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.EmptyStatement:
return ((EmptyStatementSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.GotoStatement:
return ((GotoStatementSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.BreakStatement:
return ((BreakStatementSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.ContinueStatement:
return ((ContinueStatementSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.YieldReturnStatement:
case SyntaxKind.YieldBreakStatement:
return ((YieldStatementSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.LocalFunctionStatement:
return ((LocalFunctionStatementSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.NamespaceDeclaration:
return ((NamespaceDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.UsingDirective:
return ((UsingDirectiveSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.ExternAliasDirective:
return ((ExternAliasDirectiveSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.ClassDeclaration:
return ((ClassDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.StructDeclaration:
return ((StructDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.InterfaceDeclaration:
return ((InterfaceDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.EnumDeclaration:
return ((EnumDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.DelegateDeclaration:
return ((DelegateDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.EventFieldDeclaration:
return ((EventFieldDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.MethodDeclaration:
return ((MethodDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.OperatorDeclaration:
return ((OperatorDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.ConversionOperatorDeclaration:
return ((ConversionOperatorDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.ConstructorDeclaration:
return ((ConstructorDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.BaseConstructorInitializer:
case SyntaxKind.ThisConstructorInitializer:
return ((ConstructorDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.DestructorDeclaration:
return ((DestructorDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.PropertyDeclaration:
return ((PropertyDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.IndexerDeclaration:
return ((IndexerDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
case SyntaxKind.AddAccessorDeclaration:
return ((AccessorDeclarationSyntax)currentNode).SemicolonToken.IsMissing;
default:
// At this point, the node should be empty or its children should not end with a semicolon.
Debug.Assert(!currentNode.ChildNodesAndTokens().Any()
|| !currentNode.ChildNodesAndTokens().Last().IsKind(SyntaxKind.SemicolonToken));
return false;
}
}

/// <summary>
/// Determines if a statement ends with a closing delimiter, and that closing delimiter exists.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4443,5 +4443,33 @@ class C
testHost,
Record("R"));
}

[Theory]
[CombinatorialData]
public async Task BasicRecordClassClassification(TestHost testHost)
{
await TestAsync(
@"record class R
{
R r;
R() { }
}",
testHost,
Record("R"));
}

[Theory]
[CombinatorialData]
public async Task BasicRecordStructClassification(TestHost testHost)
{
await TestAsync(
@"record struct R
{
R property { get; set; }
}",
testHost,
RecordStruct("R"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,56 @@ await TestAsync(
Punctuation.CloseCurly);
}

[Theory]
[CombinatorialData]
public async Task TestRecordClass(TestHost testHost)
{
await TestAsync(
@"record class R
{
R()
{
}
}",
testHost,
Keyword("record"),
Keyword("class"),
Record("R"),
Punctuation.OpenCurly,
Record("R"),
Punctuation.OpenParen,
Punctuation.CloseParen,
Punctuation.OpenCurly,
Punctuation.CloseCurly,
Punctuation.CloseCurly);
}

[Theory]
[CombinatorialData]
public async Task TestRecordStruct(TestHost testHost)
{
await TestAsync(
@"record struct R
{
R(int i)
{
}
}",
testHost,
Keyword("record"),
Keyword("struct"),
RecordStruct("R"),
Punctuation.OpenCurly,
RecordStruct("R"),
Punctuation.OpenParen,
Keyword("int"),
Parameter("i"),
Punctuation.CloseParen,
Punctuation.OpenCurly,
Punctuation.CloseCurly,
Punctuation.CloseCurly);
}

[Theory]
[CombinatorialData]
public async Task UsingAliasGlobalNamespace(TestHost testHost)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4206,24 +4206,50 @@ void Test()
}

[WorkItem(48453, "https://github.com/dotnet/roslyn/issues/48453")]
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)]
[InlineData("record")]
[InlineData("record class")]
public async Task TestInRecord(string record)
{
await TestInRegularAndScript1Async($@"
{record} Program
{{
int field;
public int this[int i] => [|this.field|];
}}",
$@"
{record} Program
{{
int field;
public int this[int i] => {{|Rename:GetField|}}();
private int GetField()
{{
return this.field;
}}
}}");
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsExtractMethod)]
public async Task TestInRecord()
public async Task TestInRecordStruct()
{
await TestInRegularAndScript1Async(@"
record Program
record struct Program
{
int field;
public int this[int i] => [|this.field|];
}",
@"
record Program
record struct Program
{
int field;
public int this[int i] => {|Rename:GetField|}();
private int GetField()
private readonly int GetField()
{
return this.field;
}
Expand Down
45 changes: 24 additions & 21 deletions src/EditorFeatures/CSharpTest/CodeLens/CSharpCodeLensTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,35 +215,38 @@ public class A
await RunMethodReferenceTest(input);
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeLens)]
public async Task TestFullyQualifiedName()
[Theory, Trait(Traits.Feature, Traits.Features.CodeLens)]
[InlineData("class")]
[InlineData("record class")]
[InlineData("record struct")]
public async Task TestFullyQualifiedName(string typeKind)
{
const string input = @"<Workspace>
var input = $@"<Workspace>
<Project Language=""C#"" CommonReferences=""true"" AssemblyName=""Proj1"">
<Document FilePath=""CurrentDocument.cs""><![CDATA[
public class A
{
{|A.C: public void C()
{
public {typeKind} A
{{
{{|A.C: public void C()
{{
C();
}|}
}}|}}
public class B
{
{|A+B.C: public void C()
{
public {typeKind} B
{{
{{|A+B.C: public void C()
{{
C();
}|}
}}|}}
public class D
{
{|A+B+D.C: public void C()
{
public {typeKind} D
{{
{{|A+B+D.C: public void C()
{{
C();
}|}
}
}
}
}}|}}
}}
}}
}}
]]>
</Document>
</Project>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,22 @@ class C : IList
await VerifyItemExistsAsync(markup, "IList");
}

[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task TestAtStartOfRecord()
[Theory, Trait(Traits.Feature, Traits.Features.Completion)]
[InlineData("record")]
[InlineData("record class")]
[InlineData("record struct")]
public async Task TestAtStartOfRecord(string record)
{
var markup = @"
var markup = $@"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"" LanguageVersion=""Preview"">
<Document>
using System.Collections;
record C : IList
{
{record} C : IList
{{
int $$
}
}}
</Document>
</Project>
</Workspace>";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,10 @@ public async Task SuggestEventAfterReadonlyInStruct()
[Theory, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
[WorkItem(39265, "https://github.com/dotnet/roslyn/issues/39265")]
[InlineData("struct", true)]
[InlineData("record struct", true)]
[InlineData("class", false)]
[InlineData("record", false)]
[InlineData("record class", false)]
[InlineData("interface", false)]
public async Task SuggestReadonlyPropertyAccessor(string declarationType, bool present)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8724,30 +8724,33 @@ public void M1()
}

[WorkItem(48295, "https://github.com/dotnet/roslyn/issues/48295")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)]
public async Task TestImplementOnRecord_WithSemiColonAndTrivia()
[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsImplementInterface)]
[InlineData("record")]
[InlineData("record class")]
[InlineData("record struct")]
public async Task TestImplementOnRecord_WithSemiColonAndTrivia(string record)
{
await TestInRegularAndScriptAsync(@"
await TestInRegularAndScriptAsync($@"
interface I
{
{{
void M1();
}
}}
record C : [|I|]; // hello
{record} C : [|I|]; // hello
",
@"
$@"
interface I
{
{{
void M1();
}
}}
record C : [|I|] // hello
{
{record} C : [|I|] // hello
{{
public void M1()
{
{{
throw new System.NotImplementedException();
}
}
}}
}}
");
}

Expand Down
Loading

0 comments on commit 99ee4b4

Please sign in to comment.