Get style settings in Roslyn Fixer - c#

At this moment I'm writing Roslyn analyzer with fixer that checks if constructor arguments are checked for null. And if not, fixer will add that check.
So, basically for code
public Foo(string param1)
{
}
it should produce
public Foo(string param1)
{
if (param1 == null)
throw new ArumentNullException(nameof(param1));
}
But in some cases code style could be different and it is desired to get "if" with braces like
public Foo(string param1)
{
if (param1 == null)
{
throw new ArumentNullException(nameof(param1));
}
}
or even (not sure if there any settings for that, maybe I'll add separate fixer for it):
public Foo(string param1)
{
this.param1 = param1 ?? throw new ArumentNullException(nameof(param1));
}
So, there are many options for a possible fix and I wonder - is it possible to read code style settings inside of fixer? I've checked
DocumentOptionSet options = context.Document.GetOptionsAsync()
but didn't found how to use it properly and not even sure that it is what I need.
Will appreciate any tips and ideas
UPDATE 1
So, seem that I found how it is supposed to work:
options.GetOption(CodeStyleOptions.QualifyFieldAccess);
And I can see (CSharpCodeStyleOptions.cs) with needed option PreferBraces. However, this class is internal and I can't use it in fixer.
UPDATE 2
I tried Simplifier.ReduceAsync() method, but seem that it doesn't do what I need.
UPDATE 3
So, it seems that we all treat that property wrong, because of its naming.
Here is my issue in Roslyn github. The main idea - "Prefer braces" option should be named "Require braces". New issue for this change.
And it is not supposed to get these settings from analyzer/fixer code.

Related

How can I provide a tab completion list, with the values generated on powershell startup?

So I have a cmdlet written in c#: Get-LivingCharacter. I want users to use this like Get-LivingCharacter -Name "Bran", but I would like to allow for the list of available characters to change. Maybe today, "Bran" is a valid name to pass in for Get-LivingCharacter, but maybe in the future it will not be. Things happen.
For convenience I want to allow tab-completion of this field. However, I can't seem to get that to work for non-const data sets. Dynamic fields don't even auto-complete the field name, nevermind the value, and I don't know a way to implement this for a non-dynamic field. Conceptually, I could generate a .ps1 file on startup given the current data set, and then load that ps1 as the module, but this feels a bit like killing a pup with a greatsword - lots of overkill. Is there a better option?
I had already implemented a similar function to the DynamicParam helper function, as reference in the comments. However, tab completion wasn't working. I was writing a minimal reproduction example, when...my tab completion worked.
It turns out, it reproducibly works/breaks based on the inclusion of a WriteDebug statement:
[Cmdlet("Get", "LivingCharacter")]
public class GetLivingCharacter : Cmdlet, IDynamicParameters
{
protected override void ProcessRecord()
{
}
public object GetDynamicParameters()
{
WriteDebug("Getting names"); // Tab completion won't work with this here - comment it out and it works.
^^^^^^^^^^
var chars = new List<String>() { "Bran", "Arya" };
var dict = new RuntimeDefinedParameterDictionary();
var attributes = new Collection<Attribute>
{
new ParameterAttribute
{
HelpMessage = "Enter a valid open name",
Mandatory = true
},
new ValidateSetAttribute(chars.ToArray()),
};
dict.Add("Name", new RuntimeDefinedParameter("Name", typeof(string), attributes));
return dict;
}
}
After some digging, the WriteDebug statement is throwing (which I assume is because it can't output while I'm typing). It then recreates the GetLivingCharacter class after I've finished the command to validate. It took a while to find since, because of the issue, I can't write the error to the console, so I had to append to a temp file instead.

Get SequencePoint for CustomAttribute in Fody/Mono.Cecil

I am writing a Fody Addin and I am able to inject my code and to provide error messages to the user. I am able to determine the sequence points of instructions, but I cannot find a way to find the sequence points of CustomAttributes.
I need to get this information to provide the debugger a hint where to find the location of an error, in case that an attribute has been applied wrongly.
So basically I have something like this:
[MyAttribute]
public void Test()
{
}
Now I want to get the SequencePoint of the MyAttribute attribute.
**Edit: ** As I got down voted (without any information why) here some additional information. I can access the sequence point of instructions like this:
public static SequencePoint GetSP(MethodDefinition method)
{
return method.Body.Instructions
.Where(instruction => instruction.SequencePoint != null)
.Select(instruction => instruction.SequencePoint)
.FirstOrDefault();
}
That works fine for instructions but when I access an attribute I am not sure how to get the sequence point:
public static SequencePoint GetSP(MethodDefinition method)
{
var attribute = method.CustomAttributes.First();
// what to enter here to get SequencePoint of attribute?
}
this is not possible. attributes dont have sequence points. i suggest you just use the first sequencepoint for the method instead

c# CommandLine.Parser - Use constructor that accepts Action<ParserSettings>

I was using this code, but I am getting a compiler warning that this method of creation is deprecated. As I want to remove the warning, and move to the newer version, I want to correct the code, but I can not get the CommandLineParser 1.9.7 library to work.
CommandLine.Parser OptionParser = new CommandLine.Parser(new CommandLine.ParserSettings
{
CaseSensitive = UseCaseSensitive,
IgnoreUnknownArguments = IgnoreUnknownOptions,
MutuallyExclusive = EnableMutuallyExclusive
}
);
bool Result = OptionParser.ParseArguments(Args, this);
This code works and Result would be True/False based on the parameters of the command line and options passed. However, the following warning is posted.
Warning 1 'CommandLine.Parser.Parser(CommandLine.ParserSettings)' is obsolete: 'Use constructor that accepts Action<ParserSettings>.'
The Online help shows this as an example for using the function.
new CommandLine.Parser(configuration: () => new CommandLine.ParserSettings(Console.Error))
I tried changing the code, but I am not getting the Lambda right, and am not sure how to get this to work. While the code executes, I only get the default functions, I can not seem to change the Case Sensitive, Mutually Exclusive, etc... options.
Line using the Constructor (from the inline IDE help)
bool Result = new CommandLine.Parser(configuration: (Settings) => new CommandLine.ParserSettings(UseCaseSensitive, EnableMutuallyExclusive, IgnoreUnknownOptions, null)).ParseArguments(Args, this);
Trying again with the virtual settings:
bool Result = new CommandLine.Parser(configuration: (Settings) => new CommandLine.ParserSettings
{
CaseSensitive = UseCaseSensitive,
IgnoreUnknownArguments = IgnoreUnknownOptions,
MutuallyExclusive = EnableMutuallyExclusive
}
).ParseArguments(Args, this);
The online help has not kept up with the tool, and I could use any pointers someone might have. Thanks in advance...
Looking at the source code the constructor runs that Action passed on new settings that it creates:
public Parser(Action<ParserSettings> configuration)
{
if (configuration == null) throw new ArgumentNullException("configuration");
this.settings = new ParserSettings();
configuration(this.settings);
this.settings.Consumed = true;
}
So in the Action<ParserSettings> you should set the values you want on the parameter, not create new settings (remember that an Action<T> is a prototype for a function that takes a T and does not return a value):
var parser = new CommandLine.Parser( s =>
{
s.CaseSensitive = UseCaseSensitive;
} );
NOTE: The source code I linked to does not appear to be the same version as you are using since Parser( ParserSettings ) is marked internal in the source I found, which means you wouldn't even be able to call it, and some of the ParserSettings properties do not appear in the version I found. However, I believe this answer applies to the version you have as well.

Why do I have to manually create ExpandoObject to properly use the dynamic keyword?

I was looking at the question Use 'dynamic' throw a RuntimeBinderException. I face a similar problem:
Basically, I want to create a "HTML helper" in ASP.NET MVC that uses dynamic arguments, akin to the htmlArguments parameter for many of the existing helpers (more code below):
public BootstrapCell(Action<string> emitContentAction, dynamic args)
View:
#using (grid.Cell(ViewContext.Writer.Write, new {Position = 4}))
{
<p>zomg!</p>
}
However in the naive approach, i get RuntimeBinderException thrown at me, declaring that 'object' does not contain a definition for 'Position', even though when debugging and hovering over the _args variable, it clearly does have a Position property.
The caller and the callee are in separate assemblies. Why is that problem happening?
(The solution to that has been shown in the same question: Manually create an ExpandoObject to hold the args.)
Implementation:
public class Cell
{
private readonly string _tagName;
private dynamic _args;
private Action<string> EmitContentAction;
public BootstrapCell(Action<string> emitContentAction, dynamic args) : DisposableBaseClass
{
_args = args;
EmitContentAction = emitContentAction;
OnContextEnter();
}
protected void OnContextEnter()
{
var sb = new StringBuilder("<");
sb.Append(_tagName);
if (_args.Position > 0)
{
sb.Append(" class=\"offset");
sb.Append(args.Position);
sb.Append("\"");
}
sb.Append(">");
EmitContentAction(sb.ToString());
}
}
[Edited to make clearer that my problem arises when "obviously" the Position property is set. I am aware that if the property never was defined in the first place, an exception must be raised.]
That code is fatally flawed.
It does work, as long as you specify that property:
void Bar()
{
Foo(new {Position = 0});
}
void Foo(dynamic args)
{
Console.WriteLine(args.Position);
}
That will output 0, it will not throw a RuntimeBinderException.
But the purpose of such code is the possibility for the caller to specify only the properties needed and omit the rest.
You are trying to check for this omission via if(args.Position != null). But that doesn't work, it already requires Position to exist.
When you have a look at the routing API of ASP.NET that also supports those anonymous configuration objects you will notice that the type of the parameter is object and not dynamic.
Using object instead of dynamic will enable your API to be used across assembly boundaries.
So how does it work?
Just like in the linked answer, you need to manually create a dictionary of the properties. Whether you use a plain old Dictionary<string, object> or an ExpandoObject is a matter of preference.
Using ExpandoObject will make your code a bit simpler to read and write, but it is not required.
About the actual exception you are getting:
Please note that it tells you it can't find the Position property on object. If it would be an anonymous type that was missing the Position property the exception message wouldn't refer to object but to an anonymous type. Something like this:
'<>f__AnonymousType0' does not contain a definition for 'Position'

Can I write code with an expiration date?

I just had this idea for something that I'd love to be able to use:
Let's say I have to fix a bug and I decide to write an ugly code line that fixes the immediate problem - but only because I promise myself that I will soon find the time to perform a proper refactoring.
I want to be able to somehow mark that code line as "Expired in" and add a date - so that if the code is compiled some time after that date there will be a compilation error/warning with a proper message.
Any suggestions? It must be possible to perform - maybe using some complicated #IF or some options in visual studio?
I'm using VS 2005 - mainly for C#.
Mark the code with the System.ObsoleteAttribute attribute, you'll get a compiler warning, which will nag you to fix the code
[Obsolete("You've an ugly hack here")]
public void MyUglyHack()
{
...
}
Alternatively . . .
Write your own attribute, passing it an expiration date on the constructor, in the constructor throw an exception if DateTime.Now >= expirationDate.
The compile will fail until you fix the code (or more likely increase the expiration date, or far more likely you just remove the Attribute.
oooohhh - this is 'orrible. try this for a giggle:
[AttributeUsage(AttributeTargets.All)]
public class BugExpiryAttribute : System.Attribute
{
// don't tell 'anyone' about this hack attribute!!
public BugExpiryAttribute(string bugAuthor, string expiryDate)
{
DateTime convertedDate = DateTime.Parse(expiryDate);
Debug.Assert(DateTime.Now <= convertedDate,
string.Format("{0} promised to remove this by {1}",
bugAuthor, convertedDate.ToString("dd-MMM-yyyy")));
}
}
then, decorate your method/class etc:
[BugExpiryAttribute("Jack Skit", "2011-01-01")]
public static void Main(string[] args)
{
...
}
... nasty :-)
[DISCLAIMER] - created in the name of academic interest, not production code finese!!
[edit] - just to clarify, code compiled and in production will continue to run on/after the 'bugExpriryDate'. only once the code is run in the compiler (on/after the date), will the warning message be raised (debug.assert). just thought it worth making that distinction - cheers MartinStettner.
[caveat] - if used in classes/methods etc would need to be read via reflection. however (and this is interesting) will work straight off in the compiler if used on sub Main(). how strange!! (thanks for the nod Hans...)
I think this is the reason Visual Studio has a Task List. Add the comment:
\\ TODO: Fix this spaghetti by 01APR11
and it will show up like this
.
the keywords are configurable from the options
You could write comment lines in the form
// Expires on 2011/07/01
and add a prebuild step which does a solution-wide replace of these lines by something like
#error Code expired on 2011/07/01
for all lines that contain a date before the current day. For this prebuild step you would need to write a short program (probably using regular expressions and some date comparision logic)
This step could also be performed by a VS macro, which allows for easier access to all files fo the solution but has the disadvantage that it must be installed and run on all VS installations where your project is compiled.
One more option if you have unit tests for your code you can time bomb the tests that verifies your fix. This way you don't introduce strange checks in your production code.
Also I think the best option if you have to put in hack (you've probably already spent enough time looking at it to fix properly... but still want a hack there) than open bug/create task/work item (whatever you use to track future work) and decide if you want to fix it later.
Well it doesn't do exactly what you're asking for but you could use a Debug.Assert() method call which would alert you (in Debug only) that the code has expired. One benefit would be that it wouldn't inadvertently affect your production code (compilation or execution) but would be sufficiently annoying in Debug for you to want to correct it.
// Alert the developer after 01/07/2011
Debug.Assert(Date.Now < new DateTime(2011, 7, 1))
With .NET 6+ this is quite simple when a source generator is used:
Source generator
[Generator]
public class ObsoleteFromDateSourceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
var currentDate = DateTime.Now.Date;
ImmutableArray<AttributeSyntax> attributes = context.Compilation
.SyntaxTrees.SelectMany(s => s.GetRoot().DescendantNodes())
.Where((d) => d.IsKind(SyntaxKind.Attribute))
.OfType<AttributeSyntax>()
.Where(d => d.Name.ToString() == SourceGeneratorConstants.ObsoleteFromDateAttributeName)
.ToImmutableArray();
foreach (var attribute in attributes)
{
try
{
var semanticModel = context.Compilation.GetSemanticModel(attribute.SyntaxTree);
var argumentDate = attribute.ArgumentList?.Arguments.FirstOrDefault()?.Expression;
var argumentMessage = attribute.ArgumentList?.Arguments.Skip(1).FirstOrDefault()?.Expression;
if (argumentDate != null)
{
var date = DateTime.ParseExact(semanticModel.GetConstantValue(argumentDate).ToString(), SourceGeneratorConstants.ObsoleteFromDateAttributeDateFormat, CultureInfo.InvariantCulture);
string? message = null;
if (argumentMessage is not null
&& semanticModel.GetConstantValue(argumentMessage) is Optional<object> tmp
&& tmp.HasValue)
{
message = tmp.Value?.ToString();
}
var diagnostic = Diagnostic.Create(DiagnosticDescriptors.ObsoleteFromDate(message, /*isWarning:*/ currentDate < date), attribute.GetLocation());
context.ReportDiagnostic(diagnostic);
}
else
{
throw new ArgumentNullException(paramName: "date");
}
}
catch (Exception ex)
{
var diagnostic = Diagnostic.Create(DiagnosticDescriptors.ObsoleteFromDateError(ex.Message), attribute.GetLocation());
context.ReportDiagnostic(diagnostic);
}
}
}
public void Initialize(GeneratorInitializationContext context)
{
}
}
Some constants
public static class SourceGeneratorConstants
{
public const string ObsoleteFromDateAttributeName = "ObsoleteFromDate";
public const string ObsoleteFromDateAttributeDateFormat = "yyyy-MM-dd";
}
Build diagnostics
public static class DiagnosticDescriptors
{
public const string Category = "BNX";
public const string ErrorTitle = "Source code error";
public static readonly Func<string?, bool, DiagnosticDescriptor> ObsoleteFromDate = (message, isWarning) => new DiagnosticDescriptor(
id: $"{Category}{SourceGeneratorConstants.ObsoleteFromDateAttributeName}",
title: "Obsolete code",
messageFormat: message ?? "Obsolete code, please review",
category: Category,
defaultSeverity: isWarning ? DiagnosticSeverity.Warning : DiagnosticSeverity.Error,
isEnabledByDefault: true);
public static readonly Func<string?, DiagnosticDescriptor> ObsoleteFromDateError = (message) => new DiagnosticDescriptor(
id: $"{Category}{SourceGeneratorConstants.ObsoleteFromDateAttributeName}",
title: ErrorTitle,
messageFormat: $"Unable to parse {SourceGeneratorConstants.ObsoleteFromDateAttributeName} attribute because of error: {message} Expecting the following syntax: [{SourceGeneratorConstants.ObsoleteFromDateAttributeName}(\"{SourceGeneratorConstants.ObsoleteFromDateAttributeDateFormat}\", \"message\")]",
category: Category,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
}
A custom attribute
/// <summary>
/// Triggers a build error at and after a specific system date. Source generators must be included in the project.
/// </summary>
public class ObsoleteFromDateAttribute : Attribute
{
public const string DateFormat = "yyyy-MM-dd";
/// <summary>
/// Build error message.
/// </summary>
public string Message { get; }
/// <summary>
/// System date at and after which the build error should occur.
/// </summary>
public string Date { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ObsoleteFromDateAttribute"/> class.
/// </summary>
/// <param name="date">Required. System date at and after which the build error should occur. Expected format: <see cref="DateFormat"/></param>
/// <param name="message">Optional. Build error message.</param>
public ObsoleteFromDateAttribute(string date, string message): base()
{
Date = date;
Message = message;
}
}
And finally a test:
// The following should result in a build error
[ObsoleteFromDate("2000-01-01", "We are waiting for some Jira thing 1 to complete")]
public class ObsoleteClass1
{
}
// The following should result in a build warning
[ObsoleteFromDate("3000-01-01", "We are waiting for some Jira thing 2 to complete")]
public class ObsoleteClass2
{
}
Without controlling the compiler (possible in the 5.0 timeframe with compiler as a service?), you are not going to have your code expire. You can mark the code as deprecated, or use the Obsolete attribute, or similar, to fire off a warning, but people can ignore warnings (many devs I have met have not learned the rule that warnings are errors).
I think it is a lot of work to try to protect people from themselves. It is even harder when you are protecting them from themselves in the future. Mark the code as a kludge and leave it at that.
Instead of embedding a time bomb, perhaps consider applying a BUGBUG: comment?
Rather than forcing you or someone else to fix code that may be kind of unsightly but works as expected down the road, you can just do a solution-wide search and find the ugly bits when you decide it's time to get down and refactor the really ugly stuff.
Track it in a bug instead. Then it can be properly scheduled and prioritized with other refactoring work.
TODO comments in code can have a tendency to be lost and forgotten. Throwing a compiler error after a particular date will likely lead to that date being pushed forward, or the comment/attribute removed.
I hope i can help with this. take 2 datetimepicker on tool box. And just convert 1 datetimepicker.
private void expired()
{
DateTime expired = DateTime.Parse(Convert.ToDateTime(datetimepicker1.Text).ToString());
DateTime compare = DateTime.Parse(Convert.ToDateTime(datetimepicker2.Text).ToString());
if(expired < compare)
{
MessageBox.Show("This product is expired!");
}
else
}
MessageBox.Show("This product is not expired");
{
}
Both TIME and DATE emit strings and, to my knowledge, there is no way to parse them out at the preprocessing stage.
There are a few methods you can easily do in code to ensure that the code at least warns you at run time. Including an assert is one way, putting in a code comment also works, but the way I handle it is through including a doxygen comment with a note explaining that the function contains a hack, bug, or performance issue that needs to be resolved. This ends up getting filtered by many programmers and is easily viewable on the website for myself or other people to fix.

Categories

Resources