Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ambiguous type codefix provider (using alias) #24022

Merged
merged 27 commits into from
Mar 13, 2018
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6ba1ac5
Initial commit.
MaStr11 Dec 29, 2017
0f8fd4d
Use IAddImportsService.
MaStr11 Dec 30, 2017
3e6b04e
Failed Use IAddImportFeatureService.
MaStr11 Dec 30, 2017
f1833f8
Revert "Failed Use IAddImportFeatureService."
MaStr11 Dec 30, 2017
a2f8c45
Added support for VB BC30561
MaStr11 Dec 30, 2017
5a3c419
More VB tests and some alignments between C#and VB.
MaStr11 Jan 1, 2018
4d569f3
Small changes.
MaStr11 Jan 1, 2018
2491170
Better Title for CodeAction.
MaStr11 Jan 1, 2018
19001be
Add FeatureRessources text template.
MaStr11 Jan 1, 2018
63d1741
Reverted unwanted launchSettings.json commit.
MaStr11 Jan 2, 2018
b7d2de0
Delayed some initializations.
MaStr11 Jan 2, 2018
b9a1c96
Some re-factorings and more C# tests.
MaStr11 Jan 3, 2018
f5b3377
Spelling corrections and improved naming clarity.
MaStr11 Jan 3, 2018
737143e
Removed useless comment.
MaStr11 Jan 3, 2018
c79b26c
Disable FixAll and RS1016.
MaStr11 Jan 3, 2018
527d2de
Renamed some files/classes to be in line with the naming conventions …
MaStr11 Jan 4, 2018
2d0a0aa
Added AliasImportDeclaration to SyntaxGenerator.
MaStr11 Jan 4, 2018
b0ebe21
Delayed call to addImportService to CodeAction.
MaStr11 Jan 4, 2018
ef756c8
Reverted changes to use GroupingCodeAction again.
MaStr11 Jan 4, 2018
b9e5918
Changed folders and namespaces to match naming convention.
MaStr11 Jan 5, 2018
cc862d0
Naming corrections.
MaStr11 Jan 6, 2018
ae7bdbf
Title for the context menu is predicted by simplifying the generated …
MaStr11 Jan 7, 2018
b752f84
Simplified generation of CodeAction title.
MaStr11 Jan 8, 2018
b631e91
Added test for code fix title.
MaStr11 Jan 8, 2018
5577f1a
Formatting and small re-factorings (PR feedback).
MaStr11 Jan 17, 2018
a470e46
Added SyntaxGenerator.AliasImportDeclaration overload that takes a Sy…
MaStr11 Mar 1, 2018
0d7c640
Merge remote-tracking branch 'dotnet/master' into AmbiguousTypeCodefi…
sharwell Mar 8, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/EditorFeatures/TestUtilities/Traits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public static class Features
public const string CodeActionsAddImport = "CodeActions.AddImport";
public const string CodeActionsAddMissingReference = "CodeActions.AddMissingReference";
public const string CodeActionsAddParameter = "CodeActions.AddParameter";
public const string CodeActionsAliasAmbiguousType = "CodeActions.AliasAmbiguousType";
public const string CodeActionsChangeToAsync = "CodeActions.ChangeToAsync";
public const string CodeActionsChangeToIEnumerable = "CodeActions.ChangeToIEnumerable";
public const string CodeActionsChangeToYield = "CodeActions.ChangeToYield";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

Imports System.Collections.Immutable
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.VisualBasic.AliasAmbiguousType

Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.AliasAmbiguousType
Public Class AliasAmbiguousTypeTests
Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest

Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider)
Return (Nothing, New VisualBasicAliasAmbiguousTypeCodeFixProvider())
End Function

Protected Overrides Function MassageActions(actions As ImmutableArray(Of CodeAction)) As ImmutableArray(Of CodeAction)
Return FlattenActions(actions)
End Function

Private Function GetAmbiguousDefinition(ByVal typeDefinion As String) As String
Return $"
Namespace N1
{typeDefinion}
End Namespace
Namespace N2
{typeDefinion}
End Namespace"
End Function

