I have implemented a custom diagnostic rule in C# using the Roslyn API, but it is not being applied to my code. I have verified that the rule is included in the list of enabled rules in my project's code analysis settings and that code analysis is enabled for the project. However, when I build the project, no compilation errors are reported for code that should trigger my custom rule.
Here is the code for my custom rule:
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class MoreThanOneNamespaceInFileIsNotAllowedAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "MyRules0001";
private static readonly LocalizableString Title = "This is a title";
private static readonly LocalizableString MessageFormat = "This is a message";
private static readonly LocalizableString Description = "This is a description";
private const string Category = "Naming";
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId,
Title,
MessageFormat,
Category,
DiagnosticSeverity.Error,
true,
Description);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxTreeAction(AnalyzeAction);
}
private static void AnalyzeAction(SyntaxTreeAnalysisContext context)
{
var syntaxRoot = context.Tree.GetRoot(context.CancellationToken);
var descentNodes = syntaxRoot.
DescendantNodes(node => node != null && !node.IsKind(SyntaxKind.ClassDeclaration));
var foundNode = false;
foreach (var node in descentNodes)
{
if (node.IsKind(SyntaxKind.NamespaceDeclaration))
{
if (foundNode)
{
context.ReportDiagnostic(Diagnostic.Create(Rule, Location.None));
}
else
{
foundNode = true;
}
}
}
}
}
And here is the code that I expect to trigger a compilation error:
Because two namespaces are defined in a single file.
namespace ClassLibrary1
{
public class Class1
{
}
}
namespace ClassLibrary2
{
public class Class2
{
}
}
And here is the .editorconfig
# My custom StyleCop rule
dotnet_diagnostic.MyRules0001.severity = error
I have also pushed a sample project to GitHub that reproduces the issue. Can anyone help me understand why my custom rule is not being applied and how I can fix it? Thank you in advance for any help you can provide.
Specify OutputItemType with value set to Analyzer. For example:
<ProjectReference Include="..\MyCustomRules\MyCustomRules.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<OutputItemType>Analyzer</OutputItemType>
</ProjectReference>
Related
I have a logger that records the method name (which I get through reflection) and parameters (which are manually passed to the logger). Here's an example of the proper way to do the logging:
public void Foo() {
// This is correct - the method has no parameters, neither does the logging
Logger.Log();
// Method
}
public void Foo1(int a, int b) {
// Log both of the parameters correctly
Logger.Log(a, b);
// Rest of method
}
However, people will periodically call this incorrectly. For example:
public void Foo1(int a, int b) {
// Didn't record any of the parameters
Logger.Log();
// Rest of method
}
or
public void Foo1(int a, int b, int c) {
// Someone changed the number of parameters but didn't update the logging call
Logger.Log(a, b);
}
The signature of the Log method is:
public void Log(params object[] parameters)
I'd like to have some way of requiring that Logger.Log have the same number of parameters as the method calling it does.
I know how to do this at runtime (just use reflection to get the parameter list for the caller and compare it to what parameters I actually received), but that would be a really bad solution to this I think since the vast majority of the checks would be unnecessary. (It would also mean that you wouldn't know until runtime that you had written it incorrectly, and then only if you happened to execute that particular method).
Right now we're not using FxCop unfortunately (or I'd just write some kind of rule) and I suspect that I wouldn't succeed in changing that fact. Short of writing a compiler plugin of some kind, is there a way to force people to use this method correctly?
You should be able to accomplish this using the new Roslyn API's. You'll want to install the SDK here:
https://marketplace.visualstudio.com/items?itemName=VisualStudioProductTeam.NETCompilerPlatformSDK
Once installed you should go to new project and navigate to Extensibility and you'll see the project type Analyzer with Code Fix (NuGet + VSIX) template. I created a sample project that I used to show the compiler errors:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AnalyzerTest
{
public static class Logger
{
public static void Log(params object[] parameters)
{
}
}
}
namespace AnalyzerTest
{
public class Foo
{
public void Foo1(int a, int b)
{
// Didn't record any of the parameters
Logger.Log();
// Rest of method
}
}
}
I created a separate project for the analyzer and here is the code for the analyzer class:
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Semantics;
namespace Analyzer1
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class LoggerAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "Logging";
internal const string Title = "Logging error";
internal const string MessageFormat = "Logging error {0}";
internal const string Description = "You should have the same amount of arguments in the logger as you do in the method.";
internal const string Category = "Syntax";
internal static DiagnosticDescriptor Rule =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat,
Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);
public override ImmutableArray<DiagnosticDescriptor>
SupportedDiagnostics
{ get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(
AnalyzeNode, SyntaxKind.InvocationExpression);
}
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var invocationExpr = (InvocationExpressionSyntax)context.Node;
var memberAccessExpr = invocationExpr.Expression as MemberAccessExpressionSyntax;
if (memberAccessExpr != null && memberAccessExpr.Name.ToString() != "Log")
{
return;
}
var memberSymbol =
context.SemanticModel.GetSymbolInfo(memberAccessExpr).Symbol as IMethodSymbol;
if (memberSymbol == null || !memberSymbol.ToString().StartsWith("AnalyzerTest.Logger.Log"))
{
return;
}
MethodDeclarationSyntax parent = GetParentMethod(context.Node);
if(parent == null)
{
return;
}
var argumentList = invocationExpr.ArgumentList;
Int32 parentArgCount = parent.ParameterList.Parameters.Count;
Int32 argCount = argumentList != null ? argumentList.Arguments.Count : 0;
if (parentArgCount != argCount)
{
var diagnostic = Diagnostic.Create(Rule, invocationExpr.GetLocation(), Description);
context.ReportDiagnostic(diagnostic);
}
}
private MethodDeclarationSyntax GetParentMethod(SyntaxNode node)
{
var parent = node.Parent as MethodDeclarationSyntax;
if(parent == null)
{
return GetParentMethod(node.Parent);
}
return parent;
}
}
}
While in the Analyzer with Code Fix project you can hit F5 (as long as your .Vsix project is the startup project) and it will open up another VS instance and you can choose the project you would like to test the analyzer on.
Here is the result:
It also looks like you will have to install this as a NuGet package instead of a VS Extension, for whatever reason VS Extensions don't affect the build and you will only get the warning:
https://stackoverflow.com/a/39657967/1721372
For a more complete example see here:
https://msdn.microsoft.com/en-us/magazine/dn879356.aspx
I am learning about how to use Custom Attributes to add meta info to my parameter classes. I am following an example found in a textbook titled "Professional C# 5.0".
Below is the complete program embedded in a test fixture. The Assert statement should return a value larger than 0; but it does not. I'm baffled as to why. Please assist. The code below is self contained: create a new class library project and make sure you have a reference to NUnit to run the unit test. On the other hand if you are an expert you can likely just read the code and give me feedback.
using System;
using System.Reflection;
using NUnit.Framework;
namespace GameDesigner.Sandbox.TestFixtures
{
[TestFixture]
internal class DeclarativeAttributesTestFixture
{
[Test]
public void UseReflectionToFindNumericAttributes()
{
Assembly theAssembly = typeof (PhaseState).Assembly;
Assert.AreEqual("GameDesigner.Sandbox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
theAssembly.FullName);
Attribute[] supportingAttributes = Attribute.GetCustomAttributes(theAssembly, typeof(OptimizableNumeric));
Assert.IsTrue(supportingAttributes.Length > 0, "supportingAttributes was length: " + supportingAttributes.Length);
}
}
public class PhaseState
{
public PhaseState(double temperatue, int pressure, string state)
{
Temperature = temperatue;
Pressure = pressure;
State = state;
}
[OptimizableNumeric(0.0, 300.0, 1.0)] public double Temperature;
[OptimizableNumeric(1.0, 10.0, 1.0)] public int Pressure;
[OptimizableNominal(new string[] {"solid", "liquid", "gas"})] public string State;
}
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class OptimizableNumeric : Attribute
{
private readonly double _start;
private readonly double _stop;
private readonly double _stepSize;
public OptimizableNumeric(double start, double stop, double stepSize)
{
_stepSize = stepSize;
_stop = stop;
_start = start;
}
public double Start
{
get { return _start; }
}
public double Stop
{
get { return _stop; }
}
public double StepSize
{
get { return _stepSize; }
}
}
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public class OptimizableNominal : Attribute
{
private readonly string[] _nominalList;
public OptimizableNominal(string[] nominalList)
{
_nominalList = nominalList;
}
public string[] NominalList
{
get { return _nominalList; }
}
}
}
I have tried many different examples of how to retrieve the custom attributes and none of them have generated results. As I am copying from the textbook without any understanding of what I am doing the code is hard for me to diagnose.
Attribute[] GetCustomAttributes(Assembly element, Type attributeType) method returns array of the custom attributes applied to an assembly. Attributes applied to assembly look like
[assembly: AssemblyCompany("Microsoft")]
Your attribute is not applied to assembly. Use following code to get custom attributes applied to State field:
var memberInfo = typeof(PhaseState).GetField("State");
Attribute[] supportingAttributes =
Attribute.GetCustomAttributes(memberInfo, typeof(OptimizableNominalAttribute));
If you want to check all public members in assembly which have this attribute, you can use following query:
var attributeType = typeof(OptimizableNominalAttribute);
var supportingAttributes = theAssembly.GetTypes()
.SelectMany(t => t.GetMembers()) // you can pass binding flags here
.SelectMany(m => Attribute.GetCustomAttributes(m, attributeType));
Query syntax:
var supportingAttributes =
from t in theAssembly.GetTypes()
from m in t.GetMembers()
from a in Attribute.GetCustomAttributes(m, attributeType)
select a;
i'm trying to generate Code at runtime which uses a custom class from another Namespace.
Here´s my code:
namespace Test.Programm.Network
{
class Handler
{
public void CreateAssembly()
{
string[] code =
{
#"using System;
using System.Collections;
namespace Test.Programm.Network
{
class HandleMessage
{
protected static internal Queue _queue;
public static void Received(string message)
{
lock (_queue)
{
_queue.Enqueue(message);
}
}
public HandleMessage()
{
_queue = new Queue();
}
}
}"
};
CompilerParameters parms = new CompilerParameters();
parms.GenerateExecutable = false;
parms.GenerateInMemory = true;
CodeDomProvider compiler = null;
compiler = CodeDomProvider.CreateProvider("CSharp");
CompilerResults compilerResults = compiler.CompileAssemblyFromSource(parms, code);
var cls = compilerResults.CompiledAssembly.GetType("Test.Programm.Network.HandleMessage");
Assembly assembly = compilerResults.CompiledAssembly;
var newHandler = assembly.CreateInstance(compilerResults.CompiledAssembly.GetType("Test.Programm.Network.HandleMessage").ToString());
}
}
}
But i don´t want to pass a string to my function, i want to pass an own type to that function.
Now i have a simple message class like that:
namespace Test.Programm.Messages
{
public class Message<T>
{
string _message;
}
}
if i want too add a using Test.Programm.Messages to the code i want to generate, i´m getting error that this Namespace doesn´t exist, missing reference...
I tried to add parms.ReferencedAssemblies.Add("Grid.Node.Messages"); to the code Generation, but this doesnt work. searching the web and SO haven´t given an answer yet -.-
Thanks for your help.
You should reference the assembly instead of the namespace. Something like this:
parms.ReferencedAssemblies.Add("path_to.dll");
Where path_to.dll is a path to the assembly file containing type Message<T>.
My previous post contains attempt use attribute-free (convention based) approach to configure MEF: MEF 2: import many.
But it contains export metadata attribute usage in the class PluginMetadataAttribute needed for lazy initialization plugin by condition (specific name, version).
How to get rid of ExportAttribute dependency?
I found three solution.
Solution 1 (using class constant fields, poor solution):
public class Plugin1 : IPlugin
{
public const string Name = "Plugin1";
public const string Version = "1.0.0.0";
public void Run()
{
Console.WriteLine("Plugin1 runed");
}
}
// ...
var builder = new RegistrationBuilder();
builder
.ForTypesDerivedFrom<IPlugin>()
.Export<IPlugin>(exportBuilder => {
exportBuilder.AddMetadata("Name", t => t.GetField("Name").GetRawConstantValue());
exportBuilder.AddMetadata("Version", t => t.GetField("Version").GetRawConstantValue());
});
Solution 2 (using class properties, poor solution):
public interface IPluginMetadata
{
string Name { get; }
string Version { get; }
}
public interface IPlugin : IPluginMetadata
{
void Run();
}
public class Plugin1 : IPlugin
{
public string Name { get { return "Plugin 1"; } }
public string Version { get { return "1.0.0.0"; } }
public void Run()
{
Console.WriteLine("Plugin1 runed");
}
}
And get properties values by method described this: https://stackoverflow.com/a/11162876/1986524
Solution 3 (using attributes, better but not all happy):
using System;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;
using System.Reflection;
namespace MEF2
{
public interface IPluginMetadata
{
string Name { get; }
string Version { get; }
}
public interface IPlugin
{
void Run();
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginMetadataAttribute : Attribute, IPluginMetadata
{
public string Name { get; set; }
public string Version { get; set; }
public PluginMetadataAttribute(string name, string version)
{
Name = name;
Version = version;
}
}
[PluginMetadata("Plugin1", "1.0.0.0")]
public class Plugin1 : IPlugin
{
public void Run()
{
Console.WriteLine("Plugin1 runed");
}
}
[PluginMetadata("Plugin2", "2.0.0.0")]
public class Plugin2 : IPlugin
{
public void Run()
{
Console.WriteLine("Plugin2 runed");
}
}
class Program
{
static void Main(string[] args)
{
var builder = new RegistrationBuilder();
builder
.ForTypesDerivedFrom<IPlugin>()
.Export<IPlugin>(exportBuilder => {
exportBuilder.AddMetadata("Name", t => t.GetCustomAttribute<PluginMetadataAttribute>().Name);
exportBuilder.AddMetadata("Version", t => t.GetCustomAttribute<PluginMetadataAttribute>().Version);
});
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly(), builder);
using (var container = new CompositionContainer(catalog, CompositionOptions.DisableSilentRejection)) {
var plugins = container.GetExports<IPlugin, IPluginMetadata>();
foreach (var plugin in plugins) {
Console.WriteLine("{0}, {1}", plugin.Metadata.Name, plugin.Metadata.Version);
plugin.Value.Run();
}
}
}
}
}
Solution 3 contains problem in this code:
.Export<IPlugin>(exportBuilder => {
exportBuilder.AddMetadata("Name", t => t.GetCustomAttribute<PluginMetadataAttribute>().Name);
exportBuilder.AddMetadata("Version", t => t.GetCustomAttribute<PluginMetadataAttribute>().Version);
})
Problems:
Can't cancel add metadata in case of missing metadata
Duplicate code t.GetCustomAttribute<PluginMetadataAttribute>()
Export<> don't provided filter
If anyone knows of other solutions please write.
The An Attribute-Free Approach to Configuring MEF article that you reference in your other question includes an example on how to add metadata without using an attribute.
The example shows a use of the PartBuilder.ExportProperties overload that takes an Action<PropertyInfo, ExportBuilder> as a parameter and use one of the ExportBuilder.AddMetadata overloads to add metadata for the specific export.
This is not the only way to add metadata. All export methods of PartBuilder have an overload that take an Action<> (or an Action<,>) with an ExportBuilder param. You can use these overloads and add your metadata in a similar way.
I keep getting the following error with the following code:
Error: "No exports were found that match the constraint:
ContractName MefTestSample.Contracts.ICanDoSomethingImportant"
Program.cs is as follows:
namespace MefTestSample
{
class Program
{
private static CompositionContainer m_Container;
static void Main(string[] args)
{
UseMockedUpTypes();
ICanDoSomethingImportant cool = m_Container.GetExport<ICanDoSomethingImportant>().Value;
cool.DoSomethingClever();
Console.ReadLine();
}
private static void UseMockedUpTypes()
{
//The commented out section works just by itself.
/*
m_Container =
new CompositionContainer(
new AssemblyCatalog(
typeof(MefTestSample.Mocks.ClassesDoNotNecessarly).Assembly));
*/
//This fails if i dont comment out the [ImportingConstructor] block.
var assemblyCatalog1 = new AssemblyCatalog(typeof (MefTestSample.Mocks.ClassesDoNotNecessarly).Assembly);
var myassembly = new AssemblyCatalog(typeof (ServiceLibrary.MoonService).Assembly);
var aggregateCatalog = new AggregateCatalog(assemblyCatalog1, myassembly);
m_Container = new CompositionContainer(aggregateCatalog);
}
}
}
Below is the code for ClassesDoNotNecessarly:
namespace MefTestSample.Mocks
{
[Export(typeof(ICanDoSomethingImportant))]
public class ClassesDoNotNecessarly : ICanDoSomethingImportant
{
//private IServicesContract _isc;
#region ICanDoSomethingImportant Members
/* This seems to be causing the problem.
[ImportingConstructor]
public ClassesDoNotNecessarly([Import("Moon")]IServicesContract isc)
{
string temp = isc.DisplayMessage();
Console.WriteLine(temp);
}
*/
public void DoSomethingClever()
{
Console.WriteLine("Hehe, I'm actually procrastinating!");
}
#endregion
}
}
MoonService is as follows:
namespace ServiceLibrary
{
[Export(typeof(IServicesContract))]
public class MoonService : IServicesContract
{
public string DisplayMessage()
{
return "Moon services were accessed.";
}
}
}
What i believe the problem is. If i leave program.cs as it is, and comment out the [ImportingConstructor] attribute + constructor in the ClassesDoNotNecessarly class, the program will display the text in that class.
If i uncomment the [ImportingConstructor] attribute n constructor, i then start getting the error shown at the top of this question.
Any ideas would be appreciated.
Remove the [Import("Moon")] in the constructor, the Export is not named, so the Import cannot be named in return.