AbstractAddParameterCodeFixProvider.cs 33.2 KB
Newer Older
C
CyrusNajmabadi 已提交
1 2 3 4
// 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;
C
CyrusNajmabadi 已提交
5
using System.Collections.Immutable;
C
CyrusNajmabadi 已提交
6
using System.Linq;
7
using System.Threading;
C
CyrusNajmabadi 已提交
8
using System.Threading.Tasks;
9
using Microsoft.CodeAnalysis.CodeActions;
C
CyrusNajmabadi 已提交
10
using Microsoft.CodeAnalysis.CodeFixes;
11 12
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Editing;
13
using Microsoft.CodeAnalysis.FindSymbols;
14
using Microsoft.CodeAnalysis.Formatting;
15
using Microsoft.CodeAnalysis.LanguageServices;
T
Tomas Matousek 已提交
16
using Microsoft.CodeAnalysis.PooledObjects;
17 18 19
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
C
CyrusNajmabadi 已提交
20 21 22

namespace Microsoft.CodeAnalysis.AddParameter
{
23
#pragma warning disable RS1016 // Code fix providers should provide FixAll support. https://github.com/dotnet/roslyn/issues/23528
24
    internal abstract class AbstractAddParameterCodeFixProvider<
25
#pragma warning restore RS1016 // Code fix providers should provide FixAll support.
26 27 28 29 30
        TArgumentSyntax,
        TAttributeArgumentSyntax,
        TArgumentListSyntax,
        TAttributeArgumentListSyntax,
        TInvocationExpressionSyntax,
31
        TObjectCreationExpressionSyntax> : CodeFixProvider
32 33 34 35 36
        where TArgumentSyntax : SyntaxNode
        where TArgumentListSyntax : SyntaxNode
        where TAttributeArgumentListSyntax : SyntaxNode
        where TInvocationExpressionSyntax : SyntaxNode
        where TObjectCreationExpressionSyntax : SyntaxNode
C
CyrusNajmabadi 已提交
37
    {
C
CyrusNajmabadi 已提交
38
        protected abstract ImmutableArray<string> TooManyArgumentsDiagnosticIds { get; }
39
        protected abstract ImmutableArray<string> CannotConvertDiagnosticIds { get; }
C
CyrusNajmabadi 已提交
40

41 42 43
        public override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var cancellationToken = context.CancellationToken;
C
CyrusNajmabadi 已提交
44
            var diagnostic = context.Diagnostics.First();
45 46 47 48

            var document = context.Document;
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

C
CyrusNajmabadi 已提交
49
            var initialNode = root.FindNode(diagnostic.Location.SourceSpan);
50 51

            for (var node = initialNode; node != null; node = node.Parent)
52 53 54
            {
                if (node is TObjectCreationExpressionSyntax objectCreation)
                {
C
CyrusNajmabadi 已提交
55
                    var argumentOpt = TryGetRelevantArgument(initialNode, node, diagnostic);
56
                    await HandleObjectCreationExpressionAsync(context, objectCreation, argumentOpt).ConfigureAwait(false);
57 58 59 60
                    return;
                }
                else if (node is TInvocationExpressionSyntax invocationExpression)
                {
C
CyrusNajmabadi 已提交
61
                    var argumentOpt = TryGetRelevantArgument(initialNode, node, diagnostic);
62
                    await HandleInvocationExpressionAsync(context, invocationExpression, argumentOpt).ConfigureAwait(false);
63 64 65 66 67
                    return;
                }
            }
        }

C
CyrusNajmabadi 已提交
68 69
        private TArgumentSyntax TryGetRelevantArgument(
            SyntaxNode initialNode, SyntaxNode node, Diagnostic diagnostic)
70
        {
C
CyrusNajmabadi 已提交
71 72 73 74 75
            if (this.TooManyArgumentsDiagnosticIds.Contains(diagnostic.Id))
            {
                return null;
            }

76 77 78 79 80
            if (this.CannotConvertDiagnosticIds.Contains(diagnostic.Id))
            {
                return null;
            }

81
            return initialNode.GetAncestorsOrThis<TArgumentSyntax>()
82
                              .LastOrDefault(a => a.AncestorsAndSelf().Contains(node));
83 84
        }

85
        private async Task HandleInvocationExpressionAsync(
86
            CodeFixContext context, TInvocationExpressionSyntax invocationExpression, TArgumentSyntax argumentOpt)
87
        {
88 89 90 91 92 93
            var document = context.Document;
            var cancellationToken = context.CancellationToken;
            var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();

            var expression = syntaxFacts.GetExpressionOfInvocationExpression(invocationExpression);
94

95 96
            var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken);
            var candidates = symbolInfo.CandidateSymbols.OfType<IMethodSymbol>().ToImmutableArray();
97

98
            var arguments = (SeparatedSyntaxList<TArgumentSyntax>)syntaxFacts.GetArgumentsOfInvocationExpression(invocationExpression);
M
Martin Strecker 已提交
99 100
            var argumentInsertPositionInMethodCandidates = GetArgumentInsertPositionForMethodCandidates(
                argumentOpt, semanticModel, syntaxFacts, arguments, candidates);
101
            RegisterFixForMethodOverloads(context, arguments, argumentInsertPositionInMethodCandidates);
102 103 104 105
        }

        private async Task HandleObjectCreationExpressionAsync(
            CodeFixContext context,
106 107
            TObjectCreationExpressionSyntax objectCreation,
            TArgumentSyntax argumentOpt)
