public async Task <CodeAction> IntroduceVariableAsync( Document document, TextSpan textSpan, CodeCleanupOptions options, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Refactoring_IntroduceVariable, cancellationToken)) { var semanticDocument = await SemanticDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); var state = await State.GenerateAsync((TService)this, semanticDocument, options, textSpan, cancellationToken).ConfigureAwait(false); if (state != null) { var(title, actions) = CreateActions(state, cancellationToken); if (actions.Length > 0) { // We may end up creating a lot of viable code actions for the selected // piece of code. Create a top level code action so that we don't overwhelm // the light bulb if there are a lot of other options in the list. Set // the code action as 'inlinable' so that if the lightbulb is not cluttered // then the nested items can just be lifted into it, giving the user fast // access to them. return(CodeActionWithNestedActions.Create(title, actions, isInlinable: true)); } } return(null); } }
/// <summary> /// Creates a <see cref="CodeAction"/> representing a group of code actions. /// </summary> /// <param name="title">Title of the <see cref="CodeAction"/> group.</param> /// <param name="nestedActions">The code actions within the group.</param> /// <param name="isInlinable"><see langword="true"/> to allow inlining the members of the group into the parent; /// otherwise, <see langword="false"/> to require that this group appear as a group with nested actions.</param> public static CodeAction Create(string title, ImmutableArray <CodeAction> nestedActions, bool isInlinable) { if (title is null) { throw new ArgumentNullException(nameof(title)); } if (nestedActions == null) { throw new ArgumentNullException(nameof(nestedActions)); } return(CodeActionWithNestedActions.Create(title, nestedActions, isInlinable)); }
public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var(document, textSpan, cancellationToken) = context; if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles) { return; } var expression = await document.TryGetRelevantNodeAsync <TExpressionSyntax>(textSpan, cancellationToken).ConfigureAwait(false); if (expression == null || CodeRefactoringHelpers.IsNodeUnderselected(expression, textSpan)) { return; } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var expressionType = semanticModel.GetTypeInfo(expression, cancellationToken).Type; if (expressionType is null or IErrorTypeSymbol) { return; } var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>(); // Need to special case for expressions that are contained within a parameter // because it is technically "contained" within a method, but an expression in a parameter does not make // sense to introduce. var parameterNode = expression.FirstAncestorOrSelf <SyntaxNode>(node => syntaxFacts.IsParameter(node)); if (parameterNode is not null) { return; } // Need to special case for highlighting of method types because they are also "contained" within a method, // but it does not make sense to introduce a parameter in that case. if (syntaxFacts.IsInNamespaceOrTypeContext(expression)) { return; } // Need to special case for expressions whose direct parent is a MemberAccessExpression since they will // never introduce a parameter that makes sense in that case. if (syntaxFacts.IsNameOfAnyMemberAccessExpression(expression)) { return; } var generator = SyntaxGenerator.GetGenerator(document); var containingMethod = expression.FirstAncestorOrSelf <SyntaxNode>(node => generator.GetParameterListNode(node) is not null); if (containingMethod is null) { return; } var containingSymbol = semanticModel.GetDeclaredSymbol(containingMethod, cancellationToken); if (containingSymbol is not IMethodSymbol methodSymbol) { return; } var expressionSymbol = semanticModel.GetSymbolInfo(expression, cancellationToken).Symbol; if (expressionSymbol is IParameterSymbol parameterSymbol && parameterSymbol.ContainingSymbol.Equals(containingSymbol)) { return; } // Code actions for trampoline and overloads will not be offered if the method is a constructor. // Code actions for overloads will not be offered if the method if the method is a local function. var methodKind = methodSymbol.MethodKind; if (methodKind is not(MethodKind.Ordinary or MethodKind.LocalFunction or MethodKind.Constructor)) { return; } if (IsDestructor(methodSymbol)) { return; } var actions = await GetActionsAsync(document, expression, methodSymbol, containingMethod, context.Options, cancellationToken).ConfigureAwait(false); if (actions is null) { return; } var singleLineExpression = syntaxFacts.ConvertToSingleLine(expression); var nodeString = singleLineExpression.ToString(); if (actions.Value.actions.Length > 0) { context.RegisterRefactoring(CodeActionWithNestedActions.Create( string.Format(FeaturesResources.Introduce_parameter_for_0, nodeString), actions.Value.actions, isInlinable: false, priority: CodeActionPriority.Low), textSpan); } if (actions.Value.actionsAllOccurrences.Length > 0) { context.RegisterRefactoring(CodeActionWithNestedActions.Create( string.Format(FeaturesResources.Introduce_parameter_for_all_occurrences_of_0, nodeString), actions.Value.actionsAllOccurrences, isInlinable: false, priority: CodeActionPriority.Low), textSpan); } }