From 70cb73fe5b7aba18d080145e6a6309e3a508d0a3 Mon Sep 17 00:00:00 2001 From: "Marais van Zyl (Verizon Connect)" <161749428+marais-vzc@users.noreply.github.com> Date: Thu, 1 May 2025 11:05:23 +0100 Subject: [PATCH 01/11] Add support for GitLab output format in AnalyzeCommand (#1633) * Add support for GitLab output format in AnalyzeCommand This commit adds support for the GitLab output format in the AnalyzeCommand class. Signed-off-by: Marais van Zyl * Refactor GitLab issue classes to improve readability and structure Signed-off-by: Marais van Zyl * Fix namespace usage in DiagnosticGitLabJsonSerializer for consistency Signed-off-by: Marais van Zyl * Rename variable for clarity in DiagnosticGitLabJsonSerializer * Updated help text for the output-format option * Make use of expressions for readability * Validate output format input * Improve language for description * Introduce a private static readonly field `_jsonSerializerSettings` in the `DiagnosticGitLabJsonSerializer` class to centralize JSON serialization configuration. * Correctly name validation method * Update ChangeLog for version 4.14.0 and add GitLab analyzer reports support * Update ChangeLog.md * Update src/CommandLine/Json/DiagnosticGitLabJsonSerializer.cs * Update src/CommandLine/Json/DiagnosticGitLabJsonSerializer.cs --------- Signed-off-by: Marais van Zyl Co-authored-by: Josef Pihrt --- ChangeLog.md | 4 + src/CommandLine/Commands/AnalyzeCommand.cs | 12 ++- src/CommandLine/GitLab/GitLabIssue.cs | 15 ++++ src/CommandLine/GitLab/GitLabIssueLocation.cs | 7 ++ src/CommandLine/GitLab/GitLabLocationLines.cs | 6 ++ .../Json/DiagnosticGitLabJsonSerializer.cs | 87 +++++++++++++++++++ .../Options/AnalyzeCommandLineOptions.cs | 12 ++- src/CommandLine/ParseHelpers.cs | 23 +++++ src/CommandLine/Program.cs | 3 + 9 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 src/CommandLine/GitLab/GitLabIssue.cs create mode 100644 src/CommandLine/GitLab/GitLabIssueLocation.cs create mode 100644 src/CommandLine/GitLab/GitLabLocationLines.cs create mode 100644 src/CommandLine/Json/DiagnosticGitLabJsonSerializer.cs diff --git a/ChangeLog.md b/ChangeLog.md index c1ebf472f8..4f2f97bbd2 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- [CLI] Add support for GitLab analyzer reports ([PR](https://github.com/dotnet/roslynator/pull/1633)) + ## [4.13.1] - 2025-02-23 ### Added diff --git a/src/CommandLine/Commands/AnalyzeCommand.cs b/src/CommandLine/Commands/AnalyzeCommand.cs index 63e5f5f98b..ba5d22e824 100644 --- a/src/CommandLine/Commands/AnalyzeCommand.cs +++ b/src/CommandLine/Commands/AnalyzeCommand.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Roslynator.CommandLine.Json; using Roslynator.CommandLine.Xml; using Roslynator.Diagnostics; using static Roslynator.Logger; @@ -96,8 +97,15 @@ protected override void ProcessResults(IList results) && analysisResults.Any(f => f.Diagnostics.Any() || f.CompilerDiagnostics.Any())) { CultureInfo culture = (Options.Culture is not null) ? CultureInfo.GetCultureInfo(Options.Culture) : null; - - DiagnosticXmlSerializer.Serialize(analysisResults, Options.Output, culture); + if (!string.IsNullOrWhiteSpace(Options.OutputFormat) && Options.OutputFormat.Equals("gitlab", StringComparison.CurrentCultureIgnoreCase)) + { + DiagnosticGitLabJsonSerializer.Serialize(analysisResults, Options.Output, culture); + } + else + { + // Default output format is xml + DiagnosticXmlSerializer.Serialize(analysisResults, Options.Output, culture); + } } } diff --git a/src/CommandLine/GitLab/GitLabIssue.cs b/src/CommandLine/GitLab/GitLabIssue.cs new file mode 100644 index 0000000000..3e14817c52 --- /dev/null +++ b/src/CommandLine/GitLab/GitLabIssue.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; + +namespace Roslynator.CommandLine.GitLab; + +internal sealed class GitLabIssue +{ + public string Type { get; set; } + public string Fingerprint { get; set; } + [JsonProperty("check_name")] + public string CheckName { get; set; } + public string Description { get; set; } + public string Severity { get; set; } + public GitLabIssueLocation Location { get; set; } + public string[] Categories { get; set; } +} diff --git a/src/CommandLine/GitLab/GitLabIssueLocation.cs b/src/CommandLine/GitLab/GitLabIssueLocation.cs new file mode 100644 index 0000000000..462d18e546 --- /dev/null +++ b/src/CommandLine/GitLab/GitLabIssueLocation.cs @@ -0,0 +1,7 @@ +namespace Roslynator.CommandLine.GitLab; + +internal sealed class GitLabIssueLocation +{ + public string Path { get; set; } + public GitLabLocationLines Lines { get; set; } +} diff --git a/src/CommandLine/GitLab/GitLabLocationLines.cs b/src/CommandLine/GitLab/GitLabLocationLines.cs new file mode 100644 index 0000000000..f05726d4d0 --- /dev/null +++ b/src/CommandLine/GitLab/GitLabLocationLines.cs @@ -0,0 +1,6 @@ +namespace Roslynator.CommandLine.GitLab; + +internal sealed class GitLabLocationLines +{ + public int Begin { get; set; } +} diff --git a/src/CommandLine/Json/DiagnosticGitLabJsonSerializer.cs b/src/CommandLine/Json/DiagnosticGitLabJsonSerializer.cs new file mode 100644 index 0000000000..d9cad7086d --- /dev/null +++ b/src/CommandLine/Json/DiagnosticGitLabJsonSerializer.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using Microsoft.CodeAnalysis; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Roslynator.CommandLine.GitLab; +using Roslynator.Diagnostics; + +namespace Roslynator.CommandLine.Json; + +internal static class DiagnosticGitLabJsonSerializer +{ + private static readonly JsonSerializerSettings _jsonSerializerSettings = new() + { + Formatting = Newtonsoft.Json.Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new DefaultContractResolver() + { + NamingStrategy = new CamelCaseNamingStrategy() + }, + }; + + public static void Serialize( + IEnumerable results, + string filePath, + IFormatProvider formatProvider = null) + { + IEnumerable diagnostics = results.SelectMany(f => f.CompilerDiagnostics.Concat(f.Diagnostics)); + + var reportItems = new List(); + foreach (DiagnosticInfo diagnostic in diagnostics) + { + GitLabIssueLocation location = null; + if (diagnostic.LineSpan.IsValid) + { + location = new GitLabIssueLocation() + { + Path = diagnostic.LineSpan.Path, + Lines = new GitLabLocationLines() + { + Begin = diagnostic.LineSpan.StartLinePosition.Line + }, + }; + } + + var severity = "minor"; + severity = diagnostic.Severity switch + { + DiagnosticSeverity.Warning => "major", + DiagnosticSeverity.Error => "critical", + _ => "minor", + }; + + string issueFingerPrint = $"{diagnostic.Descriptor.Id}-{diagnostic.Severity}-{location?.Path}-{location?.Lines.Begin}"; + byte[] source = Encoding.UTF8.GetBytes(issueFingerPrint); + byte[] hashBytes; +#if NETFRAMEWORK + using (var sha256 = SHA256.Create()) + hashBytes = sha256.ComputeHash(source); +#else + hashBytes = SHA256.HashData(source); +#endif + issueFingerPrint = BitConverter.ToString(hashBytes) + .Replace("-", "") + .ToLowerInvariant(); + + reportItems.Add(new GitLabIssue() + { + Type = "issue", + Fingerprint = issueFingerPrint, + CheckName = diagnostic.Descriptor.Id, + Description = diagnostic.Descriptor.Title.ToString(formatProvider), + Severity = severity, + Location = location, + Categories = new string[] { diagnostic.Descriptor.Category }, + }); + } + + string report = JsonConvert.SerializeObject(reportItems, _jsonSerializerSettings); + + File.WriteAllText(filePath, report, Encoding.UTF8); + } +} diff --git a/src/CommandLine/Options/AnalyzeCommandLineOptions.cs b/src/CommandLine/Options/AnalyzeCommandLineOptions.cs index 639be3395d..039d418d66 100644 --- a/src/CommandLine/Options/AnalyzeCommandLineOptions.cs +++ b/src/CommandLine/Options/AnalyzeCommandLineOptions.cs @@ -20,10 +20,15 @@ public class AnalyzeCommandLineOptions : AbstractAnalyzeCommandLineOptions [Option( shortName: OptionShortNames.Output, longName: "output", - HelpText = "Defines path to file that will store reported diagnostics in XML format.", + HelpText = "Defines path to file that will store reported diagnostics. The format of the file is determined by the --output-format option, with the default being xml.", MetaValue = "")] public string Output { get; set; } + [Option( + longName: "output-format", + HelpText = "Defines the file format of the report written to file. Supported options are: gitlab and xml, with xml the default if no option is provided.")] + public string OutputFormat { get; set; } + [Option( longName: "report-not-configurable", HelpText = "Indicates whether diagnostics with 'NotConfigurable' tag should be reported.")] @@ -33,4 +38,9 @@ public class AnalyzeCommandLineOptions : AbstractAnalyzeCommandLineOptions longName: "report-suppressed-diagnostics", HelpText = "Indicates whether suppressed diagnostics should be reported.")] public bool ReportSuppressedDiagnostics { get; set; } + + internal bool ValidateOutputFormat() + { + return ParseHelpers.TryParseOutputFormat(OutputFormat); + } } diff --git a/src/CommandLine/ParseHelpers.cs b/src/CommandLine/ParseHelpers.cs index 1e12f48a3d..2ac8656073 100644 --- a/src/CommandLine/ParseHelpers.cs +++ b/src/CommandLine/ParseHelpers.cs @@ -349,4 +349,27 @@ public static bool TryReadAllText( return false; } } + + public static bool TryParseOutputFormat(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + // Default to XML if no value is provided + return true; + } + + bool valid = value.Trim().ToLowerInvariant() switch + { + "gitlab" => true, + "xml" => true, + _ => false + }; + + if (!valid) + { + WriteLine($"Unknown output format '{value}'.", Verbosity.Quiet); + } + + return valid; + } } diff --git a/src/CommandLine/Program.cs b/src/CommandLine/Program.cs index 3f36d77f38..1cb094677a 100644 --- a/src/CommandLine/Program.cs +++ b/src/CommandLine/Program.cs @@ -338,6 +338,9 @@ private static async Task AnalyzeAsync(AnalyzeCommandLineOptions options) if (!TryParsePaths(options.Paths, out ImmutableArray paths)) return ExitCodes.Error; + if (!options.ValidateOutputFormat()) + return ExitCodes.Error; + var command = new AnalyzeCommand(options, severityLevel, projectFilter, CreateFileSystemFilter(options)); CommandStatus status = await command.ExecuteAsync(paths, options.MSBuildPath, options.Properties); From dfa69c7bc873e873cc435f229b9e294d847817e5 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 26 Jul 2025 12:20:49 +0200 Subject: [PATCH 02/11] Fix analyzer RCS1264 (#1666) --- ChangeLog.md | 4 ++ .../Analysis/UseVarOrExplicitTypeAnalyzer.cs | 2 +- src/CSharp/CSharp/CSharpTypeAnalysis.cs | 10 +++-- .../RCS1264UseVarOrExplicitTypeTests.cs | 38 +++++++++++++++++-- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 4f2f97bbd2..943f53254a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [CLI] Add support for GitLab analyzer reports ([PR](https://github.com/dotnet/roslynator/pull/1633)) +### Fixed + +- Fix analyzer [RCS1264](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1264) ([PR](https://github.com/dotnet/roslynator/pull/1666)) + ## [4.13.1] - 2025-02-23 ### Added diff --git a/src/Analyzers/CSharp/Analysis/UseVarOrExplicitTypeAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseVarOrExplicitTypeAnalyzer.cs index 8f457167ce..ccb2b509ed 100644 --- a/src/Analyzers/CSharp/Analysis/UseVarOrExplicitTypeAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UseVarOrExplicitTypeAnalyzer.cs @@ -43,7 +43,7 @@ private static void AnalyzeVariableDeclaration(SyntaxNodeAnalysisContext context if (style == TypeStyle.Implicit) { - if (CSharpTypeAnalysis.IsExplicitThatCanBeImplicit(variableDeclaration, context.SemanticModel, TypeAppearance.NotObvious, context.CancellationToken)) + if (CSharpTypeAnalysis.IsExplicitThatCanBeImplicit(variableDeclaration, context.SemanticModel, context.CancellationToken)) ReportExplicitToImplicit(context, variableDeclaration.Type); } else if (style == TypeStyle.Explicit) diff --git a/src/CSharp/CSharp/CSharpTypeAnalysis.cs b/src/CSharp/CSharp/CSharpTypeAnalysis.cs index e5e23408c3..1fc6475a01 100644 --- a/src/CSharp/CSharp/CSharpTypeAnalysis.cs +++ b/src/CSharp/CSharp/CSharpTypeAnalysis.cs @@ -250,11 +250,13 @@ public static bool IsExplicitThatCanBeImplicit( return IsTypeObvious(expression, typeSymbol, includeNullability: true, semanticModel, cancellationToken); case TypeAppearance.NotObvious: return !IsTypeObvious(expression, typeSymbol, includeNullability: true, semanticModel, cancellationToken); + case TypeAppearance.None: + return GetEqualityComparer(includeNullability: true).Equals( + typeSymbol, + semanticModel.GetTypeSymbol(expression, cancellationToken)); + default: + throw new InvalidOperationException($"Unknow enum value '{typeAppearance}'"); } - - Debug.Assert(typeAppearance == TypeAppearance.None, typeAppearance.ToString()); - - return true; } public static bool IsTypeObvious(ExpressionSyntax expression, SemanticModel semanticModel, CancellationToken cancellationToken) diff --git a/src/Tests/Analyzers.Tests/RCS1264UseVarOrExplicitTypeTests.cs b/src/Tests/Analyzers.Tests/RCS1264UseVarOrExplicitTypeTests.cs index 9750a6d1ad..cdf635d7eb 100644 --- a/src/Tests/Analyzers.Tests/RCS1264UseVarOrExplicitTypeTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1264UseVarOrExplicitTypeTests.cs @@ -38,6 +38,28 @@ object M() ", options: Options.AddConfigOption(ConfigOptionKeys.UseVar, ConfigOptionValues.UseVar_Always)); } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseVarOrExplicitType)] + public async Task Test_ObjectCreationExpression() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M() + { + [|string[]|] arr = new string[1]; + } +} +", @" +class C +{ + void M() + { + var arr = new string[1]; + } +} +", options: Options.AddConfigOption(ConfigOptionKeys.UseVar, ConfigOptionValues.UseVar_Always)); + } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseVarOrExplicitType)] public async Task Test_TupleExpression() { @@ -177,16 +199,26 @@ class C } [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseVarOrExplicitType)] - public async Task TestNoDiagnostic_ParseMethod() + public async Task TestDiagnostic_ParseMethod() { - await VerifyNoDiagnosticAsync(@" + await VerifyDiagnosticAndFixAsync(@" +using System; + +class C +{ + void M() + { + [|TimeSpan|] timeSpan = TimeSpan.Parse(null); + } +} +", @" using System; class C { void M() { - TimeSpan timeSpan = TimeSpan.Parse(null); + var timeSpan = TimeSpan.Parse(null); } } ", options: Options.AddConfigOption(ConfigOptionKeys.UseVar, ConfigOptionValues.UseVar_Always)); From 6a24655cd932ab3f236cf3782073510c05faa6c3 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 26 Jul 2025 12:41:40 +0200 Subject: [PATCH 03/11] Fix analyzer RCS1229 (#1667) --- ChangeLog.md | 1 + .../UnnecessaryUnsafeContextAnalyzer.cs | 34 +------- .../CSharp/Analysis/UseAsyncAwaitAnalyzer.cs | 3 +- src/CSharp/CSharp/CSharpUtility.cs | 78 +++++++------------ .../RCS1229UseAsyncAwaitTests.cs | 28 +++++++ 5 files changed, 62 insertions(+), 82 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 943f53254a..f787f4218c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fix analyzer [RCS1264](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1264) ([PR](https://github.com/dotnet/roslynator/pull/1666)) +- Fix analyzer [RCS1229](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1229) ([PR](https://github.com/dotnet/roslynator/pull/1667)) ## [4.13.1] - 2025-02-23 diff --git a/src/Analyzers/CSharp/Analysis/UnnecessaryUnsafeContextAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UnnecessaryUnsafeContextAnalyzer.cs index 7a7a178aad..2677141b23 100644 --- a/src/Analyzers/CSharp/Analysis/UnnecessaryUnsafeContextAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UnnecessaryUnsafeContextAnalyzer.cs @@ -59,7 +59,7 @@ private static void AnalyzeUnsafeStatement(SyntaxNodeAnalysisContext context) if (!unsafeStatement.Block.Statements.Any()) return; - if (!AncestorContainsUnsafeModifier(unsafeStatement.Parent)) + if (!CSharpUtility.IsInUnsafeContext(unsafeStatement.Parent)) return; DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.UnnecessaryUnsafeContext, unsafeStatement.UnsafeKeyword); @@ -166,39 +166,9 @@ private static void AnalyzeMemberDeclaration( if (index == -1) return; - if (!AncestorContainsUnsafeModifier(node.Parent)) + if (!CSharpUtility.IsInUnsafeContext(node.Parent)) return; DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.UnnecessaryUnsafeContext, modifiers[index]); } - - private static bool AncestorContainsUnsafeModifier(SyntaxNode node) - { - while (node is not null) - { - switch (node) - { - case UnsafeStatementSyntax: - return true; - case MemberDeclarationSyntax memberDeclarationSyntax: - { - if (memberDeclarationSyntax.Modifiers.Contains(SyntaxKind.UnsafeKeyword)) - return true; - - break; - } - case LocalFunctionStatementSyntax localFunctionStatement: - { - if (localFunctionStatement.Modifiers.Contains(SyntaxKind.UnsafeKeyword)) - return true; - - break; - } - } - - node = node.Parent; - } - - return false; - } } diff --git a/src/Analyzers/CSharp/Analysis/UseAsyncAwaitAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UseAsyncAwaitAnalyzer.cs index 2edaac64d6..14cf301009 100644 --- a/src/Analyzers/CSharp/Analysis/UseAsyncAwaitAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UseAsyncAwaitAnalyzer.cs @@ -162,7 +162,8 @@ private static bool IsFixable(BlockSyntax body, SyntaxNodeAnalysisContext contex walker.VisitBlock(body); - return walker.ReturnStatement is not null; + return walker.ReturnStatement is not null + && !CSharpUtility.IsInUnsafeContext(body); } finally { diff --git a/src/CSharp/CSharp/CSharpUtility.cs b/src/CSharp/CSharp/CSharpUtility.cs index 6954116817..d6627c6381 100644 --- a/src/CSharp/CSharp/CSharpUtility.cs +++ b/src/CSharp/CSharp/CSharpUtility.cs @@ -10,55 +10,35 @@ namespace Roslynator.CSharp; internal static class CSharpUtility { - //public static bool CanConvertToCollectionExpression(ImplicitObjectCreationExpressionSyntax implicitObjectCreation, SemanticModel semanticModel, CancellationToken cancellationToken) - //{ - // return !implicitObjectCreation.WalkUpParentheses().IsParentKind(SyntaxKind.Argument) - // && implicitObjectCreation.ArgumentList?.Arguments.Any() != true - // && SyntaxUtility.CanConvertToCollectionExpression(implicitObjectCreation, semanticModel, cancellationToken); - //} - - //public static bool CanConvertToCollectionExpression(ObjectCreationExpressionSyntax objectCreation, SemanticModel semanticModel, CancellationToken cancellationToken) - //{ - // return !objectCreation.WalkUpParentheses().IsParentKind(SyntaxKind.Argument) - // && objectCreation.ArgumentList?.Arguments.Any() != true - // && SyntaxUtility.CanConvertToCollectionExpression(objectCreation, semanticModel, cancellationToken); - //} - - //public static bool CanConvertToCollectionExpression(ArrayCreationExpressionSyntax arrayCreation, SemanticModel semanticModel, CancellationToken cancellationToken) - //{ - // return CanConvertToCollectionExpression(arrayCreation) - // && SyntaxUtility.CanConvertToCollectionExpression(arrayCreation, semanticModel, cancellationToken); - //} - - //public static bool CanConvertToCollectionExpression(ImplicitArrayCreationExpressionSyntax implicitArrayCreation, SemanticModel semanticModel, CancellationToken cancellationToken) - //{ - // CSharpTypeAnalysis.IsTypeObvious(implicitArrayCreation, semanticModel, cancellationToken); - // return CanConvertToCollectionExpression(implicitArrayCreation) - // && SyntaxUtility.CanConvertToCollectionExpression(implicitArrayCreation, semanticModel, cancellationToken); - //} - - //private static bool CanConvertToCollectionExpression(ExpressionSyntax expression) - //{ - // expression = expression.WalkUpParentheses(); - - // if (expression.IsParentKind( - // SyntaxKind.Argument, - // SyntaxKind.ForEachStatement, - // SyntaxKind.ForEachVariableStatement)) - // { - // return false; - // } - - // if (expression.Parent.IsKind(SyntaxKind.EqualsValueClause) - // && expression.Parent.Parent.IsKind(SyntaxKind.VariableDeclarator) - // && expression.Parent.Parent.Parent is VariableDeclarationSyntax variableDeclaration - // && variableDeclaration.Type.IsVar) - // { - // return false; - // } - - // return true; - //} + public static bool IsInUnsafeContext(SyntaxNode? node) + { + while (node is not null) + { + switch (node) + { + case UnsafeStatementSyntax: + return true; + case MemberDeclarationSyntax memberDeclarationSyntax: + { + if (memberDeclarationSyntax.Modifiers.Contains(SyntaxKind.UnsafeKeyword)) + return true; + + break; + } + case LocalFunctionStatementSyntax localFunctionStatement: + { + if (localFunctionStatement.Modifiers.Contains(SyntaxKind.UnsafeKeyword)) + return true; + + break; + } + } + + node = node.Parent; + } + + return false; + } public static bool IsNullableReferenceType( TypeSyntax type, diff --git a/src/Tests/Analyzers.Tests/RCS1229UseAsyncAwaitTests.cs b/src/Tests/Analyzers.Tests/RCS1229UseAsyncAwaitTests.cs index 3e433094e1..2fc21c22a5 100644 --- a/src/Tests/Analyzers.Tests/RCS1229UseAsyncAwaitTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1229UseAsyncAwaitTests.cs @@ -831,4 +831,32 @@ ValueTask M() } "); } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseAsyncAwait)] + public async Task TestNoDiagnostic_Unsafe() + { + await VerifyNoDiagnosticAsync(@" +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +internal abstract unsafe class UnsafeStream() : Stream +{ + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + try + { + var tcs = new TaskCompletionSource(); + + return new ValueTask(tcs.Task); + } + catch + { + throw; + } + } +} +", options: Options.WithAllowUnsafe(true)); + } } From 646f4290f7b2d2b3f67a60267f81c4d98a8f39ca Mon Sep 17 00:00:00 2001 From: Andrii Ihnatiuk Date: Sat, 26 Jul 2025 14:37:31 +0300 Subject: [PATCH 04/11] Fix analyzer RCS1250: Don't suggest a collection expression for the Dictionary (#1652) * Fix analyzer RCS1250: Don't suggest a collection expression for the Dictionary * Update ChangeLog.md * Update ChangeLog.md --------- Co-authored-by: Josef Pihrt --- ChangeLog.md | 1 + ...mplicitOrExplicitObjectCreationAnalysis.cs | 5 +++- ...seImplicitOrExplicitObjectCreationTests.cs | 27 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index f787f4218c..eaf9396daa 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix analyzer [RCS1264](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1264) ([PR](https://github.com/dotnet/roslynator/pull/1666)) - Fix analyzer [RCS1229](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1229) ([PR](https://github.com/dotnet/roslynator/pull/1667)) +- Fix analyzer [RCS1250](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1250) ([PR](https://github.com/dotnet/roslynator/pull/1652) by @aihnatiuk) ## [4.13.1] - 2025-02-23 diff --git a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitObjectCreationAnalysis.cs b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitObjectCreationAnalysis.cs index 80b90bee1c..392d4fa5aa 100644 --- a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitObjectCreationAnalysis.cs +++ b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitObjectCreationAnalysis.cs @@ -59,7 +59,10 @@ protected override bool UseCollectionExpressionFromImplicit(ref SyntaxNodeAnalys var implicitObjectCreation = (ImplicitObjectCreationExpressionSyntax)context.Node; return implicitObjectCreation.ArgumentList?.Arguments.Any() != true - && implicitObjectCreation.Initializer?.Expressions.Any(f => f.IsKind(SyntaxKind.SimpleAssignmentExpression)) != true + && implicitObjectCreation.Initializer?.Expressions + .Any(f => f.IsKind( + SyntaxKind.SimpleAssignmentExpression, + SyntaxKind.ComplexElementInitializerExpression)) != true && UseCollectionExpression(ref context); } diff --git a/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs b/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs index 26bf4f3ab8..61f082be47 100644 --- a/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs @@ -1903,4 +1903,31 @@ void M2() """, options: Options.AddConfigOption(ConfigOptionKeys.ObjectCreationTypeStyle, ConfigOptionValues.ObjectCreationTypeStyle_Implicit) .AddConfigOption(ConfigOptionKeys.UseCollectionExpression, false)); } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseImplicitOrExplicitObjectCreation)] + public async Task TestNoDiagnostic_ComplexElementInitializer() + { + await VerifyNoDiagnosticAsync(""" +using System; +using System.Collections; +using System.Collections.Generic; +class C : IEnumerable> +{ + public IEnumerator> GetEnumerator() => throw new System.NotImplementedException(); + + IEnumerator IEnumerable.GetEnumerator() => throw new System.NotImplementedException(); + + public void Add(int arg1, int arg2) + { + } + + static C M() + { + C c = new() { { 1, 2 } }; + return c; + } +} +""", options: Options.AddConfigOption(ConfigOptionKeys.ObjectCreationTypeStyle, ConfigOptionValues.ObjectCreationTypeStyle_Implicit) + .AddConfigOption(ConfigOptionKeys.UseCollectionExpression, true)); + } } From 525f3265ff0872b5af19d7fc7e444f063aa2b840 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 26 Jul 2025 13:48:06 +0200 Subject: [PATCH 05/11] Fix analyzer RCS1260 (#1668) --- ChangeLog.md | 1 + ...AddOrRemoveTrailingCommaCodeFixProvider.cs | 30 ++++++++++++ .../AddOrRemoveTrailingCommaAnalyzer.cs | 48 +++++++++++++++++++ .../RCS1260AddOrRemoveTrailingCommaTests.cs | 44 +++++++++++++++++ 4 files changed, 123 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index eaf9396daa..28312b640d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix analyzer [RCS1264](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1264) ([PR](https://github.com/dotnet/roslynator/pull/1666)) - Fix analyzer [RCS1229](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1229) ([PR](https://github.com/dotnet/roslynator/pull/1667)) - Fix analyzer [RCS1250](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1250) ([PR](https://github.com/dotnet/roslynator/pull/1652) by @aihnatiuk) +- Fix analyzer [RCS1260](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1260) ([PR](https://github.com/dotnet/roslynator/pull/1668)) ## [4.13.1] - 2025-02-23 diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs index 0275c60d6d..5ac46f2749 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs @@ -36,6 +36,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) SyntaxKind.ObjectInitializerExpression, SyntaxKind.CollectionInitializerExpression, SyntaxKind.EnumDeclaration, +#if ROSLYN_4_7 + SyntaxKind.CollectionExpression, +#endif SyntaxKind.AnonymousObjectCreationExpression))) { return; @@ -69,6 +72,33 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix(codeAction, diagnostic); } } +#if ROSLYN_4_7 + if (node is CollectionExpressionSyntax collectionExpression) + { + SeparatedSyntaxList elements = collectionExpression.Elements; + + int count = elements.Count; + + if (count == elements.SeparatorCount) + { + CodeAction codeAction = CodeAction.Create( + "Remove comma", + ct => RemoveTrailingComma(document, elements.GetSeparator(count - 1), ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + else + { + CodeAction codeAction = CodeAction.Create( + "Add comma", + ct => AddTrailingComma(document, elements.Last(), ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + } +#endif else if (node is AnonymousObjectCreationExpressionSyntax objectCreation) { SeparatedSyntaxList initializers = objectCreation.Initializers; diff --git a/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs b/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs index ed84c787cd..9ab9fc3e9a 100644 --- a/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs @@ -32,6 +32,9 @@ public override void Initialize(AnalysisContext context) context.RegisterSyntaxNodeAction(f => AnalyzeEnumDeclaration(f), SyntaxKind.EnumDeclaration); context.RegisterSyntaxNodeAction(f => AnalyzeAnonymousObjectCreationExpression(f), SyntaxKind.AnonymousObjectCreationExpression); +#if ROSLYN_4_7 + context.RegisterSyntaxNodeAction(f => AnalyzeCollectionExpression(f), SyntaxKind.CollectionExpression); +#endif context.RegisterSyntaxNodeAction( f => AnalyzeInitializerExpression(f), @@ -169,6 +172,51 @@ private static void AnalyzeAnonymousObjectCreationExpression(SyntaxNodeAnalysisC } } +#if ROSLYN_4_7 + private static void AnalyzeCollectionExpression(SyntaxNodeAnalysisContext context) + { + TrailingCommaStyle style = context.GetTrailingCommaStyle(); + + if (style == TrailingCommaStyle.None) + return; + + var objectCreation = (CollectionExpressionSyntax)context.Node; + + SeparatedSyntaxList elements = objectCreation.Elements; + + if (!elements.Any()) + return; + + int count = elements.Count; + int separatorCount = elements.SeparatorCount; + + if (count == separatorCount) + { + if (style == TrailingCommaStyle.Omit) + { + ReportRemove(context, elements.GetSeparator(count - 1)); + } + else if (style == TrailingCommaStyle.OmitWhenSingleLine + && elements.IsSingleLine(cancellationToken: context.CancellationToken)) + { + ReportRemove(context, elements.GetSeparator(count - 1)); + } + } + else if (separatorCount == count - 1) + { + if (style == TrailingCommaStyle.Include) + { + ReportAdd(context, elements.Last()); + } + else if (style == TrailingCommaStyle.OmitWhenSingleLine + && !elements.IsSingleLine(cancellationToken: context.CancellationToken)) + { + ReportAdd(context, elements.Last()); + } + } + } +#endif + private static void ReportAdd(SyntaxNodeAnalysisContext context, SyntaxNode lastNode) { DiagnosticHelpers.ReportDiagnostic( diff --git a/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs b/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs index fc527cb0c6..f479191234 100644 --- a/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs @@ -329,4 +329,48 @@ void M() } """, options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_OmitWhenSingleLine)); } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_CollectionExpression_Omit() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M() + { + int[] x = [1, 2, 3[|,|]]; + } +} +""", """ +class C +{ + void M() + { + int[] x = [1, 2, 3]; + } +} +""", options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_Omit)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_CollectionExpression_OmitWhenSingleLine() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M() + { + int[] x = [1, 2, 3[|,|]]; + } +} +""", """ + class C + { + void M() + { + int[] x = [1, 2, 3]; + } + } + """, options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_OmitWhenSingleLine)); + } } From febce84748279a433b0a7beba6df491a09e22149 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 26 Jul 2025 14:39:25 +0200 Subject: [PATCH 06/11] Fix analyzer RCS1105 (#1669) --- ChangeLog.md | 1 + .../UnnecessaryInterpolationAnalyzer.cs | 8 ++++++ .../RCS1105UnnecessaryInterpolationTests.cs | 26 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 src/Tests/Analyzers.Tests/RCS1105UnnecessaryInterpolationTests.cs diff --git a/ChangeLog.md b/ChangeLog.md index 28312b640d..5514f91dad 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix analyzer [RCS1229](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1229) ([PR](https://github.com/dotnet/roslynator/pull/1667)) - Fix analyzer [RCS1250](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1250) ([PR](https://github.com/dotnet/roslynator/pull/1652) by @aihnatiuk) - Fix analyzer [RCS1260](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1260) ([PR](https://github.com/dotnet/roslynator/pull/1668)) +- Fix analyzer [RCS1105](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1105) ([PR](https://github.com/dotnet/roslynator/pull/1669)) ## [4.13.1] - 2025-02-23 diff --git a/src/Analyzers/CSharp/Analysis/UnnecessaryInterpolationAnalyzer.cs b/src/Analyzers/CSharp/Analysis/UnnecessaryInterpolationAnalyzer.cs index 5a95077382..49bde468f3 100644 --- a/src/Analyzers/CSharp/Analysis/UnnecessaryInterpolationAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/UnnecessaryInterpolationAnalyzer.cs @@ -53,6 +53,14 @@ private static void AnalyzeInterpolation(SyntaxNodeAnalysisContext context) if (interpolatedString.StringStartToken.ValueText.Contains("@") != stringLiteralInfo.IsVerbatim) return; +#if ROSLYN_4_2 + if (interpolatedString.StringStartToken.IsKind(SyntaxKind.InterpolatedSingleLineRawStringStartToken, SyntaxKind.InterpolatedMultiLineRawStringStartToken) + && stringLiteralInfo.ContainsEscapeSequence) + { + return; + } +#endif + DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.UnnecessaryInterpolation, interpolation); } } diff --git a/src/Tests/Analyzers.Tests/RCS1105UnnecessaryInterpolationTests.cs b/src/Tests/Analyzers.Tests/RCS1105UnnecessaryInterpolationTests.cs new file mode 100644 index 0000000000..b12ecb4bd6 --- /dev/null +++ b/src/Tests/Analyzers.Tests/RCS1105UnnecessaryInterpolationTests.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Roslynator.CSharp.CodeFixes; +using Roslynator.Testing.CSharp; +using Xunit; + +namespace Roslynator.CSharp.Analysis.Tests; + +public class RCS1105UnnecessaryInterpolationTests : AbstractCSharpDiagnosticVerifier +{ + public override DiagnosticDescriptor Descriptor { get; } = DiagnosticRules.UnnecessaryInterpolation; + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseVarOrExplicitType)] + public async Task TestNoDiagnostic_RawStringLiteral() + { + await VerifyNoDiagnosticAsync("""" +class C +{ + void M() + { + var s = $"""a {"\n"} b"""; + } +} +""""); + } +} From 8335340eef4174e9ecdc31609af6f9b96c15178e Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 26 Jul 2025 14:44:30 +0200 Subject: [PATCH 07/11] Fix NRE in InvocationExpressionAnalyzer (#1670) --- .../CSharp/Analysis/OptimizeLinqMethodCallAnalysis.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Analyzers/CSharp/Analysis/OptimizeLinqMethodCallAnalysis.cs b/src/Analyzers/CSharp/Analysis/OptimizeLinqMethodCallAnalysis.cs index 57a39f1e1d..cc3a565bac 100644 --- a/src/Analyzers/CSharp/Analysis/OptimizeLinqMethodCallAnalysis.cs +++ b/src/Analyzers/CSharp/Analysis/OptimizeLinqMethodCallAnalysis.cs @@ -687,8 +687,7 @@ public static void AnalyzeOrderByIdentity(SyntaxNodeAnalysisContext context, in InvocationExpressionSyntax invocationExpression = invocationInfo.InvocationExpression; IMethodSymbol orderMethod = context.SemanticModel - .GetSymbolInfo(invocationExpression) - .Symbol + .GetSymbol(invocationExpression)? .ContainingType .FindMember(method => method.Name == "Order" && method.Parameters.Length is 1); From 2986b7f3f7a6d7b40101cf671afb394bd02c94f2 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 26 Jul 2025 15:29:59 +0200 Subject: [PATCH 08/11] Disable RCS1036 by default (#1671) --- ChangeLog.md | 5 +++++ src/Analyzers.xml | 2 +- src/Common/DiagnosticRules.Generated.cs | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 5514f91dad..8250f35a9a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -19,6 +19,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix analyzer [RCS1260](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1260) ([PR](https://github.com/dotnet/roslynator/pull/1668)) - Fix analyzer [RCS1105](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1105) ([PR](https://github.com/dotnet/roslynator/pull/1669)) +### Changed + +- Disable analyzer [RCS1036](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1036) by default ([PR](https://github.com/dotnet/roslynator/pull/1671)) + - Use analyzer [RCS0063](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0063) instead + ## [4.13.1] - 2025-02-23 ### Added diff --git a/src/Analyzers.xml b/src/Analyzers.xml index 072001d577..6eaca3df9b 100644 --- a/src/Analyzers.xml +++ b/src/Analyzers.xml @@ -2530,7 +2530,7 @@ if (f) Obsolete Use RCS0063 instead Info - true + false Date: Sat, 26 Jul 2025 15:43:53 +0200 Subject: [PATCH 09/11] Improve analyzer RCS1260 (#1672) --- ChangeLog.md | 1 + ...AddOrRemoveTrailingCommaCodeFixProvider.cs | 76 ++++++++- .../AddOrRemoveTrailingCommaAnalyzer.cs | 88 +++++++++++ .../RCS1260AddOrRemoveTrailingCommaTests.cs | 146 ++++++++++++++++++ 4 files changed, 303 insertions(+), 8 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 8250f35a9a..d182a2d73b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix analyzer [RCS1250](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1250) ([PR](https://github.com/dotnet/roslynator/pull/1652) by @aihnatiuk) - Fix analyzer [RCS1260](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1260) ([PR](https://github.com/dotnet/roslynator/pull/1668)) - Fix analyzer [RCS1105](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1105) ([PR](https://github.com/dotnet/roslynator/pull/1669)) +- Fix analyzer [RCS1260](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1260) ([PR](https://github.com/dotnet/roslynator/pull/1672)) ### Changed diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs index 5ac46f2749..96f1ef8609 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/AddOrRemoveTrailingCommaCodeFixProvider.cs @@ -31,15 +31,25 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) root, context.Span, out SyntaxNode node, - predicate: f => f.IsKind( - SyntaxKind.ArrayInitializerExpression, - SyntaxKind.ObjectInitializerExpression, - SyntaxKind.CollectionInitializerExpression, - SyntaxKind.EnumDeclaration, + predicate: f => + { + switch (f.Kind()) + { + case SyntaxKind.ArrayInitializerExpression: + case SyntaxKind.ObjectInitializerExpression: + case SyntaxKind.CollectionInitializerExpression: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.AnonymousObjectCreationExpression: + case SyntaxKind.SwitchExpression: + case SyntaxKind.PropertyPatternClause: #if ROSLYN_4_7 - SyntaxKind.CollectionExpression, + case SyntaxKind.CollectionExpression: #endif - SyntaxKind.AnonymousObjectCreationExpression))) + return true; + default: + return false; + } + })) { return; } @@ -72,8 +82,58 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix(codeAction, diagnostic); } } + else if (node is SwitchExpressionSyntax switchExpression) + { + SeparatedSyntaxList arms = switchExpression.Arms; + + int count = arms.Count; + + if (count == arms.SeparatorCount) + { + CodeAction codeAction = CodeAction.Create( + "Remove comma", + ct => RemoveTrailingComma(document, arms.GetSeparator(count - 1), ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + else + { + CodeAction codeAction = CodeAction.Create( + "Add comma", + ct => AddTrailingComma(document, arms.Last(), ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + } + else if (node is PropertyPatternClauseSyntax propertyPatternClause) + { + SeparatedSyntaxList subpatterns = propertyPatternClause.Subpatterns; + + int count = subpatterns.Count; + + if (count == subpatterns.SeparatorCount) + { + CodeAction codeAction = CodeAction.Create( + "Remove comma", + ct => RemoveTrailingComma(document, subpatterns.GetSeparator(count - 1), ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + else + { + CodeAction codeAction = CodeAction.Create( + "Add comma", + ct => AddTrailingComma(document, subpatterns.Last(), ct), + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + } #if ROSLYN_4_7 - if (node is CollectionExpressionSyntax collectionExpression) + else if (node is CollectionExpressionSyntax collectionExpression) { SeparatedSyntaxList elements = collectionExpression.Elements; diff --git a/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs b/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs index 9ab9fc3e9a..0e39cece2e 100644 --- a/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/AddOrRemoveTrailingCommaAnalyzer.cs @@ -32,6 +32,8 @@ public override void Initialize(AnalysisContext context) context.RegisterSyntaxNodeAction(f => AnalyzeEnumDeclaration(f), SyntaxKind.EnumDeclaration); context.RegisterSyntaxNodeAction(f => AnalyzeAnonymousObjectCreationExpression(f), SyntaxKind.AnonymousObjectCreationExpression); + context.RegisterSyntaxNodeAction(f => AnalyzeSwitchExpression(f), SyntaxKind.SwitchExpression); + context.RegisterSyntaxNodeAction(f => AnalyzePropertyPatternClause(f), SyntaxKind.PropertyPatternClause); #if ROSLYN_4_7 context.RegisterSyntaxNodeAction(f => AnalyzeCollectionExpression(f), SyntaxKind.CollectionExpression); #endif @@ -172,6 +174,92 @@ private static void AnalyzeAnonymousObjectCreationExpression(SyntaxNodeAnalysisC } } + private static void AnalyzeSwitchExpression(SyntaxNodeAnalysisContext context) + { + TrailingCommaStyle style = context.GetTrailingCommaStyle(); + + if (style == TrailingCommaStyle.None) + return; + + var objectCreation = (SwitchExpressionSyntax)context.Node; + + SeparatedSyntaxList arms = objectCreation.Arms; + + if (!arms.Any()) + return; + + int count = arms.Count; + int separatorCount = arms.SeparatorCount; + + if (count == separatorCount) + { + if (style == TrailingCommaStyle.Omit) + { + ReportRemove(context, arms.GetSeparator(count - 1)); + } + else if (style == TrailingCommaStyle.OmitWhenSingleLine + && arms.IsSingleLine(cancellationToken: context.CancellationToken)) + { + ReportRemove(context, arms.GetSeparator(count - 1)); + } + } + else if (separatorCount == count - 1) + { + if (style == TrailingCommaStyle.Include) + { + ReportAdd(context, arms.Last()); + } + else if (style == TrailingCommaStyle.OmitWhenSingleLine + && !arms.IsSingleLine(cancellationToken: context.CancellationToken)) + { + ReportAdd(context, arms.Last()); + } + } + } + + private static void AnalyzePropertyPatternClause(SyntaxNodeAnalysisContext context) + { + TrailingCommaStyle style = context.GetTrailingCommaStyle(); + + if (style == TrailingCommaStyle.None) + return; + + var objectCreation = (PropertyPatternClauseSyntax)context.Node; + + SeparatedSyntaxList subpatterns = objectCreation.Subpatterns; + + if (!subpatterns.Any()) + return; + + int count = subpatterns.Count; + int separatorCount = subpatterns.SeparatorCount; + + if (count == separatorCount) + { + if (style == TrailingCommaStyle.Omit) + { + ReportRemove(context, subpatterns.GetSeparator(count - 1)); + } + else if (style == TrailingCommaStyle.OmitWhenSingleLine + && subpatterns.IsSingleLine(cancellationToken: context.CancellationToken)) + { + ReportRemove(context, subpatterns.GetSeparator(count - 1)); + } + } + else if (separatorCount == count - 1) + { + if (style == TrailingCommaStyle.Include) + { + ReportAdd(context, subpatterns.Last()); + } + else if (style == TrailingCommaStyle.OmitWhenSingleLine + && !subpatterns.IsSingleLine(cancellationToken: context.CancellationToken)) + { + ReportAdd(context, subpatterns.Last()); + } + } + } + #if ROSLYN_4_7 private static void AnalyzeCollectionExpression(SyntaxNodeAnalysisContext context) { diff --git a/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs b/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs index f479191234..3c23e93902 100644 --- a/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1260AddOrRemoveTrailingCommaTests.cs @@ -330,6 +330,28 @@ void M() """, options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_OmitWhenSingleLine)); } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_CollectionExpression_Include() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M() + { + int[] x = [1, 2, 3[||]]; + } +} +""", """ + class C + { + void M() + { + int[] x = [1, 2, 3,]; + } + } + """, options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_Include)); + } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] public async Task Test_CollectionExpression_Omit() { @@ -373,4 +395,128 @@ void M() } """, options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_OmitWhenSingleLine)); } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_SwitchExpression_Include() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M(int p) + { + var x = p switch + { + 1 => "foo", + _ => "bar"[||] + }; + } +} +""", """ +class C +{ + void M(int p) + { + var x = p switch + { + 1 => "foo", + _ => "bar", + }; + } +} +""", options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_Include)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_SwitchExpression_Omit() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + void M(int p) + { + var x = p switch + { + 1 => "foo", + _ => "bar"[|,|] + }; + } +} +""", """ +class C +{ + void M(int p) + { + var x = p switch + { + 1 => "foo", + _ => "bar" + }; + } +} +""", options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_Omit)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_PatternMatching_Include() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + public int P1 { get; set; } + public int P2 { get; set; } + + void M(C p) + { + if (p is { P1: 1, P2: 2[||] }) + { + } + } +} +""", """ +class C +{ + public int P1 { get; set; } + public int P2 { get; set; } + + void M(C p) + { + if (p is { P1: 1, P2: 2, }) + { + } + } +} +""", options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_Include)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.AddOrRemoveTrailingComma)] + public async Task Test_PatternMatching_Omit() + { + await VerifyDiagnosticAndFixAsync(""" +class C +{ + public int P1 { get; set; } + public int P2 { get; set; } + + void M(C p) + { + if (p is { P1: 1, P2: 2[|,|] }) + { + } + } +} +""", """ + class C + { + public int P1 { get; set; } + public int P2 { get; set; } + + void M(C p) + { + if (p is { P1: 1, P2: 2 }) + { + } + } + } + """, options: Options.AddConfigOption(ConfigOptionKeys.TrailingCommaStyle, ConfigOptionValues.TrailingCommaStyle_Omit)); + } } From 951a309c7269ef93f9cf359a5f7964665d8c1ae5 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 26 Jul 2025 18:40:21 +0200 Subject: [PATCH 10/11] Remove legacy config options (#1304) --- ChangeLog.md | 4 + src/Analyzers.xml | 483 ------------------ .../AnalyzerOptionIsObsoleteAnalyzer.cs | 105 ---- src/Common/CSharp/CodeStyle/BodyStyle.cs | 12 +- .../CSharp/Extensions/CodeStyleExtensions.cs | 57 --- src/Common/LegacyConfigOptions.Generated.cs | 23 - .../AnalyzerOptionIsObsoleteAnalyzer.cs | 87 ---- .../ROS002AnalyzerOptionIsObsoleteTests.cs | 42 -- .../CodeGeneration/CSharp/CodeGenerator.cs | 30 -- .../Markdown/MarkdownGenerator.cs | 9 +- src/Tools/CodeGenerator/Program.cs | 5 +- src/Tools/Metadata/AnalyzerMetadata.cs | 6 - .../Metadata/LegacyAnalyzerOptionKind.cs | 12 - .../Metadata/LegacyAnalyzerOptionMetadata.cs | 69 --- src/Tools/Metadata/MetadataFile.cs | 59 --- src/Tools/MetadataGenerator/Program.cs | 5 +- 16 files changed, 11 insertions(+), 997 deletions(-) delete mode 100644 src/Analyzers/CSharp/Analysis/AnalyzerOptionIsObsoleteAnalyzer.cs delete mode 100644 src/Formatting.Analyzers/CSharp/AnalyzerOptionIsObsoleteAnalyzer.cs delete mode 100644 src/Tests/Analyzers.Tests/ROS002AnalyzerOptionIsObsoleteTests.cs delete mode 100644 src/Tools/Metadata/LegacyAnalyzerOptionKind.cs delete mode 100644 src/Tools/Metadata/LegacyAnalyzerOptionMetadata.cs diff --git a/ChangeLog.md b/ChangeLog.md index d182a2d73b..cacbdd852b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Disable analyzer [RCS1036](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1036) by default ([PR](https://github.com/dotnet/roslynator/pull/1671)) - Use analyzer [RCS0063](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0063) instead +### Removed + +- Remove legacy config options ([PR](https://github.com/dotnet/roslynator/pull/1304)) + ## [4.13.1] - 2025-02-23 ### Added diff --git a/src/Analyzers.xml b/src/Analyzers.xml index 6eaca3df9b..9f23544cd0 100644 --- a/src/Analyzers.xml +++ b/src/Analyzers.xml @@ -328,30 +328,6 @@ enum Bar - - - RCS0012 @@ -445,33 +421,6 @@ namespace N - - - RCS0016 @@ -698,29 +647,6 @@ namespace N - - - RCS0028 @@ -742,25 +668,6 @@ namespace N - - - RCS0029 @@ -832,23 +739,6 @@ namespace N - - - RCS0033 @@ -1231,28 +1121,6 @@ while (x);]]> - - - RCS0052 @@ -1272,23 +1140,6 @@ while (x);]]> - - - RCS0053 @@ -1954,35 +1805,6 @@ foreach (var item in items) RCS1015 @@ -2034,73 +1856,6 @@ foreach (var item in items) RCS1017 @@ -2155,32 +1910,6 @@ foreach (var item in items) - - - RCS1019 @@ -2561,42 +2290,6 @@ if (f) - - - RCS1037 @@ -2767,19 +2460,6 @@ catch (Exception ex) - - - RCS1046 @@ -2887,21 +2567,6 @@ if (!f) - - - RCS1051 @@ -2919,22 +2584,6 @@ if (!f) - - - RCS1052 @@ -3637,21 +3286,6 @@ object x = queue.Peek();]]> - - - RCS1079 @@ -3836,28 +3470,6 @@ public string Foo - - - RCS1091 @@ -3949,27 +3561,6 @@ namespace Foo - - - RCS1097 @@ -4162,23 +3753,6 @@ else - - - RCS1105 @@ -6102,21 +5676,6 @@ int i = (x != null) ? x.Value.GetHashCode() : 0;]]> - - - RCS1208 @@ -6291,15 +5850,6 @@ x = "";]]> RCS1214 @@ -7349,20 +6899,6 @@ public class C - - - RCS1247 @@ -7430,25 +6966,6 @@ void M() - - - RCS1249 diff --git a/src/Analyzers/CSharp/Analysis/AnalyzerOptionIsObsoleteAnalyzer.cs b/src/Analyzers/CSharp/Analysis/AnalyzerOptionIsObsoleteAnalyzer.cs deleted file mode 100644 index 2085ff1e0f..0000000000 --- a/src/Analyzers/CSharp/Analysis/AnalyzerOptionIsObsoleteAnalyzer.cs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.IO; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Roslynator.CSharp.Analysis; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public sealed class AnalyzerOptionIsObsoleteAnalyzer : AbstractAnalyzerOptionIsObsoleteAnalyzer -{ - private static ImmutableArray _supportedDiagnostics; - - public override ImmutableArray SupportedDiagnostics - { - get - { - if (_supportedDiagnostics.IsDefault) - Immutable.InterlockedInitialize(ref _supportedDiagnostics, CommonDiagnosticRules.AnalyzerOptionIsObsolete); - - return _supportedDiagnostics; - } - } - - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - - context.RegisterCompilationStartAction(compilationContext => - { - var flags = Flags.None; - - CompilationOptions compilationOptions = compilationContext.Compilation.Options; - - compilationContext.RegisterSyntaxTreeAction(context => - { - // files generated by source generator have relative path - if (!Path.IsPathRooted(context.Tree.FilePath)) - return; - - AnalyzerConfigOptions options = context.GetConfigOptions(); - - Validate(ref context, compilationOptions, options, Flags.ConvertBitwiseOperationToHasFlagCall, ref flags, DiagnosticRules.UseHasFlagMethodOrBitwiseOperator, ConfigOptions.EnumHasFlagStyle, LegacyConfigOptions.ConvertBitwiseOperationToHasFlagCall, ConfigOptionValues.EnumHasFlagStyle_Method); - Validate(ref context, compilationOptions, options, Flags.ConvertExpressionBodyToBlockBody, ref flags, DiagnosticRules.UseBlockBodyOrExpressionBody, ConfigOptions.BodyStyle, LegacyConfigOptions.ConvertExpressionBodyToBlockBody, ConfigOptionValues.BodyStyle_Block); - Validate(ref context, compilationOptions, options, Flags.ConvertExpressionBodyToBlockBodyWhenDeclarationIsMultiLine, ref flags, DiagnosticRules.UseBlockBodyOrExpressionBody, ConfigOptions.BodyStyle, LegacyConfigOptions.ConvertExpressionBodyToBlockBodyWhenDeclarationIsMultiLine, "true"); - Validate(ref context, compilationOptions, options, Flags.ConvertExpressionBodyToBlockBodyWhenExpressionIsMultiLine, ref flags, DiagnosticRules.UseBlockBodyOrExpressionBody, ConfigOptions.BodyStyle, LegacyConfigOptions.ConvertExpressionBodyToBlockBodyWhenExpressionIsMultiLine, "true"); - Validate(ref context, compilationOptions, options, Flags.ConvertMethodGroupToAnonymousFunction, ref flags, DiagnosticRules.UseAnonymousFunctionOrMethodGroup, ConfigOptions.UseAnonymousFunctionOrMethodGroup, LegacyConfigOptions.ConvertMethodGroupToAnonymousFunction, ConfigOptionValues.UseAnonymousFunctionOrMethodGroup_AnonymousFunction); - Validate(ref context, compilationOptions, options, Flags.RemoveCallToConfigureAwait, ref flags, DiagnosticRules.ConfigureAwait, ConfigOptions.ConfigureAwait, LegacyConfigOptions.RemoveCallToConfigureAwait, "false"); - Validate(ref context, compilationOptions, options, Flags.RemoveAccessibilityModifiers, ref flags, DiagnosticRules.AddOrRemoveAccessibilityModifiers, ConfigOptions.AccessibilityModifiers, LegacyConfigOptions.RemoveAccessibilityModifiers, ConfigOptionValues.AccessibilityModifiers_Implicit); - Validate(ref context, compilationOptions, options, Flags.RemoveEmptyLineBetweenClosingBraceAndSwitchSection, ref flags, DiagnosticRules.Obsolete_RemoveUnnecessaryBlankLine, ConfigOptions.BlankLineBetweenClosingBraceAndSwitchSection, LegacyConfigOptions.RemoveEmptyLineBetweenClosingBraceAndSwitchSection, "false"); - Validate(ref context, compilationOptions, options, Flags.RemoveParenthesesFromConditionOfConditionalExpressionWhenExpressionIsSingleToken, ref flags, DiagnosticRules.AddOrRemoveParenthesesFromConditionInConditionalOperator, ConfigOptions.ConditionalOperatorConditionParenthesesStyle, LegacyConfigOptions.RemoveParenthesesFromConditionOfConditionalExpressionWhenExpressionIsSingleToken, ConfigOptionValues.ConditionalOperatorConditionParenthesesStyle_OmitWhenConditionIsSingleToken); - Validate(ref context, compilationOptions, options, Flags.RemoveParenthesesWhenCreatingNewObject, ref flags, DiagnosticRules.UseImplicitOrExplicitObjectCreation, ConfigOptions.ObjectCreationParenthesesStyle, LegacyConfigOptions.RemoveParenthesesWhenCreatingNewObject, ConfigOptionValues.ObjectCreationParenthesesStyle_Omit); -#pragma warning disable CS0618 // Type or member is obsolete - Validate(ref context, compilationOptions, options, Flags.SuppressUnityScriptMethods, ref flags, DiagnosticRules.RemoveUnusedMemberDeclaration, ConfigOptions.SuppressUnityScriptMethods, LegacyConfigOptions.SuppressUnityScriptMethods, "true"); -#pragma warning restore CS0618 // Type or member is obsolete - Validate(ref context, compilationOptions, options, Flags.UseComparisonInsteadPatternMatchingToCheckForNull, ref flags, DiagnosticRules.NormalizeNullCheck, ConfigOptions.NullCheckStyle, LegacyConfigOptions.UseComparisonInsteadPatternMatchingToCheckForNull, ConfigOptionValues.NullCheckStyle_EqualityOperator); - Validate(ref context, compilationOptions, options, Flags.UseImplicitlyTypedArray, ref flags, DiagnosticRules.UseExplicitlyOrImplicitlyTypedArray, ConfigOptions.ArrayCreationTypeStyle, LegacyConfigOptions.UseImplicitlyTypedArray, ConfigOptionValues.ArrayCreationTypeStyle_Implicit); - Validate(ref context, compilationOptions, options, Flags.UseImplicitlyTypedArrayWhenTypeIsObvious, ref flags, DiagnosticRules.UseExplicitlyOrImplicitlyTypedArray, ConfigOptions.ArrayCreationTypeStyle, LegacyConfigOptions.UseImplicitlyTypedArrayWhenTypeIsObvious, ConfigOptionValues.ArrayCreationTypeStyle_ImplicitWhenTypeIsObvious); - Validate(ref context, compilationOptions, options, Flags.UseStringEmptyInsteadOfEmptyStringLiteral, ref flags, DiagnosticRules.UseEmptyStringLiteralOrStringEmpty, ConfigOptions.EmptyStringStyle, LegacyConfigOptions.UseStringEmptyInsteadOfEmptyStringLiteral, ConfigOptionValues.EmptyStringStyle_Field); - }); - }); - } - - private static void Validate( - ref SyntaxTreeAnalysisContext context, - CompilationOptions compilationOptions, - AnalyzerConfigOptions configOptions, - Flags flag, - ref Flags flags, - DiagnosticDescriptor analyzer, - ConfigOptionDescriptor option, - LegacyConfigOptionDescriptor legacyOption, - string newValue) - { - if (!flags.HasFlag(flag) - && analyzer.IsEffective(context.Tree, compilationOptions, context.CancellationToken) - && TryReportObsoleteOption(context, configOptions, legacyOption, option, newValue)) - { - flags |= flag; - } - } - - [Flags] - private enum Flags - { - None, - ConvertBitwiseOperationToHasFlagCall, - ConvertExpressionBodyToBlockBody, - ConvertExpressionBodyToBlockBodyWhenDeclarationIsMultiLine, - ConvertExpressionBodyToBlockBodyWhenExpressionIsMultiLine, - ConvertMethodGroupToAnonymousFunction, - RemoveAccessibilityModifiers, - RemoveCallToConfigureAwait, - RemoveEmptyLineBetweenClosingBraceAndSwitchSection, - RemoveParenthesesFromConditionOfConditionalExpressionWhenExpressionIsSingleToken, - RemoveParenthesesWhenCreatingNewObject, - SuppressUnityScriptMethods, - UseComparisonInsteadPatternMatchingToCheckForNull, - UseImplicitlyTypedArray, - UseImplicitlyTypedArrayWhenTypeIsObvious, - UseStringEmptyInsteadOfEmptyStringLiteral, - } -} diff --git a/src/Common/CSharp/CodeStyle/BodyStyle.cs b/src/Common/CSharp/CodeStyle/BodyStyle.cs index 41e66d61b3..bb56d193bc 100644 --- a/src/Common/CSharp/CodeStyle/BodyStyle.cs +++ b/src/Common/CSharp/CodeStyle/BodyStyle.cs @@ -43,23 +43,17 @@ public static BodyStyle Create(SyntaxNodeAnalysisContext context) option = BodyStyleOption.Expression; } } - else if (configOptions.TryGetValueAsBool(LegacyConfigOptions.ConvertExpressionBodyToBlockBody, out bool useBlockBody)) - { - option = (useBlockBody) ? BodyStyleOption.Block : BodyStyleOption.Expression; - } bool? useBlockBodyWhenDeclarationIsMultiLine = null; - if (ConfigOptions.GetValueAsBool(configOptions, ConfigOptions.UseBlockBodyWhenDeclarationSpansOverMultipleLines) == true - || configOptions.IsEnabled(LegacyConfigOptions.ConvertExpressionBodyToBlockBodyWhenDeclarationIsMultiLine)) + if (ConfigOptions.GetValueAsBool(configOptions, ConfigOptions.UseBlockBodyWhenDeclarationSpansOverMultipleLines) == true) { useBlockBodyWhenDeclarationIsMultiLine = true; } bool? useBlockBodyWhenExpressionIsMultiline = null; - if (ConfigOptions.GetValueAsBool(configOptions, ConfigOptions.UseBlockBodyWhenExpressionSpansOverMultipleLines) == true - || configOptions.IsEnabled(LegacyConfigOptions.ConvertExpressionBodyToBlockBodyWhenExpressionIsMultiLine)) + if (ConfigOptions.GetValueAsBool(configOptions, ConfigOptions.UseBlockBodyWhenExpressionSpansOverMultipleLines) == true) { useBlockBodyWhenExpressionIsMultiline = true; } @@ -67,7 +61,7 @@ public static BodyStyle Create(SyntaxNodeAnalysisContext context) return new BodyStyle(option, useBlockBodyWhenDeclarationIsMultiLine, useBlockBodyWhenExpressionIsMultiline); } - internal enum BodyStyleOption + private enum BodyStyleOption { None, Block, diff --git a/src/Common/CSharp/Extensions/CodeStyleExtensions.cs b/src/Common/CSharp/Extensions/CodeStyleExtensions.cs index 48fafdc967..1abfbbab7b 100644 --- a/src/Common/CSharp/Extensions/CodeStyleExtensions.cs +++ b/src/Common/CSharp/Extensions/CodeStyleExtensions.cs @@ -102,9 +102,6 @@ public static NewLineStyle GetNewLineBeforeWhileInDoStatement(this SyntaxNodeAna if (ConfigOptions.TryGetValueAsBool(configOptions, ConfigOptions.NewLineBeforeWhileInDoStatement, out bool addNewLine)) return (addNewLine) ? NewLineStyle.Add : NewLineStyle.Remove; - if (configOptions.TryGetValueAsBool(LegacyConfigOptions.RemoveNewLineBetweenClosingBraceAndWhileKeyword, out bool removeLine)) - return (removeLine) ? NewLineStyle.Remove : NewLineStyle.Add; - return NewLineStyle.None; } @@ -113,9 +110,6 @@ public static NewLinePosition GetBinaryOperatorNewLinePosition(this AnalyzerConf if (TryGetNewLinePosition(configOptions, ConfigOptions.BinaryOperatorNewLine, out NewLinePosition newLinePosition)) return newLinePosition; - if (configOptions.IsEnabled(LegacyConfigOptions.AddNewLineAfterBinaryOperatorInsteadOfBeforeIt)) - return NewLinePosition.After; - return NewLinePosition.None; } @@ -124,9 +118,6 @@ public static NewLinePosition GetConditionalOperatorNewLinePosition(this Analyze if (TryGetNewLinePosition(configOptions, ConfigOptions.ConditionalOperatorNewLine, out NewLinePosition newLinePosition)) return newLinePosition; - if (configOptions.IsEnabled(LegacyConfigOptions.AddNewLineAfterConditionalOperatorInsteadOfBeforeIt)) - return NewLinePosition.After; - return NewLinePosition.None; } @@ -135,9 +126,6 @@ public static NewLinePosition GetArrowTokenNewLinePosition(this AnalyzerConfigOp if (TryGetNewLinePosition(configOptions, ConfigOptions.ArrowTokenNewLine, out NewLinePosition newLinePosition)) return newLinePosition; - if (configOptions.IsEnabled(LegacyConfigOptions.AddNewLineAfterExpressionBodyArrowInsteadOfBeforeIt)) - return NewLinePosition.After; - return NewLinePosition.None; } @@ -146,9 +134,6 @@ public static NewLinePosition GetEqualsTokenNewLinePosition(this AnalyzerConfigO if (TryGetNewLinePosition(configOptions, ConfigOptions.EqualsTokenNewLine, out NewLinePosition newLinePosition)) return newLinePosition; - if (configOptions.IsEnabled(LegacyConfigOptions.AddNewLineAfterEqualsSignInsteadOfBeforeIt)) - return NewLinePosition.After; - return NewLinePosition.None; } @@ -175,9 +160,6 @@ public static UsingDirectiveBlankLineStyle GetBlankLineBetweenUsingDirectives(th } } - if (ConfigOptions.TryGetValueAsBool(configOptions, LegacyConfigOptions.RemoveEmptyLineBetweenUsingDirectivesWithDifferentRootNamespace, out bool removeLine)) - return (removeLine) ? UsingDirectiveBlankLineStyle.Never : UsingDirectiveBlankLineStyle.SeparateGroups; - return UsingDirectiveBlankLineStyle.None; } @@ -226,9 +208,6 @@ public static BlankLineStyle GetBlankLineBetweenSingleLineAccessors(this SyntaxN if (ConfigOptions.TryGetValueAsBool(configOptions, ConfigOptions.BlankLineBetweenSingleLineAccessors, out bool addLine)) return (addLine) ? BlankLineStyle.Add : BlankLineStyle.Remove; - if (ConfigOptions.TryGetValueAsBool(configOptions, LegacyConfigOptions.RemoveEmptyLineBetweenSingleLineAccessors, out bool removeLine)) - return (removeLine) ? BlankLineStyle.Remove : BlankLineStyle.Add; - return BlankLineStyle.None; } @@ -248,9 +227,6 @@ public static BlankLineStyle GetBlankLineBetweenSingleLineAccessors(this SyntaxN } } - if (configOptions.IsEnabled(LegacyConfigOptions.ConvertMethodGroupToAnonymousFunction)) - return true; - return null; } @@ -270,9 +246,6 @@ public static EnumFlagOperationStyle GetEnumHasFlagStyle(this SyntaxNodeAnalysis } } - if (configOptions.IsEnabled(LegacyConfigOptions.ConvertBitwiseOperationToHasFlagCall)) - return EnumFlagOperationStyle.HasFlagMethod; - return EnumFlagOperationStyle.None; } @@ -285,9 +258,6 @@ public static ConfigureAwaitStyle GetConfigureAwaitStyle(this SyntaxNodeAnalysis return (value) ? ConfigureAwaitStyle.Include : ConfigureAwaitStyle.Omit; } - if (configOptions.IsEnabled(LegacyConfigOptions.RemoveCallToConfigureAwait)) - return ConfigureAwaitStyle.Omit; - return ConfigureAwaitStyle.None; } @@ -304,9 +274,6 @@ public static EmptyStringStyle GetEmptyStringStyle(this SyntaxNodeAnalysisContex return EmptyStringStyle.Literal; } - if (configOptions.IsEnabled(LegacyConfigOptions.UseStringEmptyInsteadOfEmptyStringLiteral)) - return EmptyStringStyle.Field; - return EmptyStringStyle.None; } @@ -323,9 +290,6 @@ public static NullCheckStyle GetNullCheckStyle(this SyntaxNodeAnalysisContext co return NullCheckStyle.PatternMatching; } - if (configOptions.IsEnabled(LegacyConfigOptions.UseComparisonInsteadPatternMatchingToCheckForNull)) - return NullCheckStyle.EqualityOperator; - return NullCheckStyle.None; } @@ -349,9 +313,6 @@ public static ConditionalExpressionParenthesesStyle GetConditionalExpressionPare } } - if (configOptions.IsEnabled(LegacyConfigOptions.RemoveParenthesesFromConditionOfConditionalExpressionWhenExpressionIsSingleToken)) - return ConditionalExpressionParenthesesStyle.OmitWhenConditionIsSingleToken; - return ConditionalExpressionParenthesesStyle.None; } @@ -371,9 +332,6 @@ public static ObjectCreationParenthesesStyle GetObjectCreationParenthesesStyle(t } } - if (context.IsEnabled(LegacyConfigOptions.RemoveParenthesesWhenCreatingNewObject)) - return ObjectCreationParenthesesStyle.Omit; - return ObjectCreationParenthesesStyle.None; } @@ -393,9 +351,6 @@ public static AccessibilityModifierStyle GetAccessModifiersStyle(this SyntaxNode } } - if (ConfigOptions.TryGetValueAsBool(configOptions, LegacyConfigOptions.RemoveAccessibilityModifiers, out bool useImplicit)) - return (useImplicit) ? AccessibilityModifierStyle.Implicit : AccessibilityModifierStyle.Explicit; - return AccessibilityModifierStyle.None; } @@ -498,12 +453,6 @@ public static TypeStyle GetArrayCreationTypeStyle(this SyntaxNodeAnalysisContext } } - if (context.IsEnabled(LegacyConfigOptions.UseImplicitlyTypedArrayWhenTypeIsObvious)) - return TypeStyle.ImplicitWhenTypeIsObvious; - - if (context.IsEnabled(LegacyConfigOptions.UseImplicitlyTypedArray)) - return TypeStyle.Implicit; - return TypeStyle.None; } @@ -582,9 +531,6 @@ public static BodyStyle GetBodyStyle(this SyntaxNodeAnalysisContext context) return value; } - if (context.TryGetOptionAsBool(LegacyConfigOptions.RemoveEmptyLineBetweenClosingBraceAndSwitchSection, out value)) - return !value; - return null; } @@ -608,9 +554,6 @@ public static BlankLineStyle GetBlankLineAfterFileScopedNamespaceDeclaration(thi return value; #pragma warning restore CS0618 // Type or member is obsolete - if (context.TryGetOptionAsBool(LegacyConfigOptions.SuppressUnityScriptMethods, out value)) - return value; - return null; } diff --git a/src/Common/LegacyConfigOptions.Generated.cs b/src/Common/LegacyConfigOptions.Generated.cs index bf02dbbffb..24a1ddcb54 100644 --- a/src/Common/LegacyConfigOptions.Generated.cs +++ b/src/Common/LegacyConfigOptions.Generated.cs @@ -6,28 +6,5 @@ namespace Roslynator { public static partial class LegacyConfigOptions { - public static readonly LegacyConfigOptionDescriptor AddNewLineAfterBinaryOperatorInsteadOfBeforeIt = new(key: "roslynator.RCS0027.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor AddNewLineAfterConditionalOperatorInsteadOfBeforeIt = new(key: "roslynator.RCS0028.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor AddNewLineAfterEqualsSignInsteadOfBeforeIt = new(key: "roslynator.RCS0052.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor AddNewLineAfterExpressionBodyArrowInsteadOfBeforeIt = new(key: "roslynator.RCS0032.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor ConvertBitwiseOperationToHasFlagCall = new(key: "roslynator.RCS1096.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor ConvertExpressionBodyToBlockBody = new(key: "roslynator.RCS1016.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor ConvertExpressionBodyToBlockBodyWhenDeclarationIsMultiLine = new(key: "roslynator.RCS1016.use_block_body_when_declaration_is_multiline", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor ConvertExpressionBodyToBlockBodyWhenExpressionIsMultiLine = new(key: "roslynator.RCS1016.use_block_body_when_expression_is_multiline", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor ConvertMethodGroupToAnonymousFunction = new(key: "roslynator.RCS1207.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor DoNotRenamePrivateStaticFieldToCamelCaseWithUnderscore = new(key: "roslynator.RCS1045.suppress_when_field_is_static", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor RemoveAccessibilityModifiers = new(key: "roslynator.RCS1018.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor RemoveCallToConfigureAwait = new(key: "roslynator.RCS1090.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor RemoveEmptyLineBetweenClosingBraceAndSwitchSection = new(key: "roslynator.RCS1036.remove_empty_line_between_closing_brace_and_switch_section", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor RemoveEmptyLineBetweenSingleLineAccessors = new(key: "roslynator.RCS0011.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor RemoveEmptyLineBetweenUsingDirectivesWithDifferentRootNamespace = new(key: "roslynator.RCS0015.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor RemoveNewLineBetweenClosingBraceAndWhileKeyword = new(key: "roslynator.RCS0051.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor RemoveParenthesesFromConditionOfConditionalExpressionWhenExpressionIsSingleToken = new(key: "roslynator.RCS1051.do_not_parenthesize_single_token", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor RemoveParenthesesWhenCreatingNewObject = new(key: "roslynator.RCS1050.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor SuppressUnityScriptMethods = new(key: "roslynator.RCS1213.suppress_unity_script_methods", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor UseComparisonInsteadPatternMatchingToCheckForNull = new(key: "roslynator.RCS1248.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor UseImplicitlyTypedArray = new(key: "roslynator.RCS1014.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor UseImplicitlyTypedArrayWhenTypeIsObvious = new(key: "roslynator.RCS1014.use_implicit_type_when_obvious", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); - public static readonly LegacyConfigOptionDescriptor UseStringEmptyInsteadOfEmptyStringLiteral = new(key: "roslynator.RCS1078.invert", defaultValue: null, defaultValuePlaceholder: "true|false", description: ""); } } \ No newline at end of file diff --git a/src/Formatting.Analyzers/CSharp/AnalyzerOptionIsObsoleteAnalyzer.cs b/src/Formatting.Analyzers/CSharp/AnalyzerOptionIsObsoleteAnalyzer.cs deleted file mode 100644 index d1f5573dd9..0000000000 --- a/src/Formatting.Analyzers/CSharp/AnalyzerOptionIsObsoleteAnalyzer.cs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.IO; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Roslynator.Formatting.CSharp; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -internal sealed class AnalyzerOptionIsObsoleteAnalyzer : AbstractAnalyzerOptionIsObsoleteAnalyzer -{ - private static ImmutableArray _supportedDiagnostics; - - public override ImmutableArray SupportedDiagnostics - { - get - { - if (_supportedDiagnostics.IsDefault) - Immutable.InterlockedInitialize(ref _supportedDiagnostics, CommonDiagnosticRules.AnalyzerOptionIsObsolete); - - return _supportedDiagnostics; - } - } - - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - - context.RegisterCompilationStartAction(compilationContext => - { - var flags = Flags.None; - - CompilationOptions compilationOptions = compilationContext.Compilation.Options; - - compilationContext.RegisterSyntaxTreeAction(context => - { - // files generated by source generator have relative path - if (!Path.IsPathRooted(context.Tree.FilePath)) - return; - - AnalyzerConfigOptions configOptions = context.GetConfigOptions(); - - Validate(ref context, compilationOptions, configOptions, Flags.AddOrRemoveNewLineBeforeWhileInDoStatement, ref flags, DiagnosticRules.AddOrRemoveNewLineBeforeWhileInDoStatement, ConfigOptions.NewLineBeforeWhileInDoStatement, LegacyConfigOptions.RemoveNewLineBetweenClosingBraceAndWhileKeyword, "false"); - Validate(ref context, compilationOptions, configOptions, Flags.BlankLineBetweenSingleLineAccessors, ref flags, DiagnosticRules.BlankLineBetweenSingleLineAccessors, ConfigOptions.BlankLineBetweenSingleLineAccessors, LegacyConfigOptions.RemoveEmptyLineBetweenSingleLineAccessors, "false"); - Validate(ref context, compilationOptions, configOptions, Flags.BlankLineBetweenUsingDirectives, ref flags, DiagnosticRules.BlankLineBetweenUsingDirectives, ConfigOptions.BlankLineBetweenUsingDirectives, LegacyConfigOptions.RemoveEmptyLineBetweenUsingDirectivesWithDifferentRootNamespace, ConfigOptionValues.BlankLineBetweenUsingDirectives_Never); - Validate(ref context, compilationOptions, configOptions, Flags.PlaceNewLineAfterOrBeforeArrowToken, ref flags, DiagnosticRules.PlaceNewLineAfterOrBeforeArrowToken, ConfigOptions.ArrowTokenNewLine, LegacyConfigOptions.AddNewLineAfterExpressionBodyArrowInsteadOfBeforeIt, "after"); - Validate(ref context, compilationOptions, configOptions, Flags.PlaceNewLineAfterOrBeforeConditionalOperator, ref flags, DiagnosticRules.PlaceNewLineAfterOrBeforeConditionalOperator, ConfigOptions.ConditionalOperatorNewLine, LegacyConfigOptions.AddNewLineAfterConditionalOperatorInsteadOfBeforeIt, "after"); - Validate(ref context, compilationOptions, configOptions, Flags.PlaceNewLineAfterOrBeforeBinaryOperator, ref flags, DiagnosticRules.PlaceNewLineAfterOrBeforeBinaryOperator, ConfigOptions.BinaryOperatorNewLine, LegacyConfigOptions.AddNewLineAfterBinaryOperatorInsteadOfBeforeIt, "after"); - Validate(ref context, compilationOptions, configOptions, Flags.PlaceNewLineAfterOrBeforeEqualsToken, ref flags, DiagnosticRules.PlaceNewLineAfterOrBeforeEqualsToken, ConfigOptions.EqualsTokenNewLine, LegacyConfigOptions.AddNewLineAfterEqualsSignInsteadOfBeforeIt, "after"); - }); - }); - } - - private static void Validate( - ref SyntaxTreeAnalysisContext context, - CompilationOptions compilationOptions, - AnalyzerConfigOptions configOptions, - Flags flag, - ref Flags flags, - DiagnosticDescriptor analyzer, - ConfigOptionDescriptor option, - LegacyConfigOptionDescriptor legacyOption, - string newValue) - { - if (!flags.HasFlag(flag) - && analyzer.IsEffective(context.Tree, compilationOptions, context.CancellationToken) - && TryReportObsoleteOption(context, configOptions, legacyOption, option, newValue)) - { - flags |= flag; - } - } - - [Flags] - private enum Flags - { - None, - AddOrRemoveNewLineBeforeWhileInDoStatement, - BlankLineBetweenSingleLineAccessors, - BlankLineBetweenUsingDirectives, - PlaceNewLineAfterOrBeforeArrowToken, - PlaceNewLineAfterOrBeforeBinaryOperator, - PlaceNewLineAfterOrBeforeConditionalOperator, - PlaceNewLineAfterOrBeforeEqualsToken, - } -} diff --git a/src/Tests/Analyzers.Tests/ROS002AnalyzerOptionIsObsoleteTests.cs b/src/Tests/Analyzers.Tests/ROS002AnalyzerOptionIsObsoleteTests.cs deleted file mode 100644 index 722dbfe99c..0000000000 --- a/src/Tests/Analyzers.Tests/ROS002AnalyzerOptionIsObsoleteTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Roslynator.Testing; -using Roslynator.Testing.CSharp; -using Xunit; - -namespace Roslynator.CSharp.Analysis.Tests; - -public class ROS0002AnalyzerOptionIsObsoleteTests : AbstractCSharpDiagnosticVerifier -{ - public override DiagnosticDescriptor Descriptor { get; } = CommonDiagnosticRules.AnalyzerOptionIsObsolete; - - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveBracesFromIfElse)] - public async Task Test() - { - await VerifyDiagnosticAsync(@" -class C -{ - void M() - { - } -} -", options: Options.AddConfigOption("dotnet_diagnostic.RCS1207.severity", "suggestion") - .AddConfigOption(LegacyConfigOptions.ConvertMethodGroupToAnonymousFunction.Key, "true")); - } - - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveBracesFromIfElse)] - public async Task Test2() - { - await VerifyDiagnosticAsync(@" -class C -{ - void M() - { - } -} -", options: Options.EnableDiagnostic(DiagnosticRules.UseAnonymousFunctionOrMethodGroup) - .AddConfigOption(LegacyConfigOptions.ConvertMethodGroupToAnonymousFunction.Key, "true")); - } -} diff --git a/src/Tools/CodeGeneration/CSharp/CodeGenerator.cs b/src/Tools/CodeGeneration/CSharp/CodeGenerator.cs index a8f3859aab..03ca75baa0 100644 --- a/src/Tools/CodeGeneration/CSharp/CodeGenerator.cs +++ b/src/Tools/CodeGeneration/CSharp/CodeGenerator.cs @@ -77,36 +77,6 @@ public static CompilationUnitSyntax GenerateConfigOptions(IEnumerable analyzers) - { - return CompilationUnit( - UsingDirectives(), - NamespaceDeclaration( - "Roslynator", - ClassDeclaration( - Modifiers.Public_Static_Partial(), - "LegacyConfigOptions", - analyzers - .SelectMany(f => f.LegacyOptions) - .Where(f => f.Status != AnalyzerStatus.Disabled) - .OrderBy(f => f.Identifier, StringComparer.InvariantCulture) - .Select(f => - { - return FieldDeclaration( - Modifiers.Public_Static_ReadOnly(), - IdentifierName("LegacyConfigOptionDescriptor"), - f.Identifier, - ImplicitObjectCreationExpression( - ArgumentList( - Argument(NameColon("key"), StringLiteralExpression($"roslynator.{f.ParentId}.{f.OptionKey}")), - Argument(NameColon("defaultValue"), NullLiteralExpression()), - Argument(NameColon("defaultValuePlaceholder"), StringLiteralExpression("true|false")), - Argument(NameColon("description"), StringLiteralExpression(""))), - default(InitializerExpressionSyntax))); - }) - .ToSyntaxList()))); - } - public static CompilationUnitSyntax GenerateConfigOptionKeys(IEnumerable options) { CompilationUnitSyntax compilationUnit = CompilationUnit( diff --git a/src/Tools/CodeGeneration/Markdown/MarkdownGenerator.cs b/src/Tools/CodeGeneration/Markdown/MarkdownGenerator.cs index 4cf346d3ef..d2a5188905 100644 --- a/src/Tools/CodeGeneration/Markdown/MarkdownGenerator.cs +++ b/src/Tools/CodeGeneration/Markdown/MarkdownGenerator.cs @@ -158,18 +158,13 @@ public static string CreateAnalyzerMarkdown(AnalyzerMetadata analyzer, Immutable static IEnumerable CreateSamples(AnalyzerMetadata analyzer) { - List samples = analyzer.Samples; - LegacyAnalyzerOptionKind kind = analyzer.Kind; + IReadOnlyList samples = analyzer.Samples; if (samples.Count > 0) { yield return Heading2("Examples"); - string beforeHeading = (kind == LegacyAnalyzerOptionKind.Disable) - ? "code" - : "diagnostic"; - - foreach (MElement item in MarkdownGenerator.CreateSamples(samples, beforeHeading, "fix")) + foreach (MElement item in MarkdownGenerator.CreateSamples(samples, "diagnostic", "fix")) yield return item; } } diff --git a/src/Tools/CodeGenerator/Program.cs b/src/Tools/CodeGenerator/Program.cs index 577f574115..099f849cc4 100644 --- a/src/Tools/CodeGenerator/Program.cs +++ b/src/Tools/CodeGenerator/Program.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Text; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslynator.CodeGeneration; using Roslynator.CodeGeneration.CSharp; @@ -79,10 +80,6 @@ private static void Main(string[] args) Roslynator.CodeGeneration.CSharp.CodeGenerator.GenerateConfigOptions(options, metadata.Analyzers), normalizeWhitespace: false); - WriteCompilationUnit( - "Common/LegacyConfigOptions.Generated.cs", - Roslynator.CodeGeneration.CSharp.CodeGenerator.GenerateLegacyConfigOptions(metadata.Analyzers)); - WriteCompilationUnit( "Common/ConfigOptionKeys.Generated.cs", Roslynator.CodeGeneration.CSharp.CodeGenerator.GenerateConfigOptionKeys(options), diff --git a/src/Tools/Metadata/AnalyzerMetadata.cs b/src/Tools/Metadata/AnalyzerMetadata.cs index 11cc8b519c..d6ff925117 100644 --- a/src/Tools/Metadata/AnalyzerMetadata.cs +++ b/src/Tools/Metadata/AnalyzerMetadata.cs @@ -42,12 +42,6 @@ public class AnalyzerMetadata public List Links { get; } = []; - public List LegacyOptions { get; } = []; - - public List LegacyOptionAnalyzers { get; } = []; - - public LegacyAnalyzerOptionKind Kind { get; init; } - public AnalyzerMetadata Parent { get; init; } public AnalyzerStatus Status { get; init; } diff --git a/src/Tools/Metadata/LegacyAnalyzerOptionKind.cs b/src/Tools/Metadata/LegacyAnalyzerOptionKind.cs deleted file mode 100644 index 9a7c857a30..0000000000 --- a/src/Tools/Metadata/LegacyAnalyzerOptionKind.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Roslynator.Metadata; - -public enum LegacyAnalyzerOptionKind -{ - None, - Enable, - Disable, - Change, - Invert, -} diff --git a/src/Tools/Metadata/LegacyAnalyzerOptionMetadata.cs b/src/Tools/Metadata/LegacyAnalyzerOptionMetadata.cs deleted file mode 100644 index e903ba3730..0000000000 --- a/src/Tools/Metadata/LegacyAnalyzerOptionMetadata.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors. 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.Linq; - -namespace Roslynator.Metadata; - -public class LegacyAnalyzerOptionMetadata -{ - public AnalyzerMetadata CreateAnalyzerMetadata(AnalyzerMetadata parent) - { - var analyzer = new AnalyzerMetadata() - { - Id = (Id is not null) ? parent.Id + Id : null, - Identifier = Identifier, - Title = Title, - MessageFormat = Title, - Category = "AnalyzerOption", - DefaultSeverity = parent.DefaultSeverity, - IsEnabledByDefault = IsEnabledByDefault, - Status = (parent.Status != AnalyzerStatus.Enabled) ? parent.Status : Status, - SupportsFadeOut = SupportsFadeOut, - SupportsFadeOutAnalyzer = false, - MinLanguageVersion = MinLanguageVersion ?? parent.MinLanguageVersion, - Summary = Summary, - Kind = Kind, - Parent = parent, - }; - - analyzer.Samples.AddRange(Samples); - analyzer.Tags.AddRange(parent.Tags.Concat(Tags)); - - return analyzer; - } - - public string Identifier { get; init; } - - public string Id { get; init; } - - public string ParentId { get; init; } - - public string OptionKey { get; init; } - - public string OptionValue { get; init; } - - public string NewOptionKey { get; init; } - - public LegacyAnalyzerOptionKind Kind { get; init; } - - public string Title { get; init; } - - public bool IsEnabledByDefault { get; init; } - - public bool SupportsFadeOut { get; init; } - - public string MinLanguageVersion { get; init; } - - public string Summary { get; init; } - - public List Samples { get; } = []; - - [Obsolete("This property is obsolete", error: true)] - public bool IsObsolete { get; init; } - - public AnalyzerStatus Status { get; init; } - - public List Tags { get; } = []; -} diff --git a/src/Tools/Metadata/MetadataFile.cs b/src/Tools/Metadata/MetadataFile.cs index aa17991b39..444b35aa3b 100644 --- a/src/Tools/Metadata/MetadataFile.cs +++ b/src/Tools/Metadata/MetadataFile.cs @@ -42,7 +42,6 @@ public static IEnumerable ReadAnalyzers(string filePath) string remarks = element.Element("Remarks")?.Value.NormalizeNewLine(); IEnumerable samples = LoadSamples(element)?.Select(f => f with { Before = f.Before.Replace("[|Id|]", id) }); IEnumerable links = LoadLinks(element); - IEnumerable options = LoadOptions(element, id); IEnumerable configOptions = LoadConfigOptions(element); string[] tags = (element.Element("Tags")?.Value ?? "").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); AnalyzerStatus status = ParseStatus(element); @@ -73,8 +72,6 @@ public static IEnumerable ReadAnalyzers(string filePath) analyzer.ConfigOptions.AddRange(configOptions ?? Enumerable.Empty()); analyzer.Samples.AddRange(samples ?? Enumerable.Empty()); analyzer.Links.AddRange(links ?? Enumerable.Empty()); - analyzer.LegacyOptions.AddRange(options ?? Enumerable.Empty()); - analyzer.LegacyOptionAnalyzers.AddRange(analyzer.LegacyOptions.Select(f => f.CreateAnalyzerMetadata(analyzer))); yield return analyzer; } @@ -161,14 +158,6 @@ private static IEnumerable LoadLinks(XElement element) .Select(f => new LinkMetadata() { Url = f.Element("Url").Value, Text = f.Element("Text")?.Value, Title = f.Element("Title")?.Value }); } - private static IEnumerable LoadOptions(XElement element, string parentId) - { - return element - .Element("Options")? - .Elements("Option") - .Select(f => LoadOption(f, parentId)); - } - private static IEnumerable LoadConfigOptions(XElement element) { return element @@ -177,54 +166,6 @@ private static IEnumerable LoadConfigOptions(XElement elem .Select(f => new AnalyzerConfigOption("roslynator_" + f.Attribute("Key").Value, bool.Parse(f.Attribute("IsRequired")?.Value ?? bool.FalseString))); } - private static LegacyAnalyzerOptionMetadata LoadOption(XElement element, string parentId) - { - string title = element.Element("Title").Value; - - string identifier = element.Attribute("Identifier").Value; - string id = element.Element("Id")?.Value; - string optionKey = element.Element("OptionKey").Value; - string optionValue = element.Element("OptionValue")?.Value; - var kind = (LegacyAnalyzerOptionKind)Enum.Parse(typeof(LegacyAnalyzerOptionKind), element.Element("Kind").Value); - bool isEnabledByDefault = element.ElementValueAsBooleanOrDefault("IsEnabledByDefault"); - bool supportsFadeOut = element.ElementValueAsBooleanOrDefault("SupportsFadeOut"); - string minLanguageVersion = element.Element("MinLanguageVersion")?.Value; - string summary = element.Element("Summary")?.Value.NormalizeNewLine(); - IEnumerable samples = LoadSamples(element)?.Select(f => f with { Before = f.Before.Replace("[|Id|]", parentId) }); - bool isObsolete = element.AttributeValueAsBooleanOrDefault("IsObsolete"); - string[] tags = (element.Element("Tags")?.Value ?? "").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - AnalyzerStatus status = ParseStatus(element); - - string newOptionKey = element.Element("NewOptionKey")?.Value; - - if (newOptionKey?.StartsWith("roslynator_") == false) - newOptionKey = "roslynator_" + newOptionKey; - - var analyzerOption = new LegacyAnalyzerOptionMetadata() - { - Identifier = identifier, - Id = id, - ParentId = parentId, - OptionKey = optionKey, - OptionValue = optionValue, - NewOptionKey = newOptionKey, - Kind = kind, - Title = title, - IsEnabledByDefault = isEnabledByDefault, - SupportsFadeOut = supportsFadeOut, - MinLanguageVersion = minLanguageVersion, - Summary = summary, - Status = status, - }; - - if (samples is not null) - analyzerOption.Samples.AddRange(samples); - - analyzerOption.Tags.AddRange(tags); - - return analyzerOption; - } - public static IEnumerable ReadCodeFixes(string filePath) { XDocument doc = XDocument.Load(filePath); diff --git a/src/Tools/MetadataGenerator/Program.cs b/src/Tools/MetadataGenerator/Program.cs index b58c6cde48..d1e7499563 100644 --- a/src/Tools/MetadataGenerator/Program.cs +++ b/src/Tools/MetadataGenerator/Program.cs @@ -53,7 +53,6 @@ void UpdateChangeLog() string s = File.ReadAllText(path, _utf8NoBom); ImmutableDictionary dic = metadata.Analyzers - .Concat(metadata.Analyzers.SelectMany(f => f.LegacyOptionAnalyzers)) .Where(f => f.Id is not null) .ToImmutableDictionary(f => f.Id, f => f); @@ -99,9 +98,7 @@ private static void GenerateAnalyzersMarkdown(RoslynatorMetadata metadata, strin void DeleteInvalidAnalyzerMarkdowns() { - IEnumerable allIds = metadata.Analyzers - .Concat(metadata.Analyzers.SelectMany(f => f.LegacyOptionAnalyzers)) - .Select(f => f.Id); + IEnumerable allIds = metadata.Analyzers.Select(f => f.Id); foreach (string id in Directory.GetFiles(analyzersDirPath, "*.*", SearchOption.TopDirectoryOnly) .Select(f => Path.GetFileNameWithoutExtension(f)) From 46ad51564ecc675775d486ba06a08b59a1a58654 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sat, 26 Jul 2025 19:42:40 +0200 Subject: [PATCH 11/11] Release 4.14.0 (#1673) --- ChangeLog.md | 2 ++ src/VisualStudioCode/package/CHANGELOG.md | 32 +++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index cacbdd852b..2e294e77db 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.14.0] - 2025-07-26 + ### Added - [CLI] Add support for GitLab analyzer reports ([PR](https://github.com/dotnet/roslynator/pull/1633)) diff --git a/src/VisualStudioCode/package/CHANGELOG.md b/src/VisualStudioCode/package/CHANGELOG.md index 0dd4c5eedb..069a2f6fa4 100644 --- a/src/VisualStudioCode/package/CHANGELOG.md +++ b/src/VisualStudioCode/package/CHANGELOG.md @@ -15,6 +15,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - It's possible to specify a directory path and/or a file name of a test file. - Applies to testing library (Roslynator.Testing.*). +## [4.14.0] - 2025-07-26 + +### Added + +- [CLI] Add support for GitLab analyzer reports ([PR](https://github.com/dotnet/roslynator/pull/1633)) + +### Fixed + +- Fix analyzer [RCS1264](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1264) ([PR](https://github.com/dotnet/roslynator/pull/1666)) +- Fix analyzer [RCS1229](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1229) ([PR](https://github.com/dotnet/roslynator/pull/1667)) +- Fix analyzer [RCS1250](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1250) ([PR](https://github.com/dotnet/roslynator/pull/1652) by @aihnatiuk) +- Fix analyzer [RCS1260](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1260) ([PR](https://github.com/dotnet/roslynator/pull/1668)) +- Fix analyzer [RCS1105](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1105) ([PR](https://github.com/dotnet/roslynator/pull/1669)) +- Fix analyzer [RCS1260](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1260) ([PR](https://github.com/dotnet/roslynator/pull/1672)) + +### Changed + +- Disable analyzer [RCS1036](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1036) by default ([PR](https://github.com/dotnet/roslynator/pull/1671)) + - Use analyzer [RCS0063](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0063) instead + +### Removed + +- Remove legacy config options ([PR](https://github.com/dotnet/roslynator/pull/1304)) + +## [4.13.1] - 2025-02-23 + +### Added + +- Support custom path of a test file ([PR](https://github.com/dotnet/roslynator/pull/1609)) + - It's possible to specify a directory path and/or a file name of a test file. + - Applies to testing library (Roslynator.Testing.*). + ## [4.13.0] - 2025-02-09 ### Fixed