diff --git a/src/Features/Core/Shared/Options/OrganizerOptions.cs b/src/Features/Core/Shared/Options/OrganizerOptions.cs index c5f959486d3d..2f6da3d9219f 100644 --- a/src/Features/Core/Shared/Options/OrganizerOptions.cs +++ b/src/Features/Core/Shared/Options/OrganizerOptions.cs @@ -9,8 +9,10 @@ internal partial class OrganizerOptions { public const string FeatureName = "Organizer"; - [ExportOption] - public static readonly PerLanguageOption PlaceSystemNamespaceFirst = new PerLanguageOption(FeatureName, "PlaceSystemNamespaceFirst", defaultValue: true); + public static PerLanguageOption PlaceSystemNamespaceFirst + { + get { return Microsoft.CodeAnalysis.Editing.GenerationOptions.PlaceSystemNamespaceFirst; } + } /// /// This option is currently unused by Roslyn, but we might want to implement it in the diff --git a/src/Workspaces/CSharp/Portable/CSharpWorkspace.csproj b/src/Workspaces/CSharp/Portable/CSharpWorkspace.csproj index 8f7f58de7b2e..6511a79b23f7 100644 --- a/src/Workspaces/CSharp/Portable/CSharpWorkspace.csproj +++ b/src/Workspaces/CSharp/Portable/CSharpWorkspace.csproj @@ -121,6 +121,7 @@ True CSharpWorkspaceResources.resx + @@ -266,4 +267,4 @@ - + \ No newline at end of file diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index 1ea74c1f77fc..91973a9ba9b9 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration @@ -3541,22 +3542,25 @@ public override SyntaxNode WithTypeArguments(SyntaxNode expression, IEnumerable< switch (expression.Kind()) { case SyntaxKind.IdentifierName: - case SyntaxKind.GenericName: var sname = (SimpleNameSyntax)expression; return SyntaxFactory.GenericName(sname.Identifier, SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(typeArguments.Cast()))); + case SyntaxKind.GenericName: + var gname = (GenericNameSyntax)expression; + return gname.WithTypeArgumentList(SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(typeArguments.Cast()))); + case SyntaxKind.QualifiedName: var qname = (QualifiedNameSyntax)expression; - return SyntaxFactory.QualifiedName(qname.Left, (SimpleNameSyntax)WithTypeArguments(qname.Right, typeArguments)); + return qname.WithRight((SimpleNameSyntax)WithTypeArguments(qname.Right, typeArguments)); case SyntaxKind.AliasQualifiedName: var aname = (AliasQualifiedNameSyntax)expression; - return SyntaxFactory.AliasQualifiedName(aname.Alias, (SimpleNameSyntax)WithTypeArguments(aname.Name, typeArguments)); + return aname.WithName((SimpleNameSyntax)WithTypeArguments(aname.Name, typeArguments)); case SyntaxKind.SimpleMemberAccessExpression: case SyntaxKind.PointerMemberAccessExpression: var sma = (MemberAccessExpressionSyntax)expression; - return SyntaxFactory.MemberAccessExpression(expression.Kind(), sma.Expression, (SimpleNameSyntax)WithTypeArguments(sma.Name, typeArguments)); + return sma.WithName((SimpleNameSyntax)WithTypeArguments(sma.Name, typeArguments)); default: return expression; @@ -3565,7 +3569,7 @@ public override SyntaxNode WithTypeArguments(SyntaxNode expression, IEnumerable< public override SyntaxNode QualifiedName(SyntaxNode left, SyntaxNode right) { - return SyntaxFactory.QualifiedName((NameSyntax)left, (SimpleNameSyntax)right); + return SyntaxFactory.QualifiedName((NameSyntax)left, (SimpleNameSyntax)right).WithAdditionalAnnotations(Simplifier.Annotation); } public override SyntaxNode TypeExpression(ITypeSymbol typeSymbol) diff --git a/src/Workspaces/CSharp/Portable/Editing/CSharpImportAdder.cs b/src/Workspaces/CSharp/Portable/Editing/CSharpImportAdder.cs new file mode 100644 index 000000000000..0933df27bdf3 --- /dev/null +++ b/src/Workspaces/CSharp/Portable/Editing/CSharpImportAdder.cs @@ -0,0 +1,84 @@ +// 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; +using System.Composition; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Editing +{ + [ExportLanguageService(typeof(ImportAdderService), LanguageNames.CSharp), Shared] + internal class CSharpImportAdder : ImportAdderService + { + protected override INamespaceSymbol GetImportedNamespaceSymbol(SyntaxNode import, SemanticModel model) + { + var @using = import as UsingDirectiveSyntax; + if (@using != null && @using.Alias == null) + { + return model.GetSymbolInfo(@using.Name).Symbol as INamespaceSymbol; + } + + return null; + } + + protected override INamespaceSymbol GetExplicitNamespaceSymbol(SyntaxNode node, SemanticModel model) + { + var name = node as QualifiedNameSyntax; + if (name != null) + { + return GetExplicitNamespaceSymbol(name, name.Left, model); + } + + var memberAccess = node as MemberAccessExpressionSyntax; + if (memberAccess != null) + { + return GetExplicitNamespaceSymbol(memberAccess, memberAccess.Expression, model); + } + + return null; + } + + private INamespaceSymbol GetExplicitNamespaceSymbol(ExpressionSyntax fullName, ExpressionSyntax namespacePart, SemanticModel model) + { + // name must refer to something that is not a namespace, but be qualified with a namespace. + var symbol = model.GetSymbolInfo(fullName).Symbol; + var nsSymbol = model.GetSymbolInfo(namespacePart).Symbol as INamespaceSymbol; + if (symbol != null && symbol.Kind != SymbolKind.Namespace && nsSymbol != null) + { + // use the symbols containing namespace, and not the potentially less than fully qualified namespace in the full name expression. + var ns = symbol.ContainingNamespace; + if (ns != null) + { + return model.Compilation.GetCompilationNamespace(ns); + } + } + + return null; + } + + protected override SyntaxNode InsertNamespaceImport(SyntaxNode root, SyntaxGenerator gen, SyntaxNode import, OptionSet options) + { + var comparer = options.GetOption(GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.CSharp) + ? UsingsAndExternAliasesDirectiveComparer.SystemFirstInstance + : UsingsAndExternAliasesDirectiveComparer.NormalInstance; + + // find insertion point + foreach (var existingImport in gen.GetNamespaceImports(root)) + { + if (comparer.Compare(import, existingImport) < 0) + { + return gen.InsertNodesBefore(root, existingImport, new[] { import }); + } + } + + return gen.AddNamespaceImports(root, import); + } + } +} \ No newline at end of file diff --git a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.cs b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.cs index 403fd27c2bfe..d8ec740b3f68 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/CSharpSimplificationService.cs @@ -4,13 +4,16 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Simplification @@ -161,5 +164,26 @@ protected override bool CanNodeBeSimplifiedWithoutSpeculation(SyntaxNode node) { return false; } + + private static readonly string CS8019_UnusedUsingDirective = "CS8019"; + + protected override void GetUnusedNamespaceImports(SemanticModel model, HashSet namespaceImports, CancellationToken cancellationToken) + { + var root = model.SyntaxTree.GetRoot(); + var diagnostics = model.GetDiagnostics(cancellationToken: cancellationToken); + + foreach (var diagnostic in diagnostics) + { + if (diagnostic.Id == CS8019_UnusedUsingDirective) + { + var node = root.FindNode(diagnostic.Location.SourceSpan) as UsingDirectiveSyntax; + + if (node != null) + { + namespaceImports.Add(node); + } + } + } + } } } diff --git a/src/Workspaces/CSharpTest/CSharpServicesTest.csproj b/src/Workspaces/CSharpTest/CSharpServicesTest.csproj index 1e7db9881ae1..0f350f2c8389 100644 --- a/src/Workspaces/CSharpTest/CSharpServicesTest.csproj +++ b/src/Workspaces/CSharpTest/CSharpServicesTest.csproj @@ -96,6 +96,7 @@ + @@ -103,4 +104,4 @@ - + \ No newline at end of file diff --git a/src/Workspaces/CSharpTest/CodeGeneration/AddImportsTests.cs b/src/Workspaces/CSharpTest/CodeGeneration/AddImportsTests.cs new file mode 100644 index 000000000000..c0f2277e28a3 --- /dev/null +++ b/src/Workspaces/CSharpTest/CodeGeneration/AddImportsTests.cs @@ -0,0 +1,366 @@ +// 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 Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Simplification; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Editing +{ + public class AddImportsTests + { + private readonly AdhocWorkspace _ws = new AdhocWorkspace(); + private readonly Project _emptyProject; + + public AddImportsTests() + { + _emptyProject = _ws.AddProject( + ProjectInfo.Create( + ProjectId.CreateNewId(), + VersionStamp.Default, + "test", + "test.dll", + LanguageNames.CSharp, + metadataReferences: new[] { TestReferences.NetFx.v4_0_30319.mscorlib })); + } + + private Document GetDocument(string code) + { + return _emptyProject.AddDocument("test.cs", code); + } + + private void Test(string initialText, string importsAddedText, string simplifiedText, OptionSet options = null) + { + var doc = GetDocument(initialText); + options = options ?? doc.Project.Solution.Workspace.Options; + + var imported = ImportAdder.AddImportsAsync(doc, options).Result; + + if (importsAddedText != null) + { + var formatted = Formatter.FormatAsync(imported, SyntaxAnnotation.ElasticAnnotation, options).Result; + var actualText = formatted.GetTextAsync().Result.ToString(); + Assert.Equal(importsAddedText, actualText); + } + + if (simplifiedText != null) + { + var reduced = Simplifier.ReduceAsync(imported, options).Result; + var formatted = Formatter.FormatAsync(reduced, SyntaxAnnotation.ElasticAnnotation, options).Result; + + var actualText = formatted.GetTextAsync().Result.ToString(); + Assert.Equal(simplifiedText, actualText); + } + } + + [Fact] + public void TestAddImport() + { + Test( +@"class C +{ + public System.Collections.Generic.List F; +}", + +@"using System.Collections.Generic; + +class C +{ + public System.Collections.Generic.List F; +}", + +@"using System.Collections.Generic; + +class C +{ + public List F; +}"); + } + + [Fact] + public void TestAddSystemImportFirst() + { + Test( +@"using N; + +class C +{ + public System.Collections.Generic.List F; +}", + +@"using System.Collections.Generic; +using N; + +class C +{ + public System.Collections.Generic.List F; +}", + +@"using System.Collections.Generic; +using N; + +class C +{ + public List F; +}"); + } + + [Fact] + public void TestDontAddSystemImportFirst() + { + Test( +@"using N; + +class C +{ + public System.Collections.Generic.List F; +}", + +@"using N; +using System.Collections.Generic; + +class C +{ + public System.Collections.Generic.List F; +}", + +@"using N; +using System.Collections.Generic; + +class C +{ + public List F; +}", + _ws.Options.WithChangedOption(GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.CSharp, false) +); + } + + [Fact] + public void TestAddImportsInOrder() + { + Test( +@"using System.Collections; +using System.Diagnostics; + +class C +{ + public System.Collections.Generic.List F; +}", + +@"using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +class C +{ + public System.Collections.Generic.List F; +}", + +@"using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +class C +{ + public List F; +}"); + } + + [Fact] + public void TestAddMultipleImportsInOrder() + { + Test( +@"class C +{ + public System.Collections.Generic.List F; + public System.EventHandler Handler; +}", + +@"using System; +using System.Collections.Generic; + +class C +{ + public System.Collections.Generic.List F; + public System.EventHandler Handler; +}", + +@"using System; +using System.Collections.Generic; + +class C +{ + public List F; + public EventHandler Handler; +}"); + } + + [Fact] + public void TestImportNotRedundantlyAdded() + { + Test( +@"using System.Collections.Generic; + +class C +{ + public System.Collections.Generic.List F; +}", + +@"using System.Collections.Generic; + +class C +{ + public System.Collections.Generic.List F; +}", + +@"using System.Collections.Generic; + +class C +{ + public List F; +}"); + } + + [Fact] + public void TestUnusedAddedImportIsRemovedBySimplifier() + { + Test( +@"class C +{ + public System.Int32 F; +}", + +@"using System; + +class C +{ + public System.Int32 F; +}", + +@"class C +{ + public int F; +}"); + } + + [Fact] + public void TestImportNotAddedForNamespaceDeclarations() + { + Test( +@"namespace N +{ +}", + +@"namespace N +{ +}", + +@"namespace N +{ +}"); + } + + [Fact] + public void TestImportAddedAndRemovedForReferencesInsideNamespaceDeclarations() + { + Test( +@"namespace N +{ + class C + { + private N.C c; + } +}", + +@"using N; + +namespace N +{ + class C + { + private N.C c; + } +}", + +@"namespace N +{ + class C + { + private C c; + } +}"); + } + + [Fact] + public void TestImportAddedAndRemovedForReferencesMatchingNestedImports() + { + Test( +@"namespace N +{ + using System.Collections.Generic; + + class C + { + private System.Collections.Generic.List F; + } +}", + +@"using System.Collections.Generic; + +namespace N +{ + using System.Collections.Generic; + + class C + { + private System.Collections.Generic.List F; + } +}", + +@"namespace N +{ + using System.Collections.Generic; + + class C + { + private List F; + } +}"); + } + + [Fact] + public void TestImportRemovedIfItMakesReferenceAmbiguous() + { + // this is not really an artifact of the AddImports feature, it is due + // to Simplifier not reducing the namespace reference because it would + // become ambiguous, thus leaving an unused using directive + Test( + @" +namespace N { class C { } } + +class C +{ + public N.C F; +}", + + @"using N; + +namespace N { class C { } } + +class C +{ + public N.C F; +}", + + @" +namespace N { class C { } } + +class C +{ + public N.C F; +}"); + } + } +} + diff --git a/src/Workspaces/Core/Desktop/Options/ExportOptionsAttribute.cs b/src/Workspaces/Core/Desktop/Options/ExportOptionAttribute.cs similarity index 99% rename from src/Workspaces/Core/Desktop/Options/ExportOptionsAttribute.cs rename to src/Workspaces/Core/Desktop/Options/ExportOptionAttribute.cs index fe57263275a5..83de7bdcb65d 100644 --- a/src/Workspaces/Core/Desktop/Options/ExportOptionsAttribute.cs +++ b/src/Workspaces/Core/Desktop/Options/ExportOptionAttribute.cs @@ -14,4 +14,4 @@ public ExportOptionAttribute() { } } -} +} \ No newline at end of file diff --git a/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj b/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj index 6ca437c7d79c..df64dcfce5f9 100644 --- a/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj +++ b/src/Workspaces/Core/Desktop/Workspaces.Desktop.csproj @@ -85,10 +85,10 @@ InternalUtilities\GacFileResolver.cs + - @@ -179,4 +179,4 @@ - + \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/Editing/GenerationOptions.cs b/src/Workspaces/Core/Portable/Editing/GenerationOptions.cs new file mode 100644 index 000000000000..55a3398e1467 --- /dev/null +++ b/src/Workspaces/Core/Portable/Editing/GenerationOptions.cs @@ -0,0 +1,13 @@ +// 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 Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.Editing +{ + internal class GenerationOptions + { + public const string FeatureName = "Organizer"; + + public static readonly PerLanguageOption PlaceSystemNamespaceFirst = new PerLanguageOption(FeatureName, "PlaceSystemNamespaceFirst", defaultValue: true); + } +} diff --git a/src/Workspaces/Core/Portable/Editing/GenerationOptionsProvider.cs b/src/Workspaces/Core/Portable/Editing/GenerationOptionsProvider.cs new file mode 100644 index 000000000000..afe7e4d5a5e7 --- /dev/null +++ b/src/Workspaces/Core/Portable/Editing/GenerationOptionsProvider.cs @@ -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. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Options.Providers; + +namespace Microsoft.CodeAnalysis.Editing +{ + [ExportOptionProvider, Shared] + internal class GenerationOptionsProvider : IOptionProvider + { + private static readonly IEnumerable _options = ImmutableArray.Create( + GenerationOptions.PlaceSystemNamespaceFirst + ); + + public IEnumerable GetOptions() + { + return _options; + } + } +} \ No newline at end of file diff --git a/src/Workspaces/Core/Portable/Editing/ImportAdder.cs b/src/Workspaces/Core/Portable/Editing/ImportAdder.cs new file mode 100644 index 000000000000..ae0692f3bd69 --- /dev/null +++ b/src/Workspaces/Core/Portable/Editing/ImportAdder.cs @@ -0,0 +1,57 @@ +// 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.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Editing +{ + public static class ImportAdder + { + /// + /// Adds namespace imports / using directives for namespace references found in the document. + /// + public static async Task AddImportsAsync(Document document, OptionSet options = null, CancellationToken cancellationToken = default(CancellationToken)) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return await AddImportsAsync(document, root.FullSpan, options, cancellationToken).ConfigureAwait(false); + } + + /// + /// Adds namespace imports / using directives for namespace references found in the document within the span specified. + /// + public static Task AddImportsAsync(Document document, TextSpan span, OptionSet options = null, CancellationToken cancellationToken = default(CancellationToken)) + { + return AddImportsAsync(document, new[] { span }, options, cancellationToken); + } + + /// + /// Adds namespace imports / using directives for namespace references found in the document within the sub-trees annotated with the . + /// + public static async Task AddImportsAsync(Document document, SyntaxAnnotation annotation, OptionSet options = null, CancellationToken cancellationToken = default(CancellationToken)) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return await AddImportsAsync(document, root.GetAnnotatedNodesAndTokens(annotation).Select(t => t.FullSpan), options, cancellationToken).ConfigureAwait(false); + } + + /// + /// Adds namespace imports / using directives for namespace references found in the document within the spans specified. + /// + public static Task AddImportsAsync(Document document, IEnumerable spans, OptionSet options = null, CancellationToken cancellationToken = default(CancellationToken)) + { + var service = document.Project.LanguageServices.GetService(); + if (service != null) + { + return service.AddImportsAsync(document, spans, options, cancellationToken); + } + else + { + return Task.FromResult(document); + } + } + } +} diff --git a/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs b/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs new file mode 100644 index 000000000000..4c8ace4b7df2 --- /dev/null +++ b/src/Workspaces/Core/Portable/Editing/ImportAdderService.cs @@ -0,0 +1,94 @@ +// 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; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Collections; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Editing +{ + internal abstract class ImportAdderService : ILanguageService + { + public async Task AddImportsAsync(Document document, IEnumerable spans, OptionSet options, CancellationToken cancellationToken) + { + options = options ?? document.Project.Solution.Workspace.Options; + + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var root = model.SyntaxTree.GetRoot(); + + // Create a simple interval tree for simplification spans. + var spansTree = new SimpleIntervalTree(TextSpanIntervalIntrospector.Instance, spans); + + Func isInSpan = (nodeOrToken) => + spansTree.GetOverlappingIntervals(nodeOrToken.FullSpan.Start, nodeOrToken.FullSpan.Length).Any(); + + var nodesWithExplicitNamespaces = root.DescendantNodesAndSelf().Where(n => isInSpan(n) && GetExplicitNamespaceSymbol(n, model) != null).ToList(); + + var namespacesToAdd = new HashSet(); + namespacesToAdd.AddRange(nodesWithExplicitNamespaces.Select(n => GetExplicitNamespaceSymbol(n, model))); + + // annotate these nodes so they get simplified later + var newRoot = root.ReplaceNodes(nodesWithExplicitNamespaces, (o, r) => r.WithAdditionalAnnotations(Simplifier.Annotation)); + var newDoc = document.WithSyntaxRoot(newRoot); + var newModel = await newDoc.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + newRoot = this.AddNamespaceImports(newDoc, newModel, options, namespacesToAdd); + return document.WithSyntaxRoot(newRoot); + } + + private SyntaxNode AddNamespaceImports( + Document document, + SemanticModel model, + OptionSet options, + IEnumerable namespaces) + { + var existingNamespaces = new HashSet(); + this.GetExistingImportedNamespaces(document, model, existingNamespaces); + + var namespacesToAdd = new HashSet(namespaces); + namespacesToAdd.RemoveAll(existingNamespaces); + + var root = model.SyntaxTree.GetRoot(); + if (namespacesToAdd.Count == 0) + { + return root; + } + + var gen = SyntaxGenerator.GetGenerator(document); + + var newRoot = root; + foreach (var import in namespacesToAdd.Select(ns => gen.NamespaceImportDeclaration(ns.ToDisplayString()).WithAdditionalAnnotations(Simplifier.Annotation))) + { + newRoot = this.InsertNamespaceImport(newRoot, gen, import, options); + } + + return newRoot; + } + + protected virtual void GetExistingImportedNamespaces(Document document, SemanticModel model, HashSet namespaces) + { + // only consider top level imports + var gen = SyntaxGenerator.GetGenerator(document); + var root = model.SyntaxTree.GetRoot(); + var imports = gen.GetNamespaceImports(root); + + var symbols = imports.Select(imp => GetImportedNamespaceSymbol(imp, model)) + .OfType() + .Select(ns => model.Compilation.GetCompilationNamespace(ns)) + .ToList(); + + namespaces.AddRange(symbols); + } + + protected abstract INamespaceSymbol GetImportedNamespaceSymbol(SyntaxNode import, SemanticModel model); + protected abstract INamespaceSymbol GetExplicitNamespaceSymbol(SyntaxNode node, SemanticModel model); + protected abstract SyntaxNode InsertNamespaceImport(SyntaxNode root, SyntaxGenerator gen, SyntaxNode import, OptionSet options); + } +} diff --git a/src/Workspaces/Core/Portable/PublicAPI.txt b/src/Workspaces/Core/Portable/PublicAPI.txt index 2d8ca49219d2..657ac22c126a 100644 --- a/src/Workspaces/Core/Portable/PublicAPI.txt +++ b/src/Workspaces/Core/Portable/PublicAPI.txt @@ -231,6 +231,7 @@ Microsoft.CodeAnalysis.Editing.DocumentEditor Microsoft.CodeAnalysis.Editing.DocumentEditor.GetChangedDocument() Microsoft.CodeAnalysis.Editing.DocumentEditor.OriginalDocument.get Microsoft.CodeAnalysis.Editing.DocumentEditor.SemanticModel.get +Microsoft.CodeAnalysis.Editing.ImportAdder Microsoft.CodeAnalysis.Editing.SolutionEditor Microsoft.CodeAnalysis.Editing.SolutionEditor.GetChangedSolution() Microsoft.CodeAnalysis.Editing.SolutionEditor.GetDocumentEditorAsync(Microsoft.CodeAnalysis.DocumentId id, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) @@ -1089,6 +1090,10 @@ static Microsoft.CodeAnalysis.Editing.DeclarationModifiers.operator -(Microsoft. static Microsoft.CodeAnalysis.Editing.DeclarationModifiers.operator ==(Microsoft.CodeAnalysis.Editing.DeclarationModifiers left, Microsoft.CodeAnalysis.Editing.DeclarationModifiers right) static Microsoft.CodeAnalysis.Editing.DeclarationModifiers.operator |(Microsoft.CodeAnalysis.Editing.DeclarationModifiers left, Microsoft.CodeAnalysis.Editing.DeclarationModifiers right) static Microsoft.CodeAnalysis.Editing.DocumentEditor.CreateAsync(Microsoft.CodeAnalysis.Document document, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) +static Microsoft.CodeAnalysis.Editing.ImportAdder.AddImportsAsync(Microsoft.CodeAnalysis.Document document, Microsoft.CodeAnalysis.Options.OptionSet options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) +static Microsoft.CodeAnalysis.Editing.ImportAdder.AddImportsAsync(Microsoft.CodeAnalysis.Document document, Microsoft.CodeAnalysis.SyntaxAnnotation annotation, Microsoft.CodeAnalysis.Options.OptionSet options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) +static Microsoft.CodeAnalysis.Editing.ImportAdder.AddImportsAsync(Microsoft.CodeAnalysis.Document document, Microsoft.CodeAnalysis.Text.TextSpan span, Microsoft.CodeAnalysis.Options.OptionSet options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) +static Microsoft.CodeAnalysis.Editing.ImportAdder.AddImportsAsync(Microsoft.CodeAnalysis.Document document, System.Collections.Generic.IEnumerable spans, Microsoft.CodeAnalysis.Options.OptionSet options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) static Microsoft.CodeAnalysis.Editing.SymbolEditor.Create(Microsoft.CodeAnalysis.Document document) static Microsoft.CodeAnalysis.Editing.SymbolEditor.Create(Microsoft.CodeAnalysis.Solution solution) static Microsoft.CodeAnalysis.Editing.SymbolEditorExtensions.GetBaseOrInterfaceDeclarationReferenceAsync(this Microsoft.CodeAnalysis.Editing.SymbolEditor editor, Microsoft.CodeAnalysis.ISymbol symbol, Microsoft.CodeAnalysis.ITypeSymbol baseOrInterfaceType, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) diff --git a/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs b/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs index cb8e67870738..147bdf17d75b 100644 --- a/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs +++ b/src/Workspaces/Core/Portable/Simplification/AbstractSimplificationService.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Collections; @@ -39,8 +40,10 @@ protected virtual SyntaxNode TransformReducedNode(SyntaxNode reducedNode, Syntax { using (Logger.LogBlock(FunctionId.Simplifier_ReduceAsync, cancellationToken)) { + var spanList = spans?.ToList() ?? new List(); + // we have no span - if (!spans.Any()) + if (!spanList.Any()) { return document; } @@ -53,36 +56,31 @@ protected virtual SyntaxNode TransformReducedNode(SyntaxNode reducedNode, Syntax // Hence make sure we always start working off of the actual SemanticModel instead of a speculative SemanticModel. Contract.Assert(!semanticModel.IsSpeculativeSemanticModel); - var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); - var originalRoot = root; + var root = semanticModel.SyntaxTree.GetRoot(cancellationToken); #if DEBUG bool originalDocHasErrors = await document.HasAnyErrors(cancellationToken).ConfigureAwait(false); #endif - root = await this.ReduceAsync(document, root, semanticModel, spans, optionSet, reducers, cancellationToken).ConfigureAwait(false); + var reduced = await this.ReduceAsyncInternal(document, spanList, optionSet, reducers, cancellationToken).ConfigureAwait(false); - if (originalRoot != root) + if (reduced != document) { - document = document.WithSyntaxRoot(root); - #if DEBUG if (!originalDocHasErrors) { - await document.VerifyNoErrorsAsync("Error introduced by Simplification Service", cancellationToken).ConfigureAwait(false); + await reduced.VerifyNoErrorsAsync("Error introduced by Simplification Service", cancellationToken).ConfigureAwait(false); } #endif } - return document; + return reduced; } } - private async Task ReduceAsync( + private async Task ReduceAsyncInternal( Document document, - SyntaxNode root, - SemanticModel semanticModel, - IEnumerable spans, + List spans, OptionSet optionSet, IEnumerable reducers, CancellationToken cancellationToken) @@ -93,8 +91,24 @@ private async Task ReduceAsync( Func isNodeOrTokenOutsideSimplifySpans = (nodeOrToken) => !spansTree.GetOverlappingIntervals(nodeOrToken.FullSpan.Start, nodeOrToken.FullSpan.Length).Any(); + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var root = semanticModel.SyntaxTree.GetRoot(cancellationToken); + + // prep namespace imports marked for simplification + var removeIfUnusedAnnotation = new SyntaxAnnotation(); + var originalRoot = root; + root = this.PrepareNamespaceImportsForRemovalIfUnused(document, root, removeIfUnusedAnnotation, isNodeOrTokenOutsideSimplifySpans); + var hasImportsToSimplify = root != originalRoot; + + if (hasImportsToSimplify) + { + document = document.WithSyntaxRoot(root); + semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + root = semanticModel.SyntaxTree.GetRoot(cancellationToken); + } + // Get the list of syntax nodes and tokens that need to be reduced. - ImmutableArray nodesAndTokensToReduce = this.GetNodesAndTokensToReduce(root, isNodeOrTokenOutsideSimplifySpans); + var nodesAndTokensToReduce = this.GetNodesAndTokensToReduce(root, isNodeOrTokenOutsideSimplifySpans); if (nodesAndTokensToReduce.Any()) { @@ -121,10 +135,18 @@ private async Task ReduceAsync( computeReplacementToken: (o, n) => reducedTokensMap[o], trivia: SpecializedCollections.EmptyEnumerable(), computeReplacementTrivia: null); + + document = document.WithSyntaxRoot(root); } } - return root; + if (hasImportsToSimplify) + { + // remove any unused namespace imports that were marked for simplification + document = await this.RemoveUnusedNamespaceImportsAsync(document, removeIfUnusedAnnotation, cancellationToken).ConfigureAwait(false); + } + + return document; } private Task ReduceAsync( @@ -233,6 +255,52 @@ private Task ReduceAsync( return Task.WhenAll(simplifyTasks); } + + // find any namespace imports / using directives marked for simplification in the specified spans + // and add removeIfUnused annotation + private SyntaxNode PrepareNamespaceImportsForRemovalIfUnused( + Document document, + SyntaxNode root, + SyntaxAnnotation removeIfUnusedAnnotation, + Func isNodeOrTokenOutsideSimplifySpan) + { + var gen = SyntaxGenerator.GetGenerator(document); + + var importsToSimplify = root.DescendantNodes().Where(n => + !isNodeOrTokenOutsideSimplifySpan(n) + && gen.GetDeclarationKind(n) == DeclarationKind.NamespaceImport + && n.HasAnnotation(Simplifier.Annotation)); + + return root.ReplaceNodes(importsToSimplify, (o, r) => r.WithAdditionalAnnotations(removeIfUnusedAnnotation)); + } + + private async Task RemoveUnusedNamespaceImportsAsync( + Document document, + SyntaxAnnotation removeIfUnusedAnnotation, + CancellationToken cancellationToken) + { + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var root = model.SyntaxTree.GetRoot(); + var addedImports = root.GetAnnotatedNodes(removeIfUnusedAnnotation); + var unusedImports = new HashSet(); + this.GetUnusedNamespaceImports(model, unusedImports, cancellationToken); + + // only remove the unused imports that we added + unusedImports.IntersectWith(addedImports); + + if (unusedImports.Count > 0) + { + var gen = SyntaxGenerator.GetGenerator(document); + var newRoot = gen.RemoveNodes(root, unusedImports); + return document.WithSyntaxRoot(newRoot); + } + else + { + return document; + } + } + + protected abstract void GetUnusedNamespaceImports(SemanticModel model, HashSet namespaceImports, CancellationToken cancellationToken); } internal struct NodeOrTokenToReduce diff --git a/src/Workspaces/Core/Portable/Workspaces.csproj b/src/Workspaces/Core/Portable/Workspaces.csproj index 94d09cd71adc..054414fd8b67 100644 --- a/src/Workspaces/Core/Portable/Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Workspaces.csproj @@ -325,6 +325,10 @@ + + + + @@ -925,4 +929,4 @@ - + \ No newline at end of file diff --git a/src/Workspaces/VisualBasic/Portable/BasicWorkspace.vbproj b/src/Workspaces/VisualBasic/Portable/BasicWorkspace.vbproj index 6156e02741e5..5d6d3c85ce3c 100644 --- a/src/Workspaces/VisualBasic/Portable/BasicWorkspace.vbproj +++ b/src/Workspaces/VisualBasic/Portable/BasicWorkspace.vbproj @@ -133,6 +133,7 @@ + diff --git a/src/Workspaces/VisualBasic/Portable/Editing/VisualBasicImportAdder.vb b/src/Workspaces/VisualBasic/Portable/Editing/VisualBasicImportAdder.vb new file mode 100644 index 000000000000..a9f4bebe7692 --- /dev/null +++ b/src/Workspaces/VisualBasic/Portable/Editing/VisualBasicImportAdder.vb @@ -0,0 +1,92 @@ +' 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 System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Editing +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.CodeAnalysis.VisualBasic.Utilities + +Namespace Microsoft.CodeAnalysis.VisualBasic.Editing + + Partial Friend Class VisualBasicImportAdder + Inherits ImportAdderService + + Protected Overrides Sub GetExistingImportedNamespaces(document As Document, model As SemanticModel, namespaces As HashSet(Of INamespaceSymbol)) + namespaces.AddRange(model.Compilation.MemberImports.OfType(Of INamespaceSymbol)) + + ' consider all imports clauses + Dim root = DirectCast(model.SyntaxTree.GetRoot(), CompilationUnitSyntax) + For Each import As ImportsStatementSyntax In root.Imports + For Each clause In import.ImportsClauses + Dim symbol = GetImportedNamespaceSymbol(clause, model) + If symbol IsNot Nothing Then + namespaces.Add(symbol) + End If + Next + Next + End Sub + + Protected Overrides Function GetImportedNamespaceSymbol(namespaceImport As SyntaxNode, model As SemanticModel) As INamespaceSymbol + Select Case namespaceImport.Kind + Case SyntaxKind.ImportsStatement + Dim import = DirectCast(namespaceImport, ImportsStatementSyntax) + Return GetImportedNamespaceSymbol(import.ImportsClauses(0), model) + Case SyntaxKind.SimpleImportsClause + Dim clause = DirectCast(namespaceImport, SimpleImportsClauseSyntax) + Return TryCast(model.GetSymbolInfo(clause.Name).Symbol, INamespaceSymbol) + Case Else + Return Nothing + End Select + End Function + + Protected Overrides Function GetExplicitNamespaceSymbol(node As SyntaxNode, model As SemanticModel) As INamespaceSymbol + Dim qname = TryCast(node, QualifiedNameSyntax) + If qname IsNot Nothing Then + Return GetExplicitNamespaceSymbol(qname, qname.Left, model) + End If + + Dim maccess = TryCast(node, MemberAccessExpressionSyntax) + If maccess IsNot Nothing Then + Return GetExplicitNamespaceSymbol(maccess, maccess.Expression, model) + End If + + Return Nothing + End Function + + Private Overloads Function GetExplicitNamespaceSymbol(fullName As ExpressionSyntax, namespacePart As ExpressionSyntax, model As SemanticModel) As INamespaceSymbol + ' name must refer to something that is not a namespace, but be qualified with a namespace. + Dim Symbol = model.GetSymbolInfo(fullName).Symbol + Dim nsSymbol = TryCast(model.GetSymbolInfo(namespacePart).Symbol, INamespaceSymbol) + + If Symbol IsNot Nothing AndAlso Symbol.Kind <> SymbolKind.Namespace AndAlso nsSymbol IsNot Nothing Then + ' use the symbols containing namespace, and not the potentially less than fully qualified namespace in the full name expression. + Dim ns = Symbol.ContainingNamespace + If ns IsNot Nothing Then + Return model.Compilation.GetCompilationNamespace(ns) + End If + End If + + Return Nothing + End Function + + Protected Overrides Function InsertNamespaceImport(root As SyntaxNode, gen As SyntaxGenerator, import As SyntaxNode, options As OptionSet) As SyntaxNode + Dim comparer = If(options.GetOption(GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.VisualBasic), + ImportsStatementComparer.SystemFirstInstance, + ImportsStatementComparer.NormalInstance) + + ' find insertion point + For Each existingImport As SyntaxNode In gen.GetNamespaceImports(root) + If comparer.Compare(DirectCast(import, ImportsStatementSyntax), DirectCast(existingImport, ImportsStatementSyntax)) < 0 Then + Return gen.InsertNodesBefore(root, existingImport, {import}) + End If + Next + + Return gen.AddNamespaceImports(root, import) + End Function + + End Class +End Namespace diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.vb b/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.vb index be6dbcf565cb..28330b57ff6a 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/VisualBasicSimplificationService.vb @@ -4,6 +4,7 @@ Imports System.Collections.Immutable Imports System.Composition Imports System.Threading Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Editing Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Internal.Log Imports Microsoft.CodeAnalysis.Simplification @@ -162,5 +163,25 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification TypeOf node Is VariableDeclaratorSyntax AndAlso TypeOf node.Parent Is FieldDeclarationSyntax End Function + + Private Shared ReadOnly BC50000_UnusedImportsClause As String = "BC50000" + Private Shared ReadOnly BC50001_UnusedImportsStatement As String = "BC50001" + + Protected Overrides Sub GetUnusedNamespaceImports(model As SemanticModel, namespaceImports As HashSet(Of SyntaxNode), cancellationToken As CancellationToken) + Dim root = model.SyntaxTree.GetRoot() + Dim diagnostics = model.GetDiagnostics(cancellationToken:=cancellationToken) + + For Each diagnostic In diagnostics + If diagnostic.Id = BC50000_UnusedImportsClause OrElse diagnostic.Id = BC50001_UnusedImportsStatement Then + Dim node = root.FindNode(diagnostic.Location.SourceSpan) + Dim statement = TryCast(node, ImportsStatementSyntax) + Dim clause = TryCast(node, ImportsStatementSyntax) + If statement IsNot Nothing Or clause IsNot Nothing Then + namespaceImports.Add(node) + End If + End If + Next + End Sub + End Class End Namespace diff --git a/src/Workspaces/VisualBasicTest/CodeGeneration/AddImportsTests.vb b/src/Workspaces/VisualBasicTest/CodeGeneration/AddImportsTests.vb new file mode 100644 index 000000000000..f8a9fa37f4d5 --- /dev/null +++ b/src/Workspaces/VisualBasicTest/CodeGeneration/AddImportsTests.vb @@ -0,0 +1,322 @@ +' 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 Microsoft.CodeAnalysis.Editing +Imports Microsoft.CodeAnalysis.Formatting +Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.CodeAnalysis.Simplification +Imports Xunit + +Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Editting + Public Class AddImportsTests + Private ReadOnly _ws As AdhocWorkspace = New AdhocWorkspace() + Private ReadOnly _emptyProject As Project + + Public Sub New() + _emptyProject = _ws.AddProject( + ProjectInfo.Create( + ProjectId.CreateNewId(), + VersionStamp.Default, + "test", + "test.dll", + LanguageNames.VisualBasic, + metadataReferences:={TestReferences.NetFx.v4_0_30319.mscorlib})) + End Sub + + Private Function GetDocument(code As String, Optional globalImports As String() = Nothing) As Document + Dim project = _emptyProject + + If globalImports IsNot Nothing Then + Dim gi = GlobalImport.Parse(globalImports) + project = project.WithCompilationOptions(DirectCast(project.CompilationOptions, VisualBasicCompilationOptions).WithGlobalImports(gi)) + End If + + Return project.AddDocument("test.cs", code) + End Function + + Private Sub Test(initialText As String, importsAddedText As String, simplifiedText As String, Optional options As OptionSet = Nothing, Optional globalImports As String() = Nothing) + + Dim doc = GetDocument(initialText, globalImports) + options = If(options, doc.Project.Solution.Workspace.Options) + + Dim imported = ImportAdder.AddImportsAsync(doc, options).Result + + If importsAddedText IsNot Nothing Then + Dim formatted = Formatter.FormatAsync(imported, SyntaxAnnotation.ElasticAnnotation, options).Result + Dim actualText = formatted.GetTextAsync().Result.ToString() + Assert.Equal(importsAddedText, actualText) + End If + + If simplifiedText IsNot Nothing Then + Dim reduced = Simplifier.ReduceAsync(imported, options).Result + Dim formatted = Formatter.FormatAsync(reduced, SyntaxAnnotation.ElasticAnnotation, options).Result + Dim actualText = formatted.GetTextAsync().Result.ToString() + Assert.Equal(simplifiedText, actualText) + End If + End Sub + + + Public Sub TestAddImport() + Test( +"Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Imports System.Collections.Generic + +Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Imports System.Collections.Generic + +Class C + Public F As List(Of Integer) +End Class") + End Sub + + + Public Sub TestAddSystemImportFirst() + Test( +"Imports N + +Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Imports System.Collections.Generic +Imports N + +Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Imports System.Collections.Generic +Imports N + +Class C + Public F As List(Of Integer) +End Class") + End Sub + + + Public Sub TestDontAddSystemImportFirst() + Test( +"Imports N + +Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Imports N +Imports System.Collections.Generic + +Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Imports N +Imports System.Collections.Generic + +Class C + Public F As List(Of Integer) +End Class", +_ws.Options.WithChangedOption(GenerationOptions.PlaceSystemNamespaceFirst, LanguageNames.VisualBasic, False)) + End Sub + + + Public Sub TestAddImportsInOrder() + Test( +"Imports System.Collections +Imports System.Diagnostics + +Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Imports System.Collections +Imports System.Collections.Generic +Imports System.Diagnostics + +Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Imports System.Collections +Imports System.Collections.Generic +Imports System.Diagnostics + +Class C + Public F As List(Of Integer) +End Class") + End Sub + + + Public Sub TestAddMultipleImportsInOrder() + Test( +"Imports System.Collections +Imports System.Diagnostics + +Class C + Public F As System.Collections.Generic.List(Of Integer) + Public Handler As System.EventHandler +End Class", +"Imports System +Imports System.Collections +Imports System.Collections.Generic +Imports System.Diagnostics + +Class C + Public F As System.Collections.Generic.List(Of Integer) + Public Handler As System.EventHandler +End Class", +"Imports System +Imports System.Collections +Imports System.Collections.Generic +Imports System.Diagnostics + +Class C + Public F As List(Of Integer) + Public Handler As EventHandler +End Class") + End Sub + + + Public Sub TestImportNotAddedAgainIfAlreadyExists() + Test( +"Imports System.Collections.Generic + +Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Imports System.Collections.Generic + +Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Imports System.Collections.Generic + +Class C + Public F As List(Of Integer) +End Class") + End Sub + + + Public Sub TestUnusedAddedImportIsRemovedBySimplifier() + Test( +"Class C + Public F As System.Int32 +End Class", +"Imports System + +Class C + Public F As System.Int32 +End Class", +"Class C + Public F As Integer +End Class") + End Sub + + + Public Sub TestImportNotAddedIfGloballyImported() + Test( +"Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Class C + Public F As System.Collections.Generic.List(Of Integer) +End Class", +"Class C + Public F As List(Of Integer) +End Class", +globalImports:={"System.Collections.Generic"}) + + End Sub + + + Public Sub TestImportNotAddedForNamespaceDeclarations() + Test( +"Namespace N +End Namespace", +"Namespace N +End Namespace", +"Namespace N +End Namespace") + End Sub + + +Public Sub TestImportAddedAndRemovedForReferencesInsideNamespaceDeclarations() + Test( +"Namespace N + Class C + Private _c As N.C + End Class +End Namespace", +"Imports N + +Namespace N + Class C + Private _c As N.C + End Class +End Namespace", +"Namespace N + Class C + Private _c As C + End Class +End Namespace") + End Sub + + + Public Sub TestRemoveImportIfItMakesReferencesAmbiguous() + ' this is not really an artifact of the AddImports feature, it is due + ' to Simplifier not reducing the namespace reference because it would + ' become ambiguous, thus leaving an unused imports statement + + Test( +"Namespace N + Class C + End Class +End Namespace + +Class C + Private F As N.C +End Class +", +"Imports N + +Namespace N + Class C + End Class +End Namespace + +Class C + Private F As N.C +End Class +", +"Namespace N + Class C + End Class +End Namespace + +Class C + Private F As N.C +End Class +") + End Sub + + Private Sub TestPartialNamespacesNotUsed() + Test( +"Imports System.Collections + +Public Class C + Public F1 As ArrayList + Public F2 As System.Collections.Generic.List(Of Integer) +End Class", +"Imports System.Collections +Imports System.Collections.Generic + +Public Class C + Public F1 As ArrayList + Public F2 As System.Collections.Generic.List(Of Integer) +End Class", +"Imports System.Collections +Imports System.Collections.Generic + +Public Class C + Public F1 As ArrayList + Public F2 As List(Of Integer) +End Class") + End Sub + End Class +End Namespace diff --git a/src/Workspaces/VisualBasicTest/VisualBasicServicesTest.vbproj b/src/Workspaces/VisualBasicTest/VisualBasicServicesTest.vbproj index 4909da3170d9..506d5fd1dce8 100644 --- a/src/Workspaces/VisualBasicTest/VisualBasicServicesTest.vbproj +++ b/src/Workspaces/VisualBasicTest/VisualBasicServicesTest.vbproj @@ -101,6 +101,7 @@ + @@ -130,4 +131,4 @@ - + \ No newline at end of file