From d6aecd9ba7a240f9a1c256bfb05d82c31eed2985 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Mon, 28 Dec 2015 13:36:40 -0800 Subject: [PATCH] Ensure that analyzer driver processes declared namespace symbols which are scoped to source assembly being analyzed, not the merged namespace symbols with declaring references across the compilation (including referenced assemblies). --- .../Diagnostics/DiagnosticAnalyzerTests.cs | 14 ++++++++ .../AnalyzerDriver/DeclarationComputer.cs | 34 +++++++++++++++--- .../DiagnosticAnalyzer/AnalysisState.cs | 3 +- .../Diagnostics/DiagnosticServiceTests.vb | 35 +++++++++++++++++++ .../Desktop/CommonDiagnosticAnalyzers.cs | 32 +++++++++++++++++ 5 files changed, 113 insertions(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs index bcec4f86fe0..b8dc4e4df5a 100644 --- a/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Diagnostics/DiagnosticAnalyzerTests.cs @@ -1491,5 +1491,19 @@ private static void AddExpectedDiagnostic(ArrayBuilder bu var diagnostic = Diagnostic(diagnosticId, squiggledText).WithArguments(arguments).WithLocation(line, column); builder.Add(diagnostic); } + + [Fact] + public void TestEnsureNoMergedNamespaceSymbolAnalyzer() + { + var source = @"namespace N1.N2 { }"; + + var metadataReference = CreateCompilationWithMscorlib(source).ToMetadataReference(); + var compilation = CreateCompilationWithMscorlib(source, new[] { metadataReference }); + compilation.VerifyDiagnostics(); + + // Analyzer reports a diagnostic if it receives a merged namespace symbol across assemblies in compilation. + var analyzers = new DiagnosticAnalyzer[] { new EnsureNoMergedNamespaceSymbolAnalyzer() }; + compilation.VerifyAnalyzerDiagnostics(analyzers); + } } } diff --git a/src/Compilers/Core/AnalyzerDriver/DeclarationComputer.cs b/src/Compilers/Core/AnalyzerDriver/DeclarationComputer.cs index c0944b2f97e..554f60d62c3 100644 --- a/src/Compilers/Core/AnalyzerDriver/DeclarationComputer.cs +++ b/src/Compilers/Core/AnalyzerDriver/DeclarationComputer.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { @@ -12,7 +13,7 @@ internal class DeclarationComputer { internal static DeclarationInfo GetDeclarationInfo(SemanticModel model, SyntaxNode node, bool getSymbol, IEnumerable executableCodeBlocks, CancellationToken cancellationToken) { - var declaredSymbol = getSymbol ? model.GetDeclaredSymbol(node, cancellationToken) : null; + var declaredSymbol = GetDeclaredSymbol(model, node, getSymbol, cancellationToken); var codeBlocks = executableCodeBlocks?.Where(c => c != null).AsImmutableOrEmpty() ?? ImmutableArray.Empty; return new DeclarationInfo(node, codeBlocks, declaredSymbol); } @@ -24,14 +25,39 @@ internal static DeclarationInfo GetDeclarationInfo(SemanticModel model, SyntaxNo internal static DeclarationInfo GetDeclarationInfo(SemanticModel model, SyntaxNode node, bool getSymbol, SyntaxNode executableCodeBlock, CancellationToken cancellationToken) { - var declaredSymbol = getSymbol ? model.GetDeclaredSymbol(node, cancellationToken) : null; - var codeBlock = executableCodeBlock == null ? ImmutableArray.Empty : ImmutableArray.Create(executableCodeBlock); - return new DeclarationInfo(node, codeBlock, declaredSymbol); + return GetDeclarationInfo(model, node, getSymbol, SpecializedCollections.SingletonEnumerable(executableCodeBlock), cancellationToken); } internal static DeclarationInfo GetDeclarationInfo(SemanticModel model, SyntaxNode node, bool getSymbol, CancellationToken cancellationToken, params SyntaxNode[] executableCodeBlocks) { return GetDeclarationInfo(model, node, getSymbol, executableCodeBlocks.AsEnumerable(), cancellationToken); } + + private static ISymbol GetDeclaredSymbol(SemanticModel model, SyntaxNode node, bool getSymbol, CancellationToken cancellationToken) + { + if (!getSymbol) + { + return null; + } + + var declaredSymbol = model.GetDeclaredSymbol(node, cancellationToken); + + // For namespace declarations, GetDeclaredSymbol returns a compilation scoped namespace symbol, + // which includes declarations across the compilation, including those in referenced assemblies. + // However, we are only interested in the namespace symbol scoped to the compilation's source assembly. + var namespaceSymbol = declaredSymbol as INamespaceSymbol; + if (namespaceSymbol != null && namespaceSymbol.ConstituentNamespaces.Length > 1) + { + var assemblyToScope = model.Compilation.Assembly; + var assemblyScopedNamespaceSymbol = namespaceSymbol.ConstituentNamespaces.FirstOrDefault(ns => ns.ContainingAssembly == assemblyToScope); + if (assemblyScopedNamespaceSymbol != null) + { + Debug.Assert(assemblyScopedNamespaceSymbol.ConstituentNamespaces.Length == 1); + declaredSymbol = assemblyScopedNamespaceSymbol; + } + } + + return declaredSymbol; + } } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs index 1af2f9da590..a486eded020 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalysisState.cs @@ -182,7 +182,8 @@ private static ImmutableArray CreateCompilationEventsForTree(I { var builder = ImmutableArray.CreateBuilder(); foreach (var symbol in declaredSymbols) - { + { + Debug.Assert(symbol.ContainingAssembly == compilation.Assembly); builder.Add(new SymbolDeclaredCompilationEvent(compilation, symbol)); } diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 15272d85a20..11e385c573d 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -1850,5 +1850,40 @@ class MyClass Assert.Equal(HiddenDiagnosticsCompilationAnalyzer.Descriptor.Id, diagnostics.Single().Id) End Using End Sub + + + Public Sub TestEnsureNoMergedNamespaceSymbolAnalyzer() + Dim test = + + + namespace N1.N2 { class C1 { } } + + + + BaseAssembly + + namespace N1.N2 { class C2 { } } + + + + + Using workspace = TestWorkspaceFactory.CreateWorkspace(test) + Dim project = workspace.CurrentSolution.Projects.Single(Function(p As Project) p.Name = "MainAssembly") + + ' Analyzer reports a diagnostic if it receives a merged namespace symbol across assemblies in compilation. + Dim analyzer = New EnsureNoMergedNamespaceSymbolAnalyzer() + Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(analyzer)) + project = project.AddAnalyzerReference(analyzerReference) + + Dim diagnosticService = New TestDiagnosticAnalyzerService() + + Dim descriptorsMap = diagnosticService.GetDiagnosticDescriptors(project) + Assert.Equal(1, descriptorsMap.Count) + + Dim incrementalAnalyzer = diagnosticService.CreateIncrementalAnalyzer(workspace) + Dim diagnostics = diagnosticService.GetDiagnosticsAsync(project.Solution, project.Id).WaitAndGetResult(CancellationToken.None) + Assert.Equal(0, diagnostics.Count()) + End Using + End Sub End Class End Namespace diff --git a/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs b/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs index e834ec1c05e..0b13373e9d2 100644 --- a/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs +++ b/src/Test/Utilities/Desktop/CommonDiagnosticAnalyzers.cs @@ -365,6 +365,38 @@ public sealed class AnalyzerWithNoActions : DiagnosticAnalyzer public override void Initialize(AnalysisContext context) { } } + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class EnsureNoMergedNamespaceSymbolAnalyzer : DiagnosticAnalyzer + { + public const string DiagnosticId = "DiagnosticId"; + public const string Title = "Title"; + public const string Message = "Message"; + public const string Category = "Category"; + public const DiagnosticSeverity Severity = DiagnosticSeverity.Warning; + + internal static DiagnosticDescriptor Rule = + new DiagnosticDescriptor(DiagnosticId, Title, Message, + Category, Severity, isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Namespace); + } + + private void AnalyzeSymbol(SymbolAnalysisContext context) + { + // Ensure we are not invoked for merged namespace symbol, but instead for constituent namespace scoped to the source assembly. + var ns = (INamespaceSymbol)context.Symbol; + if (ns.ContainingAssembly != context.Compilation.Assembly || ns.ConstituentNamespaces.Length > 1) + { + context.ReportDiagnostic(Diagnostic.Create(Rule, ns.Locations[0])); + } + } + } + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class AnalyzerWithNoSupportedDiagnostics : DiagnosticAnalyzer { -- GitLab