Removing and Adding content with Code Fix Provider - c#

I had that previous question which was intended to resolved the state of a local variable / parameter. It works fine, I tweak it a little bit and it looks like this now :
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Linq;
namespace RefactoringEssentials.CSharp.Diagnostics
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class LocalVariableNotUsedAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor descriptor = new DiagnosticDescriptor(
CSharpDiagnosticIDs.LocalVariableNotUsedAnalyzerID,
GettextCatalog.GetString("Local variable is never used"),
GettextCatalog.GetString("Local variable is never used"),
DiagnosticAnalyzerCategories.RedundanciesInDeclarations,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: HelpLink.CreateFor(CSharpDiagnosticIDs.LocalVariableNotUsedAnalyzerID)
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(descriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(
(nodeContext) =>
{
Diagnostic diagnostic;
if (TryGetDiagnostic(nodeContext, out diagnostic))
{
nodeContext.ReportDiagnostic(diagnostic);
}
},
SyntaxKind.LocalDeclarationStatement
);
}
private static bool TryGetDiagnostic(SyntaxNodeAnalysisContext nodeContext, out Diagnostic diagnostic)
{
diagnostic = default(Diagnostic);
if (nodeContext.IsFromGeneratedCode())
return false;
var localDeclarationUnused = nodeContext.Node as LocalDeclarationStatementSyntax;
var body = localDeclarationUnused?.Parent as BlockSyntax;
if (body == null)
return false;
var dataFlow = nodeContext.SemanticModel.AnalyzeDataFlow(body);
var variablesDeclared = dataFlow.VariablesDeclared;
var variablesRead = dataFlow.ReadInside.Union(dataFlow.ReadOutside);
var unused = variablesDeclared.Except(variablesRead).ToArray();
if (unused == null)
return false;
if (localDeclarationUnused.Declaration == null || !localDeclarationUnused.Declaration.Variables.Any())
return false;
var localDeclarationSymbol = nodeContext.SemanticModel.GetDeclaredSymbol(localDeclarationUnused.Declaration.Variables.FirstOrDefault());
if (unused.Any())
{
if (unused.Contains(localDeclarationSymbol))
{
diagnostic = Diagnostic.Create(descriptor, localDeclarationUnused.Declaration.GetLocation());
return true;
}
}
return false;
}
}
}
I have build a code fix provider which is working around 40% of the time, when checking the success rate of the NUnit test. Even though I know the code is OK, there seem to be some error on my machine that only arrives when the code fix is being run. I know this because I can debug the analyzer for the tests and each one are fine.
When the code fix provider is being run, I have this error that I can't shake for some reason : "System.ArgumentNullException : Value cannot be null.
Parameter name : declaration"
I have tried debugging the code fix provider, but nothing showed me where that declaration parameter could be located.
Moreover, I am used with code fix providers that need to either replace and remove nodes. But I'm not used to fix an error and add content and I'm wondering how to do such a thing.
Here's my code fix provider which does not take care of adding information at the moment :
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CodeFixes;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace RefactoringEssentials.CSharp.Diagnostics
{
[ExportCodeFixProvider(LanguageNames.CSharp), System.Composition.Shared]
public class LocalVariableNotUsedCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds
{
get
{
return ImmutableArray.Create(CSharpDiagnosticIDs.LocalVariableNotUsedAnalyzerID);
}
}
public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var document = context.Document;
var cancellationToken = context.CancellationToken;
var span = context.Span;
var diagnostics = context.Diagnostics;
var root = await document.GetSyntaxRootAsync(cancellationToken);
var diagnostic = diagnostics.First();
var node = root.FindNode(context.Span);
if (node == null)
return;
var newRoot = root.RemoveNode(node, SyntaxRemoveOptions.KeepNoTrivia);
context.RegisterCodeFix(CodeActionFactory.Create(node.Span, diagnostic.Severity, "Remove unused local variable", document.WithSyntaxRoot(newRoot)), diagnostic);
}
}
}
and here are the tests that I'm using to make sure that the fix is ok. The last two are running just fine :-)
using NUnit.Framework;
using RefactoringEssentials.CSharp.Diagnostics;
namespace RefactoringEssentials.Tests.CSharp.Diagnostics
{
[TestFixture]
public class LocalVariableNotUsedTests : CSharpDiagnosticTestBase
{
[Test]
public void TestUnusedVariable()
{
var input = #"
class TestClass {
void TestMethod ()
{
$int i$;
}
}";
var output = #"
class TestClass {
void TestMethod ()
{
}
}";
Analyze<LocalVariableNotUsedAnalyzer>(input, output);
}
[Test]
public void TestUnusedVariable2()
{
var input2 = #"
class TestClass {
void TestMethod ()
{
$int i, j$;
j = 1;
}
}";
var output2 = #"
class TestClass {
void TestMethod ()
{
int j;
j = 1;
}
}";
Analyze<LocalVariableNotUsedAnalyzer>(input2, output2);
}
[Test]
public void TestUsedVariable()
{
var input1 = #"
class TestClass {
void TestMethod ()
{
$int i = 0$;
}
}";
var input2 = #"
class TestClass {
void TestMethod ()
{
int i;
i = 0;
}
}";
Analyze<LocalVariableNotUsedAnalyzer>(input1);
Analyze<LocalVariableNotUsedAnalyzer>(input2);
}
[Test]
public void TestUnusedForeachVariable()
{
var input = #"
class TestClass {
void TestMethod ()
{
var array = new int[10];
foreach (var i in array) {
}
}
}";
Analyze<LocalVariableNotUsedAnalyzer>(input);
}
[Test]
public void TestUsedForeachVariable()
{
var input = #"
class TestClass {
void TestMethod ()
{
var array = new int[10];
int j = 0;
foreach (var i in array) {
j += i;
}
}
}";
Analyze<LocalVariableNotUsedAnalyzer>(input);
}
}
}
Is there is anything that is not clear, I will make sure to update my thread appropriately.

