static void AddNestedFixMenu(TextEditor editor, CodeFixMenu menu, CodeAction.CodeActionWithNestedActions fixes) { int subMnemonic = 0; var subMenu = new CodeFixMenu(fixes.Title); foreach (var fix in fixes.NestedCodeActions) { AddFixMenuItem(editor, subMenu, ref subMnemonic, fix); } menu.Add(subMenu); }
private void RegisterFixForMethodOverloads( CodeFixContext context, SeparatedSyntaxList <TArgumentSyntax> arguments, ImmutableArray <ArgumentInsertPositionData <TArgumentSyntax> > methodsAndArgumentsToAdd) { var codeFixData = PrepareCreationOfCodeActions(context.Document, arguments, methodsAndArgumentsToAdd); // To keep the list of offered fixes short we create one menu entry per overload only // as long as there are two or less overloads present. If there are more overloads we // create two menu entries. One entry for non-cascading fixes and one with cascading fixes. var fixes = codeFixData.Length <= 2 ? NestByOverload() : NestByCascading(); context.RegisterFixes(fixes, context.Diagnostics); return; ImmutableArray <CodeAction> NestByOverload() { var builder = ArrayBuilder <CodeAction> .GetInstance(codeFixData.Length); foreach (var data in codeFixData) { // We create the mandatory data.CreateChangedSolutionNonCascading fix first. var title = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0, data.Method, includeParameters: true); CodeAction codeAction = new MyCodeAction( title: title, data.CreateChangedSolutionNonCascading); if (data.CreateChangedSolutionCascading != null) { // We have two fixes to offer. We nest the two fixes in an inlinable CodeAction // so the IDE is free to either show both at once or to create a sub-menu. var titleForNesting = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0, data.Method, includeParameters: true); var titleCascading = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0_and_overrides_implementations, data.Method, includeParameters: true); codeAction = new CodeAction.CodeActionWithNestedActions( title: titleForNesting, ImmutableArray.Create( codeAction, new MyCodeAction( title: titleCascading, data.CreateChangedSolutionCascading)), isInlinable: true); } // codeAction is now either a single fix or two fixes wrapped in a CodeActionWithNestedActions builder.Add(codeAction); } return(builder.ToImmutableAndFree()); } ImmutableArray <CodeAction> NestByCascading() { var builder = ArrayBuilder <CodeAction> .GetInstance(2); var nonCascadingActions = ImmutableArray.CreateRange <CodeFixData, CodeAction>(codeFixData, data => { var title = GetCodeFixTitle(FeaturesResources.Add_to_0, data.Method, includeParameters: true); return(new MyCodeAction(title: title, data.CreateChangedSolutionNonCascading)); }); var cascading = codeFixData.Where(data => data.CreateChangedSolutionCascading != null); var cascadingActions = ImmutableArray.CreateRange <CodeAction>(cascading.Select(data => { var title = GetCodeFixTitle(FeaturesResources.Add_to_0, data.Method, includeParameters: true); return(new MyCodeAction(title: title, data.CreateChangedSolutionCascading)); })); var aMethod = codeFixData.First().Method; // We need to term the MethodGroup and need an arbitrary IMethodSymbol to do so. var nestedNonCascadingTitle = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0, aMethod, includeParameters: false); // Create a sub-menu entry with all the non-cascading CodeActions. // We make sure the IDE does not inline. Otherwise the context menu gets flooded with our fixes. builder.Add(new CodeAction.CodeActionWithNestedActions(nestedNonCascadingTitle, nonCascadingActions, isInlinable: false)); if (cascadingActions.Length > 0) { // if there are cascading CodeActions create a second sub-menu. var nestedCascadingTitle = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0_and_overrides_implementations, aMethod, includeParameters: false); builder.Add(new CodeAction.CodeActionWithNestedActions(nestedCascadingTitle, cascadingActions, isInlinable: false)); } return(builder.ToImmutableAndFree()); } }
private static void RegisterFixForMethodOverloads( CodeFixContext context, SeparatedSyntaxList <TArgumentSyntax> arguments, ImmutableArray <ArgumentInsertPositionData <TArgumentSyntax> > methodsAndArgumentsToAdd) { var codeFixData = PrepareCreationOfCodeActions(context.Document, arguments, methodsAndArgumentsToAdd); // To keep the list of offered fixes short we create one menu entry per overload only // as long as there are two or less overloads present. If there are more overloads we // create two menu entries. One entry for non-cascading fixes and one with cascading fixes. var fixes = codeFixData.Length <= 2 ? NestByOverload() : NestByCascading(); context.RegisterFixes(fixes, context.Diagnostics); return; ImmutableArray <CodeAction> NestByOverload() { using var builderDisposer = ArrayBuilder <CodeAction> .GetInstance(codeFixData.Length, out var builder); foreach (var data in codeFixData) { // We create the mandatory data.CreateChangedSolutionNonCascading fix first. var title = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0, data.Method, includeParameters: true); CodeAction codeAction = new MyCodeAction( title: title, data.CreateChangedSolutionNonCascading); if (data.CreateChangedSolutionCascading != null) { // We have two fixes to offer. We nest the two fixes in an inlinable CodeAction // so the IDE is free to either show both at once or to create a sub-menu. var titleForNesting = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0, data.Method, includeParameters: true); var titleCascading = GetCodeFixTitle(FeaturesResources.Add_parameter_to_0_and_overrides_implementations, data.Method, includeParameters: true); codeAction = new CodeAction.CodeActionWithNestedActions( title: titleForNesting, ImmutableArray.Create( codeAction, new MyCodeAction( title: titleCascading, data.CreateChangedSolutionCascading)), isInlinable: true); } // codeAction is now either a single fix or two fixes wrapped in a CodeActionWithNestedActions builder.Add(codeAction); } return(builder.ToImmutable()); } ImmutableArray <CodeAction> NestByCascading() { using var builderDisposer = ArrayBuilder <CodeAction> .GetInstance(capacity : 2, out var builder); var nonCascadingActions = codeFixData.SelectAsArray(data => { var title = GetCodeFixTitle(FeaturesResources.Add_to_0, data.Method, includeParameters: true); return((CodeAction) new MyCodeAction(title: title, data.CreateChangedSolutionNonCascading)); }); var cascadingActions = codeFixData.SelectAsArray( data => data.CreateChangedSolutionCascading != null, data => { var title = GetCodeFixTitle(FeaturesResources.Add_to_0, data.Method, includeParameters: true); return((CodeAction) new MyCodeAction(title: title, data.CreateChangedSolutionCascading !)); });
static void AddNestedFixMenu(Ide.Editor.TextEditor editor, CodeFixMenu menu, CodeFixMenu fixAllMenu, CodeAction.CodeActionWithNestedActions fixes, FixAllState fixState, CancellationToken token) { int subMnemonic = 0; var subMenu = new CodeFixMenu(fixes.Title); foreach (var fix in fixes.NestedCodeActions) { AddFixMenuItem(editor, subMenu, fixAllMenu, ref subMnemonic, fix, fixState, token); } menu.Add(subMenu); }
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var(document, _, cancellationToken) = context; var calleeInvocationNode = await context.TryGetRelevantNodeAsync <TInvocationSyntax>().ConfigureAwait(false); if (calleeInvocationNode == null) { return; } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var calleeMethodSymbol = semanticModel.GetSymbolInfo(calleeInvocationNode, cancellationToken).GetAnySymbol() as IMethodSymbol; if (calleeMethodSymbol == null) { return; } if (calleeMethodSymbol.PartialImplementationPart != null) { calleeMethodSymbol = calleeMethodSymbol.PartialImplementationPart; } if (!calleeMethodSymbol.IsOrdinaryMethod() && !calleeMethodSymbol.IsExtensionMethod) { return; } if (calleeMethodSymbol.DeclaredAccessibility != Accessibility.Private) { return; } var symbolDeclarationService = document.GetRequiredLanguageService <ISymbolDeclarationService>(); var calleeMethodDeclarationSyntaxReferences = symbolDeclarationService.GetDeclarations(calleeMethodSymbol); if (calleeMethodDeclarationSyntaxReferences.Length != 1) { return; } var calleeMethodDeclarationSyntaxReference = calleeMethodDeclarationSyntaxReferences[0]; var calleeMethodNode = await calleeMethodDeclarationSyntaxReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false) as TMethodDeclarationSyntax; if (calleeMethodNode == null) { return; } var inlineExpression = GetRawInlineExpression(calleeMethodNode); // Special case 1: AwaitExpression if (_syntaxFacts.IsAwaitExpression(inlineExpression)) { // 1. If Caller & callee both have 'await' make sure there is no duplicate 'await' // Example: // Before: // async Task Caller() => await Callee(); // async Task Callee() => await Task.CompletedTask; // After: // async Task Caller() => await Task.CompletedTask; // async Task Callee() => await Task.CompletedTask; // The original inline expression in callee will be 'await Task.CompletedTask' // The caller just need 'Task.CompletedTask' without the 'await' // // 2. If Caller doesn't have await but callee has. // Example: // Before: // void Caller() { Callee().Wait();} // async Task Callee() => await DoAsync(); // After: // void Caller() { DoAsync().Wait(); } // async Task Callee() => await DoAsync(); // What caller is expecting is an expression returns 'Task', which doesn't include the 'await' inlineExpression = _syntaxFacts.GetExpressionOfAwaitExpression(inlineExpression) as TExpressionSyntax; } if (inlineExpression == null) { return; } // Special case 2: ThrowStatement & ThrowExpresion if (_syntaxFacts.IsThrowStatement(inlineExpression.Parent) || _syntaxFacts.IsThrowExpression(inlineExpression)) { // If this is a throw statement, then it should be valid for // 1. If it is invoked as ExpressionStatement // Example: // Before: // void Caller() { Callee(); } // void Callee() { throw new Exception();} // After: // void Caller() { throw new Exception(); } // void Callee() { throw new Exception();} // 2. If it is invoked in a place allow throw expression // Example: // Before: // void Caller(bool flag) { var x = flag ? Callee() : 1; } // int Callee() { throw new Exception();} // After: // void Caller() { var x = flag ? throw new Exception() : 1; } // int Callee() { throw new Exception();} // Note here throw statement is changed to throw expression after inlining // If this is a throw expression, the check is the same // 1. If it is invoked as ExpressionStatement // Example: // Before: // void Caller() { Callee(); } // void Callee() => throw new Exception(); // After: // void Caller() { throw new Exception(); } // void Callee() => throw new Exception(); // Note here throw expression is converted to throw statement // 2. If it is invoked in a place allow throw expression // Example: // Before: // void Caller(bool flag) { var x = flag ? Callee() : 1; } // int Callee() => throw new Exception(); // After: // void Caller() { var x = flag ? throw new Exception() : 1; } // int Callee() => throw new Exception(); if (!CanBeReplacedByThrowExpression(calleeInvocationNode) && !_syntaxFacts.IsExpressionStatement(calleeInvocationNode.Parent)) { return; } } var callerSymbol = GetCallerSymbol(calleeInvocationNode, semanticModel, cancellationToken); if (callerSymbol == null) { return; } var callerReferences = symbolDeclarationService.GetDeclarations(callerSymbol); if (callerReferences.Length != 1) { return; } var callerDeclarationNode = await callerReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false); var invocationOperation = semanticModel.GetOperation(calleeInvocationNode, cancellationToken) as IInvocationOperation; if (invocationOperation == null) { return; } var codeActions = GenerateCodeActions( document, calleeInvocationNode, calleeMethodSymbol, calleeMethodNode, callerSymbol, callerDeclarationNode, inlineExpression, invocationOperation); var nestedCodeAction = new CodeAction.CodeActionWithNestedActions( string.Format(FeaturesResources.Inline_0, calleeMethodSymbol.ToNameDisplayString()), codeActions, isInlinable: true); context.RegisterRefactoring(nestedCodeAction, calleeInvocationNode.Span); }