Skip to content

Commit

Permalink
Merge pull request #60684 from DoctorKrolic/allow-generate-in-return-…
Browse files Browse the repository at this point in the history
…statement

Allow generating variable with contextual keyword name in return statements
  • Loading branch information
CyrusNajmabadi authored Apr 14, 2022
2 parents d9ac639 + 7120f9d commit b922e70
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 17 deletions.
134 changes: 120 additions & 14 deletions src/EditorFeatures/CSharpTest/GenerateVariable/GenerateVariableTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ public GenerateVariableTests(ITestOutputHelper logger)
internal override (DiagnosticAnalyzer?, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (null, new CSharpGenerateVariableCodeFixProvider());

private readonly CodeStyleOption2<bool> onWithInfo = new CodeStyleOption2<bool>(true, NotificationOption2.Suggestion);
private readonly CodeStyleOption2<bool> onWithInfo = new(true, NotificationOption2.Suggestion);

// specify all options explicitly to override defaults.
private OptionsCollection ImplicitTypingEverywhere()
=> new OptionsCollection(GetLanguage())
=> new(GetLanguage())
{
{ CSharpCodeStyleOptions.VarElsewhere, onWithInfo },
{ CSharpCodeStyleOptions.VarWhenTypeIsApparent, onWithInfo },
Expand Down Expand Up @@ -4078,18 +4078,6 @@ static void Goo(IEnumerable<string?> s)
index: LocalIndex);
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)]
public async Task TestLocalMissingForVar()
{
await TestMissingInRegularAndScriptAsync(
@"class Program
{
void Main()
{
var x = [|var|];
}");
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)]
public async Task TestOutLocal1()
{
Expand Down Expand Up @@ -9618,5 +9606,123 @@ void M(bool expected)
}
}", index: Parameter);
}

[WorkItem(27646, "https://github.com/dotnet/roslyn/issues/27646")]
[Theory]
[InlineData("yield")]
[InlineData("partial")]
[InlineData("group")]
[InlineData("join")]
[InlineData("into")]
[InlineData("let")]
[InlineData("by")]
[InlineData("where")]
[InlineData("select")]
[InlineData("get")]
[InlineData("set")]
[InlineData("add")]
[InlineData("remove")]
[InlineData("orderby")]
[InlineData("alias")]
[InlineData("on")]
[InlineData("equals")]
[InlineData("ascending")]
[InlineData("descending")]
[InlineData("assembly")]
[InlineData("module")]
[InlineData("type")]
[InlineData("global")]
[InlineData("field")]
[InlineData("method")]
[InlineData("param")]
[InlineData("property")]
[InlineData("typevar")]
[InlineData("when")]
[InlineData("_")]
[InlineData("or")]
[InlineData("and")]
[InlineData("not")]
[InlineData("with")]
[InlineData("init")]
[InlineData("record")]
[InlineData("managed")]
[InlineData("unmanaged")]
[InlineData("dynamic")]
public async Task TestContextualKeywordsThatDoNotProbablyStartSyntacticConstructs_ReturnStatement(string keyword)
{
await TestInRegularAndScriptAsync(
$@"class C
{{
int M()
{{
[|return {keyword}|];
}}
}}",
$@"class C
{{
private int {keyword};
int M()
{{
return {keyword};
}}
}}");
}

[WorkItem(27646, "https://github.com/dotnet/roslyn/issues/27646")]
[Theory]
[InlineData("from")]
[InlineData("nameof")]
[InlineData("async")]
[InlineData("await")]
[InlineData("var")]
public async Task TestContextualKeywordsThatCanProbablyStartSyntacticConstructs_ReturnStatement(string keyword)
{
await TestMissingInRegularAndScriptAsync(
$@"class C
{{
int M()
{{
[|return {keyword}|];
}}
}}");
}

[WorkItem(27646, "https://github.com/dotnet/roslyn/issues/27646")]
[Theory]
[InlineData("from")]
[InlineData("nameof")]
[InlineData("async")]
[InlineData("await")]
[InlineData("var")]
public async Task TestContextualKeywordsThatCanProbablyStartSyntacticConstructs_OnTheirOwn(string keyword)
{
await TestMissingInRegularAndScriptAsync(
$@"class C
{{
int M()
{{
[|{keyword}|]
}}
}}");
}

[WorkItem(27646, "https://github.com/dotnet/roslyn/issues/27646")]
[Theory]
[InlineData("from")]
[InlineData("nameof")]
[InlineData("async")]
[InlineData("await")]
[InlineData("var")]
public async Task TestContextualKeywordsThatCanProbablyStartSyntacticConstructs_Local(string keyword)
{
await TestMissingInRegularAndScriptAsync(
$@"class Program
{{
void Main()
{{
var x = [|{keyword}|];
}}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.GenerateMember.GenerateVariable;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.CSharp.GenerateMember.GenerateVariable
Expand All @@ -33,7 +32,28 @@ protected override bool IsExplicitInterfaceGeneration(SyntaxNode node)
=> node is PropertyDeclarationSyntax;

protected override bool IsIdentifierNameGeneration(SyntaxNode node)
=> node is IdentifierNameSyntax identifier && !identifier.Identifier.CouldBeKeyword();
=> node is IdentifierNameSyntax identifierName && !IsProbablySyntacticConstruct(identifierName.Identifier);

private static bool IsProbablySyntacticConstruct(SyntaxToken token)
{
// Technically all C# contextual keywords are valid member names.
// However some of them start various syntactic constructs
// and we don't want to show "Generate <member name>" codefix for them:
// 1. "from" starts LINQ expression
// 2. "nameof" is probably nameof(some_name)
// 3. "async" can start a delegate declaration
// 4. "await" starts await expression
// 5. "var" is used in constructions like "var x = ..."
// The list can be expanded in the future if necessary
// This method tells if the given SyntaxToken is one of the cases above
var contextualKind = SyntaxFacts.GetContextualKeywordKind(token.ValueText);

return contextualKind is SyntaxKind.FromKeyword or
SyntaxKind.NameOfKeyword or
SyntaxKind.AsyncKeyword or
SyntaxKind.AwaitKeyword or
SyntaxKind.VarKeyword;
}

protected override bool ContainingTypesOrSelfHasUnsafeKeyword(INamedTypeSymbol containingType)
=> containingType.ContainingTypesOrSelfHasUnsafeKeyword();
Expand Down Expand Up @@ -69,7 +89,6 @@ protected override bool TryInitializeIdentifierNameState(
{
identifierToken = identifierName.Identifier;
if (identifierToken.ValueText != string.Empty &&
!identifierName.IsVar &&
!IsProbablyGeneric(identifierName, cancellationToken))
{
var memberAccess = identifierName.Parent as MemberAccessExpressionSyntax;
Expand Down

0 comments on commit b922e70

Please sign in to comment.