Skip to content

Commit

Permalink
Generate and package ruleset files for each analyzer package. (#1718)
Browse files Browse the repository at this point in the history
We now generate the following set of ruleset files for each analyzer package:
1. AllRulesEnabled.ruleset
2. AllRulesDefault.ruleset
3. AllRulesDisabled.ruleset

For every unique rule category within the pacakge, say security, performance, design, etc., we generate 2 ruleset files per category. For example, for Security, we generate:
1. SecurityRulesEnabled.ruleset (all security rules enabled, including the ones with IsEnabledByDefault = false. All rules from other categories are disabled)
2. SecurityRulesDefault.ruleset (all security rules have default severity and IsEnabledByDefault is honored. All rules from other categories are disabled)

For the core FxCopAnalyzers package, we also package all the legacy FxCop rulesets, which have been edited to include AllRulesDisabled.ruleset upfront to match legacy FxCop configuration.

Fixes #943
  • Loading branch information
mavasani authored Jun 12, 2018
1 parent f5dee6f commit 01f77e7
Show file tree
Hide file tree
Showing 16 changed files with 710 additions and 2 deletions.
24 changes: 24 additions & 0 deletions eng/GenerateAnalyzerNuspec.csx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ var metadataList = Args[4].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEn
var fileList = Args[5].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var assemblyList = Args[6].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var dependencyList = Args[7].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var rulesetsDir = Args[8];
var legacyRulesets = Args[9].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

var result = new StringBuilder();

Expand Down Expand Up @@ -122,6 +124,28 @@ if (fileList.Length > 0 || assemblyList.Length > 0)
result.AppendLine(FileElement(Path.Combine(assetsDir, "Uninstall.ps1"), "tools"));
}

if (rulesetsDir.Length > 0 && Directory.Exists(rulesetsDir))
{
foreach (string ruleset in Directory.EnumerateFiles(rulesetsDir))
{
if (Path.GetExtension(ruleset) == ".ruleset")
{
result.AppendLine(FileElement(Path.Combine(rulesetsDir, ruleset), "rulesets"));
}
}
}

if (legacyRulesets.Length > 0)
{
foreach (string legacyRuleset in legacyRulesets)
{
if (Path.GetExtension(legacyRuleset) == ".ruleset")
{
result.AppendLine(FileElement(Path.Combine(projectDir, legacyRuleset), @"rulesets\legacy"));
}
}
}

result.AppendLine(FileElement(Path.Combine(assetsDir, "ThirdPartyNotices.rtf"), ""));
result.AppendLine(@" </files>");

Expand Down
15 changes: 13 additions & 2 deletions eng/GenerateAnalyzerNuspec.targets
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,22 @@
<PropertyGroup>
<NuspecFile>$(IntermediateOutputPath)$(NuspecPackageId).nuspec</NuspecFile>
<NuspecBasePath>$(ArtifactsBinDir)</NuspecBasePath>
<AnalyzerRulesetsDir>"$(ArtifactsBinDir)Rulesets\$(NuspecPackageId)"</AnalyzerRulesetsDir>
</PropertyGroup>

<Target Name="GenerateAnalyzerRulesets"
BeforeTargets="GenerateAnalyzerNuspecFile"
DependsOnTargets="InitializeSourceControlInformation"
Condition="'@(AnalyzerNupkgAssembly)' != '' or '@(AnalyzerRulesetAssembly)' != ''">
<ItemGroup>
<AnalyzerRulesetAssembly Condition="'@(AnalyzerNupkgAssembly)' != '' And '@(AnalyzerRulesetAssembly)' == ''" Include="@(AnalyzerNupkgAssembly)"/>
</ItemGroup>
<Exec Command='"$(RepoRoot).dotnet\dotnet.exe" "$(ArtifactsBinDir)GenerateAnalyzerRulesets\netcoreapp2.0\GenerateAnalyzerRulesets.dll" "$(AnalyzerRulesetsDir)" "$(NuspecPackageId)" "$(TargetFramework)" "@(AnalyzerRulesetAssembly)"' />
</Target>