Related

Proper Way to Test a file method with .net

I'm new to .net and testing. My following code looks like this:
using System.Xml.Linq;
public class AnimalXmlService
{
public Animal GetAnimalInfoFromXml(string url) {
XElement xml_doc = GetXmlInfo(url);
if (xml_doc == null)
{
return null;
} else {
XElement animal_info = xml_doc.Element("Animal");
string animal_name = GetAnimalName(animal_info);
int animal_id = GetAnimalId(animal_info);
return new Animal(animal_id, animal_name);
}
}
private XElement GetXmlInfo(string url)
{
try
{
XElement animal_xml_info = XElement.Load(url);
return animal_xml_info;
}
catch
{
return null;
}
}
private int GetAnimalName(XElement animal_info)
{
....
}
}
My question is how do I stub the GetAnimalInfoFromXml to return a file? I have the sample xml file that I will be using instead of making a request. Here's my following test. I'm also wondering if there are better ways to refactor this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
namespace AnimalXmlService
{
[TestFixture]
public class AnimalXmlTest
{
[Test]
public void extracts_valid_name()
{
//get xml file?
animalService AnimalXmlService = new AnimalXmlService();
name = animalService.GetAnimalName(xml_file);
Assert.AreEqual(name, "bobby the tiger");
}
[Test]
public void extracts_valid_id()
{
//get xml file?
xml_file = fetch_file //is this appropriate?
animalService AnimalXmlService = new AnimalXmlService();
id = animalService.GetAnimalId(xml_file);
Assert.AreEqual(name, "2");
}
}
}
In this situations you can use test doubles.
First , you should make your codes more testable ( Breaking dependency )
public class AnimalXmlService
{
private readonly IXmlReader _xmlReader;
public AnimalXmlService(IXmlReader xmlReader)
{
this._xmlReader = xmlReader;
}
public Animal GetAnimalInfoFromXml(string url)
{
XElement xml_doc = _xmlReader.Load(url);
if (xml_doc == null)
{
return null;
}
else
{
XElement animal_info = xml_doc.Element("Animal");
string animal_name = GetAnimalName(animal_info);
int animal_id = GetAnimalId(animal_info);
return new Animal(animal_id, animal_name);
}
}
}
And then you should create a stub to replace your real dependency. ( Also you can use frameworks like NSubstitute,Mock,...)
public class XmlReaderStub : IXmlReader
{
public XElement XElement { get; set; }
public XElement Load(string url)
{
return XElement;
}
}
And finally
public class AnimalXmlTest
{
[Test]
public void extracts_valid_name()
{
var stub = new XmlReaderStub();
stub.XElement = new XElement(); // some XElement
animalService AnimalXmlService = new AnimalXmlService(stub);
name = animalService.GetAnimalName();
Assert.AreEqual(name, "bobby the tiger");
}
}
You can have another method in your class like the one below which returns an XmlDocument.
public XmlDocument GetXmlFile()
{
XmlDocument doc = new XmlDocument();
doc.LoadXml("<Animal><Name>Bobby the tiger</Name></Animal>");
return doc;
}