108 109 110 111 112 113
        {
            var document = context.Document;
            var cancellationToken = context.CancellationToken;
            var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();

C
CyrusNajmabadi 已提交
114
            // Not supported if this is "new { ... }" (as there are no parameters at all.
115 116 117 118 119 120
            var typeNode = syntaxFacts.GetObjectCreationType(objectCreation);
            if (typeNode == null)
            {
                return;
            }

C
CyrusNajmabadi 已提交
121 122
            // If we can't figure out the type being created, or the type isn't in source,
            // then there's nothing we can do.
123
            var type = semanticModel.GetSymbolInfo(typeNode, cancellationToken).GetAnySymbol() as INamedTypeSymbol;
C
CyrusNajmabadi 已提交
124 125 126 127 128 129
            if (type == null)
            {
                return;
            }

            if (!type.IsNonImplicitAndFromSource())
130 131 132 133 134
            {
                return;
            }

            var arguments = (SeparatedSyntaxList<TArgumentSyntax>)syntaxFacts.GetArgumentsOfObjectCreationExpression(objectCreation);
135
            var methodCandidates = type.InstanceConstructors;
136

M
Martin Strecker 已提交
137
            var insertionData = GetArgumentInsertPositionForMethodCandidates(argumentOpt, semanticModel, syntaxFacts, arguments, methodCandidates);
138

M
Martin Strecker 已提交
139
            RegisterFixForMethodOverloads(context, arguments, insertionData);
140 141
        }

M
Martin Strecker 已提交
142 143 144 145
        private void RegisterFixForMethodOverloads(
            CodeFixContext context,
            SeparatedSyntaxList<TArgumentSyntax> arguments,
            ImmutableArray<ArgumentInsertPositionData<TArgumentSyntax>> methodsAndArgumentsToAdd)
146 147 148 149
        {
            // Order by the furthest argument index to the nearest argument index.  The ones with
            // larger argument indexes mean that we matched more earlier arguments (and thus are
            // likely to be the correct match).
M
Martin Strecker 已提交
150
            foreach (var argumentInsertPositionData in methodsAndArgumentsToAdd.OrderByDescending(t => t.ArgumentInsertionIndex))
151
            {
M
Martin Strecker 已提交
152 153 154
                var methodToUpdate = argumentInsertPositionData.MethodToUpdate;
                var argumentToInsert = argumentInsertPositionData.ArgumentToInsert;
                var parameters = methodToUpdate.Parameters.Select(p => p.ToDisplayString(SimpleFormat));
155 156
                var title = GetCodeFixTitle(methodToUpdate, parameters);
                var hasCascadingDeclarations = HasCascadingDeclarations(methodToUpdate);
157

158 159 160 161
                if (hasCascadingDeclarations)
                {
                    // TODO Localization of titles
                    context.RegisterCodeFix(new GroupingCodeAction(title,
162
                        new MyCodeAction(title,
163
                            c => FixAsync(context.Document, methodToUpdate, argumentToInsert, arguments, fixAllReferences: false, c)),
164
                        new MyCodeAction(title + " (including overrides/implementations)",
165 166 167 168 169 170 171 172 173 174
                            c => FixAsync(context.Document, methodToUpdate, argumentToInsert, arguments, fixAllReferences: true, c))),
                        context.Diagnostics);
                }
                else
                {
                    context.RegisterCodeFix(
                        new MyCodeAction(title,
                            c => FixAsync(context.Document, methodToUpdate, argumentToInsert, arguments, fixAllReferences: false, c)),
                        context.Diagnostics);
                }
175 176 177
            }
        }