<Target Name="GenerateAnalyzerNuspecFile"
BeforeTargets="$(PackDependsOn)"
DependsOnTargets="InitializeSourceControlInformation"
DependsOnTargets="InitializeSourceControlInformation;GenerateAnalyzerRulesets"
Condition="'@(AnalyzerNupkgFile)' != '' or '@(AnalyzerNupkgAssembly)' != '' or'@(AnalyzerNupkgDependency)' != ''">
<PropertyGroup>
<PackageId>$(NuspecPackageId)</PackageId>
Expand All @@ -35,6 +46,6 @@
<_NuspecMetadata Include="repositoryCommit=$(SourceRevisionId)" />
<_NuspecMetadata Include="repositoryUrl=$(PrivateRepositoryUrl)" />
</ItemGroup>
<Exec Command='"$(CscToolPath)\csi.exe" "$(RepoRoot)eng\GenerateAnalyzerNuspec.csx" "$(NuspecFile)" "$(AssetsDir)\" "$(MSBuildProjectDirectory)" "$(TargetFramework)" "@(_NuspecMetadata)" "@(AnalyzerNupkgFile)" "@(AnalyzerNupkgAssembly)" "@(AnalyzerNupkgDependency)"' />
<Exec Command='"$(CscToolPath)\csi.exe" "$(RepoRoot)eng\GenerateAnalyzerNuspec.csx" "$(NuspecFile)" "$(AssetsDir)\" "$(MSBuildProjectDirectory)" "$(TargetFramework)" "@(_NuspecMetadata)" "@(AnalyzerNupkgFile)" "@(AnalyzerNupkgAssembly)" "@(AnalyzerNupkgDependency)" "$(AnalyzerRulesetsDir)" "@(AnalyzerLegacyRuleset)"' />
</Target>
</Project>
16 changes: 16 additions & 0 deletions eng/GenerateAnalyzerRulesets/GenerateAnalyzerRulesets.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImportNetSdkFromRepoToolset>false</ImportNetSdkFromRepoToolset>
</PropertyGroup>

