-
Notifications
You must be signed in to change notification settings - Fork 290
Add analyzer for OSCondition (MSTEST0059) #7015
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
d23a3b4
679b549
a5c0841
155ccc3
9063a66
cbd04fa
4953348
2e80171
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
|
||
| using System.Collections.Immutable; | ||
| using System.Composition; | ||
|
|
||
| using Analyzer.Utilities; | ||
|
|
||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CodeActions; | ||
| using Microsoft.CodeAnalysis.CodeFixes; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
| using Microsoft.CodeAnalysis.Editing; | ||
|
|
||
| using MSTest.Analyzers.Helpers; | ||
|
|
||
| namespace MSTest.Analyzers; | ||
|
|
||
| /// <summary> | ||
| /// Code fixer for <see cref="UseOSConditionAttributeInsteadOfRuntimeCheckAnalyzer"/>. | ||
| /// </summary> | ||
| [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseOSConditionAttributeInsteadOfRuntimeCheckFixer))] | ||
| [Shared] | ||
| public sealed class UseOSConditionAttributeInsteadOfRuntimeCheckFixer : CodeFixProvider | ||
| { | ||
| public override ImmutableArray<string> FixableDiagnosticIds { get; } | ||
|
Check failure on line 27 in src/Analyzers/MSTest.Analyzers.CodeFixes/UseOSConditionAttributeInsteadOfRuntimeCheckFixer.cs
|
||
| = ImmutableArray.Create(DiagnosticIds.UseOSConditionAttributeInsteadOfRuntimeCheckRuleId); | ||
|
|
||
| public override FixAllProvider GetFixAllProvider() | ||
|
Check failure on line 30 in src/Analyzers/MSTest.Analyzers.CodeFixes/UseOSConditionAttributeInsteadOfRuntimeCheckFixer.cs
|
||
Evangelink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| => WellKnownFixAllProviders.BatchFixer; | ||
|
|
||
| public override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||
|
Check failure on line 33 in src/Analyzers/MSTest.Analyzers.CodeFixes/UseOSConditionAttributeInsteadOfRuntimeCheckFixer.cs
|
||
Evangelink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| SyntaxNode root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
| Diagnostic diagnostic = context.Diagnostics[0]; | ||
|
|
||
| string? isNegatedStr = diagnostic.Properties[UseOSConditionAttributeInsteadOfRuntimeCheckAnalyzer.IsNegatedKey]; | ||
| string? osPlatform = diagnostic.Properties[UseOSConditionAttributeInsteadOfRuntimeCheckAnalyzer.OSPlatformKey]; | ||
|
|
||
| if (isNegatedStr is null || osPlatform is null || !bool.TryParse(isNegatedStr, out bool isNegated)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| SyntaxNode diagnosticNode = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); | ||
|
|
||
| // Find the containing method | ||
| MethodDeclarationSyntax? methodDeclaration = diagnosticNode.FirstAncestorOrSelf<MethodDeclarationSyntax>(); | ||
| if (methodDeclaration is null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Find the if statement to remove | ||
| IfStatementSyntax? ifStatement = diagnosticNode.FirstAncestorOrSelf<IfStatementSyntax>(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The diagnostic node here is what? Shouldn't it be the IfStatementSyntax in question already? Why do we need |
||
| if (ifStatement is null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| context.RegisterCodeFix( | ||
| CodeAction.Create( | ||
| title: CodeFixResources.UseOSConditionAttributeInsteadOfRuntimeCheckFix, | ||
| createChangedDocument: ct => AddOSConditionAttributeAsync(context.Document, root, methodDeclaration, ifStatement, osPlatform, isNegated, ct), | ||
| equivalenceKey: nameof(UseOSConditionAttributeInsteadOfRuntimeCheckFixer)), | ||
| diagnostic); | ||
| } | ||
|
|
||
| private static async Task<Document> AddOSConditionAttributeAsync( | ||
| Document document, | ||
| SyntaxNode root, | ||
| MethodDeclarationSyntax methodDeclaration, | ||
| IfStatementSyntax ifStatement, | ||
| string osPlatform, | ||
| bool isNegated, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| // Map OSPlatform to OperatingSystems enum | ||
| string? operatingSystem = MapOSPlatformToOperatingSystem(osPlatform); | ||
| if (operatingSystem is null) | ||
| { | ||
| return document; | ||
| } | ||
|
|
||
| // Determine the condition mode: | ||
| // - If isNegated is false (checking if IS on platform, then early return), we want to EXCLUDE this platform | ||
| // - If isNegated is true (checking if NOT on platform, then early return), we want to INCLUDE this platform only | ||
| // Actually: | ||
| // if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; => Include Windows only | ||
| // if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; => Exclude Windows | ||
| string conditionMode = isNegated ? "ConditionMode.Include" : "ConditionMode.Exclude"; | ||
|
|
||
| // Create the attribute | ||
| AttributeSyntax osConditionAttribute = SyntaxFactory.Attribute( | ||
| SyntaxFactory.IdentifierName("OSCondition"), | ||
| SyntaxFactory.AttributeArgumentList( | ||
| SyntaxFactory.SeparatedList(new[] | ||
| { | ||
| SyntaxFactory.AttributeArgument( | ||
| SyntaxFactory.ParseExpression(conditionMode)), | ||
| SyntaxFactory.AttributeArgument( | ||
| SyntaxFactory.ParseExpression($"OperatingSystems.{operatingSystem}")), | ||
| }))); | ||
|
|
||
| // Check if the method already has an OSCondition attribute | ||
| bool hasOSConditionAttribute = methodDeclaration.AttributeLists | ||
| .SelectMany(al => al.Attributes) | ||
| .Any(a => a.Name.ToString() is "OSCondition" or "OSConditionAttribute"); | ||
|
|
||
| if (!hasOSConditionAttribute) | ||
| { | ||
| // Add the attribute to the method | ||
| AttributeListSyntax newAttributeList = SyntaxFactory.AttributeList( | ||
| SyntaxFactory.SingletonSeparatedList(osConditionAttribute)) | ||
| .WithTrailingTrivia(SyntaxFactory.EndOfLine("\n")); | ||
|
|
||
| MethodDeclarationSyntax newMethod = methodDeclaration.AddAttributeLists(newAttributeList); | ||
| editor.ReplaceNode(methodDeclaration, newMethod); | ||
| } | ||
|
|
||
| // Remove the if statement | ||
| editor.RemoveNode(ifStatement); | ||
|
|
||
| return editor.GetChangedDocument(); | ||
| } | ||
|
|
||
| private static string? MapOSPlatformToOperatingSystem(string osPlatform) | ||
| => osPlatform.ToUpperInvariant() switch | ||
| { | ||
| "WINDOWS" => "Windows", | ||
| "LINUX" => "Linux", | ||
| "OSX" => "OSX", | ||
| "FREEBSD" => "FreeBSD", | ||
| _ => null, | ||
| }; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.