未验证 提交 b4e2e1bd 编写于 作者: M Manish Vasani 提交者: GitHub

Merge pull request #44368 from mavasani/RenameInSuppressMessage

Add FAR and Inline rename support for symbol references in global suppressions
......@@ -200,6 +200,31 @@ namespace NS
]]>
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestAliasReferenceInGlobalSuppression(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
using $$AliasToC = N.[|C|];
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("RuleCategory", "RuleId", Scope = "member", Target = "~M:N.[|C|].Goo")]
namespace N
{
class {|Definition:C|}
{
void Goo()
{
}
}
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
......
......@@ -890,6 +890,44 @@ class C
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestConstructorReferenceInGlobalSuppression(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:D.[|#ctor|]")]
class D
{
public {|Definition:$$D|}() { }
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestStaticConstructorReferenceInGlobalSuppression(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:D.[|#cctor|]")]
static class D
{
static {|Definition:$$D|}() { }
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
......
......@@ -508,6 +508,25 @@ class Definition:Program
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestFieldReferenceInGlobalSuppression(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~F:C.[|i|]")]
class C
{
int {|Definition:$$i|};
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
......
......@@ -246,6 +246,48 @@ class C
end class
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestIndexerReferenceInGlobalSuppression(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~P:C.[|Item|](System.Int32)")]
class C
{
public int {|Definition:$$this|}[int y] { get { } }
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestParameterizedPropertyReferenceInGlobalSuppression(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="Visual Basic" CommonReferences="true">
<Document><![CDATA[
<Assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope:="member", Target:="~P:C.[|Goo|](System.Int32)")>
Class C
ReadOnly Property {|Definition:$$Goo|}(x As Integer) As Integer
Get
Return 0
End Get
End Property
End Class
]]>
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
......
......@@ -2311,6 +2311,174 @@ public class D { }
}]]>
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestTypeReferenceInGlobalSuppression(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~T:N.[|C|]")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:N.[|C|].M")]
namespace N
{
class {|Definition:$$C|}
{
void M()
{
}
}
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestTypeReferenceInGlobalSuppression_NestedType(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~T:N.C1.[|C2|]")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:N.C1.[|C2|].M")]
namespace N
{
class C1
{
private class {|Definition:$$C2|}
{
void M()
{
}
}
}
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestTypeReferenceInGlobalSuppression_GenericType(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document><![CDATA[
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~T:N.[|C|]`1")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:N.[|C|]`1.M")]
namespace N
{
class {|Definition:$$C|}<T>
{
void M()
{
}
}
}]]>
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WorkItem(44401, "https://github.com/dotnet/roslyn/issues/44401")>
<WpfTheory(Skip:="https://github.com/dotnet/roslyn/issues/44401")>
<CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestTypeReferenceInGlobalSuppressionParameter(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:N.D.M(N.[|C|])")]
namespace N
{
class D
{
void M([|C|] c)
{
}
}
class {|Definition:$$C|}
{
}
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WorkItem(44401, "https://github.com/dotnet/roslyn/issues/44401")>
<WpfTheory(Skip:="https://github.com/dotnet/roslyn/issues/44401")>
<CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestTypeReferenceInGlobalSuppressionParameter_GenericType(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document><![CDATA[
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:N.D.M(N.[|C|]{System.Int32})")]
namespace N
{
class D
{
void M([|C|]<int> c)
{
}
}
class {|Definition:$$C|}<T>
{
}
}]]>
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WorkItem(44401, "https://github.com/dotnet/roslyn/issues/44401")>
<WpfTheory(Skip:="https://github.com/dotnet/roslyn/issues/44401")>
<CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestTypeReferenceInGlobalSuppressionTypeParameter_GenericType(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document><![CDATA[
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:N.[|D|].M(N.C{N.[|D|]})")]
namespace N
{
class {|Definition:$$D|}
{
void M(C<[|D|]> c)
{
}
}
class C<T>
{
}
}]]>
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
......
......@@ -399,6 +399,85 @@ namespace var { }
}
</Document>
</Project>
</Workspace>
Await TestAPI(input, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestNamespaceReferenceInGlobalSuppression(host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~N:[|N|]")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~T:[|N|].C")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:[|N|].C.M")]
namespace {|Definition:[|$$N|]|}
{
class C
{
void M()
{
}
}
}
</Document>
</Project>
</Workspace>
Await TestAPI(input, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestNamespaceReferenceInGlobalSuppression_OuterNamespace(host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~N:[|N1|]")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~N:[|N1|].N2")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~T:[|N1|].N2.C")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:[|N1|].N2.C.M")]
namespace {|Definition:[|$$N1|]|}.N2
{
class C
{
void M()
{
}
}
}
</Document>
</Project>
</Workspace>
Await TestAPI(input, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestNamespaceReferenceInGlobalSuppression_InnerNamespace(host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~N:N1.[|N2|]")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~T:N1.[|N2|].C")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:N1.[|N2|].C.M")]
namespace N1.{|Definition:[|$$N2|]|}
{
class C
{
void M()
{
}
}
}
</Document>
</Project>
</Workspace>
Await TestAPI(input, host)
End Function
......
......@@ -296,6 +296,30 @@ class A
end class
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestOperatorReferenceInGlobalSuppression(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:A.[|op_Addition|](A,A)~A")]
class A
{
void Goo()
{
var x = new A() [|$$+|] new A();
}
public static A operator {|Definition:+|}(A a, A b) { return a; }
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
......
......@@ -3215,6 +3215,45 @@ End Class
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestMethodReferenceInGlobalSuppression(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:C.[|M|]")]
class C
{
private void {|Definition:$$M|}() { }
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestMethodReferenceInGlobalSuppression_MethodWithParameters(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:C.[|M|](System.String)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:C.M(System.Int32)")]
class C
{
private void {|Definition:$$M|}(string s) { }
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
......
......@@ -945,6 +945,28 @@ namespace ConsoleApplication22
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
<WpfTheory, CombinatorialData, Trait(Traits.Feature, Traits.Features.FindReferences)>
Public Async Function TestPropertyReferenceInGlobalSuppression(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~P:N.C.[|P|]")]
namespace N
{
class C
{
public int {|Definition:$$P|} { get; set; }
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function
......
......@@ -452,12 +452,16 @@ public class [|$$C|] { }
<WpfTheory>
<CombinatorialData, Trait(Traits.Feature, Traits.Features.Rename)>
<WorkItem(700923, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/700923"), WorkItem(700925, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/700925"), WorkItem(1486, "https://github.com/dotnet/roslyn/issues/1486")>
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
Public Async Function RenameInCommentsAndStringsCSharp(host As TestHost) As Task
Using workspace = CreateWorkspaceWithWaiter(
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
<![CDATA[
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:Program.[|goo|]")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:Program.goo(System.Int32)")]
class Program
{
/// <[|goo|]> [|goo|]! </[|goo|]>
......@@ -489,6 +493,9 @@ class Program
<Project Language="C#" CommonReferences="true">
<Document>
<![CDATA[
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:Program.[|goo|]")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:Program.[|goo|](System.Int32)")]
class Program
{
/// <[|goo|]> [|goo|]! </[|goo|]>
......@@ -520,6 +527,9 @@ class Program
<Project Language="C#" CommonReferences="true">
<Document>
<![CDATA[
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:Program.[|goo|]")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:Program.[|goo|](System.Int32)")]
class Program
{
/// <[|goo|]> [|goo|]! </[|goo|]>
......@@ -550,12 +560,16 @@ class Program
<WpfTheory>
<CombinatorialData, Trait(Traits.Feature, Traits.Features.Rename)>
<WorkItem(700923, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/700923"), WorkItem(700925, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/700925"), WorkItem(1486, "https://github.com/dotnet/roslyn/issues/1486")>
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
Public Async Function RenameInCommentsAndStringsVisualBasic(host As TestHost) As Task
Using workspace = CreateWorkspaceWithWaiter(
<Workspace>
<Project Language="Visual Basic" CommonReferences="true">
<Document>
<![CDATA[
<Assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope:="member", Target:="~M:Program.[|goo|]")>
<Assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope:="member", Target:="~M:Program.goo(System.Int32)")>
Class Program
''' <[|goo|]> [|goo|]! </[|goo|]>
Public Sub [|$$goo|]()
......@@ -585,6 +599,9 @@ End Class
<Project Language="Visual Basic" CommonReferences="true">
<Document>
<![CDATA[
<Assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope:="member", Target:="~M:Program.[|goo|]")>
<Assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope:="member", Target:="~M:Program.[|goo|](System.Int32)")>
Class Program
''' <[|goo|]> [|goo|]! </[|goo|]>
Public Sub [|$$goo|]()
......@@ -614,6 +631,9 @@ End Class
<Project Language="Visual Basic" CommonReferences="true">
<Document>
<![CDATA[
<Assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope:="member", Target:="~M:Program.[|goo|]")>
<Assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope:="member", Target:="~M:Program.[|goo|](System.Int32)")>
Class Program
''' <[|goo|]> [|goo|]! </[|goo|]>
Public Sub [|$$goo|]()
......@@ -1902,5 +1922,31 @@ class [|$$Test1|]
Await VerifyTagsAreCorrect(workspace, "Test1")
End Using
End Function
<WpfTheory>
<CombinatorialData, Trait(Traits.Feature, Traits.Features.Rename)>
<WorkItem(44288, "https://github.com/dotnet/roslyn/issues/44288")>
Public Async Function RenameConstructorReferencedInGlobalSuppression(host As TestHost) As Task
Using workspace = CreateWorkspaceWithWaiter(
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
<![CDATA[
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Category", "RuleId", Scope = "member", Target = "~M:[|C|].#ctor")]
class [|C|]
{
public [|$$C|]()
{
}
}]]>
</Document>
</Project>
</Workspace>, host)
Await VerifyRenameOptionChangedSessionCommit(workspace, "C", "D")
VerifyFileName(workspace, "Test1")
End Using
End Function
End Class
End Namespace
......@@ -84,7 +84,7 @@ public string GetDisplayName(SemanticModel semanticModel, SyntaxNode node)
return FeaturesResources.paren_Unknown_paren;
}
if (CSharpSyntaxFacts.Instance.IsGlobalAttribute(node))
if (CSharpSyntaxFacts.Instance.IsGlobalAssemblyAttribute(node))
{
return "assembly: " + node.ConvertToSingleLine();
}
......
......@@ -162,7 +162,7 @@ private static SyntaxNode GetEnclosingCodeElementNode(Document document, SyntaxT
}
else if (syntaxFactsService.IsDeclaration(node) ||
syntaxFactsService.IsUsingOrExternOrImport(node) ||
syntaxFactsService.IsGlobalAttribute(node))
syntaxFactsService.IsGlobalAssemblyAttribute(node))
{
break;
}
......
......@@ -117,7 +117,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeLens
''' Gets the DisplayName for the given node.
''' </summary>
Public Function GetDisplayName(semanticModel As SemanticModel, node As SyntaxNode) As String Implements ICodeLensDisplayInfoService.GetDisplayName
If VisualBasicSyntaxFacts.Instance.IsGlobalAttribute(node) Then
If VisualBasicSyntaxFacts.Instance.IsGlobalAssemblyAttribute(node) Then
Return node.ToString()
End If
......
......@@ -2,9 +2,12 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
......@@ -39,7 +42,7 @@ internal abstract partial class AbstractReferenceFinder : IReferenceFinder
protected static bool TryGetNameWithoutAttributeSuffix(
string name,
ISyntaxFactsService syntaxFacts,
out string result)
[NotNullWhen(returnValue: true)] out string? result)
{
return name.TryGetWithoutAttributeSuffix(syntaxFacts.IsCaseSensitive, out result);
}
......@@ -79,11 +82,22 @@ protected static async Task<ImmutableArray<Document>> FindDocumentsAsync(Project
/// Finds all the documents in the provided project that contain the requested string
/// values
/// </summary>
protected static Task<ImmutableArray<Document>> FindDocumentsAsync(Project project, IImmutableSet<Document> documents, CancellationToken cancellationToken, params string[] values)
protected static Task<ImmutableArray<Document>> FindDocumentsAsync(
Project project,
IImmutableSet<Document> documents,
bool findInGlobalSuppressions,
CancellationToken cancellationToken,
params string[] values)
{
return FindDocumentsAsync(project, documents, async (d, c) =>
{
var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false);
if (findInGlobalSuppressions && info.ContainsGlobalAttributes)
{
return true;
}
foreach (var value in values)
{
if (!info.ProbablyContainsIdentifier(value))
......@@ -128,7 +142,9 @@ protected static Task<ImmutableArray<Document>> FindDocumentsAsync(Project proje
return FindDocumentsAsync(project, documents, async (d, c) =>
{
var info = await SyntaxTreeIndex.GetIndexAsync(d, c).ConfigureAwait(false);
return info.ContainsPredefinedOperator(op);
// NOTE: Predefined operators can be referenced in global suppression attributes.
return info.ContainsPredefinedOperator(op) || info.ContainsGlobalAttributes;
}, cancellationToken);
}
......@@ -152,13 +168,26 @@ protected static bool IdentifiersMatch(ISyntaxFactsService syntaxFacts, string n
string identifier,
Document document,
SemanticModel semanticModel,
Func<SyntaxToken, SyntaxNode> findParentNode,
Func<SyntaxToken, SyntaxNode>? findParentNode,
CancellationToken cancellationToken)
{
var symbolsMatch = GetStandardSymbolsMatchFunction(symbol, findParentNode, document.Project.Solution, cancellationToken);
return FindReferencesInDocumentUsingIdentifierAsync(
symbol, identifier, document, semanticModel, symbolsMatch, cancellationToken);
}
protected static Task<ImmutableArray<FinderLocation>> FindReferencesInDocumentUsingIdentifierAsync(
ISymbol symbol,
string identifier,
Document document,
SemanticModel semanticModel,
Func<SyntaxToken, SemanticModel, (bool matched, CandidateReason reason)> symbolsMatch,
CancellationToken cancellationToken)
{
var findInGlobalSuppressions = ShouldFindReferencesInGlobalSuppressions(symbol, out var docCommentId);
return FindReferencesInDocumentUsingIdentifierAsync(
identifier, document, semanticModel, symbolsMatch, cancellationToken);
identifier, document, semanticModel, symbolsMatch,
docCommentId, findInGlobalSuppressions, cancellationToken);
}
protected static async Task<ImmutableArray<FinderLocation>> FindReferencesInDocumentUsingIdentifierAsync(
......@@ -166,19 +195,29 @@ protected static bool IdentifiersMatch(ISyntaxFactsService syntaxFacts, string n
Document document,
SemanticModel semanticModel,
Func<SyntaxToken, SemanticModel, (bool matched, CandidateReason reason)> symbolsMatch,
string? docCommentId,
bool findInGlobalSuppressions,
CancellationToken cancellationToken)
{
var tokens = await GetIdentifierOrGlobalNamespaceTokensWithTextAsync(document, semanticModel, identifier, cancellationToken).ConfigureAwait(false);
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
return FindReferencesInTokens(
var references = FindReferencesInTokens(
document,
semanticModel,
tokens,
t => IdentifiersMatch(syntaxFacts, identifier, t),
symbolsMatch,
cancellationToken);
if (!findInGlobalSuppressions)
return references;
RoslynDebug.Assert(docCommentId != null);
var referencesInGlobalSuppressions = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(
document, semanticModel, syntaxFacts, docCommentId, cancellationToken).ConfigureAwait(false);
return references.AddRange(referencesInGlobalSuppressions);
}
protected static async Task<ImmutableArray<SyntaxToken>> GetIdentifierOrGlobalNamespaceTokensWithTextAsync(Document document, SemanticModel semanticModel, string identifier, CancellationToken cancellationToken)
......@@ -195,7 +234,7 @@ protected static async Task<ImmutableArray<SyntaxToken>> GetIdentifierOrGlobalNa
var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
SourceText text = null;
SourceText? text = null;
if (!info.ProbablyContainsEscapedIdentifier(identifier))
text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
......@@ -204,10 +243,10 @@ protected static async Task<ImmutableArray<SyntaxToken>> GetIdentifierOrGlobalNa
}
protected static Func<SyntaxToken, SemanticModel, (bool matched, CandidateReason reason)> GetStandardSymbolsMatchFunction(
ISymbol symbol, Func<SyntaxToken, SyntaxNode> findParentNode, Solution solution, CancellationToken cancellationToken)
ISymbol symbol, Func<SyntaxToken, SyntaxNode>? findParentNode, Solution solution, CancellationToken cancellationToken)
{
var nodeMatch = GetStandardSymbolsNodeMatchFunction(symbol, solution, cancellationToken);
findParentNode ??= (t => t.Parent);
findParentNode ??= (t => t.Parent!);
return (token, model) => nodeMatch(findParentNode(token), model);
}
......@@ -244,8 +283,8 @@ protected static async Task<ImmutableArray<SyntaxToken>> GetIdentifierOrGlobalNa
Func<SyntaxToken, SemanticModel, (bool matched, CandidateReason reason)> symbolsMatch,
CancellationToken cancellationToken)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var semanticFacts = document.GetLanguageService<ISemanticFactsService>();
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
var semanticFacts = document.GetRequiredLanguageService<ISemanticFactsService>();
var locations = ArrayBuilder<FinderLocation>.GetInstance();
foreach (var token in tokens)
......@@ -257,6 +296,8 @@ protected static async Task<ImmutableArray<SyntaxToken>> GetIdentifierOrGlobalNa
var (matched, reason) = symbolsMatch(token, semanticModel);
if (matched)
{
RoslynDebug.Assert(token.Parent != null);
var alias = FindReferenceCache.GetAliasInfo(semanticFacts, semanticModel, token, cancellationToken);
var location = token.GetLocation();
......@@ -272,21 +313,21 @@ protected static async Task<ImmutableArray<SyntaxToken>> GetIdentifierOrGlobalNa
return locations.ToImmutableAndFree();
}
private static IAliasSymbol GetAliasSymbol(
private static IAliasSymbol? GetAliasSymbol(
Document document,
SemanticModel semanticModel,
SyntaxNode node,
CancellationToken cancellationToken)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
if (syntaxFacts.IsRightSideOfQualifiedName(node))
{
node = node.Parent;
node = node.Parent!;
}
if (syntaxFacts.IsUsingDirectiveName(node))
{
var directive = node.Parent;
var directive = node.Parent!;
if (semanticModel.GetDeclaredSymbol(directive, cancellationToken) is IAliasSymbol aliasSymbol)
{
return aliasSymbol;
......@@ -313,7 +354,7 @@ protected static async Task<ImmutableArray<SyntaxToken>> GetIdentifierOrGlobalNa
ISymbol symbol,
Document document,
SemanticModel semanticModel,
Func<SyntaxToken, SyntaxNode> findParentNode,
Func<SyntaxToken, SyntaxNode>? findParentNode,
CancellationToken cancellationToken)
{
var aliasSymbols = GetAliasSymbols(document, semanticModel, nonAliasReferences, cancellationToken);
......@@ -365,10 +406,10 @@ protected static async Task<ImmutableArray<SyntaxToken>> GetIdentifierOrGlobalNa
Document document,
SemanticModel semanticModel,
ImmutableArray<IAliasSymbol> aliasSymbols,
Func<SyntaxToken, SyntaxNode> findParentNode,
Func<SyntaxToken, SyntaxNode>? findParentNode,
CancellationToken cancellationToken)
{
var syntaxFactsService = document.GetLanguageService<ISyntaxFactsService>();
var syntaxFactsService = document.GetRequiredLanguageService<ISyntaxFactsService>();
var allAliasReferences = ArrayBuilder<FinderLocation>.GetInstance();
foreach (var aliasSymbol in aliasSymbols)
{
......@@ -395,17 +436,23 @@ protected static async Task<ImmutableArray<SyntaxToken>> GetIdentifierOrGlobalNa
Func<SyntaxToken, SemanticModel, (bool matched, CandidateReason reason)> symbolsMatch,
CancellationToken cancellationToken)
{
var syntaxFactsService = document.GetLanguageService<ISyntaxFactsService>();
var syntaxFactsService = document.GetRequiredLanguageService<ISyntaxFactsService>();
var allAliasReferences = ArrayBuilder<FinderLocation>.GetInstance();
foreach (var aliasSymbol in aliasSymbols)
{
var aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync(aliasSymbol.Name, document, semanticModel, symbolsMatch, cancellationToken).ConfigureAwait(false);
var findInGlobalSuppressions = ShouldFindReferencesInGlobalSuppressions(aliasSymbol, out var docCommentId);
var aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync(
aliasSymbol.Name, document, semanticModel, symbolsMatch,
docCommentId, findInGlobalSuppressions, cancellationToken).ConfigureAwait(false);
allAliasReferences.AddRange(aliasReferences);
// the alias may reference an attribute and the alias name may end with an "Attribute" suffix. In this case search for the
// shortened name as well (e.g. using GooAttribute = MyNamespace.GooAttribute; [Goo] class C1 {})
if (TryGetNameWithoutAttributeSuffix(aliasSymbol.Name, syntaxFactsService, out var simpleName))
{
aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync(simpleName, document, semanticModel, symbolsMatch, cancellationToken).ConfigureAwait(false);
aliasReferences = await FindReferencesInDocumentUsingIdentifierAsync(
simpleName, document, semanticModel, symbolsMatch,
docCommentId, findInGlobalSuppressions, cancellationToken).ConfigureAwait(false);
allAliasReferences.AddRange(aliasReferences);
}
}
......@@ -450,9 +497,9 @@ protected static Task<ImmutableArray<Document>> FindDocumentsWithImplicitObjectC
var syntaxTreeInfo = await SyntaxTreeIndex.GetIndexAsync(document, cancellationToken).ConfigureAwait(false);
if (isRelevantDocument(syntaxTreeInfo))
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var semanticFacts = document.GetLanguageService<ISemanticFactsService>();
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
var semanticFacts = document.GetRequiredLanguageService<ISemanticFactsService>();
var syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var locations = ArrayBuilder<FinderLocation>.GetInstance();
......@@ -592,7 +639,7 @@ static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo)
}
}
private static bool Matches(ISymbol symbol1, ISymbol notNulloriginalUnreducedSymbol2)
private static bool Matches(ISymbol? symbol1, ISymbol notNulloriginalUnreducedSymbol2)
{
return symbol1 != null && SymbolEquivalenceComparer.Instance.Equals(
symbol1.GetOriginalUnreducedDefinition(),
......@@ -929,7 +976,7 @@ public override Task<ImmutableArray<Project>> DetermineProjectsToSearchAsync(ISy
SemanticModel semanticModel,
IEnumerable<SyntaxToken> tokens,
Func<SyntaxToken, bool> tokensMatch,
Func<SyntaxToken, SyntaxNode> findParentNode,
Func<SyntaxToken, SyntaxNode>? findParentNode,
CancellationToken cancellationToken)
{
var symbolsMatch = GetStandardSymbolsMatchFunction(symbol, findParentNode, document.Project.Solution, cancellationToken);
......@@ -960,11 +1007,13 @@ public override Task<ImmutableArray<Project>> DetermineProjectsToSearchAsync(ISy
Document document,
SemanticModel semanticModel,
Func<SyntaxToken, bool> tokensMatch,
Func<SyntaxToken, SyntaxNode> findParentNode,
Func<SyntaxToken, SyntaxNode>? findParentNode,
CancellationToken cancellationToken)
{
var findInGlobalSuppressions = ShouldFindReferencesInGlobalSuppressions(symbol, out var docCommentId);
var symbolsMatch = GetStandardSymbolsMatchFunction(symbol, findParentNode, document.Project.Solution, cancellationToken);
return FindReferencesInDocumentAsync(document, semanticModel, tokensMatch, symbolsMatch, cancellationToken);
return FindReferencesInDocumentAsync(document, semanticModel, tokensMatch,
symbolsMatch, docCommentId, findInGlobalSuppressions, cancellationToken);
}
protected static async Task<ImmutableArray<FinderLocation>> FindReferencesInDocumentAsync(
......@@ -972,13 +1021,24 @@ public override Task<ImmutableArray<Project>> DetermineProjectsToSearchAsync(ISy
SemanticModel semanticModel,
Func<SyntaxToken, bool> tokensMatch,
Func<SyntaxToken, SemanticModel, (bool matched, CandidateReason reason)> symbolsMatch,
string? docCommentId,
bool findInGlobalSuppressions,
CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
// Now that we have Doc Comments in place, We are searching for References in the Trivia as well by setting descendIntoTrivia: true
var tokens = root.DescendantTokens(descendIntoTrivia: true);
return FindReferencesInTokens(document, semanticModel, tokens, tokensMatch, symbolsMatch, cancellationToken);
var references = FindReferencesInTokens(document, semanticModel, tokens, tokensMatch, symbolsMatch, cancellationToken);
if (!findInGlobalSuppressions)
return references;
RoslynDebug.Assert(docCommentId != null);
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
var referencesInGlobalSuppressions = await FindReferencesInDocumentInsideGlobalSuppressionsAsync(
document, semanticModel, syntaxFacts, docCommentId, cancellationToken).ConfigureAwait(false);
return references.AddRange(referencesInGlobalSuppressions);
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.FindSymbols.Finders
{
internal abstract partial class AbstractReferenceFinder : IReferenceFinder
{
/// <summary>
/// Regex for symbol documentation comment ID.
/// For example, "~M:C.X(System.String)" represents the documentation comment ID of a method named 'X'
/// that takes a single string typed parameter and is contained in a type named 'C'.
///
/// We divide the regex it into 3 groups:
/// 1. Prefix:
/// - Starts with an optional '~'
/// - Followed by a single capital letter indicating the symbol kind (for example, 'M' indicates method symbol)
/// - Followed by ':'
/// 2. Core symbol ID, which is its fully qualified name before the optional parameter list and return type (i.e. before the '(' or '[' tokens)
/// 3. Optional parameter list and/or return type that begins with a '(' or '[' tokens.
///
/// For the above example, "~M:" is the prefix, "C.X" is the core symbol ID and "(System.String)" is the parameter list.
/// </summary>
private static readonly Regex s_docCommentIdPattern = new Regex(@"(~?[A-Z]:)([^([]*)(.*)");
protected static bool ShouldFindReferencesInGlobalSuppressions(ISymbol symbol, [NotNullWhen(returnValue: true)] out string? documentationCommentId)
{
if (!SupportsGlobalSuppression(symbol))
{
documentationCommentId = null;
return false;
}
documentationCommentId = DocumentationCommentId.CreateDeclarationId(symbol);
return documentationCommentId != null;
// Global suppressions are currently supported for types, members and
// namespaces, except global namespace.
static bool SupportsGlobalSuppression(ISymbol symbol)
=> symbol.Kind switch
{
SymbolKind.Namespace => !((INamespaceSymbol)symbol).IsGlobalNamespace,
SymbolKind.NamedType => true,
SymbolKind.Method => true,
SymbolKind.Field => true,
SymbolKind.Property => true,
SymbolKind.Event => true,
_ => false,
};
}
/// <summary>
/// Find references to a symbol inside global suppressions.
/// For example, consider a field 'Field' defined inside a type 'C'.
/// This field's documentation comment ID is 'F:C.Field'
/// A reference to this field inside a global suppression would be as following:
/// [assembly: SuppressMessage("RuleCategory", "RuleId', Scope = "member", Target = "~F:C.Field")]
/// </summary>
protected static async Task<ImmutableArray<FinderLocation>> FindReferencesInDocumentInsideGlobalSuppressionsAsync(
Document document,
SemanticModel semanticModel,
ISyntaxFacts syntaxFacts,
string docCommentId,
CancellationToken cancellationToken)
{
// Check if we have any relevant global attributes in this document.
var info = await SyntaxTreeIndex.GetIndexAsync(document, cancellationToken).ConfigureAwait(false);
if (!info.ContainsGlobalAttributes)
{
return ImmutableArray<FinderLocation>.Empty;
}
var suppressMessageAttribute = semanticModel.Compilation.SuppressMessageAttributeType();
if (suppressMessageAttribute == null)
{
return ImmutableArray<FinderLocation>.Empty;
}
// Check if we have any instances of the symbol documentation comment ID string literals within global attributes.
// These string literals represent references to the symbol.
if (!TryGetExpectedDocumentationCommentId(docCommentId, out var expectedDocCommentId))
{
return ImmutableArray<FinderLocation>.Empty;
}
// We map the positions of documentation ID literals in tree to string literal tokens,
// perform semantic checks to ensure these are valid references to the symbol
// and if so, add these locations to the computed references.
var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
using var _ = ArrayBuilder<FinderLocation>.GetInstance(out var locations);
foreach (var token in root.DescendantTokens())
{
if (IsCandidate(token, expectedDocCommentId, semanticModel, syntaxFacts, suppressMessageAttribute, cancellationToken, out var offsetOfReferenceInToken))
{
var referenceLocation = CreateReferenceLocation(offsetOfReferenceInToken, token, root, document, syntaxFacts);
locations.Add(new FinderLocation(token.Parent, referenceLocation));
}
}
return locations.ToImmutable();
// Local functions
static bool IsCandidate(
SyntaxToken token, string expectedDocCommentId, SemanticModel semanticModel, ISyntaxFacts syntaxFacts,
INamedTypeSymbol suppressMessageAttribute, CancellationToken cancellationToken, out int offsetOfReferenceInToken)
{
offsetOfReferenceInToken = -1;
// Check if this token is a named attribute argument to "Target" property of "SuppressMessageAttribute".
if (!IsValidTargetOfGlobalSuppressionAttribute(token, suppressMessageAttribute, semanticModel, syntaxFacts, cancellationToken))
{
return false;
}
// Target string must contain a valid symbol DocumentationCommentId.
if (!ValidateAndSplitDocumentationCommentId(token.ValueText, out var prefix, out var idPartBeforeArguments, out var arguments))
{
return false;
}
// We have couple of success cases:
// 1. The FAR symbol is the same one as the target of the suppression. In this case,
// target string for the suppression exactly matches the expectedDocCommentId.
// 2. The FAR symbol is one of the containing symbols of the target symbol of suppression.
// In this case, the target string for the suppression starts with the expectedDocCommentId.
//
// For example, consider the below suppression applied to field 'Field' of type 'C'
// [assembly: SuppressMessage("RuleCategory", "RuleId', Scope = "member", Target = "~F:C.Field")]
// When doing a FAR query on 'Field', we would return true from case 1.
// When doing a FAR query on 'C', we would return true from case 2.
var docCommentId = idPartBeforeArguments + arguments;
if (!docCommentId.StartsWith(expectedDocCommentId))
{
return false;
}
// We found a match, now compute the offset of the reference within the string literal token.
offsetOfReferenceInToken = prefix.Length;
if (expectedDocCommentId.Length < docCommentId.Length)
{
// Expected doc comment ID belongs to a containing symbol of the symbol referenced in suppression's target doc comment ID.
// Verify the next character in suppression doc comment ID is the '.' separator for its member.
if (docCommentId[expectedDocCommentId.Length] != '.')
return false;
offsetOfReferenceInToken += expectedDocCommentId.LastIndexOf('.') + 1;
}
else
{
// Expected doc comment ID matches the suppression's target doc comment ID.
offsetOfReferenceInToken += idPartBeforeArguments.LastIndexOf('.') + 1;
}
return true;
}
static bool IsValidTargetOfGlobalSuppressionAttribute(
SyntaxToken token,
INamedTypeSymbol suppressMessageAttribute,
SemanticModel semanticModel,
ISyntaxFacts syntaxFacts,
CancellationToken cancellationToken)
{
// We need to check if the given token is a non-null, non-empty string literal token
// passed as a named argument to 'Target' property of a global SuppressMessageAttribute.
//
// For example, consider the below global suppression.
// [assembly: SuppressMessage("RuleCategory", "RuleId', Scope = "member", Target = "F:C.Field")]
//
// We return true when processing "F:C.Field".
if (!syntaxFacts.IsStringLiteral(token))
{
return false;
}
var text = token.ValueText;
if (string.IsNullOrEmpty(text))
{
return false;
}
// We need to go from string literal token "F:C.Field" to the suppression attribute node.
// AttributeSyntax
// -> AttributeArgumentList
// -> AttributeArgument
// -> StringLiteralExpression
// -> StringLiteralToken
var attributeArgument = token.Parent?.Parent;
if (syntaxFacts.GetNameForAttributeArgument(attributeArgument) != "Target")
{
return false;
}
var attributeNode = attributeArgument!.Parent?.Parent;
if (attributeNode == null || !syntaxFacts.IsGlobalAttribute(attributeNode))
{
return false;
}
// Check the attribute type matches 'SuppressMessageAttribute'.
var attributeSymbol = semanticModel.GetSymbolInfo(attributeNode, cancellationToken).Symbol?.ContainingType;
return suppressMessageAttribute.Equals(attributeSymbol);
}
static ReferenceLocation CreateReferenceLocation(
int offsetOfReferenceInToken,
SyntaxToken token,
SyntaxNode root,
Document document,
ISyntaxFacts syntaxFacts)
{
// We found a valid reference to the symbol in documentation comment ID string literal.
// Compute the reference span within this string literal for the identifier.
// For example, consider the suppression below for field 'Field' defined in type 'C':
// [assembly: SuppressMessage("RuleCategory", "RuleId', Scope = "member", Target = "F:C.Field")]
// We compute the span for 'Field' within the target string literal.
// NOTE: '#' is also a valid char in documentation comment ID. For example, '#ctor' and '#cctor'.
var positionOfReferenceInTree = token.SpanStart + offsetOfReferenceInToken + 1;
var valueText = token.ValueText;
var length = 0;
while (offsetOfReferenceInToken < valueText.Length)
{
var ch = valueText[offsetOfReferenceInToken++];
if (ch == '#' || syntaxFacts.IsIdentifierPartCharacter(ch))
length++;
else
break;
}
// We create a reference location of the identifier span within this string literal
// that represents the symbol reference.
// We also add the location for the containing documentation comment ID string literal.
// For the suppression example above, location points to the span of 'Field' inside "F:C.Field"
// and containing string location points to the span of the entire string literal "F:C.Field".
var location = Location.Create(root.SyntaxTree, new TextSpan(positionOfReferenceInTree, length));
var containingStringLocation = token.GetLocation();
return new ReferenceLocation(document, location, containingStringLocation);
}
}
private static bool TryGetExpectedDocumentationCommentId(
string id,
[NotNullWhen(true)] out string? docCommentId)
{
if (!ValidateAndSplitDocumentationCommentId(id, out _, out var idPartBeforeArguments, out var arguments))
{
docCommentId = null;
return false;
}
docCommentId = idPartBeforeArguments + arguments;
return true;
}
private static bool ValidateAndSplitDocumentationCommentId(
string docCommentId,
[NotNullWhen(true)] out string? prefix,
[NotNullWhen(true)] out string? idPartBeforeArguments,
[NotNullWhen(true)] out string? arguments)
{
prefix = null;
idPartBeforeArguments = null;
arguments = null;
if (docCommentId == null)
{
return false;
}
// Match the documentation comment ID with the regex
var match = s_docCommentIdPattern.Match(docCommentId);
if (!match.Success)
{
return false;
}
prefix = match.Groups[1].Value;
idPartBeforeArguments = match.Groups[2].Value;
arguments = match.Groups[3].Value;
return true;
}
}
}
......@@ -24,7 +24,12 @@ private ConstructorSymbolReferenceFinder()
}
protected override bool CanFind(IMethodSymbol symbol)
=> symbol.MethodKind == MethodKind.Constructor;
=> symbol.MethodKind switch
{
MethodKind.Constructor => true,
MethodKind.StaticConstructor => true,
_ => false,
};
protected override async Task<ImmutableArray<Document>> DetermineDocumentsToSearchAsync(
IMethodSymbol symbol,
......@@ -34,10 +39,10 @@ protected override bool CanFind(IMethodSymbol symbol)
CancellationToken cancellationToken)
{
var typeName = symbol.ContainingType.Name;
var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, typeName).ConfigureAwait(false);
var documentsWithName = await FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, typeName).ConfigureAwait(false);
var documentsWithType = await FindDocumentsAsync(project, documents, symbol.ContainingType.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false);
var documentsWithAttribute = TryGetNameWithoutAttributeSuffix(typeName, project.LanguageServices.GetService<ISyntaxFactsService>(), out var simpleName)
? await FindDocumentsAsync(project, documents, cancellationToken, simpleName).ConfigureAwait(false)
var documentsWithAttribute = TryGetNameWithoutAttributeSuffix(typeName, project.LanguageServices.GetRequiredService<ISyntaxFactsService>(), out var simpleName)
? await FindDocumentsAsync(project, documents, findInGlobalSuppressions: false, cancellationToken, simpleName).ConfigureAwait(false)
: SpecializedCollections.EmptyEnumerable<Document>();
var documentsWithImplicitObjectCreations = symbol.MethodKind == MethodKind.Constructor
......@@ -142,7 +147,7 @@ protected override bool CanFind(IMethodSymbol symbol)
SemanticModel semanticModel,
CancellationToken cancellationToken)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
return TryGetNameWithoutAttributeSuffix(symbol.ContainingType.Name, syntaxFacts, out var simpleName)
? FindReferencesInDocumentUsingIdentifierAsync(symbol, simpleName, document, semanticModel, cancellationToken)
: SpecializedTasks.EmptyImmutableArray<FinderLocation>();
......
......@@ -43,7 +43,7 @@ protected override bool CanFind(IEventSymbol symbol)
FindReferencesSearchOptions options,
CancellationToken cancellationToken)
{
return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name);
return FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, symbol.Name);
}
protected override Task<ImmutableArray<FinderLocation>> FindReferencesInDocumentAsync(
......
......@@ -38,7 +38,7 @@ protected override bool CanFind(IFieldSymbol symbol)
FindReferencesSearchOptions options,
CancellationToken cancellationToken)
{
return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name);
return FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, symbol.Name);
}
protected override Task<ImmutableArray<FinderLocation>> FindReferencesInDocumentAsync(
......
......@@ -59,7 +59,7 @@ protected override bool CanFind(ITypeParameterSymbol symbol)
//
// Also, we only look for files that have the name of the owning type. This helps filter
// down the set considerably.
return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name,
return FindDocumentsAsync(project, documents, findInGlobalSuppressions: false, cancellationToken, symbol.Name,
GetMemberNameWithoutInterfaceName(symbol.DeclaringMethod.Name),
symbol.DeclaringMethod.ContainingType.Name);
}
......
......@@ -53,10 +53,10 @@ protected override bool CanFind(INamedTypeSymbol symbol)
FindReferencesSearchOptions options,
CancellationToken cancellationToken)
{
var documentsWithName = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false);
var documentsWithName = await FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, symbol.Name).ConfigureAwait(false);
var documentsWithType = await FindDocumentsAsync(project, documents, symbol.SpecialType.ToPredefinedType(), cancellationToken).ConfigureAwait(false);
var documentsWithAttribute = TryGetNameWithoutAttributeSuffix(symbol.Name, project.LanguageServices.GetService<ISyntaxFactsService>(), out var simpleName)
? await FindDocumentsAsync(project, documents, cancellationToken, simpleName).ConfigureAwait(false)
? await FindDocumentsAsync(project, documents, findInGlobalSuppressions: false, cancellationToken, simpleName).ConfigureAwait(false)
: ImmutableArray<Document>.Empty;
return documentsWithName.Concat(documentsWithType)
......@@ -107,7 +107,7 @@ protected override bool CanFind(INamedTypeSymbol symbol)
var symbolsMatch = GetStandardSymbolsMatchFunction(namedType, null, document.Project.Solution, cancellationToken);
return FindReferencesInDocumentUsingIdentifierAsync(
namedType.Name, document, semanticModel, symbolsMatch, cancellationToken);
namedType, namedType.Name, document, semanticModel, symbolsMatch, cancellationToken);
}
private static Task<ImmutableArray<FinderLocation>> FindPredefinedTypeReferencesAsync(
......@@ -126,6 +126,8 @@ protected override bool CanFind(INamedTypeSymbol symbol)
return FindReferencesInDocumentAsync(document, semanticModel, t =>
IsPotentialReference(predefinedType, syntaxFacts, t),
(t, m) => (matched: true, reason: CandidateReason.None),
docCommentId: null,
findInGlobalSuppressions: false,
cancellationToken);
}
......@@ -138,7 +140,8 @@ protected override bool CanFind(INamedTypeSymbol symbol)
var symbolsMatch = GetStandardSymbolsMatchFunction(namedType, null, document.Project.Solution, cancellationToken);
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
return TryGetNameWithoutAttributeSuffix(namedType.Name, syntaxFacts, out var simpleName)
? FindReferencesInDocumentUsingIdentifierAsync(simpleName, document, semanticModel, symbolsMatch, cancellationToken)
? FindReferencesInDocumentUsingIdentifierAsync(simpleName, document, semanticModel,
symbolsMatch, docCommentId: null, findInGlobalSuppressions: false, cancellationToken)
: SpecializedTasks.EmptyImmutableArray<FinderLocation>();
}
}
......
......@@ -24,7 +24,7 @@ protected override bool CanFind(INamespaceSymbol symbol)
FindReferencesSearchOptions options,
CancellationToken cancellationToken)
{
return FindDocumentsAsync(project, documents, cancellationToken, GetNamespaceIdentifierName(symbol));
return FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, GetNamespaceIdentifierName(symbol));
}
private static string GetNamespaceIdentifierName(INamespaceSymbol symbol)
......@@ -54,7 +54,13 @@ private static string GetNamespaceIdentifierName(INamespaceSymbol symbol)
cancellationToken);
var aliasReferences = await FindAliasReferencesAsync(nonAliasReferences, symbol, document, semanticModel, cancellationToken).ConfigureAwait(false);
return nonAliasReferences.Concat(aliasReferences);
var suppressionReferences = ShouldFindReferencesInGlobalSuppressions(symbol, out var docCommentId)
? await FindReferencesInDocumentInsideGlobalSuppressionsAsync(document, semanticModel,
syntaxFactsService, docCommentId, cancellationToken).ConfigureAwait(false)
: ImmutableArray<FinderLocation>.Empty;
return nonAliasReferences.Concat(aliasReferences).Concat(suppressionReferences);
}
}
}
......@@ -85,7 +85,7 @@ private static ImmutableArray<ISymbol> GetOtherPartsOfPartial(IMethodSymbol symb
// searches for these, then we should find usages of 'lock(goo)' or 'synclock(goo)'
// since they implicitly call those methods.
var ordinaryDocuments = await FindDocumentsAsync(project, documents, cancellationToken, methodSymbol.Name).ConfigureAwait(false);
var ordinaryDocuments = await FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, methodSymbol.Name).ConfigureAwait(false);
var forEachDocuments = IsForEachMethod(methodSymbol)
? await FindDocumentsWithForEachStatementsAsync(project, documents, cancellationToken).ConfigureAwait(false)
: ImmutableArray<Document>.Empty;
......
......@@ -31,7 +31,7 @@ protected override bool CanFind(IParameterSymbol symbol)
// elsewhere as "paramName:" or "paramName:=". We can narrow the search by
// filtering down to matches of that form. For now we just return any document
// that references something with this name.
return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name);
return FindDocumentsAsync(project, documents, findInGlobalSuppressions: false, cancellationToken, symbol.Name);
}
protected override Task<ImmutableArray<FinderLocation>> FindReferencesInDocumentAsync(
......@@ -45,7 +45,7 @@ protected override bool CanFind(IParameterSymbol symbol)
symbol, document.Project.Solution, cancellationToken);
return FindReferencesInDocumentUsingIdentifierAsync(
symbol.Name, document, semanticModel, symbolsMatch, cancellationToken);
symbol, symbol.Name, document, semanticModel, symbolsMatch, cancellationToken);
}
private static Func<SyntaxToken, SemanticModel, (bool matched, CandidateReason reason)> GetParameterSymbolsMatchFunction(
......
......@@ -46,7 +46,7 @@ protected override bool CanFind(IMethodSymbol symbol)
// This will find explicit calls to the method (which can happen when C# references
// a VB parameterized property).
var result = await FindDocumentsAsync(
project, documents, cancellationToken, symbol.Name).ConfigureAwait(false);
project, documents, findInGlobalSuppressions: true, cancellationToken, symbol.Name).ConfigureAwait(false);
if (symbol.AssociatedSymbol is IPropertySymbol property &&
options.AssociatePropertyReferencesWithSpecificAccessor)
......
......@@ -61,7 +61,7 @@ protected override bool CanFind(IPropertySymbol symbol)
FindReferencesSearchOptions options,
CancellationToken cancellationToken)
{
var ordinaryDocuments = await FindDocumentsAsync(project, documents, cancellationToken, symbol.Name).ConfigureAwait(false);
var ordinaryDocuments = await FindDocumentsAsync(project, documents, findInGlobalSuppressions: true, cancellationToken, symbol.Name).ConfigureAwait(false);
var forEachDocuments = IsForEachProperty(symbol)
? await FindDocumentsWithForEachStatementsAsync(project, documents, cancellationToken).ConfigureAwait(false)
......
......@@ -27,7 +27,7 @@ protected override bool CanFind(ITypeParameterSymbol symbol)
// parameter has a different name in different parts that we won't find it. However,
// this only happens in error situations. It is not legal in C# to use a different
// name for a type parameter in different parts.
return FindDocumentsAsync(project, documents, cancellationToken, symbol.Name, symbol.ContainingType.Name);
return FindDocumentsAsync(project, documents, findInGlobalSuppressions: false, cancellationToken, symbol.Name, symbol.ContainingType.Name);
}
protected override Task<ImmutableArray<FinderLocation>> FindReferencesInDocumentAsync(
......
......@@ -54,9 +54,16 @@ namespace Microsoft.CodeAnalysis.FindSymbols
/// </summary>
internal ImmutableDictionary<string, string> AdditionalProperties { get; }
/// <summary>
/// If this reference location is within a string literal, then this property
/// indicates the location of the containing string literal token.
/// Otherwise, <see cref="Location.None"/>.
/// </summary>
internal Location ContainingStringLocation { get; }
public CandidateReason CandidateReason { get; }
internal ReferenceLocation(Document document, IAliasSymbol alias, Location location, bool isImplicit, SymbolUsageInfo symbolUsageInfo, ImmutableDictionary<string, string> additionalProperties, CandidateReason candidateReason)
private ReferenceLocation(Document document, IAliasSymbol alias, Location location, bool isImplicit, SymbolUsageInfo symbolUsageInfo, ImmutableDictionary<string, string> additionalProperties, CandidateReason candidateReason, Location containingStringLocation)
: this()
{
this.Document = document;
......@@ -66,6 +73,26 @@ internal ReferenceLocation(Document document, IAliasSymbol alias, Location locat
this.SymbolUsageInfo = symbolUsageInfo;
this.AdditionalProperties = additionalProperties ?? ImmutableDictionary<string, string>.Empty;
this.CandidateReason = candidateReason;
this.ContainingStringLocation = containingStringLocation;
}
/// <summary>
/// Creates a reference location with the given properties.
/// </summary>
internal ReferenceLocation(Document document, IAliasSymbol alias, Location location, bool isImplicit, SymbolUsageInfo symbolUsageInfo, ImmutableDictionary<string, string> additionalProperties, CandidateReason candidateReason)
: this(document, alias, location, isImplicit, symbolUsageInfo, additionalProperties, candidateReason, containingStringLocation: Location.None)
{
}
/// <summary>
/// Creates a reference location within a string literal.
/// For example, location inside the target string of a global SuppressMessageAttribute.
/// </summary>
internal ReferenceLocation(Document document, Location location, Location containingStringLocation)
: this(document, alias: null, location, isImplicit: false,
SymbolUsageInfo.None, additionalProperties: ImmutableDictionary<string, string>.Empty,
CandidateReason.None, containingStringLocation)
{
}
/// <summary>
......
......@@ -49,7 +49,7 @@ private static bool IsAccessible(ISymbol symbol)
internal static bool OriginalSymbolsMatch(
ISymbol searchSymbol,
ISymbol symbolToMatch,
ISymbol? symbolToMatch,
Solution solution,
Compilation? searchSymbolCompilation,
Compilation? symbolToMatchCompilation)
......
......@@ -30,7 +30,8 @@ internal partial class SyntaxTreeIndex
bool containsDeconstruction,
bool containsAwait,
bool containsTupleExpressionOrTupleType,
bool containsImplicitObjectCreation)
bool containsImplicitObjectCreation,
bool containsGlobalAttributes)
: this(predefinedTypes, predefinedOperators,
ConvertToContainingNodeFlag(
containsForEachStatement,
......@@ -44,7 +45,8 @@ internal partial class SyntaxTreeIndex
containsDeconstruction,
containsAwait,
containsTupleExpressionOrTupleType,
containsImplicitObjectCreation))
containsImplicitObjectCreation,
containsGlobalAttributes))
{
}
......@@ -67,7 +69,8 @@ private ContextInfo(int predefinedTypes, int predefinedOperators, ContainingNode
bool containsDeconstruction,
bool containsAwait,
bool containsTupleExpressionOrTupleType,
bool containsImplicitObjectCreation)
bool containsImplicitObjectCreation,
bool containsGlobalAttributes)
{
var containingNodes = ContainingNodes.None;
......@@ -83,6 +86,7 @@ private ContextInfo(int predefinedTypes, int predefinedOperators, ContainingNode
containingNodes |= containsAwait ? ContainingNodes.ContainsAwait : 0;
containingNodes |= containsTupleExpressionOrTupleType ? ContainingNodes.ContainsTupleExpressionOrTupleType : 0;
containingNodes |= containsImplicitObjectCreation ? ContainingNodes.ContainsImplicitObjectCreation : 0;
containingNodes |= containsGlobalAttributes ? ContainingNodes.ContainsGlobalAttributes : 0;
return containingNodes;
}
......@@ -129,6 +133,9 @@ public bool ContainsIndexerMemberCref
public bool ContainsTupleExpressionOrTupleType
=> (_containingNodes & ContainingNodes.ContainsTupleExpressionOrTupleType) == ContainingNodes.ContainsTupleExpressionOrTupleType;
public bool ContainsGlobalAttributes
=> (_containingNodes & ContainingNodes.ContainsGlobalAttributes) == ContainingNodes.ContainsGlobalAttributes;
public void WriteTo(ObjectWriter writer)
{
writer.WriteInt32(_predefinedTypes);
......@@ -169,6 +176,7 @@ private enum ContainingNodes
ContainsAwait = 1 << 9,
ContainsTupleExpressionOrTupleType = 1 << 10,
ContainsImplicitObjectCreation = 1 << 11,
ContainsGlobalAttributes = 1 << 12,
}
}
}
......
......@@ -89,6 +89,7 @@ internal sealed partial class SyntaxTreeIndex
var containsAwait = false;
var containsTupleExpressionOrTupleType = false;
var containsImplicitObjectCreation = false;
var containsGlobalAttributes = false;
var predefinedTypes = (int)PredefinedType.None;
var predefinedOperators = (int)PredefinedOperator.None;
......@@ -118,6 +119,7 @@ internal sealed partial class SyntaxTreeIndex
containsTupleExpressionOrTupleType = containsTupleExpressionOrTupleType ||
syntaxFacts.IsTupleExpression(node) || syntaxFacts.IsTupleType(node);
containsImplicitObjectCreation = containsImplicitObjectCreation || syntaxFacts.IsImplicitObjectCreationExpression(node);
containsGlobalAttributes = containsGlobalAttributes || syntaxFacts.IsGlobalAttribute(node);
if (syntaxFacts.IsUsingAliasDirective(node) && infoFactory.TryGetAliasesFromUsingDirective(node, out var aliases))
{
......@@ -266,7 +268,8 @@ internal sealed partial class SyntaxTreeIndex
containsDeconstruction,
containsAwait,
containsTupleExpressionOrTupleType,
containsImplicitObjectCreation),
containsImplicitObjectCreation,
containsGlobalAttributes),
new DeclarationInfo(
declaredSymbolInfos.ToImmutable()),
new ExtensionMethodInfo(
......
......@@ -40,5 +40,6 @@ public ImmutableArray<int> ComplexExtensionMethodInfo
public bool ContainsElementAccessExpression => _contextInfo.ContainsElementAccessExpression;
public bool ContainsIndexerMemberCref => _contextInfo.ContainsIndexerMemberCref;
public bool ContainsTupleExpressionOrTupleType => _contextInfo.ContainsTupleExpressionOrTupleType;
public bool ContainsGlobalAttributes => _contextInfo.ContainsGlobalAttributes;
}
}
......@@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols
internal sealed partial class SyntaxTreeIndex : IObjectWritable
{
private const string PersistenceName = "<SyntaxTreeIndex>";
private static readonly Checksum SerializationFormatChecksum = Checksum.Create("18");
private static readonly Checksum SerializationFormatChecksum = Checksum.Create("19");
public readonly Checksum Checksum;
......
......@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
......@@ -37,7 +39,7 @@ private class Session
private readonly string _originalText;
private readonly string _replacementText;
private readonly RenameOptionSet _optionSet;
private readonly ImmutableHashSet<ISymbol> _nonConflictSymbols;
private readonly ImmutableHashSet<ISymbol>? _nonConflictSymbols;
private readonly CancellationToken _cancellationToken;
private readonly RenameAnnotation _renamedSymbolDeclarationAnnotation;
......@@ -49,14 +51,14 @@ private class Session
private ISet<ConflictLocationInfo> _conflictLocations;
private bool _replacementTextValid;
private List<ProjectId> _topologicallySortedProjects;
private List<ProjectId>? _topologicallySortedProjects;
private bool _documentOfRenameSymbolHasBeenRenamed;
public Session(
RenameLocations renameLocationSet,
Location renameSymbolDeclarationLocation,
string replacementText,
ImmutableHashSet<ISymbol> nonConflictSymbols,
ImmutableHashSet<ISymbol>? nonConflictSymbols,
CancellationToken cancellationToken)
{
_renameLocationSet = renameLocationSet;
......@@ -75,7 +77,7 @@ private class Session
// only process documents which possibly contain the identifiers.
_documentsIdsToBeCheckedForConflict = new HashSet<DocumentId>();
_documentIdOfRenameSymbolDeclaration = renameLocationSet.Solution.GetDocument(renameSymbolDeclarationLocation.SourceTree).Id;
_documentIdOfRenameSymbolDeclaration = renameLocationSet.Solution.GetRequiredDocument(renameSymbolDeclarationLocation.SourceTree!).Id;
_renameAnnotations = new AnnotationTable<RenameAnnotation>(RenameAnnotation.Kind);
}
......@@ -107,6 +109,7 @@ public async Task<MutableConflictResolution> ResolveConflictsAsync()
var baseSolution = _renameLocationSet.Solution;
// Process rename one project at a time to improve caching and reduce syntax tree serialization.
RoslynDebug.Assert(_topologicallySortedProjects != null);
var documentsGroupedByTopologicallySortedProjectId = _documentsIdsToBeCheckedForConflict
.GroupBy(d => d.ProjectId)
.OrderBy(g => _topologicallySortedProjects.IndexOf(g.Key));
......@@ -216,7 +219,7 @@ public async Task<MutableConflictResolution> ResolveConflictsAsync()
renamedSymbolInNewSolution,
_renameLocationSet.Symbol,
_renameLocationSet.ImplicitLocations,
await conflictResolution.CurrentSolution.GetDocument(_documentIdOfRenameSymbolDeclaration).GetSemanticModelAsync(_cancellationToken).ConfigureAwait(false),
await conflictResolution.CurrentSolution.GetRequiredDocument(_documentIdOfRenameSymbolDeclaration).GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false),
_renameSymbolDeclarationLocation,
renamedSpansTracker.GetAdjustedPosition(_renameSymbolDeclarationLocation.SourceSpan.Start, _documentIdOfRenameSymbolDeclaration),
conflictResolution,
......@@ -267,7 +270,7 @@ private async Task DebugVerifyNoErrorsAsync(MutableConflictResolution conflictRe
foreach (var documentId in documents)
{
// remember if there were issues in the document prior to renaming it.
var originalDoc = conflictResolution.OldSolution.GetDocument(documentId);
var originalDoc = conflictResolution.OldSolution.GetRequiredDocument(documentId);
documentIdErrorStateLookup.Add(documentId, await originalDoc.HasAnyErrorsAsync(_cancellationToken).ConfigureAwait(false));
}
......@@ -287,7 +290,7 @@ private async Task DebugVerifyNoErrorsAsync(MutableConflictResolution conflictRe
// errors.
if (!documentIdErrorStateLookup[documentId] && _nonConflictSymbols == null)
{
await conflictResolution.CurrentSolution.GetDocument(documentId).VerifyNoErrorsAsync("Rename introduced errors in error-free code", _cancellationToken, ignoreErrorCodes).ConfigureAwait(false);
await conflictResolution.CurrentSolution.GetRequiredDocument(documentId).VerifyNoErrorsAsync("Rename introduced errors in error-free code", _cancellationToken, ignoreErrorCodes).ConfigureAwait(false);
}
}
}
......@@ -317,8 +320,8 @@ private async Task DebugVerifyNoErrorsAsync(MutableConflictResolution conflictRe
{
foreach (var documentId in documentIdsForConflictResolution)
{
var newDocument = conflictResolution.CurrentSolution.GetDocument(documentId);
var syntaxRoot = await newDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
var newDocument = conflictResolution.CurrentSolution.GetRequiredDocument(documentId);
var syntaxRoot = await newDocument.GetRequiredSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(syntaxRoot);
foreach (var (syntax, annotation) in nodesOrTokensWithConflictCheckAnnotations)
......@@ -338,18 +341,18 @@ private async Task DebugVerifyNoErrorsAsync(MutableConflictResolution conflictRe
// If we were giving any non-conflict-symbols then ensure that we know what those symbols are in
// the current project post after our edits so far.
var currentProject = conflictResolution.CurrentSolution.GetProject(projectId);
var currentProject = conflictResolution.CurrentSolution.GetRequiredProject(projectId);
var nonConflictSymbols = await GetNonConflictSymbolsAsync(currentProject).ConfigureAwait(false);
foreach (var documentId in documentIdsForConflictResolution)
{
var newDocument = conflictResolution.CurrentSolution.GetDocument(documentId);
var syntaxRoot = await newDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
var baseDocument = conflictResolution.OldSolution.GetDocument(documentId);
var baseSyntaxTree = await baseDocument.GetSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false);
var baseRoot = await baseDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
SemanticModel newDocumentSemanticModel = null;
var syntaxFactsService = newDocument.Project.LanguageServices.GetService<ISyntaxFactsService>();
var newDocument = conflictResolution.CurrentSolution.GetRequiredDocument(documentId);
var syntaxRoot = await newDocument.GetRequiredSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
var baseDocument = conflictResolution.OldSolution.GetRequiredDocument(documentId);
var baseSyntaxTree = await baseDocument.GetRequiredSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false);
var baseRoot = await baseDocument.GetRequiredSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
SemanticModel? newDocumentSemanticModel = null;
var syntaxFactsService = newDocument.Project.LanguageServices.GetRequiredService<ISyntaxFactsService>();
// Get all tokens that need conflict check
var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(syntaxRoot);
......@@ -363,14 +366,14 @@ private async Task DebugVerifyNoErrorsAsync(MutableConflictResolution conflictRe
{
var tokenOrNode = syntax;
var conflictAnnotation = annotation;
reverseMappedLocations[tokenOrNode.GetLocation()] = baseSyntaxTree.GetLocation(conflictAnnotation.OriginalSpan);
reverseMappedLocations[tokenOrNode.GetLocation()!] = baseSyntaxTree.GetLocation(conflictAnnotation.OriginalSpan);
var originalLocation = conflictAnnotation.OriginalSpan;
ImmutableArray<ISymbol> newReferencedSymbols = default;
var hasConflict = _renameAnnotations.HasAnnotation(tokenOrNode, RenameInvalidIdentifierAnnotation.Instance);
if (!hasConflict)
{
newDocumentSemanticModel ??= await newDocument.GetSemanticModelAsync(_cancellationToken).ConfigureAwait(false);
newDocumentSemanticModel ??= await newDocument.GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false);
newReferencedSymbols = GetSymbolsInNewSolution(newDocument, newDocumentSemanticModel, conflictAnnotation, tokenOrNode);
// The semantic correctness, after rename, for each token of interest in the
......@@ -435,17 +438,17 @@ private async Task DebugVerifyNoErrorsAsync(MutableConflictResolution conflictRe
// the annotated spans in these documents to reverseMappedLocations.
foreach (var unprocessedDocumentIdWithPotentialDeclarationConflicts in allDocumentIdsInProject.Where(d => !documentIdsForConflictResolution.Contains(d)))
{
var newDocument = conflictResolution.CurrentSolution.GetDocument(unprocessedDocumentIdWithPotentialDeclarationConflicts);
var syntaxRoot = await newDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
var baseDocument = conflictResolution.OldSolution.GetDocument(unprocessedDocumentIdWithPotentialDeclarationConflicts);
var baseSyntaxTree = await baseDocument.GetSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false);
var newDocument = conflictResolution.CurrentSolution.GetRequiredDocument(unprocessedDocumentIdWithPotentialDeclarationConflicts);
var syntaxRoot = await newDocument.GetRequiredSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
var baseDocument = conflictResolution.OldSolution.GetRequiredDocument(unprocessedDocumentIdWithPotentialDeclarationConflicts);
var baseSyntaxTree = await baseDocument.GetRequiredSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false);
var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(syntaxRoot);
foreach (var (syntax, annotation) in nodesOrTokensWithConflictCheckAnnotations)
{
var tokenOrNode = syntax;
var conflictAnnotation = annotation;
reverseMappedLocations[tokenOrNode.GetLocation()] = baseSyntaxTree.GetLocation(conflictAnnotation.OriginalSpan);
reverseMappedLocations[tokenOrNode.GetLocation()!] = baseSyntaxTree.GetLocation(conflictAnnotation.OriginalSpan);
}
}
......@@ -463,7 +466,7 @@ private async Task DebugVerifyNoErrorsAsync(MutableConflictResolution conflictRe
}
}
private async Task<ImmutableHashSet<ISymbol>> GetNonConflictSymbolsAsync(Project currentProject)
private async Task<ImmutableHashSet<ISymbol>?> GetNonConflictSymbolsAsync(Project currentProject)
{
if (_nonConflictSymbols == null)
return null;
......@@ -474,10 +477,12 @@ private async Task<ImmutableHashSet<ISymbol>> GetNonConflictSymbolsAsync(Project
}
private bool IsConflictFreeChange(
ImmutableArray<ISymbol> symbols, ImmutableHashSet<ISymbol> nonConflictSymbols)
ImmutableArray<ISymbol> symbols, ImmutableHashSet<ISymbol>? nonConflictSymbols)
{
if (_nonConflictSymbols != null)
{
RoslynDebug.Assert(nonConflictSymbols != null);
foreach (var symbol in symbols)
{
// Reference not points at a symbol in the conflict-free list. This is a conflict-free change.
......@@ -649,7 +654,7 @@ private ImmutableArray<ISymbol> GetSymbolsInNewSolution(Document newDocument, Se
{
if (tokenOrNode.IsNode)
{
var invocationReferencedSymbols = SymbolsForEnclosingInvocationExpressionWorker((SyntaxNode)tokenOrNode, newDocumentSemanticModel, _cancellationToken);
var invocationReferencedSymbols = SymbolsForEnclosingInvocationExpressionWorker((SyntaxNode)tokenOrNode!, newDocumentSemanticModel, _cancellationToken);
if (!invocationReferencedSymbols.IsDefault)
newReferencedSymbols = invocationReferencedSymbols;
}
......@@ -696,7 +701,7 @@ private async Task FindDocumentsAndPossibleNameConflictsAsync()
var dependencyGraph = solution.GetProjectDependencyGraph();
_topologicallySortedProjects = dependencyGraph.GetTopologicallySortedProjects(_cancellationToken).ToList();
var allRenamedDocuments = _renameLocationSet.Locations.Select(loc => loc.Location.SourceTree).Distinct().Select(solution.GetDocument);
var allRenamedDocuments = _renameLocationSet.Locations.Select(loc => loc.Location.SourceTree!).Distinct().Select(solution.GetRequiredDocument);
_documentsIdsToBeCheckedForConflict.AddRange(allRenamedDocuments.Select(d => d.Id));
var documentsFromAffectedProjects = RenameUtilities.GetDocumentsAffectedByRename(symbol, solution, _renameLocationSet.Locations);
......@@ -769,8 +774,8 @@ private async Task AddDocumentsWithPotentialConflictsAsync(IEnumerable<Document>
{
_cancellationToken.ThrowIfCancellationRequested();
var document = originalSolution.GetDocument(documentId);
var semanticModel = await document.GetSemanticModelAsync(_cancellationToken).ConfigureAwait(false);
var document = originalSolution.GetRequiredDocument(documentId);
var semanticModel = await document.GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false);
var originalSyntaxRoot = await semanticModel.SyntaxTree.GetRootAsync(_cancellationToken).ConfigureAwait(false);
// Get all rename locations for the current document.
......@@ -782,8 +787,9 @@ private async Task AddDocumentsWithPotentialConflictsAsync(IEnumerable<Document>
var stringAndCommentTextSpansInSingleSourceTree = renameLocations
.Where(l => l.DocumentId == documentId && l.IsRenameInStringOrComment)
.GroupBy(l => l.ContainingLocationForStringOrComment)
.Select(g => g.Key)
.ToSet();
.ToImmutableDictionary(
g => g.Key,
g => GetSubSpansToRenameInStringAndCommentTextSpans(g.Key, g));
var conflictLocationSpans = _conflictLocations
.Where(t => t.DocumentId == documentId)
......@@ -810,7 +816,7 @@ private async Task AddDocumentsWithPotentialConflictsAsync(IEnumerable<Document>
_renameAnnotations,
_cancellationToken);
var renameRewriterLanguageService = document.GetLanguageService<IRenameRewriterLanguageService>();
var renameRewriterLanguageService = document.GetRequiredLanguageService<IRenameRewriterLanguageService>();
var newRoot = renameRewriterLanguageService.AnnotateAndRename(parameters);
if (newRoot == originalSyntaxRoot)
......@@ -851,6 +857,40 @@ private static bool ShouldIncludeLocation(ISet<RenameLocation> renameLocations,
return RenameLocation.ShouldRename(location);
}
/// <summary>
/// We try to compute the sub-spans to rename within the given <paramref name="containingLocationForStringOrComment"/>.
/// If we are renaming within a string, the locations to rename are always within this containing string location
/// and we can identify these sub-spans.
/// However, if we are renaming within a comment, the rename locations can be anywhere in trivia,
/// so we return null and the rename rewriter will perform a complete regex match within comment trivia
/// and rename all matches instead of specific matches.
/// </summary>
private static ImmutableSortedSet<TextSpan>? GetSubSpansToRenameInStringAndCommentTextSpans(
TextSpan containingLocationForStringOrComment,
IEnumerable<RenameLocation> locationsToRename)
{
var builder = ImmutableSortedSet.CreateBuilder<TextSpan>();
foreach (var renameLocation in locationsToRename)
{
if (!containingLocationForStringOrComment.Contains(renameLocation.Location.SourceSpan))
{
// We found a location outside the 'containingLocationForStringOrComment',
// which is likely in trivia.
// Bail out from computing specific sub-spans and let the rename rewriter
// do a full regex match and replace.
return null;
}
// Compute the sub-span within 'containingLocationForStringOrComment' that needs to be renamed.
var offset = renameLocation.Location.SourceSpan.Start - containingLocationForStringOrComment.Start;
var length = renameLocation.Location.SourceSpan.Length;
var subSpan = new TextSpan(offset, length);
builder.Add(subSpan);
}
return builder.ToImmutable();
}
}
}
}
......@@ -118,7 +118,7 @@ private static ImmutableArray<ISymbol> SymbolsForEnclosingInvocationExpressionWo
: ImmutableArray.Create(symbolInfo.Symbol);
}
private static SyntaxNode GetExpansionTargetForLocationPerLanguage(SyntaxToken tokenOrNode, Document document)
private static SyntaxNode? GetExpansionTargetForLocationPerLanguage(SyntaxToken tokenOrNode, Document document)
{
var renameRewriterService = document.GetRequiredLanguageService<IRenameRewriterLanguageService>();
var complexifiedTarget = renameRewriterService.GetExpansionTargetForLocation(tokenOrNode);
......
......@@ -119,7 +119,7 @@ internal interface IRenameRewriterLanguageService : ILanguageService
/// </summary>
/// <param name="token">The token to get the complexification target for.</param>
/// <returns></returns>
SyntaxNode GetExpansionTargetForLocation(SyntaxToken token);
SyntaxNode? GetExpansionTargetForLocation(SyntaxToken token);
}
internal abstract class AbstractRenameRewriterLanguageService : IRenameRewriterLanguageService
......@@ -128,7 +128,7 @@ internal abstract class AbstractRenameRewriterLanguageService : IRenameRewriterL
public abstract Task<ImmutableArray<Location>> ComputeDeclarationConflictsAsync(string replacementText, ISymbol renamedSymbol, ISymbol renameSymbol, IEnumerable<ISymbol> referencedSymbols, Solution baseSolution, Solution newSolution, IDictionary<Location, Location> reverseMappedLocations, CancellationToken cancellationToken);
public abstract Task<ImmutableArray<Location>> ComputeImplicitReferenceConflictsAsync(ISymbol renameSymbol, ISymbol renamedSymbol, IEnumerable<ReferenceLocation> implicitReferenceLocations, CancellationToken cancellationToken);
public abstract ImmutableArray<Location> ComputePossibleImplicitUsageConflicts(ISymbol renamedSymbol, SemanticModel semanticModel, Location originalDeclarationLocation, int newDeclarationLocationStartingPosition, CancellationToken cancellationToken);
public abstract SyntaxNode GetExpansionTargetForLocation(SyntaxToken token);
public abstract SyntaxNode? GetExpansionTargetForLocation(SyntaxToken token);
public abstract bool IsIdentifierValid(string replacementText, ISyntaxFactsService syntaxFactsService);
public abstract bool LocalVariableConflict(SyntaxToken token, IEnumerable<ISymbol> newReferencedSymbols);
public abstract void TryAddPossibleNameConflicts(ISymbol symbol, string newName, ICollection<string> possibleNameConflicts);
......
......@@ -18,6 +18,7 @@
using Roslyn.Utilities;
using System.Text.RegularExpressions;
using System.Collections.Immutable;
using System.Text;
namespace Microsoft.CodeAnalysis.Rename
{
......@@ -405,6 +406,14 @@ internal static async Task<IEnumerable<RenameLocation>> GetRenamableReferenceLoc
results.Add(new RenameLocation(aliasLocation, solution.GetRequiredDocument(aliasLocation.SourceTree).Id));
}
}
else if (location.ContainingStringLocation != Location.None)
{
// Location within a string
results.Add(new RenameLocation(
location.Location,
location.Document.Id,
containingLocationForStringOrComment: location.ContainingStringLocation.SourceSpan));
}
else
{
// The simple case, so just the single location and we're done
......@@ -537,10 +546,51 @@ private static Regex GetRegexForMatch(string matchText)
return new Regex(matchString, RegexOptions.CultureInvariant);
}
internal static string ReplaceMatchingSubStrings(string replaceInsideString, string matchText, string replacementText)
internal static string ReplaceMatchingSubStrings(
string replaceInsideString,
string matchText,
string replacementText,
ImmutableSortedSet<TextSpan>? subSpansToReplace = null)
{
var regex = GetRegexForMatch(matchText);
return regex.Replace(replaceInsideString, replacementText);
if (subSpansToReplace == null)
{
// We do not have already computed sub-spans to replace inside the string.
// Get regex for matches within the string and replace all matches with replacementText.
var regex = GetRegexForMatch(matchText);
return regex.Replace(replaceInsideString, replacementText);
}
else
{
// We are provided specific matches to replace inside the string.
// Process the input string from start to end, replacing matchText with replacementText
// at the provided sub-spans within the string for these matches.
var stringBuilder = new StringBuilder();
var startOffset = 0;
foreach (var subSpan in subSpansToReplace)
{
Debug.Assert(subSpan.Start <= replaceInsideString.Length);
Debug.Assert(subSpan.End <= replaceInsideString.Length);
// Verify that provided sub-span has a match with matchText.
if (replaceInsideString.Substring(subSpan.Start, subSpan.Length) != matchText)
continue;
// Append the sub-string from last match till the next match
var offset = subSpan.Start - startOffset;
stringBuilder.Append(replaceInsideString.Substring(startOffset, offset));
// Append the replacementText
stringBuilder.Append(replacementText);
// Update startOffset to process the next match.
startOffset += offset + subSpan.Length;
}
// Append the remaining of the sub-string within replaceInsideString after the last match.
stringBuilder.Append(replaceInsideString.Substring(startOffset));
return stringBuilder.ToString();
}
}
}
}
......
......@@ -2,7 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using Microsoft.CodeAnalysis.Rename.ConflictEngine;
using Microsoft.CodeAnalysis.Text;
......@@ -25,7 +28,7 @@ internal class RenameRewriterParameters
internal readonly ISymbol RenameSymbol;
internal readonly string ReplacementText;
internal readonly bool ReplacementTextValid;
internal readonly ISet<TextSpan> StringAndCommentTextSpans;
internal readonly ImmutableDictionary<TextSpan, ImmutableSortedSet<TextSpan>?> StringAndCommentTextSpans;
internal readonly SyntaxNode SyntaxRoot;
internal readonly Document Document;
internal readonly SemanticModel SemanticModel;
......@@ -40,7 +43,7 @@ internal class RenameRewriterParameters
string originalText,
ICollection<string> possibleNameConflicts,
Dictionary<TextSpan, RenameLocation> renameLocations,
ISet<TextSpan> stringAndCommentTextSpans,
ImmutableDictionary<TextSpan, ImmutableSortedSet<TextSpan>?> stringAndCommentTextSpans,
ISet<TextSpan> conflictLocationSpans,
Solution originalSolution,
ISymbol renameSymbol,
......
......@@ -5,6 +5,7 @@
#nullable enable
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Rename;
......@@ -98,7 +99,8 @@ internal partial class CodeAnalysisService : IRemoteRenamer
}, cancellationToken);
}
private static async Task<ImmutableHashSet<ISymbol>?> GetNonConflictSymbolsAsync(Solution solution, SerializableSymbolAndProjectId[] nonConflictSymbolIds, CancellationToken cancellationToken)
[return: NotNullIfNotNull("nonConflictSymbolIds")]
private static async Task<ImmutableHashSet<ISymbol>?> GetNonConflictSymbolsAsync(Solution solution, SerializableSymbolAndProjectId[]? nonConflictSymbolIds, CancellationToken cancellationToken)
{
if (nonConflictSymbolIds == null)
return null;
......
......@@ -1202,6 +1202,9 @@ public TextSpan GetInactiveRegionSpanAroundPosition(SyntaxTree syntaxTree, int p
public string GetNameForArgument(SyntaxNode argument)
=> (argument as ArgumentSyntax)?.NameColon?.Name.Identifier.ValueText ?? string.Empty;
public string GetNameForAttributeArgument(SyntaxNode argument)
=> (argument as AttributeArgumentSyntax)?.NameEquals?.Name.Identifier.ValueText ?? string.Empty;
public bool IsLeftSideOfDot(SyntaxNode node)
=> (node as ExpressionSyntax).IsLeftSideOfDot();
......@@ -1293,10 +1296,16 @@ public bool IsUsingOrExternOrImport(SyntaxNode node)
node.IsKind(SyntaxKind.ExternAliasDirective);
}
public bool IsGlobalAttribute(SyntaxNode node)
public bool IsGlobalAssemblyAttribute(SyntaxNode node)
=> IsGlobalAttribute(node, SyntaxKind.AssemblyKeyword);
public bool IsGlobalModuleAttribute(SyntaxNode node)
=> IsGlobalAttribute(node, SyntaxKind.ModuleKeyword);
private static bool IsGlobalAttribute(SyntaxNode node, SyntaxKind attributeTarget)
=> node.IsKind(SyntaxKind.Attribute) &&
node.Parent.IsKind(SyntaxKind.AttributeList, out AttributeListSyntax attributeList) &&
attributeList.Target?.Identifier.Kind() == SyntaxKind.AssemblyKeyword;
attributeList.Target?.Identifier.Kind() == attributeTarget;
private static bool IsMemberDeclaration(SyntaxNode node)
{
......
......@@ -92,7 +92,8 @@ internal partial interface ISyntaxFacts
bool IsTypeNamedDynamic(SyntaxToken token, SyntaxNode parent);
bool IsUsingOrExternOrImport(SyntaxNode node);
bool IsUsingAliasDirective(SyntaxNode node);
bool IsGlobalAttribute(SyntaxNode node);
bool IsGlobalAssemblyAttribute(SyntaxNode node);
bool IsGlobalModuleAttribute(SyntaxNode node);
bool IsDeclaration(SyntaxNode node);
bool IsTypeDeclaration(SyntaxNode node);
......@@ -398,6 +399,12 @@ internal partial interface ISyntaxFacts
/// </summary>
string GetNameForArgument(SyntaxNode argument);
/// <summary>
/// Given a <see cref="SyntaxNode"/>, that represents an attribute argument return the string representation of
/// that arguments name.
/// </summary>
string GetNameForAttributeArgument(SyntaxNode argument);
bool IsNameOfSubpattern(SyntaxNode node);
bool IsPropertyPatternClause(SyntaxNode node);
......
......@@ -398,19 +398,22 @@ public static bool IsUsingStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(
#region members/declarations
public static bool IsAttribute(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode node)
public static bool IsAttribute(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
=> node?.RawKind == syntaxFacts.SyntaxKinds.Attribute;
public static bool IsParameter(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode node)
public static bool IsGlobalAttribute(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
=> syntaxFacts.IsGlobalAssemblyAttribute(node) || syntaxFacts.IsGlobalModuleAttribute(node);
public static bool IsParameter(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
=> node?.RawKind == syntaxFacts.SyntaxKinds.Parameter;
public static bool IsTypeConstraint(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode node)
public static bool IsTypeConstraint(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
=> node?.RawKind == syntaxFacts.SyntaxKinds.TypeConstraint;
public static bool IsVariableDeclarator(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode node)
public static bool IsVariableDeclarator(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
=> node?.RawKind == syntaxFacts.SyntaxKinds.VariableDeclarator;
public static bool IsTypeArgumentList(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode node)
public static bool IsTypeArgumentList(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
=> node?.RawKind == syntaxFacts.SyntaxKinds.TypeArgumentList;
#endregion
......
......@@ -1228,6 +1228,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices
Return String.Empty
End Function
Public Function GetNameForAttributeArgument(argument As SyntaxNode) As String Implements ISyntaxFacts.GetNameForAttributeArgument
' All argument types are ArgumentSyntax in VB.
Return GetNameForArgument(argument)
End Function
Public Function IsLeftSideOfDot(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsLeftSideOfDot
Return TryCast(node, ExpressionSyntax).IsLeftSideOfDot()
End Function
......@@ -1314,11 +1319,19 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices
Return node.IsKind(SyntaxKind.ImportsStatement)
End Function
Public Function IsGlobalAttribute(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsGlobalAttribute
Public Function IsGlobalAssemblyAttribute(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsGlobalAssemblyAttribute
Return IsGlobalAttribute(node, SyntaxKind.AssemblyKeyword)
End Function
Public Function IsModuleAssemblyAttribute(node As SyntaxNode) As Boolean Implements ISyntaxFacts.IsGlobalModuleAttribute
Return IsGlobalAttribute(node, SyntaxKind.ModuleKeyword)
End Function
Private Shared Function IsGlobalAttribute(node As SyntaxNode, attributeTarget As SyntaxKind) As Boolean
If node.IsKind(SyntaxKind.Attribute) Then
Dim attributeNode = CType(node, AttributeSyntax)
If attributeNode.Target IsNot Nothing Then
Return attributeNode.Target.AttributeModifier.IsKind(SyntaxKind.AssemblyKeyword)
Return attributeNode.Target.AttributeModifier.IsKind(attributeTarget)
End If
End If
......
......@@ -53,9 +53,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename
Private ReadOnly _renameSpansTracker As RenamedSpansTracker
Private ReadOnly _isVerbatim As Boolean
Private ReadOnly _replacementTextValid As Boolean
Private ReadOnly _isRenamingInStrings As Boolean
Private ReadOnly _isRenamingInComments As Boolean
Private ReadOnly _stringAndCommentTextSpans As ISet(Of TextSpan)
Private ReadOnly _simplificationService As ISimplificationService
Private ReadOnly _annotatedIdentifierTokens As New HashSet(Of SyntaxToken)
Private ReadOnly _invocationExpressionsNeedingConflictChecks As New HashSet(Of InvocationExpressionSyntax)
......@@ -63,6 +60,26 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename
Private ReadOnly _semanticFactsService As ISemanticFactsService
Private ReadOnly _renameAnnotations As AnnotationTable(Of RenameAnnotation)
''' <summary>
''' Flag indicating if we should perform a rename inside string literals.
''' </summary>
Private ReadOnly _isRenamingInStrings As Boolean
''' <summary>
''' Flag indicating if we should perform a rename inside comment trivia.
''' </summary>
Private ReadOnly _isRenamingInComments As Boolean
''' <summary>
''' A map from spans of tokens needing rename within strings or comments to an optional
''' set of specific sub-spans within the token span that
''' have <see cref="_originalText"/> matches and should be renamed.
''' If this sorted set is Nothing, it indicates that sub-spans to rename within the token span
''' are not available, and a regex match should be performed to rename
''' all <see cref="_originalText"/> matches within the span.
''' </summary>
Private ReadOnly _stringAndCommentTextSpans As ImmutableDictionary(Of TextSpan, ImmutableSortedSet(Of TextSpan))
Private ReadOnly Property AnnotateForComplexification As Boolean
Get
Return Me._skipRenameForComplexification > 0 AndAlso Not Me._isProcessingComplexifiedSpans
......@@ -105,9 +122,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename
_stringAndCommentTextSpans = parameters.StringAndCommentTextSpans
_aliasSymbol = TryCast(Me._renamedSymbol, IAliasSymbol)
_renamableDeclarationLocation = Me._renamedSymbol.Locations.Where(Function(loc) loc.IsInSource AndAlso loc.SourceTree Is _semanticModel.SyntaxTree).FirstOrDefault()
_simplificationService = parameters.Document.Project.LanguageServices.GetService(Of ISimplificationService)()
_syntaxFactsService = parameters.Document.Project.LanguageServices.GetService(Of ISyntaxFactsService)()
_semanticFactsService = parameters.Document.Project.LanguageServices.GetService(Of ISemanticFactsService)()
_simplificationService = parameters.Document.Project.LanguageServices.GetRequiredService(Of ISimplificationService)()
_syntaxFactsService = parameters.Document.Project.LanguageServices.GetRequiredService(Of ISyntaxFactsService)()
_semanticFactsService = parameters.Document.Project.LanguageServices.GetRequiredService(Of ISemanticFactsService)()
_isVerbatim = Me._syntaxFactsService.IsVerbatimIdentifier(_replacementText)
_renameAnnotations = parameters.RenameAnnotations
End Sub
......@@ -400,7 +417,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename
End If
Dim newToken = oldToken
Dim shouldCheckTrivia = Me._stringAndCommentTextSpans.Contains(oldToken.Span)
Dim shouldCheckTrivia = Me._stringAndCommentTextSpans.ContainsKey(oldToken.Span)
If shouldCheckTrivia Then
Me._isProcessingStructuredTrivia += 1
newToken = MyBase.VisitToken(newToken)
......@@ -570,9 +587,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename
Return newToken
End Function
Private Function RenameInStringLiteral(oldToken As SyntaxToken, newToken As SyntaxToken, createNewStringLiteral As Func(Of SyntaxTriviaList, String, String, SyntaxTriviaList, SyntaxToken)) As SyntaxToken
Private Function RenameInStringLiteral(oldToken As SyntaxToken, newToken As SyntaxToken, subSpansToReplace As ImmutableSortedSet(Of TextSpan), createNewStringLiteral As Func(Of SyntaxTriviaList, String, String, SyntaxTriviaList, SyntaxToken)) As SyntaxToken
Dim originalString = newToken.ToString()
Dim replacedString As String = RenameLocations.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText)
Dim replacedString As String = RenameLocations.ReferenceProcessing.ReplaceMatchingSubStrings(originalString, _originalText, _replacementText, subSpansToReplace)
If replacedString <> originalString Then
Dim oldSpan = oldToken.Span
newToken = createNewStringLiteral(newToken.LeadingTrivia, replacedString, replacedString, newToken.TrailingTrivia)
......@@ -607,22 +624,23 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Rename
End Function
Private Function RenameWithinToken(oldToken As SyntaxToken, newToken As SyntaxToken) As SyntaxToken
Dim subSpansToReplace As ImmutableSortedSet(Of TextSpan) = Nothing
If Me._isProcessingComplexifiedSpans OrElse
(Me._isProcessingStructuredTrivia = 0 AndAlso Not Me._stringAndCommentTextSpans.Contains(oldToken.Span)) Then
(Me._isProcessingStructuredTrivia = 0 AndAlso Not Me._stringAndCommentTextSpans.TryGetValue(oldToken.Span, subSpansToReplace)) Then
Return newToken
End If
If Me._isRenamingInStrings Then
If Me._isRenamingInStrings OrElse subSpansToReplace?.Count > 0 Then
If newToken.Kind = SyntaxKind.StringLiteralToken Then
newToken = RenameInStringLiteral(oldToken, newToken, AddressOf SyntaxFactory.StringLiteralToken)
newToken = RenameInStringLiteral(oldToken, newToken, subSpansToReplace, AddressOf SyntaxFactory.StringLiteralToken)
ElseIf newToken.Kind = SyntaxKind.InterpolatedStringTextToken Then
newToken = RenameInStringLiteral(oldToken, newToken, AddressOf SyntaxFactory.InterpolatedStringTextToken)
newToken = RenameInStringLiteral(oldToken, newToken, subSpansToReplace, AddressOf SyntaxFactory.InterpolatedStringTextToken)
End If
End If
If Me._isRenamingInComments Then
If newToken.Kind = SyntaxKind.XmlTextLiteralToken Then
newToken = RenameInStringLiteral(oldToken, newToken, AddressOf SyntaxFactory.XmlTextLiteralToken)
newToken = RenameInStringLiteral(oldToken, newToken, subSpansToReplace, AddressOf SyntaxFactory.XmlTextLiteralToken)
ElseIf newToken.Kind = SyntaxKind.XmlNameToken AndAlso CaseInsensitiveComparison.Equals(oldToken.ValueText, _originalText) Then
Dim newIdentifierToken = SyntaxFactory.XmlNameToken(newToken.LeadingTrivia, _replacementText, SyntaxFacts.GetKeywordKind(_replacementText), newToken.TrailingTrivia)
newToken = newToken.CopyAnnotationsTo(Me._renameAnnotations.WithAdditionalAnnotations(newIdentifierToken, New RenameTokenSimplificationAnnotation() With {.OriginalTextSpan = oldToken.Span}))
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册