public GenerateLocalCodeAction(TService service, Document document, State state, CodeGenerationOptionsProvider fallbackOptions) { _service = service; _document = document; _state = state; _fallbackOptions = fallbackOptions; }
private static async Task <Document> UpdateDocumentAsync( SyntaxNode root, Document document, BlockSyntax parentBlock, LocalFunctionStatementSyntax localFunction, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var declaredSymbol = (IMethodSymbol)semanticModel.GetDeclaredSymbol(localFunction, cancellationToken); var dataFlow = semanticModel.AnalyzeDataFlow( localFunction.Body ?? (SyntaxNode)localFunction.ExpressionBody.Expression); // Exclude local function parameters in case they were captured inside the function body var captures = dataFlow.CapturedInside.Except(dataFlow.VariablesDeclared).Except(declaredSymbol.Parameters).ToList(); // First, create a parameter per each capture so that we can pass them as arguments to the final method // Filter out `this` because it doesn't need a parameter, we will just make a non-static method for that // We also make a `ref` parameter here for each capture that is being written into inside the function var capturesAsParameters = captures .Where(capture => !capture.IsThisParameter()) .Select(capture => CodeGenerationSymbolFactory.CreateParameterSymbol( attributes: default,
public override async Task <ImmutableArray <SyntaxNode> > GetReplacementMembersAsync( Document document, IPropertySymbol property, SyntaxNode propertyDeclarationNode, IFieldSymbol propertyBackingField, string desiredGetMethodName, string desiredSetMethodName, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { if (propertyDeclarationNode is not PropertyDeclarationSyntax propertyDeclaration) { return(ImmutableArray <SyntaxNode> .Empty); } var options = (CSharpCodeGenerationOptions)await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var languageVersion = syntaxTree.Options.LanguageVersion(); return(ConvertPropertyToMembers( languageVersion, SyntaxGenerator.GetGenerator(document), property, propertyDeclaration, propertyBackingField, options.PreferExpressionBodiedMethods.Value, desiredGetMethodName, desiredSetMethodName, cancellationToken)); }
private static async Task <AddConstructorParameterResult?> AddConstructorParametersFromMembersAsync( Document document, TextSpan textSpan, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Refactoring_GenerateFromMembers_AddConstructorParametersFromMembers, cancellationToken)) { var info = await GetSelectedMemberInfoAsync( document, textSpan, allowPartialSelection : true, cancellationToken).ConfigureAwait(false); if (info != null) { var state = await State.GenerateAsync(info.SelectedMembers, document, fallbackOptions, cancellationToken).ConfigureAwait(false); if (state?.ConstructorCandidates != null && !state.ConstructorCandidates.IsEmpty) { var codeGenOptions = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); var contextInfo = codeGenOptions.GetInfo(CodeGenerationContext.Default, document.Project); return(CreateCodeActions(document, contextInfo, state)); } } return(null); } }
protected override async Task <ImmutableArray <CodeAction> > GetRefactoringsForSingleParameterAsync( Document document, TParameterSyntax parameterSyntax, IParameterSymbol parameter, SyntaxNode constructorDeclaration, IMethodSymbol method, IBlockOperation?blockStatementOpt, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { // Only supported for constructor parameters. if (method.MethodKind != MethodKind.Constructor) { return(ImmutableArray <CodeAction> .Empty); } var typeDeclaration = constructorDeclaration.GetAncestor <TTypeDeclarationSyntax>(); if (typeDeclaration == null) { return(ImmutableArray <CodeAction> .Empty); } // See if we're already assigning this parameter to a field/property in this type. If so, there's nothing // more for us to do. var assignmentStatement = TryFindFieldOrPropertyAssignmentStatement(parameter, blockStatementOpt); if (assignmentStatement != null) { return(ImmutableArray <CodeAction> .Empty); } // Haven't initialized any fields/properties with this parameter. Offer to assign // to an existing matching field/prop if we can find one, or add a new field/prop // if we can't. var rules = await document.GetNamingRulesAsync(cancellationToken).ConfigureAwait(false); var parameterNameParts = IdentifierNameParts.CreateIdentifierNameParts(parameter, rules); if (parameterNameParts.BaseName == "") { return(ImmutableArray <CodeAction> .Empty); } var fieldOrProperty = await TryFindMatchingUninitializedFieldOrPropertySymbolAsync( document, parameter, blockStatementOpt, rules, parameterNameParts.BaseNameParts, cancellationToken).ConfigureAwait(false); if (fieldOrProperty != null) { return(HandleExistingFieldOrProperty( document, parameter, constructorDeclaration, blockStatementOpt, fieldOrProperty, fallbackOptions)); } return(await HandleNoExistingFieldOrPropertyAsync( document, parameter, constructorDeclaration, method, blockStatementOpt, rules, fallbackOptions, cancellationToken).ConfigureAwait(false)); }
protected abstract Task <ImmutableArray <CodeAction> > GetRefactoringsForSingleParameterAsync( Document document, TParameterSyntax parameterSyntax, IParameterSymbol parameter, SyntaxNode functionDeclaration, IMethodSymbol methodSymbol, IBlockOperation?blockStatementOpt, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken);
private static async Task AddParameterCodeActionsAsync( ArrayBuilder <CodeAction> result, Document document, State state, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { if (state.CanGenerateParameter()) { // Don't generate parameters with a `_` prefix unless that's what the user really wants as their naming style. if (await NameIsHighlyUnlikelyToWarrantSymbolAsync( document, state, SymbolKind.Parameter, Accessibility.NotApplicable, fallbackOptions, cancellationToken).ConfigureAwait(false)) { return; } var containingMethod = state.ContainingMethod; var parameterIndex = containingMethod.Parameters.Length; if (containingMethod.Parameters.Length > 0) { var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var cancellationTokenType = compilation.CancellationTokenType(); for (var i = containingMethod.Parameters.Length - 1; i >= 0; i--) { var parameter = containingMethod.Parameters[i]; // Keep moving the insertion position for the generated parameter backwards // until we get to a parameter that does not need to be at the end of the // parameter list. if (parameter.HasExplicitDefaultValue || parameter.IsParams || parameter.RefKind is RefKind.Out || Equals(parameter.Type, cancellationTokenType)) { parameterIndex = i; continue; } break; } // If we are in an extension method, then we want to make sure to insert after // the first parameter. if (containingMethod.IsExtensionMethod && parameterIndex == 0) { parameterIndex = 1; } } result.Add(new GenerateParameterCodeAction(document, state, includeOverridesAndImplementations: false, parameterIndex)); if (AddParameterService.HasCascadingDeclarations(state.ContainingMethod)) { result.Add(new GenerateParameterCodeAction(document, state, includeOverridesAndImplementations: true, parameterIndex)); } } }
private async Task <ImmutableArray <CodeAction> > HandleNoExistingFieldOrPropertyAsync( Document document, IParameterSymbol parameter, SyntaxNode constructorDeclaration, IMethodSymbol method, IBlockOperation?blockStatementOpt, ImmutableArray <NamingRule> rules, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { // Didn't find a field/prop that this parameter could be assigned to. // Offer to create new one and assign to that. using var _ = ArrayBuilder <CodeAction> .GetInstance(out var allActions); var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var(fieldAction, propertyAction) = AddSpecificParameterInitializationActions( document, parameter, constructorDeclaration, blockStatementOpt, rules, options, fallbackOptions); // Check if the surrounding parameters are assigned to another field in this class. If so, offer to // make this parameter into a field as well. Otherwise, default to generating a property var siblingFieldOrProperty = TryFindSiblingFieldOrProperty(parameter, blockStatementOpt); if (siblingFieldOrProperty is IFieldSymbol) { allActions.Add(fieldAction); allActions.Add(propertyAction); } else { allActions.Add(propertyAction); allActions.Add(fieldAction); } var(allFieldsAction, allPropertiesAction) = AddAllParameterInitializationActions( document, constructorDeclaration, method, blockStatementOpt, rules, options, fallbackOptions); if (allFieldsAction != null && allPropertiesAction != null) { if (siblingFieldOrProperty is IFieldSymbol) { allActions.Add(allFieldsAction); allActions.Add(allPropertiesAction); } else { allActions.Add(allPropertiesAction); allActions.Add(allFieldsAction); } } return(allActions.ToImmutable()); }
private (CodeAction?fieldAction, CodeAction?propertyAction) AddAllParameterInitializationActions( Document document, SyntaxNode constructorDeclaration, IMethodSymbol method, IBlockOperation?blockStatementOpt, ImmutableArray <NamingRule> rules, AccessibilityModifiersRequired accessibilityModifiersRequired, CodeGenerationOptionsProvider fallbackOptions) { if (blockStatementOpt == null) { return(default);
public static async Task <Document> MakeLocalFunctionStaticAsync( Document document, LocalFunctionStatementSyntax localFunction, ImmutableArray <ISymbol> captures, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { var root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false)) !; var syntaxEditor = new SyntaxEditor(root, document.Project.Solution.Services); await MakeLocalFunctionStaticAsync(document, localFunction, captures, syntaxEditor, fallbackOptions, cancellationToken).ConfigureAwait(false); return(document.WithSyntaxRoot(syntaxEditor.GetChangedRoot())); }
private async Task AddLocalCodeActionsAsync( ArrayBuilder <CodeAction> result, Document document, State state, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { if (state.CanGenerateLocal()) { // Don't generate locals with a `_` prefix unless that's what the user really wants as their naming style. if (await NameIsHighlyUnlikelyToWarrantSymbolAsync( document, state, SymbolKind.Local, Accessibility.NotApplicable, fallbackOptions, cancellationToken).ConfigureAwait(false)) { return; } result.Add(new GenerateLocalCodeAction((TService)this, document, state, fallbackOptions)); } }
protected override async Task <ImmutableArray <CodeAction> > GetRefactoringsForSingleParameterAsync( Document document, TParameterSyntax parameterSyntax, IParameterSymbol parameter, SyntaxNode funcOrRecord, IMethodSymbol methodSymbol, IBlockOperation?blockStatementOpt, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); // Only should provide null-checks for reference types and nullable types. if (!ParameterValidForNullCheck(document, parameter, semanticModel, blockStatementOpt, cancellationToken)) { return(ImmutableArray <CodeAction> .Empty); } // Great. There was no null check. Offer to add one. using var result = TemporaryArray <CodeAction> .Empty; result.Add(CodeAction.Create( FeaturesResources.Add_null_check, c => AddNullCheckAsync(document, parameterSyntax, parameter, funcOrRecord, methodSymbol, blockStatementOpt, c), nameof(FeaturesResources.Add_null_check))); // Also, if this was a string, offer to add the special checks to string.IsNullOrEmpty and // string.IsNullOrWhitespace. We cannot do this for records though as they have no location // to place the checks. if (parameter.Type.SpecialType == SpecialType.System_String && !IsRecordDeclaration(funcOrRecord)) { result.Add(CodeAction.Create( FeaturesResources.Add_string_IsNullOrEmpty_check, c => AddStringCheckAsync(document, parameter, funcOrRecord, methodSymbol, blockStatementOpt, nameof(string.IsNullOrEmpty), c), nameof(FeaturesResources.Add_string_IsNullOrEmpty_check))); result.Add(CodeAction.Create( FeaturesResources.Add_string_IsNullOrWhiteSpace_check, c => AddStringCheckAsync(document, parameter, funcOrRecord, methodSymbol, blockStatementOpt, nameof(string.IsNullOrWhiteSpace), c), nameof(FeaturesResources.Add_string_IsNullOrWhiteSpace_check))); } return(result.ToImmutableAndClear()); }
public IntroduceParameterDocumentRewriter( AbstractIntroduceParameterService <TExpressionSyntax, TInvocationExpressionSyntax, TObjectCreationExpressionSyntax, TIdentifierNameSyntax> service, Document originalDocument, TExpressionSyntax expression, IMethodSymbol methodSymbol, SyntaxNode containingMethod, IntroduceParameterCodeActionKind selectedCodeAction, CodeGenerationOptionsProvider fallbackOptions, bool allOccurrences) { _service = service; _originalDocument = originalDocument; _generator = SyntaxGenerator.GetGenerator(originalDocument); _syntaxFacts = originalDocument.GetRequiredLanguageService <ISyntaxFactsService>(); _semanticFacts = originalDocument.GetRequiredLanguageService <ISemanticFactsService>(); _expression = expression; _methodSymbol = methodSymbol; _containerMethod = containingMethod; _actionKind = selectedCodeAction; _allOccurrences = allOccurrences; _fallbackOptions = fallbackOptions; }
/// <summary> /// Introduces a new parameter and refactors all the call sites based on the selected code action. /// </summary> private async Task <Solution> IntroduceParameterAsync(Document originalDocument, TExpressionSyntax expression, IMethodSymbol methodSymbol, SyntaxNode containingMethod, bool allOccurrences, IntroduceParameterCodeActionKind selectedCodeAction, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { var methodCallSites = await FindCallSitesAsync(originalDocument, methodSymbol, cancellationToken).ConfigureAwait(false); var modifiedSolution = originalDocument.Project.Solution; var rewriter = new IntroduceParameterDocumentRewriter(this, originalDocument, expression, methodSymbol, containingMethod, selectedCodeAction, fallbackOptions, allOccurrences); foreach (var(project, projectCallSites) in methodCallSites.GroupBy(kvp => kvp.Key.Project)) { var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); foreach (var(document, invocations) in projectCallSites) { var newRoot = await rewriter.RewriteDocumentAsync(compilation, document, invocations, cancellationToken).ConfigureAwait(false); modifiedSolution = modifiedSolution.WithDocumentSyntaxRoot(document.Id, newRoot); } } return(modifiedSolution); }
public static async Task MakeLocalFunctionStaticAsync( Document document, LocalFunctionStatementSyntax localFunction, ImmutableArray <ISymbol> captures, SyntaxEditor syntaxEditor, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { var root = (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false)) !; var semanticModel = (await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false)) !; var localFunctionSymbol = semanticModel.GetDeclaredSymbol(localFunction, cancellationToken); Contract.ThrowIfNull(localFunctionSymbol, "We should have gotten a method symbol for a local function."); var documentImmutableSet = ImmutableHashSet.Create(document); // Finds all the call sites of the local function var referencedSymbols = await SymbolFinder.FindReferencesAsync( localFunctionSymbol, document.Project.Solution, documentImmutableSet, cancellationToken).ConfigureAwait(false); // Now we need to find all the references to the local function that we might need to fix. var shouldWarn = false; using var builderDisposer = ArrayBuilder <InvocationExpressionSyntax> .GetInstance(out var invocations); foreach (var referencedSymbol in referencedSymbols) { foreach (var location in referencedSymbol.Locations) { // We limited the search scope to the single document, // so all reference should be in the same tree. var referenceNode = root.FindNode(location.Location.SourceSpan); if (referenceNode is not IdentifierNameSyntax identifierNode) { // Unexpected scenario, skip and warn. shouldWarn = true; continue; } if (identifierNode.Parent is InvocationExpressionSyntax invocation) { invocations.Add(invocation); } else { // We won't be able to fix non-invocation references, // e.g. creating a delegate. shouldWarn = true; } } } var parameterAndCapturedSymbols = CreateParameterSymbols(captures); // Fix all invocations by passing in additional arguments. foreach (var invocation in invocations) { syntaxEditor.ReplaceNode( invocation, (node, generator) => { var currentInvocation = (InvocationExpressionSyntax)node; var seenNamedArgument = currentInvocation.ArgumentList.Arguments.Any(a => a.NameColon != null); var seenDefaultArgumentValue = currentInvocation.ArgumentList.Arguments.Count < localFunction.ParameterList.Parameters.Count; var newArguments = parameterAndCapturedSymbols.Select( p => (ArgumentSyntax)generator.Argument( seenNamedArgument || seenDefaultArgumentValue ? p.symbol.Name : null, p.symbol.RefKind, p.capture.Name.ToIdentifierName())); var newArgList = currentInvocation.ArgumentList.WithArguments(currentInvocation.ArgumentList.Arguments.AddRange(newArguments)); return(currentInvocation.WithArgumentList(newArgList)); }); } // In case any of the captured variable isn't camel-cased, // we need to change the referenced name inside local function to use the new parameter's name. foreach (var(parameter, capture) in parameterAndCapturedSymbols) { if (parameter.Name == capture.Name) { continue; } var referencedCaptureSymbols = await SymbolFinder.FindReferencesAsync( capture, document.Project.Solution, documentImmutableSet, cancellationToken).ConfigureAwait(false); foreach (var referencedSymbol in referencedCaptureSymbols) { foreach (var location in referencedSymbol.Locations) { var referenceSpan = location.Location.SourceSpan; if (!localFunction.FullSpan.Contains(referenceSpan)) { continue; } var referenceNode = root.FindNode(referenceSpan); if (referenceNode is IdentifierNameSyntax identifierNode) { syntaxEditor.ReplaceNode( identifierNode, (node, generator) => generator.IdentifierName(parameter.Name.ToIdentifierToken()).WithTriviaFrom(node)); } } } } var codeGenerator = document.GetRequiredLanguageService <ICodeGenerationService>(); var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); var info = options.GetInfo(CodeGenerationContext.Default, document.Project); // Updates the local function declaration with variables passed in as parameters syntaxEditor.ReplaceNode( localFunction, (node, generator) => { var localFunctionWithNewParameters = codeGenerator.AddParameters( node, parameterAndCapturedSymbols.SelectAsArray(p => p.symbol), info, cancellationToken); if (shouldWarn) { var annotation = WarningAnnotation.Create(CSharpCodeFixesResources.Warning_colon_Adding_parameters_to_local_function_declaration_may_produce_invalid_code); localFunctionWithNewParameters = localFunctionWithNewParameters.WithAdditionalAnnotations(annotation); } return(AddStaticModifier(localFunctionWithNewParameters, CSharpSyntaxGenerator.Instance)); }); }
public abstract Task <ImmutableArray <SyntaxNode> > GetReplacementMembersAsync( Document document, IPropertySymbol property, SyntaxNode propertyDeclaration, IFieldSymbol propertyBackingField, string desiredGetMethodName, string desiredSetMethodName, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken);
public static async Task <(Document containingDocument, SyntaxAnnotation typeAnnotation)> AddTypeToExistingFileAsync(Document document, INamedTypeSymbol newType, AnnotatedSymbolMapping symbolMapping, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { var originalRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var typeDeclaration = originalRoot.GetAnnotatedNodes(symbolMapping.TypeNodeAnnotation).Single(); var editor = new SyntaxEditor(originalRoot, symbolMapping.AnnotatedSolution.Workspace.Services); var context = new CodeGenerationContext(generateMethodBodies: true); var options = await document.GetCodeGenerationOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); var info = options.GetInfo(context, document.Project); var codeGenService = document.GetRequiredLanguageService <ICodeGenerationService>(); var newTypeNode = codeGenService.CreateNamedTypeDeclaration(newType, CodeGenerationDestination.Unspecified, info, cancellationToken) .WithAdditionalAnnotations(SimplificationHelpers.SimplifyModuleNameAnnotation); var typeAnnotation = new SyntaxAnnotation(); newTypeNode = newTypeNode.WithAdditionalAnnotations(typeAnnotation); editor.InsertBefore(typeDeclaration, newTypeNode); var newDocument = document.WithSyntaxRoot(editor.GetChangedRoot()); return(newDocument, typeAnnotation); }
/// <summary> /// Creates new code actions for each introduce parameter possibility. /// Does not create actions for overloads/trampoline if there are optional parameters or if the methodSymbol /// is a constructor. /// </summary> private async Task <(ImmutableArray <CodeAction> actions, ImmutableArray <CodeAction> actionsAllOccurrences)?> GetActionsAsync(Document document, TExpressionSyntax expression, IMethodSymbol methodSymbol, SyntaxNode containingMethod, CodeGenerationOptionsProvider fallbackOptions, CancellationToken cancellationToken) { var(shouldDisplay, containsClassExpression) = await ShouldExpressionDisplayCodeActionAsync( document, expression, cancellationToken).ConfigureAwait(false); if (!shouldDisplay) { return(null); } using var actionsBuilder = TemporaryArray <CodeAction> .Empty; using var actionsBuilderAllOccurrences = TemporaryArray <CodeAction> .Empty; var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>(); if (!containsClassExpression) { actionsBuilder.Add(CreateNewCodeAction(FeaturesResources.and_update_call_sites_directly, allOccurrences: false, IntroduceParameterCodeActionKind.Refactor)); actionsBuilderAllOccurrences.Add(CreateNewCodeAction(FeaturesResources.and_update_call_sites_directly, allOccurrences: true, IntroduceParameterCodeActionKind.Refactor)); } if (methodSymbol.MethodKind is not MethodKind.Constructor) { actionsBuilder.Add(CreateNewCodeAction( FeaturesResources.into_extracted_method_to_invoke_at_call_sites, allOccurrences: false, IntroduceParameterCodeActionKind.Trampoline)); actionsBuilderAllOccurrences.Add(CreateNewCodeAction( FeaturesResources.into_extracted_method_to_invoke_at_call_sites, allOccurrences: true, IntroduceParameterCodeActionKind.Trampoline)); if (methodSymbol.MethodKind is not MethodKind.LocalFunction) { actionsBuilder.Add(CreateNewCodeAction( FeaturesResources.into_new_overload, allOccurrences: false, IntroduceParameterCodeActionKind.Overload)); actionsBuilderAllOccurrences.Add(CreateNewCodeAction( FeaturesResources.into_new_overload, allOccurrences: true, IntroduceParameterCodeActionKind.Overload)); } } return(actionsBuilder.ToImmutableAndClear(), actionsBuilderAllOccurrences.ToImmutableAndClear()); // Local function to create a code action with more ease CodeAction CreateNewCodeAction(string actionName, bool allOccurrences, IntroduceParameterCodeActionKind selectedCodeAction) { return(CodeAction.Create( actionName, c => IntroduceParameterAsync(document, expression, methodSymbol, containingMethod, allOccurrences, selectedCodeAction, fallbackOptions, c), actionName)); } }