<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)>
Public Async Function TestAmbiguousClassObjectCreationGlobalImports() As Task
Dim classDef = GetAmbiguousDefinition("
Public Class Ambiguous
End Class")
Dim initialMarkup = "
Imports N1
Imports N2
" & classDef & "

Namespace N3
Class C
Private Sub M()
Dim a = New [|Ambiguous|]()
End Sub
End Class
End Namespace"
Dim expectedMarkupTemplate = "
Imports N1
Imports N2
{0}
" & classDef & "

Namespace N3
Class C
Private Sub M()
Dim a = New Ambiguous()
End Sub
End Class
End Namespace"
Await TestInRegularAndScriptAsync(initialMarkup, String.Format(expectedMarkupTemplate, "Imports Ambiguous = N1.Ambiguous"), index:=0)
Await TestInRegularAndScriptAsync(initialMarkup, String.Format(expectedMarkupTemplate, "Imports Ambiguous = N2.Ambiguous"), index:=1)
Await TestSmartTagTextAsync(initialMarkup, "Imports Ambiguous = N1.Ambiguous", index:=0)
End Function

<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)>
Public Async Function TestAmbiguousAttribute() As Task
Dim classDef = GetAmbiguousDefinition("
Class AAttribute
Inherits System.Attribute
End Class
")
Dim initialMarkup = "
Imports N1
Imports N2
" & classDef & "

<[|A|]()>
Class C
End Class"
Dim expectedMarkupTemplate = "
Imports N1
Imports N2
Imports AAttribute = N1.AAttribute
" & classDef & "

<[|A|]()>
Class C
End Class"
Await TestInRegularAndScriptAsync(initialMarkup, expectedMarkupTemplate)
End Function

<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)>
Public Async Function TestAmbiguousBug4817() As Task
Dim initialMarkup = "
Imports A
Imports B
Class A
Shared Sub Goo()
End Sub
End Class
Class B
Inherits A
Overloads Shared Sub Goo(x As Integer)
End Sub
End Class
Module C
Sub Main()
[|Goo|]()
End Sub
End Module
"
Await TestMissingAsync(initialMarkup)
End Function

<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)>
Public Async Function TestAmbiguousClassInModule() As Task
Dim initialMarkup = "
Imports N1, N2
Namespace N1
Module K
Class Goo
End Class
End Module
End Namespace
Namespace N2
Module L
Class Goo
End Class
End Module
End Namespace
Class A
Public d As [|Goo|]
End Class
"
Dim expectedMarkup = "
Imports N1, N2
Imports Goo = N1.Goo

Namespace N1
Module K
Class Goo
End Class
End Module
End Namespace
Namespace N2
Module L
Class Goo
End Class
End Module
End Namespace
Class A
Public d As Goo
End Class
"
Await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup)
End Function

<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)>
Public Async Function TestAmbiguousInterfaceNameReferencedInSmallCaps() As Task
Dim initialMarkup = "
Imports N1, N2
Namespace N1
Interface I1
End Interface
End Namespace
Namespace N2
Interface I1
End Interface
End Namespace
Public Class Cls2
Implements [|i1|]
End Class
"
Dim expectedMarkup = "
Imports N1, N2
Imports I1 = N1.I1

