diff --git a/src/EditorFeatures/CSharpTest/AliasAmbiguousType/AliasAmbiguousTypeTests.cs b/src/EditorFeatures/CSharpTest/AliasAmbiguousType/AliasAmbiguousTypeTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..28a5728fa032f09a27da470b706fcbe2aa88c044 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/AliasAmbiguousType/AliasAmbiguousTypeTests.cs @@ -0,0 +1,568 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.AliasAmbiguousType; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AliasAmbiguousType +{ + public class AliasAmbiguousTypeTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + { + internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) + => (null, new CSharpAliasAmbiguousTypeCodeFixProvider()); + + protected override ImmutableArray MassageActions(ImmutableArray actions) + => FlattenActions(actions); + + private string GetAmbiguousDefinition(string typeDefinion) + => $@" +namespace N1 +{{ + { typeDefinion } +}} +namespace N2 +{{ + { typeDefinion } +}}"; + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousClassObjectCreationUsingsInNamespace() + { + var classDef = GetAmbiguousDefinition("public class Ambiguous { }"); + var initialMarkup = classDef + @" +namespace Test +{ + using N1; + using N2; + class C + { + void M() + { + var a = new [|Ambiguous|](); + } + } +}"; + var expectedMarkup0 = classDef + @" +namespace Test +{ + using N1; + using N2; + using Ambiguous = N1.Ambiguous; + + class C + { + void M() + { + var a = new Ambiguous(); + } + } +}"; + var expectedMarkup1 = classDef + @" +namespace Test +{ + using N1; + using N2; + using Ambiguous = N2.Ambiguous; + + class C + { + void M() + { + var a = new Ambiguous(); + } + } +}"; + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup0, index: 0); + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup1, index: 1); + await TestSmartTagTextAsync(initialMarkup, "using Ambiguous = N1.Ambiguous;", index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousClassObjectCreationUsingsInCompilationUnit() + { + var classDef = GetAmbiguousDefinition("public class Ambiguous { }"); + await TestInRegularAndScriptAsync(@" +using N1; +using N2; +" + classDef + @" +namespace Test +{ + class C + { + void M() + { + var a = new [|Ambiguous|](); + } + } +}", @" +using N1; +using N2; +using Ambiguous = N1.Ambiguous; +" + classDef + @" +namespace Test +{ + class C + { + void M() + { + var a = new Ambiguous(); + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousClassObjectCreationGenericsDontOfferDiagnostic() + { + var genericAmbiguousClassDefinition = GetAmbiguousDefinition("public class Ambiguous { }"); + await TestMissingAsync(@" +using N1; +using N2; +" + genericAmbiguousClassDefinition + @" +namespace Test +{ + class C + { + void M() + { + var a = new [|Ambiguous|](); + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousAttribute() + { + var classDef = GetAmbiguousDefinition("public class AmbiguousAttribute: System.Attribute { }"); + await TestInRegularAndScriptAsync(@" +using N1; +using N2; +" + classDef + @" +namespace Test +{ + [[|Ambiguous|]] + class C + { + } +}", @" +using N1; +using N2; +using AmbiguousAttribute = N1.AmbiguousAttribute; +" + classDef + @" +namespace Test +{ + [Ambiguous] + class C + { + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestNamespaceAndTypenameIdenticalOffersNoDiagnostics() + { + // This gives CS0433: The type 'Ambiguous' exists in both 'Assembly1' and 'Assembly2' + // Couldn't get a CS0104 in this situation. Keep the test anyway if someone finds a way to force CS0104 here + // or CS0433 is added as a supported diagnostic for this fix. + await TestMissingAsync(@" + + + +namespace N +{ + public class Ambiguous { } +} + + + + +namespace N +{ + public class Ambiguous { } +} + + + + Assembly1 + Assembly2 + +extern alias A1; +extern alias A2; +using A1::N; +using A2::N; +namespace N1 +{ + public class C + { + void M() + { + var a = new [|Ambiguous|](); + } + } +} + + + +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousAliasNoDiagnostics() + { + await TestMissingAsync(@" +extern alias alias; +using alias=alias; +class myClass : [|alias|]::Uri + { + } +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousNestedClass() + { + var initialMarkup = @" +using static Static; +using static Static; + +public static class Static +{ + public class Nested + { + public void M() { } + } +} + +class D +{ + static void Main(string[] args) + { + var c = new [|Nested|](); + c.M(); + } +}"; + var expectedMarkup0 = @" +using static Static; +using static Static; +using Nested = Static.Nested; + +public static class Static +{ + public class Nested + { + public void M() { } + } +} + +class D +{ + static void Main(string[] args) + { + var c = new Nested(); + c.M(); + } +}"; + var expectedMarkup1 = @" +using static Static; +using static Static; +using Nested = Static.Nested; + +public static class Static +{ + public class Nested + { + public void M() { } + } +} + +class D +{ + static void Main(string[] args) + { + var c = new Nested(); + c.M(); + } +}"; + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup0, index: 0); + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup1, index: 1); + await TestSmartTagTextAsync(initialMarkup, "using Nested = Static.Nested;", index: 0); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousClassDiagnosedAtBaseList() + { + var classDef = GetAmbiguousDefinition(@"public class AmbiguousClass { }"); + var initialMarkup = @" +using N1; +using N2; +" + classDef + @" +namespace NTest +{ + public class Test : [|AmbiguousClass|] { } +} +"; + var expectedMarkup = @" +using N1; +using N2; +using AmbiguousClass = N1.AmbiguousClass; +" + classDef + @" +namespace NTest +{ + public class Test : AmbiguousClass { } +} +"; + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousClassDiagnosedAtTypeConstraint() + { + var classDef = GetAmbiguousDefinition(@"public class AmbiguousClass { }"); + var initialMarkup = @" +using N1; +using N2; +" + classDef + @" +namespace NTest +{ + public class Test where T : [|AmbiguousClass|] { } +} +"; + var expectedMarkup = @" +using N1; +using N2; +using AmbiguousClass = N1.AmbiguousClass; +" + classDef + @" +namespace NTest +{ + public class Test where T : AmbiguousClass { } +} +"; + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousEnumDiagnosedAtFieldDeclaration() + { + var enumDef = GetAmbiguousDefinition(@"public enum AmbiguousEnum { }"); + var initialMarkup = @" +using N1; +using N2; +" + enumDef + @" +namespace NTest +{ + public class Test + { + private [|AmbiguousEnum|] _AmbiguousEnum; + } +} +"; + var expectedMarkup = @" +using N1; +using N2; +using AmbiguousEnum = N1.AmbiguousEnum; +" + enumDef + @" +namespace NTest +{ + public class Test + { + private AmbiguousEnum _AmbiguousEnum; + } +} +"; + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousStructDiagnosedAtPropertyDeclaration() + { + var strcutDef = GetAmbiguousDefinition(@"public struct AmbiguousStruct { }"); + var initialMarkup = @" +using N1; +using N2; +" + strcutDef + @" +namespace NTest +{ + public class Test + { + public [|AmbiguousStruct|] AmbiguousStruct { get; } + } +} +"; + var expectedMarkup = @" +using N1; +using N2; +using AmbiguousStruct = N1.AmbiguousStruct; +" + strcutDef + @" +namespace NTest +{ + public class Test + { + public AmbiguousStruct AmbiguousStruct { get; } + } +} +"; + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousClassDiagnosedAtTypeArgument() + { + var classDef = GetAmbiguousDefinition(@"public class AmbiguousClass { }"); + var initialMarkup = @" +using N1; +using N2; +" + classDef + @" +namespace NTest +{ + public class Test + { + public void M() + { + var list = new System.Collections.Generic.List<[|AmbiguousClass|]> { new AmbiguousClass() }; + } + } +} +"; + var expectedMarkup = @" +using N1; +using N2; +using AmbiguousClass = N1.AmbiguousClass; +" + classDef + @" +namespace NTest +{ + public class Test + { + public void M() + { + var list = new System.Collections.Generic.List { new AmbiguousClass() }; + } + } +} +"; + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousClassDiagnosedAtIdentifierOfIncompleteExpression() + { + var classDef = GetAmbiguousDefinition(@"public class AmbiguousClass { }"); + var initialMarkup = @" +using N1; +using N2; +" + classDef + @" +namespace NTest +{ + public class Test + { + public void M() + { + [|AmbiguousClass|] + } + } +} +"; + var expectedMarkup = @" +using N1; +using N2; +using AmbiguousClass = N1.AmbiguousClass; +" + classDef + @" +namespace NTest +{ + public class Test + { + public void M() + { + AmbiguousClass + } + } +} +"; + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousClassDiagnosedAtMethodParameter() + { + var classDef = GetAmbiguousDefinition(@"public class AmbiguousClass { }"); + var initialMarkup = @" +using N1; +using N2; +" + classDef + @" +namespace NTest +{ + public class Test + { + public void M([|AmbiguousClass|] a) + { + } + } +} +"; + var expectedMarkup = @" +using N1; +using N2; +using AmbiguousClass = N1.AmbiguousClass; +" + classDef + @" +namespace NTest +{ + public class Test + { + public void M(AmbiguousClass a) + { + } + } +} +"; + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAliasAmbiguousType)] + public async Task TestAmbiguousClassDiagnosedAtFromClauseTypeIdentifier() + { + var classDef = GetAmbiguousDefinition(@"public class AmbiguousClass { }"); + var initialMarkup = @" +using N1; +using N2; +using System.Linq; +" + classDef + @" +namespace NTest +{ + public class Test + { + public void M() + { + var qry = from [|AmbiguousClass|] a in new object[] { } + select a; + } + } +} +"; + var expectedMarkup = @" +using N1; +using N2; +using System.Linq; +using AmbiguousClass = N1.AmbiguousClass; +" + classDef + @" +namespace NTest +{ + public class Test + { + public void M() + { + var qry = from AmbiguousClass a in new object[] { } + select a; + } + } +} +"; + await TestInRegularAndScriptAsync(initialMarkup, expectedMarkup); + } + } +} diff --git a/src/EditorFeatures/TestUtilities/Traits.cs b/src/EditorFeatures/TestUtilities/Traits.cs index 0fb1aea24775dd392e580d619276c8eb49ecf1e4..6a2071df6edbcefe69bee7e1adcc9a0f60f011e3 100644 --- a/src/EditorFeatures/TestUtilities/Traits.cs +++ b/src/EditorFeatures/TestUtilities/Traits.cs @@ -41,6 +41,7 @@ public static class Features public const string CodeActionsAddOverload = "CodeActions.AddOverloads"; public const string CodeActionsAddParameter = "CodeActions.AddParameter"; public const string CodeActionsAddParenthesesAroundConditionalExpressionInInterpolatedString = "CodeActions.AddParenthesesAroundConditionalExpressionInInterpolatedString"; + public const string CodeActionsAliasAmbiguousType = "CodeActions.AliasAmbiguousType"; public const string CodeActionsChangeToAsync = "CodeActions.ChangeToAsync"; public const string CodeActionsChangeToIEnumerable = "CodeActions.ChangeToIEnumerable"; public const string CodeActionsChangeToYield = "CodeActions.ChangeToYield"; diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/AliasAmbiguousType/AliasAmbiguousTypeTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/AliasAmbiguousType/AliasAmbiguousTypeTests.vb new file mode 100644 index 0000000000000000000000000000000000000000..0713cf13e7bca8e7c36c6834fb9bba3e0ae94596 --- /dev/null +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/AliasAmbiguousType/AliasAmbiguousTypeTests.vb @@ -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 + + + 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 + + + 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 + + + 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 + + + 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 + + + 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 + diff --git a/src/Features/CSharp/Portable/AliasAmbiguousType/CSharpAliasAmbiguousTypeCodeFixProvider.cs b/src/Features/CSharp/Portable/AliasAmbiguousType/CSharpAliasAmbiguousTypeCodeFixProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..208d113e56f9733b1d5f0c3850f33b236593128c --- /dev/null +++ b/src/Features/CSharp/Portable/AliasAmbiguousType/CSharpAliasAmbiguousTypeCodeFixProvider.cs @@ -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 + { + /// + /// 'reference' is an ambiguous reference between 'identifier' and 'identifier' + /// + private const string CS0104 = nameof(CS0104); + + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(CS0104); + + protected override string GetTextPreviewOfChange(string alias, ITypeSymbol typeSymbol) + => $"using { alias } = { typeSymbol.ToNameDisplayString() };"; + } +} diff --git a/src/Features/Core/Portable/AliasAmbiguousType/AbstractAliasAmbiguousTypeCodeFixProvider.cs b/src/Features/Core/Portable/AliasAmbiguousType/AbstractAliasAmbiguousTypeCodeFixProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..40a66612167a30f487ab34822b8fb7cba5930600 --- /dev/null +++ b/src/Features/Core/Portable/AliasAmbiguousType/AbstractAliasAmbiguousTypeCodeFixProvider.cs @@ -0,0 +1,81 @@ +// 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(); + 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(); + var syntaxGenerator = document.GetLanguageService(); + 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(symbolInfo.CandidateSymbols.Length); + foreach (var symbol in symbolInfo.CandidateSymbols.Cast()) + { + var typeName = symbol.Name; + var codeActionPreviewText = GetTextPreviewOfChange(typeName, symbol); + codeActionsBuilder.Add(new MyCodeAction(codeActionPreviewText, c => + { + var aliasDirective = syntaxGenerator.AliasImportDeclaration(typeName, symbol); + var newRoot = addImportService.AddImport(compilation, root, diagnosticNode, aliasDirective, placeSystemNamespaceFirst); + return Task.FromResult(document.WithSyntaxRoot(newRoot)); + })); + } + var groupingTitle = string.Format(FeaturesResources.Alias_ambiguous_type_0, diagnosticNode.ToString()); + var groupingCodeAction = new CodeActionWithNestedActions(groupingTitle, codeActionsBuilder.ToImmutable(), isInlinable: true); + 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> createChangedDocument) : + base(title, createChangedDocument, equivalenceKey: title) + { + } + } + } +} diff --git a/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs index 69adbf08fd91ae3c36817c33cfcf9a5d043be1d8..5c51c264b413c9e251f894d78f124c9e5039aaff 100644 --- a/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs +++ b/src/Features/Core/Portable/CodeFixes/PredefinedCodeFixProviderNames.cs @@ -9,6 +9,7 @@ internal static class PredefinedCodeFixProviderNames public const string AddAsync = nameof(AddAsync); public const string AddParameter = nameof(AddParameter); public const string AddParenthesesAroundConditionalExpressionInInterpolatedString = nameof(AddParenthesesAroundConditionalExpressionInInterpolatedString); + public const string AliasAmbiguousType = nameof(AliasAmbiguousType); public const string ApplyNamingStyle = nameof(ApplyNamingStyle); public const string AddBraces = nameof(AddBraces); public const string ChangeReturnType = nameof(ChangeReturnType); diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs index d9ef20dfe9613851155407a6ce029ad70165a0f7..a9dfa13555d328251398794d8db8ca8b1224dce5 100644 --- a/src/Features/Core/Portable/FeaturesResources.Designer.cs +++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs @@ -427,7 +427,18 @@ internal class FeaturesResources { return ResourceManager.GetString("Adding_an_imported_method_will_prevent_the_debug_session_from_continuing", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Alias ambiguous type '{0}'. + /// + internal static string Alias_ambiguous_type_0 + { + get + { + return ResourceManager.GetString("Alias_ambiguous_type_0", resourceCulture); + } + } + /// /// Looks up a localized string similar to All lowercase. /// diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index fad430187ade4634e15b9523515372912dd734af..e148e16dd490bfad3998c9fbaf37b476870ca126 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -1334,4 +1334,7 @@ This version used in: {2} indexer - + + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index ce692b345bb3d0eaec6f00b38d9695e3e53449be..e4f0368fe4208480ad4a64233d65a911aec1a244 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -1985,6 +1985,11 @@ Tato verze se používá zde: {2}. indexer + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index a41c19128859a38709049ff915461d17dd695224..b0a13cc7e17c33f5159f7fec36b95ad8f4251707 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -1985,6 +1985,11 @@ Diese Version wird verwendet in: {2} Indexer + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index bf0c74f4c7f08cd66030307b4e98c988b4f06449..53ec53954388498ad4bcfef784a22038a2f8e232 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -1985,6 +1985,11 @@ Esta versión se utiliza en: {2} indizador + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 6f66751514f7ddf132addaf77dfd6a3b5a6f6191..fb2d4683760be39e98788885b97855f3d924c35d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -1985,6 +1985,11 @@ Version utilisée dans : {2} indexeur + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 896660b25cfdce9ca6cd5f62d8de9ca8a23d453a..aef086e36e5dbfd22eccfbc2f6245fbbfb165ff1 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -1985,6 +1985,11 @@ Questa versione è usata {2} indicizzatore + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index de47819d3ac2d2d86343301e61a9bed0c3f58f29..ff90d419baedd5e0ec4bc639dfce5ae22abab86d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -1985,6 +1985,11 @@ This version used in: {2} インデクサー + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 3700ea53373810f5d87315f747d08487fff5aced..40db56ff6abe35fc95a49ba03baa8f133ca2b9ea 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -1985,6 +1985,11 @@ This version used in: {2} 인덱서 + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 3b335ae2259a2675e5736d63097adccaabfbaeb4..2ebc7240b1f17e125bf07181e7c67632978709b7 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -1985,6 +1985,11 @@ Ta wersja jest używana wersja: {2} indeksator + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index fb1cc03aa50682ed4dc0d361b708236ce5df22da..ea5a29f1b2fdcdce1a235372280cda699bd28efb 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -1985,6 +1985,11 @@ Essa versão é usada no: {2} indexador + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 88dda948826e2a405300ab61bdbd608c76ae8d33..c5a7810fbd7637022c830997bc5f8e68bb8a76e7 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -1985,6 +1985,11 @@ This version used in: {2} индексатор + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 5405336f7ab0eeb9e14057bb8807df417afa0751..f53efeed7f71a79c8b619a64422e70acb0916cce 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -1985,6 +1985,11 @@ Bu sürüm şurada kullanılır: {2} dizin oluşturucu + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index d19acae16896a5c83532e3b57b16b52789d10e8b..cd3c016bbea8870743d64d19f343ca4d78046b89 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -1985,6 +1985,11 @@ This version used in: {2} 索引器 + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index a1cb5a656e69f4d2876827a41df2c4b90efc11e5..84a91253099af31dd12cae593dfb87883c1ede36 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -1985,6 +1985,11 @@ This version used in: {2} 索引子 + + Alias ambiguous type '{0}' + Alias ambiguous type '{0}' + + \ No newline at end of file diff --git a/src/Features/VisualBasic/Portable/AliasAmbiguousType/VisualBasicAliasAmbiguousTypeCodeFixProvider.vb b/src/Features/VisualBasic/Portable/AliasAmbiguousType/VisualBasicAliasAmbiguousTypeCodeFixProvider.vb new file mode 100644 index 0000000000000000000000000000000000000000..7f57e68a619734bbdb7e2442aea8c1d03ac713dc --- /dev/null +++ b/src/Features/VisualBasic/Portable/AliasAmbiguousType/VisualBasicAliasAmbiguousTypeCodeFixProvider.vb @@ -0,0 +1,23 @@ +' 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 System.Composition +Imports Microsoft.CodeAnalysis.AliasAmbiguousType +Imports Microsoft.CodeAnalysis.CodeFixes + +Namespace Microsoft.CodeAnalysis.VisualBasic.AliasAmbiguousType + + + Friend Class VisualBasicAliasAmbiguousTypeCodeFixProvider + Inherits AbstractAliasAmbiguousTypeCodeFixProvider + + 'BC30561: '' is ambiguous, imported from the namespaces or types '' + Private Const BC30561 As String = NameOf(BC30561) + + Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(BC30561) + + Protected Overrides Function GetTextPreviewOfChange(aliasName As String, typeSymbol As ITypeSymbol) As String + Return $"Imports { aliasName } = { typeSymbol.ToNameDisplayString() }" + End Function + End Class +End Namespace diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index 98399b69302e7aef9114a10fabdaee9b5c7474e4..cde3ee831d91b4f815235c7985f71f01fcb2731e 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -82,6 +82,9 @@ public override SyntaxNode NamespaceImportDeclaration(SyntaxNode name) return SyntaxFactory.UsingDirective((NameSyntax)name); } + public override SyntaxNode AliasImportDeclaration(string aliasIdentifierName, SyntaxNode name) + => SyntaxFactory.UsingDirective(SyntaxFactory.NameEquals(aliasIdentifierName), (NameSyntax)name); + public override SyntaxNode NamespaceDeclaration(SyntaxNode name, IEnumerable declarations) { return SyntaxFactory.NamespaceDeclaration( @@ -3186,9 +3189,9 @@ private static SyntaxNode WithBaseList(SyntaxNode declaration, BaseListSyntax ba } } -#endregion + #endregion -#region Remove, Replace, Insert + #region Remove, Replace, Insert public override SyntaxNode ReplaceNode(SyntaxNode root, SyntaxNode declaration, SyntaxNode newDeclaration) { @@ -3525,9 +3528,9 @@ private static SyntaxNode ShiftTrivia(SyntaxNode root, SyntaxNode node) internal override bool IsRegularOrDocComment(SyntaxTrivia trivia) => trivia.IsRegularOrDocComment(); -#endregion + #endregion -#region Statements and Expressions + #region Statements and Expressions public override SyntaxNode AddEventHandler(SyntaxNode @event, SyntaxNode handler) { @@ -3962,6 +3965,9 @@ public override SyntaxNode QualifiedName(SyntaxNode left, SyntaxNode right) return SyntaxFactory.QualifiedName((NameSyntax)left, (SimpleNameSyntax)right).WithAdditionalAnnotations(Simplifier.Annotation); } + public override SyntaxNode NameExpression(INamespaceOrTypeSymbol namespaceOrTypeSymbol) + => namespaceOrTypeSymbol.GenerateNameSyntax(); + public override SyntaxNode TypeExpression(ITypeSymbol typeSymbol) { return typeSymbol.GenerateTypeSyntax(); @@ -4214,6 +4220,6 @@ internal override SyntaxNode RefExpression(SyntaxNode expression) public override SyntaxNode TupleExpression(IEnumerable arguments) => SyntaxFactory.TupleExpression(SyntaxFactory.SeparatedList(arguments.Select(AsArgument))); -#endregion + #endregion } } diff --git a/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs b/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs index d57bd1a9eda6df8d4e50679a9968ed0fb10b6646..6708a81b4689cfc99bd5951073c64ae38f44cc67 100644 --- a/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs +++ b/src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs @@ -787,6 +787,21 @@ public SyntaxNode NamespaceImportDeclaration(string name) return NamespaceImportDeclaration(DottedName(name)); } + /// + /// Creates an alias import declaration. + /// + /// The name of the alias. + /// The namespace or type to be aliased. + public SyntaxNode AliasImportDeclaration(string aliasIdentifierName, INamespaceOrTypeSymbol symbol) + => AliasImportDeclaration(aliasIdentifierName, NameExpression(symbol)); + + /// + /// Creates an alias import declaration. + /// + /// The name of the alias. + /// The namespace or type to be aliased. + public abstract SyntaxNode AliasImportDeclaration(string aliasIdentifierName, SyntaxNode name); + /// /// Creates an attribute. /// @@ -1689,6 +1704,13 @@ public SyntaxNode DottedName(string dottedName) private static readonly char[] s_dotSeparator = new char[] { '.' }; + /// + /// Creates a name that denotes a type or namespace. + /// + /// The symbol to create a name for. + /// + public abstract SyntaxNode NameExpression(INamespaceOrTypeSymbol namespaceOrTypeSymbol); + /// /// Creates an expression that denotes a type. /// diff --git a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt index e6817acbcad95a9f1ed5962b219937fb1c8c8d24..f12ca5188652933984e5aeb46fca36b0162625f3 100644 --- a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt @@ -1,3 +1,6 @@ +Microsoft.CodeAnalysis.Editing.SyntaxGenerator.AliasImportDeclaration(string aliasIdentifierName, Microsoft.CodeAnalysis.INamespaceOrTypeSymbol symbol) -> Microsoft.CodeAnalysis.SyntaxNode +abstract Microsoft.CodeAnalysis.Editing.SyntaxGenerator.AliasImportDeclaration(string aliasIdentifierName, Microsoft.CodeAnalysis.SyntaxNode name) -> Microsoft.CodeAnalysis.SyntaxNode +abstract Microsoft.CodeAnalysis.Editing.SyntaxGenerator.NameExpression(Microsoft.CodeAnalysis.INamespaceOrTypeSymbol namespaceOrTypeSymbol) -> Microsoft.CodeAnalysis.SyntaxNode const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.EnumMemberName = "enum member name" -> string const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.FieldName = "field name" -> string const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.ConstantName = "constant name" -> string @@ -6,4 +9,4 @@ const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.ParameterNam const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.MethodName = "method name" -> string const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.ExtensionMethodName = "extension method name" -> string const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.PropertyName = "property name" -> string -const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.EventName = "event name" -> string \ No newline at end of file +const Microsoft.CodeAnalysis.Classification.ClassificationTypeNames.EventName = "event name" -> string diff --git a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb index 88b698dc580cba288e1c926890df3568e176eeb6..fc3d40b23650e9ec03c3d27e38c004ae19078a6f 100644 --- a/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb +++ b/src/Workspaces/VisualBasic/Portable/CodeGeneration/VisualBasicSyntaxGenerator.vb @@ -341,6 +341,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Throw New NotSupportedException("ThrowExpressions are not supported in Visual Basic") End Function + Public Overrides Function NameExpression(namespaceOrTypeSymbol As INamespaceOrTypeSymbol) As SyntaxNode + Return namespaceOrTypeSymbol.GenerateTypeSyntax() + End Function + Public Overrides Function TypeExpression(typeSymbol As ITypeSymbol) As SyntaxNode Return typeSymbol.GenerateTypeSyntax() End Function @@ -1500,6 +1504,17 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Return SyntaxFactory.ImportsStatement(SyntaxFactory.SingletonSeparatedList(Of ImportsClauseSyntax)(SyntaxFactory.SimpleImportsClause(DirectCast(name, NameSyntax)))) End Function + Public Overrides Function AliasImportDeclaration(aliasIdentifierName As String, name As SyntaxNode) As SyntaxNode + If TypeOf name Is NameSyntax Then + Return SyntaxFactory.ImportsStatement(SyntaxFactory.SeparatedList(Of ImportsClauseSyntax).Add( + SyntaxFactory.SimpleImportsClause( + SyntaxFactory.ImportAliasClause(aliasIdentifierName), + CType(name, NameSyntax)))) + + End If + Throw New ArgumentException("name is not a NameSyntax.", NameOf(name)) + End Function + Public Overrides Function NamespaceDeclaration(name As SyntaxNode, nestedDeclarations As IEnumerable(Of SyntaxNode)) As SyntaxNode Dim imps As IEnumerable(Of StatementSyntax) = AsImports(nestedDeclarations) Dim members As IEnumerable(Of StatementSyntax) = AsNamespaceMembers(nestedDeclarations) @@ -2893,7 +2908,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration Case SyntaxKind.ProtectedKeyword If accessibility = Accessibility.Friend Then accessibility = Accessibility.ProtectedOrFriend - ElseIf accessibility = Accessibility.Private + ElseIf accessibility = Accessibility.Private Then accessibility = Accessibility.ProtectedAndFriend Else accessibility = Accessibility.Protected