File: Microsoft.CodeQuality.Analyzers\ApiDesignGuidelines\NestedTypesShouldNotBeVisible.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.Immutable;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines
{
    using static MicrosoftCodeQualityAnalyzersResources;

    /// <summary>
    /// CA1034: <inheritdoc cref="NestedTypesShouldNotBeVisibleTitle"/>
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    public sealed class NestedTypesShouldNotBeVisibleAnalyzer : DiagnosticAnalyzer
    {
        internal const string RuleId = "CA1034";

        private static readonly LocalizableString s_localizableTitle = CreateLocalizableResourceString(nameof(NestedTypesShouldNotBeVisibleTitle));
        private static readonly LocalizableString s_localizableDescription = CreateLocalizableResourceString(nameof(NestedTypesShouldNotBeVisibleDescription));

        internal static readonly DiagnosticDescriptor DefaultRule = DiagnosticDescriptorHelper.Create(
            RuleId,
            s_localizableTitle,
            CreateLocalizableResourceString(nameof(NestedTypesShouldNotBeVisibleMessageDefault)),
            DiagnosticCategory.Design,
            RuleLevel.CandidateForRemoval,     // Need to validate cases where people want it.
            description: s_localizableDescription,
            isPortedFxCopRule: true,
            isDataflowRule: false);

        internal static readonly DiagnosticDescriptor VisualBasicModuleRule = DiagnosticDescriptorHelper.Create(
            RuleId,
            s_localizableTitle,
            CreateLocalizableResourceString(nameof(NestedTypesShouldNotBeVisibleMessageVisualBasicModule)),
            DiagnosticCategory.Design,
            RuleLevel.CandidateForRemoval,     // Need to validate cases where people want it.
            description: s_localizableDescription,
            isPortedFxCopRule: true,
            isDataflowRule: false);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DefaultRule, VisualBasicModuleRule);

        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

            context.RegisterCompilationStartAction(
                compilationStartContext =>
                {
                    var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(compilationStartContext.Compilation);

                    INamedTypeSymbol? enumeratorType = wellKnownTypeProvider.Compilation.GetSpecialType(SpecialType.System_Collections_IEnumerator);
                    INamedTypeSymbol? dataSetType = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDataDataSet);
                    INamedTypeSymbol? dataTableType = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDataDataTable);
                    INamedTypeSymbol? dataRowType = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDataDataRow);

                    compilationStartContext.RegisterSymbolAction(
                        symbolAnalysisContext =>
                        {
                            var nestedType = (INamedTypeSymbol)symbolAnalysisContext.Symbol;
                            INamedTypeSymbol containingType = nestedType.ContainingType;
                            if (containingType == null)
                            {
                                return;
                            }

                            // Do not report diagnostic for compiler generated nested types for delegate declaration
                            if (nestedType.TypeKind == TypeKind.Delegate)
                            {
                                return;
                            }

                            // Do not report diagnostic for extension members
                            if (nestedType.TypeKind == ITypeSymbolExtensions.ExtensionTypeKind)
                            {
                                return;
                            }

                            // The Framework Design Guidelines (see 4.9 Nested Types) say that it is okay
                            // to expose nested types for advanced customization and subclassing scenarios,
                            // so, following FxCop's implementation of this rule, we allow protected and
                            // internal nested types.
                            if (nestedType.DeclaredAccessibility != Accessibility.Public)
                            {
                                return;
                            }

                            // Even if the nested type is declared public, don't complain if it's within
                            // a type that's not visible outside the assembly.
                            if (!containingType.IsExternallyVisible())
                            {
                                return;
                            }

                            // By the design guidelines, nested enumerators are exempt.
                            if (nestedType.AllInterfaces.Contains(enumeratorType))
                            {
                                return;
                            }

                            // FxCop allowed public nested enums to accommodate .NET types such as
                            // Environment.SpecialFolders.
                            if (nestedType.TypeKind == TypeKind.Enum)
                            {
                                return;
                            }

                            // Allow builder pattern
                            if (nestedType.Name.EndsWith("Builder", StringComparison.Ordinal) &&
                                containingType.ContainingType == null &&
                                !containingType.InstanceConstructors.Any(ctor => ctor.IsExternallyVisible()))
                            {
                                return;
                            }

                            if (IsDataSetSpecialCase(containingType, nestedType, dataSetType, dataTableType, dataRowType))
                            {
                                return;
                            }

                            DiagnosticDescriptor descriptor = containingType.TypeKind == TypeKind.Module
                                ? VisualBasicModuleRule
                                : DefaultRule;

                            symbolAnalysisContext.ReportDiagnostic(nestedType.CreateDiagnostic(descriptor, nestedType.Name));
                        },
                        SymbolKind.NamedType);
                });
        }

        // When you use the Visual Studio Dataset Designer to add a DataTable to a DataSet, the
        // designer generates two public nested types within the DataSet: a DataTable and a DataRow.
        // Since these are generated code, we don't want to fire on them.
        private static bool IsDataSetSpecialCase(
            INamedTypeSymbol containingType,
            INamedTypeSymbol nestedType,
            INamedTypeSymbol? dataSetType,
            INamedTypeSymbol? dataTableType,
            INamedTypeSymbol? dataRowType)
        {
            if (!containingType.GetBaseTypes().Contains(dataSetType))
            {
                return false;
            }

            var nestedTypeBases = nestedType.GetBaseTypes().ToList();
            return dataTableType != null && nestedTypeBases.Contains(dataTableType) ||
                dataRowType != null && nestedTypeBases.Contains(dataRowType);
        }
    }
}