Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial version of Property reads/writes analyzer
- Loading branch information
1 parent
195e928
commit cf34f6a
Showing
43 changed files
with
925 additions
and
239 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/Build/BuildCheck/API/IInternalBuildCheckRegistrationContext.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
using System; | ||
using Microsoft.Build.Experimental.BuildCheck; | ||
|
||
namespace Microsoft.Build.BuildCheck.Analyzers; | ||
|
||
internal interface IInternalBuildCheckRegistrationContext : IBuildCheckRegistrationContext | ||
{ | ||
void RegisterPropertyReadAction(Action<BuildCheckDataContext<PropertyReadData>> propertyReadAction); | ||
|
||
void RegisterPropertyWriteAction(Action<BuildCheckDataContext<PropertyWriteData>> propertyWriteAction); | ||
|
||
void RegisterProjectProcessingDoneAction(Action<BuildCheckDataContext<ProjectProcessingDoneData>> propertyWriteAction); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using Microsoft.Build.Experimental.BuildCheck; | ||
|
||
namespace Microsoft.Build.BuildCheck.Analyzers; | ||
|
||
internal abstract class InternalBuildAnalyzer : BuildAnalyzer | ||
{ | ||
/// <summary> | ||
/// | ||
/// </summary> | ||
/// <param name="registrationContext"></param> | ||
public abstract void RegisterInternalActions(IInternalBuildCheckRegistrationContext registrationContext); | ||
|
||
/// <summary> | ||
/// This is intentionally not implemented, as it is extended by <see cref="RegisterInternalActions"/>. | ||
/// </summary> | ||
/// <param name="registrationContext"></param> | ||
public override void RegisterActions(IBuildCheckRegistrationContext registrationContext) { } | ||
} |
154 changes: 154 additions & 0 deletions
154
src/Build/BuildCheck/Analyzers/PropertiesUsageAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
// 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 System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Microsoft.Build.BuildCheck.Infrastructure; | ||
using Microsoft.Build.Collections; | ||
using Microsoft.Build.Evaluation; | ||
using Microsoft.Build.Experimental.BuildCheck; | ||
using Microsoft.Build.Shared; | ||
|
||
namespace Microsoft.Build.BuildCheck.Analyzers; | ||
|
||
internal class PropertiesUsageAnalyzer : InternalBuildAnalyzer | ||
{ | ||
private static readonly BuildAnalyzerRule _usedBeforeInitializedRule = new BuildAnalyzerRule("AB001", "PropertyUsedBeforeDeclared", | ||
"A property that is accessed should be declared first.", | ||
"Property: [{0}] was accessed, but it was never initialized.", | ||
new BuildAnalyzerConfiguration() { Severity = BuildAnalyzerResultSeverity.Warning, IsEnabled = true, EvaluationAnalysisScope = EvaluationAnalysisScope.ProjectOnly }); | ||
|
||
private static readonly BuildAnalyzerRule _initializedAfterUsedRule = new BuildAnalyzerRule("AB002", "PropertyDeclaredAfterUsed", | ||
"A property should be declared before it is first used.", | ||
"Property: [{0}] first declared/initialized at [{1}] used before it was initialized.", | ||
new BuildAnalyzerConfiguration() { Severity = BuildAnalyzerResultSeverity.Warning, IsEnabled = true, EvaluationAnalysisScope = EvaluationAnalysisScope.ProjectOnly }); | ||
|
||
private static readonly BuildAnalyzerRule _unusedPropertyRule = new BuildAnalyzerRule("AB003", "UnusedPropertyDeclared", | ||
"A property that is not used should not be declared.", | ||
"Property: [{0}] was declared/initialized, but it was never used.", | ||
new BuildAnalyzerConfiguration() { Severity = BuildAnalyzerResultSeverity.Warning, IsEnabled = true, EvaluationAnalysisScope = EvaluationAnalysisScope.ProjectOnly }); | ||
|
||
internal static readonly IReadOnlyList<BuildAnalyzerRule> SupportedRulesList =[_usedBeforeInitializedRule, _initializedAfterUsedRule, _unusedPropertyRule]; | ||
|
||
public override string FriendlyName => "MSBuild.PropertiesUsageAnalyzer"; | ||
|
||
public override IReadOnlyList<BuildAnalyzerRule> SupportedRules => SupportedRulesList; | ||
|
||
private const string _allowUninitPropsInConditionsKey = "AllowUninitializedPropertiesInConditions"; | ||
private bool _allowUninitPropsInConditions = false; | ||
// TODO: Add scope to configuration visible by the analyzer - and reflect on it | ||
public override void Initialize(ConfigurationContext configurationContext) | ||
{ | ||
bool? allowUninitPropsInConditionsRule1 = null; | ||
bool? allowUninitPropsInConditionsRule2 = null; | ||
|
||
foreach (CustomConfigurationData customConfigurationData in configurationContext.CustomConfigurationData) | ||
{ | ||
allowUninitPropsInConditionsRule1 = | ||
GetAllowUninitPropsInConditionsConfig(customConfigurationData, _usedBeforeInitializedRule.Id); | ||
allowUninitPropsInConditionsRule2 = | ||
GetAllowUninitPropsInConditionsConfig(customConfigurationData, _initializedAfterUsedRule.Id); | ||
} | ||
|
||
if (allowUninitPropsInConditionsRule1.HasValue && | ||
allowUninitPropsInConditionsRule2.HasValue && | ||
allowUninitPropsInConditionsRule1 != allowUninitPropsInConditionsRule2) | ||
{ | ||
throw new BuildCheckConfigurationException( | ||
$"[{_usedBeforeInitializedRule.Id}] and [{_initializedAfterUsedRule.Id}] are not allowed to have differing configuration value for [{_allowUninitPropsInConditionsKey}]"); | ||
} | ||
|
||
if (allowUninitPropsInConditionsRule1.HasValue || allowUninitPropsInConditionsRule2.HasValue) | ||
{ | ||
_allowUninitPropsInConditions = allowUninitPropsInConditionsRule1 ?? allowUninitPropsInConditionsRule2 ?? false; | ||
} | ||
} | ||
|
||
private static bool? GetAllowUninitPropsInConditionsConfig(CustomConfigurationData customConfigurationData, | ||
string ruleId) | ||
{ | ||
if (customConfigurationData.RuleId.Equals(ruleId, StringComparison.InvariantCultureIgnoreCase) && | ||
(customConfigurationData.ConfigurationData?.TryGetValue(_allowUninitPropsInConditionsKey, out string? configVal) ?? false)) | ||
{ | ||
return bool.Parse(configVal); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
public override void RegisterInternalActions(IInternalBuildCheckRegistrationContext registrationContext) | ||
{ | ||
registrationContext.RegisterPropertyReadAction(ProcessPropertyRead); | ||
registrationContext.RegisterPropertyWriteAction(ProcessPropertyWrite); | ||
registrationContext.RegisterProjectProcessingDoneAction(DoneWithProject); | ||
} | ||
|
||
private Dictionary<string, IMsBuildElementLocation?> _writenProperties = new Dictionary<string, IMsBuildElementLocation?>(MSBuildNameIgnoreCaseComparer.Default); | ||
private HashSet<string> _readProperties = new HashSet<string>(MSBuildNameIgnoreCaseComparer.Default); | ||
private Dictionary<string, IMsBuildElementLocation> _uninitializedReads = new Dictionary<string, IMsBuildElementLocation>(MSBuildNameIgnoreCaseComparer.Default); | ||
|
||
private void ProcessPropertyWrite(BuildCheckDataContext<PropertyWriteData> context) | ||
{ | ||
PropertyWriteData writeData = context.Data; | ||
|
||
_writenProperties[writeData.PropertyName] = writeData.ElementLocation; | ||
|
||
if (!writeData.IsEmpty && _uninitializedReads.TryGetValue(writeData.PropertyName, out IMsBuildElementLocation? uninitReadLocation)) | ||
{ | ||
_uninitializedReads.Remove(writeData.PropertyName); | ||
|
||
context.ReportResult(BuildCheckResult.Create( | ||
_initializedAfterUsedRule, | ||
uninitReadLocation, | ||
writeData.PropertyName, writeData.ElementLocation?.LocationString ?? string.Empty)); | ||
} | ||
} | ||
|
||
private void ProcessPropertyRead(BuildCheckDataContext<PropertyReadData> context) | ||
{ | ||
PropertyReadData readData = context.Data; | ||
|
||
if (readData.PropertyReadContext != PropertyReadContext.PropertyEvaluationSelf) | ||
{ | ||
_readProperties.Add(readData.PropertyName); | ||
} | ||
|
||
if (readData.IsUninitialized && | ||
readData.PropertyReadContext != PropertyReadContext.PropertyEvaluationSelf && | ||
readData.PropertyReadContext != PropertyReadContext.ConditionEvaluationWithOneSideEmpty && | ||
(!_allowUninitPropsInConditions || readData.PropertyReadContext != PropertyReadContext.ConditionEvaluation)) | ||
{ | ||
_uninitializedReads[readData.PropertyName] = readData.ElementLocation; | ||
} | ||
} | ||
|
||
private void DoneWithProject(BuildCheckDataContext<ProjectProcessingDoneData> context) | ||
{ | ||
foreach (var propWithLocation in _writenProperties) | ||
{ | ||
if (propWithLocation.Value != null && !_readProperties.Contains(propWithLocation.Key)) | ||
{ | ||
context.ReportResult(BuildCheckResult.Create( | ||
_unusedPropertyRule, | ||
propWithLocation.Value, | ||
propWithLocation.Key)); | ||
} | ||
} | ||
|
||
foreach (var uninitializedRead in _uninitializedReads) | ||
{ | ||
context.ReportResult(BuildCheckResult.Create( | ||
_usedBeforeInitializedRule, | ||
uninitializedRead.Value, | ||
uninitializedRead.Key)); | ||
} | ||
|
||
_readProperties = new HashSet<string>(MSBuildNameIgnoreCaseComparer.Default); | ||
_writenProperties = new Dictionary<string, IMsBuildElementLocation?>(MSBuildNameIgnoreCaseComparer.Default); | ||
_uninitializedReads = new Dictionary<string, IMsBuildElementLocation>(MSBuildNameIgnoreCaseComparer.Default); | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.