| File: MetaAnalyzers\CompilerExtensionStrictApiAnalyzer.cs | Web Access |
| Project: src\roslyn\src\RoslynAnalyzers\Microsoft.CodeAnalysis.Analyzers\Core\Microsoft.CodeAnalysis.Analyzers.csproj (Microsoft.CodeAnalysis.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. using System; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers { using static CodeAnalysisDiagnosticsResources; [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] internal sealed class CompilerExtensionStrictApiAnalyzer : DiagnosticAnalyzer { private const string AssemblyReferenceValidationConfigurationKey = "roslyn_correctness.assembly_reference_validation"; private const string AssemblyReferenceValidationConfigurationRelaxedValue = "relaxed"; private static readonly LocalizableString s_localizableTitle = CreateLocalizableResourceString(nameof(DoNotRegisterCompilerTypesWithBadAssemblyReferenceRuleTitle)); private static readonly LocalizableString s_localizableDescription = CreateLocalizableResourceString(nameof(DoNotRegisterCompilerTypesWithBadAssemblyReferenceRuleDescription)); private const string HelpLinkUri = "https://github.com/dotnet/roslyn/blob/main/docs/roslyn-analyzers/rules/RS1038.md"; public static readonly DiagnosticDescriptor DoNotDeclareCompilerFeatureInAssemblyWithWorkspacesReferenceStrictRule = new( DiagnosticIds.DoNotRegisterCompilerTypesWithBadAssemblyReferenceRuleId, s_localizableTitle, CreateLocalizableResourceString(nameof(DoNotDeclareCompilerFeatureInAssemblyWithWorkspacesReferenceMessage)), DiagnosticCategory.MicrosoftCodeAnalysisCorrectness, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: s_localizableDescription, helpLinkUri: HelpLinkUri, customTags: WellKnownDiagnosticTagsExtensions.Telemetry); public static readonly DiagnosticDescriptor DoNotDeclareCSharpCompilerFeatureInAssemblyWithVisualBasicReferenceStrictRule = new( DiagnosticIds.DoNotRegisterCompilerTypesWithBadAssemblyReferenceRuleId, s_localizableTitle, CreateLocalizableResourceString(nameof(DoNotDeclareCSharpCompilerFeatureInAssemblyWithVisualBasicReferenceMessage)), DiagnosticCategory.MicrosoftCodeAnalysisCorrectness, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: s_localizableDescription, helpLinkUri: HelpLinkUri, customTags: WellKnownDiagnosticTagsExtensions.Telemetry); public static readonly DiagnosticDescriptor DoNotDeclareVisualBasicCompilerFeatureInAssemblyWithCSharpReferenceStrictRule = new( DiagnosticIds.DoNotRegisterCompilerTypesWithBadAssemblyReferenceRuleId, s_localizableTitle, CreateLocalizableResourceString(nameof(DoNotDeclareVisualBasicCompilerFeatureInAssemblyWithCSharpReferenceMessage)), DiagnosticCategory.MicrosoftCodeAnalysisCorrectness, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: s_localizableDescription, helpLinkUri: HelpLinkUri, customTags: WellKnownDiagnosticTagsExtensions.Telemetry); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create( DoNotDeclareCompilerFeatureInAssemblyWithWorkspacesReferenceStrictRule, DoNotDeclareCSharpCompilerFeatureInAssemblyWithVisualBasicReferenceStrictRule, DoNotDeclareVisualBasicCompilerFeatureInAssemblyWithCSharpReferenceStrictRule); internal static bool IsStrictAnalysisEnabled(AnalyzerOptions options) { return !options.AnalyzerConfigOptionsProvider.GlobalOptions.TryGetValue(AssemblyReferenceValidationConfigurationKey, out var value) || !value.Trim().Equals(AssemblyReferenceValidationConfigurationRelaxedValue, StringComparison.Ordinal); } public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.RegisterCompilationStartAction(context => { // This analyzer is enabled by default via a configuration option that also applies to RS1022. It needs // to proceed unless .globalconfig contains the following line to enable it: // // roslyn_correctness.assembly_reference_validation = relaxed if (!IsStrictAnalysisEnabled(context.Options)) { // RS1022 is being applied instead of RS1038 return; } var typeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); var diagnosticAnalyzer = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisDiagnosticsDiagnosticAnalyzer); if (diagnosticAnalyzer is null) return; var referencesWorkspaces = false; var referencesCSharp = false; var referencesVisualBasic = false; foreach (var assemblyName in context.Compilation.ReferencedAssemblyNames) { if (assemblyName.Name == "Microsoft.CodeAnalysis.Workspaces") { referencesWorkspaces = true; } else if (assemblyName.Name == "Microsoft.CodeAnalysis.CSharp") { referencesCSharp = true; } else if (assemblyName.Name == "Microsoft.CodeAnalysis.VisualBasic") { referencesVisualBasic = true; } } if (!referencesWorkspaces && !referencesCSharp && !referencesVisualBasic) { // This compilation doesn't reference any assemblies that would produce warnings by this analyzer return; } var diagnosticAnalyzerAttribute = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisDiagnosticsDiagnosticAnalyzerAttribute); var sourceGeneratorInterface = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisISourceGenerator); var incrementalGeneratorInterface = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisIIncrementalGenerator); var generatorAttribute = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisGeneratorAttribute); context.RegisterSymbolAction( context => { var namedType = (INamedTypeSymbol)context.Symbol; if (!IsRegisteredExtension(namedType, diagnosticAnalyzer, diagnosticAnalyzerAttribute, out var applicationSyntaxReference, out var supportsCSharp, out var supportsVisualBasic) && !IsRegisteredExtension(namedType, sourceGeneratorInterface, generatorAttribute, out applicationSyntaxReference, out supportsCSharp, out supportsVisualBasic) && !IsRegisteredExtension(namedType, incrementalGeneratorInterface, generatorAttribute, out applicationSyntaxReference, out supportsCSharp, out supportsVisualBasic)) { // This is not a compiler extension return; } DiagnosticDescriptor descriptor; if (referencesWorkspaces) { descriptor = DoNotDeclareCompilerFeatureInAssemblyWithWorkspacesReferenceStrictRule; } else if (supportsCSharp && referencesVisualBasic) { descriptor = DoNotDeclareCSharpCompilerFeatureInAssemblyWithVisualBasicReferenceStrictRule; } else if (supportsVisualBasic && referencesCSharp) { descriptor = DoNotDeclareVisualBasicCompilerFeatureInAssemblyWithCSharpReferenceStrictRule; } else { return; } context.ReportDiagnostic(Diagnostic.Create(descriptor, Location.Create(applicationSyntaxReference.SyntaxTree, applicationSyntaxReference.Span))); }, SymbolKind.NamedType); }); } private static bool IsRegisteredExtension(INamedTypeSymbol extension, [NotNullWhen(true)] INamedTypeSymbol? extensionClassOrInterface, [NotNullWhen(true)] INamedTypeSymbol? registrationAttributeType, [NotNullWhen(true)] out SyntaxReference? node, out bool supportsCSharp, out bool supportsVisualBasic) { supportsCSharp = false; supportsVisualBasic = false; if (!extension.Inherits(extensionClassOrInterface)) { node = null; return false; } foreach (var attribute in extension.GetAttributes()) { // Only examine extension registrations in source Debug.Assert(attribute.ApplicationSyntaxReference is not null, $"Expected attributes returned by {nameof(ISymbol.GetAttributes)} (as opposed to {nameof(ITypeSymbolExtensions.GetApplicableAttributes)}) to have a non-null application."); if (attribute.ApplicationSyntaxReference is null) continue; if (!attribute.AttributeClass.Inherits(registrationAttributeType)) continue; foreach (var arg in attribute.ConstructorArguments) { CheckLanguage(arg, ref supportsCSharp, ref supportsVisualBasic); if (arg.Kind == TypedConstantKind.Array) { foreach (var element in arg.Values) { CheckLanguage(element, ref supportsCSharp, ref supportsVisualBasic); } } } node = attribute.ApplicationSyntaxReference; return true; } node = null; return false; } private static void CheckLanguage(TypedConstant argument, ref bool supportsCSharp, ref bool supportsVisualBasic) { if (argument is { Kind: TypedConstantKind.Primitive, Type.SpecialType: SpecialType.System_String }) { var supportedLanguage = (string?)argument.Value; if (supportedLanguage == LanguageNames.CSharp) { supportsCSharp = true; } else if (supportedLanguage == LanguageNames.VisualBasic) { supportsVisualBasic = true; } } } } }