| File: DoNotMixAttributesFromDifferentVersionsOfMEF.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.Linq; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; namespace Roslyn.Diagnostics.Analyzers { using static RoslynDiagnosticsAnalyzersResources; /// <summary> /// RS0006: <inheritdoc cref="DoNotMixAttributesFromDifferentVersionsOfMEFTitle"/> /// </summary> [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class DoNotMixAttributesFromDifferentVersionsOfMEFAnalyzer : DiagnosticAnalyzer { internal static readonly DiagnosticDescriptor Rule = new( RoslynDiagnosticIds.MixedVersionsOfMefAttributesRuleId, CreateLocalizableResourceString(nameof(DoNotMixAttributesFromDifferentVersionsOfMEFTitle)), CreateLocalizableResourceString(nameof(DoNotMixAttributesFromDifferentVersionsOfMEFMessage)), DiagnosticCategory.RoslynDiagnosticsReliability, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: CreateLocalizableResourceString(nameof(DoNotMixAttributesFromDifferentVersionsOfMEFDescription)), 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 mefV1ExportAttribute = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemComponentModelCompositionExportAttribute); var mefV2ExportAttribute = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCompositionExportAttribute); if (mefV1ExportAttribute == null || mefV2ExportAttribute == null) { // We don't need to check assemblies unless they're referencing both versions of MEF, so we're done return; } var attributeUsageAttribute = compilationContext.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemAttributeUsageAttribute); var exportAttributes = new List<INamedTypeSymbol>() { mefV1ExportAttribute, mefV2ExportAttribute }; compilationContext.RegisterSymbolAction(c => AnalyzeSymbol(c, exportAttributes, attributeUsageAttribute), SymbolKind.NamedType); }); } private static void AnalyzeSymbol(SymbolAnalysisContext symbolContext, IEnumerable<INamedTypeSymbol> exportAttributes, INamedTypeSymbol? attributeUsageAttribute) { var namedType = (INamedTypeSymbol)symbolContext.Symbol; var namedTypeAttributes = namedType.GetApplicableAttributes(attributeUsageAttribute); // Figure out which export attributes are being used here var appliedExportAttributes = exportAttributes.Where(e => namedTypeAttributes.Any(ad => ad.AttributeClass.DerivesFrom(e))).ToList(); // If we have no exports we're done if (appliedExportAttributes.Count == 0) { return; } var badNamespaces = exportAttributes.Except(appliedExportAttributes).Select(s => s.ContainingNamespace).ToSet(); var goodNamespaces = appliedExportAttributes.Select(s => s.ContainingNamespace).ToSet(); // Now look at all attributes and see if any are metadata attributes from badNamespaces, but none from good namepaces. foreach (var namedTypeAttribute in namedTypeAttributes) { var appliedMetadataAttributes = namedTypeAttribute.AttributeClass.GetApplicableAttributes(attributeUsageAttribute) .Where(ad => ad.AttributeClass.Name.Equals("MetadataAttributeAttribute", StringComparison.Ordinal)); if (appliedMetadataAttributes.Any(ad => badNamespaces.Contains(ad.AttributeClass.ContainingNamespace)) && !appliedMetadataAttributes.Any(ad => goodNamespaces.Contains(ad.AttributeClass.ContainingNamespace))) { ReportDiagnostic(symbolContext, namedType, namedTypeAttribute); } } // Also look through all members and their attributes, and see if any are using from bad places foreach (var member in namedType.GetMembers()) { foreach (var attribute in member.GetAttributes()) { if (badNamespaces.Contains(attribute.AttributeClass.ContainingNamespace)) { ReportDiagnostic(symbolContext, namedType, attribute); } } // if it's a constructor, we should also check parameters since they may have [ImportMany] if (member is IMethodSymbol methodSymbol && methodSymbol.MethodKind == MethodKind.Constructor) { foreach (var parameter in methodSymbol.Parameters) { foreach (var attribute in parameter.GetAttributes()) { if (badNamespaces.Contains(attribute.AttributeClass.ContainingNamespace)) { ReportDiagnostic(symbolContext, namedType, attribute); } } } } } } private static void ReportDiagnostic(SymbolAnalysisContext symbolContext, INamedTypeSymbol exportedType, AttributeData problematicAttribute) { if (problematicAttribute.ApplicationSyntaxReference == null) { symbolContext.ReportDiagnostic(symbolContext.Symbol.CreateDiagnostic(Rule, problematicAttribute.AttributeClass.Name, exportedType.Name)); } else { // Attribute '{0}' comes from a different version of MEF than the export attribute on '{1}' var diagnostic = problematicAttribute.ApplicationSyntaxReference.CreateDiagnostic( Rule, symbolContext.CancellationToken, problematicAttribute.AttributeClass.Name, exportedType.Name); symbolContext.ReportDiagnostic(diagnostic); } } } }