178
        /// <summary>
179
        /// Checks if there are indications that there might be more than one declarations that need to be fixed.
180 181 182 183
        /// The check does not look-up if there are other declarations (this is done later in the CodeAction).
        /// </summary>
        private bool HasCascadingDeclarations(IMethodSymbol method)
        {
184 185 186 187 188 189
            // Don't cascade constructors
            if (method.IsConstructor())
            {
                return false;
            }

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
            // Virtual methods of all kinds might have overrides somewhere else that need to be fixed.
            if (method.IsVirtual || method.IsOverride || method.IsAbstract)
            {
                return true;
            }

            // If interfaces are involved we will fix those too
            // Explicit interface implementations are easy
            if (method.ExplicitInterfaceImplementations.Length > 0)
            {
                return true;
            }

            // For implicit interface implementations lets check if the characteristic of the method
            // allows it to implicit implement an interface member.
            if (method.DeclaredAccessibility == Accessibility.Private || method.DeclaredAccessibility == Accessibility.NotApplicable)
            {
                return false;
            }

            if (method.IsStatic)
            {
                return false;
            }

            // Now check if the method does implement an interface member
216 217 218 219 220 221
            if (method.ExplicitOrImplicitInterfaceImplementations().Length > 0)
            {
                return true;
            }

            return false;
222 223 224 225 226 227 228 229 230
        }

        private static string GetCodeFixTitle(IMethodSymbol methodToUpdate, IEnumerable<string> parameters)
        {
            var signature = $"{methodToUpdate.Name}({string.Join(", ", parameters)})";
            var title = string.Format(FeaturesResources.Add_parameter_to_0, signature);
            return title;
        }

M
Martin Strecker 已提交
231 232 233 234 235 236
        private ImmutableArray<ArgumentInsertPositionData<TArgumentSyntax>> GetArgumentInsertPositionForMethodCandidates(
            TArgumentSyntax argumentOpt,
            SemanticModel semanticModel,
            ISyntaxFactsService syntaxFacts,
            SeparatedSyntaxList<TArgumentSyntax> arguments,
            ImmutableArray<IMethodSymbol> methodCandidates)
237
        {
238
            var comparer = syntaxFacts.StringComparer;
M
Martin Strecker 已提交
239
            var methodsAndArgumentToAdd = ArrayBuilder<ArgumentInsertPositionData<TArgumentSyntax>>.GetInstance();
C
CyrusNajmabadi 已提交
240

241
            foreach (var method in methodCandidates.OrderBy(m => m.Parameters.Length))
242
            {
243
                if (method.IsNonImplicitAndFromSource())
244
                {
245
                    var isNamedArgument = !string.IsNullOrWhiteSpace(syntaxFacts.GetNameForArgument(argumentOpt));
246

247
                    if (isNamedArgument || NonParamsParameterCount(method) < arguments.Count)
248
                    {
249 250 251 252 253 254 255 256 257 258 259
                        var argumentToAdd = DetermineFirstArgumentToAdd(
                        semanticModel, syntaxFacts, comparer, method,
                        arguments, argumentOpt);

                        if (argumentToAdd != null)
                        {
                            if (argumentOpt != null && argumentToAdd != argumentOpt)
                            {
                                // We were trying to fix a specific argument, but the argument we want
                                // to fix is something different.  That means there was an error earlier
                                // than this argument.  Which means we're looking at a non-viable 
M
Martin Strecker 已提交
260
                                // constructor or method.  Skip this one.
261 262 263
                                continue;
                            }

M
Martin Strecker 已提交
264 265
                            methodsAndArgumentToAdd.Add(new ArgumentInsertPositionData<TArgumentSyntax>(
                                method, argumentToAdd, arguments.IndexOf(argumentToAdd)));
266
                        }
267 268 269
                    }
                }
            }
C
CyrusNajmabadi 已提交
270

M
Martin Strecker 已提交
271
            return methodsAndArgumentToAdd.ToImmutableAndFree();
272 273
        }

