From 5c76cef7b2cadc455a4afa4478c2c53e3489df5d Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Mon, 30 Sep 2019 17:15:24 -0700 Subject: [PATCH 1/4] Generate editorconfig based rule configuration files in NuGet packages --- eng/GenerateAnalyzerNuspec.csx | 31 +- eng/GenerateAnalyzerNuspec.targets | 5 +- src/GenerateAnalyzerRulesets/Program.cs | 519 +++++++++++------- .../Microsoft.CodeAnalysis.FxCopAnalyzers.md | 2 +- .../Microsoft.CodeQuality.Analyzers.md | 2 +- 5 files changed, 357 insertions(+), 202 deletions(-) diff --git a/eng/GenerateAnalyzerNuspec.csx b/eng/GenerateAnalyzerNuspec.csx index 521bb82bec..5a946ead96 100644 --- a/eng/GenerateAnalyzerNuspec.csx +++ b/eng/GenerateAnalyzerNuspec.csx @@ -1,3 +1,5 @@ +using System.IO; + string nuspecFile = Args[0]; string assetsDir = Args[1]; string projectDir = Args[2]; @@ -10,14 +12,15 @@ var assemblyList = Args[8].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEn var dependencyList = Args[9].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); var libraryList = Args[10].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); var rulesetsDir = Args[11]; -var legacyRulesets = Args[12].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); -var artifactsBinDir = Args[13]; -var analyzerDocumentationFileDir = Args[14]; -var analyzerDocumentationFileName = Args[15]; -var analyzerSarifFileDir = Args[16]; -var analyzerSarifFileName = Args[17]; -var analyzerConfigurationFileDir = Args[18]; -var analyzerConfigurationFileName = Args[19]; +var editorconfigsDir = Args[12]; +var legacyRulesets = Args[13].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); +var artifactsBinDir = Args[14]; +var analyzerDocumentationFileDir = Args[15]; +var analyzerDocumentationFileName = Args[16]; +var analyzerSarifFileDir = Args[17]; +var analyzerSarifFileName = Args[18]; +var analyzerConfigurationFileDir = Args[19]; +var analyzerConfigurationFileName = Args[20]; var result = new StringBuilder(); @@ -187,6 +190,18 @@ if (rulesetsDir.Length > 0 && Directory.Exists(rulesetsDir)) } } +if (editorconfigsDir.Length > 0 && Directory.Exists(editorconfigsDir)) +{ + foreach (string directory in Directory.EnumerateDirectories(editorconfigsDir)) + { + var directoryName = new DirectoryInfo(directory).Name; + foreach (string editorconfig in Directory.EnumerateFiles(directory)) + { + result.AppendLine(FileElement(Path.Combine(directory, editorconfig), $"editorconfig\\{directoryName}")); + } + } +} + if (analyzerDocumentationFileDir.Length > 0 && Directory.Exists(analyzerDocumentationFileDir) && analyzerDocumentationFileName.Length > 0) { var fileWithPath = Path.Combine(analyzerDocumentationFileDir, analyzerDocumentationFileName); diff --git a/eng/GenerateAnalyzerNuspec.targets b/eng/GenerateAnalyzerNuspec.targets index 2030a67ea1..842ac58da3 100644 --- a/eng/GenerateAnalyzerNuspec.targets +++ b/eng/GenerateAnalyzerNuspec.targets @@ -26,6 +26,7 @@ <_GeneratedRulesetsDir>$(IntermediateOutputPath)Rulesets + <_GeneratedEditorconfigsDir>$(IntermediateOutputPath)Editorconfig false true true @@ -65,7 +66,7 @@ - + $(_CscToolPath)\ - + \ No newline at end of file diff --git a/src/GenerateAnalyzerRulesets/Program.cs b/src/GenerateAnalyzerRulesets/Program.cs index 5412fafb4d..139b575264 100644 --- a/src/GenerateAnalyzerRulesets/Program.cs +++ b/src/GenerateAnalyzerRulesets/Program.cs @@ -19,7 +19,7 @@ public static class Program { public static int Main(string[] args) { - const int expectedArguments = 13; + const int expectedArguments = 14; if (args.Length != expectedArguments) { @@ -28,18 +28,19 @@ public static int Main(string[] args) } string analyzerRulesetsDir = args[0]; - string binDirectory = args[1]; - string configuration = args[2]; - string tfm = args[3]; - var assemblyList = args[4].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList(); - string propsFileDir = args[5]; - string propsFileName = args[6]; - string analyzerDocumentationFileDir = args[7]; - string analyzerDocumentationFileName = args[8]; - string analyzerSarifFileDir = args[9]; - string analyzerSarifFileName = args[10]; - var analyzerVersion = args[11]; - if (!bool.TryParse(args[12], out var containsPortedFxCopRules)) + string analyzerEditorconfigsDir = args[1]; + string binDirectory = args[2]; + string configuration = args[3]; + string tfm = args[4]; + var assemblyList = args[5].Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + string propsFileDir = args[6]; + string propsFileName = args[7]; + string analyzerDocumentationFileDir = args[8]; + string analyzerDocumentationFileName = args[9]; + string analyzerSarifFileDir = args[10]; + string analyzerSarifFileName = args[11]; + var analyzerVersion = args[12]; + if (!bool.TryParse(args[13], out var containsPortedFxCopRules)) { containsPortedFxCopRules = false; } @@ -87,37 +88,37 @@ public static int Main(string[] args) } } - createRuleset( - "AllRulesDefault.ruleset", - "All Rules with default action", - @"All Rules with default action. Rules with IsEnabledByDefault = false are disabled.", + createRulesetAndEditorconfig( + "AllRulesDefault", + "All Rules with default severity", + @"All Rules with default severity. Rules with IsEnabledByDefault = false are disabled.", RulesetKind.AllDefault); - 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.", + createRulesetAndEditorconfig( + "AllRulesEnabled", + "All Rules Enabled with default severity", + "All Rules are enabled with default severity. Rules with IsEnabledByDefault = false are force enabled with default severity.", RulesetKind.AllEnabled); - createRuleset( - "AllRulesDisabled.ruleset", + createRulesetAndEditorconfig( + "AllRulesDisabled", "All Rules Disabled", @"All Rules are disabled.", RulesetKind.AllDisabled); 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.", + createRulesetAndEditorconfig( + $"{category}RulesDefault", + $"{category} Rules with default severity", + $@"All {category} Rules with default severity. 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.", + createRulesetAndEditorconfig( + $"{category}RulesEnabled", + $"{category} Rules Enabled with default severity", + $@"All {category} Rules are enabled with default severity. {category} Rules with IsEnabledByDefault = false are force enabled with default severity. Rules from a different category are disabled.", RulesetKind.CategoryEnabled, categoryOpt: category); } @@ -129,17 +130,17 @@ public static int Main(string[] args) foreach (var customTag in customTagsToGenerateRulesets) { - createRuleset( - $"{customTag}RulesDefault.ruleset", - $"{customTag} Rules with default action", - $@"All {customTag} Rules with default action. Rules with IsEnabledByDefault = false and non-{customTag} rules are disabled.", + createRulesetAndEditorconfig( + $"{customTag}RulesDefault", + $"{customTag} Rules with default severity", + $@"All {customTag} Rules with default severity. Rules with IsEnabledByDefault = false and non-{customTag} rules are disabled.", RulesetKind.CustomTagDefault, customTagOpt: customTag); - createRuleset( - $"{customTag}RulesEnabled.ruleset", - $"{customTag} Rules Enabled with default action", - $@"All {customTag} Rules are enabled with default action. {customTag} Rules with IsEnabledByDefault = false are force enabled with default action. Non-{customTag} Rules are disabled.", + createRulesetAndEditorconfig( + $"{customTag}RulesEnabled", + $"{customTag} Rules Enabled with default severity", + $@"All {customTag} Rules are enabled with default severity. {customTag} Rules with IsEnabledByDefault = false are force enabled with default severity. Non-{customTag} Rules are disabled.", RulesetKind.CustomTagEnabled, customTagOpt: customTag); } @@ -153,162 +154,17 @@ public static int Main(string[] args) return 0; // Local functions. - void createRuleset( - string rulesetFileName, - string rulesetName, - string rulesetDescription, + void createRulesetAndEditorconfig( + string fileName, + string title, + string description, RulesetKind rulesetKind, string categoryOpt = null, string customTagOpt = null) { - Debug.Assert(categoryOpt == null || customTagOpt == null); - Debug.Assert((categoryOpt != null) == (rulesetKind == RulesetKind.CategoryDefault || rulesetKind == RulesetKind.CategoryEnabled)); - Debug.Assert((customTagOpt != null) == (rulesetKind == RulesetKind.CustomTagDefault || rulesetKind == RulesetKind.CustomTagEnabled)); - - var result = new StringBuilder(); - startRuleset(); - if (categoryOpt == null && customTagOpt == null) - { - addRules(categoryPass: false, customTagPass: false); - } - else - { - result.AppendLine($@" "); - addRules(categoryPass: categoryOpt != null, customTagPass: customTagOpt != null); - result.AppendLine(); - result.AppendLine($@" "); - addRules(categoryPass: false, customTagPass: false); - } - - endRuleset(); - var directory = Directory.CreateDirectory(analyzerRulesetsDir); - var rulesetFilePath = Path.Combine(directory.FullName, rulesetFileName); - File.WriteAllText(rulesetFilePath, result.ToString()); + CreateRuleset(analyzerRulesetsDir, fileName + ".ruleset", title, description, rulesetKind, categoryOpt, customTagOpt, allRulesByAssembly); + CreateEditorconfig(analyzerEditorconfigsDir, fileName, title, description, rulesetKind, categoryOpt, customTagOpt, allRulesByAssembly); return; - - void startRuleset() - { - result.AppendLine(@""); - result.AppendLine($@""); - } - - void endRuleset() - { - result.AppendLine(""); - } - - void addRules(bool categoryPass, bool customTagPass) - { - foreach (var rulesByAssembly in allRulesByAssembly) - { - string assemblyName = rulesByAssembly.Key; - SortedList sortedRulesById = rulesByAssembly.Value; - - if (!sortedRulesById.Any(r => !shouldSkipRule(r.Value))) - { - // Bail out if we don't have any rule to be added for this assembly. - continue; - } - - startRules(assemblyName); - - foreach (var rule in sortedRulesById) - { - addRule(rule.Value); - } - - endRules(); - } - - return; - - void startRules(string assemblyName) - { - result.AppendLine($@" "); - } - - void endRules() - { - result.AppendLine(" "); - } - - void addRule(DiagnosticDescriptor rule) - { - if (shouldSkipRule(rule)) - { - return; - } - - string ruleAction = getRuleAction(rule); - var spacing = new string(' ', count: 15 - ruleAction.Length); - result.AppendLine($@" {spacing} "); - } - - bool shouldSkipRule(DiagnosticDescriptor rule) - { - switch (rulesetKind) - { - case RulesetKind.CategoryDefault: - case RulesetKind.CategoryEnabled: - if (categoryPass) - { - return rule.Category != categoryOpt; - } - else - { - return rule.Category == categoryOpt; - } - - case RulesetKind.CustomTagDefault: - case RulesetKind.CustomTagEnabled: - if (customTagPass) - { - return !rule.CustomTags.Contains(customTagOpt); - } - else - { - return rule.CustomTags.Contains(customTagOpt); - } - - default: - return false; - } - } - - string getRuleAction(DiagnosticDescriptor rule) - { - return rulesetKind switch - { - RulesetKind.CategoryDefault => getRuleActionCore(enable: categoryPass && rule.IsEnabledByDefault), - - RulesetKind.CategoryEnabled => getRuleActionCore(enable: categoryPass), - - RulesetKind.CustomTagDefault => getRuleActionCore(enable: customTagPass && rule.IsEnabledByDefault), - - RulesetKind.CustomTagEnabled => getRuleActionCore(enable: customTagPass), - - RulesetKind.AllDefault => getRuleActionCore(enable: rule.IsEnabledByDefault), - - RulesetKind.AllEnabled => getRuleActionCore(enable: true), - - RulesetKind.AllDisabled => getRuleActionCore(enable: false), - - _ => throw new InvalidProgramException(), - }; - - string getRuleActionCore(bool enable) - { - if (enable) - { - return rule.DefaultSeverity.ToString(); - } - else - { - return "None"; - } - } - } - } } void createPropsFile() @@ -578,6 +434,289 @@ static string getLevel(DiagnosticSeverity severity) } } + private static void CreateRuleset( + string analyzerRulesetsDir, + string rulesetFileName, + string rulesetTitle, + string rulesetDescription, + RulesetKind rulesetKind, + string categoryOpt, + string customTagOpt, + SortedList> allRulesByAssembly) + { + var text = GetRulesetOrEditorconfigText( + rulesetKind, + startRuleset, + endRuleset, + startRulesSectionForAssembly, + endRulesSectionForAssembly, + addRuleEntry, + getSeverityString, + commentStart: " ", + categoryOpt, + customTagOpt, + allRulesByAssembly); + + var directory = Directory.CreateDirectory(analyzerRulesetsDir); + var rulesetFilePath = Path.Combine(directory.FullName, rulesetFileName); + File.WriteAllText(rulesetFilePath, text); + return; + + // Local functions + void startRuleset(StringBuilder result) + { + result.AppendLine(@""); + result.AppendLine($@""); + } + + static void endRuleset(StringBuilder result) + { + result.AppendLine(""); + } + + static void startRulesSectionForAssembly(StringBuilder result, string assemblyName) + { + result.AppendLine($@" "); + } + + static void endRulesSectionForAssembly(StringBuilder result) + { + result.AppendLine(" "); + } + + static void addRuleEntry(StringBuilder result, DiagnosticDescriptor rule, string severity, string spacingForTitle) + { + result.AppendLine($@" {spacingForTitle} "); + } + + static string getSeverityString(DiagnosticSeverity? severityOpt) + { + return severityOpt.HasValue ? severityOpt.ToString() : "None"; + } + } + + private static void CreateEditorconfig( + string analyzerEditorconfigsDir, + string editorconfigFolder, + string editorconfigTitle, + string editorconfigDescription, + RulesetKind rulesetKind, + string categoryOpt, + string customTagOpt, + SortedList> allRulesByAssembly) + { + var text = GetRulesetOrEditorconfigText( + rulesetKind, + startEditorconfig, + endEditorconfig, + startRulesSectionForAssembly, + endRulesSectionForAssembly, + addRuleEntry, + getSeverityString, + commentStart: "# ", + commentEnd: string.Empty, + categoryOpt, + customTagOpt, + allRulesByAssembly); + + var directory = Directory.CreateDirectory(Path.Combine(analyzerEditorconfigsDir, editorconfigFolder)); + var editorconfigFilePath = Path.Combine(directory.FullName, ".editorconfig"); + File.WriteAllText(editorconfigFilePath, text); + return; + + // Local functions + void startEditorconfig(StringBuilder result) + { + result.AppendLine(@"# NOTE: Requires **VS2019 16.3** or later"); + result.AppendLine(); + result.AppendLine($@"# {editorconfigTitle}"); + result.AppendLine($@"# Description: {editorconfigDescription}"); + result.AppendLine(); + result.AppendLine(@"# To learn more about .editorconfig see https://editorconfig.org/"); + result.AppendLine(@"root = true"); + result.AppendLine(); + result.AppendLine(@"# Code files"); + result.AppendLine(@"[*.{cs,vb}]"); + } + + static void endEditorconfig(StringBuilder _) + { + } + + static void startRulesSectionForAssembly(StringBuilder result, string assemblyName) + { + result.AppendLine(); + result.AppendLine($@"# Rules in '{assemblyName}.dll'"); + } + + static void endRulesSectionForAssembly(StringBuilder _) + { + } + + static void addRuleEntry(StringBuilder result, DiagnosticDescriptor rule, string severity, string spacingForTitle) + { + result.AppendLine($@"dotnet_diagnostic.{rule.Id}.severity = {severity} {spacingForTitle} # {rule.Title}"); + } + + static string getSeverityString(DiagnosticSeverity? severityOpt) + { + if (!severityOpt.HasValue) + { + return "none"; + } + + return severityOpt.Value switch + { + DiagnosticSeverity.Error => "error", + DiagnosticSeverity.Warning => "warning", + DiagnosticSeverity.Info => "suggestion", + DiagnosticSeverity.Hidden => "silent", + _ => throw new NotImplementedException(severityOpt.Value.ToString()), + }; + } + } + + private static string GetRulesetOrEditorconfigText( + RulesetKind rulesetKind, + Action startRulesetOrEditorconfig, + Action endRulesetOrEditorconfig, + Action startRulesSectionForAssembly, + Action endRulesSectionForAssembly, + Action addRuleEntry, + Func getSeverityString, + string commentStart, + string commentEnd, + string categoryOpt, + string customTagOpt, + SortedList> allRulesByAssembly) + { + Debug.Assert(categoryOpt == null || customTagOpt == null); + Debug.Assert((categoryOpt != null) == (rulesetKind == RulesetKind.CategoryDefault || rulesetKind == RulesetKind.CategoryEnabled)); + Debug.Assert((customTagOpt != null) == (rulesetKind == RulesetKind.CustomTagDefault || rulesetKind == RulesetKind.CustomTagEnabled)); + + var result = new StringBuilder(); + startRulesetOrEditorconfig(result); + if (categoryOpt == null && customTagOpt == null) + { + addRules(categoryPass: false, customTagPass: false); + } + else + { + result.AppendLine($@"{commentStart}{categoryOpt ?? customTagOpt} Rules{commentEnd}"); + addRules(categoryPass: categoryOpt != null, customTagPass: customTagOpt != null); + result.AppendLine(); + result.AppendLine($@"{commentStart}Other Rules{commentEnd}"); + addRules(categoryPass: false, customTagPass: false); + } + + endRulesetOrEditorconfig(result); + return result.ToString(); + + void addRules(bool categoryPass, bool customTagPass) + { + foreach (var rulesByAssembly in allRulesByAssembly) + { + string assemblyName = rulesByAssembly.Key; + SortedList sortedRulesById = rulesByAssembly.Value; + + if (!sortedRulesById.Any(r => !shouldSkipRule(r.Value))) + { + // Bail out if we don't have any rule to be added for this assembly. + continue; + } + + startRulesSectionForAssembly(result, assemblyName); + + foreach (var rule in sortedRulesById) + { + addRule(rule.Value); + } + + endRulesSectionForAssembly(result); + } + + return; + + void addRule(DiagnosticDescriptor rule) + { + if (shouldSkipRule(rule)) + { + return; + } + + string severity = getRuleAction(rule); + var spacing = new string(' ', count: 15 - severity.Length); + addRuleEntry(result, rule, severity, spacing); + } + + bool shouldSkipRule(DiagnosticDescriptor rule) + { + switch (rulesetKind) + { + case RulesetKind.CategoryDefault: + case RulesetKind.CategoryEnabled: + if (categoryPass) + { + return rule.Category != categoryOpt; + } + else + { + return rule.Category == categoryOpt; + } + + case RulesetKind.CustomTagDefault: + case RulesetKind.CustomTagEnabled: + if (customTagPass) + { + return !rule.CustomTags.Contains(customTagOpt); + } + else + { + return rule.CustomTags.Contains(customTagOpt); + } + + default: + return false; + } + } + + string getRuleAction(DiagnosticDescriptor rule) + { + return rulesetKind switch + { + RulesetKind.CategoryDefault => getRuleActionCore(enable: categoryPass && rule.IsEnabledByDefault), + + RulesetKind.CategoryEnabled => getRuleActionCore(enable: categoryPass), + + RulesetKind.CustomTagDefault => getRuleActionCore(enable: customTagPass && rule.IsEnabledByDefault), + + RulesetKind.CustomTagEnabled => getRuleActionCore(enable: customTagPass), + + RulesetKind.AllDefault => getRuleActionCore(enable: rule.IsEnabledByDefault), + + RulesetKind.AllEnabled => getRuleActionCore(enable: true), + + RulesetKind.AllDisabled => getRuleActionCore(enable: false), + + _ => throw new InvalidProgramException(), + }; + + string getRuleActionCore(bool enable) + { + if (enable) + { + return getSeverityString(rule.DefaultSeverity); + } + else + { + return getSeverityString(null); + } + } + } + } + } + private enum RulesetKind { AllDefault, diff --git a/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.md b/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.md index cce89c9f0b..3d3b57be83 100644 --- a/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.md +++ b/src/Microsoft.CodeAnalysis.FxCopAnalyzers/Microsoft.CodeAnalysis.FxCopAnalyzers.md @@ -99,7 +99,7 @@ Sr. No. | Rule ID | Title | Category | Enabled | CodeFix | Description | 96 | [CA2101](https://docs.microsoft.com/visualstudio/code-quality/ca2101-specify-marshaling-for-p-invoke-string-arguments) | Specify marshaling for P/Invoke string arguments | Globalization | True | True | A platform invoke member allows partially trusted callers, has a string parameter, and does not explicitly marshal the string. This can cause a potential security vulnerability. | 97 | [CA2119](https://docs.microsoft.com/visualstudio/code-quality/ca2119-seal-methods-that-satisfy-private-interfaces) | Seal methods that satisfy private interfaces | Security | True | True | An inheritable public type provides an overridable method implementation of an internal (Friend in Visual Basic) interface. To fix a violation of this rule, prevent the method from being overridden outside the assembly. | 98 | [CA2153](https://docs.microsoft.com/visualstudio/code-quality/ca2153-avoid-handling-corrupted-state-exceptions) | Do Not Catch Corrupted State Exceptions | Security | True | False | Catching corrupted state exceptions could mask errors (such as access violations), resulting in inconsistent state of execution or making it easier for attackers to compromise system. Instead, catch and handle a more specific set of exception type(s) or re-throw the exception | -99 | [CA2200](https://docs.microsoft.com/visualstudio/code-quality/ca2200-rethrow-to-preserve-stack-details) | Rethrow to preserve stack details. | Usage | True | False | Re-throwing caught exception changes stack information. | +99 | [CA2200](https://docs.microsoft.com/visualstudio/code-quality/ca2200-rethrow-to-preserve-stack-details) | Rethrow to preserve stack details. | Usage | True | True | Re-throwing caught exception changes stack information. | 100 | [CA2201](https://docs.microsoft.com/visualstudio/code-quality/ca2201-do-not-raise-reserved-exception-types) | Do not raise reserved exception types | Usage | False | False | An exception of type that is not sufficiently specific or reserved by the runtime should never be raised by user code. This makes the original error difficult to detect and debug. If this exception instance might be thrown, use a different exception type. | 101 | [CA2207](https://docs.microsoft.com/visualstudio/code-quality/ca2207-initialize-value-type-static-fields-inline) | Initialize value type static fields inline | Usage | True | False | A value type declares an explicit static constructor. To fix a violation of this rule, initialize all static data when it is declared and remove the static constructor. | 102 | [CA2208](https://docs.microsoft.com/visualstudio/code-quality/ca2208-instantiate-argument-exceptions-correctly) | Instantiate argument exceptions correctly | Usage | True | False | A call is made to the default (parameterless) constructor of an exception type that is or derives from ArgumentException, or an incorrect string argument is passed to a parameterized constructor of an exception type that is or derives from ArgumentException. | diff --git a/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md b/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md index bd771450b2..d390f0044c 100644 --- a/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md +++ b/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md @@ -73,7 +73,7 @@ Sr. No. | Rule ID | Title | Category | Enabled | CodeFix | Description | 70 | [CA1823](https://docs.microsoft.com/visualstudio/code-quality/ca1823-avoid-unused-private-fields) | Avoid unused private fields | Performance | True | True | Private fields were detected that do not appear to be accessed in the assembly. | 71 | [CA2007](https://docs.microsoft.com/visualstudio/code-quality/ca2007-do-not-directly-await-task) | Consider calling ConfigureAwait on the awaited task | Reliability | True | True | When an asynchronous method awaits a Task directly, continuation occurs in the same thread that created the task. Consider calling Task.ConfigureAwait(Boolean) to signal your intention for continuation. Call ConfigureAwait(false) on the task to schedule continuations to the thread pool, thereby avoiding a deadlock on the UI thread. Passing false is a good option for app-independent libraries. Calling ConfigureAwait(true) on the task has the same behavior as not explicitly calling ConfigureAwait. By explicitly calling this method, you're letting readers know you intentionally want to perform the continuation on the original synchronization context. | 72 | [CA2119](https://docs.microsoft.com/visualstudio/code-quality/ca2119-seal-methods-that-satisfy-private-interfaces) | Seal methods that satisfy private interfaces | Security | True | True | An inheritable public type provides an overridable method implementation of an internal (Friend in Visual Basic) interface. To fix a violation of this rule, prevent the method from being overridden outside the assembly. | -73 | [CA2200](https://docs.microsoft.com/visualstudio/code-quality/ca2200-rethrow-to-preserve-stack-details) | Rethrow to preserve stack details. | Usage | True | False | Re-throwing caught exception changes stack information. | +73 | [CA2200](https://docs.microsoft.com/visualstudio/code-quality/ca2200-rethrow-to-preserve-stack-details) | Rethrow to preserve stack details. | Usage | True | True | Re-throwing caught exception changes stack information. | 74 | [CA2211](https://docs.microsoft.com/visualstudio/code-quality/ca2211-non-constant-fields-should-not-be-visible) | Non-constant fields should not be visible | Usage | True | False | Static fields that are neither constants nor read-only are not thread-safe. Access to such a field must be carefully controlled and requires advanced programming techniques to synchronize access to the class object. | 75 | [CA2214](https://docs.microsoft.com/visualstudio/code-quality/ca2214-do-not-call-overridable-methods-in-constructors) | Do not call overridable methods in constructors | Usage | True | False | Virtual methods defined on the class should not be called from constructors. If a derived class has overridden the method, the derived class version will be called (before the derived class constructor is called). | 76 | [CA2217](https://docs.microsoft.com/visualstudio/code-quality/ca2217-do-not-mark-enums-with-flagsattribute) | Do not mark enums with FlagsAttribute | Usage | False | True | An externally visible enumeration is marked by using FlagsAttribute, and it has one or more values that are not powers of two or a combination of the other defined values on the enumeration. | From 0b8763f3fc99123937e1f362219b7522dd20e9f6 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Tue, 1 Oct 2019 06:09:41 -0700 Subject: [PATCH 2/4] Do not group rules by assembly name --- eng/GenerateAnalyzerNuspec.targets | 2 +- src/GenerateAnalyzerRulesets/Program.cs | 84 +++++++++++-------------- 2 files changed, 36 insertions(+), 50 deletions(-) diff --git a/eng/GenerateAnalyzerNuspec.targets b/eng/GenerateAnalyzerNuspec.targets index 842ac58da3..b1bb2a3135 100644 --- a/eng/GenerateAnalyzerNuspec.targets +++ b/eng/GenerateAnalyzerNuspec.targets @@ -66,7 +66,7 @@ - + >(); var allRulesById = new SortedList(); var fixableDiagnosticIds = new HashSet(); var categories = new HashSet(); @@ -62,7 +62,6 @@ public static int Main(string[] args) var analyzerFileReference = new AnalyzerFileReference(path, AnalyzerAssemblyLoader.Instance); var analyzers = analyzerFileReference.GetAnalyzersForAllLanguages(); - var rulesById = new SortedList(); var assemblyRulesMetadata = (path, rules: new SortedList()); @@ -72,14 +71,12 @@ public static int Main(string[] args) foreach (var rule in analyzer.SupportedDiagnostics) { - rulesById[rule.Id] = rule; allRulesById[rule.Id] = rule; categories.Add(rule.Category); assemblyRulesMetadata.rules[rule.Id] = (rule, analyzerType.Name, analyzerType.GetCustomAttribute(true)?.Languages); } } - allRulesByAssembly.Add(assemblyName, rulesById); rulesMetadata.Add(assemblyName, assemblyRulesMetadata); foreach (var id in analyzerFileReference.GetFixers().SelectMany(fixer => fixer.FixableDiagnosticIds)) @@ -162,8 +159,8 @@ public static int Main(string[] args) string categoryOpt = null, string customTagOpt = null) { - CreateRuleset(analyzerRulesetsDir, fileName + ".ruleset", title, description, rulesetKind, categoryOpt, customTagOpt, allRulesByAssembly); - CreateEditorconfig(analyzerEditorconfigsDir, fileName, title, description, rulesetKind, categoryOpt, customTagOpt, allRulesByAssembly); + CreateRuleset(analyzerRulesetsDir, fileName + ".ruleset", title, description, rulesetKind, categoryOpt, customTagOpt, allRulesById, analyzerPackageName); + CreateEditorconfig(analyzerEditorconfigsDir, fileName, title, description, rulesetKind, categoryOpt, customTagOpt, allRulesById); return; } @@ -195,7 +192,7 @@ static string getFlowAnalysisFeatureFlag() string getCodeAnalysisTreatWarningsNotAsErrors() { - var allRuleIds = string.Join(';', allRulesByAssembly.Values.SelectMany(l => l.Keys).Distinct()); + var allRuleIds = string.Join(';', allRulesById.Keys); return $@" ", categoryOpt, customTagOpt, - allRulesByAssembly); + sortedRulesById); var directory = Directory.CreateDirectory(analyzerRulesetsDir); var rulesetFilePath = Path.Combine(directory.FullName, rulesetFileName); @@ -475,12 +471,12 @@ static void endRuleset(StringBuilder result) result.AppendLine(""); } - static void startRulesSectionForAssembly(StringBuilder result, string assemblyName) + void startRulesSection(StringBuilder result) { - result.AppendLine($@" "); + result.AppendLine($@" "); } - static void endRulesSectionForAssembly(StringBuilder result) + static void endRulesSection(StringBuilder result) { result.AppendLine(" "); } @@ -504,21 +500,21 @@ static string getSeverityString(DiagnosticSeverity? severityOpt) RulesetKind rulesetKind, string categoryOpt, string customTagOpt, - SortedList> allRulesByAssembly) + SortedList sortedRulesById) { var text = GetRulesetOrEditorconfigText( rulesetKind, startEditorconfig, endEditorconfig, - startRulesSectionForAssembly, - endRulesSectionForAssembly, + startRulesSection, + endRulesSection, addRuleEntry, getSeverityString, commentStart: "# ", commentEnd: string.Empty, categoryOpt, customTagOpt, - allRulesByAssembly); + sortedRulesById); var directory = Directory.CreateDirectory(Path.Combine(analyzerEditorconfigsDir, editorconfigFolder)); var editorconfigFilePath = Path.Combine(directory.FullName, ".editorconfig"); @@ -533,24 +529,20 @@ void startEditorconfig(StringBuilder result) result.AppendLine($@"# {editorconfigTitle}"); result.AppendLine($@"# Description: {editorconfigDescription}"); result.AppendLine(); - result.AppendLine(@"# To learn more about .editorconfig see https://editorconfig.org/"); - result.AppendLine(@"root = true"); - result.AppendLine(); result.AppendLine(@"# Code files"); result.AppendLine(@"[*.{cs,vb}]"); + result.AppendLine(); } static void endEditorconfig(StringBuilder _) { } - static void startRulesSectionForAssembly(StringBuilder result, string assemblyName) + static void startRulesSection(StringBuilder result) { - result.AppendLine(); - result.AppendLine($@"# Rules in '{assemblyName}.dll'"); } - static void endRulesSectionForAssembly(StringBuilder _) + static void endRulesSection(StringBuilder result) { } @@ -581,15 +573,15 @@ static string getSeverityString(DiagnosticSeverity? severityOpt) RulesetKind rulesetKind, Action startRulesetOrEditorconfig, Action endRulesetOrEditorconfig, - Action startRulesSectionForAssembly, - Action endRulesSectionForAssembly, + Action startRulesSection, + Action endRulesSection, Action addRuleEntry, Func getSeverityString, string commentStart, string commentEnd, string categoryOpt, string customTagOpt, - SortedList> allRulesByAssembly) + SortedList sortedRulesById) { Debug.Assert(categoryOpt == null || customTagOpt == null); Debug.Assert((categoryOpt != null) == (rulesetKind == RulesetKind.CategoryDefault || rulesetKind == RulesetKind.CategoryEnabled)); @@ -615,27 +607,21 @@ static string getSeverityString(DiagnosticSeverity? severityOpt) void addRules(bool categoryPass, bool customTagPass) { - foreach (var rulesByAssembly in allRulesByAssembly) + if (!sortedRulesById.Any(r => !shouldSkipRule(r.Value))) { - string assemblyName = rulesByAssembly.Key; - SortedList sortedRulesById = rulesByAssembly.Value; - - if (!sortedRulesById.Any(r => !shouldSkipRule(r.Value))) - { - // Bail out if we don't have any rule to be added for this assembly. - continue; - } - - startRulesSectionForAssembly(result, assemblyName); + // Bail out if we don't have any rule to be added for this assembly. + return; + } - foreach (var rule in sortedRulesById) - { - addRule(rule.Value); - } + startRulesSection(result); - endRulesSectionForAssembly(result); + foreach (var rule in sortedRulesById) + { + addRule(rule.Value); } + endRulesSection(result); + return; void addRule(DiagnosticDescriptor rule) From ed00ceaff0a7e0d09607fd78c32e501a4a8958f7 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Tue, 1 Oct 2019 06:10:38 -0700 Subject: [PATCH 3/4] Unused parameters --- src/GenerateAnalyzerRulesets/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GenerateAnalyzerRulesets/Program.cs b/src/GenerateAnalyzerRulesets/Program.cs index 15d7850028..952ef28ad6 100644 --- a/src/GenerateAnalyzerRulesets/Program.cs +++ b/src/GenerateAnalyzerRulesets/Program.cs @@ -538,11 +538,11 @@ static void endEditorconfig(StringBuilder _) { } - static void startRulesSection(StringBuilder result) + static void startRulesSection(StringBuilder _) { } - static void endRulesSection(StringBuilder result) + static void endRulesSection(StringBuilder _) { } From 17bf198ca7f59cd252b1882935ea3795472a18c2 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Tue, 1 Oct 2019 07:09:15 -0700 Subject: [PATCH 4/4] Move comments to preceding line --- src/GenerateAnalyzerRulesets/Program.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/GenerateAnalyzerRulesets/Program.cs b/src/GenerateAnalyzerRulesets/Program.cs index 952ef28ad6..3ecf619705 100644 --- a/src/GenerateAnalyzerRulesets/Program.cs +++ b/src/GenerateAnalyzerRulesets/Program.cs @@ -481,9 +481,10 @@ static void endRulesSection(StringBuilder result) result.AppendLine(" "); } - static void addRuleEntry(StringBuilder result, DiagnosticDescriptor rule, string severity, string spacingForTitle) + static void addRuleEntry(StringBuilder result, DiagnosticDescriptor rule, string severity) { - result.AppendLine($@" {spacingForTitle} "); + var spacing = new string(' ', count: 15 - severity.Length); + result.AppendLine($@" {spacing} "); } static string getSeverityString(DiagnosticSeverity? severityOpt) @@ -546,9 +547,11 @@ static void endRulesSection(StringBuilder _) { } - static void addRuleEntry(StringBuilder result, DiagnosticDescriptor rule, string severity, string spacingForTitle) + static void addRuleEntry(StringBuilder result, DiagnosticDescriptor rule, string severity) { - result.AppendLine($@"dotnet_diagnostic.{rule.Id}.severity = {severity} {spacingForTitle} # {rule.Title}"); + result.AppendLine(); + result.AppendLine($"# {rule.Id}: {rule.Title}"); + result.AppendLine($@"dotnet_diagnostic.{rule.Id}.severity = {severity}"); } static string getSeverityString(DiagnosticSeverity? severityOpt) @@ -575,7 +578,7 @@ static string getSeverityString(DiagnosticSeverity? severityOpt) Action endRulesetOrEditorconfig, Action startRulesSection, Action endRulesSection, - Action addRuleEntry, + Action addRuleEntry, Func getSeverityString, string commentStart, string commentEnd, @@ -598,6 +601,8 @@ static string getSeverityString(DiagnosticSeverity? severityOpt) result.AppendLine($@"{commentStart}{categoryOpt ?? customTagOpt} Rules{commentEnd}"); addRules(categoryPass: categoryOpt != null, customTagPass: customTagOpt != null); result.AppendLine(); + result.AppendLine(); + result.AppendLine(); result.AppendLine($@"{commentStart}Other Rules{commentEnd}"); addRules(categoryPass: false, customTagPass: false); } @@ -632,8 +637,7 @@ void addRule(DiagnosticDescriptor rule) } string severity = getRuleAction(rule); - var spacing = new string(' ', count: 15 - severity.Length); - addRuleEntry(result, rule, severity, spacing); + addRuleEntry(result, rule, severity); } bool shouldSkipRule(DiagnosticDescriptor rule)