提交 9b60985b 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #17975 from CyrusNajmabadi/pickMembersOptions

Add "Add null checks" option when a user tries to generate a constructor from members.
// 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.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings;
using Microsoft.CodeAnalysis.GenerateConstructorFromMembers;
using Microsoft.CodeAnalysis.PickMembers;
......@@ -729,6 +727,79 @@ public Z(string b, int a)
chosenSymbols: new string[] { "b", "a" });
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructorFromMembers)]
public async Task TestAddNullChecks1()
{
await TestWithPickMembersDialogAsync(
@"
using System;
using System.Collections.Generic;
class Z
{
int a;
string b;
[||]
}",
@"
using System;
using System.Collections.Generic;
class Z
{
int a;
string b;
public Z(int a, string b)
{
this.a = a;
this.b = b ?? throw new ArgumentNullException(nameof(b));
}
}",
chosenSymbols: new string[] { "a", "b" },
optionsCallback: options => options[0].Value = true);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructorFromMembers)]
public async Task TestAddNullChecks2()
{
await TestWithPickMembersDialogAsync(
@"
using System;
using System.Collections.Generic;
class Z
{
int a;
string b;
[||]
}",
@"
using System;
using System.Collections.Generic;
class Z
{
int a;
string b;
public Z(int a, string b)
{
if (b == null)
{
throw new ArgumentNullException(nameof(b));
}
this.a = a;
this.b = b;
}
}",
chosenSymbols: new string[] { "a", "b" },
optionsCallback: options => options[0].Value = true,
parameters: new TestParameters(options:
Option(CodeStyleOptions.PreferThrowExpression, CodeStyleOptions.FalseWithNoneEnforcement)));
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateConstructorFromMembers)]
public async Task TestMissingOnMember1()
{
......
......@@ -113,33 +113,45 @@ public abstract class AbstractCodeActionTest : AbstractCodeActionOrUserDiagnosti
}
protected static Document GetDocument(TestWorkspace workspace)
{
return workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id);
}
=> workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id);
private class TestPickMembersService : IPickMembersService
{
private readonly ImmutableArray<string> _memberNames;
private readonly Action<ImmutableArray<PickMembersOption>> _optionsCallback;
public TestPickMembersService(ImmutableArray<string> memberNames)
=> _memberNames = memberNames;
public TestPickMembersService(
ImmutableArray<string> memberNames,
Action<ImmutableArray<PickMembersOption>> optionsCallback)
{
_memberNames = memberNames;
_optionsCallback = optionsCallback;
}
public PickMembersResult PickMembers(string title, ImmutableArray<ISymbol> members)
=> new PickMembersResult(_memberNames.IsDefault
? members
: _memberNames.SelectAsArray(n => members.Single(m => m.Name == n)));
public PickMembersResult PickMembers(
string title, ImmutableArray<ISymbol> members,
ImmutableArray<PickMembersOption> options)
{
_optionsCallback?.Invoke(options);
return new PickMembersResult(
_memberNames.IsDefault
? members
: _memberNames.SelectAsArray(n => members.Single(m => m.Name == n)),
options);
}
}
internal Task TestWithPickMembersDialogAsync(
string initialMarkup,
string expectedMarkup,
string[] chosenSymbols,
Action<ImmutableArray<PickMembersOption>> optionsCallback = null,
int index = 0,
bool ignoreTrivia = true,
CodeActionPriority? priority = null,
TestParameters parameters = default(TestParameters))
{
var pickMembersService = new TestPickMembersService(chosenSymbols.AsImmutableOrNull());
var pickMembersService = new TestPickMembersService(chosenSymbols.AsImmutableOrNull(), optionsCallback);
return TestInRegularAndScript1Async(
initialMarkup, expectedMarkup,
index, ignoreTrivia, priority,
......
......@@ -134,6 +134,7 @@
<Compile Include="CodeFixes\FixAllOccurrences\FixSomeCodeAction.cs" />
<Compile Include="AddPackage\InstallPackageDirectlyCodeAction.cs" />
<Compile Include="AddConstructorParametersFromMembers\State.cs" />
<Compile Include="GenerateConstructorFromMembers\GenerateConstructorFromMembersOptions.cs" />
<Compile Include="GenerateConstructorFromMembers\GenerateConstructorWithDialogCodeAction.cs" />
<Compile Include="GenerateEqualsAndGetHashCodeFromMembers\FormatLargeBinaryExpressionRule.cs" />
<Compile Include="GenerateEqualsAndGetHashCodeFromMembers\GenerateEqualsAndHashWithDialogCodeAction.cs" />
......@@ -730,4 +731,4 @@
<ItemGroup />
<Import Project="..\..\..\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems" Label="Shared" />
<Import Project="..\..\..\..\build\Targets\Imports.targets" />
</Project>
</Project>
\ No newline at end of file
......@@ -161,6 +161,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Add null checks.
/// </summary>
internal static string Add_null_checks {
get {
return ResourceManager.GetString("Add_null_checks", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add optional parameters to &apos;{0}&apos;.
/// </summary>
......
......@@ -1211,4 +1211,7 @@ This version used in: {2}</value>
<data name="Pick_members_to_override" xml:space="preserve">
<value>Pick members to override</value>
</data>
<data name="Add_null_checks" xml:space="preserve">
<value>Add null checks</value>
</data>
</root>
\ No newline at end of file
......@@ -7,9 +7,11 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.GenerateConstructorFromMembers
{
......@@ -20,15 +22,18 @@ private class ConstructorDelegatingCodeAction : CodeAction
private readonly GenerateConstructorFromMembersCodeRefactoringProvider _service;
private readonly Document _document;
private readonly State _state;
private readonly bool _addNullChecks;
public ConstructorDelegatingCodeAction(
GenerateConstructorFromMembersCodeRefactoringProvider service,
Document document,
State state)
State state,
bool addNullChecks)
{
_service = service;
_document = document;
_state = state;
_addNullChecks = addNullChecks;
}
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
......@@ -39,26 +44,35 @@ protected override async Task<Document> GetChangedDocumentAsync(CancellationToke
//
// Otherwise, just generate a normal constructor that assigns any provided
// parameters into fields.
var provider = _document.Project.Solution.Workspace.Services.GetLanguageServices(_state.ContainingType.Language);
var factory = provider.GetService<SyntaxGenerator>();
var codeGenerationService = provider.GetService<ICodeGenerationService>();
var project = _document.Project;
var languageServices = project.Solution.Workspace.Services.GetLanguageServices(_state.ContainingType.Language);
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var factory = languageServices.GetService<SyntaxGenerator>();
var codeGenerationService = languageServices.GetService<ICodeGenerationService>();
var thisConstructorArguments = factory.CreateArguments(
_state.Parameters.Take(_state.DelegatedConstructor.Parameters.Length).ToImmutableArray());
var statements = ArrayBuilder<SyntaxNode>.GetInstance();
var nullCheckStatements = ArrayBuilder<SyntaxNode>.GetInstance();
var assignStatements = ArrayBuilder<SyntaxNode>.GetInstance();
var options = await _document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var useThrowExpressions = options.GetOption(CodeStyleOptions.PreferThrowExpression).Value;
for (var i = _state.DelegatedConstructor.Parameters.Length; i < _state.Parameters.Length; i++)
{
var symbolName = _state.SelectedMembers[i].Name;
var parameterName = _state.Parameters[i].Name;
var assignExpression = factory.AssignmentStatement(
factory.MemberAccessExpression(
factory.ThisExpression(),
factory.IdentifierName(symbolName)),
factory.IdentifierName(parameterName));
var expressionStatement = factory.ExpressionStatement(assignExpression);
statements.Add(expressionStatement);
var parameter = _state.Parameters[i];
var fieldAccess = factory.MemberAccessExpression(
factory.ThisExpression(),
factory.IdentifierName(symbolName));
factory.AddAssignmentStatements(
compilation, parameter, fieldAccess,
_addNullChecks, useThrowExpressions,
nullCheckStatements, assignStatements);
}
var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
......@@ -71,6 +85,7 @@ protected override async Task<Document> GetChangedDocumentAsync(CancellationToke
? syntaxTree.GetLocation(_state.TextSpan)
: null;
var statements = nullCheckStatements.ToImmutableAndFree().Concat(assignStatements.ToImmutableAndFree());
var result = await codeGenerationService.AddMethodAsync(
_document.Project.Solution,
_state.ContainingType,
......@@ -80,7 +95,7 @@ protected override async Task<Document> GetChangedDocumentAsync(CancellationToke
modifiers: new DeclarationModifiers(),
typeName: _state.ContainingType.Name,
parameters: _state.Parameters,
statements: statements.ToImmutableAndFree(),
statements: statements,
thisConstructorArguments: thisConstructorArguments),
new CodeGenerationOptions(
contextLocation: syntaxTree.GetLocation(_state.TextSpan),
......
......@@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
......@@ -19,15 +20,18 @@ private class FieldDelegatingCodeAction : CodeAction
private readonly GenerateConstructorFromMembersCodeRefactoringProvider _service;
private readonly Document _document;
private readonly State _state;
private readonly bool _addNullChecks;
public FieldDelegatingCodeAction(
GenerateConstructorFromMembersCodeRefactoringProvider service,
Document document,
State state)
State state,
bool addNullChecks)
{
_service = service;
_document = document;
_state = state;
_addNullChecks = addNullChecks;
}
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
......@@ -47,12 +51,19 @@ protected override async Task<Document> GetChangedDocumentAsync(CancellationToke
var factory = _document.GetLanguageService<SyntaxGenerator>();
var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var options = await _document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var preferThrowExpression = options.GetOption(CodeStyleOptions.PreferThrowExpression).Value;
var compilation = await _document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var members = factory.CreateFieldDelegatingConstructor(
compilation,
_state.ContainingType.Name,
_state.ContainingType,
_state.Parameters,
parameterToExistingFieldMap,
parameterToNewFieldMap: null,
addNullChecks: _addNullChecks,
preferThrowExpression: preferThrowExpression,
cancellationToken: cancellationToken);
// If the user has selected a set of members (i.e. TextSpan is not empty), then we will
......
......@@ -21,6 +21,8 @@ namespace Microsoft.CodeAnalysis.GenerateConstructorFromMembers
[ExtensionOrder(Before = PredefinedCodeRefactoringProviderNames.GenerateEqualsAndGetHashCodeFromMembers)]
internal partial class GenerateConstructorFromMembersCodeRefactoringProvider : AbstractGenerateFromMembersCodeRefactoringProvider
{
private const string AddNullChecksId = nameof(AddNullChecksId);
private readonly IPickMembersService _pickMembersService_forTesting;
public GenerateConstructorFromMembersCodeRefactoringProvider() : this(null)
......@@ -46,7 +48,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
return;
}
var actions = await this.GenerateConstructorFromMembersAsync(document, textSpan, cancellationToken).ConfigureAwait(false);
var actions = await this.GenerateConstructorFromMembersAsync(
document, textSpan, addNullChecks: false, cancellationToken: cancellationToken).ConfigureAwait(false);
context.RegisterRefactorings(actions);
if (actions.IsDefaultOrEmpty && textSpan.IsEmpty)
......@@ -104,7 +107,8 @@ private async Task HandleNonSelectionAsync(CodeRefactoringContext context)
if (state != null)
{
context.RegisterRefactoring(new FieldDelegatingCodeAction(this, document, state));
context.RegisterRefactoring(
new FieldDelegatingCodeAction(this, document, state, addNullChecks: false));
}
}
......@@ -112,13 +116,29 @@ private async Task HandleNonSelectionAsync(CodeRefactoringContext context)
return;
}
var pickMemberOptions = ArrayBuilder<PickMembersOption>.GetInstance();
var canAddNullCheck = viableMembers.Any(
m => m.GetSymbolType().CanAddNullCheck());
if (canAddNullCheck)
{
var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var optionValue = options.GetOption(GenerateConstructorFromMembersOptions.AddNullChecks);
pickMemberOptions.Add(new PickMembersOption(
AddNullChecksId,
FeaturesResources.Add_null_checks,
optionValue));
}
context.RegisterRefactoring(
new GenerateConstructorWithDialogCodeAction(
this, document, textSpan, containingType, viableMembers));
this, document, textSpan, containingType, viableMembers,
pickMemberOptions.ToImmutableAndFree()));
}
public async Task<ImmutableArray<CodeAction>> GenerateConstructorFromMembersAsync(
Document document, TextSpan textSpan, CancellationToken cancellationToken)
Document document, TextSpan textSpan, bool addNullChecks, CancellationToken cancellationToken)
{
using (Logger.LogBlock(FunctionId.Refactoring_GenerateFromMembers_GenerateConstructorFromMembers, cancellationToken))
{
......@@ -128,7 +148,7 @@ private async Task HandleNonSelectionAsync(CodeRefactoringContext context)
var state = State.TryGenerate(this, document, textSpan, info.ContainingType, info.SelectedMembers, cancellationToken);
if (state != null && state.MatchingConstructor == null)
{
return GetCodeActions(document, state);
return GetCodeActions(document, state, addNullChecks);
}
}
......@@ -136,14 +156,14 @@ private async Task HandleNonSelectionAsync(CodeRefactoringContext context)
}
}
private ImmutableArray<CodeAction> GetCodeActions(Document document, State state)
private ImmutableArray<CodeAction> GetCodeActions(Document document, State state, bool addNullChecks)
{
var result = ArrayBuilder<CodeAction>.GetInstance();
result.Add(new FieldDelegatingCodeAction(this, document, state));
result.Add(new FieldDelegatingCodeAction(this, document, state, addNullChecks));
if (state.DelegatedConstructor != null)
{
result.Add(new ConstructorDelegatingCodeAction(this, document, state));
result.Add(new ConstructorDelegatingCodeAction(this, document, state, addNullChecks));
}
return result.ToImmutableAndFree();
......
// 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.Options;
namespace Microsoft.CodeAnalysis.GenerateConstructorFromMembers
{
internal static class GenerateConstructorFromMembersOptions
{
public static readonly PerLanguageOption<bool> AddNullChecks = new PerLanguageOption<bool>(
nameof(GenerateConstructorFromMembersOptions),
nameof(AddNullChecks), defaultValue: false,
storageLocations: new RoamingProfileStorageLocation(
$"TextEditor.%LANGUAGE%.Specific.{nameof(GenerateConstructorFromMembersOptions)}.{nameof(AddNullChecks)}"));
}
}
\ No newline at end of file
// 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.Generic;
using System.Collections.Immutable;
using System.Linq;
......@@ -22,6 +21,7 @@ private class GenerateConstructorWithDialogCodeAction : CodeActionWithOptions
private readonly GenerateConstructorFromMembersCodeRefactoringProvider _service;
private readonly TextSpan _textSpan;
private readonly ImmutableArray<ISymbol> _viableMembers;
private readonly ImmutableArray<PickMembersOption> _pickMembersOptions;
public override string Title => FeaturesResources.Generate_constructor;
......@@ -29,21 +29,25 @@ private class GenerateConstructorWithDialogCodeAction : CodeActionWithOptions
GenerateConstructorFromMembersCodeRefactoringProvider service,
Document document, TextSpan textSpan,
INamedTypeSymbol containingType,
ImmutableArray<ISymbol> viableMembers)
ImmutableArray<ISymbol> viableMembers,
ImmutableArray<PickMembersOption> pickMembersOptions)
{
_service = service;
_document = document;
_textSpan = textSpan;
_containingType = containingType;
_viableMembers = viableMembers;
_pickMembersOptions = pickMembersOptions;
}
public override object GetOptions(CancellationToken cancellationToken)
{
var workspace = _document.Project.Solution.Workspace;
var service = _service._pickMembersService_forTesting ?? workspace.Services.GetService<IPickMembersService>();
return service.PickMembers(
FeaturesResources.Pick_members_to_be_used_as_constructor_parameters, _viableMembers);
FeaturesResources.Pick_members_to_be_used_as_constructor_parameters,
_viableMembers, _pickMembersOptions);
}
protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(
......@@ -55,6 +59,20 @@ public override object GetOptions(CancellationToken cancellationToken)
return ImmutableArray<CodeActionOperation>.Empty;
}
var addNullChecksOption = result.Options.FirstOrDefault(o => o.Id == AddNullChecksId);
if (addNullChecksOption != null)
{
// If we presented the 'Add null check' option, then persist whatever value
// the user chose. That way we'll keep that as the default for the next time
// the user opens the dialog.
var workspace = _document.Project.Solution.Workspace;
workspace.Options = workspace.Options.WithChangedOption(
GenerateConstructorFromMembersOptions.AddNullChecks,
_document.Project.Language,
addNullChecksOption.Value);
}
var addNullChecks = (addNullChecksOption?.Value).GetValueOrDefault();
var state = State.TryGenerate(
_service, _document, _textSpan, _containingType,
result.Members, cancellationToken);
......@@ -66,7 +84,7 @@ public override object GetOptions(CancellationToken cancellationToken)
{
if (state.MatchingConstructor.IsImplicitlyDeclared)
{
var codeAction = new FieldDelegatingCodeAction(_service, _document, state);
var codeAction = new FieldDelegatingCodeAction(_service, _document, state, addNullChecks);
return await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
}
......@@ -80,8 +98,8 @@ public override object GetOptions(CancellationToken cancellationToken)
else
{
var codeAction = state.DelegatedConstructor != null
? new ConstructorDelegatingCodeAction(_service, _document, state)
: (CodeAction)new FieldDelegatingCodeAction(_service, _document, state);
? new ConstructorDelegatingCodeAction(_service, _document, state, addNullChecks)
: (CodeAction)new FieldDelegatingCodeAction(_service, _document, state, addNullChecks);
return await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
}
......
......@@ -43,7 +43,8 @@ private class GenerateEqualsAndGetHashCodeWithDialogCodeAction : CodeActionWithO
public override object GetOptions(CancellationToken cancellationToken)
{
var service = _service._pickMembersService_forTestingPurposes ?? _document.Project.Solution.Workspace.Services.GetService<IPickMembersService>();
return service.PickMembers(FeaturesResources.Pick_members_to_be_used_in_Equals_GetHashCode, _viableMembers);
return service.PickMembers(FeaturesResources.Pick_members_to_be_used_in_Equals_GetHashCode,
_viableMembers);
}
protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(object options, CancellationToken cancellationToken)
......
......@@ -203,7 +203,10 @@ private async Task<Document> GenerateDelegatedConstructorAsync()
out var parameterToExistingFieldMap, out var parameterToNewFieldMap, out var remainingParameters);
var fields = syntaxFactory.CreateFieldsForParameters(remainingParameters, parameterToNewFieldMap);
var assignStatements = syntaxFactory.CreateAssignmentStatements(remainingParameters, parameterToExistingFieldMap, parameterToNewFieldMap);
var assignStatements = syntaxFactory.CreateAssignmentStatements(
_document.SemanticModel.Compilation, remainingParameters,
parameterToExistingFieldMap, parameterToNewFieldMap,
addNullChecks: false, preferThrowExpression: false);
var allParameters = delegatedConstructor.Parameters.Concat(remainingParameters);
......@@ -218,7 +221,7 @@ private async Task<Document> GenerateDelegatedConstructorAsync()
modifiers: default(DeclarationModifiers),
typeName: _state.TypeToGenerateIn.Name,
parameters: allParameters,
statements: assignStatements.ToImmutableArray(),
statements: assignStatements,
baseConstructorArguments: baseConstructorArguments,
thisConstructorArguments: thisConstructorArguments);
......@@ -250,8 +253,12 @@ private async Task<Document> GenerateFieldDelegatingConstructorAsync()
var syntaxTree = _document.SyntaxTree;
var members = syntaxFactory.CreateFieldDelegatingConstructor(
_state.TypeToGenerateIn.Name, _state.TypeToGenerateIn, parameters,
parameterToExistingFieldMap, parameterToNewFieldMap, _cancellationToken);
_document.SemanticModel.Compilation,
_state.TypeToGenerateIn.Name,
_state.TypeToGenerateIn, parameters,
parameterToExistingFieldMap, parameterToNewFieldMap,
addNullChecks: false, preferThrowExpression: false,
cancellationToken: _cancellationToken);
var result = await codeGenerationService.AddMembersAsync(
_document.Project.Solution,
......
......@@ -242,7 +242,11 @@ private void AddProperties(ArrayBuilder<ISymbol> members)
if (!(parameters.Count == 0 && options != null && (options.TypeKind == TypeKind.Struct || options.TypeKind == TypeKind.Structure)))
{
members.AddRange(factory.CreateFieldDelegatingConstructor(
DetermineName(), null, parameters.ToImmutable(), parameterToExistingFieldMap, parameterToNewFieldMap, _cancellationToken));
_document.SemanticModel.Compilation,
DetermineName(), null, parameters.ToImmutable(),
parameterToExistingFieldMap, parameterToNewFieldMap,
addNullChecks: false, preferThrowExpression: false,
cancellationToken: _cancellationToken));
}
parameters.Free();
......
......@@ -7,6 +7,22 @@ namespace Microsoft.CodeAnalysis.PickMembers
{
internal interface IPickMembersService : IWorkspaceService
{
PickMembersResult PickMembers(string title, ImmutableArray<ISymbol> members);
PickMembersResult PickMembers(
string title, ImmutableArray<ISymbol> members,
ImmutableArray<PickMembersOption> options = default(ImmutableArray<PickMembersOption>));
}
internal class PickMembersOption
{
public PickMembersOption(string id, string title, bool value)
{
Id = id;
Title = title;
Value = value;
}
public string Id { get; }
public string Title { get; }
public bool Value { get; set; }
}
}
\ No newline at end of file
......@@ -10,15 +10,19 @@ internal class PickMembersResult
public readonly bool IsCanceled;
public readonly ImmutableArray<ISymbol> Members;
public readonly ImmutableArray<PickMembersOption> Options;
private PickMembersResult(bool isCanceled)
{
IsCanceled = isCanceled;
}
public PickMembersResult(ImmutableArray<ISymbol> members)
public PickMembersResult(
ImmutableArray<ISymbol> members,
ImmutableArray<PickMembersOption> options)
{
Members = members;
Options = options;
}
}
}
\ No newline at end of file
......@@ -53,6 +53,10 @@
Grid.Row="0"
Header="{Binding ElementName=dialog, Path=PickMembersTitle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
......@@ -131,6 +135,22 @@
MinWidth="73"
MinHeight="21"/>
</StackPanel>
<ItemsControl Grid.Row="1"
Grid.ColumnSpan="2"
ItemsSource="{Binding Options}"
IsTabStop="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Margin="8,0,0,5">
<TextBlock Text="{Binding Title}"
TextWrapping="Wrap" />
</CheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</GroupBox>
<StackPanel Grid.Row="1"
......
......@@ -7,6 +7,7 @@
using System.Windows.Media;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.PickMembers;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
......@@ -16,11 +17,16 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.PickMembers
{
internal class PickMembersDialogViewModel : AbstractNotifyPropertyChanged
{
public List<MemberSymbolViewModel> MemberContainers { get; set; }
public List<OptionViewModel> Options { get; set; }
internal PickMembersDialogViewModel(
IGlyphService glyphService,
ImmutableArray<ISymbol> members)
ImmutableArray<ISymbol> members,
ImmutableArray<PickMembersOption> options)
{
MemberContainers = members.Select(m => new MemberSymbolViewModel(m, glyphService)).ToList();
Options = options.Select(o => new OptionViewModel(o)).ToList();
}
internal void DeselectAll()
......@@ -143,15 +149,8 @@ private void Move(List<MemberSymbolViewModel> list, int index, int delta)
list.Insert(index + delta, param);
SelectedIndex += delta;
//NotifyPropertyChanged(nameof(AllParameters));
//NotifyPropertyChanged(nameof(SignatureDisplay));
//NotifyPropertyChanged(nameof(SignaturePreviewAutomationText));
//NotifyPropertyChanged(nameof(IsOkButtonEnabled));
}
public List<MemberSymbolViewModel> MemberContainers { get; set; }
internal class MemberSymbolViewModel : AbstractNotifyPropertyChanged
{
private readonly IGlyphService _glyphService;
......@@ -184,5 +183,33 @@ public bool IsChecked
public string MemberAutomationText => MemberSymbol.Kind + " " + MemberName;
}
internal class OptionViewModel : AbstractNotifyPropertyChanged
{
public PickMembersOption Option { get; }
public string Title { get; }
public OptionViewModel(PickMembersOption option)
{
Option = option;
Title = option.Title;
IsChecked = option.Value;
}
private bool _isChecked;
public bool IsChecked
{
get => _isChecked;
set
{
Option.Value = value;
SetProperty(ref _isChecked, value);
}
}
public string MemberAutomationText => Option.Title;
}
}
}
\ No newline at end of file
......@@ -21,9 +21,12 @@ public VisualStudioPickMembersService(IGlyphService glyphService)
_glyphService = glyphService;
}
public PickMembersResult PickMembers(string title, ImmutableArray<ISymbol> members)
public PickMembersResult PickMembers(
string title, ImmutableArray<ISymbol> members, ImmutableArray<PickMembersOption> options)
{
var viewModel = new PickMembersDialogViewModel(_glyphService, members);
options = options.NullToEmpty();
var viewModel = new PickMembersDialogViewModel(_glyphService, members, options);
var dialog = new PickMembersDialog(viewModel, title);
var result = dialog.ShowModal();
......@@ -32,7 +35,8 @@ public PickMembersResult PickMembers(string title, ImmutableArray<ISymbol> membe
return new PickMembersResult(
viewModel.MemberContainers.Where(c => c.IsChecked)
.Select(c => c.MemberSymbol)
.ToImmutableArray());
.ToImmutableArray(),
options);
}
else
{
......
......@@ -4,8 +4,10 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
......@@ -15,7 +17,14 @@ namespace Microsoft.CodeAnalysis.Editing
/// <summary>
/// A language agnostic factory for creating syntax nodes.
///
/// This API can be used to create language specific syntax nodes that are semantically similar between languages.
/// This API can be used to create language specific syntax nodes that are semantically
/// similar between languages.
///
/// The trees generated by this API will try to respect user preferences when
/// possible. For example, generating <see cref="MemberAccessExpression(SyntaxNode, string)"/>
/// will be done in a way such that "this." or "Me." will be simplified according to user
/// preference if any <see cref="Simplifier.ReduceAsync(Document, OptionSet, CancellationToken)" />
/// overload is called.
/// </summary>
public abstract class SyntaxGenerator : ILanguageService
{
......
......@@ -64,16 +64,21 @@ internal static partial class ICodeDefinitionFactoryExtensions
public static IEnumerable<ISymbol> CreateFieldDelegatingConstructor(
this SyntaxGenerator factory,
Compilation compilation,
string typeName,
INamedTypeSymbol containingTypeOpt,
ImmutableArray<IParameterSymbol> parameters,
IDictionary<string, ISymbol> parameterToExistingFieldMap,
IDictionary<string, string> parameterToNewFieldMap,
bool addNullChecks,
bool preferThrowExpression,
CancellationToken cancellationToken)
{
var fields = factory.CreateFieldsForParameters(parameters, parameterToNewFieldMap);
var statements = factory.CreateAssignmentStatements(parameters, parameterToExistingFieldMap, parameterToNewFieldMap)
.Select(s => s.WithAdditionalAnnotations(Simplifier.Annotation));
var statements = factory.CreateAssignmentStatements(
compilation, parameters, parameterToExistingFieldMap, parameterToNewFieldMap,
addNullChecks, preferThrowExpression).SelectAsArray(
s => s.WithAdditionalAnnotations(Simplifier.Annotation));
foreach (var field in fields)
{
......@@ -86,7 +91,7 @@ internal static partial class ICodeDefinitionFactoryExtensions
modifiers: new DeclarationModifiers(),
typeName: typeName,
parameters: parameters,
statements: statements.ToImmutableArray(),
statements: statements,
thisConstructorArguments: GetThisConstructorArguments(containingTypeOpt, parameterToExistingFieldMap));
}
......@@ -164,12 +169,44 @@ private static bool TryGetValue(IDictionary<string, ISymbol> dictionary, string
return false;
}
public static IEnumerable<SyntaxNode> CreateAssignmentStatements(
public static SyntaxNode CreateThrowArgumentNullExpression(
this SyntaxGenerator factory,
Compilation compilation,
IParameterSymbol parameter)
{
return factory.ThrowExpression(
factory.ObjectCreationExpression(
compilation.GetTypeByMetadataName("System.ArgumentNullException"),
factory.NameOfExpression(
factory.IdentifierName(parameter.Name))));
}
public static SyntaxNode CreateIfNullThrowStatement(
this SyntaxGenerator factory,
Compilation compilation,
IParameterSymbol parameter)
{
return factory.IfStatement(
factory.ReferenceEqualsExpression(
factory.IdentifierName(parameter.Name),
factory.NullLiteralExpression()),
SpecializedCollections.SingletonEnumerable(
factory.ExpressionStatement(
factory.CreateThrowArgumentNullExpression(compilation, parameter))));
}
public static ImmutableArray<SyntaxNode> CreateAssignmentStatements(
this SyntaxGenerator factory,
Compilation compilation,
IList<IParameterSymbol> parameters,
IDictionary<string, ISymbol> parameterToExistingFieldMap,
IDictionary<string, string> parameterToNewFieldMap)
IDictionary<string, string> parameterToNewFieldMap,
bool addNullChecks,
bool preferThrowExpression)
{
var nullCheckStatements = ArrayBuilder<SyntaxNode>.GetInstance();
var assignStatements = ArrayBuilder<SyntaxNode>.GetInstance();
foreach (var parameter in parameters)
{
var refKind = parameter.RefKind;
......@@ -179,12 +216,12 @@ private static bool TryGetValue(IDictionary<string, ISymbol> dictionary, string
if (refKind == RefKind.Out)
{
// If it's an out param, then don't create a field for it. Instead, assign
// assign the default value for that type (i.e. "default(...)") to it.
// the default value for that type (i.e. "default(...)") to it.
var assignExpression = factory.AssignmentStatement(
factory.IdentifierName(parameterName),
factory.DefaultExpression(parameterType));
var statement = factory.ExpressionStatement(assignExpression);
yield return statement;
assignStatements.Add(statement);
}
else
{
......@@ -195,13 +232,61 @@ private static bool TryGetValue(IDictionary<string, ISymbol> dictionary, string
{
var fieldAccess = factory.MemberAccessExpression(factory.ThisExpression(), factory.IdentifierName(fieldName))
.WithAdditionalAnnotations(Simplifier.Annotation);
var assignExpression = factory.AssignmentStatement(
fieldAccess, factory.IdentifierName(parameterName));
var statement = factory.ExpressionStatement(assignExpression);
yield return statement;
factory.AddAssignmentStatements(
compilation, parameter, fieldAccess,
addNullChecks, preferThrowExpression,
nullCheckStatements, assignStatements);
}
}
}
return nullCheckStatements.ToImmutableAndFree().Concat(assignStatements.ToImmutableAndFree());
}
public static void AddAssignmentStatements(
this SyntaxGenerator factory,
Compilation compilation,
IParameterSymbol parameter,
SyntaxNode fieldAccess,
bool addNullChecks,
bool preferThrowExpression,
ArrayBuilder<SyntaxNode> nullCheckStatements,
ArrayBuilder<SyntaxNode> assignStatements)
{
var shouldAddNullCheck = addNullChecks && parameter.Type.CanAddNullCheck();
if (shouldAddNullCheck && preferThrowExpression)
{
// Generate: this.x = x ?? throw ...
assignStatements.Add(CreateAssignWithNullCheckStatement(
factory, compilation, parameter, fieldAccess));
}
else
{
if (shouldAddNullCheck)
{
// generate: if (x == null) throw ...
nullCheckStatements.Add(
factory.CreateIfNullThrowStatement(compilation, parameter));
}
// generate: this.x = x;
assignStatements.Add(
factory.ExpressionStatement(
factory.AssignmentStatement(
fieldAccess,
factory.IdentifierName(parameter.Name))));
}
}
public static SyntaxNode CreateAssignWithNullCheckStatement(
this SyntaxGenerator factory, Compilation compilation, IParameterSymbol parameter, SyntaxNode fieldAccess)
{
return factory.ExpressionStatement(factory.AssignmentStatement(
fieldAccess,
factory.CoalesceExpression(
factory.IdentifierName(parameter.Name),
factory.CreateThrowArgumentNullExpression(compilation, parameter))));
}
public static async Task<IPropertySymbol> OverridePropertyAsync(
......
......@@ -19,6 +19,9 @@ internal static partial class ITypeSymbolExtensions
private const string DefaultParameterName = "p";
private const string DefaultBuiltInParameterName = "v";
public static bool CanAddNullCheck(this ITypeSymbol type)
=> type != null && (type.IsReferenceType || type.IsNullable());
public static IList<INamedTypeSymbol> GetAllInterfacesIncludingThis(this ITypeSymbol type)
{
var allInterfaces = type.AllInterfaces;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册