274 275 276
        private int NonParamsParameterCount(IMethodSymbol method)
            => method.IsParams() ? method.Parameters.Length - 1 : method.Parameters.Length;

277
        private async Task<Solution> FixAsync(
H
Heejae Chang 已提交
278
            Document invocationDocument,
279 280 281
            IMethodSymbol method,
            TArgumentSyntax argument,
            SeparatedSyntaxList<TArgumentSyntax> argumentList,
282
            bool fixAllReferences,
283 284
            CancellationToken cancellationToken)
        {
285 286 287
            var solution = invocationDocument.Project.Solution;
            var argumentType = await GetArgumentTypeAsync(invocationDocument, argument, cancellationToken).ConfigureAwait(false);
            // the argumentNameSuggestion is the base for the parameter name. For each method declaration the name is made unique to avoid name collisions.
M
Martin Strecker 已提交
288
            var (argumentNameSuggestion, isNamedArgument) = await GetNameSuggestionForArgumentAsync(invocationDocument, argument, cancellationToken).ConfigureAwait(false);
289 290 291 292
            var referencedSymbols = fixAllReferences
                ? await FindMethodDeclarationReferences(invocationDocument, method, cancellationToken).ConfigureAwait(false)
                : method.GetAllMethodSymbolsOfPartialParts();
            // TODO: Insert hint in the fix with a warning if anySymbolsReferenceNotInSource is true
293 294 295
            var anySymbolReferencesNotInSource = referencedSymbols.Any(symbol => !symbol.IsFromSource());
            var locationsInSource = referencedSymbols.Where(symbol => symbol.IsFromSource());
            // Indexing Locations[0] is valid because IMethodSymbols have one location at most and IsFromSource() tests if there is at least one location.
296
            var locationsByDocument = locationsInSource.ToLookup(declarationLocation
297
                => solution.GetDocument(declarationLocation.Locations[0].SourceTree));
298 299 300 301 302
            foreach (var documentLookup in locationsByDocument)
            {
                var document = documentLookup.Key;
                var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
                var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
303
                var editor = new SyntaxEditor(syntaxRoot, solution.Workspace);
304
                var generator = editor.Generator;
305
                foreach (var methodDeclaration in documentLookup)
306
                {
307
                    var methodNode = syntaxRoot.FindNode(methodDeclaration.Locations[0].SourceSpan);
308
                    var parameterSymbol = CreateParameterSymbol(
309
                        methodDeclaration, argumentType, argumentNameSuggestion);
310

311 312
                    var parameterDeclaration = generator.ParameterDeclaration(parameterSymbol)
                                                        .WithAdditionalAnnotations(Formatter.Annotation);
313 314 315 316 317
                    if (anySymbolReferencesNotInSource && methodDeclaration == method)
                    {
                        parameterDeclaration = parameterDeclaration.WithAdditionalAnnotations(
                            ConflictAnnotation.Create("One or more method declarations that needed to be updated are not available as source code."));
                    }
318 319 320 321
                    var existingParameters = generator.GetParameters(methodNode);
                    var insertionIndex = isNamedArgument
                        ? existingParameters.Count
                        : argumentList.IndexOf(argument);
322

323 324 325 326
                    if (method.IsExtensionMethod)
                    {
                        insertionIndex++;
                    }
327

328 329 330
                    AddParameter(
                        syntaxFacts, editor, methodNode, argument,
                        insertionIndex, parameterDeclaration, cancellationToken);
C
CyrusNajmabadi 已提交
331

332 333
                }
                var newRoot = editor.GetChangedRoot();
334
                solution = solution.WithDocumentSyntaxRoot(document.Id, newRoot);
335
            }
336 337 338 339 340 341 342 343 344 345 346

            return solution;
        }

        private static async Task<ITypeSymbol> GetArgumentTypeAsync(Document invocationDocument, TArgumentSyntax argument, CancellationToken cancellationToken)
        {
            var syntaxFacts = invocationDocument.GetLanguageService<ISyntaxFactsService>();
            var semanticModel = await invocationDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var argumentExpression = syntaxFacts.GetExpressionOfArgument(argument);
            var argumentType = semanticModel.GetTypeInfo(argumentExpression).Type ?? semanticModel.Compilation.ObjectType;
            return argumentType;
347
        }