Namespace N1
Interface I1
End Interface
End Namespace
Namespace N2
Interface I1
End Interface
End Namespace
Public Class Cls2
Implements i1
End Class
"
Await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup)
End Function
End Class
End Namespace

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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.Composition;
using Microsoft.CodeAnalysis.AliasAmbiguousType;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.CSharp.AliasAmbiguousType
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.AliasAmbiguousType), Shared]
[ExtensionOrder(After = PredefinedCodeFixProviderNames.FullyQualify)]
internal class CSharpAliasAmbiguousTypeCodeFixProvider : AbstractAliasAmbiguousTypeCodeFixProvider
{
/// <summary>
/// 'reference' is an ambiguous reference between 'identifier' and 'identifier'
/// </summary>
private const string CS0104 = nameof(CS0104);

public override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(CS0104);

protected override string GetTextPreviewOfChange(string alias, ITypeSymbol typeSymbol)
=> $"using { alias } = { typeSymbol.ToNameDisplayString() };";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImports;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using static Microsoft.CodeAnalysis.CodeActions.CodeAction;

namespace Microsoft.CodeAnalysis.AliasAmbiguousType
{
internal abstract class AbstractAliasAmbiguousTypeCodeFixProvider : CodeFixProvider
{

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra new line.

protected abstract string GetTextPreviewOfChange(string aliasName, ITypeSymbol typeSymbol);

public override FixAllProvider GetFixAllProvider() => null;

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var cancellationToken = context.CancellationToken;
var document = context.Document;
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

// Innermost: We are looking for an IdentifierName. IdentifierName is sometimes at the same span as its parent (e.g. SimpleBaseTypeSyntax).
var diagnosticNode = root.FindNode(context.Span, getInnermostNodeForTie: true);
if (!syntaxFacts.IsIdentifierName(diagnosticNode))
{
return;
}

var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var symbolInfo = semanticModel.GetSymbolInfo(diagnosticNode, cancellationToken);
if (SymbolCandidatesContainsSupportedSymbols(symbolInfo))
{
var addImportService = document.GetLanguageService<IAddImportsService>();
var syntaxGenerator = document.GetLanguageService<SyntaxGenerator>();
var compilation = semanticModel.Compilation;
var optionSet = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var placeSystemNamespaceFirst = optionSet.GetOption(GenerationOptions.PlaceSystemNamespaceFirst, document.Project.Language);
var codeActionsBuilder = ImmutableArray.CreateBuilder<CodeAction>(symbolInfo.CandidateSymbols.Length);
foreach (var symbol in symbolInfo.CandidateSymbols.Cast<ITypeSymbol>())
{
var typeName = symbol.Name;
var codeActionPreviewText = GetTextPreviewOfChange(typeName, symbol);
Task<Document> CreateChangedDocument(CancellationToken c)
{
var aliasDirective = syntaxGenerator.AliasImportDeclaration(typeName, symbol);
var newRoot = addImportService.AddImport(compilation, root, diagnosticNode, aliasDirective, placeSystemNamespaceFirst);
return Task.FromResult(document.WithSyntaxRoot(newRoot));
};
codeActionsBuilder.Add(new MyCodeAction(codeActionPreviewText, CreateChangedDocument));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: our pattern is to generally use a lambda here. but it's not a big deal. if you prefer the above form, then keep it.

}
var groupingTitle = string.Format(FeaturesResources.Alias_ambiguous_type_0, diagnosticNode.ToString());
var groupingCodeAction = new GroupingCodeAction(groupingTitle, codeActionsBuilder.ToImmutable());
context.RegisterCodeFix(groupingCodeAction, context.Diagnostics.First());
}
}

private static bool SymbolCandidatesContainsSupportedSymbols(SymbolInfo symbolInfo)
=> symbolInfo.CandidateReason == CandidateReason.Ambiguous &&
// Arity: Aliases can only name closed constructed types. (See also proposal https://github.com/dotnet/csharplang/issues/1239)
// Aliasing as a closed constructed type is possible but would require to remove the type arguments from the diagnosed node.
// It is unlikely that the user wants that and so generic types are not supported.
symbolInfo.CandidateSymbols.All(symbol => symbol.IsKind(SymbolKind.NamedType) &&
symbol.GetArity() == 0);

private class MyCodeAction : DocumentChangeAction
{
public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument) :
base(title, createChangedDocument, equivalenceKey: title)
{
}
}

private class GroupingCodeAction : CodeActionWithNestedActions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for this type. CodeActionWithNestedActions can just be instanted directly. (however, keep MyCodeAction, that specific subclass is used for telemetry purposes).

{
public GroupingCodeAction(string title, ImmutableArray<CodeAction> nestedActions)
: base(title, nestedActions, isInlinable: true)
{
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ internal static class PredefinedCodeFixProviderNames
public const string AddAwait = nameof(AddAwait);
public const string AddAsync = nameof(AddAsync);
public const string AddParameter = nameof(AddParameter);
public const string AliasAmbiguousType = nameof(AliasAmbiguousType);
public const string ApplyNamingStyle = nameof(ApplyNamingStyle);
public const string AddBraces = nameof(AddBraces);
public const string ChangeReturnType = nameof(ChangeReturnType);
Expand Down
13 changes: 12 additions & 1 deletion src/Features/Core/Portable/FeaturesResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/Features/Core/Portable/FeaturesResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1334,4 +1334,7 @@ This version used in: {2}</value>
<data name="indexer_" xml:space="preserve">
<value>indexer</value>
</data>
</root>
<data name="Alias_ambiguous_type_0" xml:space="preserve">
<value>Alias ambiguous type '{0}'</value>
</data>
</root>
5 changes: 5 additions & 0 deletions src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,11 @@ Tato verze se používá zde: {2}.</target>
<target state="translated">indexer</target>
<note />
</trans-unit>
<trans-unit id="Alias_ambiguous_type_0">
<source>Alias ambiguous type '{0}'</source>
<target state="new">Alias ambiguous type '{0}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
5 changes: 5 additions & 0 deletions src/Features/Core/Portable/xlf/FeaturesResources.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,11 @@ Diese Version wird verwendet in: {2}</target>
<target state="translated">Indexer</target>
<note />
</trans-unit>
<trans-unit id="Alias_ambiguous_type_0">
<source>Alias ambiguous type '{0}'</source>
<target state="new">Alias ambiguous type '{0}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
5 changes: 5 additions & 0 deletions src/Features/Core/Portable/xlf/FeaturesResources.es.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,11 @@ Esta versión se utiliza en: {2}</target>
<target state="translated">indizador</target>
<note />
</trans-unit>
<trans-unit id="Alias_ambiguous_type_0">
<source>Alias ambiguous type '{0}'</source>
<target state="new">Alias ambiguous type '{0}'</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
Loading