File: BuildCheck\Checks\TargetFrameworkUnexpectedCheck.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Collections;
using Microsoft.Build.Construction;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
 
namespace Microsoft.Build.Experimental.BuildCheck.Checks;
internal class TargetFrameworkUnexpectedCheck : Check
{
    private const string RuleId = "BC0108";
    public static CheckRule SupportedRule = new CheckRule(RuleId, "TargetFrameworkUnexpected",
        ResourceUtilities.GetResourceString("BuildCheck_BC0108_Title")!,
        ResourceUtilities.GetResourceString("BuildCheck_BC0108_MessageFmt")!,
        new CheckConfiguration() { RuleId = RuleId, Severity = CheckResultSeverity.Warning });
 
    public override string FriendlyName => "MSBuild.TargetFrameworkUnexpected";
 
    public override IReadOnlyList<CheckRule> SupportedRules { get; } = [SupportedRule];
 
    public override void Initialize(ConfigurationContext configurationContext)
    {
        /* This is it - no custom configuration */
    }
 
    public override void RegisterActions(IBuildCheckRegistrationContext registrationContext)
    {
        registrationContext.RegisterEvaluatedPropertiesAction(EvaluatedPropertiesAction);
        registrationContext.RegisterEvaluatedItemsAction(EvaluatedItemsAction);
    }
 
    internal override bool IsBuiltIn => true;
 
    private readonly HashSet<string> _projectsSeen = new(MSBuildNameIgnoreCaseComparer.Default);
    private string? _tfm;
 
    private void EvaluatedPropertiesAction(BuildCheckDataContext<EvaluatedPropertiesCheckData> context)
    {
        // Resetting state for the next project.
        _tfm = null;
 
        // See CopyAlwaysCheck.EvaluatedPropertiesAction for explanation. 
        if (_projectsSeen.Contains(context.Data.ProjectFilePath))
        {
            return;
        }
 
        string? frameworks = null;
        string? framework = null;
        // TargetFramework(s) is specified
        if ((context.Data.EvaluatedProperties.TryGetValue(PropertyNames.TargetFrameworks, out frameworks) ||
             context.Data.EvaluatedProperties.TryGetValue(PropertyNames.TargetFramework, out framework)) &&
            !string.IsNullOrEmpty(framework ?? frameworks)
            &&
            !IsSdkStyleProject(context.Data.EvaluatedProperties) && !IsCppCliProject(context.Data.EvaluatedProperties)
            )
        {
            // Indicating that to the EvaluatedItemsAction, that if this project is recognized as manged - we should emit diagnostics.
            _tfm = framework ?? frameworks;
        }
 
        bool IsSdkStyleProject(IReadOnlyDictionary<string, string> evaluatedProperties)
            => evaluatedProperties.TryGetValue(PropertyNames.UsingMicrosoftNETSdk, out string? usingSdkStr) &&
               usingSdkStr.IsMSBuildTrueString();
 
        bool IsCppCliProject(IReadOnlyDictionary<string, string> evaluatedProperties)
            => evaluatedProperties.TryGetValue("CLRSupport", out string? clrSupportStr) &&
               MSBuildNameIgnoreCaseComparer.Default.Equals(clrSupportStr, "NetCore");
    }
 
    private void EvaluatedItemsAction(BuildCheckDataContext<EvaluatedItemsCheckData> context)
    {
        // Neither TargetFrameworks nor TargetFramework is specified, or the project is not Sdk-style nor C++/CLI project.
        if (_tfm == null)
        {
            return;
        }
 
        // We want to avoid repeated checking of a same project (as it might be evaluated multiple times)
        //  for this reason we use a hashset with already seen projects.
        if (!_projectsSeen.Add(context.Data.ProjectFilePath))
        {
            return;
        }
 
        foreach (ItemData itemData in context.Data.EnumerateItemsOfType(ItemNames.ProjectCapability))
        {
            if (MSBuildNameIgnoreCaseComparer.Default.Equals(itemData.EvaluatedInclude, ItemMetadataNames.managed))
            {
                // {0} specifies 'TargetFramework(s)' property value
                context.ReportResult(BuildCheckResult.Create(
                    SupportedRule,
                    // Populating precise location tracked via https://github.com/dotnet/msbuild/issues/10383
                    ElementLocation.EmptyLocation,
                    Path.GetFileName(context.Data.ProjectFilePath),
                    _tfm));
 
                break;
            }
        }
    }
}