Can't dynamically invoke a method in c#

I have a class whose method I want to invoke dynamically. But I am not able to do it. Am I missing something?
public class P_WATER
{
private int[] jDS = new int[20];
private int n;
public int[] JDS { get => jDS; set => jDS = value; }
public int N { get => n; set => n = value; }
public void P_WATER1()
{
//something...
}
}
public class Test
{
P_WATER P_WATERState1 = new P_WATER();
PLibStateList.Add(P_WATERState1);
// Try to invoke methods from each objects.
foreach (object item in StateUtility.PLibStateList)
{
Type objType= item.GetType();
objType.InvokeMember(objType.Name + "1", BindingFlags.InvokeMethod, null, item, null);
}
}
When trying to invoke the method I am getting the following exception:
Could not load file or assembly 'System.Runtime, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.
But my assembly is well bound to the project.
I've tried the same code (with little modifications) in VS2019 (Console App) and worked just fine... weird... check the using statements for ambiguity (just in case):
using System;
using System.Collections.Generic;
using System.Reflection;
My full code:
using System;
using System.Collections.Generic;
using System.Reflection;
namespace cant_dynamically_invoke_a_method_in_c_sharp
{
class Program
{
static void Main(string[] args)
{
Test.InvokeStuff();
}
}
public class P_WATER
{
private int[] jDS = new int[20];
private int n;
public int[] JDS { get => jDS; set => jDS = value; }
public int N { get => n; set => n = value; }
public void P_WATER1()
{
//something...
Console.WriteLine("Success!");
}
}
public class Test
{
public static void InvokeStuff()
{
// Needed to mock this up
List<P_WATER> PLibStateList = new List<P_WATER>();
P_WATER P_WATERState1 = new P_WATER();
PLibStateList.Add(P_WATERState1);
// Try to invoke methods from each objects.
foreach (object item in PLibStateList)
{
Type objType = item.GetType();
objType.InvokeMember(objType.Name + "1", BindingFlags.InvokeMethod, null, item, null);
}
}
}
}
Entered the method successfuly:
Regards!

How do i get symbol references in a diagnostic without SymbolFinder?

