-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Changes from 24 commits
6ba1ac5
0f8fd4d
3e6b04e
f1833f8
a2f8c45
5a3c419
4d569f3
2491170
19001be
63d1741
b7d2de0
b9a1c96
f5b3377
737143e
c79b26c
527d2de
2d0a0aa
b0ebe21
ef756c8
b9e5918
cc862d0
ae7bdbf
b752f84
b631e91
5577f1a
a470e46
0d7c640
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
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 | ||
{ | ||
|
||
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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
{ | ||
} | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra new line.