348

349 350
        private static async Task<ImmutableArray<IMethodSymbol>> FindMethodDeclarationReferences(
            Document invocationDocument, IMethodSymbol method, CancellationToken cancellationToken)
351 352 353 354 355 356 357 358 359
        {
            var progress = new StreamingProgressCollector(StreamingFindReferencesProgress.Instance);

            await SymbolFinder.FindReferencesAsync(
                symbolAndProjectId: SymbolAndProjectId.Create(method, invocationDocument.Project.Id),
                solution: invocationDocument.Project.Solution,
                documents: null,
                progress: progress,
                cancellationToken: cancellationToken).ConfigureAwait(false);
360 361
            var referencedSymbols = progress.GetReferencedSymbols();
            return referencedSymbols.Select(referencedSymbol => referencedSymbol.Definition).OfType<IMethodSymbol>().ToImmutableArray();
362
        }
363

364 365
        private async Task<(string argumentNameSuggestion, bool isNamed)> GetNameSuggestionForArgumentAsync(
            Document invocationDocument, TArgumentSyntax argument, CancellationToken cancellationToken)
366
        {
367
            var syntaxFacts = invocationDocument.GetLanguageService<ISyntaxFactsService>();
368

369
            var argumentName = syntaxFacts.GetNameForArgument(argument);
370 371
            if (!string.IsNullOrWhiteSpace(argumentName))
            {
372
                return (argumentNameSuggestion: argumentName, isNamed: true);
373 374 375
            }
            else
            {
376
                var semanticModel = await invocationDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
377 378 379
                var expression = syntaxFacts.GetExpressionOfArgument(argument);
                var semanticFacts = invocationDocument.GetLanguageService<ISemanticFactsService>();
                argumentName = semanticFacts.GenerateNameForExpression(
380
                    semanticModel, expression, capitalize: false, cancellationToken: cancellationToken);
381 382 383
                return (argumentNameSuggestion: argumentName, isNamed: false);
            }
        }
384