Currently i've got this code:
private async Task<bool> IsMentionedInDisposeCallAsync(SyntaxNodeAnalysisContext context, FieldDeclarationSyntax fieldDeclarationSyntax)
{
foreach (var variableDeclaratorSyntax in fieldDeclarationSyntax.Declaration.Variables)
{
var declaredSymbol = context.SemanticModel.GetDeclaredSymbol(variableDeclaratorSyntax);
if (declaredSymbol is IFieldSymbol fieldSymbol)
{
// SymbolFinder.FindReferencesAsync()
var b = fieldSymbol.Locations;
// context.SemanticModel.Compilation.
}
}
return false;
}
And this scenario:
private static readonly string TestSourceImplementsDisposableAndDoesMentionDisposableField = #"
using System;
using System.IO;
namespace ConsoleApplication1
{
public class SampleDisposable : IDisposable
{
public void Dispose()
{
}
}
public class SampleConsumer : IDisposable
{
private SampleDisposable _disposable = new SampleDisposable();
private IDisposable _ms = new MemoryStream();
public void Dispose()
{
_disposable?.Dispose();
_ms?.Dispose();
}
}
}";
Ultimately my desire is to figure out whether a dispose method is accessing a disposable field. Unfortunately i can't seem to find a way to get this working without using SymbolFinder, which requires a solution.
I did something similar with SymbolFinder and it was an easy thing to do - but how do i do it from the functionality available within a diagnostic?
Am i missing something obvious here?
You could simply use the SemanticModel to analyse the type used for the field like this:
private async Task<bool> IsMentionedInDisposeCallAsync(SyntaxNodeAnalysisContext context, FieldDeclarationSyntax fieldDeclarationSyntax)
{
foreach (var variableDeclaratorSyntax in fieldDeclarationSyntax.Declaration.Variables)
{
var declaredSymbol = context.SemanticModel.GetDeclaredSymbol(variableDeclaratorSyntax);
if (declaredSymbol is IFieldSymbol fieldSymbol)
{
var isDisposeable = CheckIsTypeIDisposeable(fieldSymbol.Type as INamedTypeSymbol);
// SymbolFinder.FindReferencesAsync()
var b = fieldSymbol.Locations;
// context.SemanticModel.Compilation.
}
}
return false;
}
private string fullQualifiedAssemblyNameOfIDisposeable = typeof(IDisposable).AssemblyQualifiedName;
private bool CheckIsTypeIDisposeable(INamedTypeSymbol type)
{
// Identify the IDisposable class. You can use any method to do this here
// A type.ToDisplayString() == "System.IDisposable" might do it for you
if(fullQualifiedAssemblyNameOfIDisposeable ==
type.ToDisplayString() + ", " + type.ContainingAssembly.ToDisplayString())
{
return true;
}
if(type.BaseType != null)
{
if (CheckIsTypeIDisposeable(type.BaseType))
{
return true;
}
}
foreach(var #interface in type.AllInterfaces)
{
if (CheckIsTypeIDisposeable(#interface))
{
return true;
}
}
return false;
}
Basically you would search through all interfaces of the class and the base class recursively to find the type corresponding to IDisposeable - which should be somewhere in the hierarchy.

Create a Roslyn analyzer to detect the "Sleep()" method call in a .cs file

I am asking this question in continuity to this one
I would like to create a roslyn analyzer to detect the use of the sleep method in a .cs file. Could someone please help me correct my code ?
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace SleepCustomRule
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SleepCustomRuleAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "SleepCheck";
private const string Title = "Sleep function use in forbidden";
private const string MessageFormat = "Remove this usage of sleep function";
private const string Description = "Sleep function forbidden";
private const string Category = "Usage";
private static readonly ISet<string> Bannedfunctions = ImmutableHashSet.Create("Sleep");
private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category,
DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.GenericName);
}
private static void AnalyzeNode(SymbolAnalysisContext context)
{
var tree = CSharpSyntaxTree.ParseText(#"
public class Sample
{
public string FooProperty {get; set;}
public void FooMethod()
{
}
}");
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var compilation = CSharpCompilation.Create("MyCompilation",
syntaxTrees: new[] { tree }, references: new[] { mscorlib });
var semanticModel = compilation.GetSemanticModel(tree);
var methods = tree.GetRoot().DescendantNodes().OfType<MethodDeclarationSyntax>();
foreach (var node in methods)
{
if (node != null)
{
// node – is your current syntax node
// semanticalModel – is your semantical model
ISymbol symbol = semanticModel.GetSymbolInfo(node).Symbol ?? semanticModel.GetDeclaredSymbol(node);
if (symbol.Kind == SymbolKind.Method)
{
string methodName = "sleep";
if ((symbol as IMethodSymbol).Name.ToLower() == methodName)
{
// you find your method
var diagnostic = Diagnostic.Create(Rule, symbol.Locations[0], symbol.Name);
context.ReportDiagnostic(Diagnostic.Create(Rule, node.Identifier.GetLocation()));
}
}
}
}
}
}
}
I have those two errors which I could not correct:
1- RegisterSyntaxNodeAction in
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.GenericName);
2- Assembly in
var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
3- Besides, I would like to detect the use of the sleep method from a given file not from the var tree I made.
Thank you so much in advance!

How to remove SyntaxToken when using a Code Fix Provider