<Import Project="Sdk.props" Sdk="RoslynTools.RepoToolset" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis" Version="$(MicrosoftCodeAnalysisVersion)" />
</ItemGroup>
</Project>
259 changes: 259 additions & 0 deletions eng/GenerateAnalyzerRulesets/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace GenerateAnalyzerRulesets
{
class Program
{
public static void Main(string[] args)
{
if (args.Length != 4)
{
throw new ArgumentException($"Excepted 4 arguments, found {args.Length}: {string.Join(';', args)}");
}

string analyzerRulesetsDir = args[0];
string analyzerPackageName = args[1];
string tfm = args[2];
var assemblyList = args[3].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList();

var allRulesByAssembly = new SortedList<string, SortedList<string, DiagnosticDescriptor>>();
var categories = new HashSet<string>();
foreach (string assembly in assemblyList)
{
var assemblyName = Path.GetFileNameWithoutExtension(assembly);
string path = Path.Combine(analyzerRulesetsDir, @"..\..", assemblyName, tfm, assembly);
if (!File.Exists(path))
{
throw new ArgumentException($"{path} does not exist", "assemblyList");
}

var analyzerFileReference = new AnalyzerFileReference(path, AnalyzerAssemblyLoader.Instance);
var analyzers = analyzerFileReference.GetAnalyzersForAllLanguages();
var rules = analyzers.SelectMany(a => a.SupportedDiagnostics);
if (rules.Any())
{
var rulesById = new SortedList<string, DiagnosticDescriptor>();
foreach (DiagnosticDescriptor rule in rules)
{
rulesById[rule.Id] = rule;
categories.Add(rule.Category);
}

allRulesByAssembly.Add(assemblyName, rulesById);
}
}

createRuleset(
"AllRulesDefault.ruleset",
"All Rules with default action",
@"All Rules with default action. Rules with IsEnabledByDefault = false are disabled.",
RulesetKind.AllDefault,
categoryOpt: null);

createRuleset(
"AllRulesEnabled.ruleset",
"All Rules Enabled with default action",
"All Rules are enabled with default action. Rules with IsEnabledByDefault = false are force enabled with default action.",
RulesetKind.AllEnabled,
categoryOpt: null);

createRuleset(
"AllRulesDisabled.ruleset",
"All Rules Disabled",
@"All Rules are disabled.",
RulesetKind.AllDisabled,
categoryOpt: null);

foreach (var category in categories)
{
createRuleset(
$"{category}RulesDefault.ruleset",
$"{category} Rules with default action",
$@"All {category} Rules with default action. Rules with IsEnabledByDefault = false or from a different category are disabled.",
RulesetKind.CategoryDefault,
categoryOpt: category);

createRuleset(
$"{category}RulesEnabled.ruleset",
$"{category} Rules Enabled with default action",
$@"All {category} Rules are enabled with default action. {category} Rules with IsEnabledByDefault = false are force enabled with default action. Rules from a different category are disabled.",
RulesetKind.CategoryEnabled,
categoryOpt: category);
}

void createRuleset(
string rulesetFileName,
string rulesetName,
string rulesetDescription,
RulesetKind rulesetKind,
string categoryOpt)
{
Debug.Assert((categoryOpt != null) == (rulesetKind == RulesetKind.CategoryDefault || rulesetKind == RulesetKind.CategoryEnabled));

var result = new StringBuilder();
startRuleset();
if (categoryOpt == null)
{
addRules(categoryPass: false);
}
else
{
result.AppendLine($@" <!-- {categoryOpt} Rules -->");
addRules(categoryPass: true);
result.AppendLine();
result.AppendLine($@" <!-- Other Rules -->");
addRules(categoryPass: false);
}

endRuleset();
var directory = Directory.CreateDirectory(analyzerRulesetsDir);
var rulesetFilePath = Path.Combine(directory.FullName, rulesetFileName);
File.WriteAllText(rulesetFilePath, result.ToString());
return;

void startRuleset()
{
result.AppendLine(@"<?xml version=""1.0""?>");
result.AppendLine($@"<RuleSet Name=""{rulesetName}"" Description=""{rulesetDescription}"" ToolsVersion=""15.0"">");
}

void endRuleset()
{
result.AppendLine("</RuleSet>");
}

void addRules(bool categoryPass)
{
foreach (var rulesByAssembly in allRulesByAssembly)
{
string assemblyName = rulesByAssembly.Key;
SortedList<string, DiagnosticDescriptor> sortedRulesById = rulesByAssembly.Value;

startRules(assemblyName);

foreach (var rule in sortedRulesById)
{
addRule(rule.Value);
}

endRules();
}

return;

void startRules(string assemblyName)
{
result.AppendLine($@" <Rules AnalyzerId=""{assemblyName}"" RuleNamespace=""{assemblyName}"">");
}

void endRules()
{
result.AppendLine(" </Rules>");
}

void addRule(DiagnosticDescriptor rule)
{
if (shouldSkipRule(rule))
{
return;
}

string ruleAction = getRuleAction(rule);
var spacing = new string(' ', count: 15 - ruleAction.Length);
result.AppendLine($@" <Rule Id=""{rule.Id}"" Action=""{ruleAction}"" /> {spacing} <!-- {rule.Title} -->");
}

bool shouldSkipRule(DiagnosticDescriptor rule)
{
switch (rulesetKind)
{
case RulesetKind.CategoryDefault:
case RulesetKind.CategoryEnabled:
if (categoryPass)
{
return rule.Category != categoryOpt;
}
else
{
return rule.Category == categoryOpt;
}

default:
return false;
}
}

string getRuleAction(DiagnosticDescriptor rule)
{
switch (rulesetKind)
{
case RulesetKind.CategoryDefault:
return getRuleActionCore(enable: categoryPass && rule.IsEnabledByDefault);

case RulesetKind.CategoryEnabled:
return getRuleActionCore(enable: categoryPass);

case RulesetKind.AllDefault:
return getRuleActionCore(enable: rule.IsEnabledByDefault);

case RulesetKind.AllEnabled:
return getRuleActionCore(enable: true);

case RulesetKind.AllDisabled:
return getRuleActionCore(enable: false);

default:
throw new InvalidProgramException();
}

string getRuleActionCore(bool enable)
{
if (enable)
{
return rule.DefaultSeverity.ToString();
}
else
{
return "None";
}
}
}
}
}
}

private enum RulesetKind
{
AllDefault,
CategoryDefault,
AllEnabled,
CategoryEnabled,
AllDisabled,
}

private sealed class AnalyzerAssemblyLoader : IAnalyzerAssemblyLoader
{
public static IAnalyzerAssemblyLoader Instance = new AnalyzerAssemblyLoader();

private AnalyzerAssemblyLoader() { }
public void AddDependencyLocation(string fullPath)
{
}

public Assembly LoadFromPath(string fullPath)
{
return Assembly.LoadFrom(fullPath);
}
}
}
}
1 change: 1 addition & 0 deletions eng/common/CIBuild.cmd
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@echo off
powershell -ExecutionPolicy ByPass -command "& """%~dp0Build.ps1""" -restore -build -projects """%~dp0..\GenerateAnalyzerRulesets\GenerateAnalyzerRulesets.csproj""" %*"
powershell -ExecutionPolicy ByPass -command "& """%~dp0Build.ps1""" -restore -build -test -sign -pack -ci %*"
exit /b %ErrorLevel%
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<RuleSet Name="Microsoft All Rules" Description="This rule set contains all rules. Running this rule set may result in a large number of warnings being reported. Use this rule set to get a comprehensive picture of all issues in your code. This can help you decide which of the more focused rule sets are most appropriate to run for your projects." ToolsVersion="10.0">
<Include Path="..\AllRulesEnabled.ruleset" Action="Default" />
</RuleSet>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0"?>
<RuleSet Name="Microsoft Basic Correctness Rules" Description="These rules focus on logic errors and common mistakes made in the usage of framework APIs. Include this rule set to expand on the list of warnings reported by the minimum recommended rules." ToolsVersion="10.0">
<Include Path="minimumrecommendedrules.ruleset" Action="Default" />
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
<Rule Id="CA1008" Action="Warning" />
<Rule Id="CA1013" Action="Warning" />
<Rule Id="CA1303" Action="Warning" />
<Rule Id="CA1308" Action="Warning" />
<Rule Id="CA1806" Action="Warning" />
<Rule Id="CA1816" Action="Warning" />
<Rule Id="CA1819" Action="Warning" />
<Rule Id="CA1820" Action="Warning" />
<Rule Id="CA1903" Action="Warning" />
<Rule Id="CA2004" Action="Warning" />
<Rule Id="CA2006" Action="Warning" />
<Rule Id="CA2102" Action="Warning" />
<Rule Id="CA2104" Action="Warning" />
<Rule Id="CA2105" Action="Warning" />
<Rule Id="CA2106" Action="Warning" />
<Rule Id="CA2115" Action="Warning" />
<Rule Id="CA2119" Action="Warning" />
<Rule Id="CA2120" Action="Warning" />
<Rule Id="CA2121" Action="Warning" />
<Rule Id="CA2130" Action="Warning" />
<Rule Id="CA2205" Action="Warning" />
<Rule Id="CA2215" Action="Warning" />
<Rule Id="CA2221" Action="Warning" />
<Rule Id="CA2222" Action="Warning" />
<Rule Id="CA2223" Action="Warning" />
<Rule Id="CA2224" Action="Warning" />
<Rule Id="CA2226" Action="Warning" />
<Rule Id="CA2227" Action="Warning" />
<Rule Id="CA2231" Action="Warning" />
<Rule Id="CA2239" Action="Warning" />
</Rules>
</RuleSet>
Loading

0 comments on commit 01f77e7

Please sign in to comment.