385 386 387 388 389 390 391
        private IParameterSymbol CreateParameterSymbol(
            IMethodSymbol method,
            ITypeSymbol parameterType,
            string argumentNameSuggestion)
        {
            var uniqueName = NameGenerator.EnsureUniqueness(argumentNameSuggestion, method.Parameters.Select(p => p.Name));
            var newParameterSymbol = CodeGenerationSymbolFactory.CreateParameterSymbol(
392
                    attributes: default, refKind: RefKind.None, isParams: false, type: parameterType, name: uniqueName);
393
            return newParameterSymbol;
394 395 396 397 398 399 400
        }

        private static void AddParameter(
            ISyntaxFactsService syntaxFacts,
            SyntaxEditor editor,
            SyntaxNode declaration,
            TArgumentSyntax argument,
C
CyrusNajmabadi 已提交
401
            int insertionIndex,
402 403 404
            SyntaxNode parameterDeclaration,
            CancellationToken cancellationToken)
        {
C
CyrusNajmabadi 已提交
405
            var sourceText = declaration.SyntaxTree.GetText(cancellationToken);
406 407 408 409 410
            var generator = editor.Generator;

            var existingParameters = generator.GetParameters(declaration);
            var placeOnNewLine = ShouldPlaceParametersOnNewLine(existingParameters, cancellationToken);

411 412 413 414 415 416 417
            if (!placeOnNewLine)
            {
                // Trivial case.  Just let the stock editor impl handle this for us.
                editor.InsertParameter(declaration, insertionIndex, parameterDeclaration);
                return;
            }

C
CyrusNajmabadi 已提交
418
            if (insertionIndex == existingParameters.Count)
419
            {
420 421 422
                // Placing the last parameter on its own line.  Get the indentation of the 
                // curent last parameter and give the new last parameter the same indentation.
                var leadingIndentation = GetDesiredLeadingIndentation(
H
Heejae Chang 已提交
423
                    generator, syntaxFacts, existingParameters[existingParameters.Count - 1], includeLeadingNewLine: true);
424 425 426
                parameterDeclaration = parameterDeclaration.WithPrependedLeadingTrivia(leadingIndentation)
                                                            .WithAdditionalAnnotations(Formatter.Annotation);

427 428
                editor.AddParameter(declaration, parameterDeclaration);
            }
429
            else if (insertionIndex == 0)
430
            {
431 432 433 434 435 436
                // Inserting into the start of the list.  The existing first parameter might
                // be on the same line as the parameter list, or it might be on the next line.
                var firstParameter = existingParameters[0];
                var previousToken = firstParameter.GetFirstToken().GetPreviousToken();

                if (sourceText.AreOnSameLine(previousToken, firstParameter.GetFirstToken()))
437
                {
438
                    // First parameter is on hte same line as the method.  
439

440 441 442 443 444 445 446 447 448 449 450 451 452
                    // We want to insert the parameter at the front of the exsiting parameter
                    // list.  That means we need to move the current first parameter to a new
                    // line.  Give the current first parameter the indentation of the second
                    // parameter in the list.
                    editor.InsertParameter(declaration, insertionIndex, parameterDeclaration);
                    var nextParameter = existingParameters[insertionIndex];

                    var nextLeadingIndentation = GetDesiredLeadingIndentation(
                        generator, syntaxFacts, existingParameters[insertionIndex + 1], includeLeadingNewLine: true);
                    editor.ReplaceNode(
                        nextParameter,
                        nextParameter.WithPrependedLeadingTrivia(nextLeadingIndentation)
                                     .WithAdditionalAnnotations(Formatter.Annotation));
453 454 455
                }
                else
                {
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
                    // First parameter is on its own line.  No need to adjust its indentation.
                    // Just copy its indentation over to the parameter we're inserting, and
                    // make sure the current first parameter gets a newline so it stays on 
                    // its own line.

                    // We want to insert the parameter at the front of the exsiting parameter
                    // list.  That means we need to move the current first parameter to a new
                    // line.  Give the current first parameter the indentation of the second
                    // parameter in the list.
                    var firstLeadingIndentation = GetDesiredLeadingIndentation(
                        generator, syntaxFacts, existingParameters[0], includeLeadingNewLine: false);

                    editor.InsertParameter(declaration, insertionIndex,
                        parameterDeclaration.WithLeadingTrivia(firstLeadingIndentation));
                    var nextParameter = existingParameters[insertionIndex];

                    editor.ReplaceNode(
                        nextParameter,
                        nextParameter.WithPrependedLeadingTrivia(generator.ElasticCarriageReturnLineFeed)
                                     .WithAdditionalAnnotations(Formatter.Annotation));
476 477
                }
            }
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
            else
            {
                // We're inserting somewhere after the start (but not at the end). Because 
                // we've set placeOnNewLine, we know that the current comma we'll be placed
                // after already have a newline following it.  So all we need for this new 
                // parameter is to get the indentation of the following parameter.
                // Because we're going to 'steal' the existing comma from that parameter,
                // ensure that the next parameter has a new-line added to it so that it will
                // still stay on a new line.
                var nextParameter = existingParameters[insertionIndex];
                var leadingIndentation = GetDesiredLeadingIndentation(
                    generator, syntaxFacts, existingParameters[insertionIndex], includeLeadingNewLine: false);
                parameterDeclaration = parameterDeclaration.WithPrependedLeadingTrivia(leadingIndentation);

                editor.InsertParameter(declaration, insertionIndex, parameterDeclaration);
                editor.ReplaceNode(
                    nextParameter,
                    nextParameter.WithPrependedLeadingTrivia(generator.ElasticCarriageReturnLineFeed)
                                 .WithAdditionalAnnotations(Formatter.Annotation));
            }
498 499 500
        }

        private static List<SyntaxTrivia> GetDesiredLeadingIndentation(
H
Heejae Chang 已提交
501
            SyntaxGenerator generator, ISyntaxFactsService syntaxFacts,
502 503 504 505 506 507 508 509
            SyntaxNode node, bool includeLeadingNewLine)
        {
            var triviaList = new List<SyntaxTrivia>();
            if (includeLeadingNewLine)
            {
                triviaList.Add(generator.ElasticCarriageReturnLineFeed);
            }

H
Heejae Chang 已提交
510 511
            var lastWhitespace = default(SyntaxTrivia);
            foreach (var trivia in node.GetLeadingTrivia().Reverse())
512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
            {
                if (syntaxFacts.IsWhitespaceTrivia(trivia))
                {
                    lastWhitespace = trivia;
                }
                else if (syntaxFacts.IsEndOfLineTrivia(trivia))
                {
                    break;
                }
            }

            if (lastWhitespace.RawKind != 0)
            {
                triviaList.Add(lastWhitespace);
            }

            return triviaList;
        }