I'm working on a code base which has the purpose to format code. In some instances, I have to remove syntax tokens such as the keyword "params" inside the parameter list if I found one and it's redundant or remove some unneeded braces ( "{" "}").
The thing is, it's so hard and uneasy to remove syntax token from the syntax tree. I have tried many different solutions that you can find below, but for the life of me, I cannot succeed. I'd like a solution that could work for any syntax token U might encounter and that I need to possibly remove from the syntax tree.
In the following case, I have built an analyzer that is currently functionnal but the code fix provider is not quite working... (It's a case of redundant params keyword usage).
//UPDATE - I have provided full code of Analyzer/Code fix provider / NUnit tests
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Formatting;
namespace RefactoringEssentials.CSharp.Diagnostics
{
[ExportCodeFixProvider(LanguageNames.CSharp), System.Composition.Shared]
public class RedundantParamsCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds
{
get
{
return ImmutableArray.Create(CSharpDiagnosticIDs.RedundantParamsAnalyzerID);
}
}
public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var document = context.Document;
var cancellationToken = context.CancellationToken;
var span = context.Span;
var diagnostics = context.Diagnostics;
var root = await document.GetSyntaxRootAsync(cancellationToken);
var diagnostic = diagnostics.First();
var node = root.FindNode(context.Span) as ParameterSyntax;
if (node == null)
return;
if (!node.Modifiers.Any(x => x.IsKind(SyntaxKind.ParamsKeyword)))
return;
var oldParameterNode = node;
var paramList = node.Parent as ParameterListSyntax;
if (paramList == null)
return;
//var newRoot = root.ReplaceNode(
// oldParameterNode.Parent as ParameterListSyntax,
// paramList.WithParameters
// (SyntaxFactory.SeparatedList(paramList.Parameters.ToArray()))
// .WithLeadingTrivia(node.GetLeadingTrivia())
// .WithTrailingTrivia(node.GetTrailingTrivia()))
// .WithAdditionalAnnotations(Formatter.Annotation);
//var paramsKeyword = (node.Modifiers.FirstOrDefault(x => x.IsKind(SyntaxKind.ParamsKeyword)));
//var indexParams = node.Modifiers.IndexOf(paramsKeyword);
//var syntaxListWithoutParams = node.Modifiers.RemoveAt(indexParams);
//node.ReplaceToken(paramsKeyword, syntaxListWithoutParams.AsEnumerable());
context.RegisterCodeFix(CodeActionFactory.Create(node.Span, diagnostic.Severity, "Remove 'params' modifier", token =>
{
var newNode = SyntaxFactory.Parameter(node.AttributeLists,node.Modifiers.Remove(SyntaxFactory.Token(SyntaxKind.ParamsKeyword)),node.Type,node.Identifier,node.Default);
var newRoot = root.ReplaceNode(node, newNode);
return Task.FromResult(document.WithSyntaxRoot(newRoot));
}), diagnostic);
//context.RegisterCodeFix(CodeActionFactory.Create(node.SKCpan, diagnostic.Severity, , document.WithSyntaxRoot(newRoot)), diagnostic);
}
}
}
This is the use cases in my situation.
using System;
using NUnit.Framework;
using RefactoringEssentials.CSharp.Diagnostics;
namespace RefactoringEssentials.Tests.CSharp.Diagnostics
{
[TestFixture]
public class RedundantParamsTests : CSharpDiagnosticTestBase
{
[Test]
public void TestBasicCase()
{
Analyze<RedundantParamsAnalyzer>(#"class FooBar
{
public virtual void Foo(string fmt, object[] args)
{
}
}
class FooBar2 : FooBar
{
public override void Foo(string fmt, $params object[] args$)
{
System.Console.WriteLine(fmt, args);
}
}", #"class FooBar
{
public virtual void Foo(string fmt, object[] args)
{
}
}
class FooBar2 : FooBar
{
public override void Foo(string fmt, object[] args)
{
System.Console.WriteLine(fmt, args);
}
}");
}
[Test]
public void TestValidCase()
{
Analyze<RedundantParamsAnalyzer>(#"class FooBar
{
public virtual void Foo(string fmt, object[] args)
{
}
}
class FooBar2 : FooBar
{
public override void Foo(string fmt, object[] args)
{
System.Console.WriteLine(fmt, args);
}
}");
}
[Test]
public void ValideParamsUsageTests()
{
Analyze<RedundantParamsAnalyzer>(#"class FooBar
{
public virtual void Foo(string fmt, params object[] args)
{
}
}
class FooBar2 : FooBar
{
public override void Foo(string fmt, params object[] args)
{
System.Console.WriteLine(fmt, args);
}
}");
}
[Test]
public void TestDisable()
{
Analyze<RedundantParamsAnalyzer>(#"class FooBar
{
public virtual void Foo(string fmt, object[] args)
{
}
}
class FooBar2 : FooBar
{
// ReSharper disable once RedundantParams
public override void Foo(string fmt, params object[] args)
{
System.Console.WriteLine(fmt, args);
}
}");
}
}
}
For those who might be interested in how I determine that the params is redundant, here's the logic:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Linq;
namespace RefactoringEssentials.CSharp.Diagnostics
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class RedundantParamsAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor descriptor = new DiagnosticDescriptor(
CSharpDiagnosticIDs.RedundantParamsAnalyzerID,
GettextCatalog.GetString("'params' is ignored on overrides"),
GettextCatalog.GetString("'params' is always ignored in overrides"),
DiagnosticAnalyzerCategories.RedundanciesInDeclarations,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: HelpLink.CreateFor(CSharpDiagnosticIDs.RedundantParamsAnalyzerID),
customTags: DiagnosticCustomTags.Unnecessary
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(descriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(
(nodeContext) =>
{
Diagnostic diagnostic;
if (TryGetParamsDiagnostic(nodeContext, out diagnostic))
{
nodeContext.ReportDiagnostic(diagnostic);
}
},
SyntaxKind.ParameterList
);
}
//I think it's a better decision to head in this direction instead of MethodDeclaration.
private static bool TryGetParamsDiagnostic(SyntaxNodeAnalysisContext nodeContext, out Diagnostic diagnostic)
{
diagnostic = default(Diagnostic);
if (nodeContext.IsFromGeneratedCode())
return false;
var paramList = nodeContext.Node as ParameterListSyntax;
var declaration = paramList?.Parent as MethodDeclarationSyntax;
if (declaration == null)
return false;
if (declaration.Modifiers.Count == 0 || !declaration.Modifiers.Any(SyntaxKind.OverrideKeyword))
return false;
var lastParam = declaration.ParameterList.Parameters.LastOrDefault();
SyntaxToken? paramsModifierToken = null;
if (lastParam == null)
return false;
foreach (var x in lastParam.Modifiers)
{
if (x.IsKind(SyntaxKind.ParamsKeyword))
{
paramsModifierToken = x;
break;
}
}
if (!paramsModifierToken.HasValue ||
!paramsModifierToken.Value.IsKind(SyntaxKind.ParamsKeyword))
return false;
diagnostic = Diagnostic.Create(descriptor, lastParam.GetLocation());
return true;
}
}
In this case, you don't want to remove the whole node, rather you just want to remove the params modifier. Since SyntaxNodes are immutable, you need to create a new node with the appropriate modifiers using the WithModifiers method:
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var diagnostic = context.Diagnostics.First();
context.RegisterCodeFix(CodeAction.Create("Remove 'params' modifier", async token =>
{
var document = context.Document;
var root = await document.GetSyntaxRootAsync(token);
var fullParameterNode = root.FindNode(diagnostic.Location.SourceSpan, false) as ParameterSyntax;
// Keep all modifiers except the params
var newModifiers = fullParameterNode.Modifiers.Where(m => !m.IsKind(SyntaxKind.ParamsKeyword));
var syntaxModifiers = SyntaxTokenList.Create(new SyntaxToken());
syntaxModifiers.AddRange(newModifiers);
var updatedParameterNode = fullParameterNode.WithModifiers(syntaxModifiers);
var newDoc = document.WithSyntaxRoot(root.ReplaceNode(fullParameterNode, updatedParameterNode));
return newDoc;
}, "KEY"), diagnostic);
}
As for a generic remove option for syntax, I don't know of one that is easy to use. The Node.ReplaceSyntax is a rather complicated method and I usually find it easier to work with the ReplaceNode or RemoveNode methods.

Categories

Resources