File: Microsoft.CodeQuality.Analyzers\ApiDesignGuidelines\TypeNamesShouldNotMatchNamespaces.cs
Web Access
Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers)
// 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);
            }
        }
    }
}