├── .gitattributes ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .gitmodules ├── LICENSE.txt ├── README.md ├── clean.bat ├── coverage.bat ├── doc └── reference │ ├── CNUL_ResharperNullabilityAnnotationsCanBeConvertedToCSharpSyntax.md │ ├── RINUL_MemberIsMissingItemNullabilityAnnotation.md │ ├── RNUL_MemberIsMissingNullabilityAnnotation.md │ └── XNUL_SuggestToDisableReportingOnNullableValueTypes.md └── src ├── CodeAnalysis.ruleset ├── CodeContractNullability ├── CodeContractNullability.Test │ ├── CodeContractNullability.Test.csproj │ ├── CodeContractNullability.Test.csproj.DotSettings │ ├── ConversionNullabilityTest.cs │ ├── GitHubIssueAttribute.cs │ ├── ItemNullabilityTest.cs │ ├── NullabilityAnalysisTestFixture.cs │ ├── NullabilityTest.cs │ ├── ParsedSourceCode.cs │ ├── ReportOnNullableValueTypesTest.cs │ ├── ReusableAnalyzerOnFileSystemTest.cs │ ├── Specs │ │ ├── AttributeDiscoverySpecs.cs │ │ ├── BugFixes.cs │ │ ├── ExternalAnnotationMemberNamingSpecs.cs │ │ ├── FieldCollectionSpecs.cs │ │ ├── FieldSpecs.cs │ │ ├── ItemNullabilityFixAllSpecs.cs │ │ ├── LambdaSpecs.cs │ │ ├── LocalFunctionSpecs.cs │ │ ├── MethodReturnValueCollectionSpecs.cs │ │ ├── MethodReturnValueSpecs.cs │ │ ├── NullabilityFixAllSpecs.cs │ │ ├── NullableReferenceTypeConversionSpecs.cs │ │ ├── ParameterCollectionSpecs.cs │ │ ├── ParameterSpecs.cs │ │ ├── PropertyCollectionSpecs.cs │ │ ├── PropertySpecs.cs │ │ ├── ReportOnNullableValueTypesSpecs.cs │ │ ├── ScanningForExternalAnnotationsSpecs.cs │ │ └── TokenTriviaSpecs.cs │ ├── SymbolType.cs │ ├── TempAssemblyScope.cs │ └── TestDataBuilders │ │ ├── AnalyzerSettingsBuilder.cs │ │ ├── ConversionDiagnosticMessageBuilder.cs │ │ ├── DiagnosticMessageBuilder.cs │ │ ├── ExactSourceCodeBuilder.cs │ │ ├── ExternalAnnotationFragmentBuilder.cs │ │ ├── ExternalAnnotationParameterBuilder.cs │ │ ├── ExternalAnnotationsBuilder.cs │ │ ├── ITestDataBuilder.cs │ │ ├── MemberSourceCodeBuilder.cs │ │ ├── NullabilityAttributesBuilder.cs │ │ ├── NullabilityAttributesDefinition.cs │ │ ├── SourceCodeBuilder.cs │ │ ├── SourceCodeBuilderExtensions.cs │ │ └── TypeSourceCodeBuilder.cs ├── CodeContractNullability.Vsix │ ├── CodeContractNullability.Vsix.csproj │ ├── packages.config │ └── source.extension.vsixmanifest └── CodeContractNullability │ ├── AnalyzerSettings.cs │ ├── BaseAnalyzer.cs │ ├── BaseCodeFixProvider.cs │ ├── CodeContractItemNullabilityAnalyzer.cs │ ├── CodeContractItemNullabilityCodeFixProvider.cs │ ├── CodeContractNullability.csproj │ ├── CodeContractNullabilityAnalyzer.cs │ ├── CodeContractNullabilityCodeFixProvider.cs │ ├── Conversion │ ├── NullConversionContext.cs │ ├── NullConversionScope.cs │ ├── ResharperNullabilityAttributesRemover.cs │ ├── ResharperNullabilitySymbolState.cs │ ├── ResharperNullableStatus.cs │ └── TypeDeclarationWriter.cs │ ├── DisableReportOnNullableValueTypesCodeFixProvider.cs │ ├── DocumentBasedFixAllProvider.cs │ ├── ExternalAnnotations │ ├── AssemblyExternalAnnotationsLoader.cs │ ├── CachingExternalAnnotationsResolver.cs │ ├── ExternalAnnotationDocumentParser.cs │ ├── FolderExternalAnnotationsLoader.cs │ ├── FolderOnDiskScanner.cs │ ├── GlobalAnnotationCacheProvider.cs │ ├── IExternalAnnotationsResolver.cs │ ├── LocalAnnotationCacheProvider.cs │ ├── MissingExternalAnnotationsException.cs │ ├── SimpleExternalAnnotationsResolver.cs │ └── Storage │ │ ├── ExternalAnnotationsCache.cs │ │ ├── ExternalAnnotationsMap.cs │ │ ├── MemberNullabilityInfo.cs │ │ └── ParameterNullabilityMap.cs │ ├── FixAllContextHelper.cs │ ├── FrameworkTypeCache.cs │ ├── GeneratedCodeDocumentCache.cs │ ├── NullabilityAttributes │ ├── CompilationAttributeScanner.cs │ ├── INullabilityAttributeProvider.cs │ ├── NullabilityAttributeCache.cs │ ├── NullabilityAttributeMetadataNames.cs │ ├── NullabilityAttributeSymbols.cs │ └── SimpleNullabilityAttributeProvider.cs │ ├── NullableReferenceTypeConversionAnalyzer.cs │ ├── NullableReferenceTypeConversionCodeFixProvider.cs │ ├── NullableReferenceTypeSupport.cs │ ├── ReadMe.txt │ ├── SettingsProvider.cs │ ├── SymbolAnalysis │ ├── AnalysisScope.cs │ ├── BaseSymbolAnalyzer.cs │ ├── FieldAnalyzer.cs │ ├── FunctionAnalysis.cs │ ├── MethodReturnValueAnalyzer.cs │ ├── ParameterAnalyzer.cs │ ├── PropertyAnalyzer.cs │ ├── SymbolAnalyzerFactory.cs │ └── SymbolExtensions.cs │ ├── SyntaxNodeExtensions.cs │ ├── Utilities │ ├── AnalysisContextExtensions.cs │ ├── CodeTimer.cs │ ├── EnumerableExtensions.cs │ ├── ExtensionPoint.cs │ ├── FreshReference.cs │ ├── Guard.cs │ ├── ICacheProvider.cs │ └── StringExtensions.cs │ └── tools │ ├── install.ps1 │ └── uninstall.ps1 ├── Directory.Build.props ├── ResharperCodeContractNullability.sln └── ResharperCodeContractNullability.sln.DotSettings /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Version Used**: 2 | 3 | **Steps to Reproduce**: 4 | 5 | 1. One 6 | 1. Two 7 | 1. Three 8 | 9 | **Expected Behavior**: 10 | 11 | **Actual Behavior**: 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | 254 | # OpenCover code coverage output 255 | coverage/ 256 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/CodeContractNullability/CodeContractNullability.Test/RoslynTestFramework"] 2 | path = src/CodeContractNullability/CodeContractNullability.Test/RoslynTestFramework 3 | url = https://github.com/bkoelman/RoslynTestFramework 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Resharper Code Contract Nullability 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/bw9h05bekojslnmr?svg=true)](https://ci.appveyor.com/project/bkoelman/resharpercodecontractnullability/branch/master) 4 | [![codecov](https://codecov.io/gh/bkoelman/ResharperCodeContractNullability/branch/master/graph/badge.svg)](https://codecov.io/gh/bkoelman/ResharperCodeContractNullability) 5 | 6 | This Visual Studio analyzer supports you in consequently annotating your codebase with Resharpers nullability attributes. Doing so improves the [nullability analysis engine in Resharper](https://www.jetbrains.com/resharper/help/Code_Analysis__Code_Annotations.html), so `NullReferenceException`s at runtime will become something from the past. 7 | 8 | **Update:** When used on a project that has C# nullable reference types enabled, this analyzer suppresses its original behavior and turns into a utility to convert existing Resharper nullability attributes to C# 8 syntax. See [documentation](doc/reference/CNUL_ResharperNullabilityAnnotationsCanBeConvertedToCSharpSyntax.md) for details. 9 | 10 | ## Get started 11 | 12 | * You need [Visual Studio](https://www.visualstudio.com/) 2015/2017/2019 and [Resharper](https://www.jetbrains.com/resharper/) v9 (or higher) to use this analyzer. See [here](https://github.com/bkoelman/ResharperCodeContractNullabilityFxCop/) if you use Visual Studio 2013 or lower. 13 | 14 | * From the NuGet package manager console: 15 | 16 | `Install-Package ResharperCodeContractNullability` 17 | 18 | `Install-Package JetBrains.Annotations` 19 | 20 | * Rebuild your solution 21 | 22 | Alternatively, you can install as a Visual Studio Extension from the [Visual Studio Gallery](https://visualstudiogallery.msdn.microsoft.com/97bdc5f4-f209-4441-a313-2c6e92631eaf). 23 | 24 | Instead of adding the JetBrains package, you can [put the annotation definitions directly in your source code](https://www.jetbrains.com/resharper/help/Code_Analysis__Annotations_in_Source_Code.html). In that case, it's [recommended](http://blog.jetbrains.com/dotnet/2015/08/12/how-to-use-jetbrains-annotations-to-improve-resharper-inspections/) to set the `conditional` and/or `internal` options checked. 25 | 26 | To make analysis work over multiple projects in your solution, define the `JETBRAINS_ANNOTATIONS` conditional compilation symbol in your project properties. 27 | 28 | ![Analyzer in action](https://github.com/bkoelman/ResharperCodeContractNullability/blob/gh-pages/images/analyzer-in-action.png) 29 | 30 | ## Trying out the latest build 31 | 32 | After each commit, a new prerelease NuGet package is automatically published to AppVeyor at https://ci.appveyor.com/project/bkoelman/resharpercodecontractnullability/branch/master/artifacts. To try it out, follow the next steps: 33 | 34 | * In Visual Studio: **Tools**, **NuGet Package Manager**, **Package Manager Settings**, **Package Sources** 35 | * Click **+** 36 | * Name: **AppVeyor ResharperCodeContractNullability**, Source: **https://ci.appveyor.com/nuget/resharpercodecontractnullability** 37 | * Click **Update**, **Ok** 38 | * Open the NuGet package manager console (**Tools**, **NuGet Package Manager**, **Package Manager Console**) 39 | * Select **AppVeyor ResharperCodeContractNullability** as package source 40 | * Run command: `Install-Package ResharperCodeContractNullability -pre` 41 | 42 | ## Running on your build server 43 | 44 | This assumes your project uses ResharperCodeContractNullability via NuGet, but Resharper is not installed on your build server. To make the analyzer succeed there, simply add another NuGet reference to your project. 45 | 46 | * From the NuGet package manager console: 47 | 48 | `Install-Package JetBrains.ExternalAnnotations` 49 | -------------------------------------------------------------------------------- /clean.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | for /R %%x in (bin) do if exist "%%x" rmdir /s /q "%%x" 1>NUL 2>NUL 3 | for /R %%x in (obj) do if exist "%%x" rmdir /s /q "%%x" 1>NUL 2>NUL 4 | 5 | rmdir /s /q src\.vs 1>NUL 2>NUL 6 | rmdir /s /q src\packages 1>NUL 2>NUL 7 | rmdir /s /q coverage 1>NUL 2>NUL 8 | 9 | del /f /s /q *.user 1>NUL 2>NUL 10 | -------------------------------------------------------------------------------- /coverage.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rd /s /q coverage 2>nul 3 | md coverage 4 | 5 | set configuration=Debug 6 | set opencover="%USERPROFILE%\.nuget\packages\OpenCover\4.7.922\tools\OpenCover.Console.exe" 7 | set reportgenerator="%USERPROFILE%\.nuget\packages\ReportGenerator\4.8.7\tools\net47\ReportGenerator.exe" 8 | set testrunner="%USERPROFILE%\.nuget\packages\xunit.runner.console\2.4.1\tools\net472\xunit.console.x86.exe" 9 | set target=".\src\CodeContractNullability\CodeContractNullability.Test\bin\%configuration%\net472\CodeContractNullability.Test.dll -noshadow" 10 | set filter="+[CodeContractNullability*]* -[CodeContractNullability.Test*]*" 11 | set coveragefile=".\coverage\CodeCoverage.xml" 12 | 13 | %opencover% -register:user -target:%testrunner% -targetargs:%target% -filter:%filter% -hideskipped:All -output:%coveragefile% 14 | %reportgenerator% -targetdir:.\coverage -reports:%coveragefile% 15 | -------------------------------------------------------------------------------- /doc/reference/CNUL_ResharperNullabilityAnnotationsCanBeConvertedToCSharpSyntax.md: -------------------------------------------------------------------------------- 1 | # CNUL: Resharper nullability annotation(s) can be converted to C# syntax 2 | 3 | ## Cause 4 | A member or parameter contains Resharper nullability annotations, while the project is configured to enable C# nullable reference types. 5 | 6 | ## Rule description 7 | Nullable reference types are a new language feature in C# v8. It supersedes the nullability annotations that were introduced by Resharper. This rule offers to convert existing code that uses the Resharper attribute to the new C# syntax. 8 | 9 | ## How to fix violations 10 | Run the associated code fixer by selecting "Convert to C# syntax". It is recommended to run on entire document, project or solution (if memory allows). 11 | Remember to uninstall the ResharperCodeContractNullability package after all your code has been converted. 12 | 13 | ## When to suppress warnings 14 | There is no technical reason to suppress this warning. 15 | 16 | ## Example of a violation 17 | 18 | ### Description 19 | The type `C` defines members and parameters that are decorated with Resharper annotations. 20 | 21 | ### Code 22 | ```csharp 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Threading.Tasks; 26 | using JetBrains.Annotations; 27 | 28 | namespace N 29 | { 30 | public class C 31 | { 32 | [CanBeNull] 33 | [ItemNotNull] 34 | private readonly List _f = new List(); 35 | 36 | [CanBeNull] 37 | [ItemNotNull] 38 | public ICollection P => _f?.AsReadOnly(); 39 | 40 | [NotNull] 41 | [ItemNotNull] 42 | public Lazy M([NotNull] [ItemNotNull] Task p) 43 | { 44 | _f?.Add(p.Result); 45 | return new Lazy(() => p.Result); 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | ## Example of how to fix 52 | 53 | ### Description 54 | All annotated members and parameters of the type `C` are now converted. 55 | 56 | ### Code 57 | 58 | ```csharp 59 | using System; 60 | using System.Collections.Generic; 61 | using System.Threading.Tasks; 62 | 63 | namespace N 64 | { 65 | public class C 66 | { 67 | private readonly List? _f = new List(); 68 | 69 | public ICollection? P => _f?.AsReadOnly(); 70 | 71 | public Lazy M(Task p) 72 | { 73 | _f?.Add(p.Result); 74 | return new Lazy(() => p.Result); 75 | } 76 | } 77 | } 78 | ``` 79 | 80 | ## Related rules 81 | 82 | RNUL: [Member is missing nullability annotation](RNUL_MemberIsMissingNullabilityAnnotation.md) 83 | 84 | RINUL: [Member is missing item nullability annotation](RINUL_MemberIsMissingItemNullabilityAnnotation.md) 85 | -------------------------------------------------------------------------------- /doc/reference/RINUL_MemberIsMissingItemNullabilityAnnotation.md: -------------------------------------------------------------------------------- 1 | # RINUL: Member is missing item nullability annotation 2 | 3 | ## Cause 4 | The item of a container member or parameter technically can contain `null`, but it is not indicated whether callers should account for a `null` value. 5 | 6 | ## Rule description 7 | Resharper performs nullability analysis and issues warnings, such as missing or redundant null checks. 8 | Annotating container members and parameters, such as collections, tasks and lazy types, with nullability attributes improves the results of that analysis engine. 9 | 10 | ## How to fix violations 11 | Annotate the target member or parameter with `JetBrains.Annotations.ItemCanBeNullAttribute` or `JetBrains.Annotations.ItemNotNullAttribute`. 12 | 13 | ## When to suppress warnings 14 | There is no technical reason to suppress this warning. 15 | 16 | ## Example of a violation 17 | 18 | ### Description 19 | The type `C` defines container members and parameters that can contain `null`, which are not annotated. 20 | 21 | ### Code 22 | ```csharp 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Threading.Tasks; 26 | 27 | namespace N 28 | { 29 | public class C 30 | { 31 | private readonly List _f = new List(); 32 | 33 | public ICollection P => _f.AsReadOnly(); 34 | 35 | public Lazy M(Task p) 36 | { 37 | _f.Add(p.Result); 38 | return new Lazy(() => p.Result); 39 | } 40 | } 41 | } 42 | ``` 43 | 44 | ## Example of how to fix 45 | 46 | ### Description 47 | All container members of the type `C` that can contain `null` are now annotated. 48 | 49 | ### Code 50 | 51 | ```csharp 52 | using System; 53 | using System.Collections.Generic; 54 | using System.Threading.Tasks; 55 | using JetBrains.Annotations; 56 | 57 | namespace N 58 | { 59 | public class C 60 | { 61 | [ItemCanBeNull] private readonly List _f = new List(); 62 | 63 | [ItemCanBeNull] public ICollection P => _f.AsReadOnly(); 64 | 65 | [ItemNotNull] public Lazy M([ItemNotNull] Task p) 66 | { 67 | _f.Add(p.Result); 68 | return new Lazy(() => p.Result); 69 | } 70 | } 71 | } 72 | ``` 73 | 74 | ## Related rules 75 | 76 | RNUL: [Member is missing nullability annotation](RNUL_MemberIsMissingNullabilityAnnotation.md) 77 | 78 | XNUL: [Suggest to disable reporting on nullable value types](XNUL_SuggestToDisableReportingOnNullableValueTypes.md) 79 | -------------------------------------------------------------------------------- /doc/reference/RNUL_MemberIsMissingNullabilityAnnotation.md: -------------------------------------------------------------------------------- 1 | # RNUL: Member is missing nullability annotation 2 | 3 | ## Cause 4 | A member or parameter technically can contain `null`, but it is not indicated whether callers should account for a `null` value. 5 | 6 | ## Rule description 7 | Resharper performs nullability analysis and issues warnings, such as missing or redundant null checks. 8 | Annotating members and parameters with nullability attributes improves the results of that analysis engine. 9 | 10 | ## How to fix violations 11 | Annotate the target member or parameter with `JetBrains.Annotations.CanBeNullAttribute` or `JetBrains.Annotations.NotNullAttribute`. 12 | 13 | ## When to suppress warnings 14 | There is no technical reason to suppress this warning. 15 | 16 | ## Example of a violation 17 | 18 | ### Description 19 | The type `C` defines members and parameters that can contain `null`, which are not annotated. 20 | 21 | ### Code 22 | ```csharp 23 | namespace N 24 | { 25 | public class C 26 | { 27 | private string _f; 28 | 29 | public string P => _f; 30 | 31 | public string M(string p) 32 | { 33 | _f = p; 34 | return p; 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | ## Example of how to fix 41 | 42 | ### Description 43 | All members of the type `C` that can contain `null` are now annotated. 44 | 45 | ### Code 46 | 47 | ```csharp 48 | using JetBrains.Annotations; 49 | 50 | namespace N 51 | { 52 | public class C 53 | { 54 | [CanBeNull] private string _f; 55 | 56 | [CanBeNull] public string P => _f; 57 | 58 | [NotNull] public string M([NotNull] string p) 59 | { 60 | _f = p; 61 | return p; 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | ## Related rules 68 | 69 | RINUL: [Member is missing item nullability annotation](RINUL_MemberIsMissingItemNullabilityAnnotation.md) 70 | 71 | XNUL: [Suggest to disable reporting on nullable value types](XNUL_SuggestToDisableReportingOnNullableValueTypes.md) 72 | -------------------------------------------------------------------------------- /doc/reference/XNUL_SuggestToDisableReportingOnNullableValueTypes.md: -------------------------------------------------------------------------------- 1 | # XNUL: Suggest to disable reporting on nullable value types 2 | 3 | ## Cause 4 | [RNUL](RNUL_MemberIsMissingNullabilityAnnotation.md) or [RINUL](RINUL_MemberIsMissingItemNullabilityAnnotation.md) is reported on a member. 5 | Some users prefer to not get nullability warnings reported on nullable value types. This rule facilitates a shortcut to suppress such warnings. 6 | 7 | ## Rule description 8 | This pseudo-rule exists to provide a context-menu action that updates project configuration to disable reporting on nullable value types. 9 | 10 | ## How to fix violations 11 | N/A. 12 | 13 | ## When to suppress warnings 14 | Suppress this rule to hide the context-menu action that disables reporting on nullable value types. 15 | 16 | ## Example of a violation 17 | 18 | ### Description 19 | The type `C` defines a field of type ``System.Nullable`1``, which is not annotated. 20 | 21 | ### Code 22 | ```csharp 23 | namespace N 24 | { 25 | public class C 26 | { 27 | private int? _f; 28 | } 29 | } 30 | ``` 31 | 32 | ## Example of how to fix 33 | 34 | ### Description 35 | N/A. 36 | 37 | ## Related rules 38 | 39 | RNUL: [Member is missing nullability annotation](RNUL_MemberIsMissingNullabilityAnnotation.md) 40 | 41 | RINUL: [Member is missing item nullability annotation](RINUL_MemberIsMissingItemNullabilityAnnotation.md) 42 | -------------------------------------------------------------------------------- /src/CodeAnalysis.ruleset: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/CodeContractNullability.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472;net452 5 | 6 | 7 | 8 | TRACE;DEBUG;JETBRAINS_ANNOTATIONS 9 | 10 | 11 | 12 | TRACE;RELEASE;JETBRAINS_ANNOTATIONS 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | 31 | 32 | 33 | 34 | 35 | all 36 | runtime; build; native; contentfiles; analyzers; buildtransitive 37 | 38 | 39 | all 40 | runtime; build; native; contentfiles; analyzers; buildtransitive 41 | 42 | 43 | 44 | 45 | 46 | 47 | all 48 | runtime; build; native; contentfiles; analyzers 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/CodeContractNullability.Test.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True 6 | True 7 | 8 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/ConversionNullabilityTest.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.NullabilityAttributes; 2 | using CodeContractNullability.Test.TestDataBuilders; 3 | using CodeContractNullability.Utilities; 4 | using JetBrains.Annotations; 5 | using Microsoft.CodeAnalysis.CodeFixes; 6 | using Microsoft.CodeAnalysis.Diagnostics; 7 | using RoslynTestFramework; 8 | 9 | namespace CodeContractNullability.Test 10 | { 11 | public abstract class ConversionNullabilityTest : AnalysisTestFixture 12 | { 13 | protected override string DiagnosticId => NullableReferenceTypeConversionAnalyzer.DiagnosticId; 14 | 15 | protected override DiagnosticAnalyzer CreateAnalyzer() 16 | { 17 | var analyzer = new NullableReferenceTypeConversionAnalyzer(); 18 | analyzer.NullabilityAttributeProvider.Override(new SimpleNullabilityAttributeProvider()); 19 | return analyzer; 20 | } 21 | 22 | protected override CodeFixProvider CreateFixProvider() 23 | { 24 | return new NullableReferenceTypeConversionCodeFixProvider(); 25 | } 26 | 27 | private protected void VerifyDiagnostics([NotNull] ParsedSourceCode source, [NotNull] [ItemNotNull] params string[] messages) 28 | { 29 | Guard.NotNull(source, nameof(source)); 30 | 31 | AssertDiagnostics(source.TestContext, messages); 32 | } 33 | 34 | private protected void VerifyFix([NotNull] ParsedSourceCode source, [NotNull] [ItemNotNull] params string[] messages) 35 | { 36 | Guard.NotNull(source, nameof(source)); 37 | 38 | FixProviderTestContext fixContext = CreateFixTestContext(source); 39 | 40 | AssertDiagnosticsWithCodeFixes(fixContext, messages); 41 | } 42 | 43 | private protected void VerifyFixes([NotNull] ParsedSourceCode source, [NotNull] [ItemNotNull] params string[] messages) 44 | { 45 | Guard.NotNull(source, nameof(source)); 46 | 47 | FixProviderTestContext fixContext = CreateFixTestContext(source); 48 | 49 | string[] equivalenceKeysForFixAll = 50 | { 51 | nameof(NullableReferenceTypeConversionCodeFixProvider) 52 | }; 53 | 54 | fixContext = fixContext.WithEquivalenceKeysForFixAll(equivalenceKeysForFixAll); 55 | 56 | AssertDiagnosticsWithAllCodeFixes(fixContext, messages); 57 | } 58 | 59 | [NotNull] 60 | private static FixProviderTestContext CreateFixTestContext([NotNull] ParsedSourceCode source) 61 | { 62 | string[] expectedCode = 63 | { 64 | source.ExpectedText 65 | }; 66 | 67 | return new FixProviderTestContext(source.TestContext, expectedCode, source.CodeComparisonMode); 68 | } 69 | 70 | [NotNull] 71 | protected static string CreateMessageForField([NotNull] string name) 72 | { 73 | return new ConversionDiagnosticMessageBuilder() 74 | .OfType(SymbolType.Field) 75 | .Named(name) 76 | .Build(); 77 | } 78 | 79 | [NotNull] 80 | protected static string CreateMessageForProperty([NotNull] string name) 81 | { 82 | return new ConversionDiagnosticMessageBuilder() 83 | .OfType(SymbolType.Property) 84 | .Named(name) 85 | .Build(); 86 | } 87 | 88 | [NotNull] 89 | protected static string CreateMessageForMethod([NotNull] string name) 90 | { 91 | return new ConversionDiagnosticMessageBuilder() 92 | .OfType(SymbolType.Method) 93 | .Named(name) 94 | .Build(); 95 | } 96 | 97 | [NotNull] 98 | protected static string CreateMessageForDelegate([NotNull] string name) 99 | { 100 | return new ConversionDiagnosticMessageBuilder() 101 | .OfType(SymbolType.Delegate) 102 | .Named(name) 103 | .Build(); 104 | } 105 | 106 | [NotNull] 107 | protected static string CreateMessageForParameter([NotNull] string name) 108 | { 109 | return new ConversionDiagnosticMessageBuilder() 110 | .OfType(SymbolType.Parameter) 111 | .Named(name) 112 | .Build(); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/GitHubIssueAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CodeContractNullability.Test 4 | { 5 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] 6 | internal sealed class GitHubIssueAttribute : Attribute 7 | { 8 | // ReSharper disable once MemberCanBePrivate.Global 9 | // ReSharper disable once UnusedAutoPropertyAccessor.Global 10 | public int Id { get; } 11 | 12 | internal GitHubIssueAttribute(int id) 13 | { 14 | Id = id; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/ItemNullabilityTest.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.Test.TestDataBuilders; 2 | using JetBrains.Annotations; 3 | using Microsoft.CodeAnalysis.CodeFixes; 4 | 5 | namespace CodeContractNullability.Test 6 | { 7 | public abstract class ItemNullabilityTest : NullabilityAnalysisTestFixture 8 | { 9 | protected override string DiagnosticId => CodeContractItemNullabilityAnalyzer.DiagnosticId; 10 | 11 | protected override string NotNullAttributeName => "ItemNotNull"; 12 | protected override string CanBeNullAttributeName => "ItemCanBeNull"; 13 | 14 | protected override BaseAnalyzer CreateNullabilityAnalyzer() 15 | { 16 | return new CodeContractItemNullabilityAnalyzer(); 17 | } 18 | 19 | protected override CodeFixProvider CreateFixProvider() 20 | { 21 | return new CodeContractItemNullabilityCodeFixProvider(); 22 | } 23 | 24 | [NotNull] 25 | protected static string CreateMessageForField([NotNull] string name) 26 | { 27 | return new DiagnosticMessageBuilder() 28 | .OfType(SymbolType.Field) 29 | .Named(name) 30 | .ForItem() 31 | .Build(); 32 | } 33 | 34 | [NotNull] 35 | protected static string CreateMessageForProperty([NotNull] string name) 36 | { 37 | return new DiagnosticMessageBuilder() 38 | .OfType(SymbolType.Property) 39 | .Named(name) 40 | .ForItem() 41 | .Build(); 42 | } 43 | 44 | [NotNull] 45 | protected static string CreateMessageForMethod([NotNull] string name) 46 | { 47 | return new DiagnosticMessageBuilder() 48 | .OfType(SymbolType.Method) 49 | .Named(name) 50 | .ForItem() 51 | .Build(); 52 | } 53 | 54 | [NotNull] 55 | protected static string CreateMessageForParameter([NotNull] string name) 56 | { 57 | return new DiagnosticMessageBuilder() 58 | .OfType(SymbolType.Parameter) 59 | .Named(name) 60 | .ForItem() 61 | .Build(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/NullabilityAnalysisTestFixture.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.ExternalAnnotations; 2 | using CodeContractNullability.NullabilityAttributes; 3 | using CodeContractNullability.Test.TestDataBuilders; 4 | using CodeContractNullability.Utilities; 5 | using JetBrains.Annotations; 6 | using Microsoft.CodeAnalysis.Diagnostics; 7 | using RoslynTestFramework; 8 | 9 | namespace CodeContractNullability.Test 10 | { 11 | public abstract class NullabilityAnalysisTestFixture : AnalysisTestFixture 12 | { 13 | [NotNull] 14 | private IExternalAnnotationsResolver externalAnnotationsResolver = new SimpleExternalAnnotationsResolver(new ExternalAnnotationsBuilder().Build()); 15 | 16 | [NotNull] 17 | protected abstract string NotNullAttributeName { get; } 18 | 19 | [NotNull] 20 | protected abstract string CanBeNullAttributeName { get; } 21 | 22 | private protected void VerifyNullabilityDiagnostic([NotNull] ParsedSourceCode source, [NotNull] [ItemNotNull] params string[] messages) 23 | { 24 | Guard.NotNull(source, nameof(source)); 25 | 26 | externalAnnotationsResolver = new SimpleExternalAnnotationsResolver(source.ExternalAnnotationsMap); 27 | 28 | AssertDiagnostics(source.TestContext, messages); 29 | } 30 | 31 | private protected virtual void VerifyNullabilityFix([NotNull] ParsedSourceCode source, [NotNull] [ItemNotNull] params string[] messages) 32 | { 33 | Guard.NotNull(source, nameof(source)); 34 | Guard.NotNull(messages, nameof(messages)); 35 | 36 | externalAnnotationsResolver = new SimpleExternalAnnotationsResolver(source.ExternalAnnotationsMap); 37 | 38 | FixProviderTestContext fixContext = CreateFixTestContext(source); 39 | 40 | AssertDiagnosticsWithCodeFixes(fixContext, messages); 41 | } 42 | 43 | private protected void VerifyNullabilityFixes([NotNull] ParsedSourceCode source, [NotNull] [ItemNotNull] params string[] messages) 44 | { 45 | Guard.NotNull(source, nameof(source)); 46 | Guard.NotNull(messages, nameof(messages)); 47 | 48 | externalAnnotationsResolver = new SimpleExternalAnnotationsResolver(source.ExternalAnnotationsMap); 49 | 50 | FixProviderTestContext fixContext = CreateFixTestContext(source); 51 | 52 | string[] equivalenceKeysForFixAll = 53 | { 54 | "Decorate with " + NotNullAttributeName, 55 | "Decorate with " + CanBeNullAttributeName 56 | }; 57 | 58 | fixContext = fixContext.WithEquivalenceKeysForFixAll(equivalenceKeysForFixAll); 59 | 60 | AssertDiagnosticsWithAllCodeFixes(fixContext, messages); 61 | } 62 | 63 | [NotNull] 64 | private FixProviderTestContext CreateFixTestContext([NotNull] ParsedSourceCode source) 65 | { 66 | string fixNotNull = source.GetExpectedTextForAttribute(NotNullAttributeName); 67 | string fixCanBeNull = source.GetExpectedTextForAttribute(CanBeNullAttributeName); 68 | 69 | string[] expectedCode = 70 | { 71 | fixNotNull, 72 | fixCanBeNull 73 | }; 74 | 75 | return new FixProviderTestContext(source.TestContext, expectedCode, source.CodeComparisonMode); 76 | } 77 | 78 | protected override DiagnosticAnalyzer CreateAnalyzer() 79 | { 80 | BaseAnalyzer analyzer = CreateNullabilityAnalyzer(); 81 | analyzer.ExternalAnnotationsResolver.Override(externalAnnotationsResolver); 82 | analyzer.NullabilityAttributeProvider.Override(new SimpleNullabilityAttributeProvider()); 83 | return analyzer; 84 | } 85 | 86 | [NotNull] 87 | protected abstract BaseAnalyzer CreateNullabilityAnalyzer(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/NullabilityTest.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.Test.TestDataBuilders; 2 | using JetBrains.Annotations; 3 | using Microsoft.CodeAnalysis.CodeFixes; 4 | 5 | namespace CodeContractNullability.Test 6 | { 7 | public abstract class NullabilityTest : NullabilityAnalysisTestFixture 8 | { 9 | protected override string DiagnosticId => CodeContractNullabilityAnalyzer.DiagnosticId; 10 | 11 | protected override string NotNullAttributeName => "NotNull"; 12 | protected override string CanBeNullAttributeName => "CanBeNull"; 13 | 14 | protected override BaseAnalyzer CreateNullabilityAnalyzer() 15 | { 16 | return new CodeContractNullabilityAnalyzer(); 17 | } 18 | 19 | protected override CodeFixProvider CreateFixProvider() 20 | { 21 | return new CodeContractNullabilityCodeFixProvider(); 22 | } 23 | 24 | [NotNull] 25 | protected static string CreateMessageForField([NotNull] string name) 26 | { 27 | return new DiagnosticMessageBuilder() 28 | .OfType(SymbolType.Field) 29 | .Named(name) 30 | .Build(); 31 | } 32 | 33 | [NotNull] 34 | protected static string CreateMessageForProperty([NotNull] string name) 35 | { 36 | return new DiagnosticMessageBuilder() 37 | .OfType(SymbolType.Property) 38 | .Named(name) 39 | .Build(); 40 | } 41 | 42 | [NotNull] 43 | protected static string CreateMessageForMethod([NotNull] string name) 44 | { 45 | return new DiagnosticMessageBuilder() 46 | .OfType(SymbolType.Method) 47 | .Named(name) 48 | .Build(); 49 | } 50 | 51 | [NotNull] 52 | protected static string CreateMessageForParameter([NotNull] string name) 53 | { 54 | return new DiagnosticMessageBuilder() 55 | .OfType(SymbolType.Parameter) 56 | .Named(name) 57 | .Build(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/ParsedSourceCode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using CodeContractNullability.ExternalAnnotations.Storage; 4 | using CodeContractNullability.Utilities; 5 | using JetBrains.Annotations; 6 | using RoslynTestFramework; 7 | 8 | namespace CodeContractNullability.Test 9 | { 10 | internal sealed class ParsedSourceCode 11 | { 12 | [NotNull] 13 | private readonly FixableDocument document; 14 | 15 | [NotNull] 16 | private readonly string attributePrefix; 17 | 18 | [NotNull] 19 | public AnalyzerTestContext TestContext { get; } 20 | 21 | [NotNull] 22 | public ExternalAnnotationsMap ExternalAnnotationsMap { get; } 23 | 24 | [NotNull] 25 | public string ExpectedText => document.ExpectedText; 26 | 27 | public TextComparisonMode CodeComparisonMode { get; } 28 | 29 | public ParsedSourceCode([NotNull] string sourceText, [NotNull] AnalyzerTestContext testContext, [NotNull] ExternalAnnotationsMap externalAnnotationsMap, 30 | [ItemNotNull] [NotNull] IList nestedTypes, TextComparisonMode codeComparisonMode) 31 | { 32 | Guard.NotNull(sourceText, nameof(sourceText)); 33 | Guard.NotNull(testContext, nameof(testContext)); 34 | Guard.NotNull(externalAnnotationsMap, nameof(externalAnnotationsMap)); 35 | Guard.NotNull(nestedTypes, nameof(nestedTypes)); 36 | 37 | document = new FixableDocument(sourceText); 38 | TestContext = testContext.WithCode(document.SourceText, document.SourceSpans); 39 | ExternalAnnotationsMap = externalAnnotationsMap; 40 | attributePrefix = ExtractAttributePrefix(nestedTypes); 41 | CodeComparisonMode = codeComparisonMode; 42 | } 43 | 44 | [NotNull] 45 | private static string ExtractAttributePrefix([NotNull] [ItemNotNull] IList nestedTypes) 46 | { 47 | var attributePrefixBuilder = new StringBuilder(); 48 | 49 | foreach (string nestedType in nestedTypes) 50 | { 51 | int lastSpaceIndex = nestedType.LastIndexOf(' '); 52 | string typeName = lastSpaceIndex != -1 ? nestedType.Substring(lastSpaceIndex + 1) : nestedType; 53 | 54 | attributePrefixBuilder.Append(typeName); 55 | attributePrefixBuilder.Append('.'); 56 | } 57 | 58 | return attributePrefixBuilder.ToString(); 59 | } 60 | 61 | [NotNull] 62 | public string GetExpectedTextForAttribute([NotNull] string attributeName) 63 | { 64 | Guard.NotNull(attributeName, nameof(attributeName)); 65 | 66 | string annotation = "[" + attributePrefix + attributeName + "]"; 67 | return document.ExpectedText.Replace("NullabilityAttributePlaceholder", annotation); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/ReportOnNullableValueTypesTest.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.Utilities; 2 | using Microsoft.CodeAnalysis.CodeFixes; 3 | using RoslynTestFramework; 4 | 5 | namespace CodeContractNullability.Test 6 | { 7 | public abstract class ReportOnNullableValueTypesTest : NullabilityTest 8 | { 9 | protected override string DiagnosticId => BaseAnalyzer.DisableReportOnNullableValueTypesDiagnosticId; 10 | 11 | protected override string NotNullAttributeName => "ignored"; 12 | 13 | protected override CodeFixProvider CreateFixProvider() 14 | { 15 | return new DisableReportOnNullableValueTypesCodeFixProvider(); 16 | } 17 | 18 | private protected override void VerifyNullabilityFix(ParsedSourceCode source, params string[] messages) 19 | { 20 | Guard.NotNull(source, nameof(source)); 21 | 22 | string[] expectedCode = 23 | { 24 | source.ExpectedText 25 | }; 26 | 27 | var fixTestContext = new FixProviderTestContext(source.TestContext, expectedCode, TextComparisonMode.ExactMatch); 28 | 29 | AssertDiagnosticsWithCodeFixes(fixTestContext, messages); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/ReusableAnalyzerOnFileSystemTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using CodeContractNullability.ExternalAnnotations; 4 | using CodeContractNullability.NullabilityAttributes; 5 | using CodeContractNullability.Utilities; 6 | using JetBrains.Annotations; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | using TestableFileSystem.Interfaces; 9 | 10 | namespace CodeContractNullability.Test 11 | { 12 | internal sealed class ReusableAnalyzerOnFileSystemTest : NullabilityTest 13 | { 14 | [NotNull] 15 | private readonly IFileSystem fileSystem; 16 | 17 | [CanBeNull] 18 | private BaseAnalyzer analyzer; 19 | 20 | [CanBeNull] 21 | private CachingExternalAnnotationsResolver externalAnnotationsResolver; 22 | 23 | public ReusableAnalyzerOnFileSystemTest([NotNull] IFileSystem fileSystem) 24 | { 25 | Guard.NotNull(fileSystem, nameof(fileSystem)); 26 | this.fileSystem = fileSystem; 27 | } 28 | 29 | public void VerifyNullabilityDiagnostics([NotNull] ParsedSourceCode source, [NotNull] [ItemNotNull] params string[] messages) 30 | { 31 | VerifyNullabilityDiagnostic(source, messages); 32 | } 33 | 34 | [NotNull] 35 | public string CreateMessageFor(SymbolType symbolType, [NotNull] string name) 36 | { 37 | switch (symbolType) 38 | { 39 | case SymbolType.Field: 40 | { 41 | return CreateMessageForField(name); 42 | } 43 | case SymbolType.Property: 44 | { 45 | return CreateMessageForProperty(name); 46 | } 47 | case SymbolType.Method: 48 | { 49 | return CreateMessageForMethod(name); 50 | } 51 | case SymbolType.Parameter: 52 | { 53 | return CreateMessageForParameter(name); 54 | } 55 | default: 56 | { 57 | throw new NotSupportedException(); 58 | } 59 | } 60 | } 61 | 62 | public void WaitForFileEvictionFromSideBySideCache([NotNull] string path) 63 | { 64 | Guard.NotNull(path, nameof(path)); 65 | 66 | if (externalAnnotationsResolver != null) 67 | { 68 | TimeSpan timeout = TimeSpan.FromSeconds(3); 69 | DateTime startTime = DateTime.UtcNow; 70 | 71 | while (startTime + timeout > DateTime.UtcNow) 72 | { 73 | if (!externalAnnotationsResolver.IsFileInSideBySideCache(path)) 74 | { 75 | return; 76 | } 77 | 78 | Thread.Sleep(50); 79 | } 80 | } 81 | 82 | throw new TimeoutException($"Timed out waiting for eviction of '{path}' from side-by-side cache."); 83 | } 84 | 85 | protected override DiagnosticAnalyzer CreateAnalyzer() 86 | { 87 | if (analyzer == null) 88 | { 89 | analyzer = CreateNullabilityAnalyzer(); 90 | externalAnnotationsResolver = new CachingExternalAnnotationsResolver(fileSystem, new LocalAnnotationCacheProvider(fileSystem)); 91 | 92 | analyzer.FileSystem.Override(fileSystem); 93 | analyzer.ExternalAnnotationsResolver.Override(externalAnnotationsResolver); 94 | analyzer.NullabilityAttributeProvider.Override(new SimpleNullabilityAttributeProvider()); 95 | } 96 | 97 | return analyzer; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/Specs/BugFixes.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.Test.TestDataBuilders; 2 | using Xunit; 3 | 4 | namespace CodeContractNullability.Test.Specs 5 | { 6 | /// 7 | /// Tests for reported bugs. 8 | /// 9 | public sealed class BugFixes : NullabilityTest 10 | { 11 | [Fact] 12 | [GitHubIssue(4)] 13 | public void 14 | When_deriving_constructed_arrays_from_externally_annotated_interface_with_open_array_types_it_must_be_skipped() 15 | { 16 | // Arrange 17 | ParsedSourceCode source = new TypeSourceCodeBuilder() 18 | .InGlobalScope(@" 19 | public interface I 20 | { 21 | T[] P { get; } 22 | T[] M(T[] p, int i); 23 | } 24 | public class C : I 25 | { 26 | public string[] P { get { throw new NotImplementedException(); } } 27 | public string[] M(string[] p, int i) { throw new NotImplementedException(); } 28 | } 29 | ") 30 | .ExternallyAnnotated(new ExternalAnnotationsBuilder() 31 | .IncludingMember(new ExternalAnnotationFragmentBuilder() 32 | .Named("P:I`1.P") 33 | .NotNull()) 34 | .IncludingMember(new ExternalAnnotationFragmentBuilder() 35 | .Named("M:I`1.M(`0[],System.Int32)") 36 | .NotNull() 37 | .WithParameter(new ExternalAnnotationParameterBuilder() 38 | .Named("p") 39 | .NotNull()))) 40 | .Build(); 41 | 42 | // Act and assert 43 | VerifyNullabilityDiagnostic(source); 44 | } 45 | 46 | [Fact] 47 | [GitHubIssue(4)] 48 | public void When_deriving_constructed_arrays_from_externally_annotated_class_with_open_array_types_it_must_be_skipped() 49 | { 50 | // Arrange 51 | ParsedSourceCode source = new TypeSourceCodeBuilder() 52 | .InGlobalScope(@" 53 | public abstract class B 54 | { 55 | public abstract T[] P { get; } 56 | public abstract T[] M(T[] p, int i); 57 | } 58 | public class D : B 59 | { 60 | public override string[] P { get { throw new NotImplementedException(); } } 61 | public override string[] M(string[] p, int i) { throw new NotImplementedException(); } 62 | } 63 | ") 64 | .ExternallyAnnotated(new ExternalAnnotationsBuilder() 65 | .IncludingMember(new ExternalAnnotationFragmentBuilder() 66 | .Named("P:B`1.P") 67 | .NotNull()) 68 | .IncludingMember(new ExternalAnnotationFragmentBuilder() 69 | .Named("M:B`1.M(`0[],System.Int32)") 70 | .NotNull() 71 | .WithParameter(new ExternalAnnotationParameterBuilder() 72 | .Named("p") 73 | .NotNull()))) 74 | .Build(); 75 | 76 | // Act and assert 77 | VerifyNullabilityDiagnostic(source); 78 | } 79 | 80 | [Fact] 81 | [GitHubIssue(13)] 82 | public void 83 | When_parameter_in_non_generic_class_that_derives_from_generic_base_class_that_implements_annotated_generic_interface_it_must_be_skipped() 84 | { 85 | // Arrange 86 | ParsedSourceCode source = new TypeSourceCodeBuilder() 87 | .InGlobalScope(@" 88 | public interface IErrorDemoInterface 89 | { 90 | void DoSomething([NotNull] T text); 91 | } 92 | public class ErrorDemoBase : IErrorDemoInterface 93 | { 94 | public virtual void DoSomething(T text) 95 | { 96 | } 97 | } 98 | public class ErrorDemo : ErrorDemoBase 99 | { 100 | public override void DoSomething(string text) 101 | { 102 | } 103 | } 104 | ") 105 | .Build(); 106 | 107 | // Act and assert 108 | VerifyNullabilityDiagnostic(source); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/Specs/ItemNullabilityFixAllSpecs.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CodeContractNullability.Test.TestDataBuilders; 3 | using Xunit; 4 | 5 | namespace CodeContractNullability.Test.Specs 6 | { 7 | public sealed class ItemNullabilityFixAllSpecs : ItemNullabilityTest 8 | { 9 | [Fact] 10 | public void When_parameter_item_types_are_reference_it_must_all_be_reported_and_fixed() 11 | { 12 | ParsedSourceCode source = new TypeSourceCodeBuilder() 13 | .Using(typeof(IEnumerable<>).Namespace) 14 | .InGlobalScope(@" 15 | class C 16 | { 17 | void M([+NullabilityAttributePlaceholder+] IEnumerable [|p1|], [+NullabilityAttributePlaceholder+] IEnumerable [|p2|]) 18 | { 19 | } 20 | } 21 | ") 22 | .Build(); 23 | 24 | VerifyNullabilityFixes(source, 25 | CreateMessageForParameter("p1"), 26 | CreateMessageForParameter("p2")); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/Specs/LambdaSpecs.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.Test.TestDataBuilders; 2 | using Xunit; 3 | 4 | namespace CodeContractNullability.Test.Specs 5 | { 6 | /// 7 | /// Tests for reporting nullability diagnostics on lambda expressions. 8 | /// 9 | public sealed class LambdaSpecs : NullabilityTest 10 | { 11 | [Fact] 12 | public void When_lambda_parameter_is_nullable_it_must_be_skipped() 13 | { 14 | // Arrange 15 | ParsedSourceCode source = new MemberSourceCodeBuilder() 16 | .InDefaultClass(@" 17 | public void M() 18 | { 19 | Func f = p => 1; 20 | } 21 | ") 22 | .Build(); 23 | 24 | // Act and assert 25 | VerifyNullabilityDiagnostic(source); 26 | } 27 | 28 | [Fact] 29 | public void When_lambda_return_value_is_nullable_it_must_be_skipped() 30 | { 31 | // Arrange 32 | ParsedSourceCode source = new MemberSourceCodeBuilder() 33 | .InDefaultClass(@" 34 | public void M() 35 | { 36 | Func f = p => null; 37 | } 38 | ") 39 | .Build(); 40 | 41 | // Act and assert 42 | VerifyNullabilityDiagnostic(source); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/Specs/LocalFunctionSpecs.cs: -------------------------------------------------------------------------------- 1 | #if !NET452 2 | using CodeContractNullability.Test.TestDataBuilders; 3 | using Xunit; 4 | 5 | namespace CodeContractNullability.Test.Specs 6 | { 7 | /// 8 | /// Tests for reporting nullability diagnostics on local functions. 9 | /// 10 | public sealed class LocalFunctionSpecs : NullabilityTest 11 | { 12 | [Fact] 13 | public void When_local_function_parameter_is_nullable_it_must_be_skipped() 14 | { 15 | // Arrange 16 | ParsedSourceCode source = new MemberSourceCodeBuilder() 17 | .InDefaultClass(@" 18 | public void M() 19 | { 20 | void L(string s) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | 25 | L(null); 26 | } 27 | ") 28 | .Build(); 29 | 30 | // Act and assert 31 | VerifyNullabilityDiagnostic(source); 32 | } 33 | 34 | [Fact] 35 | public void When_local_function_return_value_is_nullable_it_must_be_skipped() 36 | { 37 | // Arrange 38 | ParsedSourceCode source = new MemberSourceCodeBuilder() 39 | .InDefaultClass(@" 40 | public void M() 41 | { 42 | string L() 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | 47 | string s = L(); 48 | } 49 | ") 50 | .Build(); 51 | 52 | // Act and assert 53 | VerifyNullabilityDiagnostic(source); 54 | } 55 | } 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/Specs/NullabilityFixAllSpecs.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.Test.TestDataBuilders; 2 | using Xunit; 3 | 4 | namespace CodeContractNullability.Test.Specs 5 | { 6 | public sealed class NullabilityFixAllSpecs : NullabilityTest 7 | { 8 | [Fact] 9 | public void When_parameter_types_are_reference_it_must_all_be_reported_and_fixed() 10 | { 11 | ParsedSourceCode source = new TypeSourceCodeBuilder() 12 | .InGlobalScope(@" 13 | class C 14 | { 15 | void M([+NullabilityAttributePlaceholder+] string [|p1|], [+NullabilityAttributePlaceholder+] string [|p2|]) 16 | { 17 | } 18 | } 19 | ") 20 | .Build(); 21 | 22 | VerifyNullabilityFixes(source, 23 | CreateMessageForParameter("p1"), 24 | CreateMessageForParameter("p2")); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/Specs/ReportOnNullableValueTypesSpecs.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.Test.TestDataBuilders; 2 | using Xunit; 3 | 4 | namespace CodeContractNullability.Test.Specs 5 | { 6 | /// 7 | /// Tests for disabling reporting on nullable types, which is configurable. 8 | /// 9 | public sealed class ReportOnNullableValueTypesSpecs : ReportOnNullableValueTypesTest 10 | { 11 | [Fact] 12 | public void When_field_type_is_nullable_it_must_be_reported_and_disabled() 13 | { 14 | // Arrange 15 | ParsedSourceCode source = new MemberSourceCodeBuilder() 16 | .InDefaultClass(@" 17 | int? [|f|]; 18 | ") 19 | .Build(); 20 | 21 | // Act and assert 22 | VerifyNullabilityFix(source, 23 | "IMPORTANT: Due to a bug in Visual Studio, additional steps are needed. Expand the arrow to the left of this message for details."); 24 | } 25 | 26 | [Fact] 27 | public void When_field_type_is_nullable_and_settings_are_corrupt_it_must_be_reported_and_disabled() 28 | { 29 | // Arrange 30 | ParsedSourceCode source = new MemberSourceCodeBuilder() 31 | .WithSettingsText("*** BAD XML ***") 32 | .InDefaultClass(@" 33 | int? [|f|]; 34 | ") 35 | .Build(); 36 | 37 | // Act and assert 38 | VerifyNullabilityFix(source, 39 | "IMPORTANT: Due to a bug in Visual Studio, additional steps are needed. Expand the arrow to the left of this message for details."); 40 | } 41 | 42 | [Fact] 43 | public void When_field_type_is_nullable_but_analysis_is_disabled_it_must_be_skipped() 44 | { 45 | // Arrange 46 | ParsedSourceCode source = new MemberSourceCodeBuilder() 47 | .WithSettings(new AnalyzerSettingsBuilder() 48 | .DisableReportOnNullableValueTypes) 49 | .InDefaultClass(@" 50 | int? f; 51 | ") 52 | .Build(); 53 | 54 | // Act and assert 55 | VerifyNullabilityFix(source); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/SymbolType.cs: -------------------------------------------------------------------------------- 1 | namespace CodeContractNullability.Test 2 | { 3 | internal enum SymbolType 4 | { 5 | Field, 6 | Property, 7 | Method, 8 | Delegate, 9 | Parameter 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TempAssemblyScope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using JetBrains.Annotations; 4 | 5 | namespace CodeContractNullability.Test 6 | { 7 | #pragma warning disable FS01 // Usage of non-testable file system. 8 | internal sealed class TempAssemblyScope : IDisposable 9 | { 10 | [NotNull] 11 | public string TempPath { get; } 12 | 13 | [NotNull] 14 | public string AssemblyPath => TempPath + ".dll"; 15 | 16 | public TempAssemblyScope() 17 | { 18 | TempPath = Path.GetTempFileName(); 19 | } 20 | 21 | public void Dispose() 22 | { 23 | File.Delete(AssemblyPath); 24 | File.Delete(TempPath); 25 | } 26 | } 27 | #pragma warning restore FS01 // Usage of non-testable file system. 28 | } 29 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/AnalyzerSettingsBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Text; 3 | using System.Threading; 4 | using CodeContractNullability.Utilities; 5 | using JetBrains.Annotations; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | using Microsoft.CodeAnalysis.Text; 9 | 10 | namespace CodeContractNullability.Test.TestDataBuilders 11 | { 12 | internal sealed class AnalyzerSettingsBuilder : ITestDataBuilder 13 | { 14 | private bool disableReportOnNullableValueTypes; 15 | 16 | [NotNull] 17 | public AnalyzerSettingsBuilder DisableReportOnNullableValueTypes 18 | { 19 | get 20 | { 21 | disableReportOnNullableValueTypes = true; 22 | return this; 23 | } 24 | } 25 | 26 | public AnalyzerSettings Build() 27 | { 28 | return new AnalyzerSettings(disableReportOnNullableValueTypes); 29 | } 30 | 31 | [NotNull] 32 | public static AnalyzerOptions ToOptions([NotNull] AnalyzerSettings settings) 33 | { 34 | Guard.NotNull(settings, nameof(settings)); 35 | 36 | string content = SettingsProvider.ToFileContent(settings); 37 | return ToOptions(content); 38 | } 39 | 40 | [NotNull] 41 | public static AnalyzerOptions ToOptions([NotNull] string settingsText) 42 | { 43 | Guard.NotNull(settingsText, nameof(settingsText)); 44 | 45 | AdditionalText additionalText = new FakeAdditionalText(settingsText); 46 | return new AnalyzerOptions(ImmutableArray.Create(additionalText)); 47 | } 48 | 49 | private sealed class FakeAdditionalText : AdditionalText 50 | { 51 | [NotNull] 52 | private readonly SourceText sourceText; 53 | 54 | [NotNull] 55 | public override string Path { get; } = SettingsProvider.SettingsFileName; 56 | 57 | public FakeAdditionalText([NotNull] string content) 58 | { 59 | sourceText = new FakeSourceText(content, SettingsProvider.CreateEncoding()); 60 | } 61 | 62 | [NotNull] 63 | public override SourceText GetText(CancellationToken cancellationToken = default) 64 | { 65 | return sourceText; 66 | } 67 | 68 | private sealed class FakeSourceText : SourceText 69 | { 70 | [NotNull] 71 | private readonly string content; 72 | 73 | [NotNull] 74 | public override Encoding Encoding { get; } 75 | 76 | public override int Length => content.Length; 77 | 78 | public override char this[int position] => content[position]; 79 | 80 | public FakeSourceText([NotNull] string content, [NotNull] Encoding encoding) 81 | { 82 | Guard.NotNull(content, nameof(content)); 83 | Guard.NotNull(encoding, nameof(encoding)); 84 | 85 | this.content = content; 86 | Encoding = encoding; 87 | } 88 | 89 | public override void CopyTo(int sourceIndex, [NotNull] char[] destination, int destinationIndex, int count) 90 | { 91 | content.CopyTo(sourceIndex, destination, destinationIndex, count); 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/ConversionDiagnosticMessageBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace CodeContractNullability.Test.TestDataBuilders 5 | { 6 | internal sealed class ConversionDiagnosticMessageBuilder : ITestDataBuilder 7 | { 8 | [CanBeNull] 9 | private SymbolType? symbolType; 10 | 11 | [CanBeNull] 12 | private string symbolName; 13 | 14 | public string Build() 15 | { 16 | if (symbolType == null || symbolName == null) 17 | { 18 | throw new InvalidOperationException("Symbol type or name must be set."); 19 | } 20 | 21 | string typeName = symbolType.ToString().ToLowerInvariant(); 22 | return $"Resharper nullability annotation(s) on {typeName} '{symbolName}' can be converted to C# syntax."; 23 | } 24 | 25 | [NotNull] 26 | public ConversionDiagnosticMessageBuilder OfType(SymbolType type) 27 | { 28 | symbolType = type; 29 | return this; 30 | } 31 | 32 | [NotNull] 33 | public ConversionDiagnosticMessageBuilder Named([CanBeNull] string name) 34 | { 35 | symbolName = name; 36 | return this; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/DiagnosticMessageBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace CodeContractNullability.Test.TestDataBuilders 5 | { 6 | internal sealed class DiagnosticMessageBuilder : ITestDataBuilder 7 | { 8 | [CanBeNull] 9 | private SymbolType? symbolType; 10 | 11 | [CanBeNull] 12 | private string symbolName; 13 | 14 | private bool isItem; 15 | 16 | public string Build() 17 | { 18 | if (symbolType == null || symbolName == null) 19 | { 20 | throw new InvalidOperationException("Symbol type or name must be set."); 21 | } 22 | 23 | string nullability = isItem ? "item nullability" : "nullability"; 24 | return $"{symbolType} '{symbolName}' is missing {nullability} annotation."; 25 | } 26 | 27 | [NotNull] 28 | public DiagnosticMessageBuilder OfType(SymbolType type) 29 | { 30 | symbolType = type; 31 | return this; 32 | } 33 | 34 | [NotNull] 35 | public DiagnosticMessageBuilder Named([CanBeNull] string name) 36 | { 37 | symbolName = name; 38 | return this; 39 | } 40 | 41 | [NotNull] 42 | public DiagnosticMessageBuilder ForItem() 43 | { 44 | isItem = true; 45 | return this; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/ExactSourceCodeBuilder.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.ExternalAnnotations.Storage; 2 | using CodeContractNullability.Utilities; 3 | using JetBrains.Annotations; 4 | using RoslynTestFramework; 5 | 6 | namespace CodeContractNullability.Test.TestDataBuilders 7 | { 8 | internal sealed class ExactSourceCodeBuilder : ITestDataBuilder 9 | { 10 | [NotNull] 11 | [ItemNotNull] 12 | private static readonly string[] EmptyStringArray = new string[0]; 13 | 14 | [NotNull] 15 | private string sourceText = string.Empty; 16 | 17 | [NotNull] 18 | public static string PublicGlobalNullabilityAttributes => new NullabilityAttributesBuilder().InGlobalNamespace().Build().SourceText; 19 | 20 | public ParsedSourceCode Build() 21 | { 22 | ExternalAnnotationsMap map = new ExternalAnnotationsBuilder().Build(); 23 | 24 | return new ParsedSourceCode(sourceText, SourceCodeBuilder.DefaultTestContext, map, EmptyStringArray, TextComparisonMode.ExactMatch); 25 | } 26 | 27 | [NotNull] 28 | public ExactSourceCodeBuilder Exactly([NotNull] string text) 29 | { 30 | Guard.NotNull(text, nameof(text)); 31 | 32 | sourceText = NormalizeLineBreaks(text); 33 | return this; 34 | } 35 | 36 | [NotNull] 37 | private static string NormalizeLineBreaks([NotNull] string text) 38 | { 39 | return text.Replace("\n", "\r\n").Replace("\r\r", "\r"); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/ExternalAnnotationFragmentBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Xml.Linq; 3 | using CodeContractNullability.Utilities; 4 | using JetBrains.Annotations; 5 | 6 | namespace CodeContractNullability.Test.TestDataBuilders 7 | { 8 | internal sealed class ExternalAnnotationFragmentBuilder : ITestDataBuilder 9 | { 10 | /* Example output: 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | */ 24 | 25 | [NotNull] 26 | [ItemNotNull] 27 | private readonly List parameters = new List(); 28 | 29 | [NotNull] 30 | private string memberName = "value"; 31 | 32 | [CanBeNull] 33 | private bool? isNotNull; 34 | 35 | public XElement Build() 36 | { 37 | var element = new XElement("member", new XAttribute("name", memberName)); 38 | 39 | if (isNotNull != null) 40 | { 41 | element.Add(new XElement("attribute", 42 | new XAttribute("ctor", 43 | isNotNull.Value 44 | ? "M:JetBrains.Annotations.NotNullAttribute.#ctor" 45 | : "M:JetBrains.Annotations.CanBeNullAttribute.#ctor"))); 46 | } 47 | 48 | foreach (XElement parameter in parameters) 49 | { 50 | element.Add(parameter); 51 | } 52 | 53 | return element; 54 | } 55 | 56 | [NotNull] 57 | public ExternalAnnotationFragmentBuilder Named([NotNull] string name) 58 | { 59 | Guard.NotNull(name, nameof(name)); 60 | 61 | memberName = name; 62 | return this; 63 | } 64 | 65 | [NotNull] 66 | public ExternalAnnotationFragmentBuilder NotNull() 67 | { 68 | isNotNull = true; 69 | return this; 70 | } 71 | 72 | [NotNull] 73 | public ExternalAnnotationFragmentBuilder CanBeNull() 74 | { 75 | isNotNull = false; 76 | return this; 77 | } 78 | 79 | [NotNull] 80 | public ExternalAnnotationFragmentBuilder WithParameter([NotNull] ExternalAnnotationParameterBuilder builder) 81 | { 82 | Guard.NotNull(builder, nameof(builder)); 83 | 84 | XElement parameter = builder.Build(); 85 | parameters.Add(parameter); 86 | return this; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/ExternalAnnotationParameterBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml.Linq; 3 | using CodeContractNullability.Utilities; 4 | using JetBrains.Annotations; 5 | 6 | namespace CodeContractNullability.Test.TestDataBuilders 7 | { 8 | internal sealed class ExternalAnnotationParameterBuilder : ITestDataBuilder 9 | { 10 | [CanBeNull] 11 | private bool? isNotNull; 12 | 13 | [NotNull] 14 | private string parameterName = "value"; 15 | 16 | public XElement Build() 17 | { 18 | if (isNotNull == null) 19 | { 20 | throw new InvalidOperationException("Nullability must be set explicitly."); 21 | } 22 | 23 | return new XElement("parameter", new XAttribute("name", parameterName), 24 | new XElement("attribute", 25 | new XAttribute("ctor", 26 | isNotNull.Value 27 | ? "M:JetBrains.Annotations.NotNullAttribute.#ctor" 28 | : "M:JetBrains.Annotations.CanBeNullAttribute.#ctor"))); 29 | } 30 | 31 | [NotNull] 32 | public ExternalAnnotationParameterBuilder Named([NotNull] string name) 33 | { 34 | Guard.NotNull(name, nameof(name)); 35 | 36 | parameterName = name; 37 | return this; 38 | } 39 | 40 | [NotNull] 41 | public ExternalAnnotationParameterBuilder NotNull() 42 | { 43 | isNotNull = true; 44 | return this; 45 | } 46 | 47 | [NotNull] 48 | public ExternalAnnotationParameterBuilder CanBeNull() 49 | { 50 | isNotNull = false; 51 | return this; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/ExternalAnnotationsBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Xml.Linq; 6 | using CodeContractNullability.ExternalAnnotations; 7 | using CodeContractNullability.ExternalAnnotations.Storage; 8 | using CodeContractNullability.Utilities; 9 | using JetBrains.Annotations; 10 | 11 | namespace CodeContractNullability.Test.TestDataBuilders 12 | { 13 | internal sealed class ExternalAnnotationsBuilder : ITestDataBuilder 14 | { 15 | [NotNull] 16 | [ItemNotNull] 17 | private readonly List fragments = new List(); 18 | 19 | public ExternalAnnotationsMap Build() 20 | { 21 | if (fragments.Any()) 22 | { 23 | string document = GetText(); 24 | return Parse(document); 25 | } 26 | 27 | return new ExternalAnnotationsMap(); 28 | } 29 | 30 | [NotNull] 31 | public string GetXml() 32 | { 33 | string text = GetText(); 34 | XDocument document = XDocument.Parse(text); 35 | return document.ToString(); 36 | } 37 | 38 | [NotNull] 39 | private string GetText() 40 | { 41 | var textBuilder = new StringBuilder(); 42 | 43 | textBuilder.AppendLine(@" 44 | "); 45 | 46 | foreach (XElement fragment in fragments) 47 | { 48 | textBuilder.AppendLine(fragment.ToString()); 49 | } 50 | 51 | textBuilder.AppendLine(""); 52 | return textBuilder.ToString(); 53 | } 54 | 55 | [NotNull] 56 | private static ExternalAnnotationsMap Parse([NotNull] string document) 57 | { 58 | var annotations = new ExternalAnnotationsMap(); 59 | var parser = new ExternalAnnotationDocumentParser(); 60 | 61 | using (var reader = new StringReader(document)) 62 | { 63 | parser.ProcessDocument(reader, annotations); 64 | } 65 | 66 | return annotations; 67 | } 68 | 69 | [NotNull] 70 | public ExternalAnnotationsBuilder IncludingMember([NotNull] ExternalAnnotationFragmentBuilder builder) 71 | { 72 | Guard.NotNull(builder, nameof(builder)); 73 | 74 | XElement fragment = builder.Build(); 75 | fragments.Add(fragment); 76 | return this; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/ITestDataBuilder.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace CodeContractNullability.Test.TestDataBuilders 4 | { 5 | internal interface ITestDataBuilder 6 | { 7 | [NotNull] 8 | // ReSharper disable once UnusedMemberInSuper.Global 9 | T Build(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/MemberSourceCodeBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using CodeContractNullability.Utilities; 4 | using JetBrains.Annotations; 5 | 6 | namespace CodeContractNullability.Test.TestDataBuilders 7 | { 8 | /// 9 | internal sealed class MemberSourceCodeBuilder : SourceCodeBuilder 10 | { 11 | [NotNull] 12 | [ItemNotNull] 13 | private readonly List members = new List(); 14 | 15 | protected override string GetSourceCode() 16 | { 17 | var builder = new StringBuilder(); 18 | 19 | AppendClassStart(builder); 20 | AppendClassMembers(builder); 21 | AppendClassEnd(builder); 22 | 23 | return builder.ToString(); 24 | } 25 | 26 | private static void AppendClassStart([NotNull] StringBuilder builder) 27 | { 28 | builder.AppendLine("public class Test"); 29 | builder.AppendLine("{"); 30 | } 31 | 32 | private void AppendClassMembers([NotNull] StringBuilder builder) 33 | { 34 | string code = GetLinesOfCode(members); 35 | builder.AppendLine(code); 36 | } 37 | 38 | private static void AppendClassEnd([NotNull] StringBuilder builder) 39 | { 40 | builder.AppendLine("}"); 41 | } 42 | 43 | [NotNull] 44 | public MemberSourceCodeBuilder InDefaultClass([NotNull] string memberCode) 45 | { 46 | Guard.NotNull(memberCode, nameof(memberCode)); 47 | 48 | members.Add(memberCode); 49 | return this; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/NullabilityAttributesBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CodeContractNullability.Utilities; 3 | using JetBrains.Annotations; 4 | 5 | namespace CodeContractNullability.Test.TestDataBuilders 6 | { 7 | internal sealed class NullabilityAttributesBuilder : ITestDataBuilder 8 | { 9 | [NotNull] 10 | [ItemNotNull] 11 | private readonly List nestedTypes = new List(); 12 | 13 | [NotNull] 14 | private string codeNamespace = "Namespace.For.JetBrains.Annotation.Attributes"; 15 | 16 | public NullabilityAttributesDefinition Build() 17 | { 18 | return new NullabilityAttributesDefinition(codeNamespace, nestedTypes); 19 | } 20 | 21 | [NotNull] 22 | public NullabilityAttributesBuilder InCodeNamespace([NotNull] string ns) 23 | { 24 | Guard.NotNull(ns, nameof(ns)); 25 | 26 | codeNamespace = ns; 27 | return this; 28 | } 29 | 30 | [NotNull] 31 | public NullabilityAttributesBuilder InGlobalNamespace() 32 | { 33 | return InCodeNamespace(string.Empty); 34 | } 35 | 36 | [NotNull] 37 | public NullabilityAttributesBuilder NestedInTypes([NotNull] [ItemNotNull] IEnumerable scopes) 38 | { 39 | Guard.NotNull(scopes, nameof(scopes)); 40 | 41 | nestedTypes.AddRange(scopes); 42 | return this; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/NullabilityAttributesDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text; 4 | using CodeContractNullability.Utilities; 5 | using JetBrains.Annotations; 6 | 7 | namespace CodeContractNullability.Test.TestDataBuilders 8 | { 9 | internal sealed class NullabilityAttributesDefinition 10 | { 11 | private const string AttributesDeclarationText = @" 12 | [System.AttributeUsage( 13 | System.AttributeTargets.Method | System.AttributeTargets.Parameter | System.AttributeTargets.Property | 14 | System.AttributeTargets.Delegate | System.AttributeTargets.Field | System.AttributeTargets.Event)] 15 | [System.Diagnostics.Conditional(""JETBRAINS_ANNOTATIONS"")] 16 | public sealed class CanBeNullAttribute : System.Attribute { } 17 | 18 | [System.AttributeUsage( 19 | System.AttributeTargets.Method | System.AttributeTargets.Parameter | System.AttributeTargets.Property | 20 | System.AttributeTargets.Delegate | System.AttributeTargets.Field | System.AttributeTargets.Event)] 21 | [System.Diagnostics.Conditional(""JETBRAINS_ANNOTATIONS"")] 22 | public sealed class NotNullAttribute : System.Attribute { } 23 | 24 | [System.AttributeUsage( 25 | System.AttributeTargets.Method | System.AttributeTargets.Parameter | System.AttributeTargets.Property | 26 | System.AttributeTargets.Delegate | System.AttributeTargets.Field)] 27 | [System.Diagnostics.Conditional(""JETBRAINS_ANNOTATIONS"")] 28 | public sealed class ItemNotNullAttribute : System.Attribute { } 29 | 30 | [System.AttributeUsage( 31 | System.AttributeTargets.Method | System.AttributeTargets.Parameter | System.AttributeTargets.Property | 32 | System.AttributeTargets.Delegate | System.AttributeTargets.Field)] 33 | [System.Diagnostics.Conditional(""JETBRAINS_ANNOTATIONS"")] 34 | public sealed class ItemCanBeNullAttribute : System.Attribute { } 35 | "; 36 | 37 | [NotNull] 38 | private static readonly string AttributesDeclarationTextIndented; 39 | 40 | [NotNull] 41 | public string CodeNamespace { get; } 42 | 43 | [NotNull] 44 | [ItemNotNull] 45 | public IList NestedTypes { get; } 46 | 47 | [NotNull] 48 | public string SourceText 49 | { 50 | get 51 | { 52 | var textBuilder = new StringBuilder(); 53 | 54 | if (true) 55 | { 56 | if (!string.IsNullOrEmpty(CodeNamespace)) 57 | { 58 | textBuilder.AppendLine(); 59 | textBuilder.AppendLine("namespace " + CodeNamespace); 60 | textBuilder.AppendLine("{"); 61 | } 62 | 63 | foreach (string nestedType in NestedTypes) 64 | { 65 | textBuilder.AppendLine(nestedType); 66 | textBuilder.AppendLine("{"); 67 | } 68 | 69 | textBuilder.AppendLine(!string.IsNullOrEmpty(CodeNamespace) 70 | ? AttributesDeclarationTextIndented 71 | : AttributesDeclarationText); 72 | 73 | for (int index = 0; index < NestedTypes.Count; index++) 74 | { 75 | textBuilder.AppendLine("}"); 76 | } 77 | 78 | if (!string.IsNullOrEmpty(CodeNamespace)) 79 | { 80 | textBuilder.AppendLine("}"); 81 | } 82 | } 83 | 84 | return textBuilder.ToString(); 85 | } 86 | } 87 | 88 | static NullabilityAttributesDefinition() 89 | { 90 | AttributesDeclarationTextIndented = PrefixLinesWith(" "); 91 | } 92 | 93 | public NullabilityAttributesDefinition([NotNull] string codeNamespace, [NotNull] [ItemNotNull] IList nestedTypes) 94 | { 95 | Guard.NotNull(codeNamespace, nameof(codeNamespace)); 96 | Guard.NotNull(nestedTypes, nameof(nestedTypes)); 97 | 98 | CodeNamespace = codeNamespace; 99 | NestedTypes = nestedTypes; 100 | } 101 | 102 | [NotNull] 103 | private static string PrefixLinesWith([NotNull] string prefix) 104 | { 105 | var builder = new StringBuilder(); 106 | 107 | using (var reader = new StringReader(AttributesDeclarationText)) 108 | { 109 | string line; 110 | 111 | while ((line = reader.ReadLine()) != null) 112 | { 113 | builder.Append(prefix); 114 | builder.AppendLine(line); 115 | } 116 | } 117 | 118 | return builder.ToString(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Test/TestDataBuilders/TypeSourceCodeBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using CodeContractNullability.Utilities; 4 | using JetBrains.Annotations; 5 | 6 | namespace CodeContractNullability.Test.TestDataBuilders 7 | { 8 | /// 9 | internal sealed class TypeSourceCodeBuilder : SourceCodeBuilder 10 | { 11 | [NotNull] 12 | [ItemNotNull] 13 | private readonly List types = new List(); 14 | 15 | protected override string GetSourceCode() 16 | { 17 | var builder = new StringBuilder(); 18 | 19 | AppendTypes(builder); 20 | 21 | return builder.ToString(); 22 | } 23 | 24 | private void AppendTypes([NotNull] StringBuilder builder) 25 | { 26 | string code = GetLinesOfCode(types); 27 | builder.AppendLine(code); 28 | } 29 | 30 | [NotNull] 31 | public TypeSourceCodeBuilder ClearGlobalScope() 32 | { 33 | types.Clear(); 34 | return this; 35 | } 36 | 37 | [NotNull] 38 | public TypeSourceCodeBuilder InGlobalScope([NotNull] string typeCode) 39 | { 40 | Guard.NotNull(typeCode, nameof(typeCode)); 41 | 42 | types.Add(typeCode); 43 | return this; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Vsix/CodeContractNullability.Vsix.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 15.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | Debug 10 | AnyCPU 11 | AnyCPU 12 | 2.0 13 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | {3B498FFB-024D-4BB1-8D55-165FF10FCEA5} 15 | Library 16 | Properties 17 | CodeContractNullability 18 | ResharperCodeContractNullability 19 | v4.6 20 | false 21 | false 22 | false 23 | false 24 | false 25 | false 26 | Roslyn 27 | 28 | 29 | 30 | False 31 | 32 | 33 | true 34 | full 35 | false 36 | ..\..\..\bin\Debug 37 | DEBUG;TRACE 38 | prompt 39 | 4 40 | 41 | 42 | pdbonly 43 | true 44 | ..\..\..\bin\Release 45 | TRACE 46 | prompt 47 | 4 48 | 49 | 50 | Program 51 | $(DevEnvDir)devenv.exe 52 | /rootsuffix Roslyn 53 | 54 | 55 | 56 | 57 | Designer 58 | 59 | 60 | 61 | 62 | {1881B7FC-7A28-42ED-94B4-3F2B03FEA0BA} 63 | CodeContractNullability 64 | 65 | 66 | 67 | 68 | LICENSE.txt 69 | true 70 | 71 | 72 | 73 | 74 | ..\..\packages\MsgPack.Cli.1.0.1\lib\net46\MsgPack.dll 75 | 76 | 77 | ..\..\packages\TestableFileSystem.Interfaces.2.0.1\lib\net45\TestableFileSystem.Interfaces.dll 78 | 79 | 80 | ..\..\packages\TestableFileSystem.Wrappers.2.0.1\lib\net45\TestableFileSystem.Wrappers.dll 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | (\d+)\.(\d+).(\d+) 94 | %(assemblyVersionInfo.Version) 95 | $([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern))) 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 106 | 107 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Vsix/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability.Vsix/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 4 | 5 | 7 | Resharper Code Contract Nullability 8 | Reports diagnostics, helping you to annotate your source tree with (Item)NotNull / (Item)CanBeNull attributes. See also: https://www.jetbrains.com/resharper/help/Code_Analysis__Code_Annotations.html 9 | https://github.com/bkoelman/ResharperCodeContractNullability 10 | LICENSE.txt 11 | resharper, code contracts, annotations, nullability, analyzer, canbenull, notnull, itemcanbenull, itemnotnull, rnul, rinul 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/AnalyzerSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using JetBrains.Annotations; 4 | 5 | namespace CodeContractNullability 6 | { 7 | [DataContract(Namespace = SettingsNamespace)] 8 | [Serializable] 9 | public sealed class AnalyzerSettings 10 | { 11 | private const string SettingsNamespace = "ResharperCodeContractNullabilitySettings"; 12 | 13 | [NotNull] 14 | internal static readonly AnalyzerSettings Default = new AnalyzerSettings(); 15 | 16 | [DataMember(Name = "disableReportOnNullableValueTypes")] 17 | public bool DisableReportOnNullableValueTypes { get; private set; } 18 | 19 | public AnalyzerSettings() 20 | { 21 | } 22 | 23 | public AnalyzerSettings(bool disableReportOnNullableValueTypes) 24 | { 25 | DisableReportOnNullableValueTypes = disableReportOnNullableValueTypes; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/CodeContractItemNullabilityAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.Utilities; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | 5 | namespace CodeContractNullability 6 | { 7 | /// 8 | /// Entry point for analyzer that creates diagnostics for members that need item nullability annotation. 9 | /// 10 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 11 | public sealed class CodeContractItemNullabilityAnalyzer : BaseAnalyzer 12 | { 13 | public const string DiagnosticId = "RINUL"; 14 | 15 | public CodeContractItemNullabilityAnalyzer() 16 | : base(true) 17 | { 18 | } 19 | 20 | protected override DiagnosticDescriptor CreateRuleFor(string memberTypePascalCase) 21 | { 22 | string title = $"{memberTypePascalCase} is missing item nullability annotation."; 23 | string messageFormat = $"{memberTypePascalCase} '{{0}}' is missing item nullability annotation."; 24 | 25 | string description = 26 | $"The item type of this sequence/collection {memberTypePascalCase.ToCamelCase()} is a reference type or nullable type; it should be annotated with [ItemNotNull] or [ItemCanBeNull]."; 27 | 28 | return new DiagnosticDescriptor(DiagnosticId, title, messageFormat, Category, DiagnosticSeverity.Warning, true, description, 29 | "https://github.com/bkoelman/ResharperCodeContractNullability/blob/master/doc/reference/RINUL_MemberIsMissingItemNullabilityAnnotation.md"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/CodeContractItemNullabilityCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using JetBrains.Annotations; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeFixes; 6 | 7 | namespace CodeContractNullability 8 | { 9 | /// 10 | /// Provides code fixes for diagnostics reported by . 11 | /// 12 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CodeContractItemNullabilityCodeFixProvider))] 13 | [Shared] 14 | public sealed class CodeContractItemNullabilityCodeFixProvider : BaseCodeFixProvider 15 | { 16 | [ItemNotNull] 17 | public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CodeContractItemNullabilityAnalyzer.DiagnosticId); 18 | 19 | public CodeContractItemNullabilityCodeFixProvider() 20 | : base(true) 21 | { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/CodeContractNullability.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net45 5 | false 6 | True 7 | 8 | 9 | 10 | 11 | ResharperCodeContractNullability.NuGetBugRequiresNewId 12 | Resharper Code Contract Nullability 13 | 2.0.3-pre 14 | Bart Koelman 15 | 16 | Apache-2.0 17 | https://github.com/bkoelman/ResharperCodeContractNullability 18 | false 19 | Reports diagnostics, helping you to annotate your source tree with (Item)NotNull / (Item)CanBeNull attributes. See also: https://www.jetbrains.com/resharper/help/Code_Analysis__Code_Annotations.html 20 | 21 | You need Visual Studio 2015/2017/2019 and Resharper v9 or higher to use this analyzer. See package "ResharperCodeContractNullabilityFxCop" if you use Visual Studio 2013 or lower. 22 | Fixed packaging issue. 23 | Apache License, Version 2.0 24 | resharper code contracts annotations nullability analyzer canbenull notnull itemcanbenull itemnotnull rnul rinul 25 | true 26 | true 27 | 28 | 29 | 30 | ..\..\..\bin\Debug 31 | 32 | 33 | 34 | ..\..\..\bin\Release 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | all 59 | runtime; build; native; contentfiles; analyzers; buildtransitive 60 | 61 | 62 | 63 | 64 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/CodeContractNullabilityAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.Utilities; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | 5 | namespace CodeContractNullability 6 | { 7 | /// 8 | /// Entry point for analyzer that creates diagnostics for members that need nullability annotation. 9 | /// 10 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 11 | public sealed class CodeContractNullabilityAnalyzer : BaseAnalyzer 12 | { 13 | public const string DiagnosticId = "RNUL"; 14 | 15 | public CodeContractNullabilityAnalyzer() 16 | : base(false) 17 | { 18 | } 19 | 20 | protected override DiagnosticDescriptor CreateRuleFor(string memberTypePascalCase) 21 | { 22 | string title = $"{memberTypePascalCase} is missing nullability annotation."; 23 | string messageFormat = $"{memberTypePascalCase} '{{0}}' is missing nullability annotation."; 24 | 25 | string description = 26 | $"The type of this {memberTypePascalCase.ToCamelCase()} is a reference type or nullable type; it should be annotated with [NotNull] or [CanBeNull]."; 27 | 28 | return new DiagnosticDescriptor(DiagnosticId, title, messageFormat, Category, DiagnosticSeverity.Warning, true, description, 29 | "https://github.com/bkoelman/ResharperCodeContractNullability/blob/master/doc/reference/RNUL_MemberIsMissingNullabilityAnnotation.md"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/CodeContractNullabilityCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using JetBrains.Annotations; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeFixes; 6 | 7 | namespace CodeContractNullability 8 | { 9 | /// 10 | /// Provides code fixes for diagnostics reported by . 11 | /// 12 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CodeContractNullabilityCodeFixProvider))] 13 | [Shared] 14 | public sealed class CodeContractNullabilityCodeFixProvider : BaseCodeFixProvider 15 | { 16 | [ItemNotNull] 17 | public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CodeContractNullabilityAnalyzer.DiagnosticId); 18 | 19 | public CodeContractNullabilityCodeFixProvider() 20 | : base(false) 21 | { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Conversion/NullConversionContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CodeContractNullability.Utilities; 6 | using JetBrains.Annotations; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.Editing; 9 | using Microsoft.CodeAnalysis.Formatting; 10 | using Microsoft.CodeAnalysis.Options; 11 | using Microsoft.CodeAnalysis.Text; 12 | 13 | namespace CodeContractNullability.Conversion 14 | { 15 | internal sealed class NullConversionContext 16 | { 17 | [NotNull] 18 | private readonly SyntaxNode primaryDeclarationSyntax; 19 | 20 | [NotNull] 21 | private readonly DocumentEditor primaryEditor; 22 | 23 | [NotNull] 24 | private readonly Dictionary otherEditors = new Dictionary(); 25 | 26 | [NotNull] 27 | public FrameworkTypeCache TypeCache { get; } 28 | 29 | public CancellationToken CancellationToken { get; } 30 | 31 | private NullConversionContext([NotNull] SyntaxNode declarationSyntax, [NotNull] DocumentEditor editor, [NotNull] FrameworkTypeCache typeCache, 32 | CancellationToken cancellationToken) 33 | { 34 | primaryDeclarationSyntax = declarationSyntax; 35 | primaryEditor = editor; 36 | TypeCache = typeCache; 37 | CancellationToken = cancellationToken; 38 | } 39 | 40 | [ItemNotNull] 41 | public static async Task Create([NotNull] SyntaxNode declarationSyntax, [NotNull] Document document, 42 | [NotNull] FrameworkTypeCache typeCache, CancellationToken cancellationToken) 43 | { 44 | Guard.NotNull(declarationSyntax, nameof(declarationSyntax)); 45 | Guard.NotNull(document, nameof(document)); 46 | Guard.NotNull(typeCache, nameof(typeCache)); 47 | 48 | DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); 49 | 50 | return new NullConversionContext(declarationSyntax, editor, typeCache, cancellationToken); 51 | } 52 | 53 | [ItemNotNull] 54 | public async Task GetEditorForDeclaration([NotNull] ISymbol declarationSymbol) 55 | { 56 | Guard.NotNull(declarationSymbol, nameof(declarationSymbol)); 57 | 58 | SyntaxNode firstDeclarationSyntax = declarationSymbol.DeclaringSyntaxReferences.Select(reference => reference.GetSyntax(CancellationToken)).First(); 59 | 60 | if (firstDeclarationSyntax.SyntaxTree.FilePath == primaryDeclarationSyntax.SyntaxTree.FilePath) 61 | { 62 | return primaryEditor; 63 | } 64 | 65 | Solution solution = primaryEditor.OriginalDocument.Project.Solution; 66 | Document otherDocument = solution.GetDocument(firstDeclarationSyntax.SyntaxTree); 67 | 68 | if (!otherEditors.ContainsKey(otherDocument.Id)) 69 | { 70 | otherEditors[otherDocument.Id] = await DocumentEditor.CreateAsync(otherDocument, CancellationToken).ConfigureAwait(false); 71 | } 72 | 73 | return otherEditors[otherDocument.Id]; 74 | } 75 | 76 | [ItemNotNull] 77 | public async Task GetSolution() 78 | { 79 | Document primaryDocument = await GetDocumentFormatted(primaryEditor, CancellationToken).ConfigureAwait(false); 80 | Solution solution = primaryDocument.Project.Solution; 81 | 82 | if (otherEditors.Any()) 83 | { 84 | foreach (Task documentTask in otherEditors.Values.Select(async e => 85 | await GetDocumentFormatted(e, CancellationToken).ConfigureAwait(false))) 86 | { 87 | Document otherDocument = await documentTask.ConfigureAwait(false); 88 | SourceText text = await otherDocument.GetTextAsync(CancellationToken).ConfigureAwait(false); 89 | solution = solution.WithDocumentText(otherDocument.Id, text); 90 | } 91 | } 92 | 93 | return solution; 94 | } 95 | 96 | [ItemNotNull] 97 | private static async Task GetDocumentFormatted([NotNull] DocumentEditor editor, CancellationToken cancellationToken) 98 | { 99 | Document document = editor.GetChangedDocument(); 100 | OptionSet options = document.Project.Solution.Workspace.Options; 101 | 102 | Document formatted = await Formatter.FormatAsync(document, Formatter.Annotation, options, cancellationToken).ConfigureAwait(false); 103 | 104 | return formatted; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Conversion/ResharperNullabilitySymbolState.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.Utilities; 2 | using JetBrains.Annotations; 3 | 4 | namespace CodeContractNullability.Conversion 5 | { 6 | internal sealed class ResharperNullabilitySymbolState 7 | { 8 | [NotNull] 9 | public static readonly ResharperNullabilitySymbolState Default = 10 | new ResharperNullabilitySymbolState(ResharperNullableStatus.Unspecified, ResharperNullableStatus.Unspecified); 11 | 12 | public ResharperNullableStatus PrimaryStatus { get; } 13 | public ResharperNullableStatus ItemStatus { get; } 14 | 15 | public ResharperNullabilitySymbolState(ResharperNullableStatus primaryStatus, ResharperNullableStatus itemStatus) 16 | { 17 | PrimaryStatus = primaryStatus; 18 | ItemStatus = itemStatus; 19 | } 20 | 21 | [NotNull] 22 | public ResharperNullabilitySymbolState ApplyOverride([NotNull] ResharperNullabilitySymbolState newState) 23 | { 24 | Guard.NotNull(newState, nameof(newState)); 25 | 26 | ResharperNullableStatus primaryStatus = ApplyOverride(PrimaryStatus, newState.PrimaryStatus); 27 | ResharperNullableStatus itemStatus = ApplyOverride(ItemStatus, newState.ItemStatus); 28 | 29 | return new ResharperNullabilitySymbolState(primaryStatus, itemStatus); 30 | } 31 | 32 | private static ResharperNullableStatus ApplyOverride(ResharperNullableStatus currentStatus, ResharperNullableStatus newStatus) 33 | { 34 | return newStatus == ResharperNullableStatus.Unspecified ? currentStatus : newStatus; 35 | } 36 | 37 | [NotNull] 38 | public ResharperNullabilitySymbolState ClearPrimaryStatus() 39 | { 40 | return new ResharperNullabilitySymbolState(ResharperNullableStatus.Unspecified, ItemStatus); 41 | } 42 | 43 | [NotNull] 44 | public ResharperNullabilitySymbolState ClearItemStatus() 45 | { 46 | return new ResharperNullabilitySymbolState(PrimaryStatus, ResharperNullableStatus.Unspecified); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Conversion/ResharperNullableStatus.cs: -------------------------------------------------------------------------------- 1 | namespace CodeContractNullability.Conversion 2 | { 3 | internal enum ResharperNullableStatus 4 | { 5 | Unspecified, 6 | NotNull, 7 | CanBeNull 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Conversion/TypeDeclarationWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CodeContractNullability.Utilities; 3 | using JetBrains.Annotations; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.Formatting; 7 | 8 | namespace CodeContractNullability.Conversion 9 | { 10 | internal static class TypeDeclarationWriter 11 | { 12 | // From: internal const string Microsoft.CodeAnalysis.SyntaxNodeExtensions.IdAnnotationKind in Microsoft.CodeAnalysis.dll 13 | private const string SyntaxNodeExtensionsIdAnnotationKind = "Id"; 14 | 15 | [NotNull] 16 | public static SyntaxNode ToNullableTypeSyntax([NotNull] SyntaxNode declarationTypeSyntax, [NotNull] ResharperNullabilitySymbolState nullabilityState) 17 | { 18 | Guard.NotNull(declarationTypeSyntax, nameof(declarationTypeSyntax)); 19 | Guard.NotNull(nullabilityState, nameof(nullabilityState)); 20 | 21 | string currentTypeName = declarationTypeSyntax.ToString(); 22 | string newTypeName = currentTypeName; 23 | 24 | if (nullabilityState.PrimaryStatus == ResharperNullableStatus.CanBeNull) 25 | { 26 | newTypeName = AddQuestionMarkToTypeName(newTypeName); 27 | } 28 | 29 | if (nullabilityState.ItemStatus == ResharperNullableStatus.CanBeNull) 30 | { 31 | newTypeName = AddQuestionMarkToItemTypeName(newTypeName); 32 | } 33 | 34 | if (newTypeName != currentTypeName) 35 | { 36 | return SyntaxFactory.ParseTypeName(newTypeName).WithTriviaFrom(declarationTypeSyntax) 37 | .WithAdditionalAnnotations(declarationTypeSyntax.GetAnnotations(SyntaxNodeExtensionsIdAnnotationKind)) 38 | .WithAdditionalAnnotations(Formatter.Annotation); 39 | } 40 | 41 | return declarationTypeSyntax; 42 | } 43 | 44 | [NotNull] 45 | private static string AddQuestionMarkToTypeName([NotNull] string typeName) 46 | { 47 | return typeName.EndsWith("?", StringComparison.Ordinal) ? typeName : typeName + "?"; 48 | } 49 | 50 | [NotNull] 51 | private static string AddQuestionMarkToItemTypeName([NotNull] string typeName) 52 | { 53 | int closingAngleIndex = typeName.LastIndexOf('>'); 54 | 55 | if (closingAngleIndex != -1) 56 | { 57 | string leftPart = typeName.Substring(0, closingAngleIndex); 58 | string rightPart = typeName.Substring(closingAngleIndex); 59 | 60 | return leftPart.EndsWith("?", StringComparison.Ordinal) ? typeName : leftPart + "?" + rightPart; 61 | } 62 | 63 | int closingBracketIndex = typeName.LastIndexOf("[]", StringComparison.Ordinal); 64 | 65 | if (closingBracketIndex != -1) 66 | { 67 | string leftPart = typeName.Substring(0, closingBracketIndex); 68 | string rightPart = typeName.Substring(closingBracketIndex); 69 | 70 | return leftPart.EndsWith("?", StringComparison.Ordinal) ? typeName : leftPart + "?" + rightPart; 71 | } 72 | 73 | // Item*-attribute on type that is not an array/collection/task/lazy. Attribute has no meaning, so ignore it. 74 | return typeName; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/DisableReportOnNullableValueTypesCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using JetBrains.Annotations; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.CodeActions; 8 | using Microsoft.CodeAnalysis.CodeFixes; 9 | 10 | namespace CodeContractNullability 11 | { 12 | /// 13 | /// Provides a code fix to create a configuration file that disables reporting on nullable value types. 14 | /// 15 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DisableReportOnNullableValueTypesCodeFixProvider))] 16 | [Shared] 17 | public sealed class DisableReportOnNullableValueTypesCodeFixProvider : CodeFixProvider 18 | { 19 | [ItemNotNull] 20 | public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(BaseAnalyzer.DisableReportOnNullableValueTypesDiagnosticId); 21 | 22 | [NotNull] 23 | public override Task RegisterCodeFixesAsync(CodeFixContext context) 24 | { 25 | foreach (Diagnostic diagnostic in context.Diagnostics) 26 | { 27 | var codeAction = CodeAction.Create("Disable reporting on nullable value types in project", _ => CreateOrUpdateSolution(context)); 28 | 29 | context.RegisterCodeFix(codeAction, diagnostic); 30 | } 31 | 32 | return Task.FromResult(0); 33 | } 34 | 35 | [NotNull] 36 | [ItemNotNull] 37 | private Task CreateOrUpdateSolution(CodeFixContext context) 38 | { 39 | string content = SettingsProvider.ToFileContent(new AnalyzerSettings(true)); 40 | 41 | TextDocument existingDocument = 42 | context.Document.Project.AdditionalDocuments.FirstOrDefault(document => SettingsProvider.IsSettingsFile(document.FilePath)); 43 | 44 | Project project = context.Document.Project; 45 | 46 | if (existingDocument != null) 47 | { 48 | project = project.RemoveAdditionalDocument(existingDocument.Id); 49 | } 50 | 51 | TextDocument newDocument = project.AddAdditionalDocument(SettingsProvider.SettingsFileName, content); 52 | return Task.FromResult(newDocument.Project.Solution); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/DocumentBasedFixAllProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Immutable; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using JetBrains.Annotations; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.CodeActions; 8 | using Microsoft.CodeAnalysis.CodeFixes; 9 | 10 | namespace CodeContractNullability 11 | { 12 | /// 13 | /// Provides a base class to write a that fixes documents independently. 14 | /// 15 | /// Based on sources from project https://github.com/DotNetAnalyzers/StyleCopAnalyzers. 16 | /// 17 | /// 18 | internal abstract class DocumentBasedFixAllProvider : FixAllProvider 19 | { 20 | [NotNull] 21 | [ItemNotNull] 22 | public override Task GetFixAsync([NotNull] FixAllContext fixAllContext) 23 | { 24 | CodeAction fixAction = null; 25 | 26 | string codeActionTitle = GetCodeActionTitle(fixAllContext.CodeActionEquivalenceKey); 27 | 28 | switch (fixAllContext.Scope) 29 | { 30 | case FixAllScope.Document: 31 | { 32 | fixAction = CodeAction.Create(codeActionTitle, 33 | cancellationToken => GetDocumentFixesAsync(fixAllContext.WithCancellationToken(cancellationToken)), 34 | fixAllContext.CodeActionEquivalenceKey); 35 | 36 | break; 37 | } 38 | case FixAllScope.Project: 39 | { 40 | fixAction = CodeAction.Create(codeActionTitle, 41 | cancellationToken => GetProjectFixesAsync(fixAllContext.WithCancellationToken(cancellationToken), fixAllContext.Project), 42 | fixAllContext.CodeActionEquivalenceKey); 43 | 44 | break; 45 | } 46 | case FixAllScope.Solution: 47 | { 48 | fixAction = CodeAction.Create(codeActionTitle, 49 | cancellationToken => GetSolutionFixesAsync(fixAllContext.WithCancellationToken(cancellationToken)), 50 | fixAllContext.CodeActionEquivalenceKey); 51 | 52 | break; 53 | } 54 | } 55 | 56 | return Task.FromResult(fixAction); 57 | } 58 | 59 | [NotNull] 60 | protected abstract string GetCodeActionTitle([NotNull] string codeActionEquivalenceKey); 61 | 62 | /// 63 | /// Fixes all occurrences of a diagnostic in a specific document. 64 | /// 65 | /// 66 | /// The context for the Fix All operation. 67 | /// 68 | /// 69 | /// The document to fix. 70 | /// 71 | /// 72 | /// The diagnostics to fix in the document. 73 | /// 74 | /// 75 | /// 76 | /// The new representing the root of the fixed document. 77 | /// 78 | /// -or- 79 | /// 80 | /// , if no changes were made to the document. 81 | /// 82 | /// 83 | [NotNull] 84 | [ItemCanBeNull] 85 | protected abstract Task FixAllInDocumentAsync([NotNull] FixAllContext fixAllContext, [NotNull] Document document, 86 | [ItemNotNull] ImmutableArray diagnostics); 87 | 88 | [ItemNotNull] 89 | private async Task GetDocumentFixesAsync([NotNull] FixAllContext fixAllContext) 90 | { 91 | ImmutableDictionary> documentDiagnosticsToFix = 92 | await FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(fixAllContext).ConfigureAwait(false); 93 | 94 | if (!documentDiagnosticsToFix.TryGetValue(fixAllContext.Document, out ImmutableArray diagnostics)) 95 | { 96 | return fixAllContext.Document; 97 | } 98 | 99 | SyntaxNode newRoot = await FixAllInDocumentAsync(fixAllContext, fixAllContext.Document, diagnostics).ConfigureAwait(false); 100 | return newRoot == null ? fixAllContext.Document : fixAllContext.Document.WithSyntaxRoot(newRoot); 101 | } 102 | 103 | [ItemNotNull] 104 | private async Task GetSolutionFixesAsync([NotNull] FixAllContext fixAllContext, [ItemNotNull] ImmutableArray documents) 105 | { 106 | ImmutableDictionary> documentDiagnosticsToFix = 107 | await FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(fixAllContext).ConfigureAwait(false); 108 | 109 | Solution solution = fixAllContext.Solution; 110 | var newDocuments = new List>(documents.Length); 111 | 112 | foreach (Document document in documents) 113 | { 114 | if (!documentDiagnosticsToFix.TryGetValue(document, out ImmutableArray diagnostics)) 115 | { 116 | newDocuments.Add(document.GetSyntaxRootAsync(fixAllContext.CancellationToken)); 117 | continue; 118 | } 119 | 120 | newDocuments.Add(FixAllInDocumentAsync(fixAllContext, document, diagnostics)); 121 | } 122 | 123 | for (int i = 0; i < documents.Length; i++) 124 | { 125 | SyntaxNode newDocumentRoot = await newDocuments[i].ConfigureAwait(false); 126 | 127 | if (newDocumentRoot == null) 128 | { 129 | continue; 130 | } 131 | 132 | solution = solution.WithDocumentSyntaxRoot(documents[i].Id, newDocumentRoot); 133 | } 134 | 135 | return solution; 136 | } 137 | 138 | [NotNull] 139 | [ItemNotNull] 140 | private Task GetProjectFixesAsync([NotNull] FixAllContext fixAllContext, [NotNull] Project project) 141 | { 142 | return GetSolutionFixesAsync(fixAllContext, project.Documents.ToImmutableArray()); 143 | } 144 | 145 | [NotNull] 146 | [ItemNotNull] 147 | private Task GetSolutionFixesAsync([NotNull] FixAllContext fixAllContext) 148 | { 149 | ImmutableArray documents = fixAllContext.Solution.Projects.SelectMany(i => i.Documents).ToImmutableArray(); 150 | return GetSolutionFixesAsync(fixAllContext, documents); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/AssemblyExternalAnnotationsLoader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using CodeContractNullability.ExternalAnnotations.Storage; 3 | using CodeContractNullability.Utilities; 4 | using JetBrains.Annotations; 5 | using Microsoft.CodeAnalysis; 6 | using TestableFileSystem.Interfaces; 7 | 8 | namespace CodeContractNullability.ExternalAnnotations 9 | { 10 | /// 11 | /// Attempts to find and parse a side-by-side [AssemblyName].ExternalAnnotations.xml file that resides in the same folder as the assembly that contains 12 | /// the requested symbol. 13 | /// 14 | internal sealed class AssemblyExternalAnnotationsLoader 15 | { 16 | [NotNull] 17 | private readonly IFileSystem fileSystem; 18 | 19 | public AssemblyExternalAnnotationsLoader([NotNull] IFileSystem fileSystem) 20 | { 21 | Guard.NotNull(fileSystem, nameof(fileSystem)); 22 | this.fileSystem = fileSystem; 23 | } 24 | 25 | [CanBeNull] 26 | public string GetPathForExternalSymbolOrNull([NotNull] ISymbol symbol, [NotNull] Compilation compilation) 27 | { 28 | Guard.NotNull(symbol, nameof(symbol)); 29 | Guard.NotNull(compilation, nameof(compilation)); 30 | 31 | if (symbol.ContainingAssembly != null) 32 | { 33 | var assemblyReference = compilation.GetMetadataReference(symbol.ContainingAssembly) as PortableExecutableReference; 34 | 35 | string assemblyPath = assemblyReference?.FilePath; 36 | string folder = Path.GetDirectoryName(assemblyPath); 37 | 38 | if (folder != null) 39 | { 40 | string assemblyFileName = Path.GetFileNameWithoutExtension(assemblyPath); 41 | string annotationFilePath = Path.Combine(folder, assemblyFileName + ".ExternalAnnotations.xml"); 42 | 43 | return fileSystem.File.Exists(annotationFilePath) ? annotationFilePath : null; 44 | } 45 | } 46 | 47 | return null; 48 | } 49 | 50 | [NotNull] 51 | public ExternalAnnotationsMap ParseFile([NotNull] string externalAnnotationsPath) 52 | { 53 | Guard.NotNull(externalAnnotationsPath, nameof(externalAnnotationsPath)); 54 | 55 | using (StreamReader reader = fileSystem.File.OpenText(externalAnnotationsPath)) 56 | { 57 | var map = new ExternalAnnotationsMap(); 58 | 59 | var parser = new ExternalAnnotationDocumentParser(); 60 | parser.ProcessDocument(reader, map); 61 | 62 | return map; 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/CachingExternalAnnotationsResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using CodeContractNullability.ExternalAnnotations.Storage; 5 | using CodeContractNullability.Utilities; 6 | using JetBrains.Annotations; 7 | using Microsoft.CodeAnalysis; 8 | using TestableFileSystem.Interfaces; 9 | 10 | namespace CodeContractNullability.ExternalAnnotations 11 | { 12 | /// 13 | /// Performs one-time load of files from built-in Resharper External Annotation folders, along with a cached set of per-assembly External Annotations 14 | /// (loaded from [AssemblyName].ExternalAnnotations.xml in assembly folder). The annotation files from this last set typically come from NuGet packages 15 | /// or assembly references. From that set, each per-assembly file is monitored for filesystem changes and flushed accordingly. 16 | /// 17 | public sealed class CachingExternalAnnotationsResolver : IExternalAnnotationsResolver 18 | { 19 | [NotNull] 20 | private readonly AssemblyExternalAnnotationsLoader loader; 21 | 22 | [NotNull] 23 | private readonly IFileSystem fileSystem; 24 | 25 | [NotNull] 26 | private readonly ICacheProvider cacheProvider; 27 | 28 | [NotNull] 29 | private readonly ConcurrentDictionary assemblyCache = 30 | new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); 31 | 32 | public CachingExternalAnnotationsResolver([NotNull] IFileSystem fileSystem, [NotNull] ICacheProvider cacheProvider) 33 | { 34 | Guard.NotNull(fileSystem, nameof(fileSystem)); 35 | Guard.NotNull(cacheProvider, nameof(cacheProvider)); 36 | 37 | this.fileSystem = fileSystem; 38 | this.cacheProvider = cacheProvider; 39 | 40 | loader = new AssemblyExternalAnnotationsLoader(fileSystem); 41 | } 42 | 43 | public void EnsureScanned() 44 | { 45 | cacheProvider.GetValue(); 46 | } 47 | 48 | public bool HasAnnotationForSymbol(ISymbol symbol, bool appliesToItem, Compilation compilation) 49 | { 50 | Guard.NotNull(symbol, nameof(symbol)); 51 | Guard.NotNull(compilation, nameof(compilation)); 52 | 53 | return HasAnnotationInSharedCache(symbol, appliesToItem) || HasAnnotationInSideBySideFile(symbol, appliesToItem, compilation); 54 | } 55 | 56 | private bool HasAnnotationInSharedCache([NotNull] ISymbol symbol, bool appliesToItem) 57 | { 58 | return cacheProvider.GetValue().Contains(symbol, appliesToItem); 59 | } 60 | 61 | private bool HasAnnotationInSideBySideFile([NotNull] ISymbol symbol, bool appliesToItem, [NotNull] Compilation compilation) 62 | { 63 | string path = loader.GetPathForExternalSymbolOrNull(symbol, compilation); 64 | 65 | if (path != null) 66 | { 67 | AssemblyCacheEntry entry = assemblyCache.GetOrAdd(path, CreateAssemblyCacheEntry); 68 | return entry.Map.Contains(symbol, appliesToItem); 69 | } 70 | 71 | return false; 72 | } 73 | 74 | public bool IsFileInSideBySideCache([NotNull] string path) 75 | { 76 | Guard.NotNull(path, nameof(path)); 77 | 78 | return assemblyCache.ContainsKey(path); 79 | } 80 | 81 | [NotNull] 82 | private AssemblyCacheEntry CreateAssemblyCacheEntry([NotNull] string path) 83 | { 84 | ExternalAnnotationsMap assemblyAnnotationsMap = loader.ParseFile(path); 85 | IFileSystemWatcher fileWatcher = CreateAssemblyAnnotationsFileWatcher(path); 86 | 87 | return new AssemblyCacheEntry(assemblyAnnotationsMap, fileWatcher); 88 | } 89 | 90 | [NotNull] 91 | private IFileSystemWatcher CreateAssemblyAnnotationsFileWatcher([NotNull] string path) 92 | { 93 | string directoryName = Path.GetDirectoryName(path); 94 | 95 | if (directoryName == null) 96 | { 97 | throw new InvalidOperationException($"Internal error: failed to extract directory from path '{path}'."); 98 | } 99 | 100 | string filter = Path.GetFileName(path); 101 | IFileSystemWatcher assemblyAnnotationsFileWatcher = fileSystem.ConstructFileSystemWatcher(directoryName, filter); 102 | 103 | assemblyAnnotationsFileWatcher.Changed += WatcherOnChanged; 104 | assemblyAnnotationsFileWatcher.Created += WatcherOnChanged; 105 | assemblyAnnotationsFileWatcher.Deleted += WatcherOnChanged; 106 | assemblyAnnotationsFileWatcher.Renamed += (s, e) => WatcherOnChanged(s, OldValuesFrom(e)); 107 | 108 | assemblyAnnotationsFileWatcher.EnableRaisingEvents = true; 109 | return assemblyAnnotationsFileWatcher; 110 | } 111 | 112 | private void WatcherOnChanged([NotNull] object sender, [NotNull] FileSystemEventArgs e) 113 | { 114 | if (assemblyCache.TryRemove(e.FullPath, out AssemblyCacheEntry existing)) 115 | { 116 | existing.Watcher.EnableRaisingEvents = false; 117 | existing.Watcher.Dispose(); 118 | } 119 | } 120 | 121 | [NotNull] 122 | private static FileSystemEventArgs OldValuesFrom([NotNull] RenamedEventArgs e) 123 | { 124 | string directoryName = Path.GetDirectoryName(e.OldFullPath); 125 | 126 | if (directoryName == null) 127 | { 128 | throw new InvalidOperationException($"Internal error: failed to extract directory from path '{e.OldFullPath}'."); 129 | } 130 | 131 | return new FileSystemEventArgs(e.ChangeType, directoryName, e.OldName); 132 | } 133 | 134 | private sealed class AssemblyCacheEntry 135 | { 136 | [NotNull] 137 | public ExternalAnnotationsMap Map { get; } 138 | 139 | [NotNull] 140 | public IFileSystemWatcher Watcher { get; } 141 | 142 | public AssemblyCacheEntry([NotNull] ExternalAnnotationsMap map, [NotNull] IFileSystemWatcher watcher) 143 | { 144 | Guard.NotNull(map, nameof(map)); 145 | Guard.NotNull(watcher, nameof(watcher)); 146 | 147 | Map = map; 148 | Watcher = watcher; 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/ExternalAnnotationDocumentParser.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Xml.Linq; 3 | using CodeContractNullability.ExternalAnnotations.Storage; 4 | using CodeContractNullability.Utilities; 5 | using JetBrains.Annotations; 6 | 7 | namespace CodeContractNullability.ExternalAnnotations 8 | { 9 | /// 10 | /// Parses the contents of a single external annotations xml file. 11 | /// 12 | public sealed class ExternalAnnotationDocumentParser 13 | { 14 | public void ProcessDocument([NotNull] TextReader reader, [NotNull] ExternalAnnotationsMap result) 15 | { 16 | Guard.NotNull(reader, nameof(reader)); 17 | Guard.NotNull(result, nameof(result)); 18 | 19 | XElement assemblyElement = XDocument.Load(reader).Element("assembly"); 20 | 21 | if (assemblyElement != null) 22 | { 23 | // Known limitation: we are not entirely correct here, by ignoring assembly info. 24 | // You'll run into this, for example, with the next block of code: 25 | // 26 | // public class MyEnumerator : IEnumerator 27 | // { 28 | // public bool MoveNext() { throw new System.NotImplementedException(); } 29 | // 30 | // public void Reset() { } 31 | // 32 | // [CanBeNull] 33 | // public object Current { get; } 34 | // } 35 | // 36 | // When you set project properties to target .NET Framework v4.5, Resharper is fine with 37 | // the [CanBeNull]. But if you switch to target .NET Framework v2, then Resharper grays 38 | // out the [CanBeNull], with hover message "Base declaration has the same annotation". 39 | // This is because the external annotation file "2.0.0.0.Interfaces.Nullness.Gen.xml" 40 | // contains the following snapshot: 41 | // 42 | // 43 | // 44 | // 45 | // 46 | // 47 | // 48 | // 49 | // But when targeting the .NET Framework v4.5, mscorlib v4.0.0.0 is used, so this snapshot 50 | // does not apply. To support this, we need to add assembly info to our data structure 51 | // for each symbol. That makes the data set grow a lot, taking longer to load/save. 52 | 53 | foreach (XElement memberElement in assemblyElement.Elements("member")) 54 | { 55 | ProcessMember(memberElement, result); 56 | } 57 | } 58 | } 59 | 60 | private static void ProcessMember([NotNull] XElement memberElement, [NotNull] ExternalAnnotationsMap result) 61 | { 62 | string memberType = "?"; 63 | string memberName = memberElement.Attribute("name")?.Value; 64 | 65 | if (memberName != null) 66 | { 67 | if (memberName.Length > 2 && memberName[1] == ':') 68 | { 69 | memberType = memberName[0].ToString(); 70 | memberName = memberName.Substring(2); 71 | } 72 | 73 | MemberNullabilityInfo memberInfo = result.ContainsKey(memberName) ? result[memberName] : new MemberNullabilityInfo(memberType); 74 | 75 | foreach (XElement childElement in memberElement.Elements()) 76 | { 77 | ProcessChildOfMember(childElement, memberInfo); 78 | } 79 | 80 | result[memberName] = memberInfo; 81 | } 82 | } 83 | 84 | private static void ProcessChildOfMember([NotNull] XElement childElement, [NotNull] MemberNullabilityInfo memberInfo) 85 | { 86 | if (childElement.Name == "parameter") 87 | { 88 | string parameterName = childElement.Attribute("name")?.Value; 89 | 90 | if (parameterName != null) 91 | { 92 | foreach (XElement attributeElement in childElement.Elements("attribute")) 93 | { 94 | if (ElementHasNullabilityDefinition(attributeElement)) 95 | { 96 | memberInfo.ParametersNullability[parameterName] = true; 97 | } 98 | } 99 | } 100 | } 101 | else if (childElement.Name == "attribute") 102 | { 103 | if (ElementHasNullabilityDefinition(childElement)) 104 | { 105 | memberInfo.HasNullabilityDefined = true; 106 | } 107 | } 108 | } 109 | 110 | private static bool ElementHasNullabilityDefinition([NotNull] XElement element) 111 | { 112 | string attributeName = element.Attribute("ctor")?.Value; 113 | 114 | return attributeName == "M:JetBrains.Annotations.NotNullAttribute.#ctor" || attributeName == "M:JetBrains.Annotations.CanBeNullAttribute.#ctor"; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/GlobalAnnotationCacheProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using CodeContractNullability.ExternalAnnotations.Storage; 4 | using CodeContractNullability.Utilities; 5 | using JetBrains.Annotations; 6 | using TestableFileSystem.Wrappers; 7 | 8 | namespace CodeContractNullability.ExternalAnnotations 9 | { 10 | public sealed class GlobalAnnotationCacheProvider : ICacheProvider 11 | { 12 | [NotNull] 13 | [ItemNotNull] 14 | private static readonly Lazy GlobalCache; 15 | 16 | static GlobalAnnotationCacheProvider() 17 | { 18 | GlobalCache = new Lazy(new FolderExternalAnnotationsLoader(FileSystemWrapper.Default).Create, 19 | LazyThreadSafetyMode.ExecutionAndPublication); 20 | } 21 | 22 | public ExternalAnnotationsMap GetValue() 23 | { 24 | return GlobalCache.Value; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/IExternalAnnotationsResolver.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace CodeContractNullability.ExternalAnnotations 5 | { 6 | /// 7 | /// Determines whether an external nullability annotation exists for a symbol. 8 | /// 9 | public interface IExternalAnnotationsResolver 10 | { 11 | void EnsureScanned(); 12 | 13 | bool HasAnnotationForSymbol([NotNull] ISymbol symbol, bool appliesToItem, [NotNull] Compilation compilation); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/LocalAnnotationCacheProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using CodeContractNullability.ExternalAnnotations.Storage; 4 | using CodeContractNullability.Utilities; 5 | using JetBrains.Annotations; 6 | using TestableFileSystem.Interfaces; 7 | 8 | namespace CodeContractNullability.ExternalAnnotations 9 | { 10 | public sealed class LocalAnnotationCacheProvider : ICacheProvider 11 | { 12 | [NotNull] 13 | [ItemNotNull] 14 | private readonly Lazy localCache; 15 | 16 | public LocalAnnotationCacheProvider([NotNull] IFileSystem fileSystem) 17 | { 18 | Guard.NotNull(fileSystem, nameof(fileSystem)); 19 | 20 | localCache = new Lazy(new FolderExternalAnnotationsLoader(fileSystem).Create, LazyThreadSafetyMode.ExecutionAndPublication); 21 | } 22 | 23 | public ExternalAnnotationsMap GetValue() 24 | { 25 | return localCache.Value; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/MissingExternalAnnotationsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace CodeContractNullability.ExternalAnnotations 5 | { 6 | [Serializable] 7 | public sealed class MissingExternalAnnotationsException : Exception 8 | { 9 | public MissingExternalAnnotationsException([NotNull] string message, [CanBeNull] Exception innerException) 10 | : base(message, innerException) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/SimpleExternalAnnotationsResolver.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.ExternalAnnotations.Storage; 2 | using CodeContractNullability.Utilities; 3 | using JetBrains.Annotations; 4 | using Microsoft.CodeAnalysis; 5 | 6 | namespace CodeContractNullability.ExternalAnnotations 7 | { 8 | /// 9 | /// Provides a simple wrapper for an existing . 10 | /// 11 | public sealed class SimpleExternalAnnotationsResolver : IExternalAnnotationsResolver 12 | { 13 | [NotNull] 14 | private readonly ExternalAnnotationsMap source; 15 | 16 | public SimpleExternalAnnotationsResolver([NotNull] ExternalAnnotationsMap source) 17 | { 18 | Guard.NotNull(source, nameof(source)); 19 | 20 | this.source = source; 21 | } 22 | 23 | public void EnsureScanned() 24 | { 25 | } 26 | 27 | public bool HasAnnotationForSymbol(ISymbol symbol, bool appliesToItem, Compilation compilation) 28 | { 29 | Guard.NotNull(symbol, nameof(symbol)); 30 | 31 | return source.Contains(symbol, appliesToItem); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/Storage/ExternalAnnotationsCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using CodeContractNullability.Utilities; 4 | using JetBrains.Annotations; 5 | 6 | namespace CodeContractNullability.ExternalAnnotations.Storage 7 | { 8 | /// 9 | /// Represents the external annotations cache file, stored in compact form. 10 | /// 11 | [DataContract(Namespace = CacheNamespace)] 12 | [Serializable] 13 | public sealed class ExternalAnnotationsCache 14 | { 15 | internal const string CacheNamespace = "CodeContractNullability"; 16 | 17 | [DataMember(Name = "lastWriteTimeUtc")] 18 | public DateTime LastWriteTimeUtc { get; private set; } 19 | 20 | [DataMember(Name = "annotations")] 21 | [NotNull] 22 | public ExternalAnnotationsMap ExternalAnnotations { get; private set; } 23 | 24 | public ExternalAnnotationsCache() 25 | { 26 | ExternalAnnotations = new ExternalAnnotationsMap(); 27 | } 28 | 29 | public ExternalAnnotationsCache(DateTime lastWriteTimeUtc, [NotNull] ExternalAnnotationsMap externalAnnotations) 30 | { 31 | Guard.NotNull(externalAnnotations, nameof(externalAnnotations)); 32 | 33 | LastWriteTimeUtc = lastWriteTimeUtc; 34 | ExternalAnnotations = externalAnnotations; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/Storage/ExternalAnnotationsMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using CodeContractNullability.Utilities; 5 | using JetBrains.Annotations; 6 | using Microsoft.CodeAnalysis; 7 | 8 | namespace CodeContractNullability.ExternalAnnotations.Storage 9 | { 10 | /// 11 | /// Data storage for external annotations. 12 | /// 13 | [CollectionDataContract(Name = "annotations", ItemName = "e", KeyName = "k", ValueName = "v", Namespace = ExternalAnnotationsCache.CacheNamespace)] 14 | [Serializable] 15 | public sealed class ExternalAnnotationsMap : Dictionary 16 | { 17 | public ExternalAnnotationsMap() 18 | { 19 | } 20 | 21 | private ExternalAnnotationsMap([NotNull] SerializationInfo info, StreamingContext context) 22 | : base(info, context) 23 | { 24 | } 25 | 26 | internal bool Contains([NotNull] ISymbol symbol, bool appliesToItem) 27 | { 28 | Guard.NotNull(symbol, nameof(symbol)); 29 | 30 | if (appliesToItem) 31 | { 32 | // Note: At the time of writing (August 2015), the set of Resharper external annotations does not 33 | // include ItemNotNull / ItemCanBeNull elements. But we likely need to add support for them in the future. 34 | return false; 35 | } 36 | 37 | symbol = symbol.OriginalDefinition ?? symbol; 38 | 39 | if (symbol is IParameterSymbol) 40 | { 41 | string methodId = symbol.ContainingSymbol.GetDocumentationCommentId(); 42 | MemberNullabilityInfo memberInfo = TryGetMemberById(methodId); 43 | 44 | return memberInfo != null && memberInfo.ParametersNullability.ContainsKey(symbol.Name) && memberInfo.ParametersNullability[symbol.Name]; 45 | } 46 | else 47 | { 48 | string id = symbol.GetDocumentationCommentId(); 49 | MemberNullabilityInfo memberInfo = TryGetMemberById(id); 50 | return memberInfo != null && memberInfo.HasNullabilityDefined; 51 | } 52 | } 53 | 54 | [CanBeNull] 55 | private MemberNullabilityInfo TryGetMemberById([CanBeNull] string id) 56 | { 57 | if (!string.IsNullOrEmpty(id) && id[1] == ':') 58 | { 59 | // N = namespace, M = method, F = field, E = event, P = property, T = type 60 | string type = id.Substring(0, 1); 61 | string key = id.Substring(2); 62 | 63 | if (ContainsKey(key)) 64 | { 65 | MemberNullabilityInfo memberInfo = this[key]; 66 | 67 | if (memberInfo.Type == type) 68 | { 69 | return memberInfo; 70 | } 71 | } 72 | } 73 | 74 | return null; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/Storage/MemberNullabilityInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | using CodeContractNullability.Utilities; 3 | using JetBrains.Annotations; 4 | 5 | namespace CodeContractNullability.ExternalAnnotations.Storage 6 | { 7 | /// 8 | /// Data storage for external annotations. 9 | /// 10 | [DataContract(Name = "i", Namespace = ExternalAnnotationsCache.CacheNamespace)] 11 | public sealed class MemberNullabilityInfo 12 | { 13 | [DataMember(Name = "t")] 14 | [NotNull] 15 | public string Type { get; private set; } 16 | 17 | [DataMember(Name = "n")] 18 | public bool HasNullabilityDefined { get; set; } 19 | 20 | [DataMember(Name = "p")] 21 | [NotNull] 22 | public ParameterNullabilityMap ParametersNullability { get; private set; } 23 | 24 | // ReSharper disable once NotNullMemberIsNotInitialized 25 | // Reason: This ctor is only needed for MsgPack serializer. 26 | public MemberNullabilityInfo() 27 | { 28 | } 29 | 30 | public MemberNullabilityInfo([NotNull] string type) 31 | { 32 | Guard.NotNull(type, nameof(type)); 33 | 34 | Type = type; 35 | ParametersNullability = new ParameterNullabilityMap(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ExternalAnnotations/Storage/ParameterNullabilityMap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using JetBrains.Annotations; 5 | 6 | namespace CodeContractNullability.ExternalAnnotations.Storage 7 | { 8 | /// 9 | /// Data storage for external annotations. 10 | /// 11 | [CollectionDataContract(Name = "p", ItemName = "e", KeyName = "k", ValueName = "v", Namespace = ExternalAnnotationsCache.CacheNamespace)] 12 | [Serializable] 13 | public sealed class ParameterNullabilityMap : Dictionary 14 | { 15 | public ParameterNullabilityMap() 16 | { 17 | } 18 | 19 | private ParameterNullabilityMap([NotNull] SerializationInfo info, StreamingContext context) 20 | : base(info, context) 21 | { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/FixAllContextHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Immutable; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using JetBrains.Annotations; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CodeFixes; 9 | 10 | namespace CodeContractNullability 11 | { 12 | /// 13 | /// Based on sources from project https://github.com/DotNetAnalyzers/StyleCopAnalyzers. 14 | /// 15 | internal static class FixAllContextHelper 16 | { 17 | [ItemNotNull] 18 | public static async Task>> GetDocumentDiagnosticsToFixAsync( 19 | [NotNull] FixAllContext fixAllContext) 20 | { 21 | var allDiagnostics = ImmutableArray.Empty; 22 | var projectsToFix = ImmutableArray.Empty; 23 | 24 | Document document = fixAllContext.Document; 25 | Project project = fixAllContext.Project; 26 | 27 | switch (fixAllContext.Scope) 28 | { 29 | case FixAllScope.Document: 30 | { 31 | if (document != null) 32 | { 33 | ImmutableArray documentDiagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false); 34 | return ImmutableDictionary>.Empty.SetItem(document, documentDiagnostics); 35 | } 36 | 37 | break; 38 | } 39 | case FixAllScope.Project: 40 | { 41 | projectsToFix = ImmutableArray.Create(project); 42 | allDiagnostics = await GetAllDiagnosticsAsync(fixAllContext, project).ConfigureAwait(false); 43 | 44 | break; 45 | } 46 | case FixAllScope.Solution: 47 | { 48 | projectsToFix = project.Solution.Projects.Where(p => p.Language == project.Language).ToImmutableArray(); 49 | 50 | var diagnostics = new ConcurrentDictionary>(); 51 | var tasks = new Task[projectsToFix.Length]; 52 | 53 | for (int i = 0; i < projectsToFix.Length; i++) 54 | { 55 | fixAllContext.CancellationToken.ThrowIfCancellationRequested(); 56 | 57 | Project projectToFix = projectsToFix[i]; 58 | 59 | tasks[i] = Task.Run(async () => 60 | { 61 | ImmutableArray projectDiagnostics = await GetAllDiagnosticsAsync(fixAllContext, projectToFix).ConfigureAwait(false); 62 | diagnostics.TryAdd(projectToFix.Id, projectDiagnostics); 63 | }, fixAllContext.CancellationToken); 64 | } 65 | 66 | await Task.WhenAll(tasks).ConfigureAwait(false); 67 | 68 | allDiagnostics = allDiagnostics.AddRange(diagnostics.SelectMany(i => i.Value.Where(x => fixAllContext.DiagnosticIds.Contains(x.Id)))); 69 | 70 | break; 71 | } 72 | } 73 | 74 | if (allDiagnostics.IsEmpty) 75 | { 76 | return ImmutableDictionary>.Empty; 77 | } 78 | 79 | return await GetDocumentDiagnosticsToFixAsync(allDiagnostics, projectsToFix, fixAllContext.CancellationToken).ConfigureAwait(false); 80 | } 81 | 82 | /// 83 | /// Gets all instances within a specific which are relevant to a . 84 | /// 85 | /// 86 | /// The context for the Fix All operation. 87 | /// 88 | /// 89 | /// The project. 90 | /// 91 | /// 92 | /// A representing the asynchronous operation. When the task completes successfully, the 93 | /// will contain the requested diagnostics. 94 | /// 95 | private static async Task> GetAllDiagnosticsAsync([NotNull] FixAllContext fixAllContext, [NotNull] Project project) 96 | { 97 | return await fixAllContext.GetAllDiagnosticsAsync(project).ConfigureAwait(false); 98 | } 99 | 100 | [ItemNotNull] 101 | private static async Task>> GetDocumentDiagnosticsToFixAsync( 102 | [ItemNotNull] ImmutableArray diagnostics, [ItemNotNull] ImmutableArray projects, CancellationToken cancellationToken) 103 | { 104 | ImmutableDictionary treeToDocumentMap = await GetTreeToDocumentMapAsync(projects, cancellationToken).ConfigureAwait(false); 105 | 106 | ImmutableDictionary>.Builder builder = 107 | ImmutableDictionary.CreateBuilder>(); 108 | 109 | foreach (IGrouping documentAndDiagnostics in diagnostics.GroupBy(d => GetReportedDocument(d, treeToDocumentMap))) 110 | { 111 | cancellationToken.ThrowIfCancellationRequested(); 112 | 113 | Document document = documentAndDiagnostics.Key; 114 | ImmutableArray diagnosticsForDocument = documentAndDiagnostics.ToImmutableArray(); 115 | 116 | builder.Add(document, diagnosticsForDocument); 117 | } 118 | 119 | return builder.ToImmutable(); 120 | } 121 | 122 | [ItemNotNull] 123 | private static async Task> GetTreeToDocumentMapAsync([ItemNotNull] ImmutableArray projects, 124 | CancellationToken cancellationToken) 125 | { 126 | ImmutableDictionary.Builder builder = ImmutableDictionary.CreateBuilder(); 127 | 128 | foreach (Project project in projects) 129 | { 130 | cancellationToken.ThrowIfCancellationRequested(); 131 | 132 | foreach (Document document in project.Documents) 133 | { 134 | cancellationToken.ThrowIfCancellationRequested(); 135 | 136 | SyntaxTree tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); 137 | builder.Add(tree, document); 138 | } 139 | } 140 | 141 | return builder.ToImmutable(); 142 | } 143 | 144 | [CanBeNull] 145 | private static Document GetReportedDocument([NotNull] Diagnostic diagnostic, [NotNull] ImmutableDictionary treeToDocumentsMap) 146 | { 147 | SyntaxTree tree = diagnostic.Location.SourceTree; 148 | return tree != null && treeToDocumentsMap.TryGetValue(tree, out Document document) ? document : null; 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/FrameworkTypeCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Runtime.CompilerServices; 7 | using System.Threading.Tasks; 8 | using CodeContractNullability.Utilities; 9 | using JetBrains.Annotations; 10 | using Microsoft.CodeAnalysis; 11 | 12 | namespace CodeContractNullability 13 | { 14 | internal sealed class FrameworkTypeCache 15 | { 16 | [NotNull] 17 | private readonly Compilation compilation; 18 | 19 | [NotNull] 20 | private readonly ConcurrentDictionary typeMap = new ConcurrentDictionary(); 21 | 22 | [CanBeNull] 23 | public INamedTypeSymbol EnumerableOfT => GetCached(typeof(IEnumerable<>)); 24 | 25 | [CanBeNull] 26 | public INamedTypeSymbol Enumerable => GetCached(typeof(IEnumerable)); 27 | 28 | [CanBeNull] 29 | public INamedTypeSymbol String => GetCached(typeof(string)); 30 | 31 | [CanBeNull] 32 | public INamedTypeSymbol Object => GetCached(typeof(object)); 33 | 34 | [CanBeNull] 35 | public INamedTypeSymbol LazyOfT => GetCached(typeof(Lazy<>)); 36 | 37 | [CanBeNull] 38 | public INamedTypeSymbol TaskOfT => GetCached(typeof(Task<>)); 39 | 40 | [CanBeNull] 41 | public INamedTypeSymbol ValueTaskOfT => GetCached("System.Threading.Tasks.ValueTask`1"); 42 | 43 | [CanBeNull] 44 | public INamedTypeSymbol CompilerGeneratedAttribute => GetCached(typeof(CompilerGeneratedAttribute)); 45 | 46 | [CanBeNull] 47 | public INamedTypeSymbol DebuggerNonUserCodeAttribute => GetCached(typeof(DebuggerNonUserCodeAttribute)); 48 | 49 | [CanBeNull] 50 | public INamedTypeSymbol ConditionalAttribute => GetCached(typeof(ConditionalAttribute)); 51 | 52 | public FrameworkTypeCache([NotNull] Compilation compilation) 53 | { 54 | Guard.NotNull(compilation, nameof(compilation)); 55 | this.compilation = compilation; 56 | } 57 | 58 | [CanBeNull] 59 | private INamedTypeSymbol GetCached([NotNull] Type type) 60 | { 61 | string typeName = type.FullName; 62 | 63 | if (typeName == null) 64 | { 65 | throw new InvalidOperationException($"Internal error: failed to resolve full name of type '{type}'."); 66 | } 67 | 68 | return GetCached(typeName); 69 | } 70 | 71 | [CanBeNull] 72 | private INamedTypeSymbol GetCached([NotNull] string typeName) 73 | { 74 | return typeMap.GetOrAdd(typeName, _ => compilation.GetTypeByMetadataName(typeName)); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/GeneratedCodeDocumentCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | using System.Threading; 8 | using CodeContractNullability.Utilities; 9 | using JetBrains.Annotations; 10 | using Microsoft.CodeAnalysis; 11 | using Microsoft.CodeAnalysis.CSharp; 12 | using Microsoft.CodeAnalysis.CSharp.Syntax; 13 | 14 | namespace CodeContractNullability 15 | { 16 | /// 17 | /// Within a compilation, caches per-file whether it consists of generated code. 18 | /// 19 | /// 20 | /// Inspired by StyleCop source code. Path: DotNetAnalyzers/StyleCopAnalyzers/StyleCop.Analyzers/StyleCop.Analyzers/GeneratedCodeAnalysisExtensions.cs 21 | /// 23 | /// 24 | internal sealed class GeneratedCodeDocumentCache 25 | { 26 | [NotNull] 27 | private readonly FileCommentScanner scanner = new FileCommentScanner(); 28 | 29 | [NotNull] 30 | private readonly ConcurrentDictionary fileResultCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); 31 | 32 | public bool IsInGeneratedCodeDocument([NotNull] ISymbol symbol, CancellationToken cancellationToken) 33 | { 34 | Guard.NotNull(symbol, nameof(symbol)); 35 | 36 | // Return false when not all locations have generated comment; for example, partial classes. 37 | 38 | IEnumerable locations = symbol.Locations.Where(location => location.IsInSource); 39 | var trees = new HashSet(locations.Select(location => location.SourceTree)); 40 | 41 | return trees.Any() && trees.All(tree => IsGeneratedCodeDocument(tree, cancellationToken)); 42 | } 43 | 44 | private bool IsGeneratedCodeDocument([NotNull] SyntaxTree syntaxTree, CancellationToken cancellationToken) 45 | { 46 | Guard.NotNull(syntaxTree, nameof(syntaxTree)); 47 | 48 | if (scanner.IsFileNameGenerated(syntaxTree.FilePath)) 49 | { 50 | return true; 51 | } 52 | 53 | string key = syntaxTree.FilePath; 54 | 55 | if (!fileResultCache.ContainsKey(key)) 56 | { 57 | fileResultCache[key] = scanner.HasAutoGeneratedCommentHeader(syntaxTree, cancellationToken); 58 | } 59 | 60 | return fileResultCache[key]; 61 | } 62 | 63 | /// 64 | /// Determines whether a source code file is auto-generated, by looking at its top-level comments. 65 | /// 66 | private sealed class FileCommentScanner 67 | { 68 | [NotNull] 69 | private static readonly Regex FileNameRegex = 70 | new Regex(@"(^TemporaryGeneratedFile_.*|^assemblyinfo|^assemblyattributes|\.(g\.i|g|designer|generated|assemblyattributes))\.(cs|vb)$", 71 | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture); 72 | 73 | public bool IsFileNameGenerated([CanBeNull] string filePath) 74 | { 75 | if (filePath != null) 76 | { 77 | string fileName = Path.GetFileName(filePath); 78 | return FileNameRegex.IsMatch(fileName); 79 | } 80 | 81 | return false; 82 | } 83 | 84 | public bool HasAutoGeneratedCommentHeader([NotNull] SyntaxTree tree, CancellationToken cancellationToken) 85 | { 86 | Guard.NotNull(tree, nameof(tree)); 87 | 88 | SyntaxNode root = tree.GetRoot(cancellationToken); 89 | 90 | if (root != null) 91 | { 92 | SyntaxTriviaList? trivia = GetTopOfFileTriviaOrNull(root); 93 | 94 | if (trivia != null) 95 | { 96 | return trivia.Value.Any(t => IsCodeComment(t) && t.ToString().Contains(" 8 | /// Provides access to the (Item)NotNullAttribute and (Item)CanBeNullAttribute symbols in a compilation. 9 | /// 10 | public interface INullabilityAttributeProvider 11 | { 12 | [CanBeNull] 13 | NullabilityAttributeSymbols GetSymbols([NotNull] Compilation compilation, CancellationToken cancellationToken = default); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/NullabilityAttributes/NullabilityAttributeCache.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using CodeContractNullability.Utilities; 3 | using JetBrains.Annotations; 4 | using Microsoft.CodeAnalysis; 5 | 6 | namespace CodeContractNullability.NullabilityAttributes 7 | { 8 | /// 9 | /// Provides cached access to the (Item)NotNullAttribute and (Item)CanBeNullAttribute symbols in a compilation. The cache is used for hinting, resulting 10 | /// in potential faster lookups over (similar) compilations and solutions. 11 | /// 12 | internal sealed class CachingNullabilityAttributeProvider : INullabilityAttributeProvider 13 | { 14 | [NotNull] 15 | private static readonly FreshReference LastSeenNames = new FreshReference(null); 16 | 17 | [NotNull] 18 | private readonly FreshReference names = new FreshReference(null); 19 | 20 | [NotNull] 21 | private readonly FreshReference symbols = new FreshReference(null); 22 | 23 | public CachingNullabilityAttributeProvider([CanBeNull] NullabilityAttributeMetadataNames names = null) 24 | { 25 | this.names.Value = names; 26 | } 27 | 28 | public NullabilityAttributeSymbols GetSymbols(Compilation compilation, CancellationToken cancellationToken = default) 29 | { 30 | Guard.NotNull(compilation, nameof(compilation)); 31 | 32 | NullabilityAttributeSymbols symbolsSnapshot = symbols.Value; 33 | NullabilityAttributeMetadataNames previousNames = symbolsSnapshot?.GetMetadataNames() ?? names.Value ?? LastSeenNames.Value; 34 | symbolsSnapshot = previousNames?.GetSymbolsOrNull(compilation); 35 | 36 | if (symbolsSnapshot == null) 37 | { 38 | var provider = new SimpleNullabilityAttributeProvider(); 39 | symbolsSnapshot = provider.GetSymbols(compilation, cancellationToken); 40 | } 41 | 42 | if (symbolsSnapshot != null) 43 | { 44 | names.Value = symbolsSnapshot.GetMetadataNames(); 45 | LastSeenNames.Value = names.Value; 46 | } 47 | 48 | symbols.Value = symbolsSnapshot; 49 | return symbolsSnapshot; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/NullabilityAttributes/NullabilityAttributeMetadataNames.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using CodeContractNullability.Utilities; 3 | using JetBrains.Annotations; 4 | using Microsoft.CodeAnalysis; 5 | 6 | namespace CodeContractNullability.NullabilityAttributes 7 | { 8 | /// 9 | /// Holds information about where the nullability attributes are located. 10 | /// 11 | public sealed class NullabilityAttributeMetadataNames 12 | { 13 | private const string KeyNotNull = "NotNull"; 14 | private const string KeyCanBeNull = "CanBeNull"; 15 | private const string KeyItemNotNull = "ItemNotNull"; 16 | private const string KeyItemCanBeNull = "ItemCanBeNull"; 17 | 18 | [NotNull] 19 | private string NotNull { get; } 20 | 21 | [NotNull] 22 | private string CanBeNull { get; } 23 | 24 | [NotNull] 25 | private string ItemNotNull { get; } 26 | 27 | [NotNull] 28 | private string ItemCanBeNull { get; } 29 | 30 | public NullabilityAttributeMetadataNames([NotNull] string notNull, [NotNull] string canBeNull, [NotNull] string itemNotNull, 31 | [NotNull] string itemCanBeNull) 32 | { 33 | Guard.NotNull(notNull, nameof(notNull)); 34 | Guard.NotNull(canBeNull, nameof(canBeNull)); 35 | Guard.NotNull(itemNotNull, nameof(itemNotNull)); 36 | Guard.NotNull(itemCanBeNull, nameof(itemCanBeNull)); 37 | 38 | NotNull = notNull; 39 | CanBeNull = canBeNull; 40 | ItemNotNull = itemNotNull; 41 | ItemCanBeNull = itemCanBeNull; 42 | } 43 | 44 | [CanBeNull] 45 | public NullabilityAttributeSymbols GetSymbolsOrNull([NotNull] Compilation compilation) 46 | { 47 | Guard.NotNull(compilation, nameof(compilation)); 48 | 49 | INamedTypeSymbol notNullSymbol = GetVisibleAttribute(NotNull, compilation); 50 | INamedTypeSymbol canBeNullSymbol = GetVisibleAttribute(CanBeNull, compilation); 51 | INamedTypeSymbol itemNotNullSymbol = GetVisibleAttribute(ItemNotNull, compilation); 52 | INamedTypeSymbol itemCanBeNullSymbol = GetVisibleAttribute(ItemCanBeNull, compilation); 53 | 54 | return notNullSymbol != null && canBeNullSymbol != null && itemNotNullSymbol != null && itemCanBeNullSymbol != null 55 | ? new NullabilityAttributeSymbols(notNullSymbol, canBeNullSymbol, itemNotNullSymbol, itemCanBeNullSymbol) 56 | : null; 57 | } 58 | 59 | [CanBeNull] 60 | private INamedTypeSymbol GetVisibleAttribute([NotNull] string fullTypeName, [NotNull] Compilation compilation) 61 | { 62 | INamedTypeSymbol attributeSymbol = compilation.GetTypeByMetadataName(fullTypeName); 63 | 64 | return attributeSymbol != null && IsDefinedInSameAssembly(attributeSymbol, compilation.Assembly) ? attributeSymbol : null; 65 | } 66 | 67 | private bool IsDefinedInSameAssembly([NotNull] INamedTypeSymbol type, [NotNull] IAssemblySymbol assembly) 68 | { 69 | return type.ContainingAssembly.Equals(assembly); 70 | } 71 | 72 | [NotNull] 73 | public ImmutableDictionary ToImmutableDictionary() 74 | { 75 | // @formatter:wrap_chained_method_calls chop_always 76 | 77 | return ImmutableDictionary.Create() 78 | .Add(KeyNotNull, NotNull) 79 | .Add(KeyCanBeNull, CanBeNull) 80 | .Add(KeyItemNotNull, ItemNotNull) 81 | .Add(KeyItemCanBeNull, ItemCanBeNull); 82 | 83 | // @formatter:wrap_chained_method_calls restore 84 | } 85 | 86 | [NotNull] 87 | public static NullabilityAttributeMetadataNames FromImmutableDictionary([NotNull] ImmutableDictionary properties) 88 | { 89 | Guard.NotNull(properties, nameof(properties)); 90 | 91 | return new NullabilityAttributeMetadataNames(properties[KeyNotNull], properties[KeyCanBeNull], properties[KeyItemNotNull], 92 | properties[KeyItemCanBeNull]); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/NullabilityAttributes/NullabilityAttributeSymbols.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using CodeContractNullability.SymbolAnalysis; 3 | using CodeContractNullability.Utilities; 4 | using JetBrains.Annotations; 5 | using Microsoft.CodeAnalysis; 6 | 7 | namespace CodeContractNullability.NullabilityAttributes 8 | { 9 | /// 10 | /// Exposes the found nullability attributes as (source or external) symbols. 11 | /// 12 | public sealed class NullabilityAttributeSymbols 13 | { 14 | [NotNull] 15 | public INamedTypeSymbol NotNull { get; } 16 | 17 | [NotNull] 18 | public INamedTypeSymbol CanBeNull { get; } 19 | 20 | [NotNull] 21 | public INamedTypeSymbol ItemNotNull { get; } 22 | 23 | [NotNull] 24 | public INamedTypeSymbol ItemCanBeNull { get; } 25 | 26 | public NullabilityAttributeSymbols([NotNull] INamedTypeSymbol notNull, [NotNull] INamedTypeSymbol canBeNull, [NotNull] INamedTypeSymbol itemNotNull, 27 | [NotNull] INamedTypeSymbol itemCanBeNull) 28 | { 29 | Guard.NotNull(notNull, nameof(notNull)); 30 | Guard.NotNull(canBeNull, nameof(canBeNull)); 31 | Guard.NotNull(itemNotNull, nameof(itemNotNull)); 32 | Guard.NotNull(itemCanBeNull, nameof(itemCanBeNull)); 33 | 34 | NotNull = notNull; 35 | CanBeNull = canBeNull; 36 | ItemNotNull = itemNotNull; 37 | ItemCanBeNull = itemCanBeNull; 38 | } 39 | 40 | [NotNull] 41 | public NullabilityAttributeMetadataNames GetMetadataNames() 42 | { 43 | return new NullabilityAttributeMetadataNames(NotNull.GetFullMetadataName(), CanBeNull.GetFullMetadataName(), ItemNotNull.GetFullMetadataName(), 44 | ItemCanBeNull.GetFullMetadataName()); 45 | } 46 | 47 | [NotNull] 48 | public ImmutableDictionary GetMetadataNamesAsProperties() 49 | { 50 | return GetMetadataNames().ToImmutableDictionary(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/NullabilityAttributes/SimpleNullabilityAttributeProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using CodeContractNullability.Utilities; 3 | using Microsoft.CodeAnalysis; 4 | 5 | namespace CodeContractNullability.NullabilityAttributes 6 | { 7 | /// 8 | /// Provides direct access to the (Item)NotNullAttribute and (Item)CanBeNullAttribute symbols in a compilation. 9 | /// 10 | public sealed class SimpleNullabilityAttributeProvider : INullabilityAttributeProvider 11 | { 12 | public NullabilityAttributeSymbols GetSymbols(Compilation compilation, CancellationToken cancellationToken = default) 13 | { 14 | Guard.NotNull(compilation, nameof(compilation)); 15 | 16 | var scanner = new CompilationAttributeScanner(); 17 | return scanner.Scan(compilation, cancellationToken); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/NullableReferenceTypeConversionCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CodeContractNullability.Conversion; 6 | using JetBrains.Annotations; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CodeActions; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CSharp.Syntax; 11 | 12 | namespace CodeContractNullability 13 | { 14 | /// 15 | /// Provides a code fix to convert Resharper nullability annotations to C# syntax. 16 | /// 17 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(NullableReferenceTypeConversionCodeFixProvider))] 18 | [Shared] 19 | public sealed class NullableReferenceTypeConversionCodeFixProvider : CodeFixProvider 20 | { 21 | [ItemNotNull] 22 | public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(NullableReferenceTypeConversionAnalyzer.DiagnosticId); 23 | 24 | [NotNull] 25 | public override FixAllProvider GetFixAllProvider() 26 | { 27 | return WellKnownFixAllProviders.BatchFixer; 28 | } 29 | 30 | [NotNull] 31 | public override async Task RegisterCodeFixesAsync(CodeFixContext context) 32 | { 33 | SemanticModel model = await context.Document.GetSemanticModelAsync().ConfigureAwait(false); 34 | var typeCache = new FrameworkTypeCache(model.Compilation); 35 | 36 | foreach (Diagnostic diagnostic in context.Diagnostics) 37 | { 38 | SyntaxNode syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 39 | SyntaxNode targetSyntax = syntaxRoot.FindNode(context.Span); 40 | SyntaxNode declarationSyntax = targetSyntax.TranslateDeclarationSyntax(); 41 | 42 | ISymbol declarationSymbol = DeclarationSyntaxToSymbol(declarationSyntax, model); 43 | 44 | RegisterFixForSyntaxNode(declarationSyntax, declarationSymbol, diagnostic, context, typeCache); 45 | } 46 | } 47 | 48 | [NotNull] 49 | private static ISymbol DeclarationSyntaxToSymbol([NotNull] SyntaxNode declarationSyntax, [NotNull] SemanticModel model) 50 | { 51 | if (declarationSyntax is FieldDeclarationSyntax fieldSyntax) 52 | { 53 | declarationSyntax = fieldSyntax.Declaration.Variables.First(); 54 | } 55 | 56 | return model.GetDeclaredSymbol(declarationSyntax); 57 | } 58 | 59 | private void RegisterFixForSyntaxNode([NotNull] SyntaxNode declarationSyntax, [NotNull] ISymbol declarationSymbol, [NotNull] Diagnostic diagnostic, 60 | CodeFixContext context, [NotNull] FrameworkTypeCache typeCache) 61 | { 62 | var codeAction = CodeAction.Create("Convert to C# syntax", 63 | token => ChangeSolutionAsync(declarationSyntax, declarationSymbol, context.Document, typeCache, token), 64 | nameof(NullableReferenceTypeConversionCodeFixProvider)); 65 | 66 | context.RegisterCodeFix(codeAction, diagnostic); 67 | } 68 | 69 | [ItemNotNull] 70 | private async Task ChangeSolutionAsync([NotNull] SyntaxNode declarationSyntax, [NotNull] ISymbol declarationSymbol, 71 | [NotNull] Document document, [NotNull] FrameworkTypeCache typeCache, CancellationToken cancellationToken) 72 | { 73 | NullConversionContext context = await NullConversionContext.Create(declarationSyntax, document, typeCache, cancellationToken).ConfigureAwait(false); 74 | 75 | var scope = new NullConversionScope(context, declarationSyntax, declarationSymbol); 76 | await scope.RewriteDeclaration(ResharperNullabilitySymbolState.Default).ConfigureAwait(false); 77 | 78 | return await context.GetSolution().ConfigureAwait(false); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/NullableReferenceTypeSupport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Threading; 5 | using JetBrains.Annotations; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.CSharp; 8 | 9 | namespace CodeContractNullability 10 | { 11 | internal static class NullableReferenceTypeSupport 12 | { 13 | [NotNull] 14 | [ItemNotNull] 15 | private static readonly object[] EmptyObjectArray = new object[0]; 16 | 17 | [NotNull] 18 | [ItemCanBeNull] 19 | private static readonly Lazy LazyNullableContextOptionsProperty = 20 | new Lazy(() => typeof(CSharpCompilationOptions).GetProperty("NullableContextOptions"), LazyThreadSafetyMode.PublicationOnly); 21 | 22 | public static bool IsActive([NotNull] Compilation compilation) 23 | { 24 | ParseOptions optionsOrNull = compilation.SyntaxTrees.FirstOrDefault()?.Options; 25 | return optionsOrNull != null && IsLanguageVersionEightOrHigher(optionsOrNull) && IsNullableAnnotationContextEnabled(compilation.Options); 26 | } 27 | 28 | private static bool IsLanguageVersionEightOrHigher([NotNull] ParseOptions parseOptions) 29 | { 30 | return ((CSharpParseOptions)parseOptions).LanguageVersion >= (LanguageVersion)8; 31 | } 32 | 33 | private static bool IsNullableAnnotationContextEnabled([NotNull] CompilationOptions compilationOptions) 34 | { 35 | PropertyInfo property = LazyNullableContextOptionsProperty.Value; 36 | 37 | if (property != null) 38 | { 39 | string enumText = property.GetGetMethod().Invoke(compilationOptions, EmptyObjectArray).ToString(); 40 | return enumText == "Enable" || enumText == "SafeOnly"; 41 | } 42 | 43 | return false; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/ReadMe.txt: -------------------------------------------------------------------------------- 1 |  2 | Building this project will produce an analyzer .dll, as well as the 3 | following two ways you may wish to package that analyzer: 4 | * A NuGet package (.nupkg file) that will add your assembly as a 5 | project-local analyzer that participates in builds. 6 | * A VSIX extension (.vsix file) that will apply your analyzer to all projects 7 | and works just in the IDE. 8 | 9 | To debug your analyzer, make sure the default project is the VSIX project and 10 | start debugging. This will deploy the analyzer as a VSIX into another instance 11 | of Visual Studio, which is useful for debugging, even if you intend to produce 12 | a NuGet package. 13 | 14 | 15 | TRYING OUT YOUR NUGET PACKAGE 16 | 17 | To try out the NuGet package: 18 | 1. Create a local NuGet feed by following the instructions here: 19 | > http://docs.nuget.org/docs/creating-packages/hosting-your-own-nuget-feeds 20 | 2. Copy the .nupkg file into that folder. 21 | 3. Open the target project in Visual Studio 2015/2017/2019. 22 | 4. Right-click on the project node in Solution Explorer and choose Manage 23 | NuGet Packages. 24 | 5. Select the NuGet feed you created on the left. 25 | 6. Choose your analyzer from the list and click Install. 26 | 27 | If you want to automatically deploy the .nupkg file to the local feed folder 28 | when you build this project, follow these steps: 29 | 1. Right-click on this project in Solution Explorer and choose 'Unload Project'. 30 | 2. Right-click on this project and click "Edit". 31 | 3. Scroll down to the "AfterBuild" target. 32 | 4. In the "Exec" task, change the value inside "Command" after the -OutputDirectory 33 | path to point to your local NuGet feed folder. -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/SettingsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.Serialization; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Xml; 9 | using CodeContractNullability.Utilities; 10 | using JetBrains.Annotations; 11 | using Microsoft.CodeAnalysis; 12 | using Microsoft.CodeAnalysis.Diagnostics; 13 | using Microsoft.CodeAnalysis.Text; 14 | 15 | namespace CodeContractNullability 16 | { 17 | public static class SettingsProvider 18 | { 19 | public const string SettingsFileName = "ResharperCodeContractNullability.config"; 20 | 21 | [NotNull] 22 | public static Encoding CreateEncoding() 23 | { 24 | return new UTF8Encoding(); 25 | } 26 | 27 | [NotNull] 28 | internal static AnalyzerSettings LoadSettings([NotNull] AnalyzerOptions options, CancellationToken cancellationToken) 29 | { 30 | Guard.NotNull(options, nameof(options)); 31 | 32 | AdditionalText settingsFileOrNull = options.AdditionalFiles.FirstOrDefault(file => IsSettingsFile(file.Path)); 33 | 34 | if (settingsFileOrNull != null) 35 | { 36 | SourceText fileText = settingsFileOrNull.GetText(cancellationToken); 37 | 38 | try 39 | { 40 | return ReadSourceText(fileText, reader => 41 | { 42 | var serializer = new DataContractSerializer(typeof(AnalyzerSettings)); 43 | return (AnalyzerSettings)serializer.ReadObject(reader); 44 | }, cancellationToken); 45 | } 46 | catch (Exception ex) 47 | { 48 | Debug.Write("Failed to parse analyzer settings file. Using default settings. Exception: " + ex); 49 | } 50 | } 51 | 52 | return AnalyzerSettings.Default; 53 | } 54 | 55 | internal static bool IsSettingsFile([NotNull] string filePath) 56 | { 57 | string fileName = Path.GetFileName(filePath); 58 | return string.Equals(fileName, SettingsFileName, StringComparison.OrdinalIgnoreCase); 59 | } 60 | 61 | [NotNull] 62 | private static TResult ReadSourceText([NotNull] SourceText sourceText, [NotNull] Func readAction, 63 | CancellationToken cancellationToken) 64 | { 65 | using (var stream = new MemoryStream()) 66 | { 67 | using (var writer = new StreamWriter(stream)) 68 | { 69 | sourceText.Write(writer, cancellationToken); 70 | writer.Flush(); 71 | 72 | stream.Seek(0, SeekOrigin.Begin); 73 | 74 | using (var textReader = new StreamReader(stream)) 75 | { 76 | using (var xmlReader = XmlReader.Create(textReader)) 77 | { 78 | return readAction(xmlReader); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | [NotNull] 86 | public static string ToFileContent([NotNull] AnalyzerSettings settings) 87 | { 88 | Guard.NotNull(settings, nameof(settings)); 89 | 90 | Encoding encoding = CreateEncoding(); 91 | 92 | return GetStringForXml(encoding, writer => 93 | { 94 | var serializer = new DataContractSerializer(typeof(AnalyzerSettings)); 95 | serializer.WriteObject(writer, settings); 96 | }); 97 | } 98 | 99 | [NotNull] 100 | private static string GetStringForXml([NotNull] Encoding encoding, [NotNull] Action writeAction) 101 | { 102 | using (var stream = new MemoryStream()) 103 | { 104 | using (var textWriter = new StreamWriter(stream)) 105 | { 106 | using (var xmlWriter = XmlWriter.Create(textWriter, new XmlWriterSettings 107 | { 108 | Encoding = encoding, 109 | Indent = true 110 | })) 111 | { 112 | writeAction(xmlWriter); 113 | 114 | xmlWriter.Flush(); 115 | textWriter.Flush(); 116 | 117 | stream.Seek(0, SeekOrigin.Begin); 118 | 119 | using (var reader = new StreamReader(stream)) 120 | { 121 | return reader.ReadToEnd(); 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/SymbolAnalysis/AnalysisScope.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.ExternalAnnotations; 2 | using CodeContractNullability.Utilities; 3 | using JetBrains.Annotations; 4 | using Microsoft.CodeAnalysis; 5 | 6 | namespace CodeContractNullability.SymbolAnalysis 7 | { 8 | internal sealed class AnalysisScope 9 | { 10 | [NotNull] 11 | public IExternalAnnotationsResolver ExternalAnnotations { get; } 12 | 13 | [NotNull] 14 | public GeneratedCodeDocumentCache GeneratedCodeCache { get; } 15 | 16 | [NotNull] 17 | public FrameworkTypeCache TypeCache { get; } 18 | 19 | [NotNull] 20 | public AnalyzerSettings Settings { get; } 21 | 22 | [NotNull] 23 | public DiagnosticDescriptor DisableReportOnNullableValueTypesRule { get; } 24 | 25 | public bool AppliesToItem { get; } 26 | 27 | public AnalysisScope([NotNull] IExternalAnnotationsResolver externalAnnotations, [NotNull] GeneratedCodeDocumentCache generatedCodeCache, 28 | [NotNull] FrameworkTypeCache typeCache, [NotNull] AnalyzerSettings settings, [NotNull] DiagnosticDescriptor disableReportOnNullableValueTypesRule, 29 | bool appliesToItem) 30 | { 31 | Guard.NotNull(externalAnnotations, nameof(externalAnnotations)); 32 | Guard.NotNull(generatedCodeCache, nameof(generatedCodeCache)); 33 | Guard.NotNull(typeCache, nameof(typeCache)); 34 | Guard.NotNull(settings, nameof(settings)); 35 | Guard.NotNull(disableReportOnNullableValueTypesRule, nameof(disableReportOnNullableValueTypesRule)); 36 | 37 | ExternalAnnotations = externalAnnotations; 38 | GeneratedCodeCache = generatedCodeCache; 39 | TypeCache = typeCache; 40 | Settings = settings; 41 | DisableReportOnNullableValueTypesRule = disableReportOnNullableValueTypesRule; 42 | AppliesToItem = appliesToItem; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/SymbolAnalysis/FieldAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | 5 | namespace CodeContractNullability.SymbolAnalysis 6 | { 7 | /// 8 | /// Performs analysis of a field. 9 | /// 10 | internal sealed class FieldAnalyzer : BaseSymbolAnalyzer 11 | { 12 | public FieldAnalyzer(SymbolAnalysisContext context, [NotNull] AnalysisScope scope) 13 | : base(context, scope) 14 | { 15 | } 16 | 17 | protected override ITypeSymbol GetSymbolType() 18 | { 19 | return Symbol.Type; 20 | } 21 | 22 | protected override bool RequiresAnnotation() 23 | { 24 | return !Symbol.HasConstantValue; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/SymbolAnalysis/FunctionAnalysis.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace CodeContractNullability.SymbolAnalysis 5 | { 6 | /// 7 | /// General knowledge to support the symbol analysis process. 8 | /// 9 | internal static class FunctionAnalysis 10 | { 11 | private const MethodKind MethodKindLocalFunction = (MethodKind)17; 12 | 13 | public static readonly ImmutableArray KindsToSkip = ImmutableArray.Create(MethodKind.AnonymousFunction, MethodKind.LambdaMethod, 14 | MethodKind.PropertyGet, MethodKind.PropertySet, MethodKindLocalFunction); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/SymbolAnalysis/MethodReturnValueAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | 5 | namespace CodeContractNullability.SymbolAnalysis 6 | { 7 | /// 8 | /// Performs analysis of the return value of a method. 9 | /// 10 | internal sealed class MethodReturnValueAnalyzer : BaseSymbolAnalyzer 11 | { 12 | public MethodReturnValueAnalyzer(SymbolAnalysisContext context, [NotNull] AnalysisScope scope) 13 | : base(context, scope) 14 | { 15 | } 16 | 17 | protected override ITypeSymbol GetSymbolType() 18 | { 19 | return Symbol.ReturnType; 20 | } 21 | 22 | protected override bool RequiresAnnotation() 23 | { 24 | if (FunctionAnalysis.KindsToSkip.Contains(Symbol.MethodKind)) 25 | { 26 | return false; 27 | } 28 | 29 | if (!AppliesToItem && Symbol.IsAsync) 30 | { 31 | return false; 32 | } 33 | 34 | return base.RequiresAnnotation(); 35 | } 36 | 37 | protected override bool HasAnnotationInBaseClass() 38 | { 39 | IMethodSymbol baseMember = Symbol.OverriddenMethod; 40 | 41 | while (baseMember != null) 42 | { 43 | if (baseMember.HasNullabilityAnnotation(AppliesToItem) || HasExternalAnnotationFor(baseMember) || HasAnnotationInInterface(baseMember)) 44 | { 45 | return true; 46 | } 47 | 48 | baseMember = baseMember.OverriddenMethod; 49 | } 50 | 51 | return false; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/SymbolAnalysis/ParameterAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Immutable; 3 | using JetBrains.Annotations; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | namespace CodeContractNullability.SymbolAnalysis 8 | { 9 | /// 10 | /// Performs analysis of a method parameter. 11 | /// 12 | internal sealed class ParameterAnalyzer : BaseSymbolAnalyzer 13 | { 14 | public ParameterAnalyzer(SymbolAnalysisContext context, [NotNull] AnalysisScope scope) 15 | : base(context, scope) 16 | { 17 | } 18 | 19 | protected override ITypeSymbol GetSymbolType() 20 | { 21 | return Symbol.Type; 22 | } 23 | 24 | protected override bool RequiresAnnotation() 25 | { 26 | if (ContainerIsLambda()) 27 | { 28 | return false; 29 | } 30 | 31 | return base.RequiresAnnotation(); 32 | } 33 | 34 | private bool ContainerIsLambda() 35 | { 36 | return Symbol.ContainingSymbol is IMethodSymbol method && FunctionAnalysis.KindsToSkip.Contains(method.MethodKind); 37 | } 38 | 39 | protected override bool HasAnnotationInBaseClass() 40 | { 41 | IParameterSymbol baseParameter = TryGetBaseParameterFor(Symbol); 42 | 43 | while (baseParameter != null) 44 | { 45 | if (baseParameter.HasNullabilityAnnotation(AppliesToItem) || HasExternalAnnotationFor(baseParameter) || HasAnnotationInInterface(baseParameter)) 46 | { 47 | return true; 48 | } 49 | 50 | baseParameter = TryGetBaseParameterFor(baseParameter); 51 | } 52 | 53 | return false; 54 | } 55 | 56 | [CanBeNull] 57 | private IParameterSymbol TryGetBaseParameterFor([NotNull] IParameterSymbol parameterSymbol) 58 | { 59 | var containingMethod = parameterSymbol.ContainingSymbol as IMethodSymbol; 60 | IMethodSymbol baseMethod = containingMethod?.OverriddenMethod; 61 | 62 | if (baseMethod != null) 63 | { 64 | int parameterIndex = containingMethod.Parameters.IndexOf(parameterSymbol); 65 | return baseMethod.Parameters[parameterIndex]; 66 | } 67 | 68 | var containingProperty = parameterSymbol.ContainingSymbol as IPropertySymbol; 69 | IPropertySymbol baseProperty = containingProperty?.OverriddenProperty; 70 | 71 | if (baseProperty != null) 72 | { 73 | int parameterIndex = containingProperty.Parameters.IndexOf(parameterSymbol); 74 | return baseProperty.Parameters[parameterIndex]; 75 | } 76 | 77 | return null; 78 | } 79 | 80 | protected override bool HasAnnotationInInterface(IParameterSymbol parameter) 81 | { 82 | ISymbol containingMember = parameter.ContainingSymbol; 83 | 84 | foreach (INamedTypeSymbol @interface in parameter.ContainingType.AllInterfaces) 85 | { 86 | foreach (ISymbol interfaceMember in @interface.GetMembers()) 87 | { 88 | ISymbol implementer = parameter.ContainingType.FindImplementationForInterfaceMember(interfaceMember); 89 | 90 | if (containingMember.Equals(implementer)) 91 | { 92 | ImmutableArray parameters = GetParametersFor(containingMember); 93 | int parameterIndex = parameters.IndexOf(parameter); 94 | 95 | ImmutableArray interfaceParameters = GetParametersFor(interfaceMember); 96 | IParameterSymbol interfaceParameter = interfaceParameters[parameterIndex]; 97 | 98 | if (interfaceParameter.HasNullabilityAnnotation(AppliesToItem) || HasExternalAnnotationFor(interfaceParameter)) 99 | { 100 | return true; 101 | } 102 | } 103 | } 104 | } 105 | 106 | return false; 107 | } 108 | 109 | [ItemNotNull] 110 | private ImmutableArray GetParametersFor([NotNull] ISymbol symbol) 111 | { 112 | if (symbol is IMethodSymbol method) 113 | { 114 | return method.Parameters; 115 | } 116 | 117 | if (symbol is IPropertySymbol property) 118 | { 119 | return property.Parameters; 120 | } 121 | 122 | throw new NotSupportedException($"Expected IMethodSymbol or IPropertySymbol, not {symbol.GetType()}."); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/SymbolAnalysis/PropertyAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | 5 | namespace CodeContractNullability.SymbolAnalysis 6 | { 7 | /// 8 | /// Performs analysis of a property. 9 | /// 10 | internal sealed class PropertyAnalyzer : BaseSymbolAnalyzer 11 | { 12 | public PropertyAnalyzer(SymbolAnalysisContext context, [NotNull] AnalysisScope scope) 13 | : base(context, scope) 14 | { 15 | } 16 | 17 | protected override ITypeSymbol GetSymbolType() 18 | { 19 | return Symbol.Type; 20 | } 21 | 22 | protected override bool HasAnnotationInBaseClass() 23 | { 24 | IPropertySymbol baseMember = Symbol.OverriddenProperty; 25 | 26 | while (baseMember != null) 27 | { 28 | if (baseMember.HasNullabilityAnnotation(AppliesToItem) || HasExternalAnnotationFor(baseMember) || HasAnnotationInInterface(baseMember)) 29 | { 30 | return true; 31 | } 32 | 33 | baseMember = baseMember.OverriddenProperty; 34 | } 35 | 36 | return false; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/SymbolAnalysis/SymbolAnalyzerFactory.cs: -------------------------------------------------------------------------------- 1 | using CodeContractNullability.Utilities; 2 | using JetBrains.Annotations; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | 5 | namespace CodeContractNullability.SymbolAnalysis 6 | { 7 | internal sealed class SymbolAnalyzerFactory 8 | { 9 | [NotNull] 10 | private readonly AnalysisScope scope; 11 | 12 | public SymbolAnalyzerFactory([NotNull] AnalysisScope scope) 13 | { 14 | Guard.NotNull(scope, nameof(scope)); 15 | 16 | this.scope = scope; 17 | } 18 | 19 | [NotNull] 20 | public FieldAnalyzer GetFieldAnalyzer(SymbolAnalysisContext context) 21 | { 22 | return new FieldAnalyzer(context, scope); 23 | } 24 | 25 | [NotNull] 26 | public PropertyAnalyzer GetPropertyAnalyzer(SymbolAnalysisContext context) 27 | { 28 | return new PropertyAnalyzer(context, scope); 29 | } 30 | 31 | [NotNull] 32 | public MethodReturnValueAnalyzer GetMethodReturnValueAnalyzer(SymbolAnalysisContext context) 33 | { 34 | return new MethodReturnValueAnalyzer(context, scope); 35 | } 36 | 37 | [NotNull] 38 | public ParameterAnalyzer GetParameterAnalyzer(SymbolAnalysisContext context) 39 | { 40 | return new ParameterAnalyzer(context, scope); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/SyntaxNodeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JetBrains.Annotations; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp.Syntax; 6 | 7 | namespace CodeContractNullability 8 | { 9 | /// 10 | internal static class SyntaxNodeExtensions 11 | { 12 | [NotNull] 13 | public static SyntaxNode TranslateDeclarationSyntax([NotNull] this SyntaxNode syntax) 14 | { 15 | if (syntax is VariableDeclaratorSyntax fieldVariableSyntax) 16 | { 17 | return fieldVariableSyntax.GetAncestorOrThis(); 18 | } 19 | 20 | if (syntax is IdentifierNameSyntax identifierNameSyntax && 21 | identifierNameSyntax.Parent is ConversionOperatorDeclarationSyntax conversionOperatorDeclarationSyntax) 22 | { 23 | return conversionOperatorDeclarationSyntax; 24 | } 25 | 26 | return syntax; 27 | } 28 | 29 | [CanBeNull] 30 | private static TNode GetAncestorOrThis([CanBeNull] this SyntaxNode node) 31 | where TNode : SyntaxNode 32 | { 33 | return GetAncestorsOrThis(node).FirstOrDefault(); 34 | } 35 | 36 | [NotNull] 37 | [ItemNotNull] 38 | private static IEnumerable GetAncestorsOrThis([CanBeNull] this SyntaxNode node) 39 | where TNode : SyntaxNode 40 | { 41 | return node?.AncestorsAndSelf().OfType() ?? Enumerable.Empty(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Utilities/AnalysisContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | 5 | namespace CodeContractNullability.Utilities 6 | { 7 | internal static class AnalysisContextExtensions 8 | { 9 | public static SymbolAnalysisContext ToSymbolContext(this SyntaxNodeAnalysisContext syntaxContext) 10 | { 11 | ISymbol symbol = syntaxContext.SemanticModel.GetDeclaredSymbol(syntaxContext.Node); 12 | return SyntaxToSymbolContext(syntaxContext, symbol); 13 | } 14 | 15 | private static SymbolAnalysisContext SyntaxToSymbolContext(SyntaxNodeAnalysisContext context, [CanBeNull] ISymbol symbol) 16 | { 17 | return new SymbolAnalysisContext(symbol, context.SemanticModel.Compilation, context.Options, context.ReportDiagnostic, _ => true, 18 | context.CancellationToken); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Utilities/CodeTimer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using JetBrains.Annotations; 4 | 5 | namespace CodeContractNullability.Utilities 6 | { 7 | /// 8 | /// Logs the execution duration of a code block for diagnostics purposes. 9 | /// 10 | internal sealed class CodeTimer : IDisposable 11 | { 12 | [NotNull] 13 | private readonly string text; 14 | 15 | [NotNull] 16 | private readonly Stopwatch stopwatch = new Stopwatch(); 17 | 18 | public CodeTimer([NotNull] string text) 19 | { 20 | Guard.NotNull(text, nameof(text)); 21 | 22 | this.text = text; 23 | stopwatch.Start(); 24 | } 25 | 26 | public void Dispose() 27 | { 28 | stopwatch.Stop(); 29 | Debug.WriteLine($"Duration of {text}: {stopwatch.ElapsedMilliseconds} msec"); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Utilities/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using JetBrains.Annotations; 3 | 4 | namespace CodeContractNullability.Utilities 5 | { 6 | internal static class EnumerableExtensions 7 | { 8 | [NotNull] 9 | [ItemNotNull] 10 | public static IEnumerable PrependIfNotNull([NotNull] [ItemNotNull] this IEnumerable source, [CanBeNull] T firstElement) 11 | where T : class 12 | { 13 | Guard.NotNull(source, nameof(source)); 14 | 15 | if (firstElement != null) 16 | { 17 | yield return firstElement; 18 | } 19 | 20 | foreach (T item in source) 21 | { 22 | yield return item; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Utilities/ExtensionPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace CodeContractNullability.Utilities 5 | { 6 | /// 7 | /// Provides a mechanism to prevent creating a (potentially expensive) default object and injecting a simpler variant. Intended to speed up running unit 8 | /// tests. 9 | /// 10 | /// 11 | /// Type of the object that is replaceable. 12 | /// 13 | public sealed class ExtensionPoint 14 | where TInterface : class 15 | { 16 | [NotNull] 17 | private readonly Func createDefaultInstanceFactory; 18 | 19 | [CanBeNull] 20 | private TInterface specificInstance; 21 | 22 | [CanBeNull] 23 | private TInterface activeInstance; 24 | 25 | internal ExtensionPoint([NotNull] Func createDefaultInstance) 26 | { 27 | Guard.NotNull(createDefaultInstance, nameof(createDefaultInstance)); 28 | createDefaultInstanceFactory = createDefaultInstance; 29 | } 30 | 31 | [NotNull] 32 | private static TInterface InstantiateNotNull([NotNull] Func valueFactory) 33 | { 34 | TInterface result = valueFactory(); 35 | 36 | if (result == null) 37 | { 38 | throw new Exception($"Failed to create instance of {typeof(TInterface)}."); 39 | } 40 | 41 | return result; 42 | } 43 | 44 | [NotNull] 45 | internal TInterface GetCached() 46 | { 47 | // ReSharper disable once ConvertIfStatementToNullCoalescingExpression 48 | if (activeInstance == null) 49 | { 50 | activeInstance = specificInstance ?? InstantiateNotNull(createDefaultInstanceFactory); 51 | } 52 | 53 | return activeInstance; 54 | } 55 | 56 | public void Override([NotNull] TInterface instance) 57 | { 58 | Guard.NotNull(instance, nameof(instance)); 59 | 60 | specificInstance = instance; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Utilities/FreshReference.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using JetBrains.Annotations; 3 | 4 | namespace CodeContractNullability.Utilities 5 | { 6 | /// 7 | /// Holds a reference to an object, where reading and writing the wrapped value always atomically returns the latest value. 8 | /// 9 | /// 10 | /// The type of the wrapped object reference. 11 | /// 12 | /// 13 | /// 14 | /// It is strongly recommended to mark members in your class as readonly, because accidentally replacing a 15 | /// FreshReference object with another FreshReference object defeats the whole purpose of this class. 16 | /// 17 | /// 18 | /// Note that only guards non-cached and atomic exchange of the wrapped object reference. If you need to access members 19 | /// of the wrapped object reference non-cached or atomically, locking is probably a better solution. 20 | /// 21 | /// 22 | internal sealed class FreshReference 23 | where T : class 24 | { 25 | [CanBeNull] 26 | private T innerValue; 27 | 28 | [CanBeNull] 29 | public T Value 30 | { 31 | get => Interlocked.CompareExchange(ref innerValue, null, null); 32 | set => Interlocked.Exchange(ref innerValue, value); 33 | } 34 | 35 | public FreshReference([CanBeNull] T value) 36 | { 37 | // ReSharper disable once DoNotCallOverridableMethodsInConstructor 38 | Value = value; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Utilities/Guard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace CodeContractNullability.Utilities 5 | { 6 | /// 7 | /// Member precondition checks. 8 | /// 9 | public static class Guard 10 | { 11 | [AssertionMethod] 12 | [ContractAnnotation("value: null => halt")] 13 | public static void NotNull([CanBeNull] [NoEnumeration] T value, [NotNull] [InvokerParameterName] string name) 14 | where T : class 15 | { 16 | if (value is null) 17 | { 18 | throw new ArgumentNullException(name); 19 | } 20 | } 21 | 22 | [AssertionMethod] 23 | [ContractAnnotation("value: null => halt")] 24 | public static void NotNullNorWhiteSpace([CanBeNull] string value, [NotNull] [InvokerParameterName] string name) 25 | { 26 | NotNull(value, name); 27 | 28 | if (string.IsNullOrWhiteSpace(value)) 29 | { 30 | throw new ArgumentException($"'{name}' cannot be empty or contain only whitespace.", name); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Utilities/ICacheProvider.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace CodeContractNullability.Utilities 4 | { 5 | public interface ICacheProvider 6 | { 7 | [NotNull] 8 | T GetValue(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/Utilities/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | 3 | namespace CodeContractNullability.Utilities 4 | { 5 | internal static class StringExtensions 6 | { 7 | [NotNull] 8 | public static string ToCamelCase([NotNull] this string memberTypePascalCase) 9 | { 10 | Guard.NotNullNorWhiteSpace(memberTypePascalCase, nameof(memberTypePascalCase)); 11 | 12 | string firstChar = memberTypePascalCase.Substring(0, 1).ToLowerInvariant(); 13 | return memberTypePascalCase.Length > 1 ? firstChar + memberTypePascalCase.Substring(1) : firstChar; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/tools/install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | if($project.Object.SupportsPackageDependencyResolution) 4 | { 5 | if($project.Object.SupportsPackageDependencyResolution()) 6 | { 7 | # Do not install analyzers via install.ps1, instead let the project system handle it. 8 | return 9 | } 10 | } 11 | 12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve 13 | 14 | foreach($analyzersPath in $analyzersPaths) 15 | { 16 | if (Test-Path $analyzersPath) 17 | { 18 | # Install the language agnostic analyzers. 19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) 20 | { 21 | if($project.Object.AnalyzerReferences) 22 | { 23 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 24 | } 25 | } 26 | } 27 | } 28 | 29 | # $project.Type gives the language name like (C# or VB.NET) 30 | $languageFolder = "" 31 | if($project.Type -eq "C#") 32 | { 33 | $languageFolder = "cs" 34 | } 35 | if($project.Type -eq "VB.NET") 36 | { 37 | $languageFolder = "vb" 38 | } 39 | if($languageFolder -eq "") 40 | { 41 | return 42 | } 43 | 44 | foreach($analyzersPath in $analyzersPaths) 45 | { 46 | # Install language specific analyzers. 47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 48 | if (Test-Path $languageAnalyzersPath) 49 | { 50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) 51 | { 52 | if($project.Object.AnalyzerReferences) 53 | { 54 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/CodeContractNullability/CodeContractNullability/tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | if($project.Object.SupportsPackageDependencyResolution) 4 | { 5 | if($project.Object.SupportsPackageDependencyResolution()) 6 | { 7 | # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. 8 | return 9 | } 10 | } 11 | 12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve 13 | 14 | foreach($analyzersPath in $analyzersPaths) 15 | { 16 | # Uninstall the language agnostic analyzers. 17 | if (Test-Path $analyzersPath) 18 | { 19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) 20 | { 21 | if($project.Object.AnalyzerReferences) 22 | { 23 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 24 | } 25 | } 26 | } 27 | } 28 | 29 | # $project.Type gives the language name like (C# or VB.NET) 30 | $languageFolder = "" 31 | if($project.Type -eq "C#") 32 | { 33 | $languageFolder = "cs" 34 | } 35 | if($project.Type -eq "VB.NET") 36 | { 37 | $languageFolder = "vb" 38 | } 39 | if($languageFolder -eq "") 40 | { 41 | return 42 | } 43 | 44 | foreach($analyzersPath in $analyzersPaths) 45 | { 46 | # Uninstall language specific analyzers. 47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 48 | if (Test-Path $languageAnalyzersPath) 49 | { 50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) 51 | { 52 | if($project.Object.AnalyzerReferences) 53 | { 54 | try 55 | { 56 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 57 | } 58 | catch 59 | { 60 | 61 | } 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | True 5 | 6 | 7 | full 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/ResharperCodeContractNullability.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29613.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeContractNullability.Vsix", "CodeContractNullability\CodeContractNullability.Vsix\CodeContractNullability.Vsix.csproj", "{3B498FFB-024D-4BB1-8D55-165FF10FCEA5}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeContractNullability", "CodeContractNullability\CodeContractNullability\CodeContractNullability.csproj", "{1881B7FC-7A28-42ED-94B4-3F2B03FEA0BA}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeContractNullability.Test", "CodeContractNullability\CodeContractNullability.Test\CodeContractNullability.Test.csproj", "{7337149E-293E-4775-8844-771D6B1D2A91}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {3B498FFB-024D-4BB1-8D55-165FF10FCEA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {3B498FFB-024D-4BB1-8D55-165FF10FCEA5}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {3B498FFB-024D-4BB1-8D55-165FF10FCEA5}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {3B498FFB-024D-4BB1-8D55-165FF10FCEA5}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {1881B7FC-7A28-42ED-94B4-3F2B03FEA0BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {1881B7FC-7A28-42ED-94B4-3F2B03FEA0BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {1881B7FC-7A28-42ED-94B4-3F2B03FEA0BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {1881B7FC-7A28-42ED-94B4-3F2B03FEA0BA}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {7337149E-293E-4775-8844-771D6B1D2A91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {7337149E-293E-4775-8844-771D6B1D2A91}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {7337149E-293E-4775-8844-771D6B1D2A91}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {7337149E-293E-4775-8844-771D6B1D2A91}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {0E7BC359-7536-4E55-8B4F-E36600AA29E0} 36 | EndGlobalSection 37 | EndGlobal 38 | --------------------------------------------------------------------------------