提交 2079e408 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #20147 from CyrusNajmabadi/initParameterNoBlock

Improve 'Initialize Parameter' so it works with members that don't have their bodies written yet.
Fixes #19956
......@@ -122,7 +122,7 @@ class C
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)]
public async Task TestNotOnPartialMethod1()
public async Task TestNotOnExternParameter()
{
await TestMissingInRegularAndScriptAsync(
@"
......@@ -130,16 +130,29 @@ public async Task TestNotOnPartialMethod1()
class C
{
private partial void M([||]string s);
extern void M([||]string s);
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)]
public async Task TestNotOnPartialMethodDefinition1()
{
await TestMissingInRegularAndScriptAsync(
@"
using System;
class C
{
partial void M([||]string s);
private void M(string s)
partial void M(string s)
{
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)]
public async Task TestNotOnPartialMethod2()
public async Task TestNotOnPartialMethodDefinition2()
{
await TestMissingInRegularAndScriptAsync(
@"
......@@ -147,11 +160,75 @@ public async Task TestNotOnPartialMethod2()
class C
{
private void M(string s)
partial void M(string s)
{
}
private partial void M([||]string s);
partial void M([||]string s);
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)]
public async Task TestOnPartialMethodImplementation1()
{
await TestInRegularAndScript1Async(
@"
using System;
class C
{
partial void M(string s);
partial void M([||]string s)
{
}
}",
@"
using System;
class C
{
partial void M(string s);
partial void M(string s)
{
if (s == null)
{
throw new ArgumentNullException(nameof(s));
}
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)]
public async Task TestOnPartialMethodImplementation2()
{
await TestInRegularAndScript1Async(
@"
using System;
class C
{
partial void M([||]string s)
{
}
partial void M(string s);
}",
@"
using System;
class C
{
partial void M(string s)
{
if (s == null)
{
throw new ArgumentNullException(nameof(s));
}
}
partial void M(string s);
}");
}
......@@ -784,5 +861,32 @@ public C(string s)
parameters: new TestParameters(options:
Option(CSharpCodeStyleOptions.PreferBraces, CodeStyleOptions.FalseWithNoneEnforcement)));
}
[WorkItem(19956, "https://github.com/dotnet/roslyn/issues/19956")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)]
public async Task TestNoBlock()
{
await TestInRegularAndScript1Async(
@"
using System;
class C
{
public C(string s[||])
}",
@"
using System;
class C
{
public C(string s)
{
if (s == null)
{
throw new ArgumentNullException(nameof(s));
}
}
}", ignoreTrivia: false);
}
}
}
\ No newline at end of file
......@@ -569,5 +569,29 @@ public C(string s, string t)
public string T { get; }
}");
}
[WorkItem(19956, "https://github.com/dotnet/roslyn/issues/19956")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsInitializeParameter)]
public async Task TestNoBlock()
{
await TestInRegularAndScript1Async(
@"
class C
{
private string s;
public C(string s[||])
}",
@"
class C
{
private string s;
public C(string s)
{
this.s = s;
}
}", ignoreTrivia: false);
}
}
}
\ No newline at end of file
......@@ -5,6 +5,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.InitializeParameter;
using Microsoft.CodeAnalysis.Semantics;
namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter
{
......@@ -24,8 +25,8 @@ protected override SyntaxNode GetTypeBlock(SyntaxNode node)
protected override SyntaxNode GetBody(BaseMethodDeclarationSyntax containingMember)
=> InitializeParameterHelpers.GetBody(containingMember);
protected override void InsertStatement(SyntaxEditor editor, SyntaxNode body, SyntaxNode statementToAddAfterOpt, StatementSyntax statement)
=> InitializeParameterHelpers.InsertStatement(editor, body, statementToAddAfterOpt, statement);
protected override void InsertStatement(SyntaxEditor editor, BaseMethodDeclarationSyntax methodDeclarationSyntax, SyntaxNode statementToAddAfterOpt, StatementSyntax statement)
=> InitializeParameterHelpers.InsertStatement(editor, methodDeclarationSyntax, statementToAddAfterOpt, statement);
protected override bool IsImplicitConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination)
=> InitializeParameterHelpers.IsImplicitConversion(compilation, source, destination);
......
......@@ -25,11 +25,11 @@ protected override SyntaxNode GetTypeBlock(SyntaxNode node)
protected override SyntaxNode GetBody(BaseMethodDeclarationSyntax containingMember)
=> InitializeParameterHelpers.GetBody(containingMember);
protected override SyntaxNode GetLastStatement(IBlockStatement blockStatement)
=> InitializeParameterHelpers.GetLastStatement(blockStatement);
protected override SyntaxNode TryGetLastStatement(IBlockStatement blockStatementOpt)
=> InitializeParameterHelpers.TryGetLastStatement(blockStatementOpt);
protected override void InsertStatement(SyntaxEditor editor, SyntaxNode body, SyntaxNode statementToAddAfterOpt, StatementSyntax statement)
=> InitializeParameterHelpers.InsertStatement(editor, body, statementToAddAfterOpt, statement);
protected override void InsertStatement(SyntaxEditor editor, BaseMethodDeclarationSyntax methodDeclaration, SyntaxNode statementToAddAfterOpt, StatementSyntax statement)
=> InitializeParameterHelpers.InsertStatement(editor, methodDeclaration, statementToAddAfterOpt, statement);
protected override bool IsImplicitConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination)
=> InitializeParameterHelpers.IsImplicitConversion(compilation, source, destination);
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Semantics;
......@@ -17,44 +17,45 @@ public static SyntaxNode GetBody(BaseMethodDeclarationSyntax containingMember)
public static bool IsImplicitConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination)
=> compilation.ClassifyConversion(source: source, destination: destination).IsImplicit;
public static SyntaxNode GetLastStatement(IBlockStatement blockStatement)
=> blockStatement.Syntax is BlockSyntax block
public static SyntaxNode TryGetLastStatement(IBlockStatement blockStatementOpt)
=> blockStatementOpt?.Syntax is BlockSyntax block
? block.Statements.LastOrDefault()
: blockStatement.Syntax;
: blockStatementOpt?.Syntax;
public static void InsertStatement(
SyntaxEditor editor,
SyntaxNode body,
BaseMethodDeclarationSyntax methodDeclaration,
SyntaxNode statementToAddAfterOpt,
StatementSyntax statement)
{
var generator = editor.Generator;
if (body is ArrowExpressionClauseSyntax arrowExpression)
if (methodDeclaration.ExpressionBody != null)
{
// If this is a => method, then we'll have to convert the method to have a block
// body. Add the new statement as the first/last statement of the new block
// depending if we were asked to go after something or not.
var methodBase = (BaseMethodDeclarationSyntax)body.Parent;
if (statementToAddAfterOpt == null)
{
editor.SetStatements(methodBase,
editor.SetStatements(
methodDeclaration,
ImmutableArray.Create(
statement,
generator.ExpressionStatement(arrowExpression.Expression)));
generator.ExpressionStatement(methodDeclaration.ExpressionBody.Expression)));
}
else
{
editor.SetStatements(methodBase,
editor.SetStatements(
methodDeclaration,
ImmutableArray.Create(
generator.ExpressionStatement(arrowExpression.Expression),
generator.ExpressionStatement(methodDeclaration.ExpressionBody.Expression),
statement));
}
}
else if (body is BlockSyntax block)
else if (methodDeclaration.Body != null)
{
// Look for the statement we were asked to go after.
var block = methodDeclaration.Body;
var indexToAddAfter = block.Statements.IndexOf(s => s == statementToAddAfterOpt);
if (indexToAddAfter >= 0)
{
......@@ -77,7 +78,10 @@ public static SyntaxNode GetLastStatement(IBlockStatement blockStatement)
}
else
{
throw new InvalidOperationException();
editor.ReplaceNode(
methodDeclaration,
methodDeclaration.WithSemicolonToken(default(SyntaxToken))
.WithBody(SyntaxFactory.Block(statement)));
}
}
}
......
......@@ -33,7 +33,8 @@ internal abstract partial class AbstractAddParameterCheckCodeRefactoringProvider
where TBinaryExpressionSyntax : TExpressionSyntax
{
protected override async Task<ImmutableArray<CodeAction>> GetRefactoringsAsync(
Document document, IParameterSymbol parameter, IBlockStatement blockStatement, CancellationToken cancellationToken)
Document document, IParameterSymbol parameter, TMemberDeclarationSyntax memberDeclaration,
IBlockStatement blockStatementOpt, CancellationToken cancellationToken)
{
// Only should provide null-checks for reference types and nullable types.
if (!parameter.Type.IsReferenceType &&
......@@ -51,18 +52,21 @@ internal abstract partial class AbstractAddParameterCheckCodeRefactoringProvider
// Note: we only check the top level statements of the block. I think that's sufficient
// as this will catch the 90% case, while not being that bad an experience even when
// people do strange things in their constructors.
foreach (var statement in blockStatement.Statements)
if (blockStatementOpt != null)
{
if (IsIfNullCheck(statement, parameter))
foreach (var statement in blockStatementOpt.Statements)
{
return ImmutableArray<CodeAction>.Empty;
}
if (IsIfNullCheck(statement, parameter))
{
return ImmutableArray<CodeAction>.Empty;
}
if (ContainsNullCoalesceCheck(
syntaxFacts, semanticModel, statement,
parameter, cancellationToken))
{
return ImmutableArray<CodeAction>.Empty;
if (ContainsNullCoalesceCheck(
syntaxFacts, semanticModel, statement,
parameter, cancellationToken))
{
return ImmutableArray<CodeAction>.Empty;
}
}
}
......@@ -70,7 +74,7 @@ internal abstract partial class AbstractAddParameterCheckCodeRefactoringProvider
var result = ArrayBuilder<CodeAction>.GetInstance();
result.Add(new MyCodeAction(
FeaturesResources.Add_null_check,
c => AddNullCheckAsync(document, parameter, blockStatement, c)));
c => AddNullCheckAsync(document, parameter, memberDeclaration, blockStatementOpt, c)));
// Also, if this was a string, offer to add the special checks to
// string.IsNullOrEmpty and string.IsNullOrWhitespace.
......@@ -78,11 +82,11 @@ internal abstract partial class AbstractAddParameterCheckCodeRefactoringProvider
{
result.Add(new MyCodeAction(
FeaturesResources.Add_string_IsNullOrEmpty_check,
c => AddStringCheckAsync(document, parameter, blockStatement, nameof(string.IsNullOrEmpty), c)));
c => AddStringCheckAsync(document, parameter, memberDeclaration, blockStatementOpt, nameof(string.IsNullOrEmpty), c)));
result.Add(new MyCodeAction(
FeaturesResources.Add_string_IsNullOrWhiteSpace_check,
c => AddStringCheckAsync(document, parameter, blockStatement, nameof(string.IsNullOrWhiteSpace), c)));
c => AddStringCheckAsync(document, parameter, memberDeclaration, blockStatementOpt, nameof(string.IsNullOrWhiteSpace), c)));
}
return result.ToImmutableAndFree();
......@@ -167,12 +171,13 @@ private bool IsNullLiteral(IOperation operand)
private async Task<Document> AddNullCheckAsync(
Document document,
IParameterSymbol parameter,
IBlockStatement blockStatement,
TMemberDeclarationSyntax memberDeclaration,
IBlockStatement blockStatementOpt,
CancellationToken cancellationToken)
{
// First see if we can convert a statement of the form "this.s = s" into "this.s = s ?? throw ...".
var documentOpt = await TryAddNullCheckToAssignmentAsync(
document, parameter, blockStatement, cancellationToken).ConfigureAwait(false);
document, parameter, blockStatementOpt, cancellationToken).ConfigureAwait(false);
if (documentOpt != null)
{
......@@ -181,7 +186,7 @@ private bool IsNullLiteral(IOperation operand)
// If we can't, then just offer to add an "if (s == null)" statement.
return await AddNullCheckStatementAsync(
document, parameter, blockStatement,
document, parameter, memberDeclaration, blockStatementOpt,
(c, g) => CreateNullCheckStatement(c, g, parameter),
cancellationToken).ConfigureAwait(false);
}
......@@ -189,12 +194,13 @@ private bool IsNullLiteral(IOperation operand)
private async Task<Document> AddStringCheckAsync(
Document document,
IParameterSymbol parameter,
IBlockStatement blockStatement,
TMemberDeclarationSyntax memberDeclaration,
IBlockStatement blockStatementOpt,
string methodName,
CancellationToken cancellationToken)
{
return await AddNullCheckStatementAsync(
document, parameter, blockStatement,
document, parameter, memberDeclaration, blockStatementOpt,
(c, g) => CreateStringCheckStatement(c, g, parameter, methodName),
cancellationToken).ConfigureAwait(false);
}
......@@ -202,7 +208,8 @@ private bool IsNullLiteral(IOperation operand)
private async Task<Document> AddNullCheckStatementAsync(
Document document,
IParameterSymbol parameter,
IBlockStatement blockStatement,
TMemberDeclarationSyntax memberDeclaration,
IBlockStatement blockStatementOpt,
Func<Compilation, SyntaxGenerator, TStatementSyntax> generateNullCheck,
CancellationToken cancellationToken)
{
......@@ -217,8 +224,8 @@ private bool IsNullLiteral(IOperation operand)
// and assignments in the constructor to match the order of parameters in the method
// signature.
var statementToAddAfter = GetStatementToAddNullCheckAfter(
semanticModel, parameter, blockStatement, cancellationToken);
InsertStatement(editor, blockStatement.Syntax, statementToAddAfter, nullCheckStatement);
semanticModel, parameter, blockStatementOpt, cancellationToken);
InsertStatement(editor, memberDeclaration, statementToAddAfter, nullCheckStatement);
var newRoot = editor.GetChangedRoot();
return document.WithSyntaxRoot(newRoot);
......@@ -258,34 +265,39 @@ private bool IsNullLiteral(IOperation operand)
private SyntaxNode GetStatementToAddNullCheckAfter(
SemanticModel semanticModel,
IParameterSymbol parameter,
IBlockStatement blockStatement,
IBlockStatement blockStatementOpt,
CancellationToken cancellationToken)
{
if (blockStatementOpt == null)
{
return null;
}
var methodSymbol = (IMethodSymbol)parameter.ContainingSymbol;
var parameterIndex = methodSymbol.Parameters.IndexOf(parameter);
var parameterIndex = methodSymbol.Parameters.IndexOf(parameter);
// look for an existing check for a parameter that comes before us.
// If we find one, we'll add ourselves after that parameter check.
for (var i = parameterIndex - 1; i >= 0; i--)
// look for an existing check for a parameter that comes before us.
// If we find one, we'll add ourselves after that parameter check.
for (var i = parameterIndex - 1; i >= 0; i--)
{
var checkStatement = TryFindParameterCheckStatement(
semanticModel, methodSymbol.Parameters[i], blockStatementOpt, cancellationToken);
if (checkStatement != null)
{
var checkStatement = TryFindParameterCheckStatement(
semanticModel, methodSymbol.Parameters[i], blockStatement, cancellationToken);
if (checkStatement != null)
{
return checkStatement.Syntax;
}
return checkStatement.Syntax;
}
}
// look for an existing check for a parameter that comes before us.
// If we find one, we'll add ourselves after that parameter check.
for (var i = parameterIndex + 1; i < methodSymbol.Parameters.Length; i++)
{
var checkStatement = TryFindParameterCheckStatement(
semanticModel, methodSymbol.Parameters[i], blockStatement, cancellationToken);
semanticModel, methodSymbol.Parameters[i], blockStatementOpt, cancellationToken);
if (checkStatement != null)
{
var statementIndex = blockStatement.Statements.IndexOf(checkStatement);
return statementIndex > 0 ? blockStatement.Statements[statementIndex - 1].Syntax : null;
var statementIndex = blockStatementOpt.Statements.IndexOf(checkStatement);
return statementIndex > 0 ? blockStatementOpt.Statements[statementIndex - 1].Syntax : null;
}
}
......@@ -301,23 +313,26 @@ private bool IsNullLiteral(IOperation operand)
private IOperation TryFindParameterCheckStatement(
SemanticModel semanticModel,
IParameterSymbol parameterSymbol,
IBlockStatement blockStatement,
IBlockStatement blockStatementOpt,
CancellationToken cancellationToken)
{
foreach (var statement in blockStatement.Statements)
if (blockStatementOpt != null)
{
if (statement is IIfStatement ifStatement)
foreach (var statement in blockStatementOpt.Statements)
{
if (ContainsParameterReference(semanticModel, ifStatement.Condition, parameterSymbol, cancellationToken))
if (statement is IIfStatement ifStatement)
{
return statement;
if (ContainsParameterReference(semanticModel, ifStatement.Condition, parameterSymbol, cancellationToken))
{
return statement;
}
continue;
}
continue;
// Stop hunting after we hit something that isn't an if-statement
break;
}
// Stop hunting after we hit something that isn't an if-statement
break;
}
return null;
......@@ -326,13 +341,18 @@ private bool IsNullLiteral(IOperation operand)
private async Task<Document> TryAddNullCheckToAssignmentAsync(
Document document,
IParameterSymbol parameter,
IBlockStatement blockStatement,
IBlockStatement blockStatementOpt,
CancellationToken cancellationToken)
{
// tries to convert "this.s = s" into "this.s = s ?? throw ...". Only supported
// in languages that have a throw-expression, and only if the user has set the
// preference that they like throw-expressions.
if (blockStatementOpt == null)
{
return null;
}
var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
if (!syntaxFacts.SupportsThrowExpression(syntaxTree.Options))
......@@ -349,7 +369,7 @@ private bool IsNullLiteral(IOperation operand)
// Look through all the top level statements in the block to see if we can
// find an existing field/property assignment involving this parameter.
var containingType = parameter.ContainingType;
foreach (var statement in blockStatement.Statements)
foreach (var statement in blockStatementOpt.Statements)
{
if (IsFieldOrPropertyAssignment(statement, containingType, out var assignmentExpression) &&
IsParameterReference(assignmentExpression.Value, parameter))
......
......@@ -57,10 +57,11 @@ internal abstract partial class AbstractInitializeMemberFromParameterCodeRefacto
new NamingStyles.NamingStyle(Guid.NewGuid(), prefix: "_", capitalizationScheme: Capitalization.CamelCase),
enforcementLevel: DiagnosticSeverity.Hidden));
protected abstract SyntaxNode GetLastStatement(IBlockStatement blockStatement);
protected abstract SyntaxNode TryGetLastStatement(IBlockStatement blockStatementOpt);
protected override async Task<ImmutableArray<CodeAction>> GetRefactoringsAsync(
Document document, IParameterSymbol parameter, IBlockStatement blockStatement, CancellationToken cancellationToken)
Document document, IParameterSymbol parameter, TMemberDeclarationSyntax memberDeclaration,
IBlockStatement blockStatementOpt, CancellationToken cancellationToken)
{
// Only supported for constructor parameters.
var methodSymbol = parameter.ContainingSymbol as IMethodSymbol;
......@@ -70,7 +71,7 @@ internal abstract partial class AbstractInitializeMemberFromParameterCodeRefacto
}
var assignmentStatement = TryFindFieldOrPropertyAssignmentStatement(
parameter, blockStatement);
parameter, blockStatementOpt);
if (assignmentStatement != null)
{
// We're already assigning this parameter to a field/property in this type.
......@@ -83,7 +84,7 @@ internal abstract partial class AbstractInitializeMemberFromParameterCodeRefacto
// if we can't.
var fieldOrProperty = await TryFindMatchingUninitializedFieldOrPropertySymbolAsync(
document, parameter, blockStatement, cancellationToken).ConfigureAwait(false);
document, parameter, blockStatementOpt, cancellationToken).ConfigureAwait(false);
if (fieldOrProperty != null)
{
......@@ -98,7 +99,8 @@ internal abstract partial class AbstractInitializeMemberFromParameterCodeRefacto
return ImmutableArray.Create<CodeAction>(new MyCodeAction(
title,
c => AddSymbolInitializationAsync(document, parameter, blockStatement, fieldOrProperty, c)));
c => AddSymbolInitializationAsync(
document, parameter, memberDeclaration, blockStatementOpt, fieldOrProperty, c)));
}
else
{
......@@ -119,9 +121,9 @@ internal abstract partial class AbstractInitializeMemberFromParameterCodeRefacto
// we could consider swapping this if people prefer creating private fields more.
return ImmutableArray.Create<CodeAction>(
new MyCodeAction(string.Format(FeaturesResources.Create_and_initialize_property_0, property.Name),
c => AddSymbolInitializationAsync(document, parameter, blockStatement, property, c)),
c => AddSymbolInitializationAsync(document, parameter, memberDeclaration, blockStatementOpt, property, c)),
new MyCodeAction(string.Format(FeaturesResources.Create_and_initialize_field_0, field.Name),
c => AddSymbolInitializationAsync(document, parameter, blockStatement, field, c)));
c => AddSymbolInitializationAsync(document, parameter, memberDeclaration, blockStatementOpt, field, c)));
}
}
......@@ -194,8 +196,8 @@ private static string GenerateUniqueName(IParameterSymbol parameter, List<string
}
private async Task<Document> AddSymbolInitializationAsync(
Document document, IParameterSymbol parameter, IBlockStatement blockStatement,
ISymbol fieldOrProperty, CancellationToken cancellationToken)
Document document, IParameterSymbol parameter, TMemberDeclarationSyntax memberDeclaration,
IBlockStatement blockStatementOpt, ISymbol fieldOrProperty, CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
......@@ -211,11 +213,10 @@ private static string GenerateUniqueName(IParameterSymbol parameter, List<string
// First, look for the right containing type (As a type may be partial).
// We want the type-block that this constructor is contained within.
var blockSyntax = blockStatement.Syntax;
var typeDeclaration =
parameter.ContainingType.DeclaringSyntaxReferences
.Select(r => GetTypeBlock(r.GetSyntax(cancellationToken)))
.Single(d => blockSyntax.Ancestors().Contains(d));
.Single(d => memberDeclaration.Ancestors().Contains(d));
// Now add the field/property to this type. Use the 'ReplaceNode+callback' form
// so that nodes will be appropriate tracked and so we can then update the constructor
......@@ -233,13 +234,13 @@ private static string GenerateUniqueName(IParameterSymbol parameter, List<string
{
return CodeGenerator.AddPropertyDeclaration(
currentTypeDecl, property, workspace,
GetAddOptions<IPropertySymbol>(parameter, blockStatement, typeDeclaration, cancellationToken));
GetAddOptions<IPropertySymbol>(parameter, blockStatementOpt, typeDeclaration, cancellationToken));
}
else if (fieldOrProperty is IFieldSymbol field)
{
return CodeGenerator.AddFieldDeclaration(
currentTypeDecl, field, workspace,
GetAddOptions<IFieldSymbol>(parameter, blockStatement, typeDeclaration, cancellationToken));
GetAddOptions<IFieldSymbol>(parameter, blockStatementOpt, typeDeclaration, cancellationToken));
}
else
{
......@@ -261,15 +262,15 @@ private static string GenerateUniqueName(IParameterSymbol parameter, List<string
// We'll want to keep initialization statements in the same order as we see
// parameters for the constructor.
var statementToAddAfterOpt = TryGetStatementToAddInitializationAfter(
semanticModel, parameter, blockStatement, cancellationToken);
semanticModel, parameter, blockStatementOpt, cancellationToken);
InsertStatement(editor, blockStatement.Syntax, statementToAddAfterOpt, initializationStatement);
InsertStatement(editor, memberDeclaration, statementToAddAfterOpt, initializationStatement);
return document.WithSyntaxRoot(editor.GetChangedRoot());
}
private CodeGenerationOptions GetAddOptions<TSymbol>(
IParameterSymbol parameter, IBlockStatement blockStatement,
IParameterSymbol parameter, IBlockStatement blockStatementOpt,
SyntaxNode typeDeclaration, CancellationToken cancellationToken)
where TSymbol : ISymbol
{
......@@ -279,7 +280,7 @@ private static string GenerateUniqueName(IParameterSymbol parameter, List<string
for (var i = parameterIndex - 1; i >= 0; i--)
{
var statement = TryFindFieldOrPropertyAssignmentStatement(
methodSymbol.Parameters[i], blockStatement, out var fieldOrProperty);
methodSymbol.Parameters[i], blockStatementOpt, out var fieldOrProperty);
if (statement != null &&
fieldOrProperty is TSymbol symbol)
......@@ -297,7 +298,7 @@ private static string GenerateUniqueName(IParameterSymbol parameter, List<string
for (var i = parameterIndex + 1; i < methodSymbol.Parameters.Length; i++)
{
var statement = TryFindFieldOrPropertyAssignmentStatement(
methodSymbol.Parameters[i], blockStatement, out var fieldOrProperty);
methodSymbol.Parameters[i], blockStatementOpt, out var fieldOrProperty);
if (statement != null &&
fieldOrProperty is TSymbol symbol)
......@@ -318,7 +319,7 @@ private static string GenerateUniqueName(IParameterSymbol parameter, List<string
private SyntaxNode TryGetStatementToAddInitializationAfter(
SemanticModel semanticModel,
IParameterSymbol parameter,
IBlockStatement blockStatement,
IBlockStatement blockStatementOpt,
CancellationToken cancellationToken)
{
var methodSymbol = (IMethodSymbol)parameter.ContainingSymbol;
......@@ -329,7 +330,7 @@ private static string GenerateUniqueName(IParameterSymbol parameter, List<string
for (var i = parameterIndex - 1; i >= 0; i--)
{
var statement = TryFindFieldOrPropertyAssignmentStatement(
methodSymbol.Parameters[i], blockStatement);
methodSymbol.Parameters[i], blockStatementOpt);
if (statement != null)
{
return statement.Syntax;
......@@ -341,33 +342,36 @@ private static string GenerateUniqueName(IParameterSymbol parameter, List<string
for (var i = parameterIndex + 1; i < methodSymbol.Parameters.Length; i++)
{
var statement = TryFindFieldOrPropertyAssignmentStatement(
methodSymbol.Parameters[i], blockStatement);
methodSymbol.Parameters[i], blockStatementOpt);
if (statement != null)
{
var statementIndex = blockStatement.Statements.IndexOf(statement);
return statementIndex > 0 ? blockStatement.Statements[statementIndex - 1].Syntax : null;
var statementIndex = blockStatementOpt.Statements.IndexOf(statement);
return statementIndex > 0 ? blockStatementOpt.Statements[statementIndex - 1].Syntax : null;
}
}
// We couldn't find a reasonable location for the new initialization statement.
// Just place ourselves after the last statement in the constructor.
return GetLastStatement(blockStatement);
return TryGetLastStatement(blockStatementOpt);
}
private IOperation TryFindFieldOrPropertyAssignmentStatement(IParameterSymbol parameter, IBlockStatement blockStatement)
=> TryFindFieldOrPropertyAssignmentStatement(parameter, blockStatement, out var fieldOrProperty);
private IOperation TryFindFieldOrPropertyAssignmentStatement(IParameterSymbol parameter, IBlockStatement blockStatementOpt)
=> TryFindFieldOrPropertyAssignmentStatement(parameter, blockStatementOpt, out var fieldOrProperty);
private IOperation TryFindFieldOrPropertyAssignmentStatement(
IParameterSymbol parameter, IBlockStatement blockStatement, out ISymbol fieldOrProperty)
IParameterSymbol parameter, IBlockStatement blockStatementOpt, out ISymbol fieldOrProperty)
{
var containingType = parameter.ContainingType;
foreach (var statement in blockStatement.Statements)
if (blockStatementOpt != null)
{
// look for something of the form: "this.s = s" or "this.s = s ?? ..."
if (IsFieldOrPropertyAssignment(statement, containingType, out var assignmentExpression, out fieldOrProperty) &&
IsParameterReferenceOrCoalesceOfParameterReference(assignmentExpression, parameter))
var containingType = parameter.ContainingType;
foreach (var statement in blockStatementOpt.Statements)
{
return statement;
// look for something of the form: "this.s = s" or "this.s = s ?? ..."
if (IsFieldOrPropertyAssignment(statement, containingType, out var assignmentExpression, out fieldOrProperty) &&
IsParameterReferenceOrCoalesceOfParameterReference(assignmentExpression, parameter))
{
return statement;
}
}
}
......@@ -397,7 +401,7 @@ private IOperation TryFindFieldOrPropertyAssignmentStatement(IParameterSymbol pa
}
private async Task<ISymbol> TryFindMatchingUninitializedFieldOrPropertySymbolAsync(
Document document, IParameterSymbol parameter, IBlockStatement blockStatement, CancellationToken cancellationToken)
Document document, IParameterSymbol parameter, IBlockStatement blockStatementOpt, CancellationToken cancellationToken)
{
// Look for a field/property that really looks like it corresponds to this parameter.
// Use a variety of heuristics around the name/type to see if this is a match.
......@@ -426,7 +430,7 @@ private IOperation TryFindFieldOrPropertyAssignmentStatement(IParameterSymbol pa
if (memberWithName is IFieldSymbol field &&
!field.IsConst &&
IsImplicitConversion(compilation, source: parameter.Type, destination: field.Type) &&
!ContainsMemberAssignment(blockStatement, field))
!ContainsMemberAssignment(blockStatementOpt, field))
{
return field;
}
......@@ -438,7 +442,7 @@ private IOperation TryFindFieldOrPropertyAssignmentStatement(IParameterSymbol pa
if (memberWithName is IPropertySymbol property &&
property.IsWritableInConstructor() &&
IsImplicitConversion(compilation, source: parameter.Type, destination: property.Type) &&
!ContainsMemberAssignment(blockStatement, property))
!ContainsMemberAssignment(blockStatementOpt, property))
{
return property;
}
......@@ -451,15 +455,18 @@ private IOperation TryFindFieldOrPropertyAssignmentStatement(IParameterSymbol pa
}
private bool ContainsMemberAssignment(
IBlockStatement blockStatement, ISymbol member)
IBlockStatement blockStatementOpt, ISymbol member)
{
foreach (var statement in blockStatement.Statements)
if (blockStatementOpt != null)
{
if (IsFieldOrPropertyAssignment(statement, member.ContainingType, out var assignmentExpression) &&
UnwrapImplicitConversion(assignmentExpression.Target) is IMemberReferenceExpression memberReference &&
member.Equals(memberReference.Member))
foreach (var statement in blockStatementOpt.Statements)
{
return true;
if (IsFieldOrPropertyAssignment(statement, member.ContainingType, out var assignmentExpression) &&
UnwrapImplicitConversion(assignmentExpression.Target) is IMemberReferenceExpression memberReference &&
member.Equals(memberReference.Member))
{
return true;
}
}
}
......
......@@ -35,10 +35,12 @@ internal abstract partial class AbstractInitializeParameterCodeRefactoringProvid
protected abstract SyntaxNode GetTypeBlock(SyntaxNode node);
protected abstract void InsertStatement(
SyntaxEditor editor, SyntaxNode body,
SyntaxEditor editor, TMemberDeclarationSyntax memberDeclaration,
SyntaxNode statementToAddAfterOpt, TStatementSyntax statement);
protected abstract Task<ImmutableArray<CodeAction>> GetRefactoringsAsync(Document document, IParameterSymbol parameter, IBlockStatement blockStatement, CancellationToken cancellationToken);
protected abstract Task<ImmutableArray<CodeAction>> GetRefactoringsAsync(
Document document, IParameterSymbol parameter, TMemberDeclarationSyntax containingMember,
IBlockStatement blockStatementOpt, CancellationToken cancellationToken);
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
......@@ -79,8 +81,15 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var method = (IMethodSymbol)semanticModel.GetDeclaredSymbol(containingMember, cancellationToken);
var parameter = (IParameterSymbol)semanticModel.GetDeclaredSymbol(parameterNode, cancellationToken);
if (method.IsAbstract ||
method.IsExtern ||
method.PartialImplementationPart != null ||
method.ContainingType.TypeKind == TypeKind.Interface)
{
return;
}
var parameter = (IParameterSymbol)semanticModel.GetDeclaredSymbol(parameterNode, cancellationToken);
if (!method.Parameters.Contains(parameter))
{
return;
......@@ -92,22 +101,25 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
}
// Only offered on method-like things that have a body (i.e. non-interface/non-abstract).
var body = GetBody(containingMember);
if (body == null)
{
return;
}
var bodyOpt = GetBody(containingMember);
var memberOperation = GetOperation(semanticModel, body, cancellationToken);
if (!(memberOperation is IBlockStatement blockStatement))
// We support initializing parameters, even when the containing member doesn't have a
// body. This is useful for when the user is typing a new constructor and hasn't written
// the body yet.
var blockStatementOpt = default(IBlockStatement);
if (bodyOpt != null)
{
return;
blockStatementOpt = GetOperation(semanticModel, bodyOpt, cancellationToken) as IBlockStatement;
if (blockStatementOpt == null)
{
return;
}
}
// Ok. Looks like a reasonable parameter to analyze. Defer to subclass to
// actually determine if there are any viable refactorings here.
context.RegisterRefactorings(await GetRefactoringsAsync(
document, parameter, blockStatement, cancellationToken).ConfigureAwait(false));
document, parameter, containingMember, blockStatementOpt, cancellationToken).ConfigureAwait(false));
}
private TParameterSyntax GetParameterNode(SyntaxToken token, int position)
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.Semantics
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.InitializeParameter
......@@ -15,17 +16,17 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InitializeParameter
Public Shared Sub InsertStatement(
editor As SyntaxEditor,
body As SyntaxNode,
methodBlock As MethodBlockBaseSyntax,
statementToAddAfterOpt As SyntaxNode,
statement As StatementSyntax)
Dim methodBlock = DirectCast(body, MethodBlockBaseSyntax)
Dim statements = methodBlock.Statements
If statementToAddAfterOpt IsNot Nothing Then
editor.InsertAfter(statementToAddAfterOpt, statement)
Else
Dim newStatements = statements.Insert(0, statement)
editor.SetStatements(body, newStatements)
editor.SetStatements(methodBlock, newStatements)
End If
End Sub
End Class
......
......@@ -4,6 +4,7 @@ Imports System.Composition
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.InitializeParameter
Imports Microsoft.CodeAnalysis.Semantics
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.InitializeParameter
......@@ -29,8 +30,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InitializeParameter
Return InitializeParameterHelpers.IsImplicitConversion(compilation, source, destination)
End Function
Protected Overrides Sub InsertStatement(editor As SyntaxEditor, body As SyntaxNode, statementToAddAfterOpt As SyntaxNode, statement As StatementSyntax)
InitializeParameterHelpers.InsertStatement(editor, body, statementToAddAfterOpt, statement)
Protected Overrides Sub InsertStatement(editor As SyntaxEditor, methodDeclaration As MethodBlockBaseSyntax, statementToAddAfterOpt As SyntaxNode, statement As StatementSyntax)
InitializeParameterHelpers.InsertStatement(editor, methodDeclaration, statementToAddAfterOpt, statement)
End Sub
End Class
End Namespace
\ No newline at end of file
......@@ -22,7 +22,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InitializeParameter
Return DirectCast(node, TypeStatementSyntax).Parent
End Function
Protected Overrides Function GetLastStatement(blockStatement As IBlockStatement) As SyntaxNode
Protected Overrides Function TryGetLastStatement(blockStatement As IBlockStatement) As SyntaxNode
Return DirectCast(blockStatement.Syntax, MethodBlockBaseSyntax).Statements.LastOrDefault()
End Function
......@@ -34,8 +34,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InitializeParameter
Return InitializeParameterHelpers.IsImplicitConversion(compilation, source, destination)
End Function
Protected Overrides Sub InsertStatement(editor As SyntaxEditor, body As SyntaxNode, statementToAddAfterOpt As SyntaxNode, statement As StatementSyntax)
InitializeParameterHelpers.InsertStatement(editor, body, statementToAddAfterOpt, statement)
Protected Overrides Sub InsertStatement(editor As SyntaxEditor, methodDeclaration As MethodBlockBaseSyntax, statementToAddAfterOpt As SyntaxNode, statement As StatementSyntax)
InitializeParameterHelpers.InsertStatement(editor, methodDeclaration, statementToAddAfterOpt, statement)
End Sub
End Class
End Namespace
\ No newline at end of file
......@@ -96,6 +96,7 @@
<Compile Include="CodeStyle\TypeStyle\TypeStyle.cs" />
<Compile Include="CodeStyle\TypeStyle\TypeStyleHelper.cs" />
<Compile Include="Composition\CSharpWorkspaceFeatures.cs" />
<Compile Include="Extensions\BaseMethodDeclarationSyntaxExtensions.cs" />
<Compile Include="Simplification\Reducers\CSharpDefaultExpressionReducer.Rewriter.cs" />
<Compile Include="Diagnostics\CSharpDiagnosticPropertiesService.cs" />
<Compile Include="Execution\CSharpOptionsSerializationService.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.CodeAnalysis.CSharp.Extensions
{
internal static class BaseMethodDeclarationSyntaxExtensions
{
public static BaseMethodDeclarationSyntax WithSemicolonToken(this BaseMethodDeclarationSyntax node, SyntaxToken token)
{
if (node != null)
{
switch (node.Kind())
{
case SyntaxKind.ConstructorDeclaration: return ((ConstructorDeclarationSyntax)node).WithSemicolonToken(token);
case SyntaxKind.DestructorDeclaration: return ((DestructorDeclarationSyntax)node).WithSemicolonToken(token);
case SyntaxKind.MethodDeclaration: return ((MethodDeclarationSyntax)node).WithSemicolonToken(token);
case SyntaxKind.OperatorDeclaration: return ((OperatorDeclarationSyntax)node).WithSemicolonToken(token);
case SyntaxKind.ConversionOperatorDeclaration: return ((ConversionOperatorDeclarationSyntax)node).WithSemicolonToken(token);
}
}
return node;
}
public static BaseMethodDeclarationSyntax WithBody(this BaseMethodDeclarationSyntax node, BlockSyntax body)
{
if (node != null)
{
switch (node.Kind())
{
case SyntaxKind.ConstructorDeclaration: return ((ConstructorDeclarationSyntax)node).WithBody(body);
case SyntaxKind.DestructorDeclaration: return ((DestructorDeclarationSyntax)node).WithBody(body);
case SyntaxKind.MethodDeclaration: return ((MethodDeclarationSyntax)node).WithBody(body);
case SyntaxKind.OperatorDeclaration: return ((OperatorDeclarationSyntax)node).WithBody(body);
case SyntaxKind.ConversionOperatorDeclaration: return ((ConversionOperatorDeclarationSyntax)node).WithBody(body);
}
}
return node;
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册