未验证 提交 bdcf1326 编写于 作者: J Joey Robichaud 提交者: GitHub

Merge pull request #37228 from YairHalberstadt/feature/add-import-annotation

Feature/add import annotation
......@@ -1982,7 +1982,12 @@ static void Main(string[] args)
{
System.Console.WriteLine([|^1|]);
}
}", TestSources.Index + @"
}",
@"
using System;" +
TestSources.Index +
@"
class Program
{
static void Main(string[] args)
......@@ -1990,7 +1995,7 @@ static void Main(string[] args)
System.Console.WriteLine({|Rename:NewMethod|}());
}
private static System.Index NewMethod()
private static Index NewMethod()
{
return ^1;
}
......@@ -2007,7 +2012,12 @@ static void Main(string[] args)
{
System.Console.WriteLine([|..|]);
}
}", TestSources.Index + TestSources.Range + @"
}",
@"
using System;" +
TestSources.Index +
TestSources.Range + @"
class Program
{
static void Main(string[] args)
......@@ -2015,7 +2025,7 @@ static void Main(string[] args)
System.Console.WriteLine({|Rename:NewMethod|}());
}
private static System.Range NewMethod()
private static Range NewMethod()
{
return ..;
}
......@@ -2032,7 +2042,12 @@ static void Main(string[] args)
{
System.Console.WriteLine([|..1|]);
}
}", TestSources.Index + TestSources.Range + @"
}",
@"
using System;" +
TestSources.Index +
TestSources.Range + @"
class Program
{
static void Main(string[] args)
......@@ -2040,7 +2055,7 @@ static void Main(string[] args)
System.Console.WriteLine({|Rename:NewMethod|}());
}
private static System.Range NewMethod()
private static Range NewMethod()
{
return ..1;
}
......@@ -2057,7 +2072,12 @@ static void Main(string[] args)
{
System.Console.WriteLine([|1..|]);
}
}", TestSources.Index + TestSources.Range + @"
}",
@"
using System;" +
TestSources.Index +
TestSources.Range + @"
class Program
{
static void Main(string[] args)
......@@ -2065,7 +2085,7 @@ static void Main(string[] args)
System.Console.WriteLine({|Rename:NewMethod|}());
}
private static System.Range NewMethod()
private static Range NewMethod()
{
return 1..;
}
......@@ -2082,7 +2102,12 @@ static void Main(string[] args)
{
System.Console.WriteLine([|1..2|]);
}
}", TestSources.Index + TestSources.Range + @"
}",
@"
using System;" +
TestSources.Index +
TestSources.Range + @"
class Program
{
static void Main(string[] args)
......@@ -2090,7 +2115,7 @@ static void Main(string[] args)
System.Console.WriteLine({|Rename:NewMethod|}());
}
private static System.Range NewMethod()
private static Range NewMethod()
{
return 1..2;
}
......
......@@ -620,6 +620,8 @@ void Method()
}
";
var expected = @"
using System.Collections.Generic;
class Test
{
void Method()
......@@ -643,14 +645,14 @@ public override bool Equals(object obj)
{
return obj is NewClass other &&
A == other.A &&
System.Collections.Generic.EqualityComparer<object>.Default.Equals(B, other.B);
EqualityComparer<object>.Default.Equals(B, other.B);
}
public override int GetHashCode()
{
var hashCode = -1817952719;
hashCode = hashCode * -1521134295 + A.GetHashCode();
hashCode = hashCode * -1521134295 + System.Collections.Generic.EqualityComparer<object>.Default.GetHashCode(B);
hashCode = hashCode * -1521134295 + EqualityComparer<object>.Default.GetHashCode(B);
return hashCode;
}
}";
......
......@@ -1163,6 +1163,8 @@ void Method()
}
";
var expected = @"
using System.Collections.Generic;
class Test
{
void Method()
......@@ -1186,14 +1188,14 @@ public override bool Equals(object obj)
{
return obj is NewStruct other &&
a == other.a &&
System.Collections.Generic.EqualityComparer<object>.Default.Equals(b, other.b);
EqualityComparer<object>.Default.Equals(b, other.b);
}
public override int GetHashCode()
{
var hashCode = 2118541809;
hashCode = hashCode * -1521134295 + a.GetHashCode();
hashCode = hashCode * -1521134295 + System.Collections.Generic.EqualityComparer<object>.Default.GetHashCode(b);
hashCode = hashCode * -1521134295 + EqualityComparer<object>.Default.GetHashCode(b);
return hashCode;
}
......@@ -1229,6 +1231,8 @@ void Method()
}
";
var expected = @"
using System.Collections.Generic;
class Test
{
void Method()
......@@ -1252,14 +1256,14 @@ public override bool Equals(object obj)
{
return obj is NewStruct other &&
a == other.a &&
System.Collections.Generic.EqualityComparer<object>.Default.Equals(b, other.b);
EqualityComparer<object>.Default.Equals(b, other.b);
}
public override int GetHashCode()
{
var hashCode = 2118541809;
hashCode = hashCode * -1521134295 + a.GetHashCode();
hashCode = hashCode * -1521134295 + System.Collections.Generic.EqualityComparer<object>.Default.GetHashCode(b);
hashCode = hashCode * -1521134295 + EqualityComparer<object>.Default.GetHashCode(b);
return hashCode;
}
......@@ -2067,6 +2071,7 @@ void Blah<T>(T t)
";
var expected = @"
using System;
using System.Collections.Generic;
class Test<T>
{
......@@ -2101,14 +2106,14 @@ public NewStruct(T a, int b)
public override bool Equals(object obj)
{
return obj is NewStruct<T> other &&
System.Collections.Generic.EqualityComparer<T>.Default.Equals(a, other.a) &&
EqualityComparer<T>.Default.Equals(a, other.a) &&
b == other.b;
}
public override int GetHashCode()
{
var hashCode = 2118541809;
hashCode = hashCode * -1521134295 + System.Collections.Generic.EqualityComparer<T>.Default.GetHashCode(a);
hashCode = hashCode * -1521134295 + EqualityComparer<T>.Default.GetHashCode(a);
hashCode = hashCode * -1521134295 + b.GetHashCode();
return hashCode;
}
......
......@@ -5725,7 +5725,7 @@ void TestMethod(IEnumerable<C> c)
}
}",
@"using System;
using System.Collections.Generic;
using System.Collections.Generic;
class C
{
void TestMethod(IEnumerable<C> c)
......
......@@ -42,6 +42,7 @@ public async Task BracketedIdentifierSimplificationTest()
' mscorlib.v4_6_1038_0.dll
#End Region
Imports System
Imports System.Runtime.InteropServices
Namespace System
......
......@@ -28,7 +28,7 @@ internal class ExtractInterfaceTestState : IDisposable
public static ExtractInterfaceTestState Create(string markup, string languageName, CompilationOptions compilationOptions)
{
var exportProvider = ExportProviderFactory.CreateExportProvider();
var exportProvider = TestExportProvider.ExportProviderWithCSharpAndVisualBasic;
var workspace = languageName == LanguageNames.CSharp
? TestWorkspace.CreateCSharp(markup, exportProvider: exportProvider, compilationOptions: compilationOptions as CSharpCompilationOptions)
: TestWorkspace.CreateVisualBasic(markup, exportProvider: exportProvider, compilationOptions: compilationOptions);
......
......@@ -444,6 +444,8 @@ class Test
end class
"
Dim expected = "
Imports System.Collections.Generic
class Test
sub Method()
dim t1 = New {|Rename:NewClass|}(1, directcast(New NewClass(1, directcast(nothing, object)), object))
......@@ -463,13 +465,13 @@ Friend Class NewClass
Dim other = TryCast(obj, NewClass)
Return other IsNot Nothing AndAlso
A = other.A AndAlso
System.Collections.Generic.EqualityComparer(Of Object).Default.Equals(B, other.B)
EqualityComparer(Of Object).Default.Equals(B, other.B)
End Function
Public Overrides Function GetHashCode() As Integer
Dim hashCode As Long = -1817952719
hashCode = (hashCode * -1521134295 + A.GetHashCode()).GetHashCode()
hashCode = (hashCode * -1521134295 + System.Collections.Generic.EqualityComparer(Of Object).Default.GetHashCode(B)).GetHashCode()
hashCode = (hashCode * -1521134295 + EqualityComparer(Of Object).Default.GetHashCode(B)).GetHashCode()
Return hashCode
End Function
End Class
......@@ -570,7 +572,7 @@ End Class
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToClass)>
Public Async Function CapturedTypeParameters() As Task
Dim text = "
imports system.collections.generic
imports System.Collections.Generic
class Test(of X as {structure})
sub Method(of Y as {class, new})(lst as List(of X), arr as Y())
......@@ -579,7 +581,7 @@ class Test(of X as {structure})
end class
"
Dim expected = "
imports system.collections.generic
imports System.Collections.Generic
class Test(of X as {structure})
sub Method(of Y as {class, new})(lst as List(of X), arr as Y())
......
......@@ -1031,6 +1031,8 @@ class Test
end sub
end class"
Dim expected = "
Imports System.Collections.Generic
class Test
sub Method()
dim t1 = New {|Rename:NewStruct|}(a:=1, directcast(New NewStruct(a:=1, directcast(nothing, object)), object))
......@@ -1053,13 +1055,13 @@ Friend Structure NewStruct
Dim other = DirectCast(obj, NewStruct)
Return a = other.a AndAlso
System.Collections.Generic.EqualityComparer(Of Object).Default.Equals(b, other.b)
EqualityComparer(Of Object).Default.Equals(b, other.b)
End Function
Public Overrides Function GetHashCode() As Integer
Dim hashCode As Long = 2118541809
hashCode = (hashCode * -1521134295 + a.GetHashCode()).GetHashCode()
hashCode = (hashCode * -1521134295 + System.Collections.Generic.EqualityComparer(Of Object).Default.GetHashCode(b)).GetHashCode()
hashCode = (hashCode * -1521134295 + EqualityComparer(Of Object).Default.GetHashCode(b)).GetHashCode()
Return hashCode
End Function
......@@ -1089,6 +1091,8 @@ class Test
end sub
end class"
Dim expected = "
Imports System.Collections.Generic
class Test
sub Method()
dim t1 = New NewStruct(a:=1, directcast(New {|Rename:NewStruct|}(a:=1, directcast(nothing, object)), object))
......@@ -1111,13 +1115,13 @@ Friend Structure NewStruct
Dim other = DirectCast(obj, NewStruct)
Return a = other.a AndAlso
System.Collections.Generic.EqualityComparer(Of Object).Default.Equals(b, other.b)
EqualityComparer(Of Object).Default.Equals(b, other.b)
End Function
Public Overrides Function GetHashCode() As Integer
Dim hashCode As Long = 2118541809
hashCode = (hashCode * -1521134295 + a.GetHashCode()).GetHashCode()
hashCode = (hashCode * -1521134295 + System.Collections.Generic.EqualityComparer(Of Object).Default.GetHashCode(b)).GetHashCode()
hashCode = (hashCode * -1521134295 + EqualityComparer(Of Object).Default.GetHashCode(b)).GetHashCode()
Return hashCode
End Function
......@@ -1201,7 +1205,7 @@ End Structure
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertTupleToStruct)>
Public Async Function CapturedMethodTypeParameters() As Task
Dim text = "
imports system.collections.generic
imports System.Collections.Generic
class Test(of X as {structure})
sub Method(of Y as {class, new})(x as List(of X), y as Y())
......@@ -1209,7 +1213,7 @@ class Test(of X as {structure})
end sub
end class"
Dim expected = "
imports system.collections.generic
imports System.Collections.Generic
class Test(of X as {structure})
sub Method(of Y as {class, new})(x as List(of X), y as Y())
......@@ -1759,6 +1763,7 @@ class Test(of T)
end class"
Dim expected = "
imports System
Imports System.Collections.Generic
class Test(of T)
sub Method(t as T)
......@@ -1790,13 +1795,13 @@ Friend Structure NewStruct(Of T)
End If
Dim other = DirectCast(obj, NewStruct(Of T))
Return Collections.Generic.EqualityComparer(Of T).Default.Equals(a, other.a) AndAlso
Return EqualityComparer(Of T).Default.Equals(a, other.a) AndAlso
b = other.b
End Function
Public Overrides Function GetHashCode() As Integer
Dim hashCode As Long = 2118541809
hashCode = (hashCode * -1521134295 + Collections.Generic.EqualityComparer(Of T).Default.GetHashCode(a)).GetHashCode()
hashCode = (hashCode * -1521134295 + EqualityComparer(Of T).Default.GetHashCode(a)).GetHashCode()
hashCode = (hashCode * -1521134295 + b.GetHashCode()).GetHashCode()
Return hashCode
End Function
......
......@@ -3902,7 +3902,7 @@ Module M
Dim x As Boolean = Await [|F|]().ConfigureAwait(False)
End Sub
End Module",
"Imports System
"Imports System
Imports System.Linq
Imports System.Threading.Tasks
......
......@@ -376,7 +376,7 @@ private string GetUsingDirectiveString(INamespaceOrTypeSymbol namespaceOrTypeSym
var addImportService = document.GetLanguageService<IAddImportsService>();
var newRoot = addImportService.AddImports(
semanticModel.Compilation, root, contextNode, newImports, placeSystemNamespaceFirst);
semanticModel.Compilation, root, contextNode, newImports, placeSystemNamespaceFirst, cancellationToken);
return (CompilationUnitSyntax)newRoot;
}
finally
......@@ -397,7 +397,7 @@ private string GetUsingDirectiveString(INamespaceOrTypeSymbol namespaceOrTypeSym
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var service = document.GetLanguageService<IAddImportsService>();
var newRoot = service.AddImport(
compilation, root, contextNode, usingDirective, placeSystemNamespaceFirst);
compilation, root, contextNode, usingDirective, placeSystemNamespaceFirst, cancellationToken);
return document.WithSyntaxRoot(newRoot);
}
......
......@@ -52,7 +52,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
codeActionsBuilder.Add(new MyCodeAction(codeActionPreviewText, c =>
{
var aliasDirective = syntaxGenerator.AliasImportDeclaration(typeName, symbol);
var newRoot = addImportService.AddImport(compilation, root, diagnosticNode, aliasDirective, placeSystemNamespaceFirst);
var newRoot = addImportService.AddImport(compilation, root, diagnosticNode, aliasDirective, placeSystemNamespaceFirst, cancellationToken);
return Task.FromResult(document.WithSyntaxRoot(newRoot));
}));
}
......
......@@ -789,7 +789,7 @@ await FixReferencesAsync(document, changeNamespaceService, addImportService, ref
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
root = addImportService.AddImports(compilation, root, contextLocation, imports, placeSystemNamespaceFirst);
root = addImportService.AddImports(compilation, root, contextLocation, imports, placeSystemNamespaceFirst, cancellationToken);
document = document.WithSyntaxRoot(root);
}
......
......@@ -243,7 +243,7 @@ internal override async Task<CompletionChange> GetChangeAsync(Document document,
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var importNode = CreateImport(document, containingNamespace);
var rootWithImport = addImportService.AddImport(compilation, root, addImportContextNode, importNode, placeSystemNamespaceFirst);
var rootWithImport = addImportService.AddImport(compilation, root, addImportContextNode, importNode, placeSystemNamespaceFirst, cancellationToken);
var documentWithImport = document.WithSyntaxRoot(rootWithImport);
var formattedDocumentWithImport = await Formatter.FormatAsync(documentWithImport, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
......
......@@ -106,15 +106,9 @@ private async Task<Document> UpdateDocumentAndAddImportsAsync(SyntaxNode oldType
var oldRoot = await _document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newDocument = _document.WithSyntaxRoot(
oldRoot.ReplaceNode(oldType, newType));
var options = await _document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var placeSystemNamespaceFirst = options.GetOption(GenerationOptions.PlaceSystemNamespaceFirst);
var codeGenService = _document.GetLanguageService<ICodeGenerationService>();
newDocument = await codeGenService.AddImportsAsync(
newDocument = await ImportAdder.AddImportsFromSymbolAnnotationAsync(
newDocument,
new CodeGenerationOptions(placeSystemNamespaceFirst: placeSystemNamespaceFirst),
cancellationToken).ConfigureAwait(false);
cancellationToken: cancellationToken).ConfigureAwait(false);
return newDocument;
}
......
......@@ -3,10 +3,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.LanguageServices;
......@@ -44,6 +46,8 @@ public async Task<Document> AddSourceToAsync(Document document, Compilation symb
CreateCodeGenerationOptions(newSemanticModel.SyntaxTree.GetLocation(new TextSpan()), symbol),
cancellationToken).ConfigureAwait(false);
document = await RemoveSimplifierAnnotationsFromImports(document, cancellationToken).ConfigureAwait(false);
var docCommentFormattingService = document.GetLanguageService<IDocumentationCommentFormattingService>();
var docWithDocComments = await ConvertDocCommentsToRegularComments(document, docCommentFormattingService, cancellationToken).ConfigureAwait(false);
......@@ -56,6 +60,27 @@ public async Task<Document> AddSourceToAsync(Document document, Compilation symb
return await Simplifier.ReduceAsync(formattedDoc, reducers, null, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// <see cref="ImportAdderService"/> adds <see cref="Simplifier.Annotation"/> to Import Directives it adds,
/// which causes the <see cref="Simplifier"/> to remove import directives when thety are only used by attributes.
/// Presumably this is because MetadataAsSource isn't actually semantically valid code.
///
/// To fix this we remove these annotations.
/// </summary>
private static async Task<Document> RemoveSimplifierAnnotationsFromImports(Document document, CancellationToken cancellationToken)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var importDirectives = (await document.GetSyntaxRootAsync().ConfigureAwait(false))
.DescendantNodesAndSelf()
.Where(syntaxFacts.IsUsingOrExternOrImport);
return await document.ReplaceNodesAsync(
importDirectives,
(o, c) => c.WithoutAnnotations(Simplifier.Annotation),
cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// provide formatting rules to be used when formatting MAS file
/// </summary>
......
......@@ -292,7 +292,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.AddImport
Dim importService = document.GetLanguageService(Of IAddImportsService)
Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
Dim newRoot = importService.AddImport(compilation, root, contextNode, importsStatement, placeSystemNamespaceFirst)
Dim newRoot = importService.AddImport(compilation, root, contextNode, importsStatement, placeSystemNamespaceFirst, cancellationToken)
newRoot = newRoot.WithAdditionalAnnotations(CaseCorrector.Annotation, Formatter.Annotation)
Dim newDocument = document.WithSyntaxRoot(newRoot)
......
......@@ -111,7 +111,7 @@ public override int GetExpansionFunction(IXMLDOMNode xmlFunctionNode, string bst
var addImportService = document.GetLanguageService<IAddImportsService>();
var compilation = document.Project.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken);
var newRoot = addImportService.AddImports(compilation, root, contextLocation, newUsingDirectives, placeSystemNamespaceFirst);
var newRoot = addImportService.AddImports(compilation, root, contextLocation, newUsingDirectives, placeSystemNamespaceFirst, cancellationToken);
var newDocument = document.WithSyntaxRoot(newRoot);
......
......@@ -4,9 +4,11 @@
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using Microsoft.CodeAnalysis.AddImports;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.CSharp.AddImports
......@@ -40,12 +42,13 @@ protected override bool IsStaticUsing(UsingDirectiveSyntax usingOrAlias)
SyntaxNode staticUsingContainer,
SyntaxNode aliasContainer,
bool placeSystemNamespaceFirst,
SyntaxNode root)
SyntaxNode root,
CancellationToken cancellationToken)
{
var rewriter = new Rewriter(
externAliases, usingDirectives, staticUsingDirectives,
aliasDirectives, externContainer, usingContainer,
staticUsingContainer, aliasContainer, placeSystemNamespaceFirst);
staticUsingContainer, aliasContainer, placeSystemNamespaceFirst, cancellationToken);
var newRoot = rewriter.Visit(root);
return newRoot;
......@@ -67,9 +70,15 @@ protected override SyntaxList<ExternAliasDirectiveSyntax> GetExterns(SyntaxNode
_ => default,
};
protected override bool IsEquivalentImport(SyntaxNode a, SyntaxNode b)
{
return a.IsEquivalentTo(b, topLevel: false);
}
private class Rewriter : CSharpSyntaxRewriter
{
private readonly bool _placeSystemNamespaceFirst;
private readonly CancellationToken _cancellationToken;
private readonly SyntaxNode _externContainer;
private readonly SyntaxNode _usingContainer;
private readonly SyntaxNode _aliasContainer;
......@@ -89,7 +98,8 @@ private class Rewriter : CSharpSyntaxRewriter
SyntaxNode usingContainer,
SyntaxNode aliasContainer,
SyntaxNode staticUsingContainer,
bool placeSystemNamespaceFirst)
bool placeSystemNamespaceFirst,
CancellationToken cancellationToken)
{
_externAliases = externAliases;
_usingDirectives = usingDirectives;
......@@ -100,6 +110,7 @@ private class Rewriter : CSharpSyntaxRewriter
_aliasContainer = aliasContainer;
_staticUsingContainer = staticUsingContainer;
_placeSystemNamespaceFirst = placeSystemNamespaceFirst;
_cancellationToken = cancellationToken;
}
public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
......@@ -107,6 +118,11 @@ public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax
// recurse downwards so we visit inner namespaces first.
var rewritten = (NamespaceDeclarationSyntax)base.VisitNamespaceDeclaration(node);
if (!node.CanAddUsingDirectives(_cancellationToken))
{
return rewritten;
}
if (node == _aliasContainer)
{
rewritten = rewritten.AddUsingDirectives(_aliasDirectives, _placeSystemNamespaceFirst);
......@@ -135,6 +151,11 @@ public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node)
// recurse downwards so we visit inner namespaces first.
var rewritten = (CompilationUnitSyntax)base.VisitCompilationUnit(node);
if (!node.CanAddUsingDirectives(_cancellationToken))
{
return rewritten;
}
if (node == _aliasContainer)
{
rewritten = rewritten.AddUsingDirectives(_aliasDirectives, _placeSystemNamespaceFirst);
......
......@@ -34,12 +34,6 @@ public override CodeGenerationDestination GetDestination(SyntaxNode node)
protected override IComparer<SyntaxNode> GetMemberComparer()
=> CSharpDeclarationComparer.WithoutNamesInstance;
protected override AbstractImportsAdder CreateImportsAdder(
Document document)
{
return new UsingDirectivesAdder(document);
}
protected override IList<bool> GetAvailableInsertionIndices(SyntaxNode destination, CancellationToken cancellationToken)
{
if (destination is TypeDeclarationSyntax typeDeclaration)
......
// 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 Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration
{
internal partial class UsingDirectivesAdder
{
private class Rewriter : CSharpSyntaxRewriter
{
private readonly Document _document;
private readonly IDictionary<SyntaxNode, IList<INamespaceSymbol>> _namespacesToImport;
private readonly CancellationToken _cancellationToken;
private readonly bool _placeSystemNamespaceFirst;
public Rewriter(
Document document,
IDictionary<SyntaxNode, IList<INamespaceSymbol>> namespacesToImport,
bool placeSystemNamespaceFirst,
CancellationToken cancellationToken)
{
_document = document;
_namespacesToImport = namespacesToImport;
_placeSystemNamespaceFirst = placeSystemNamespaceFirst;
_cancellationToken = cancellationToken;
}
private IList<UsingDirectiveSyntax> CreateDirectives(IList<INamespaceSymbol> namespaces)
{
var usingDirectives =
from n in namespaces
let displayString = n.ToDisplayString(SymbolDisplayFormats.NameFormat)
let name = SyntaxFactory.ParseName(displayString).WithAdditionalAnnotations(Simplifier.Annotation)
select SyntaxFactory.UsingDirective(name);
return usingDirectives.ToList();
}
public override SyntaxNode VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
{
var result = (NamespaceDeclarationSyntax)base.VisitNamespaceDeclaration(node);
if (!_namespacesToImport.TryGetValue(node, out var namespaces))
{
return result;
}
if (!result.CanAddUsingDirectives(_cancellationToken))
{
return result;
}
var directives = CreateDirectives(namespaces);
return result.AddUsingDirectives(directives, _placeSystemNamespaceFirst, Formatter.Annotation);
}
public override SyntaxNode VisitCompilationUnit(CompilationUnitSyntax node)
{
var result = (CompilationUnitSyntax)base.VisitCompilationUnit(node);
if (!_namespacesToImport.TryGetValue(node, out var namespaces))
{
return result;
}
if (!result.CanAddUsingDirectives(_cancellationToken))
{
return result;
}
var directives = CreateDirectives(namespaces);
return result.AddUsingDirectives(directives, _placeSystemNamespaceFirst, Formatter.Annotation);
}
}
}
}
// 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.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration
{
internal partial class UsingDirectivesAdder : AbstractImportsAdder
{
public UsingDirectivesAdder(Document document)
: base(document)
{
}
protected override SyntaxNode GetImportsContainer(SyntaxNode node)
{
return node.GetInnermostNamespaceDeclarationWithUsings() ?? (SyntaxNode)node.GetAncestorOrThis<CompilationUnitSyntax>();
}
protected override SyntaxNode GetInnermostNamespaceScope(SyntaxNodeOrToken nodeOrToken)
{
var node = nodeOrToken.IsNode ? nodeOrToken.AsNode() : nodeOrToken.Parent;
return node.GetAncestorOrThis<NamespaceDeclarationSyntax>() ?? (SyntaxNode)node.GetAncestorOrThis<CompilationUnitSyntax>();
}
protected override IList<INamespaceSymbol> GetExistingNamespaces(
SemanticModel semanticModel,
SyntaxNode contextNode,
CancellationToken cancellationToken)
{
return contextNode is NamespaceDeclarationSyntax
? GetExistingNamespaces(semanticModel, (NamespaceDeclarationSyntax)contextNode, cancellationToken)
: GetExistingNamespaces(semanticModel, (CompilationUnitSyntax)contextNode, cancellationToken);
}
private IList<INamespaceSymbol> GetExistingNamespaces(
SemanticModel semanticModel, NamespaceDeclarationSyntax namespaceDeclaration, CancellationToken cancellationToken)
{
var q = from u in namespaceDeclaration.Usings
let symbol = semanticModel.GetSymbolInfo(u.Name, cancellationToken).Symbol as INamespaceSymbol
where symbol != null && !symbol.IsGlobalNamespace
select symbol;
var usingImports = q.ToList();
var namespaceSymbol = semanticModel.GetDeclaredSymbol(namespaceDeclaration, cancellationToken) as INamespaceSymbol;
var namespaceImports = GetContainingNamespacesAndThis(namespaceSymbol).ToList();
var outerNamespaces = this.GetExistingNamespaces(semanticModel, namespaceDeclaration.Parent, cancellationToken);
return outerNamespaces.Concat(namespaceImports).Concat(usingImports)
.Distinct()
.OrderBy(INamespaceSymbolExtensions.CompareNamespaces)
.ToList();
}
private IList<INamespaceSymbol> GetExistingNamespaces(
SemanticModel semanticModel, CompilationUnitSyntax compilationUnit, CancellationToken cancellationToken)
{
var q = from u in compilationUnit.Usings
let symbol = semanticModel.GetSymbolInfo(u.Name, cancellationToken).Symbol as INamespaceSymbol
where symbol != null && !symbol.IsGlobalNamespace
select symbol;
return q.ToList();
}
public override async Task<Document> AddAsync(
bool placeSystemNamespaceFirst,
CodeGenerationOptions options,
CancellationToken cancellationToken)
{
var importsContainerToMissingNamespaces = await DetermineNamespaceToImportAsync(
options, cancellationToken).ConfigureAwait(false);
if (importsContainerToMissingNamespaces.Count == 0)
{
return this.Document;
}
var root = await this.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var rewriter = new Rewriter(this.Document, importsContainerToMissingNamespaces, options.PlaceSystemNamespaceFirst, cancellationToken);
var newRoot = rewriter.Visit(root);
return this.Document.WithSyntaxRoot(newRoot);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Simplification;
namespace Microsoft.CodeAnalysis.CSharp.Editing
{
......@@ -15,7 +22,7 @@ public CSharpImportAdder()
{
}
protected override INamespaceSymbol GetExplicitNamespaceSymbol(SyntaxNode node, SemanticModel model)
protected override INamespaceSymbol? GetExplicitNamespaceSymbol(SyntaxNode node, SemanticModel model)
{
switch (node)
{
......@@ -28,8 +35,16 @@ protected override INamespaceSymbol GetExplicitNamespaceSymbol(SyntaxNode node,
return null;
}
private INamespaceSymbol GetExplicitNamespaceSymbol(ExpressionSyntax fullName, ExpressionSyntax namespacePart, SemanticModel model)
protected override SyntaxNode MakeSafeToAddNamespaces(SyntaxNode root, IEnumerable<INamespaceOrTypeSymbol> namespaceMembers, IEnumerable<IMethodSymbol> extensionMethods, SemanticModel model, Workspace workspace, CancellationToken cancellationToken)
{
var rewriter = new Rewriter(namespaceMembers, extensionMethods, model, workspace, cancellationToken);
return rewriter.Visit(root);
}
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;
if (symbol != null && symbol.Kind != SymbolKind.Namespace && model.GetSymbolInfo(namespacePart).Symbol is INamespaceSymbol nsSymbol)
......@@ -44,5 +59,137 @@ private INamespaceSymbol GetExplicitNamespaceSymbol(ExpressionSyntax fullName, E
return null;
}
private class Rewriter : CSharpSyntaxRewriter
{
private readonly SemanticModel _model;
private readonly Workspace _workspace;
private readonly CancellationToken _cancellationToken;
/// <summary>
/// A hashset containing the short names of all namespace members
/// </summary>
private readonly HashSet<string> _namespaceMembers;
/// <summary>
/// A hashset containing the short names of all extension methods
/// </summary>
private readonly HashSet<string> _extensionMethods;
public Rewriter(
IEnumerable<INamespaceOrTypeSymbol> namespaceMembers,
IEnumerable<IMethodSymbol> extensionMethods,
SemanticModel model,
Workspace workspace,
CancellationToken cancellationToken)
{
_model = model;
_workspace = workspace;
_cancellationToken = cancellationToken;
_namespaceMembers = new HashSet<string>(namespaceMembers.Select(x => x.Name));
_extensionMethods = new HashSet<string>(extensionMethods.Select(x => x.Name));
}
public override bool VisitIntoStructuredTrivia => true;
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
{
// We only care about xml doc comments
var leadingTrivia = CanHaveDocComments(node) ? VisitList(node.GetLeadingTrivia()) : node.GetLeadingTrivia();
if (_namespaceMembers.Contains(node.Identifier.Text))
{
var expanded = Simplifier.Expand<SyntaxNode>(node, _model, _workspace, cancellationToken: _cancellationToken);
return expanded.WithLeadingTrivia(leadingTrivia);
}
return node.WithLeadingTrivia(leadingTrivia);
}
public override SyntaxNode VisitGenericName(GenericNameSyntax node)
{
// We only care about xml doc comments
var leadingTrivia = CanHaveDocComments(node) ? VisitList(node.GetLeadingTrivia()) : node.GetLeadingTrivia();
if (_namespaceMembers.Contains(node.Identifier.Text))
{
// No need to visit type argument list as simplifier will expand everything
var expanded = Simplifier.Expand<SyntaxNode>(node, _model, _workspace, cancellationToken: _cancellationToken);
return expanded.WithLeadingTrivia(leadingTrivia);
}
var typeArgumentList = (TypeArgumentListSyntax)base.Visit(node.TypeArgumentList);
return node.Update(node.Identifier.WithLeadingTrivia(leadingTrivia), typeArgumentList);
}
public override SyntaxNode VisitQualifiedName(QualifiedNameSyntax node)
{
var left = (NameSyntax)base.Visit(node.Left);
// We don't recurse on the right, as if B is a member of the imported namespace, A.B is still not ambiguous
var right = node.Right;
if (right is GenericNameSyntax genericName)
{
var typeArgumentList = (TypeArgumentListSyntax)base.Visit(genericName.TypeArgumentList);
right = genericName.Update(genericName.Identifier, typeArgumentList);
}
return node.Update(left, node.DotToken, right);
}
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
{
// No need to visit trivia, as we only care about xml doc comments
if (node.Expression is MemberAccessExpressionSyntax memberAccess)
{
if (_extensionMethods.Contains(memberAccess.Name.Identifier.Text))
{
// No need to visit this as simplifier will expand everything
return Simplifier.Expand<SyntaxNode>(node, _model, _workspace, cancellationToken: _cancellationToken);
}
}
return base.VisitInvocationExpression(node);
}
public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
{
node = (MemberAccessExpressionSyntax)base.VisitMemberAccessExpression(node);
if (_extensionMethods.Contains(node.Name.Identifier.Text))
{
// If an extension method is used as a delegate rather than invoked directly,
// there is no semantically valid transformation that will fully qualify the extension method.
// For example `Func<int> f = x.M;` is not the same as Func<int> f = () => Extensions.M(x);`
// since one captures x by value, and the other by reference.
//
// We will not visit this node if the parent node was an InvocationExpression,
// since we would have expanded the parent node entirely, rather than visiting it.
// Therefore it's possible that this is an extension method being used as a delegate so we warn.
node = node.WithAdditionalAnnotations(WarningAnnotation.Create(string.Format(
WorkspacesResources.Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access,
node.Name.Identifier.Text)));
}
return node;
}
private bool CanHaveDocComments(NameSyntax node)
{
// a node can only have doc comments in its leading trivia if it's the first node in a member declaration syntax.
SyntaxNode current = node;
while (current.Parent != null)
{
var parent = current.Parent;
if (parent is NameSyntax && parent.ChildNodes().First() == current)
{
current = parent;
continue;
}
return parent is MemberDeclarationSyntax && parent.ChildNodes().First() == current;
}
return false;
}
}
}
}
......@@ -633,8 +633,7 @@ SyntaxToken GetNewIdentifier(SyntaxToken _identifier)
var parent = originalSimpleName.Parent;
// do not complexify further for location where only simple names are allowed
if (parent is MemberDeclarationSyntax ||
parent is MemberBindingExpressionSyntax ||
if (parent is MemberBindingExpressionSyntax ||
originalSimpleName.GetAncestor<NameEqualsSyntax>() != null ||
(parent is MemberAccessExpressionSyntax && parent.Kind() != SyntaxKind.SimpleMemberAccessExpression) ||
((parent.Kind() == SyntaxKind.SimpleMemberAccessExpression || parent.Kind() == SyntaxKind.NameMemberCref) && originalSimpleName.IsRightSideOfDot()) ||
......
......@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.AddImports
......@@ -57,12 +58,12 @@ private static ImmutableArray<SyntaxNode> GetAllContainers(SyntaxNode root, Synt
{
foreach (var node in containers)
{
if (GetUsingsAndAliases(node).Any(u => u.IsEquivalentTo(import, topLevel: false)))
if (GetUsingsAndAliases(node).Any(u => IsEquivalentImport(u, import)))
{
return true;
}
if (GetExterns(node).Any(u => u.IsEquivalentTo(import, topLevel: false)))
if (GetExterns(node).Any(u => IsEquivalentImport(u, import)))
{
return true;
}
......@@ -70,7 +71,7 @@ private static ImmutableArray<SyntaxNode> GetAllContainers(SyntaxNode root, Synt
foreach (var node in globalImports)
{
if (node.IsEquivalentTo(import, topLevel: false))
if (IsEquivalentImport(node, import))
{
return true;
}
......@@ -79,6 +80,8 @@ private static ImmutableArray<SyntaxNode> GetAllContainers(SyntaxNode root, Synt
return false;
}
protected abstract bool IsEquivalentImport(SyntaxNode a, SyntaxNode b);
public SyntaxNode GetImportContainer(SyntaxNode root, SyntaxNode? contextLocation, SyntaxNode import)
{
contextLocation ??= root;
......@@ -110,7 +113,8 @@ public SyntaxNode GetImportContainer(SyntaxNode root, SyntaxNode? contextLocatio
SyntaxNode root,
SyntaxNode? contextLocation,
IEnumerable<SyntaxNode> newImports,
bool placeSystemNamespaceFirst)
bool placeSystemNamespaceFirst,
CancellationToken cancellationToken)
{
contextLocation ??= root;
......@@ -130,7 +134,7 @@ public SyntaxNode GetImportContainer(SyntaxNode root, SyntaxNode? contextLocatio
externAliases, usingDirectives, staticUsingDirectives,
aliasDirectives, externContainer, usingContainer,
staticUsingContainer, aliasContainer,
placeSystemNamespaceFirst, root);
placeSystemNamespaceFirst, root, cancellationToken);
return newRoot;
}
......@@ -138,7 +142,8 @@ public SyntaxNode GetImportContainer(SyntaxNode root, SyntaxNode? contextLocatio
protected abstract SyntaxNode Rewrite(
TExternSyntax[] externAliases, TUsingOrAliasSyntax[] usingDirectives, TUsingOrAliasSyntax[] staticUsingDirectives,
TUsingOrAliasSyntax[] aliasDirectives, SyntaxNode externContainer, SyntaxNode usingContainer,
SyntaxNode staticUsingContainer, SyntaxNode aliasContainer, bool placeSystemNamespaceFirst, SyntaxNode root);
SyntaxNode staticUsingContainer, SyntaxNode aliasContainer, bool placeSystemNamespaceFirst, SyntaxNode root,
CancellationToken cancellationToken);
private void GetContainers(SyntaxNode root, SyntaxNode contextLocation, out SyntaxNode externContainer, out SyntaxNode usingContainer, out SyntaxNode staticUsingContainer, out SyntaxNode aliasContainer)
{
......
......@@ -3,6 +3,7 @@
#nullable enable
using System.Collections.Generic;
using System.Threading;
using Microsoft.CodeAnalysis.Host;
using Roslyn.Utilities;
......@@ -25,17 +26,17 @@ internal interface IAddImportsService : ILanguageService
SyntaxNode AddImports(
Compilation compilation, SyntaxNode root, SyntaxNode? contextLocation,
IEnumerable<SyntaxNode> newImports, bool placeSystemNamespaceFirst);
IEnumerable<SyntaxNode> newImports, bool placeSystemNamespaceFirst, CancellationToken cancellationToken);
}
internal static class IAddImportServiceExtensions
{
public static SyntaxNode AddImport(
this IAddImportsService service, Compilation compilation, SyntaxNode root,
SyntaxNode contextLocation, SyntaxNode newImport, bool placeSystemNamespaceFirst)
SyntaxNode contextLocation, SyntaxNode newImport, bool placeSystemNamespaceFirst, CancellationToken cancellationToken)
{
return service.AddImports(compilation, root, contextLocation,
SpecializedCollections.SingletonEnumerable(newImport), placeSystemNamespaceFirst);
SpecializedCollections.SingletonEnumerable(newImport), placeSystemNamespaceFirst, cancellationToken);
}
}
}
......@@ -11,6 +11,7 @@
using Microsoft.CodeAnalysis.CaseCorrection;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Utilities;
......@@ -267,6 +268,9 @@ protected virtual Task<Document> PostProcessChangesAsync(Document document, Canc
{
if (document.SupportsSyntaxTree)
{
document = await ImportAdder.AddImportsFromSymbolAnnotationAsync(
document, Simplifier.AddImportsAnnotation, safe: true, cancellationToken: cancellationToken).ConfigureAwait(false);
document = await Simplifier.ReduceAsync(document, Simplifier.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
// format any node with explicit formatter annotation
......
......@@ -6,8 +6,10 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeGeneration
......@@ -27,38 +29,45 @@ internal abstract partial class AbstractCodeGenerationService : ICodeGenerationS
public TDeclarationNode AddEvent<TDeclarationNode>(TDeclarationNode destination, IEventSymbol @event, CodeGenerationOptions options, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode
{
return AddEvent(destination, @event, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken));
return WithAnnotations(AddEvent(destination, @event, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken)), options);
}
public TDeclarationNode AddField<TDeclarationNode>(TDeclarationNode destination, IFieldSymbol field, CodeGenerationOptions options, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode
{
return AddField(destination, field, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken));
return WithAnnotations(AddField(destination, field, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken)), options);
}
public TDeclarationNode AddMethod<TDeclarationNode>(TDeclarationNode destination, IMethodSymbol method, CodeGenerationOptions options, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode
{
return AddMethod(destination, method, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken));
return WithAnnotations(AddMethod(destination, method, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken)), options);
}
public TDeclarationNode AddProperty<TDeclarationNode>(TDeclarationNode destination, IPropertySymbol property, CodeGenerationOptions options, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode
{
return AddProperty(destination, property, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken));
return WithAnnotations(AddProperty(destination, property, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken)), options);
}
public TDeclarationNode AddNamedType<TDeclarationNode>(TDeclarationNode destination, INamedTypeSymbol namedType, CodeGenerationOptions options, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode
{
return AddNamedType(destination, namedType, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken), cancellationToken);
return WithAnnotations(AddNamedType(destination, namedType, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken), cancellationToken), options);
}
public TDeclarationNode AddNamespace<TDeclarationNode>(TDeclarationNode destination, INamespaceSymbol @namespace, CodeGenerationOptions options, CancellationToken cancellationToken) where TDeclarationNode : SyntaxNode
{
return AddNamespace(destination, @namespace, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken), cancellationToken);
return WithAnnotations(AddNamespace(destination, @namespace, options ?? CodeGenerationOptions.Default, GetAvailableInsertionIndices(destination, cancellationToken), cancellationToken), options);
}
public TDeclarationNode AddMembers<TDeclarationNode>(TDeclarationNode destination, IEnumerable<ISymbol> members, CodeGenerationOptions options, CancellationToken cancellationToken)
where TDeclarationNode : SyntaxNode
{
return AddMembers(destination, members, GetAvailableInsertionIndices(destination, cancellationToken), options ?? CodeGenerationOptions.Default, cancellationToken);
return WithAnnotations(AddMembers(destination, members, GetAvailableInsertionIndices(destination, cancellationToken), options ?? CodeGenerationOptions.Default, cancellationToken), options);
}
private TNode WithAnnotations<TNode>(TNode node, CodeGenerationOptions options) where TNode : SyntaxNode
{
return options?.AddImports ?? true
? node.WithAdditionalAnnotations(Simplifier.AddImportsAnnotation)
: node;
}
protected abstract TDeclarationNode AddEvent<TDeclarationNode>(TDeclarationNode destination, IEventSymbol @event, CodeGenerationOptions options, IList<bool> availableIndices) where TDeclarationNode : SyntaxNode;
......@@ -88,8 +97,6 @@ public TDeclarationNode AddMembers<TDeclarationNode>(TDeclarationNode destinatio
public abstract SyntaxNode CreateNamedTypeDeclaration(INamedTypeSymbol namedType, CodeGenerationDestination destination, CodeGenerationOptions options, CancellationToken cancellationToken);
public abstract SyntaxNode CreateNamespaceDeclaration(INamespaceSymbol @namespace, CodeGenerationDestination destination, CodeGenerationOptions options, CancellationToken cancellationToken);
protected abstract AbstractImportsAdder CreateImportsAdder(Document document);
protected static T Cast<T>(object value)
{
return (T)value;
......@@ -198,21 +205,16 @@ protected static T Cast<T>(object value)
if (options.AddImports)
{
newDocument = await AddImportsAsync(
newDocument, options, cancellationToken).ConfigureAwait(false);
newDocument = await ImportAdder.AddImportsFromSymbolAnnotationAsync(
newDocument,
safe: true,
await newDocument.GetOptionsAsync(cancellationToken).ConfigureAwait(false),
cancellationToken).ConfigureAwait(false);
}
return newDocument;
}
public async Task<Document> AddImportsAsync(Document document, CodeGenerationOptions options, CancellationToken cancellationToken)
{
options ??= CodeGenerationOptions.Default;
var adder = this.CreateImportsAdder(document);
var newDocument = await adder.AddAsync(options.PlaceSystemNamespaceFirst, options, cancellationToken).ConfigureAwait(false);
return newDocument;
}
protected TDeclarationNode AddMembers<TDeclarationNode>(
TDeclarationNode destination,
IEnumerable<ISymbol> members,
......
// 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;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeGeneration
{
internal abstract partial class AbstractImportsAdder
{
protected readonly Document Document;
protected AbstractImportsAdder(Document document)
{
this.Document = document;
}
protected abstract IList<INamespaceSymbol> GetExistingNamespaces(SemanticModel semanticModel, SyntaxNode namespaceScope, CancellationToken cancellationToken);
// protected abstract SyntaxNode GetContextNode(SyntaxNodeOrToken node);
protected abstract SyntaxNode GetImportsContainer(SyntaxNode node);
protected abstract SyntaxNode GetInnermostNamespaceScope(SyntaxNodeOrToken node);
public abstract Task<Document> AddAsync(bool placeSystemNamespaceFirst, CodeGenerationOptions options, CancellationToken cancellationToken);
protected async Task<IDictionary<SyntaxNode, ISet<INamedTypeSymbol>>> GetAllReferencedDefinitionsAsync(
Compilation compilation, CancellationToken cancellationToken)
{
var namespaceScopeToReferencedDefinitions = new Dictionary<SyntaxNode, ISet<INamedTypeSymbol>>();
var root = await Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
ISet<INamedTypeSymbol> createSet(SyntaxNode _) => new HashSet<INamedTypeSymbol>();
var annotatedNodes = root.GetAnnotatedNodesAndTokens(SymbolAnnotation.Kind);
foreach (var annotatedNode in annotatedNodes)
{
cancellationToken.ThrowIfCancellationRequested();
if (annotatedNode.GetAnnotations(DoNotAddImportsAnnotation.Kind).Any())
{
continue;
}
SyntaxNode namespaceScope = null;
var annotations = annotatedNode.GetAnnotations(SymbolAnnotation.Kind);
foreach (var annotation in annotations)
{
cancellationToken.ThrowIfCancellationRequested();
foreach (var namedType in SymbolAnnotation.GetSymbols(annotation, compilation).OfType<INamedTypeSymbol>())
{
cancellationToken.ThrowIfCancellationRequested();
if (!IsBuiltIn(namedType))
{
namespaceScope ??= this.GetInnermostNamespaceScope(annotatedNode);
var referencedDefinitions = namespaceScopeToReferencedDefinitions.GetOrAdd(
namespaceScope, createSet);
referencedDefinitions.Add(namedType);
}
}
}
}
return namespaceScopeToReferencedDefinitions;
}
private bool IsBuiltIn(INamedTypeSymbol type)
{
switch (type.OriginalDefinition.SpecialType)
{
case Microsoft.CodeAnalysis.SpecialType.System_Object:
case Microsoft.CodeAnalysis.SpecialType.System_Void:
case Microsoft.CodeAnalysis.SpecialType.System_Boolean:
case Microsoft.CodeAnalysis.SpecialType.System_Char:
case Microsoft.CodeAnalysis.SpecialType.System_SByte:
case Microsoft.CodeAnalysis.SpecialType.System_Byte:
case Microsoft.CodeAnalysis.SpecialType.System_Int16:
case Microsoft.CodeAnalysis.SpecialType.System_UInt16:
case Microsoft.CodeAnalysis.SpecialType.System_Int32:
case Microsoft.CodeAnalysis.SpecialType.System_UInt32:
case Microsoft.CodeAnalysis.SpecialType.System_Int64:
case Microsoft.CodeAnalysis.SpecialType.System_UInt64:
case Microsoft.CodeAnalysis.SpecialType.System_Decimal:
case Microsoft.CodeAnalysis.SpecialType.System_Single:
case Microsoft.CodeAnalysis.SpecialType.System_Double:
case Microsoft.CodeAnalysis.SpecialType.System_String:
case Microsoft.CodeAnalysis.SpecialType.System_Nullable_T:
return true;
}
return false;
}
protected async Task<IDictionary<SyntaxNode, IList<INamespaceSymbol>>> DetermineNamespaceToImportAsync(
CodeGenerationOptions options, CancellationToken cancellationToken)
{
var semanticModel = await this.Document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var compilation = semanticModel.Compilation;
// First, find all the named types referenced by code that we are trying to generated.
var namespaceScopeToReferencedDefinitions = await GetAllReferencedDefinitionsAsync(
compilation, cancellationToken).ConfigureAwait(false);
var importsContainerToMissingImports = new Dictionary<SyntaxNode, IList<INamespaceSymbol>>();
// Next determine the namespaces we'd have to have imported in order to reference them
// in a non-qualified manner.
foreach (var kvp in namespaceScopeToReferencedDefinitions)
{
var namespaceScope = kvp.Key;
var referencedDefinitions = kvp.Value;
var referencedNamespaces =
referencedDefinitions.Select(t => t.ContainingNamespace)
.WhereNotNull()
.Where(n => !n.IsGlobalNamespace)
.Distinct()
.ToList();
AddMissingNamespaces(
semanticModel, importsContainerToMissingImports, namespaceScope, referencedNamespaces, cancellationToken);
}
if (options.AdditionalImports.Any())
{
var root = await this.Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
AddMissingNamespaces(
semanticModel, importsContainerToMissingImports, root, options.AdditionalImports, cancellationToken);
}
return importsContainerToMissingImports;
}
private void AddMissingNamespaces(
SemanticModel semanticModel,
Dictionary<SyntaxNode, IList<INamespaceSymbol>> importsContainerToMissingImports,
SyntaxNode namespaceScope,
IEnumerable<INamespaceSymbol> referencedNamespaces,
CancellationToken cancellationToken)
{
var existingNamespaces = this.GetExistingNamespaces(semanticModel, namespaceScope, cancellationToken);
var missingInThisScope = referencedNamespaces.Except(existingNamespaces, INamespaceSymbolExtensions.EqualityComparer);
var importsContainer = this.GetImportsContainer(namespaceScope);
var missingImports = importsContainerToMissingImports.GetOrAdd(importsContainer, _ => new List<INamespaceSymbol>());
var updatedResult = missingImports.Concat(missingInThisScope)
.Distinct(INamespaceSymbolExtensions.EqualityComparer)
.OrderBy(INamespaceSymbolExtensions.CompareNamespaces)
.ToList();
missingImports.Clear();
missingImports.AddRange(updatedResult);
}
// TODO(cyrusn): Implement this.
//
// General algorithm. See the set of types+arity imported by the current set of imports.
// Then see the types+arity of the namespace that's being added. If there are any
// intersections then we may cause an ambiguity in the code. To check if there will actually
// be an ambiguity, Look for SimpleNameNodes in the code that match the name+arity. If we
// run into any, then just return that an ambiguity is possible. (Note: if this creates too
// many false positive, then we may want to do some binding of the node to see if there's
// actually an ambiguity).
protected virtual bool CouldCauseAmbiguity(ISet<INamespaceSymbol> currentImportedNamespaces, INamespaceSymbol namespaceToImport)
{
return false;
}
protected static IEnumerable<INamespaceSymbol> GetContainingNamespacesAndThis(INamespaceSymbol namespaceSymbol)
{
while (namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace)
{
yield return namespaceSymbol;
namespaceSymbol = namespaceSymbol.ContainingNamespace;
}
}
}
}
......@@ -2,6 +2,8 @@
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeGeneration
......@@ -47,9 +49,10 @@ internal class CodeGenerationOptions
public Location BeforeThisLocation { get; }
/// <summary>
/// True if the code generation service should try to automatically add imports to the file
/// for any generated code. Defaults to true. Not used when generating directly into a
/// declaration.
/// True if the code generation service should add <see cref="Simplifier.AddImportsAnnotation"/>,
/// and when not generating directly into a declaration, should try to automatically add imports to the file
/// for any generated code.
/// Defaults to true.
/// </summary>
public bool AddImports { get; }
......
......@@ -119,8 +119,6 @@ internal interface ICodeGenerationService : ILanguageService
/// </summary>
TDeclarationNode AddStatements<TDeclarationNode>(TDeclarationNode destination, IEnumerable<SyntaxNode> statements, CodeGenerationOptions options = null, CancellationToken cancellationToken = default) where TDeclarationNode : SyntaxNode;
Task<Document> AddImportsAsync(Document document, CodeGenerationOptions options, CancellationToken cancellationToken);
/// <summary>
/// Adds a field with the provided signature into destination.
/// </summary>
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Collections.Generic;
using System.Linq;
using System.Threading;
......@@ -7,6 +9,7 @@
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editing
{
......@@ -15,38 +18,116 @@ public static class ImportAdder
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document.
/// </summary>
public static async Task<Document> AddImportsAsync(Document document, OptionSet options = null, CancellationToken cancellationToken = default)
public static Task<Document> AddImportsAsync(Document document, OptionSet? options = null, CancellationToken cancellationToken = default)
{
return AddImportsFromSyntaxesAsync(document, safe: false, options, cancellationToken);
}
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document within the span specified.
/// </summary>
public static Task<Document> AddImportsAsync(Document document, TextSpan span, OptionSet? options = null, CancellationToken cancellationToken = default)
{
return AddImportsFromSyntaxesAsync(document, new[] { span }, safe: false, options, cancellationToken);
}
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document within the sub-trees annotated with the <see cref="SyntaxAnnotation"/>.
/// </summary>
public static Task<Document> AddImportsAsync(Document document, SyntaxAnnotation annotation, OptionSet? options = null, CancellationToken cancellationToken = default)
{
return AddImportsFromSyntaxesAsync(document, annotation, safe: false, options, cancellationToken);
}
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document within the spans specified.
/// </summary>
public static Task<Document> AddImportsAsync(Document document, IEnumerable<TextSpan> spans, OptionSet? options = null, CancellationToken cancellationToken = default)
{
return AddImportsFromSyntaxesAsync(document, spans, safe: false, options, cancellationToken);
}
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document.
/// </summary>
internal static async Task<Document> AddImportsFromSyntaxesAsync(Document document, bool safe = true, OptionSet? options = null, CancellationToken cancellationToken = default)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
Contract.ThrowIfNull(root);
return await AddImportsFromSyntaxesAsync(document, root.FullSpan, safe, options, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document within the span specified.
/// </summary>
internal static Task<Document> AddImportsFromSyntaxesAsync(Document document, TextSpan span, bool safe = true, OptionSet? options = null, CancellationToken cancellationToken = default)
{
return AddImportsFromSyntaxesAsync(document, new[] { span }, safe, options, cancellationToken);
}
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document within the sub-trees annotated with the <see cref="SyntaxAnnotation"/>.
/// </summary>
internal static async Task<Document> AddImportsFromSyntaxesAsync(Document document, SyntaxAnnotation annotation, bool safe = true, OptionSet? options = null, CancellationToken cancellationToken = default)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
Contract.ThrowIfNull(root);
return await AddImportsFromSyntaxesAsync(document, root.GetAnnotatedNodesAndTokens(annotation).Select(t => t.FullSpan), safe, options, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document within the spans specified.
/// </summary>
internal static Task<Document> AddImportsFromSyntaxesAsync(Document document, IEnumerable<TextSpan> spans, bool safe = true, OptionSet? options = null, CancellationToken cancellationToken = default)
{
var service = document.GetLanguageService<ImportAdderService>();
if (service != null)
{
return service.AddImportsAsync(document, spans, ImportAdderService.Strategy.AddImportsFromSyntaxes, safe, options, cancellationToken);
}
else
{
return Task.FromResult(document);
}
}
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document.
/// </summary>
internal static async Task<Document> AddImportsFromSymbolAnnotationAsync(Document document, bool safe = true, OptionSet? options = null, CancellationToken cancellationToken = default)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
return await AddImportsAsync(document, root.FullSpan, options, cancellationToken).ConfigureAwait(false);
Contract.ThrowIfNull(root);
return await AddImportsFromSymbolAnnotationAsync(document, root.FullSpan, safe, options, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document within the span specified.
/// </summary>
public static Task<Document> AddImportsAsync(Document document, TextSpan span, OptionSet options = null, CancellationToken cancellationToken = default)
internal static Task<Document> AddImportsFromSymbolAnnotationAsync(Document document, TextSpan span, bool safe = true, OptionSet? options = null, CancellationToken cancellationToken = default)
{
return AddImportsAsync(document, new[] { span }, options, cancellationToken);
return AddImportsFromSymbolAnnotationAsync(document, new[] { span }, safe, options, cancellationToken);
}
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document within the sub-trees annotated with the <see cref="SyntaxAnnotation"/>.
/// </summary>
public static async Task<Document> AddImportsAsync(Document document, SyntaxAnnotation annotation, OptionSet options = null, CancellationToken cancellationToken = default)
internal static async Task<Document> AddImportsFromSymbolAnnotationAsync(Document document, SyntaxAnnotation annotation, bool safe = true, OptionSet? options = null, CancellationToken cancellationToken = default)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
return await AddImportsAsync(document, root.GetAnnotatedNodesAndTokens(annotation).Select(t => t.FullSpan), options, cancellationToken).ConfigureAwait(false);
Contract.ThrowIfNull(root);
return await AddImportsFromSymbolAnnotationAsync(document, root.GetAnnotatedNodesAndTokens(annotation).Select(t => t.FullSpan), safe, options, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Adds namespace imports / using directives for namespace references found in the document within the spans specified.
/// </summary>
public static Task<Document> AddImportsAsync(Document document, IEnumerable<TextSpan> spans, OptionSet options = null, CancellationToken cancellationToken = default)
internal static Task<Document> AddImportsFromSymbolAnnotationAsync(Document document, IEnumerable<TextSpan> spans, bool safe = true, OptionSet? options = null, CancellationToken cancellationToken = default)
{
var service = document.GetLanguageService<ImportAdderService>();
if (service != null)
{
return service.AddImportsAsync(document, spans, options, cancellationToken);
return service.AddImportsAsync(document, spans, ImportAdderService.Strategy.AddImportsFromSymbolAnnotations, safe, options, cancellationToken);
}
else
{
......
// 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;
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImports;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
......@@ -18,44 +23,278 @@ namespace Microsoft.CodeAnalysis.Editing
{
internal abstract class ImportAdderService : ILanguageService
{
public enum Strategy
{
AddImportsFromSyntaxes,
AddImportsFromSymbolAnnotations,
}
public async Task<Document> AddImportsAsync(
Document document, IEnumerable<TextSpan> spans,
OptionSet options, CancellationToken cancellationToken)
Document document,
IEnumerable<TextSpan> spans,
Strategy strategy,
bool safe,
OptionSet? options,
CancellationToken cancellationToken)
{
options ??= await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
Contract.ThrowIfNull(model);
var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
var addImportsService = document.GetLanguageService<IAddImportsService>();
var generator = SyntaxGenerator.GetGenerator(document);
// Create a simple interval tree for simplification spans.
var spansTree = new SimpleIntervalTree<TextSpan>(TextSpanIntervalIntrospector.Instance, spans);
bool isInSpan(SyntaxNodeOrToken nodeOrToken) =>
spansTree.HasIntervalThatOverlapsWith(nodeOrToken.FullSpan.Start, nodeOrToken.FullSpan.Length);
var nodes = root.DescendantNodesAndSelf().Where(IsInSpan);
var (importDirectivesToAdd, namespaceSymbols, context) = strategy switch
{
Strategy.AddImportsFromSymbolAnnotations
=> GetImportDirectivesFromAnnotatedNodesAsync(nodes, root, model, addImportsService, generator, cancellationToken),
Strategy.AddImportsFromSyntaxes
=> GetImportDirectivesFromSyntaxesAsync(nodes, ref root, model, addImportsService, generator, cancellationToken),
_ => throw new InvalidEnumArgumentException(nameof(strategy), (int)strategy, typeof(Strategy)),
};
var nodesWithExplicitNamespaces = root.DescendantNodesAndSelf().Where(n => isInSpan(n) && GetExplicitNamespaceSymbol(n, model) != null).ToList();
if (importDirectivesToAdd.Length == 0)
{
return document.WithSyntaxRoot(root); //keep any added simplifier annotations
}
var namespacesToAdd = new HashSet<INamespaceSymbol>();
namespacesToAdd.AddRange(nodesWithExplicitNamespaces.Select(
n => GetExplicitNamespaceSymbol(n, model)));
if (safe)
{
// Mark the context with an annotation.
// This will allow us to find it after we have called MakeSafeToAddNamespaces.
var annotation = new SyntaxAnnotation();
document = document.WithSyntaxRoot(root.ReplaceNode(context, context.WithAdditionalAnnotations(annotation)));
root = (await document.GetSyntaxRootAsync().ConfigureAwait(false))!;
var generator = SyntaxGenerator.GetGenerator(document);
var imports = namespacesToAdd.Select(ns => generator.NamespaceImportDeclaration(ns.ToDisplayString()).WithAdditionalAnnotations(Simplifier.Annotation))
.ToArray();
model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
Contract.ThrowIfNull(model);
// Make Safe to add namespaces
document = document.WithSyntaxRoot(
MakeSafeToAddNamespaces(root, namespaceSymbols, model, document.Project.Solution.Workspace, cancellationToken));
root = (await document.GetSyntaxRootAsync().ConfigureAwait(false))!;
// annotate these nodes so they get simplified later
var newRoot = root.ReplaceNodes(
nodesWithExplicitNamespaces,
(o, r) => r.WithAdditionalAnnotations(Simplifier.Annotation));
model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
Contract.ThrowIfNull(model);
// Find the context. It might be null if we have removed the context in the process of complexifying the tree.
context = root.DescendantNodesAndSelf().FirstOrDefault(x => x.HasAnnotation(annotation)) ?? root;
}
var placeSystemNamespaceFirst = options.GetOption(GenerationOptions.PlaceSystemNamespaceFirst, document.Project.Language);
var addImportsService = document.GetLanguageService<IAddImportsService>();
var finalRoot = addImportsService.AddImports(
model.Compilation, newRoot, newRoot, imports, placeSystemNamespaceFirst);
return document.WithSyntaxRoot(finalRoot);
root = addImportsService.AddImports(model.Compilation, root, context, importDirectivesToAdd, placeSystemNamespaceFirst, cancellationToken);
return document.WithSyntaxRoot(root);
bool IsInSpan(SyntaxNode node) =>
spansTree.HasIntervalThatOverlapsWith(node.FullSpan.Start, node.FullSpan.Length);
}
protected abstract INamespaceSymbol? GetExplicitNamespaceSymbol(SyntaxNode node, SemanticModel model);
private SyntaxNode MakeSafeToAddNamespaces(
SyntaxNode root,
IEnumerable<INamespaceSymbol> namespaceSymbols,
SemanticModel model,
Workspace workspace,
CancellationToken cancellationToken)
{
var namespaceMembers = namespaceSymbols.SelectMany(x => x.GetMembers());
var extensionMethods =
namespaceMembers.OfType<INamedTypeSymbol>().Where(t => t.MightContainExtensionMethods)
.SelectMany(x => x.GetMembers().OfType<IMethodSymbol>().Where(x => x.IsExtensionMethod));
return MakeSafeToAddNamespaces(root, namespaceMembers, extensionMethods, model, workspace, cancellationToken);
}
/// <summary>
/// Fully qualifies parts of the document that may change meaning if namespaces are added,
/// and marks them with <see cref="Simplifier.Annotation"/> so they can be reduced later.
/// </summary>
protected abstract SyntaxNode MakeSafeToAddNamespaces(
SyntaxNode root,
IEnumerable<INamespaceOrTypeSymbol> namespaceMembers,
IEnumerable<IMethodSymbol> extensionMethods,
SemanticModel model,
Workspace workspace,
CancellationToken cancellationToken);
private SyntaxNode GenerateNamespaceImportDeclaration(INamespaceSymbol namespaceSymbol, SyntaxGenerator generator)
{
// We add Simplifier.Annotation so that the import can be removed if it turns out to be unnecessary.
// This can happen for a number of reasons (we replace the type with var, inbuilt type, alias, etc.)
return generator
.NamespaceImportDeclaration(namespaceSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat))
.WithAdditionalAnnotations(Simplifier.Annotation, Formatter.Annotation);
}
protected abstract INamespaceSymbol GetExplicitNamespaceSymbol(SyntaxNode node, SemanticModel model);
/// <summary>
///
/// </summary>
/// <param name="root">ref as we add simplifier annotations to nodes with explicit namespaces</param>
/// <returns></returns>
private (ImmutableArray<SyntaxNode> imports, IEnumerable<INamespaceSymbol> namespaceSymbols, SyntaxNode? context) GetImportDirectivesFromSyntaxesAsync(
IEnumerable<SyntaxNode> syntaxNodes,
ref SyntaxNode root,
SemanticModel model,
IAddImportsService addImportsService,
SyntaxGenerator generator,
CancellationToken cancellationToken
)
{
var importsToAdd = ArrayBuilder<SyntaxNode>.GetInstance();
var nodesWithExplicitNamespaces = syntaxNodes
.Select(n => (syntaxnode: n, namespaceSymbol: GetExplicitNamespaceSymbol(n, model)))
.Where(x => x.namespaceSymbol != null);
var nodesToSimplify = ArrayBuilder<SyntaxNode>.GetInstance();
var addedSymbols = new HashSet<INamespaceSymbol>();
foreach (var (node, namespaceSymbol) in nodesWithExplicitNamespaces)
{
cancellationToken.ThrowIfCancellationRequested();
nodesToSimplify.Add(node);
if (addedSymbols.Contains(namespaceSymbol))
{
continue;
}
var namespaceSyntax = GenerateNamespaceImportDeclaration(namespaceSymbol, generator);
if (addImportsService.HasExistingImport(model.Compilation, root, node, namespaceSyntax))
{
continue;
}
if (IsInsideNamespace(node, namespaceSymbol, model, cancellationToken))
{
continue;
}
addedSymbols.Add(namespaceSymbol);
importsToAdd.Add(namespaceSyntax);
}
if (nodesToSimplify.Count == 0)
{
nodesToSimplify.Free();
return (importsToAdd.ToImmutableAndFree(), addedSymbols, null);
}
var annotation = new SyntaxAnnotation();
root = root.ReplaceNodes(
nodesToSimplify,
(o, r) => r.WithAdditionalAnnotations(Simplifier.Annotation, annotation));
var first = root.DescendantNodesAndSelf().First(x => x.HasAnnotation(annotation));
var last = root.DescendantNodesAndSelf().Last(x => x.HasAnnotation(annotation));
nodesToSimplify.Free();
return (importsToAdd.ToImmutableAndFree(), addedSymbols, first.GetCommonRoot(last));
}
private (ImmutableArray<SyntaxNode> imports, IEnumerable<INamespaceSymbol> namespaceSymbols, SyntaxNode? context) GetImportDirectivesFromAnnotatedNodesAsync(
IEnumerable<SyntaxNode> syntaxNodes,
SyntaxNode root,
SemanticModel model,
IAddImportsService addImportsService,
SyntaxGenerator generator,
CancellationToken cancellationToken)
{
SyntaxNode? first = null;
SyntaxNode? last = null;
var importsToAdd = ArrayBuilder<SyntaxNode>.GetInstance();
var annotatedNodes = syntaxNodes.Where(x => x.HasAnnotations(SymbolAnnotation.Kind));
var addedSymbols = new HashSet<INamespaceSymbol>();
foreach (var annotatedNode in annotatedNodes)
{
cancellationToken.ThrowIfCancellationRequested();
if (annotatedNode.GetAnnotations(DoNotAddImportsAnnotation.Kind).Any())
{
continue;
}
var annotations = annotatedNode.GetAnnotations(SymbolAnnotation.Kind);
foreach (var annotation in annotations)
{
cancellationToken.ThrowIfCancellationRequested();
foreach (var namedType in SymbolAnnotation.GetSymbols(annotation, model.Compilation).OfType<INamedTypeSymbol>())
{
cancellationToken.ThrowIfCancellationRequested();
if (namedType.OriginalDefinition.IsSpecialType() || namedType.IsNullable())
{
continue;
}
var namespaceSymbol = namedType.ContainingNamespace;
if (namespaceSymbol is null || namespaceSymbol.IsGlobalNamespace)
{
continue;
}
first ??= annotatedNode;
last = annotatedNode;
if (addedSymbols.Contains(namespaceSymbol))
{
continue;
}
var namespaceSyntax = GenerateNamespaceImportDeclaration(namespaceSymbol, generator);
if (addImportsService.HasExistingImport(model.Compilation, root, annotatedNode, namespaceSyntax))
{
continue;
}
if (IsInsideNamespace(annotatedNode, namespaceSymbol, model, cancellationToken))
{
continue;
}
addedSymbols.Add(namespaceSymbol);
importsToAdd.Add(namespaceSyntax);
}
}
}
// we don't add simplifier annotations here,
// since whatever added the symbol annotation probably also added simplifier annotations,
// and if not they probably didn't for a reason
return (importsToAdd.ToImmutableAndFree(), addedSymbols, first?.GetCommonRoot(last));
}
/// <summary>
/// Checks if the namespace declaration <paramref name="node"/> is contained inside,
/// or any of its ancestor namespaces are the same as <paramref name="symbol"/>
/// </summary>
private bool IsInsideNamespace(SyntaxNode node, INamespaceSymbol symbol, SemanticModel model, CancellationToken cancellationToken)
{
var containedNamespace = model.GetEnclosingNamespace(node.SpanStart, cancellationToken);
while (containedNamespace != null)
{
if (containedNamespace.Equals(symbol))
return true;
containedNamespace = containedNamespace.ContainingNamespace;
}
return false;
}
}
}
*REMOVED*Microsoft.CodeAnalysis.TextDocument.Project.set -> void
*REMOVED*Microsoft.CodeAnalysis.TextDocument.TextDocument() -> void
static Microsoft.CodeAnalysis.Formatting.Formatter.OrganizeImportsAsync(Microsoft.CodeAnalysis.Document document, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<Microsoft.CodeAnalysis.Document>
static Microsoft.CodeAnalysis.Simplification.Simplifier.AddImportsAnnotation.get -> Microsoft.CodeAnalysis.SyntaxAnnotation
......@@ -6,6 +6,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
......@@ -46,6 +47,12 @@ public static partial class Simplifier
/// </summary>
public static SyntaxAnnotation SpecialTypeAnnotation { get; } = new SyntaxAnnotation();
/// <summary>
/// The annotation <see cref="CodeAction.CleanupDocumentAsync"/> used to identify sub trees to look for symbol annotations on.
/// It will then add import directives for these symbol annotations.
/// </summary>
public static SyntaxAnnotation AddImportsAnnotation { get; } = new SyntaxAnnotation();
/// <summary>
/// Expand qualifying parts of the specified subtree, annotating the parts using the <see cref="Annotation" /> annotation.
/// </summary>
......
......@@ -3744,6 +3744,16 @@ internal class WorkspacesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Adding imports will bring an extension method into scope with the same name as &apos;{0}&apos;.
/// </summary>
internal static string Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access {
get {
return ResourceManager.GetString("Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_na" +
"me_as_member_access", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Workspace is not empty..
/// </summary>
......
......@@ -1473,4 +1473,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<data name="Symbol_specifications" xml:space="preserve">
<value>Symbol specifications</value>
</data>
<data name="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access" xml:space="preserve">
<value>Adding imports will bring an extension method into scope with the same name as '{0}'</value>
</data>
</root>
\ No newline at end of file
......@@ -1312,6 +1312,11 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou šířkou se o
<target state="translated">Soubory jazyka Visual Basic</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">Pracovní prostor není platný.</target>
......
......@@ -1312,6 +1312,11 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg
<target state="translated">Visual Basic-Dateien</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">Arbeitsbereich ist nicht leer.</target>
......
......@@ -1312,6 +1312,11 @@ Las aserciones posteriores positivas de ancho cero se usan normalmente al princi
<target state="translated">Archivos de Visual Basic</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">El área de trabajo no está vacía.</target>
......
......@@ -1312,6 +1312,11 @@ Les assertions de postanalyse positives de largeur nulle sont généralement uti
<target state="translated">Fichiers Visual Basic</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">L'espace de travail n'est pas vide.</target>
......
......@@ -1312,6 +1312,11 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all'
<target state="translated">File Visual Basic</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">L'area di lavoro non è vuota.</target>
......
......@@ -1312,6 +1312,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Visual Basic ファイル</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">ワークスペースが空ではありません。</target>
......
......@@ -1312,6 +1312,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Visual Basic 파일</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">작업 영역이 비어 있지 않습니다.</target>
......
......@@ -1312,6 +1312,11 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk
<target state="translated">Pliki języka Visual Basic</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">Obszar roboczy nie jest pusty.</target>
......
......@@ -1312,6 +1312,11 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas
<target state="translated">Arquivos do Visual Basic</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">Workspace não está vazio.</target>
......
......@@ -1312,6 +1312,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Файлы Visual Basic</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">Рабочая область не пуста.</target>
......
......@@ -1312,6 +1312,11 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri
<target state="translated">Visual Basic dosyaları</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">Çalışma alanı boş değil.</target>
......
......@@ -1312,6 +1312,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">visual basic 文件</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">工作区不为空。</target>
......
......@@ -1312,6 +1312,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Visual Basic 檔案</target>
<note />
</trans-unit>
<trans-unit id="Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access">
<source>Adding imports will bring an extension method into scope with the same name as '{0}'</source>
<target state="new">Adding imports will bring an extension method into scope with the same name as '{0}'</target>
<note />
</trans-unit>
<trans-unit id="Workspace_is_not_empty">
<source>Workspace is not empty.</source>
<target state="translated">工作區不是空的。</target>
......
......@@ -2,11 +2,13 @@
Imports System.Collections.Immutable
Imports System.Composition
Imports System.Threading
Imports Microsoft.CodeAnalysis.AddImports
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.Utilities
Namespace Microsoft.CodeAnalysis.VisualBasic.AddImports
<ExportLanguageService(GetType(IAddImportsService), LanguageNames.VisualBasic), [Shared]>
......@@ -21,6 +23,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.AddImports
Public Sub New()
End Sub
Private Shared ImportsStatementComparer As ImportsStatementComparer = New ImportsStatementComparer(New CaseInsensitiveTokenComparer())
Protected Overrides Function IsEquivalentImport(a As SyntaxNode, b As SyntaxNode) As Boolean
Dim importsA = TryCast(a, ImportsStatementSyntax)
Dim importsB = TryCast(b, ImportsStatementSyntax)
If importsA Is Nothing OrElse importsB Is Nothing Then
Return False
End If
Return ImportsStatementComparer.Compare(importsA, importsB) = 0
End Function
Protected Overrides Function GetGlobalImports(compilation As Compilation) As ImmutableArray(Of SyntaxNode)
Dim generator = VisualBasicSyntaxGenerator.Instance
Dim result = ArrayBuilder(Of SyntaxNode).GetInstance()
......@@ -67,14 +82,34 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.AddImports
staticUsingContainer As SyntaxNode,
aliasContainer As SyntaxNode,
placeSystemNamespaceFirst As Boolean,
root As SyntaxNode) As SyntaxNode
root As SyntaxNode,
cancellationToken As CancellationToken) As SyntaxNode
Dim compilationUnit = DirectCast(root, CompilationUnitSyntax)
If Not compilationUnit.CanAddImportsStatements(cancellationToken) Then
Return compilationUnit
End If
Return compilationUnit.AddImportsStatements(
usingDirectives.Concat(aliasDirectives).ToList(),
placeSystemNamespaceFirst,
Array.Empty(Of SyntaxAnnotation))
End Function
Private Class CaseInsensitiveTokenComparer
Implements IComparer(Of SyntaxToken)
Public Function Compare(x As SyntaxToken, y As SyntaxToken) As Integer Implements IComparer(Of SyntaxToken).Compare
' By using 'ValueText' we get the value that is normalized. i.e.
' [class] will be 'class', and unicode escapes will be converted
' to actual unicode. This allows sorting to work properly across
' tokens that have different source representations, but which
' mean the same thing.
' Don't bother checking the raw kind, since this will only ever be used with Identifier tokens.
Return CaseInsensitiveComparer.Default.Compare(x.GetIdentifierText(), y.GetIdentifierText())
End Function
End Class
End Class
End Namespace
' 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.Threading
Imports System.Threading.Tasks
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CaseCorrection
Imports Microsoft.CodeAnalysis.CodeGeneration
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Simplification
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
Friend Class ImportsStatementsAdder
Inherits AbstractImportsAdder
Public Sub New(document As Document)
MyBase.New(document)
End Sub
Protected Overrides Function GetInnermostNamespaceScope(nodeOrToken As SyntaxNodeOrToken) As SyntaxNode
Dim node = If(nodeOrToken.IsNode, nodeOrToken.AsNode, nodeOrToken.Parent)
Return If(node.GetAncestorOrThis(Of NamespaceBlockSyntax),
DirectCast(node.GetAncestorOrThis(Of CompilationUnitSyntax)(), SyntaxNode))
End Function
Protected Overrides Function GetImportsContainer(node As SyntaxNode) As SyntaxNode
Return node.GetAncestorOrThis(Of CompilationUnitSyntax)()
End Function
Protected Overloads Overrides Function GetExistingNamespaces(semanticModel As SemanticModel, namespaceScope As SyntaxNode, cancellationToken As CancellationToken) As IList(Of INamespaceSymbol)
If TypeOf namespaceScope Is NamespaceBlockSyntax Then
Return GetExistingNamespaces(semanticModel, DirectCast(namespaceScope, NamespaceBlockSyntax), cancellationToken)
Else
Return GetExistingNamespaces(semanticModel, DirectCast(namespaceScope, CompilationUnitSyntax), cancellationToken)
End If
End Function
Private Overloads Function GetExistingNamespaces(semanticModel As SemanticModel, namespaceDeclaration As NamespaceBlockSyntax, cancellationToken As CancellationToken) As IList(Of INamespaceSymbol)
Dim namespaceSymbol = TryCast(semanticModel.GetDeclaredSymbol(namespaceDeclaration, cancellationToken), INamespaceSymbol)
Dim namespaceImports = GetContainingNamespacesAndThis(namespaceSymbol).ToList()
Dim outerNamespaces = Me.GetExistingNamespaces(semanticModel, namespaceDeclaration.Parent, cancellationToken)
Return outerNamespaces.Concat(namespaceImports).
Distinct().
OrderBy(INamespaceSymbolExtensions.CompareNamespaces).
ToList()
End Function
Private Overloads Function GetExistingNamespaces(semanticModel As SemanticModel, compilationUnit As CompilationUnitSyntax, cancellationToken As CancellationToken) As IList(Of INamespaceSymbol)
Dim memberImports =
From i In compilationUnit.Imports
From c In i.ImportsClauses.OfType(Of SimpleImportsClauseSyntax)()
Where c.Alias Is Nothing
Let symbol = TryCast(semanticModel.GetSymbolInfo(c.Name, cancellationToken).Symbol, INamespaceSymbol)
Where symbol IsNot Nothing AndAlso Not symbol.IsGlobalNamespace
Select symbol
Dim compilationImports =
DirectCast(semanticModel.Compilation, VisualBasicCompilation).MemberImports.OfType(Of INamespaceSymbol)()
Return memberImports.Concat(compilationImports).
Distinct().
OrderBy(INamespaceSymbolExtensions.CompareNamespaces).
ToList()
End Function
Public Overrides Async Function AddAsync(
placeSystemNamespaceFirst As Boolean,
options As CodeGenerationOptions,
cancellationToken As CancellationToken) As Task(Of Document)
Dim importsContainerToMissingNamespaces = Await DetermineNamespaceToImportAsync(
options, cancellationToken).ConfigureAwait(False)
If importsContainerToMissingNamespaces.Count = 0 Then
Return Me.Document
End If
Dim commonRoot = Await Document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
Dim root = DirectCast(commonRoot, CompilationUnitSyntax)
If Not root.CanAddImportsStatements(cancellationToken) Then
Return Me.Document
End If
Dim usingDirectives =
From n In importsContainerToMissingNamespaces.Values.Flatten
Let name = DirectCast(n.GenerateTypeSyntax(addGlobal:=False), NameSyntax).WithAdditionalAnnotations(Simplifier.Annotation)
Select SyntaxFactory.ImportsStatement(
importsClauses:=SyntaxFactory.SingletonSeparatedList(Of ImportsClauseSyntax)(SyntaxFactory.SimpleImportsClause(name)))
Dim newRoot = root.AddImportsStatements(
usingDirectives.ToList(), placeSystemNamespaceFirst,
CaseCorrector.Annotation, Formatter.Annotation)
Return Document.WithSyntaxRoot(newRoot)
End Function
End Class
End Namespace
......@@ -23,10 +23,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
Return VisualBasicCodeGenerationHelpers.GetDestination(containerNode)
End Function
Protected Overrides Function CreateImportsAdder(document As Document) As AbstractImportsAdder
Return New ImportsStatementsAdder(document)
End Function
Protected Overrides Function GetMemberComparer() As IComparer(Of SyntaxNode)
Return VisualBasicDeclarationComparer.WithoutNamesInstance
End Function
......
' 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.Composition
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.Simplification
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Editing
......@@ -29,6 +32,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Editing
Return Nothing
End Function
Protected Overrides Function MakeSafeToAddNamespaces(root As SyntaxNode, namespaceMembers As IEnumerable(Of INamespaceOrTypeSymbol), extensionMethods As IEnumerable(Of IMethodSymbol), model As SemanticModel, workspace As Workspace, cancellationToken As CancellationToken) As SyntaxNode
Dim Rewriter = New Rewriter(namespaceMembers, extensionMethods, model, workspace, cancellationToken)
Return Rewriter.Visit(root)
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
......@@ -44,5 +53,92 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Editing
Return Nothing
End Function
Private Class Rewriter
Inherits VisualBasicSyntaxRewriter
Private ReadOnly _workspace As Workspace
Private ReadOnly _cancellationToken As CancellationToken
Private ReadOnly _model As SemanticModel
Private ReadOnly _namespaceMembers As HashSet(Of String)
Private ReadOnly _extensionMethods As HashSet(Of String)
Public Sub New(namespaceMembers As IEnumerable(Of INamespaceOrTypeSymbol), extensionMethods As IEnumerable(Of IMethodSymbol), model As SemanticModel, workspace As Workspace, cancellationToken As CancellationToken)
_model = model
_workspace = workspace
_cancellationToken = cancellationToken
_namespaceMembers = New HashSet(Of String)(namespaceMembers.[Select](Function(x) x.Name), CaseInsensitiveComparison.Comparer)
_extensionMethods = New HashSet(Of String)(extensionMethods.[Select](Function(x) x.Name), CaseInsensitiveComparison.Comparer)
End Sub
Public Overrides ReadOnly Property VisitIntoStructuredTrivia As Boolean
Get
Return True
End Get
End Property
Public Overrides Function VisitIdentifierName(node As IdentifierNameSyntax) As SyntaxNode
If _namespaceMembers.Contains(node.Identifier.Text) Then
Return Simplifier.Expand(Of SyntaxNode)(node, _model, _workspace, cancellationToken:=_cancellationToken)
End If
Return node
End Function
Public Overrides Function VisitGenericName(node As GenericNameSyntax) As SyntaxNode
If _namespaceMembers.Contains(node.Identifier.Text) Then
Return Simplifier.Expand(Of SyntaxNode)(node, _model, _workspace, cancellationToken:=_cancellationToken)
End If
Dim typeArgumentList = DirectCast(MyBase.Visit(node.TypeArgumentList), TypeArgumentListSyntax)
Return node.Update(node.Identifier, typeArgumentList)
End Function
Public Overrides Function VisitQualifiedName(node As QualifiedNameSyntax) As SyntaxNode
Dim left = CType(MyBase.Visit(node.Left), NameSyntax)
' We don't recurse on the right, as if B is a member of the imported namespace, A.B is still not ambiguous
Dim right = node.Right
If TypeOf right Is GenericNameSyntax Then
Dim genericName = DirectCast(right, GenericNameSyntax)
Dim typeArgumentList = DirectCast(MyBase.Visit(genericName.TypeArgumentList), TypeArgumentListSyntax)
right = genericName.Update(genericName.Identifier, typeArgumentList)
End If
Return node.Update(left, node.DotToken, right)
End Function
Public Overrides Function VisitInvocationExpression(node As InvocationExpressionSyntax) As SyntaxNode
If TypeOf node.Expression Is MemberAccessExpressionSyntax Then
Dim memberAccess = DirectCast(node.Expression, MemberAccessExpressionSyntax)
If _extensionMethods.Contains(memberAccess.Name.Identifier.Text) Then
' No need to visit this as simplifier will expand everything
Return Simplifier.Expand(Of SyntaxNode)(node, _model, _workspace, cancellationToken:=_cancellationToken)
End If
End If
Return MyBase.VisitInvocationExpression(node)
End Function
Public Overrides Function VisitMemberAccessExpression(ByVal node As MemberAccessExpressionSyntax) As SyntaxNode
node = DirectCast(MyBase.VisitMemberAccessExpression(node), MemberAccessExpressionSyntax)
If _extensionMethods.Contains(node.Name.Identifier.Text) Then
' If an extension method is used as a delegate rather than invoked directly,
' there is no semantically valid transformation that will fully qualify the extension method.
' For example `Dim f As Func<int> = x.M;` is not the same as `Dim f As Func<int> = Function(x) Extensions.M(x);`
' since one captures x by value, and the other by reference.
'
' We will not visit this node if the parent node was an InvocationExpression,
' since we would have expanded the parent node entirely, rather than visiting it.
' Therefore it's possible that this is an extension method being used as a delegate so we warn.
node = node.WithAdditionalAnnotations(WarningAnnotation.Create(String.Format(
WorkspacesResources.Warning_adding_imports_will_bring_an_extension_method_into_scope_with_the_same_name_as_member_access,
node.Name.Identifier.Text)))
End If
Return node
End Function
End Class
End Class
End Namespace
......@@ -9,19 +9,22 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Utilities
Friend Class ImportsClauseComparer
Implements IComparer(Of ImportsClauseSyntax)
Public Shared ReadOnly Instance As IComparer(Of ImportsClauseSyntax) = New ImportsClauseComparer()
Public Shared ReadOnly NormalInstance As IComparer(Of ImportsClauseSyntax) = New ImportsClauseComparer()
Private ReadOnly _nameComparer As IComparer(Of NameSyntax)
Private ReadOnly _tokenComparer As IComparer(Of SyntaxToken)
Private Sub New()
_nameComparer = NameSyntaxComparer.Create(TokenComparer.NormalInstance)
_tokenComparer = TokenComparer.NormalInstance
End Sub
Public Function Compare(x As ImportsClauseSyntax, y As ImportsClauseSyntax) As Integer Implements IComparer(Of ImportsClauseSyntax).Compare
Return CompareClauses(x, y, _nameComparer)
End Function
Public Sub New(tokenComparer As IComparer(Of SyntaxToken))
_nameComparer = NameSyntaxComparer.Create(tokenComparer)
_tokenComparer = tokenComparer
End Sub
Friend Shared Function CompareClauses(x As ImportsClauseSyntax, y As ImportsClauseSyntax, nameComparer As IComparer(Of NameSyntax)) As Integer
Friend Function Compare(x As ImportsClauseSyntax, y As ImportsClauseSyntax) As Integer Implements IComparer(Of ImportsClauseSyntax).Compare
Dim imports1 = TryCast(x, SimpleImportsClauseSyntax)
Dim imports2 = TryCast(y, SimpleImportsClauseSyntax)
Dim xml1 = TryCast(x, XmlNamespaceImportsClauseSyntax)
......@@ -41,21 +44,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Utilities
ElseIf imports1.Alias Is Nothing AndAlso imports2.Alias IsNot Nothing Then
Return -1
ElseIf imports1.Alias IsNot Nothing AndAlso imports2.Alias IsNot Nothing Then
Return TokenComparer.NormalInstance.Compare(imports1.Alias.Identifier, imports2.Alias.Identifier)
Return _tokenComparer.Compare(imports1.Alias.Identifier, imports2.Alias.Identifier)
Else
Return nameComparer.Compare(imports1.Name, imports2.Name)
Return _nameComparer.Compare(imports1.Name, imports2.Name)
End If
End If
Return 0
End Function
Private Shared Function CompareXmlNames(xmlName1 As XmlNameSyntax, xmlName2 As XmlNameSyntax) As Integer
Private Function CompareXmlNames(xmlName1 As XmlNameSyntax, xmlName2 As XmlNameSyntax) As Integer
Dim tokens1 = xmlName1.DescendantTokens().Where(Function(t) t.Kind = SyntaxKind.IdentifierToken).ToList()
Dim tokens2 = xmlName2.DescendantTokens().Where(Function(t) t.Kind = SyntaxKind.IdentifierToken).ToList()
For i = 0 To Math.Min(tokens1.Count - 1, tokens2.Count - 1)
Dim compare = TokenComparer.NormalInstance.Compare(tokens1(i), tokens2(i))
Dim compare = _tokenComparer.Compare(tokens1(i), tokens2(i))
If compare <> 0 Then
Return compare
End If
......
......@@ -134,7 +134,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Utilities
Public Shared Function Organize(clauses As SeparatedSyntaxList(Of ImportsClauseSyntax),
placeSystemNamespaceFirst As Boolean) As SeparatedSyntaxList(Of ImportsClauseSyntax)
If clauses.Count > 0 Then
Dim result = clauses.OrderBy(ImportsClauseComparer.Instance).ToList()
Dim result = clauses.OrderBy(ImportsClauseComparer.NormalInstance).ToList()
If Not result.SequenceEqual(clauses) Then
Return SyntaxFactory.SeparatedList(result, clauses.GetSeparators())
......
......@@ -13,14 +13,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Utilities
Friend Class ImportsStatementComparer
Implements IComparer(Of ImportsStatementSyntax)
Public Shared ReadOnly SystemFirstInstance As IComparer(Of ImportsStatementSyntax) = New ImportsStatementComparer(NameSyntaxComparer.Create(TokenComparer.SystemFirstInstance))
Public Shared ReadOnly NormalInstance As IComparer(Of ImportsStatementSyntax) = New ImportsStatementComparer(NameSyntaxComparer.Create(TokenComparer.NormalInstance))
Public Shared ReadOnly SystemFirstInstance As IComparer(Of ImportsStatementSyntax) = New ImportsStatementComparer(TokenComparer.SystemFirstInstance)
Public Shared ReadOnly NormalInstance As IComparer(Of ImportsStatementSyntax) = New ImportsStatementComparer(TokenComparer.NormalInstance)
Private ReadOnly _nameComparer As IComparer(Of NameSyntax)
Private ReadOnly _importsClauseComparer As IComparer(Of ImportsClauseSyntax)
Private Sub New(nameComparer As IComparer(Of NameSyntax))
Debug.Assert(nameComparer IsNot Nothing)
Me._nameComparer = nameComparer
Public Sub New(tokenComparer As IComparer(Of SyntaxToken))
Debug.Assert(tokenComparer IsNot Nothing)
Me._importsClauseComparer = New ImportsClauseComparer(tokenComparer)
End Sub
Public Function Compare(directive1 As ImportsStatementSyntax, directive2 As ImportsStatementSyntax) As Integer Implements IComparer(Of ImportsStatementSyntax).Compare
......@@ -36,12 +36,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Utilities
ElseIf directive2.ImportsClauses.Count = 0 Then
Return 1
Else
Return ImportsClauseComparer.CompareClauses(directive1.ImportsClauses(0), directive2.ImportsClauses(0), _nameComparer)
Return _importsClauseComparer.Compare(directive1.ImportsClauses(0), directive2.ImportsClauses(0))
End If
End Function
Private Function CompareNames(nameSyntax1 As NameSyntax, nameSyntax2 As NameSyntax) As Integer
Return _nameComparer.Compare(nameSyntax1, nameSyntax2)
End Function
End Class
End Namespace
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册