| File: ExportedPartsShouldHaveImportingConstructor.cs | Web Access |
| Project: src\roslyn\src\RoslynAnalyzers\Roslyn.Diagnostics.Analyzers\Core\Roslyn.Diagnostics.Analyzers.csproj (Roslyn.Diagnostics.Analyzers) |
// 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 disable warnings using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Linq; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; namespace Roslyn.Diagnostics.Analyzers { using static RoslynDiagnosticsAnalyzersResources; /// <summary> /// RS0034: <inheritdoc cref="ExportedPartsShouldHaveImportingConstructorTitle"/> /// MEF-exported types should have exactly one constructor, which should be explicitly defined and marked with /// <see cref="ImportingConstructorAttribute"/>. /// </summary> [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class ExportedPartsShouldHaveImportingConstructor : DiagnosticAnalyzer { internal static readonly DiagnosticDescriptor Rule = new( RoslynDiagnosticIds.ExportedPartsShouldHaveImportingConstructorRuleId, CreateLocalizableResourceString(nameof(ExportedPartsShouldHaveImportingConstructorTitle)), CreateLocalizableResourceString(nameof(ExportedPartsShouldHaveImportingConstructorMessage)), DiagnosticCategory.RoslynDiagnosticsReliability, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: CreateLocalizableResourceString(nameof(ExportedPartsShouldHaveImportingConstructorDescription)), helpLinkUri: null, customTags: WellKnownDiagnosticTagsExtensions.Telemetry); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(compilationContext => { var exportAttributeV1 = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemComponentModelCompositionExportAttribute); var importingConstructorAttributeV1 = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemComponentModelCompositionImportingConstructorAttribute); var exportAttributeV2 = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCompositionExportAttribute); var inheritedExportAttribute = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemComponentModelCompositionInheritedExportAttribute); var importingConstructorAttributeV2 = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCompositionImportingConstructorAttribute); if (exportAttributeV1 is null && exportAttributeV2 is null) { // We don't need to check assemblies unless they're referencing MEF, so we're done return; } if (exportAttributeV1 is object && importingConstructorAttributeV1 is null) { throw new InvalidOperationException("Found MEF v1 ExportAttribute, but could not find the corresponding ImportingConstructorAttribute."); } if (exportAttributeV2 is object && importingConstructorAttributeV2 is null) { throw new InvalidOperationException("Found MEF v2 ExportAttribute, but could not find the corresponding ImportingConstructorAttribute."); } compilationContext.RegisterSymbolAction(symbolContext => { var namedType = (INamedTypeSymbol)symbolContext.Symbol; var exportAttributes = namedType.GetApplicableExportAttributes(exportAttributeV1, exportAttributeV2, inheritedExportAttribute); AnalyzeSymbolForAttribute(ref symbolContext, exportAttributeV1, importingConstructorAttributeV1, namedType, exportAttributes); AnalyzeSymbolForAttribute(ref symbolContext, exportAttributeV2, importingConstructorAttributeV2, namedType, exportAttributes); }, SymbolKind.NamedType); }); } private static void AnalyzeSymbolForAttribute(ref SymbolAnalysisContext context, INamedTypeSymbol? exportAttribute, INamedTypeSymbol? importingConstructorAttribute, INamedTypeSymbol namedType, IEnumerable<AttributeData> exportAttributes) { if (exportAttribute is null) { return; } var exportAttributeApplication = exportAttributes.FirstOrDefault(ad => ad.AttributeClass.DerivesFrom(exportAttribute)); if (exportAttributeApplication is null) { return; } IMethodSymbol? importingConstructor = null; var nonImportingConstructors = ImmutableArray<IMethodSymbol>.Empty; foreach (var constructor in namedType.Constructors) { if (constructor.IsStatic) { // Ignore static constructors continue; } if (constructor.IsImplicitlyDeclared) { if (exportAttributeApplication.ApplicationSyntaxReference is object) { // '{0}' is MEF-exported and should have a single importing constructor of the correct form context.ReportDiagnostic( exportAttributeApplication.ApplicationSyntaxReference.CreateDiagnostic( Rule, ScenarioProperties.ImplicitConstructor, context.CancellationToken, namedType.Name)); } continue; } var constructorAttributes = constructor.GetAttributes(); var appliedImportingConstructorAttribute = constructorAttributes.FirstOrDefault(ad => ad.AttributeClass.DerivesFrom(importingConstructorAttribute)); if (appliedImportingConstructorAttribute is null) { nonImportingConstructors = nonImportingConstructors.Add(constructor); continue; } importingConstructor = constructor; if (constructor.DeclaredAccessibility != Accessibility.Public) { // '{0}' is MEF-exported and should have a single importing constructor of the correct form context.ReportDiagnostic( appliedImportingConstructorAttribute.ApplicationSyntaxReference.CreateDiagnostic( Rule, ScenarioProperties.NonPublicConstructor, context.CancellationToken, namedType.Name)); continue; } } IMethodSymbol? missingImportingConstructor = null; if (importingConstructor is null) { missingImportingConstructor = nonImportingConstructors.FirstOrDefault(constructor => constructor.DeclaredAccessibility == Accessibility.Public) ?? nonImportingConstructors.FirstOrDefault(); } foreach (var constructor in nonImportingConstructors) { var properties = Equals(constructor, missingImportingConstructor) ? ScenarioProperties.MissingAttribute : ScenarioProperties.MultipleConstructors; // '{0}' is MEF-exported and should have a single importing constructor of the correct form context.ReportDiagnostic(constructor.DeclaringSyntaxReferences.CreateDiagnostic(Rule, properties, context.CancellationToken, namedType.Name)); continue; } } internal static class Scenario { public const string ImplicitConstructor = nameof(ImplicitConstructor); public const string NonPublicConstructor = nameof(NonPublicConstructor); public const string MissingAttribute = nameof(MissingAttribute); public const string MultipleConstructors = nameof(MultipleConstructors); } private static class ScenarioProperties { public static readonly ImmutableDictionary<string, string?> ImplicitConstructor = ImmutableDictionary.Create<string, string?>().Add(nameof(Scenario), nameof(ImplicitConstructor)); public static readonly ImmutableDictionary<string, string?> NonPublicConstructor = ImmutableDictionary.Create<string, string?>().Add(nameof(Scenario), nameof(NonPublicConstructor)); public static readonly ImmutableDictionary<string, string?> MissingAttribute = ImmutableDictionary.Create<string, string?>().Add(nameof(Scenario), nameof(MissingAttribute)); public static readonly ImmutableDictionary<string, string?> MultipleConstructors = ImmutableDictionary.Create<string, string?>().Add(nameof(Scenario), nameof(MultipleConstructors)); } } }