// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines { using static MicrosoftCodeQualityAnalyzersResources; /// <summary> /// CA1724: <inheritdoc cref="TypeNamesShouldNotMatchNamespacesTitle"/> /// </summary> [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class TypeNamesShouldNotMatchNamespacesAnalyzer : DiagnosticAnalyzer { internal const string RuleId = "CA1724"; private static readonly LocalizableString s_localizableTitle = CreateLocalizableResourceString(nameof(TypeNamesShouldNotMatchNamespacesTitle)); private static readonly LocalizableString s_localizableDescription = CreateLocalizableResourceString(nameof(TypeNamesShouldNotMatchNamespacesDescription)); internal static readonly DiagnosticDescriptor DefaultRule = DiagnosticDescriptorHelper.Create( RuleId, s_localizableTitle, CreateLocalizableResourceString(nameof(TypeNamesShouldNotMatchNamespacesMessageDefault)), DiagnosticCategory.Naming, RuleLevel.Disabled, description: s_localizableDescription, isPortedFxCopRule: true, isDataflowRule: false, isReportedAtCompilationEnd: true); internal static readonly DiagnosticDescriptor SystemRule = DiagnosticDescriptorHelper.Create( RuleId, s_localizableTitle, CreateLocalizableResourceString(nameof(TypeNamesShouldNotMatchNamespacesMessageSystem)), DiagnosticCategory.Naming, RuleLevel.Disabled, description: s_localizableDescription, isPortedFxCopRule: true, isDataflowRule: false, isReportedAtCompilationEnd: true); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DefaultRule, SystemRule); private static readonly object s_lock = new(); private static ImmutableDictionary<string, string>? s_wellKnownSystemNamespaceTable; private static ImmutableDictionary<string, string> WellKnownSystemNamespaceTable { get { InitializeWellKnownSystemNamespaceTable(); RoslynDebug.Assert(s_wellKnownSystemNamespaceTable != null); return s_wellKnownSystemNamespaceTable; } } public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); context.RegisterCompilationStartAction( compilationStartAnalysisContext => { var externallyVisibleNamedTypes = new ConcurrentBag<INamedTypeSymbol>(); compilationStartAnalysisContext.RegisterSymbolAction( symbolAnalysisContext => { var namedType = (INamedTypeSymbol)symbolAnalysisContext.Symbol; if (namedType.IsExternallyVisible()) { externallyVisibleNamedTypes.Add(namedType); } }, SymbolKind.NamedType); compilationStartAnalysisContext.RegisterCompilationEndAction( compilationAnalysisContext => { var namespaceNamesInCompilation = new ConcurrentBag<string>(); Compilation compilation = compilationAnalysisContext.Compilation; AddNamespacesFromCompilation(namespaceNamesInCompilation, compilation.GlobalNamespace); /* We construct a dictionary whose keys are all the components of all the namespace names in the compilation, * and whose values are the namespace names of which the components are a part. For example, if the compilation * includes namespaces A.B and C.D, the dictionary will map "A" to "A", "B" to "A.B", "C" to "C", and "D" to "C.D". * When the analyzer encounters a type name that appears in a dictionary, it will emit a diagnostic, for instance, * "Type name "D" conflicts with namespace name "C.D"". * A component can occur in more than one namespace (for example, you might have namespaces "A" and "A.B".). * In that case, we have to choose one namespace to report the diagnostic on. We want to make sure that this is * deterministic (we don't want to complain about "A" in one compilation, and about "A.B" in the next). * By calling ToImmutableSortedSet on the list of namespace names in the compilation, we ensure that * we'll always construct the dictionary with the same set of keys. */ var namespaceComponentToNamespaceNameDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); UpdateNamespaceTable(namespaceComponentToNamespaceNameDictionary, namespaceNamesInCompilation.ToImmutableSortedSet()); foreach (INamedTypeSymbol symbol in externallyVisibleNamedTypes) { string symbolName = symbol.Name; if (WellKnownSystemNamespaceTable.TryGetValue(symbolName, out var wellKnownSystemNamespaceValue)) { compilationAnalysisContext.ReportDiagnostic(symbol.CreateDiagnostic(SystemRule, symbolName, wellKnownSystemNamespaceValue)); } else if (namespaceComponentToNamespaceNameDictionary.TryGetValue(symbolName, out var namespaceNameValue)) { compilationAnalysisContext.ReportDiagnostic(symbol.CreateDiagnostic(DefaultRule, symbolName, namespaceNameValue)); } } }); }); } private static bool AddNamespacesFromCompilation(ConcurrentBag<string> namespaceNamesInCompilation, INamespaceSymbol @namespace) { bool hasExternallyVisibleType = false; foreach (INamespaceSymbol namespaceMember in @namespace.GetNamespaceMembers()) { if (AddNamespacesFromCompilation(namespaceNamesInCompilation, namespaceMember)) { hasExternallyVisibleType = true; } } if (!hasExternallyVisibleType) { foreach (var type in @namespace.GetTypeMembers()) { if (type.IsExternallyVisible()) { hasExternallyVisibleType = true; break; } } } if (hasExternallyVisibleType) { namespaceNamesInCompilation.Add(@namespace.ToDisplayString()); return true; } return false; } private static void InitializeWellKnownSystemNamespaceTable() { if (s_wellKnownSystemNamespaceTable == null) { lock (s_lock) { if (s_wellKnownSystemNamespaceTable == null) { #region List of Well known System Namespaces var wellKnownSystemNamespaces = new List<string> { "Microsoft.CSharp", "Microsoft.SqlServer.Server", "Microsoft.VisualBasic", "Microsoft.Win32", "Microsoft.Win32.SafeHandles", "System", "System.CodeDom", "System.CodeDom.Compiler", "System.Collections", "System.Collections.Generic", "System.Collections.ObjectModel", "System.Collections.Specialized", "System.ComponentModel", "System.ComponentModel.Design", "System.ComponentModel.Design.Serialization", "System.Configuration", "System.Configuration.Assemblies", "System.Data", "System.Data.Common", "System.Data.Odbc", "System.Data.OleDb", "System.Data.Sql", "System.Data.SqlClient", "System.Data.SqlTypes", "System.Deployment.Internal", "System.Diagnostics", "System.Diagnostics.CodeAnalysis", "System.Diagnostics.SymbolStore", "System.Drawing", "System.Drawing.Design", "System.Drawing.Drawing2D", "System.Drawing.Imaging", "System.Drawing.Printing", "System.Drawing.Text", "System.Globalization", "System.IO", "System.IO.Compression", "System.IO.IsolatedStorage", "System.IO.Ports", "System.Media", "System.Net", "System.Net.Cache", "System.Net.Configuration", "System.Net.Mail", "System.Net.Mime", "System.Net.NetworkInformation", "System.Net.Security", "System.Net.Sockets", "System.Reflection", "System.Reflection.Emit", "System.Resources", "System.Runtime", "System.Runtime.CompilerServices", "System.Runtime.ConstrainedExecution", "System.Runtime.Hosting", "System.Runtime.InteropServices", "System.Runtime.InteropServices.ComTypes", "System.Runtime.InteropServices.Expando", "System.Runtime.Remoting", "System.Runtime.Remoting.Activation", "System.Runtime.Remoting.Channels", "System.Runtime.Remoting.Contexts", "System.Runtime.Remoting.Lifetime", "System.Runtime.Remoting.Messaging", "System.Runtime.Remoting.Metadata", "System.Runtime.Remoting.Metadata.W3cXsd2001", "System.Runtime.Remoting.Proxies", "System.Runtime.Remoting.Services", "System.Runtime.Serialization", "System.Runtime.Serialization.Formatters", "System.Runtime.Serialization.Formatters.Binary", "System.Runtime.Versioning", "System.Security", "System.Security.AccessControl", "System.Security.Authentication", "System.Security.Cryptography", "System.Security.Cryptography.X509Certificates", "System.Security.Permissions", "System.Security.Policy", "System.Security.Principal", "System.Text", "System.Text.RegularExpressions", "System.Threading", "System.Timers", "System.Web", "System.Web.Caching", "System.Web.Compilation", "System.Web.Configuration", "System.Web.Configuration.Internal", "System.Web.Handlers", "System.Web.Hosting", "System.Web.Mail", "System.Web.Management", "System.Web.Profile", "System.Web.Security", "System.Web.SessionState", "System.Web.UI", "System.Web.UI.Adapters", "System.Web.UI.HtmlControls", "System.Web.UI.WebControls", "System.Web.UI.WebControls.Adapters", "System.Web.UI.WebControls.WebParts", "System.Web.Util", "System.Windows.Forms", "System.Windows.Forms.ComponentModel.Com2Interop", "System.Windows.Forms.Design", "System.Windows.Forms.Layout", "System.Windows.Forms.PropertyGridInternal", "System.Windows.Forms.VisualStyles", "System.Xml", "System.Xml.Schema", "System.Xml.Serialization", "System.Xml.Serialization.Advanced", "System.Xml.Serialization.Configuration", "System.Xml.XPath", "System.Xml.Xsl" }; #endregion var wellKnownSystemNamespaceTable = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); UpdateNamespaceTable(wellKnownSystemNamespaceTable, wellKnownSystemNamespaces); s_wellKnownSystemNamespaceTable = wellKnownSystemNamespaceTable.ToImmutableDictionary(); } } } } private static void UpdateNamespaceTable(Dictionary<string, string> namespaceTable, IList<string> namespaces) { if (namespaces == null) { return; } foreach (string namespaceName in namespaces) { UpdateNamespaceTable(namespaceTable, namespaceName); } } private static void UpdateNamespaceTable(Dictionary<string, string> namespaceTable, string namespaceName) { foreach (string word in namespaceName.Split('.')) { if (!namespaceTable.ContainsKey(word)) namespaceTable.Add(word, namespaceName); } } } }