Getting started with source analyzers

I'm working on a rather large project and to keep myself from making certain mistakes in the future I need to make sure I never create fields or properties of certain types, with some specific exceptions. Since there is nothing in the language to enforce this I thought I could maybe write a code analyzer for this. However I have never written an analyzer before. Although I can easily find all the technical details online, I have some questions regarding conventions/convenience for the structure of the project(s). I will be using the analyzer exclusively in a single solution (class library and some small console applications spanning many projects, 100k+ lines of code) and will need to use types defined in this solution. Should I create my analyzers inside this solution or outside of it? Do I put all analyzers in a single project or do I keep them separate from each other? Does anyone have any general advice for someone getting started with this, any pitfalls to avoid?
10 Replies
Thinker
Thinker7mo ago
and will need to use types defined in this solution
You can't really do this
HerpeDerpeDerp
HerpeDerpeDerp7mo ago
Hmm Are there any ways to work around this? Essentially I do not want to allow fields of types that implement a certain interface unless those fields have a specific attribute Or something along those lines I would be fine with declaring these interfaces and attributes outside the main project/solution
Unknown User
Unknown User7mo ago
Message Not Public
Sign In & Join Server To View
viceroypenguin
viceroypenguin7mo ago
for the record, there is this: https://github.com/dotnet/roslyn-analyzers#microsoftcodeanalysisbannedapianalyzers; you can ban certain classes/methods/etc, and then selectively #pragma that error off in certain areas to allow it in limited case.
HerpeDerpeDerp
HerpeDerpeDerp7mo ago
Thanks, I was not aware of that. However, I'm not interested in the public api, but rather in the private fields
viceroypenguin
viceroypenguin7mo ago
correct. but this analyzer will prevent you from using fields of certain types, if you would like
HerpeDerpeDerp
HerpeDerpeDerp7mo ago
Which rule would that be? I don't see it And I need some weird rather specific rules. The types are allowed as type parameters of fields of certain generic types, but not of other generic types, for example
viceroypenguin
viceroypenguin7mo ago
https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/Microsoft.CodeAnalysis.BannedApiAnalyzers.md; mark RS0030 as an error, and create a .txt file for the analyzer to use to ban types. that's... what are you doing? $xy
MODiX
MODiX7mo ago
The XY Problem
Asking about your attempted solution rather than your actual problem
HerpeDerpeDerp
HerpeDerpeDerp7mo ago
I'm making a game and in that game there are several types of objects that exist for some time but can be removed at some point. Many of these objects will have references to each other, such as a projectile keeping track of who fired it because it needs that information when it hits something. It is in theory possible to not have such references between objects, but I don't really see any method that doesn't have major drawbacks. Problems arise when such objects get removed as references to these removed objects might still exist somewhere and attempting to do things with these removed objects through those references can lead to all kinds of bugs. Simply flagging the instance as being removed and always checking the flag before doing something with the instance seems like an easy solution, although still very easy to miss some of the (tens of) thousands of checks, but many of those instances will be reused to avoid memory allocations and a simple flag won't do. The solutions I'm currently leaning towards is giving each instance a version number which is incremented each time the instance is removed. Whenever one objects stores a reference to another objects, it does this wrapped in a struct that stores the current version number (as of making the struct) of the referenced instance with it. If I try to get the reference from the struct, it first compares the current version against the stored version number and if they are not equal it won't give me the reference but instead just null. It is still perfectly fine for functions to pass references to each other directly without the wrappers because the removal of objects only happens at very specific moments so it suffices that those unwrapped references are just never stored. But I don't trust myself to never mess this up and accidentally store an unwrapped reference somewhere leading to a possibly very hard to find bug Regardless of whether or not this is a good way of solving the problem of references to removed objects lingering around, I would also just like to learn a bit about things like analyzers and source generators in general as I've never used them yet. That's why I asked specifically about analyzers. Even if they're not a good solution to the problem, I would still like to know if/how they could be used