Add analyzer for banning APIs in analyzers#6115
Conversation
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## main #6115 +/- ##
==========================================
- Coverage 96.05% 96.03% -0.03%
==========================================
Files 1343 1348 +5
Lines 309713 309832 +119
Branches 9956 9967 +11
==========================================
+ Hits 297490 297533 +43
- Misses 9822 9898 +76
Partials 2401 2401 |
Did you mean to link it into |
Yes, this is going to be a problem of lot of false positives. Basically large number of Roslyn based command line tools will start seeing these warnings. I think a better approach would be to err on the side of few false negatives. I see couple of alternatives:
We would also need to support a syntax to allow specifying banned scope as a type hierarchy such that usage is banned within a type and all it's sub-types AND specifying banned scope as an interface name such that usage is banned within types whose AllInterfaces contains the interface.
Both these approaches will have false negatives if the APIs that you intend to ban are used in helper methods in some shared static/helper types, but I think that will likely be rare and acceptable false negatives. |
|
One other approach comes to mind, which might be good 2-step solution to avoid both false positives and false negatives:
Note that we use similar 2-step approach to enable whole bunch of Release tracking analyzer rules for analyzer projects. We have this special helper diagnostic to enable the analyzer, it's code fix adds new additional files to the analyzer project, which in turn causes the real release tracking analyzer rules to light up. You can see a small demo of the experience below: |
Yes, I totally mixed these up, thanks. |
That's a very promising approach. I imagine that giving a single explanatory warning would be less overwhelming to have suddenly appear in your build, compared to a potentially very large number of banned API diagnostics. So it seems pretty likely that the right thing would be to ask the author to either "flip a switch" to get the whole set of diagnostics, or just suppress the one if they want to be left alone. (edit: I had more thoughts here but realized they were redundant with what you already wrote..) |
|
@RikkiGibson Scanning entire project for presence of analyzers or generators would mean this can only be a CompilationEnd diagnostic, i.e. build-only diagnostic. Using an MsBuild property or additional file entry as this flip would enable us to have this reported even for open files, which is a requirement for code fix support for diagnostics. We don’t support code fixes for build-only diagnostics. |
|
Got it. It sounds like it would be best to just report the "please explicitly opt in or out of banned API checks" diagnostic whenever we encounter an analyzer/generator type, and it's OK if that results in many similar diagnostics being reported in the project. |
|
Yes. And I now think it would be best to have a boolean editorconfig option for user’s choice. |
|
Marking draft while I work on implementing the design described in the above thread |
|
@mavasani Please let me know if the general design seems to be going in the right direction. Later on I will add more coverage, e.g. when generators are present. I will also fill out the BannedSymbols text file for what we'd like to ban as a starting point. |
|
Talked to @jaredpar about the design and the main concern was the evolvability of adding potentially disruptive analyzer diagnostics over time. Basically, the question is, should we instead have a single this is an analyzer project setting, versus having disparate enforce analyzer banned APIs, along with possible future enforce analyzer coding pattern X settings. The feeling was that having a single setting for all time to opt in to all the analyzer diagnostics we think are appropriate might be the way to go, and if people have issues with specific enforcements within that, then they can just suppress the specific diagnostics by ID in whatever scope they deem appropriate. |
mavasani
left a comment
There was a problem hiding this comment.
This looks great! I have minor suggestions, feel free to address/ignore as appropriate.
| <value>The symbol '{0}' is banned for use by analyzers{1}</value> | ||
| </data> | ||
| <data name="SymbolIsBannedInAnalyzersTitle" xml:space="preserve"> | ||
| <value>Do not used APIs banned for analyzers</value> |
There was a problem hiding this comment.
| <value>Do not used APIs banned for analyzers</value> | |
| <value>Do not use APIs banned for analyzers</value> |
| compilationContext.RegisterSymbolAction(analyzePossibleAnalyzerOrGenerator, SymbolKind.NamedType); | ||
|
|
||
| const string fileName = "Microsoft.CodeAnalysis.Analyzers.AnalyzerBannedSymbols.txt"; | ||
| var stream = typeof(SymbolIsBannedInAnalyzersAnalyzer<>).Assembly.GetManifestResourceStream(fileName); |
There was a problem hiding this comment.
Another option is to package this text file as an additional file in the NuGet package, and have the analyzer access it via AdditionalFiles on the analysis context. This approach has the advantage that the user has the option to hand edit the txt file within the downloaded NuGet package on their machine to include/exclude additional banned APIs in it for customized experience.
In case you would like to take this route: We have post-build tooling in the repo that generates package specific targets file and embeds it into the NuGet package. You can add the ItemGroup to always add this txt file as an additional file by appending to the below string:
roslyn-analyzers/src/Tools/GenerateDocumentationAndConfigFiles/Program.cs
Lines 1560 to 1575 in d80e535
You would then need to update https://github.com/dotnet/roslyn-analyzers/blob/main/nuget/Microsoft.CodeAnalysis.Analyzers/Microsoft.CodeAnalysis.Analyzers.Package.csproj to include this file as an AnalyzerNupkgFile, similar to
Feel free to ignore this suggestion completely :-)
There was a problem hiding this comment.
Let's track this as a suggestion, and if we find that people need this in order to onboard to the new analyzer, then we'll add it at that point. #6132
| return attributeData.AttributeClass.Equals(diagnosticAnalyzerAttributeType, SymbolEqualityComparer.Default) | ||
| || attributeData.AttributeClass.Equals(generatorAttributeType, SymbolEqualityComparer.Default); |
There was a problem hiding this comment.
Should we move this attribute type check upfront in this method? I presume it will be cheaper then the code to look for the option setting?
| |Category|MicrosoftCodeAnalysisCorrectness| | ||
| |Enabled|True| | ||
| |Severity|Error| | ||
| |CodeFix|False| |
There was a problem hiding this comment.
It would be awesome if this supported a code fix that either creates or appends to an .editorconfig file and adds <%option%> = true entry. Can you file a tracking issue for adding this code fix in future?
| <PackageId>*$(MSBuildProjectFile)*</PackageId> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <Compile Include="..\..\Microsoft.CodeAnalysis.BannedApiAnalyzers\Core\SymbolIsBannedAnalyzerBase.cs" Link="SymbolIsBannedAnalyzerBase.cs" /> |
There was a problem hiding this comment.
Another option is to move this file into the shared utilities project, similar to the common base type shared across analyzer packages here: https://github.com/dotnet/roslyn-analyzers/blob/main/src/Utilities/Compiler/DoNotCatchGeneralUnlessRethrown.cs
There was a problem hiding this comment.
I wasn't able to make this work. I might not have gotten the configuration of the shproj or some #if directives set up properly.
| public const string MicrosoftCodeAnalysisCSharpExtensions = "Microsoft.CodeAnalysis.CSharpExtensions"; | ||
| public const string MicrosoftCodeAnalysisDiagnostic = "Microsoft.CodeAnalysis.Diagnostic"; | ||
| public const string MicrosoftCodeAnalysisDiagnosticDescriptor = "Microsoft.CodeAnalysis.DiagnosticDescriptor"; | ||
| public const string MicrosoftCodeAnalysisGeneratorAttribute = "Microsoft.CodeAnalysis.GeneratorAttribute"; |
|
My one worry here is that we're tying a lot to editor config. What is the story for customers that are building analyzers but don't use editor config today? How easy is it for them to get to a place where they're specifying this setting? Is there a good single line discussion about why we didn't use an msbuild property here? Probably want to include @KathleenDollard for documentation and discussion. |
|
It seems like roslyn-analyzers is geared toward keeping the msbuild properties which affect analyzer behavior a small/closed set. https://github.com/dotnet/roslyn-analyzers/blob/89942866e0fde9a71327be082183b6b3b972a08b/src/Utilities/Compiler/Options/MSBuildPropertyOptionNames.cs @mavasani might have insight about whether an msbuild property could be problematic here. I could certainly imagine people wanting to enable these errors in some source files but not others. But even if we use an msbuild property, which applies to the whole project, granular suppressions can be accomplished through the normal analyzer diagnostic suppression mechanisms. |
@RikkiGibson We don't have any restriction as such. These are the MSBuild properties that flow to the analyzers via compiler generated globalconfig file. Given this feature was added pretty recently, very few analyzers have been added which require an MSBuild property as an option.
I think this would be just fine. This does seem like a better design as theoretically the flag you are adding is a project level property, not per-file or per-directory one. |
…IsBannedInAnalyzersAnalyzer.cs Co-authored-by: Jared Parsons <jaredpparsons@gmail.com>
|
The following CI failure is happening consistently. Main branch builds appear to be clean so I don't know what's going wrong. |
|
Added a demo gif to the PR description. |
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s), but failed to run 1 pipeline(s). |
wh-which one? will it be forever a mystery? |
| T:System.IO.File; Do not do file IO in analyzers | ||
| T:System.IO.Directory; Do not do file IO in analyzers | ||
| M:System.IO.Path.GetTempPath; Do not do file IO in analyzers | ||
| T:System.Environment; Analyzers should not read their settings directly from environment variables | ||
| M:System.Reflection.Assembly.Load(System.Byte[]); Analyzers should only load their dependencies via standard runtime mechanisms | ||
| M:System.Reflection.Assembly.Load(System.String); Analyzers should only load their dependencies via standard runtime mechanisms | ||
| M:System.Reflection.Assembly.Load(System.Reflection.AssemblyName); Analyzers should only load their dependencies via standard runtime mechanisms | ||
| M:System.Reflection.Assembly.Load(System.Byte[],System.Byte[]); Analyzers should only load their dependencies via standard runtime mechanisms | ||
| P:System.Globalization.CultureInfo.CurrentCulture; Analyzers should use the locale given by the compiler command line arguments, not the CurrentCulture | ||
| P:System.Globalization.CultureInfo.CurrentUICulture; Analyzers should use the locale given by the compiler command line arguments, not the CurrentUICulture No newline at end of file |
There was a problem hiding this comment.
@RikkiGibson Should all "analyzers" in these messages be updated to "analyzers or generators"
|
I'm sorry, I can't find any documentation or recommendation on what to actually do in some cases, for instance:
The reason why we added Env is to support simple "debugging" tools in case of errors as it might be challenging sometimes with analyzers. Is there a way to pass from VS an option towards the analyzer, and if so, how? |

See dotnet/roslyn#63290
In order to reduce the magnitude of the break/disruption here, we've done the following:
EnforceExtendedAnalyzerRules.<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>. But they could also specify<EnforceExtendedAnalyzerRules>false</EnforceExtendedAnalyzerRules>if they just want to shut these new warnings up entirely.<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>is specified, we go ahead and actually subject the user to a potentially quite large amount of new errors if they are using banned APIs.This gif roughly demonstrates the onboarding/usage experience:
