| File: BuildCheck\Checks\NoEnvironmentVariablePropertyCheck.cs | Web Access |
| Project: src\msbuild\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; using System.Collections.Generic; using Microsoft.Build.BuildCheck.Infrastructure; using Microsoft.Build.Shared; namespace Microsoft.Build.Experimental.BuildCheck.Checks; internal sealed class NoEnvironmentVariablePropertyCheck : Check { public static CheckRule SupportedRule = new CheckRule( "BC0103", "NoEnvironmentVariablePropertyCheck", ResourceUtilities.GetResourceString("BuildCheck_BC0103_Title")!, ResourceUtilities.GetResourceString("BuildCheck_BC0103_MessageFmt")!, new CheckConfiguration() { Severity = CheckResultSeverity.Suggestion }); private const string RuleId = "BC0103"; private const string VerboseEnvVariableOutputKey = "allow_displaying_environment_variable_value"; private readonly Queue<(string projectPath, BuildCheckDataContext<EnvironmentVariableCheckData>)> _buildCheckResults = new Queue<(string, BuildCheckDataContext<EnvironmentVariableCheckData>)>(); /// <summary> /// Contains the list of viewed environment variables. /// </summary> private readonly HashSet<EnvironmentVariableIdentityKey> _environmentVariablesCache = new HashSet<EnvironmentVariableIdentityKey>(); private bool _isVerboseEnvVarOutput; private EvaluationCheckScope _scope; public override string FriendlyName => "MSBuild.NoEnvironmentVariablePropertyCheck"; public override IReadOnlyList<CheckRule> SupportedRules { get; } = [SupportedRule]; public override void Initialize(ConfigurationContext configurationContext) { _scope = configurationContext.CheckConfig[0].EvaluationCheckScope; foreach (CustomConfigurationData customConfigurationData in configurationContext.CustomConfigurationData) { bool? isVerboseEnvVarOutput = GetVerboseEnvVarOutputConfig(customConfigurationData, RuleId); _isVerboseEnvVarOutput = isVerboseEnvVarOutput.HasValue && isVerboseEnvVarOutput.Value; } CheckScopeClassifier.NotifyOnScopingReadiness += HandleScopeReadiness; } public override void RegisterActions(IBuildCheckRegistrationContext registrationContext) => registrationContext.RegisterEnvironmentVariableReadAction(ProcessEnvironmentVariableReadAction); internal override bool IsBuiltIn => true; private void ProcessEnvironmentVariableReadAction(BuildCheckDataContext<EnvironmentVariableCheckData> context) { EnvironmentVariableIdentityKey identityKey = new(context.Data.EnvironmentVariableName, context.Data.EnvironmentVariableLocation); if (!_environmentVariablesCache.Contains(identityKey)) { // Scope information is available after evaluation of the project file. If it is not ready, we will report the check later. if (!CheckScopeClassifier.IsScopingReady(_scope)) { _buildCheckResults.Enqueue((context.Data.ProjectFilePath, context)); } else if (CheckScopeClassifier.IsActionInObservedScope(_scope, context.Data.EnvironmentVariableLocation.File, context.Data.ProjectFilePath)) { context.ReportResult(BuildCheckResult.CreateBuiltIn( SupportedRule, context.Data.EnvironmentVariableLocation, GetFormattedMessage(context.Data.EnvironmentVariableName, context.Data.EnvironmentVariableValue))); } _environmentVariablesCache.Add(identityKey); } } private static bool? GetVerboseEnvVarOutputConfig(CustomConfigurationData customConfigurationData, string ruleId) => customConfigurationData.RuleId.Equals(ruleId, StringComparison.InvariantCultureIgnoreCase) && (customConfigurationData.ConfigurationData?.TryGetValue(VerboseEnvVariableOutputKey, out string? configVal) ?? false) ? bool.Parse(configVal) : null; private void HandleScopeReadiness() { while (_buildCheckResults.Count > 0) { (string projectPath, BuildCheckDataContext<EnvironmentVariableCheckData> context) = _buildCheckResults.Dequeue(); if (!CheckScopeClassifier.IsActionInObservedScope(_scope, context.Data.EnvironmentVariableLocation.File, projectPath)) { continue; } context.ReportResult(BuildCheckResult.CreateBuiltIn( SupportedRule, context.Data.EnvironmentVariableLocation, GetFormattedMessage(context.Data.EnvironmentVariableName, context.Data.EnvironmentVariableValue))); } CheckScopeClassifier.NotifyOnScopingReadiness -= HandleScopeReadiness; } private string GetFormattedMessage(string envVariableName, string envVariableValue) => _isVerboseEnvVarOutput ? string.Format(ResourceUtilities.GetResourceString("BuildCheck_BC0103_MessageAddendum")!, envVariableName, envVariableValue) : $"'{envVariableName}'"; internal class EnvironmentVariableIdentityKey(string environmentVariableName, IMSBuildElementLocation location) : IEquatable<EnvironmentVariableIdentityKey> { public string EnvironmentVariableName { get; } = environmentVariableName; public IMSBuildElementLocation Location { get; } = location; public override bool Equals(object? obj) => Equals(obj as EnvironmentVariableIdentityKey); public bool Equals(EnvironmentVariableIdentityKey? other) => other != null && EnvironmentVariableName == other.EnvironmentVariableName && Location.File == other.Location.File && Location.Line == other.Location.Line && Location.Column == other.Location.Column; public override int GetHashCode() { int hashCode = 17; hashCode = hashCode * 31 + (Location.File != null ? Location.File.GetHashCode() : 0); hashCode = hashCode * 31 + Location.Line.GetHashCode(); hashCode = hashCode * 31 + Location.Column.GetHashCode(); return hashCode; } } }