530

531 532 533 534 535 536
        private static bool ShouldPlaceParametersOnNewLine(
            IReadOnlyList<SyntaxNode> parameters, CancellationToken cancellationToken)
        {
            if (parameters.Count <= 1)
            {
                return false;
537
            }
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552

            var text = parameters[0].SyntaxTree.GetText(cancellationToken);
            for (int i = 1, n = parameters.Count; i < n; i++)
            {
                var lastParameter = parameters[i - 1];
                var thisParameter = parameters[i];

                if (text.AreOnSameLine(lastParameter.GetLastToken(), thisParameter.GetFirstToken()))
                {
                    return false;
                }
            }

            // All parameters are on different lines.  Place the new parameter on a new line as well.
            return true;
553 554 555 556 557 558 559 560 561 562
        }

        private static readonly SymbolDisplayFormat SimpleFormat =
                    new SymbolDisplayFormat(
                        typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
                        genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
                        parameterOptions: SymbolDisplayParameterOptions.IncludeParamsRefOut | SymbolDisplayParameterOptions.IncludeType,
                        miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

        private TArgumentSyntax DetermineFirstArgumentToAdd(
563 564 565 566
            SemanticModel semanticModel,
            ISyntaxFactsService syntaxFacts,
            StringComparer comparer,
            IMethodSymbol method,
567 568
            SeparatedSyntaxList<TArgumentSyntax> arguments,
            TArgumentSyntax argumentOpt)
569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
        {
            var methodParameterNames = new HashSet<string>(comparer);
            methodParameterNames.AddRange(method.Parameters.Select(p => p.Name));

            for (int i = 0, n = arguments.Count; i < n; i++)
            {
                var argument = arguments[i];
                var argumentName = syntaxFacts.GetNameForArgument(argument);

                if (!string.IsNullOrWhiteSpace(argumentName))
                {
                    // If the user provided an argument-name and we don't have any parameters that
                    // match, then this is the argument we want to add a parameter for.
                    if (!methodParameterNames.Contains(argumentName))
                    {
                        return argument;
                    }
                }
                else
                {
                    // Positional argument.  If the position is beyond what the method supports,
                    // then this definitely is an argument we could add.
                    if (i >= method.Parameters.Length)
                    {
593 594 595 596 597 598
                        if (method.Parameters.LastOrDefault()?.IsParams == true)
                        {
                            // Last parameter is a params.  We can't place any parameters past it.
                            return null;
                        }

599 600 601
                        return argument;
                    }

C
CyrusNajmabadi 已提交
602 603
                    // Now check the type of the argument versus the type of the parameter.  If they
                    // don't match, then this is the argument we should make the parameter for.
604 605 606 607 608 609 610 611
                    var expressionOfArgument = syntaxFacts.GetExpressionOfArgument(argument);
                    if (expressionOfArgument is null)
                    {
                        return null;
                    }
                    var argumentTypeInfo = semanticModel.GetTypeInfo(expressionOfArgument);
                    var isNullLiteral = syntaxFacts.IsNullLiteralExpression(expressionOfArgument);
                    var isDefaultLiteral = syntaxFacts.IsDefaultLiteralExpression(expressionOfArgument);
C
CyrusNajmabadi 已提交
612

C
CyrusNajmabadi 已提交
613 614 615
                    if (argumentTypeInfo.Type == null && argumentTypeInfo.ConvertedType == null)
                    {
                        // Didn't know the type of the argument.  We shouldn't assume it doesn't
616 617
                        // match a parameter.  However, if the user wrote 'null' and it didn't
                        // match anything, then this is the problem argument.
C
CyrusNajmabadi 已提交
618
                        if (!isNullLiteral && !isDefaultLiteral)
619 620 621
                        {
                            continue;
                        }
C
CyrusNajmabadi 已提交
622 623
                    }

624 625
                    var parameter = method.Parameters[i];

C
CyrusNajmabadi 已提交
626
                    if (!TypeInfoMatchesType(argumentTypeInfo, parameter.Type, isNullLiteral, isDefaultLiteral))
627
                    {
C
CyrusNajmabadi 已提交
628
                        if (TypeInfoMatchesWithParamsExpansion(argumentTypeInfo, parameter, isNullLiteral, isDefaultLiteral))
629
                        {
C
CyrusNajmabadi 已提交
630 631 632 633
                            // The argument matched if we expanded out the params-parameter.
                            // As the params-parameter has to be last, there's nothing else to 
                            // do here.
                            return null;
634 635
                        }

636 637 638 639 640 641 642
                        return argument;
                    }
                }
            }

            return null;
        }
C
CyrusNajmabadi 已提交
643

C
CyrusNajmabadi 已提交
644
        private bool TypeInfoMatchesWithParamsExpansion(
645
            TypeInfo argumentTypeInfo, IParameterSymbol parameter,
C
CyrusNajmabadi 已提交
646
            bool isNullLiteral, bool isDefaultLiteral)
C
CyrusNajmabadi 已提交
647 648 649
        {
            if (parameter.IsParams && parameter.Type is IArrayTypeSymbol arrayType)
            {
C
CyrusNajmabadi 已提交
650
                if (TypeInfoMatchesType(argumentTypeInfo, arrayType.ElementType, isNullLiteral, isDefaultLiteral))
C
CyrusNajmabadi 已提交
651 652 653 654 655 656 657 658
                {
                    return true;
                }
            }

            return false;
        }

C
CyrusNajmabadi 已提交
659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
        private bool TypeInfoMatchesType(
            TypeInfo argumentTypeInfo, ITypeSymbol type,
            bool isNullLiteral, bool isDefaultLiteral)
        {
            if (type.Equals(argumentTypeInfo.Type) || type.Equals(argumentTypeInfo.ConvertedType))
            {
                return true;
            }

            if (isDefaultLiteral)
            {
                return true;
            }

            if (isNullLiteral)
            {
                return type.IsReferenceType || type.IsNullable();
            }

678 679 680 681 682
            if (type.Kind == SymbolKind.TypeParameter)
            {
                return true;
            }

C
CyrusNajmabadi 已提交
683 684
            return false;
        }
685

686
        private class MyCodeAction : CodeAction.SolutionChangeAction
C
CyrusNajmabadi 已提交
687
        {
688
            public MyCodeAction(string title, Func<CancellationToken, Task<Solution>> createChangedSolution)
689
                : base(title, createChangedSolution)
690 691
            {
            }
C
CyrusNajmabadi 已提交
692
        }
693 694 695 696 697 698 699 700

        private class GroupingCodeAction : CodeAction.CodeActionWithNestedActions
        {
            public GroupingCodeAction(string title, MyCodeAction directDeclarationOnly, MyCodeAction allDeclarations)
                : base(title, ImmutableArray.Create<CodeAction>(directDeclarationOnly, allDeclarations), isInlinable: true)
            {
            }
        }
C
CyrusNajmabadi 已提交
701
    }
T
Tomas Matousek 已提交
702
}