├── .editorconfig ├── .gitignore ├── LICENSE ├── README.MD ├── Roslyn.Testing ├── Analyzer │ ├── CSharpDiagnosticAnalyzerTest.cs │ ├── DiagnosticVerifier.Helpers.cs │ ├── DiagnosticVerifier.cs │ └── VerifyDiagnosticAnalyzerResult.cs ├── CodeFix │ ├── CSharpCodeFixProviderTest.cs │ ├── CodeFixProviderTestExtensions.cs │ └── VerifyCodeFixProviderResult.cs ├── Model │ ├── DiagnosticResult.cs │ ├── DiagnosticResultLocation.cs │ └── FileReaderTest.cs └── Roslyn.Testing.csproj ├── System.IO.Abstractions.Analyzers.Tests ├── Analyzers │ ├── DirectoryAnalyzerTests.cs │ ├── DirectoryInfoAnalyzerTests.cs │ ├── FileAnalyzerTests.cs │ ├── FileInfoAnalyzerTests.cs │ ├── FileStreamAnalyzerTests.cs │ ├── PathAnalyzerTests.cs │ ├── StreamReaderAnalyzerTests.cs │ └── StreamWriterAnalyzerTests.cs ├── CodeFixes │ ├── DirectoryCodeFixTests.cs │ ├── DirectoryInfoCodeFixTests.cs │ ├── FileCodeFixTests.cs │ ├── FileInfoCodeFixTests.cs │ ├── FileServiceConstructorInitialCodeFixTests.cs │ ├── FileServiceInterfaceInjectionCodeFixTests.cs │ ├── FileStreamCodeFixTests.cs │ └── PathCodeFixTests.cs ├── System.IO.Abstractions.Analyzers.Tests.csproj └── TestData │ ├── Analyzer │ ├── DirectoryAnalyzer │ │ ├── FalsePositive.txt │ │ ├── ModelObjectFalsePositive.txt │ │ ├── UsingStaticFalsePositive.txt │ │ ├── Valid.txt │ │ └── WithOutFileSystem.txt │ ├── DirectoryInfoAnalyzer │ │ ├── ModelObjectFalsePositive.txt │ │ ├── StaticInvocation.txt │ │ ├── UsingStaticFalsePositive.txt │ │ ├── Valid.txt │ │ └── WithOutFileSystem.txt │ ├── FileAnalyzer │ │ ├── UsingStaticFalsePositive.txt │ │ ├── Valid.txt │ │ └── WithOutFileSystem.txt │ ├── FileInfoAnalyzer │ │ ├── UsingStaticFalsePositive.txt │ │ ├── Valid.txt │ │ └── WithOutFileSystem.txt │ ├── FileStreamAnalyzer │ │ ├── UsingStaticFalsePositive.txt │ │ ├── Valid.txt │ │ └── WithOutFileSystem.txt │ ├── PathAnalyzer │ │ ├── UsingStaticFalsePositive.txt │ │ ├── UsingStaticLinqFalsePositive.txt │ │ ├── Valid.txt │ │ └── WithOutFileSystem.txt │ ├── StreamReaderAnalyzer │ │ ├── UsingFilename.txt │ │ ├── UsingFilenameAndOtherParameters.txt │ │ └── UsingStream.txt │ └── StreamWriterAnalyzer │ │ ├── UsingFilename.txt │ │ ├── UsingFilenameAndOtherParameters.txt │ │ └── UsingStream.txt │ └── CodeFix │ ├── DirectoryCodeFix │ ├── AfterFix.txt │ └── BeforeFix.txt │ ├── DirectoryInfoCodeFix │ ├── AfterFix.txt │ └── BeforeFix.txt │ ├── FileCodeFix │ ├── AfterFix.txt │ └── BeforeFix.txt │ ├── FileInfoCodeFix │ ├── AfterFix.txt │ └── BeforeFix.txt │ ├── FileServiceConstructorInitialCodeFix │ ├── AfterFix.txt │ ├── AfterFixWithCustomField.txt │ ├── BeforeFix.txt │ └── BeforeFixWithCustomField.txt │ ├── FileServiceInterfaceInjectionCodeFix │ ├── AfterFix.txt │ ├── AfterFixConstructorParameterUniqName.txt │ ├── AfterFixContainsAssignment.txt │ ├── BeforeFix.txt │ ├── BeforeFixContainsAssignment.txt │ ├── BeforeFixExistingConstructorParameter.txt │ ├── BeforeFixExistingConstructorParameterUniqName.txt │ ├── BeforeFixExistsAbstractionsUsing.txt │ └── BeforeFixWithoutConstructor.txt │ ├── FileStreamCodeFix │ ├── AfterFix.txt │ └── BeforeFix.txt │ └── PathCodeFix │ ├── AfterFix.txt │ └── BeforeFix.txt ├── System.IO.Abstractions.Analyzers.Vsix ├── System.IO.Abstractions.Analyzers.Vsix.csproj └── source.extension.vsixmanifest ├── System.IO.Abstractions.Analyzers.sln ├── System.IO.Abstractions.Analyzers ├── Analyzers │ ├── BaseFileSystemAnalyzer.cs │ ├── BaseFileSystemNodeAnalyzer.cs │ └── FileSystemTypeAnalyzers │ │ ├── DirectoryAnalyzer.cs │ │ ├── DirectoryInfoAnalyzer.cs │ │ ├── FileAnalyzer.cs │ │ ├── FileInfoAnalyzer.cs │ │ ├── FileStreamAnalyzer.cs │ │ ├── PathAnalyzer.cs │ │ ├── StreamReaderAnalyzer.cs │ │ └── StreamWriterAnalyzer.cs ├── CodeActions │ ├── DirectoryInfoCodeAction.cs │ ├── FileInfoCodeAction.cs │ ├── FileServiceConstructorInitialCodeAction.cs │ ├── FileServiceInterfaceInjectionCodeAction.cs │ ├── FileStreamCodeAction.cs │ └── FileSystemInvokeCodeAction.cs ├── CodeFixes │ ├── BaseInvokeCodeFix.cs │ ├── DirectoryCodeFix.cs │ ├── DirectoryInfoCodeFix.cs │ ├── FileCodeFix.cs │ ├── FileInfoCodeFix.cs │ ├── FileServiceConstructorInitialCodeFix.cs │ ├── FileServiceInterfaceInjectionCodeFix.cs │ ├── FileStreamCodeFix.cs │ └── PathCodeFix.cs ├── Constants.cs ├── FileSystemContext.cs ├── RoslynToken │ └── RoslynClassFileSystem.cs ├── System.IO.Abstractions.Analyzers.csproj └── tools │ ├── install.ps1 │ └── uninstall.ps1 ├── docs ├── IO0001.MD ├── IO0002.MD ├── IO0003.MD ├── IO0004.MD ├── IO0005.MD ├── IO0006.MD └── IO0007.MD ├── icon_256x256.png └── renovate.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### VisualStudio template 3 | ## Ignore Visual Studio temporary files, build results, and 4 | ## files generated by popular Visual Studio add-ons. 5 | ## 6 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015/2017 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # Visual Studio 2017 auto generated files 35 | Generated\ Files/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | # Benchmark Results 51 | BenchmarkDotNet.Artifacts/ 52 | 53 | # .NET Core 54 | project.lock.json 55 | project.fragment.lock.json 56 | artifacts/ 57 | 58 | # StyleCop 59 | StyleCopReport.xml 60 | 61 | # Files built by Visual Studio 62 | *_i.c 63 | *_p.c 64 | *_i.h 65 | *.ilk 66 | *.meta 67 | *.obj 68 | *.iobj 69 | *.pch 70 | *.pdb 71 | *.ipdb 72 | *.pgc 73 | *.pgd 74 | *.rsp 75 | *.sbr 76 | *.tlb 77 | *.tli 78 | *.tlh 79 | *.tmp 80 | *.tmp_proj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # Visual Studio Trace Files 110 | *.e2e 111 | 112 | # TFS 2012 Local Workspace 113 | $tf/ 114 | 115 | # Guidance Automation Toolkit 116 | *.gpState 117 | 118 | # ReSharper is a .NET coding add-in 119 | _ReSharper*/ 120 | *.[Rr]e[Ss]harper 121 | *.DotSettings.user 122 | 123 | # JustCode is a .NET coding add-in 124 | .JustCode 125 | 126 | # TeamCity is a build add-in 127 | _TeamCity* 128 | 129 | # DotCover is a Code Coverage Tool 130 | *.dotCover 131 | 132 | # AxoCover is a Code Coverage Tool 133 | .axoCover/* 134 | !.axoCover/settings.json 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # Note: Comment the next line if you want to checkin your web deploy settings, 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/[Pp]ackages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/[Pp]ackages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/[Pp]ackages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | *.appx 207 | 208 | # Visual Studio cache files 209 | # files ending in .cache can be ignored 210 | *.[Cc]ache 211 | # but keep track of directories ending in .cache 212 | !*.[Cc]ache/ 213 | 214 | # Others 215 | ClientBin/ 216 | ~$* 217 | *~ 218 | *.dbmdl 219 | *.dbproj.schemaview 220 | *.jfm 221 | *.pfx 222 | *.publishsettings 223 | orleans.codegen.cs 224 | 225 | # Including strong name files can present a security risk 226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 227 | #*.snk 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | ServiceFabricBackup/ 244 | *.rptproj.bak 245 | 246 | # SQL Server files 247 | *.mdf 248 | *.ldf 249 | *.ndf 250 | 251 | # Business Intelligence projects 252 | *.rdl.data 253 | *.bim.layout 254 | *.bim_*.settings 255 | *.rptproj.rsuser 256 | 257 | # Microsoft Fakes 258 | FakesAssemblies/ 259 | 260 | # GhostDoc plugin setting file 261 | *.GhostDoc.xml 262 | 263 | # Node.js Tools for Visual Studio 264 | .ntvs_analysis.dat 265 | node_modules/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush 296 | .cr/ 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | 321 | # Azure Stream Analytics local run output 322 | ASALocalRun/ 323 | 324 | # MSBuild Binary and Structured Log 325 | *.binlog 326 | 327 | # NVidia Nsight GPU debugger configuration file 328 | *.nvuser 329 | 330 | # MFractors (Xamarin productivity tool) working folder 331 | .mfractor/ 332 | 333 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Inyutin Maxim and Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | 1. Add to project nuget package `TestableIO.System.IO.Abstractions` [![](https://img.shields.io/nuget/v/TestableIO.System.IO.Abstractions.svg)](https://www.nuget.org/packages/TestableIO.System.IO.Abstractions) 4 | ``` bash 5 | > dotnet add package TestableIO.System.IO.Abstractions 6 | ``` 7 | 2. Add to project nuget package `TestableIO.System.IO.Abstractions.Analyzers` [![](https://img.shields.io/nuget/v/TestableIO.System.IO.Abstractions.Analyzers.svg)](https://www.nuget.org/packages/TestableIO.System.IO.Abstractions.Analyzers) 8 | ``` bash 9 | > dotnet add package TestableIO.System.IO.Abstractions.Analyzers 10 | ``` 11 | 12 | # Contacts 13 | 14 | - [![Telegram Chat](https://img.shields.io/badge/Chat-Telegram-0F80C1.svg)](https://t.me/System_IO_Abstractions_Analyzer) 15 | 16 | # Contributing 17 | 18 | - Send your `Pull Request` 19 | - Add new [issue](https://github.com/TestableIO/System.IO.Abstractions.Analyzers/issues/new) 20 | 21 | # Sponsors 22 | - [![Donate_To_Card](https://img.shields.io/badge/Donate_To_Card-donate-red.svg)](https://money.alfabank.ru/p2p/web/transfer/minyutin) 23 | - [![PayPal](https://img.shields.io/badge/PayPal-donate-red.svg)](https://www.paypal.me/InyutinMaxim) 24 | -------------------------------------------------------------------------------- /Roslyn.Testing/Analyzer/CSharpDiagnosticAnalyzerTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.Diagnostics; 5 | using Roslyn.Testing.Model; 6 | using Shouldly; 7 | 8 | namespace Roslyn.Testing.Analyzer; 9 | 10 | public abstract class CSharpDiagnosticAnalyzerTest : FileReaderTest 11 | where T : DiagnosticAnalyzer, new() 12 | { 13 | /// 14 | public override string Filepath => 15 | _diagnosticAnalyzer.GetType() 16 | .Name; 17 | 18 | /// 19 | public override string PathToTestData => "./TestData/Analyzer/"; 20 | 21 | private readonly DiagnosticAnalyzer _diagnosticAnalyzer; 22 | 23 | protected CSharpDiagnosticAnalyzerTest() => _diagnosticAnalyzer = new T(); 24 | 25 | protected virtual IEnumerable GetAdditionalReferences() => Enumerable.Empty(); 26 | 27 | /// 28 | /// Called to test a C# DiagnosticAnalyzer when applied on the single inputted 29 | /// string as a source 30 | /// Note: input a DiagnosticResult for each Diagnostic expected 31 | /// 32 | /// A class in the form of a string to run the analyzer on 33 | /// 34 | /// DiagnosticResults that should appear after the analyzer 35 | /// is run on the source 36 | /// 37 | protected void VerifyDiagnostic(string source, DiagnosticResult[] expected) => VerifyDiagnostic(new[] 38 | { 39 | source 40 | }, expected); 41 | 42 | protected void VerifyDiagnostic(string source, DiagnosticResult expected) => VerifyDiagnostic(new[] 43 | { 44 | source 45 | }, new[] 46 | { 47 | expected 48 | }); 49 | 50 | protected void VerifyNoDiagnosticTriggered(string source) => VerifyDiagnostic(new[] 51 | { 52 | source 53 | }, new DiagnosticResult[0]); 54 | 55 | /// 56 | /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as 57 | /// a source 58 | /// Note: input a DiagnosticResult for each Diagnostic expected 59 | /// 60 | /// 61 | /// An array of strings to create source documents from to 62 | /// run the analyzers on 63 | /// 64 | /// 65 | /// DiagnosticResults that should appear after the analyzer 66 | /// is run on the sources 67 | /// 68 | protected void VerifyDiagnostic(string[] sources, DiagnosticResult[] expected) 69 | { 70 | var actual = _diagnosticAnalyzer.GetSortedDiagnostics(sources, LanguageNames.CSharp, GetAdditionalReferences()); 71 | var result = _diagnosticAnalyzer.VerifyDiagnosticResults(actual, expected); 72 | result.Success.ShouldBe(true, result.ErrorMessage); 73 | } 74 | } -------------------------------------------------------------------------------- /Roslyn.Testing/Analyzer/DiagnosticVerifier.Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using JetBrains.Annotations; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | using Microsoft.CodeAnalysis.Diagnostics; 10 | using Microsoft.CodeAnalysis.Text; 11 | 12 | namespace Roslyn.Testing.Analyzer; 13 | 14 | /// 15 | /// Class for turning strings into documents and getting the diagnostics on them 16 | /// All methods are static 17 | /// 18 | public abstract class DiagnosticVerifier 19 | { 20 | private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); 21 | 22 | private static readonly MetadataReference SystemCoreReference = 23 | MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); 24 | 25 | private static readonly MetadataReference CSharpSymbolsReference = 26 | MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); 27 | 28 | private static readonly MetadataReference CodeAnalysisReference = 29 | MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); 30 | 31 | private static readonly MetadataReference SystemDiagReference = MetadataReference.CreateFromFile(typeof(Process).Assembly.Location); 32 | 33 | internal static string DefaultFilePathPrefix = "Test"; 34 | 35 | internal static string CSharpDefaultFileExt = "cs"; 36 | 37 | internal static string VisualBasicDefaultExt = "vb"; 38 | 39 | internal static string TestProjectName = "TestProject"; 40 | 41 | #region Get Diagnostics 42 | 43 | /// 44 | /// Given classes in the form of strings, their language, and an 45 | /// IDiagnosticAnlayzer to apply to it, return the diagnostics found in the string 46 | /// after converting it to a document. 47 | /// 48 | /// Classes in the form of strings 49 | /// The language the source classes are in 50 | /// The analyzer to be run on the sources 51 | /// 52 | /// 53 | /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by 54 | /// Location 55 | /// 56 | [UsedImplicitly] 57 | private static Diagnostic[] GetSortedDiagnostics(string[] sources, string language, ImmutableArray analyzers, 58 | IEnumerable references = null) => 59 | GetSortedDiagnosticsFromDocuments(analyzers, GetDocuments(sources, language, references)); 60 | 61 | /// 62 | /// Given an analyzer and a document to apply it to, run the analyzer and gather an 63 | /// array of diagnostics found in it. 64 | /// The returned diagnostics are then ordered by location in the source document. 65 | /// 66 | /// The analyzer to run on the documents 67 | /// The Documents that the analyzer will be run on 68 | /// 69 | /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by 70 | /// Location 71 | /// 72 | protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(ImmutableArray analyzers, Document[] documents) 73 | { 74 | var projects = new HashSet(); 75 | 76 | foreach (var document in documents) 77 | { 78 | projects.Add(document.Project); 79 | } 80 | 81 | var diagnostics = new List(); 82 | 83 | foreach (var project in projects) 84 | { 85 | var compilationWithAnalyzers = project.GetCompilationAsync() 86 | .GetAwaiter() 87 | .GetResult() 88 | .WithAnalyzers(analyzers); 89 | 90 | var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync() 91 | .GetAwaiter() 92 | .GetResult(); 93 | 94 | foreach (var diag in diags) 95 | { 96 | if (diag.Location == Location.None || diag.Location.IsInMetadata) 97 | { 98 | diagnostics.Add(diag); 99 | } else 100 | { 101 | for (var i = 0; i < documents.Length; i++) 102 | { 103 | var document = documents[i]; 104 | 105 | var tree = document.GetSyntaxTreeAsync() 106 | .GetAwaiter() 107 | .GetResult(); 108 | 109 | if (tree == diag.Location.SourceTree) 110 | { 111 | diagnostics.Add(diag); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | 118 | var results = SortDiagnostics(diagnostics); 119 | diagnostics.Clear(); 120 | 121 | return results; 122 | } 123 | 124 | /// 125 | /// Sort diagnostics by location in source document 126 | /// 127 | /// The list of Diagnostics to be sorted 128 | /// An IEnumerable containing the Diagnostics in order of Location 129 | private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics) => diagnostics 130 | .OrderBy(d => d.Location.SourceSpan.Start) 131 | .ToArray(); 132 | 133 | #endregion 134 | 135 | #region Set up compilation and documents 136 | 137 | /// 138 | /// Given an array of strings as sources and a language, turn them into a project 139 | /// and return the documents and spans of it. 140 | /// 141 | /// Classes in the form of strings 142 | /// The language the source code is in 143 | /// 144 | /// 145 | /// A Tuple containing the Documents produced from the sources and their TextSpans 146 | /// if relevant 147 | /// 148 | private static Document[] GetDocuments(string[] sources, string language, IEnumerable references = null) 149 | { 150 | if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic) 151 | { 152 | throw new ArgumentException("Unsupported Language"); 153 | } 154 | 155 | var project = CreateProject(sources, language, references); 156 | var documents = project.Documents.ToArray(); 157 | 158 | if (sources.Length != documents.Length) 159 | { 160 | throw new SystemException("Amount of sources did not match amount of Documents created"); 161 | } 162 | 163 | return documents; 164 | } 165 | 166 | /// 167 | /// Create a Document from a string through creating a project that contains it. 168 | /// 169 | /// Classes in the form of a string 170 | /// The language the source code is in 171 | /// 172 | /// A Document created from the source string 173 | protected static Document CreateDocument(string source, string language = LanguageNames.CSharp, 174 | IEnumerable references = null) => CreateProject(new[] 175 | { 176 | source 177 | }, language, references) 178 | .Documents.First(); 179 | 180 | /// 181 | /// Create a project using the inputted strings as sources. 182 | /// 183 | /// Classes in the form of strings 184 | /// The language the source code is in 185 | /// 186 | /// 187 | /// A Project created out of the Documents created from the source 188 | /// strings 189 | /// 190 | private static Project CreateProject(string[] sources, string language = LanguageNames.CSharp, 191 | IEnumerable references = null) 192 | { 193 | var fileNamePrefix = DefaultFilePathPrefix; 194 | 195 | var fileExt = language == LanguageNames.CSharp 196 | ? CSharpDefaultFileExt 197 | : VisualBasicDefaultExt; 198 | 199 | var projectId = ProjectId.CreateNewId(TestProjectName); 200 | 201 | var solution = new AdhocWorkspace() 202 | .CurrentSolution 203 | .AddProject(projectId, TestProjectName, TestProjectName, language) 204 | .AddMetadataReference(projectId, CorlibReference) 205 | .AddMetadataReference(projectId, SystemCoreReference) 206 | .AddMetadataReference(projectId, CSharpSymbolsReference) 207 | .AddMetadataReference(projectId, CodeAnalysisReference) 208 | .AddMetadataReference(projectId, SystemDiagReference); 209 | 210 | if (references != null) 211 | { 212 | solution = solution.AddMetadataReferences(projectId, references); 213 | } 214 | 215 | var count = 0; 216 | 217 | foreach (var source in sources) 218 | { 219 | var newFileName = fileNamePrefix + count + "." + fileExt; 220 | var documentId = DocumentId.CreateNewId(projectId, newFileName); 221 | solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); 222 | count++; 223 | } 224 | 225 | return solution.GetProject(projectId); 226 | } 227 | 228 | #endregion 229 | } -------------------------------------------------------------------------------- /Roslyn.Testing/Analyzer/DiagnosticVerifier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | using Microsoft.CodeAnalysis.Diagnostics; 10 | using Microsoft.CodeAnalysis.Text; 11 | using Roslyn.Testing.Model; 12 | 13 | namespace Roslyn.Testing.Analyzer; 14 | 15 | internal static class DiagnosticAnalyzerTestExtensions 16 | { 17 | private static readonly MetadataReference CoreLibraryReference = 18 | MetadataReference.CreateFromFile(typeof(object).Assembly.Location); 19 | 20 | private static readonly MetadataReference SystemCoreReference = 21 | MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); 22 | 23 | private static readonly MetadataReference CSharpSymbolsReference = 24 | MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); 25 | 26 | private static readonly MetadataReference CodeAnalysisReference = 27 | MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); 28 | 29 | private static readonly MetadataReference SystemDiagnosticReference = 30 | MetadataReference.CreateFromFile(typeof(Process).Assembly.Location); 31 | 32 | internal static string DefaultFilePathPrefix = "Test"; 33 | 34 | internal static string CSharpDefaultFileExt = "cs"; 35 | 36 | internal static string VisualBasicDefaultExt = "vb"; 37 | 38 | internal static string TestProjectName = "TestProject"; 39 | 40 | #region [Get Diagnostics] 41 | 42 | /// 43 | /// Given classes in the form of strings, their language, and an 44 | /// IDiagnosticAnalyzer to apply to it, return the diagnostics found in the string 45 | /// after converting it to a document. 46 | /// 47 | /// Classes in the form of strings 48 | /// The language the source classes are in 49 | /// The analyzer to be run on the sources 50 | /// 51 | /// 52 | /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by 53 | /// Location 54 | /// 55 | public static Diagnostic[] GetSortedDiagnostics(this DiagnosticAnalyzer analyzer, 56 | string[] sources, 57 | string language, IEnumerable references = null) => 58 | GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language, references)); 59 | 60 | /// 61 | /// Given an analyzer and a document to apply it to, run the analyzer and gather an 62 | /// array of diagnostics found in it. 63 | /// The returned diagnostics are then ordered by location in the source document. 64 | /// 65 | /// The analyzer to run on the documents 66 | /// The Documents that the analyzer will be run on 67 | /// 68 | /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by 69 | /// Location 70 | /// 71 | public static Diagnostic[] GetSortedDiagnosticsFromDocuments(this DiagnosticAnalyzer analyzer, 72 | Document[] documents) 73 | { 74 | var projects = new HashSet(); 75 | 76 | foreach (var document in documents) 77 | { 78 | projects.Add(document.Project); 79 | } 80 | 81 | var diagnostics = new List(); 82 | 83 | foreach (var project in projects) 84 | { 85 | var compilationWithAnalyzers = 86 | project.GetCompilationAsync() 87 | .GetAwaiter() 88 | .GetResult() 89 | .WithAnalyzers(ImmutableArray.Create(analyzer)); 90 | 91 | var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync() 92 | .GetAwaiter() 93 | .GetResult(); 94 | 95 | foreach (var diag in diags) 96 | { 97 | if (diag.Location == Location.None || diag.Location.IsInMetadata) 98 | { 99 | diagnostics.Add(diag); 100 | } else 101 | { 102 | foreach (var document in documents) 103 | { 104 | var tree = document.GetSyntaxTreeAsync() 105 | .GetAwaiter() 106 | .GetResult(); 107 | 108 | if (tree == diag.Location.SourceTree) 109 | { 110 | diagnostics.Add(diag); 111 | } 112 | } 113 | } 114 | } 115 | } 116 | 117 | var results = SortDiagnostics(diagnostics); 118 | diagnostics.Clear(); 119 | 120 | return results; 121 | } 122 | 123 | /// 124 | /// Sort diagnostics by location in source document 125 | /// 126 | /// The list of Diagnostics to be sorted 127 | /// An IEnumerable containing the Diagnostics in order of Location 128 | private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics) => diagnostics 129 | .OrderBy(d => d.Location.SourceSpan.Start) 130 | .ToArray(); 131 | 132 | #endregion 133 | 134 | #region [Set up compilation and documents] 135 | 136 | /// 137 | /// Given an array of strings as sources and a language, turn them into a project 138 | /// and return the documents and spans of it. 139 | /// 140 | /// Classes in the form of strings 141 | /// The language the source code is in 142 | /// 143 | /// 144 | /// A Tuple containing the Documents produced from the sources and their TextSpans 145 | /// if relevant 146 | /// 147 | private static Document[] GetDocuments(string[] sources, string language, IEnumerable references = null) 148 | { 149 | if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic) 150 | { 151 | throw new ArgumentException("Unsupported Language"); 152 | } 153 | 154 | var project = CreateProject(sources, language, references); 155 | var documents = project.Documents.ToArray(); 156 | 157 | if (sources.Length != documents.Length) 158 | { 159 | throw new SystemException("Amount of sources did not match amount of Documents created"); 160 | } 161 | 162 | return documents; 163 | } 164 | 165 | /// 166 | /// Create a Document from a string through creating a project that contains it. 167 | /// 168 | /// Classes in the form of a string 169 | /// The language the source code is in 170 | /// 171 | /// A Document created from the source string 172 | public static Document CreateDocument(string source, string language, IEnumerable references = null) => 173 | CreateProject(new[] 174 | { 175 | source 176 | }, language, references) 177 | .Documents.First(); 178 | 179 | /// 180 | /// Create a project using the inputted strings as sources. 181 | /// 182 | /// Classes in the form of strings 183 | /// The language the source code is in 184 | /// Дополнительные сборки 185 | /// 186 | /// A Project created out of the Documents created from the source 187 | /// strings 188 | /// 189 | private static Project CreateProject(string[] sources, string language, IEnumerable references = null) 190 | { 191 | var fileNamePrefix = DefaultFilePathPrefix; 192 | 193 | var fileExt = language == LanguageNames.CSharp 194 | ? CSharpDefaultFileExt 195 | : VisualBasicDefaultExt; 196 | 197 | var projectId = ProjectId.CreateNewId(TestProjectName); 198 | 199 | var solution = new AdhocWorkspace() 200 | .CurrentSolution 201 | .AddProject(projectId, TestProjectName, TestProjectName, language) 202 | .AddMetadataReference(projectId, CoreLibraryReference) 203 | .AddMetadataReference(projectId, SystemCoreReference) 204 | .AddMetadataReference(projectId, CSharpSymbolsReference) 205 | .AddMetadataReference(projectId, CodeAnalysisReference) 206 | .AddMetadataReference(projectId, SystemDiagnosticReference); 207 | 208 | if (references != null) 209 | { 210 | solution = solution.AddMetadataReferences(projectId, references); 211 | } 212 | 213 | var count = 0; 214 | 215 | foreach (var source in sources) 216 | { 217 | var newFileName = fileNamePrefix + count + "." + fileExt; 218 | var documentId = DocumentId.CreateNewId(projectId, newFileName); 219 | solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); 220 | count++; 221 | } 222 | 223 | return solution.GetProject(projectId); 224 | } 225 | 226 | #endregion 227 | 228 | #region [Actual comparisons and verifications] 229 | 230 | /// 231 | /// Checks each of the actual Diagnostics found and compares them with the 232 | /// corresponding DiagnosticResult in the array of expected results. 233 | /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, 234 | /// Severity, and Message of the DiagnosticResult match the actual diagnostic. 235 | /// 236 | /// 237 | /// The Diagnostics found by the compiler after running the analyzer on the source 238 | /// code 239 | /// 240 | /// The analyzer that was being run on the sources 241 | /// 242 | /// Diagnostic Results that should have appeared in 243 | /// the code 244 | /// 245 | public static VerifyDiagnosticAnalyzerResult VerifyDiagnosticResults(this DiagnosticAnalyzer analyzer, 246 | IEnumerable actualResults, 247 | DiagnosticResult[] expectedResults) 248 | { 249 | var expectedCount = expectedResults.Length; 250 | var actualCount = actualResults.Count(); 251 | 252 | if (expectedCount != actualCount) 253 | { 254 | var diagnosticsOutput = actualResults.Any() 255 | ? FormatDiagnostics(analyzer, actualResults.ToArray()) 256 | : " NONE."; 257 | 258 | var msg = GetMismatchNumberOfDiagnosticsMessage(expectedCount, actualCount, diagnosticsOutput); 259 | 260 | return VerifyDiagnosticAnalyzerResult.Fail(msg); 261 | } 262 | 263 | for (var i = 0; i < expectedResults.Length; i++) 264 | { 265 | var actual = actualResults.ElementAt(i); 266 | var expected = expectedResults[i]; 267 | 268 | if (expected.Line == -1 && expected.Column == -1) 269 | { 270 | if (actual.Location != Location.None) 271 | { 272 | var msg = GetExpectedDiagnosticWithNoLocation(analyzer, actual); 273 | 274 | return VerifyDiagnosticAnalyzerResult.Fail(msg); 275 | } 276 | } else 277 | { 278 | var locationResult = 279 | VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); 280 | 281 | if (!locationResult.Success) 282 | { 283 | return locationResult; 284 | } 285 | 286 | var additionalLocations = actual.AdditionalLocations.ToArray(); 287 | 288 | if (additionalLocations.Length != expected.Locations.Length - 1) 289 | { 290 | var msg = GetNotExpectedLocation(analyzer, actual, expected, additionalLocations); 291 | 292 | return VerifyDiagnosticAnalyzerResult.Fail(msg); 293 | } 294 | 295 | for (var j = 0; j < additionalLocations.Length; ++j) 296 | { 297 | locationResult = VerifyDiagnosticLocation(analyzer, 298 | actual, 299 | additionalLocations[j], 300 | expected.Locations[j + 1]); 301 | 302 | if (!locationResult.Success) 303 | { 304 | return locationResult; 305 | } 306 | } 307 | } 308 | 309 | if (actual.Id != expected.Id) 310 | { 311 | var msg = GetNoExpectedDiagnosticId(analyzer, actual, expected); 312 | 313 | return VerifyDiagnosticAnalyzerResult.Fail(msg); 314 | } 315 | 316 | if (actual.Severity != expected.Severity) 317 | { 318 | var msg = GetNotExpectedSeverityMessage(analyzer, actual, expected); 319 | 320 | return VerifyDiagnosticAnalyzerResult.Fail(msg); 321 | } 322 | 323 | if (actual.GetMessage() != expected.Message) 324 | { 325 | var msg = GetNotExcpectedMessage(analyzer, actual, expected); 326 | 327 | return VerifyDiagnosticAnalyzerResult.Fail(msg); 328 | } 329 | } 330 | 331 | return VerifyDiagnosticAnalyzerResult.Ok(); 332 | } 333 | 334 | /// 335 | /// Helper method to VerifyDiagnosticResult that checks the location of a 336 | /// diagnostic and compares it with the location in the expected DiagnosticResult. 337 | /// 338 | /// The analyzer that was being run on the sources 339 | /// The diagnostic that was found in the code 340 | /// The Location of the Diagnostic found in the code 341 | /// 342 | /// The DiagnosticResultLocation that should have been 343 | /// found 344 | /// 345 | private static VerifyDiagnosticAnalyzerResult VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, 346 | Diagnostic diagnostic, 347 | Location actual, 348 | DiagnosticResultLocation expected) 349 | { 350 | var actualSpan = actual.GetLineSpan(); 351 | 352 | var isInExpectedFile = actualSpan.Path == expected.Path 353 | || (actualSpan.Path != null 354 | && actualSpan.Path.Contains("Test0.") 355 | && expected.Path.Contains("Test.")); 356 | 357 | if (!isInExpectedFile) 358 | { 359 | var msg = GetNotInExpectedFileMessage(analyzer, diagnostic, expected, actualSpan); 360 | 361 | return VerifyDiagnosticAnalyzerResult.Fail(msg); 362 | } 363 | 364 | var actualLinePosition = actualSpan.StartLinePosition; 365 | 366 | // Only check line position if there is an actual line in the real diagnostic 367 | if (actualLinePosition.Line > 0) 368 | { 369 | if (actualLinePosition.Line + 1 != expected.Line) 370 | { 371 | var msg = GetNotInExpectedLineMessage(analyzer, diagnostic, expected, actualLinePosition); 372 | 373 | return VerifyDiagnosticAnalyzerResult.Fail(msg); 374 | } 375 | } 376 | 377 | // Only check column position if there is an actual column position in the real diagnostic 378 | if (actualLinePosition.Character > 0) 379 | { 380 | if (actualLinePosition.Character + 1 != expected.Column) 381 | { 382 | var msg = GetNotInExpectedColumn(analyzer, diagnostic, expected, actualLinePosition); 383 | 384 | return VerifyDiagnosticAnalyzerResult.Fail(msg); 385 | } 386 | } 387 | 388 | return VerifyDiagnosticAnalyzerResult.Ok(); 389 | } 390 | 391 | private static string GetMismatchNumberOfDiagnosticsMessage(int expectedCount, int actualCount, 392 | string diagnosticsOutput) => 393 | $"Mismatch between number of diagnostics returned, expected \"{expectedCount}\" actual \"{actualCount}\"{Environment.NewLine}{Environment.NewLine}Diagnostics:{Environment.NewLine}{diagnosticsOutput}{Environment.NewLine}"; 394 | 395 | private static string GetExpectedDiagnosticWithNoLocation(DiagnosticAnalyzer analyzer, Diagnostic actual) => 396 | $"Expected:\nA project diagnostic with No location\nActual:\n{FormatDiagnostics(analyzer, new[] { actual })}"; 397 | 398 | private static string GetNotExpectedLocation(DiagnosticAnalyzer analyzer, 399 | Diagnostic actual, 400 | DiagnosticResult expected, 401 | Location[] additionalLocations) => 402 | $"Expected {expected.Locations.Length - 1} additional locations but got {additionalLocations.Length} for Diagnostic:{Environment.NewLine} {FormatDiagnostics(analyzer, new[] { actual })}{Environment.NewLine}"; 403 | 404 | private static string GetNoExpectedDiagnosticId(DiagnosticAnalyzer analyzer, 405 | Diagnostic actual, 406 | DiagnosticResult expected) => 407 | $"Expected diagnostic id to be \"{expected.Id}\" was \"{actual.Id}\"{Environment.NewLine}{Environment.NewLine}Diagnostic:{Environment.NewLine} {FormatDiagnostics(analyzer, new[] { actual })}{Environment.NewLine}"; 408 | 409 | private static string GetNotExpectedSeverityMessage(DiagnosticAnalyzer analyzer, 410 | Diagnostic actual, 411 | DiagnosticResult expected) => 412 | $"Expected diagnostic severity to be \"{expected.Severity}\" was \"{actual.Severity}\"{Environment.NewLine}{Environment.NewLine}Diagnostic:{Environment.NewLine} {FormatDiagnostics(analyzer, new[] { actual })}{Environment.NewLine}"; 413 | 414 | private static string GetNotExcpectedMessage(DiagnosticAnalyzer analyzer, 415 | Diagnostic actual, 416 | DiagnosticResult expected) => 417 | $"Expected diagnostic message to be \"{expected.Message}\" was \"{actual.GetMessage()}\"{Environment.NewLine}{Environment.NewLine}Diagnostic:{Environment.NewLine} {FormatDiagnostics(analyzer, new[] { actual })}{Environment.NewLine}"; 418 | 419 | private static string GetNotInExpectedColumn(DiagnosticAnalyzer analyzer, 420 | Diagnostic diagnostic, 421 | DiagnosticResultLocation expected, 422 | LinePosition actualLinePosition) => 423 | $"Expected diagnostic to start at column \"{expected.Column}\" was actually at column \"{actualLinePosition.Character + 1}\"{Environment.NewLine}{Environment.NewLine}Diagnostic:{Environment.NewLine} {FormatDiagnostics(analyzer, new[] { diagnostic })}{Environment.NewLine}"; 424 | 425 | private static string GetNotInExpectedLineMessage(DiagnosticAnalyzer analyzer, 426 | Diagnostic diagnostic, 427 | DiagnosticResultLocation expected, 428 | LinePosition actualLinePosition) => 429 | $"Expected diagnostic to be on line \"{expected.Line}\" was actually on line \"{actualLinePosition.Line + 1}\"{Environment.NewLine}{Environment.NewLine}Diagnostic:{Environment.NewLine} {FormatDiagnostics(analyzer, new[] { diagnostic })}{Environment.NewLine}"; 430 | 431 | private static string GetNotInExpectedFileMessage(DiagnosticAnalyzer analyzer, 432 | Diagnostic diagnostic, 433 | DiagnosticResultLocation expected, 434 | FileLinePositionSpan actualSpan) => 435 | $"Expected diagnostic to be in file \"{expected.Path}\" was actually in file \"{actualSpan.Path}\"{Environment.NewLine}{Environment.NewLine}Diagnostic:{Environment.NewLine} {FormatDiagnostics(analyzer, new[] { diagnostic })}{Environment.NewLine}"; 436 | 437 | /// 438 | /// Helper method to format a Diagnostic into an easily readable string 439 | /// 440 | /// The analyzer that this verifier tests 441 | /// The Diagnostics to be formatted 442 | /// The Diagnostics formatted as a string 443 | private static string FormatDiagnostics(DiagnosticAnalyzer analyzer, Diagnostic[] diagnostics) 444 | { 445 | var builder = new StringBuilder(); 446 | 447 | for (var i = 0; i < diagnostics.Length; ++i) 448 | { 449 | builder.AppendLine("// " + diagnostics[i]); 450 | 451 | var analyzerType = analyzer.GetType(); 452 | var rules = analyzer.SupportedDiagnostics; 453 | 454 | foreach (var rule in rules) 455 | { 456 | if (rule != null 457 | && rule.Id 458 | == diagnostics[i] 459 | .Id) 460 | { 461 | var location = diagnostics[i] 462 | .Location; 463 | 464 | if (location == Location.None) 465 | { 466 | builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id); 467 | } else 468 | { 469 | if (!location.IsInSource) 470 | { 471 | var msg = 472 | $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}{Environment.NewLine}"; 473 | 474 | throw new(msg); 475 | } 476 | 477 | var resultMethodName = diagnostics[i] 478 | .Location.SourceTree.FilePath.EndsWith(".cs") 479 | ? "GetCSharpResultAt" 480 | : "GetBasicResultAt"; 481 | 482 | var linePosition = diagnostics[i] 483 | .Location.GetLineSpan() 484 | .StartLinePosition; 485 | 486 | builder.AppendFormat("{0}({1}, {2}, {3}.{4})", 487 | resultMethodName, 488 | linePosition.Line + 1, 489 | linePosition.Character + 1, 490 | analyzerType.Name, 491 | rule.Id); 492 | } 493 | 494 | if (i != diagnostics.Length - 1) 495 | { 496 | builder.Append(','); 497 | } 498 | 499 | builder.AppendLine(); 500 | 501 | break; 502 | } 503 | } 504 | } 505 | 506 | return builder.ToString(); 507 | } 508 | 509 | #endregion 510 | } -------------------------------------------------------------------------------- /Roslyn.Testing/Analyzer/VerifyDiagnosticAnalyzerResult.cs: -------------------------------------------------------------------------------- 1 | namespace Roslyn.Testing.Analyzer; 2 | 3 | internal struct VerifyDiagnosticAnalyzerResult 4 | { 5 | public bool Success { get; private set; } 6 | 7 | public string ErrorMessage { get; private set; } 8 | 9 | public static VerifyDiagnosticAnalyzerResult Ok() => new() 10 | { 11 | Success = true 12 | }; 13 | 14 | public static VerifyDiagnosticAnalyzerResult Fail(string message) => new() 15 | { 16 | Success = false, 17 | ErrorMessage = message 18 | }; 19 | } -------------------------------------------------------------------------------- /Roslyn.Testing/CodeFix/CSharpCodeFixProviderTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CodeFixes; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | using Roslyn.Testing.Model; 7 | using Shouldly; 8 | 9 | namespace Roslyn.Testing.CodeFix; 10 | 11 | public abstract class CSharpCodeFixProviderTest : FileReaderTest 12 | where TDiagnosticAnalyzer : DiagnosticAnalyzer, new() 13 | where TCodeFixProvider : CodeFixProvider, new() 14 | { 15 | #region To be implemented by Test classes 16 | 17 | /// 18 | public override string Filepath => 19 | _codeFixProvider.GetType() 20 | .Name; 21 | 22 | /// 23 | public override string PathToTestData => "./TestData/CodeFix/"; 24 | 25 | protected virtual IEnumerable GetAdditionalReferences() => Enumerable.Empty(); 26 | 27 | #endregion 28 | 29 | private readonly TCodeFixProvider _codeFixProvider; 30 | 31 | private readonly TDiagnosticAnalyzer _diagnosticAnalyzer; 32 | 33 | protected CSharpCodeFixProviderTest() 34 | { 35 | _diagnosticAnalyzer = new(); 36 | _codeFixProvider = new(); 37 | } 38 | 39 | /// 40 | /// Called to test a C# codefix when applied on the inputted string as a source 41 | /// 42 | /// 43 | /// A class in the form of a string before the CodeFix was 44 | /// applied to it 45 | /// 46 | /// 47 | /// A class in the form of a string after the CodeFix was 48 | /// applied to it 49 | /// 50 | /// 51 | /// Index determining which codefix to apply if there 52 | /// are multiple 53 | /// 54 | /// 55 | /// A bool controlling whether or not the test will fail if the CodeFix introduces 56 | /// other warnings after being applied 57 | /// 58 | protected void VerifyFix(string oldSource, 59 | string newSource, 60 | int? codeFixIndex = null, 61 | bool allowNewCompilerDiagnostics = false) 62 | { 63 | var result = _codeFixProvider.VerifyFix(LanguageNames.CSharp, 64 | _diagnosticAnalyzer, 65 | oldSource, 66 | newSource, 67 | codeFixIndex, 68 | allowNewCompilerDiagnostics, 69 | GetAdditionalReferences()); 70 | 71 | if (result.Success) 72 | { 73 | return; 74 | } 75 | 76 | if (string.IsNullOrEmpty(result.ErrorMessage)) 77 | { 78 | result.NewSource.ShouldContainWithoutWhitespace(result.ActualSource); 79 | } else 80 | { 81 | result.Success.ShouldBeTrue(result.ErrorMessage); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /Roslyn.Testing/CodeFix/CodeFixProviderTestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using Microsoft.CodeAnalysis; 6 | using Microsoft.CodeAnalysis.CodeActions; 7 | using Microsoft.CodeAnalysis.CodeFixes; 8 | using Microsoft.CodeAnalysis.Diagnostics; 9 | using Microsoft.CodeAnalysis.Formatting; 10 | using Microsoft.CodeAnalysis.Simplification; 11 | using Roslyn.Testing.Analyzer; 12 | 13 | namespace Roslyn.Testing.CodeFix; 14 | 15 | internal static class CodeFixProviderTestExtensions 16 | { 17 | /// 18 | /// Apply the inputted CodeAction to the inputted document. 19 | /// Meant to be used to apply codefixes. 20 | /// 21 | /// The Document to apply the fix on 22 | /// A CodeAction that will be applied to the Document. 23 | /// A Document with the changes from the CodeAction 24 | public static Document ApplyFix(this Document document, CodeAction codeAction) 25 | { 26 | var operations = codeAction.GetOperationsAsync(CancellationToken.None) 27 | .GetAwaiter() 28 | .GetResult(); 29 | 30 | var solution = operations.OfType() 31 | .Single() 32 | .ChangedSolution; 33 | 34 | return solution.GetDocument(document.Id); 35 | } 36 | 37 | #region [Actual comparisons and verifications] 38 | 39 | /// 40 | /// General verifier for codefixes. 41 | /// Creates a Document from the source string, then gets diagnostics on it and 42 | /// applies the relevant codefixes. 43 | /// Then gets the string after the codefix is applied and compares it with the 44 | /// expected result. 45 | /// Note: If any codefix causes new diagnostics to show up, the test fails unless 46 | /// allowNewCompilerDiagnostics is set to true. 47 | /// 48 | /// The language the source code is in 49 | /// The analyzer to be applied to the source code 50 | /// 51 | /// The codefix to be applied to the code wherever 52 | /// the relevant Diagnostic is found 53 | /// 54 | /// 55 | /// A class in the form of a string before the CodeFix was 56 | /// applied to it 57 | /// 58 | /// 59 | /// A class in the form of a string after the CodeFix was 60 | /// applied to it 61 | /// 62 | /// 63 | /// Index determining which codefix to apply if there 64 | /// are multiple 65 | /// 66 | /// 67 | /// A bool controlling whether or not the test will fail if the CodeFix introduces 68 | /// other warnings after being applied 69 | /// 70 | /// 71 | public static VerifyCodeFixProviderResult VerifyFix(this CodeFixProvider codeFixProvider, 72 | string language, 73 | DiagnosticAnalyzer analyzer, 74 | string oldSource, 75 | string newSource, 76 | int? codeFixIndex, 77 | bool allowNewCompilerDiagnostics, 78 | IEnumerable additionalReferences = null) 79 | { 80 | var document = DiagnosticAnalyzerTestExtensions.CreateDocument(oldSource, language, additionalReferences); 81 | 82 | var analyzerDiagnostics = analyzer.GetSortedDiagnosticsFromDocuments(new[] 83 | { 84 | document 85 | }); 86 | 87 | var compilerDiagnostics = document.GetCompilerDiagnostics(); 88 | var attempts = analyzerDiagnostics.Length; 89 | 90 | for (var i = 0; i < attempts; ++i) 91 | { 92 | var actions = new List(); 93 | var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, _) => actions.Add(a), CancellationToken.None); 94 | 95 | codeFixProvider.RegisterCodeFixesAsync(context) 96 | .GetAwaiter() 97 | .GetResult(); 98 | 99 | if (!actions.Any()) 100 | { 101 | break; 102 | } 103 | 104 | if (codeFixIndex != null) 105 | { 106 | document = document.ApplyFix(actions.ElementAt((int) codeFixIndex)); 107 | 108 | break; 109 | } 110 | 111 | document = document.ApplyFix(actions.ElementAt(0)); 112 | 113 | analyzerDiagnostics = analyzer.GetSortedDiagnosticsFromDocuments(new[] 114 | { 115 | document 116 | }); 117 | 118 | var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, document.GetCompilerDiagnostics()); 119 | 120 | //check if applying the code fix introduced any new compiler diagnostics 121 | if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) 122 | { 123 | // Format and get the compiler diagnostics again so that the locations make sense in the output 124 | document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync() 125 | .GetAwaiter() 126 | .GetResult(), 127 | Formatter.Annotation, 128 | document.Project.Solution.Workspace)); 129 | 130 | newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, document.GetCompilerDiagnostics()); 131 | var msg = GetNewCompilerDiagnosticsIntroducedMessage(document, newCompilerDiagnostics); 132 | 133 | return VerifyCodeFixProviderResult.Fail(msg); 134 | } 135 | 136 | //check if there are analyzer diagnostics left after the code fix 137 | if (!analyzerDiagnostics.Any()) 138 | { 139 | break; 140 | } 141 | } 142 | 143 | //after applying all of the code fixes, compare the resulting string to the inputted one 144 | var actual = document.GetStringFromDocument(); 145 | 146 | return newSource.Equals(actual) 147 | ? VerifyCodeFixProviderResult.Ok() 148 | : VerifyCodeFixProviderResult.Fail(newSource, actual); 149 | } 150 | 151 | private static string GetNewCompilerDiagnosticsIntroducedMessage(Document document, IEnumerable newCompilerDiagnostics) => 152 | $"Fix introduced new compiler diagnostics:{Environment.NewLine}{string.Join("{Environment.NewLine}", newCompilerDiagnostics.Select(d => d.ToString()))}{Environment.NewLine}{Environment.NewLine}New document:{Environment.NewLine}{document.GetSyntaxRootAsync().GetAwaiter().GetResult().ToFullString()}{Environment.NewLine}"; 153 | 154 | /// 155 | /// Get the existing compiler diagnostics on the inputted document. 156 | /// 157 | /// 158 | /// The Document to run the compiler diagnostic analyzers 159 | /// on 160 | /// 161 | /// The compiler diagnostics that were found in the code 162 | private static IEnumerable GetCompilerDiagnostics(this Document document) => document.GetSemanticModelAsync() 163 | .GetAwaiter() 164 | .GetResult() 165 | .GetDiagnostics(); 166 | 167 | /// 168 | /// Given a document, turn it into a string based on the syntax root 169 | /// 170 | /// The Document to be converted to a string 171 | /// A string containing the syntax of the Document after formatting 172 | public static string GetStringFromDocument(this Document document) 173 | { 174 | var simplifiedDoc = Simplifier.ReduceAsync(document, Simplifier.Annotation) 175 | .GetAwaiter() 176 | .GetResult(); 177 | 178 | var root = simplifiedDoc.GetSyntaxRootAsync() 179 | .GetAwaiter() 180 | .GetResult(); 181 | 182 | root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace); 183 | 184 | return root.GetText() 185 | .ToString(); 186 | } 187 | 188 | /// 189 | /// Compare two collections of Diagnostics,and return a list of any new diagnostics 190 | /// that appear only in the second collection. 191 | /// Note: Considers Diagnostics to be the same if they have the same Ids. In the 192 | /// case of multiple diagnostics with the same Id in a row, 193 | /// this method may not necessarily return the new one. 194 | /// 195 | /// 196 | /// The Diagnostics that existed in the code before the 197 | /// CodeFix was applied 198 | /// 199 | /// 200 | /// The Diagnostics that exist in the code after the 201 | /// CodeFix was applied 202 | /// 203 | /// 204 | /// A list of Diagnostics that only surfaced in the code after the CodeFix was 205 | /// applied 206 | /// 207 | private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, 208 | IEnumerable newDiagnostics) 209 | { 210 | var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start) 211 | .ToArray(); 212 | 213 | var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start) 214 | .ToArray(); 215 | 216 | var oldIndex = 0; 217 | var newIndex = 0; 218 | 219 | while (newIndex < newArray.Length) 220 | { 221 | if (oldIndex < oldArray.Length 222 | && oldArray[oldIndex] 223 | .Id 224 | == newArray[newIndex] 225 | .Id) 226 | { 227 | ++oldIndex; 228 | ++newIndex; 229 | } else 230 | { 231 | yield return newArray[newIndex++]; 232 | } 233 | } 234 | } 235 | 236 | #endregion 237 | } -------------------------------------------------------------------------------- /Roslyn.Testing/CodeFix/VerifyCodeFixProviderResult.cs: -------------------------------------------------------------------------------- 1 | namespace Roslyn.Testing.CodeFix; 2 | 3 | internal struct VerifyCodeFixProviderResult 4 | { 5 | public bool Success { get; private set; } 6 | 7 | public string ErrorMessage { get; private set; } 8 | 9 | public string ActualSource { get; private set; } 10 | 11 | public string NewSource { get; private set; } 12 | 13 | public static VerifyCodeFixProviderResult Ok() => new() 14 | { 15 | Success = true 16 | }; 17 | 18 | public static VerifyCodeFixProviderResult Fail(string message) => new() 19 | { 20 | Success = false, 21 | ErrorMessage = message 22 | }; 23 | 24 | internal static VerifyCodeFixProviderResult Fail(string newSource, string actualSource) => new() 25 | { 26 | Success = false, 27 | NewSource = newSource, 28 | ActualSource = actualSource 29 | }; 30 | } -------------------------------------------------------------------------------- /Roslyn.Testing/Model/DiagnosticResult.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace Roslyn.Testing.Model; 4 | 5 | /// 6 | /// Struct that stores information about a Diagnostic appearing in a source 7 | /// 8 | public struct DiagnosticResult 9 | { 10 | private DiagnosticResultLocation[] _locations; 11 | 12 | public DiagnosticResultLocation[] Locations 13 | { 14 | get => 15 | _locations 16 | ?? (_locations = new DiagnosticResultLocation[] 17 | { 18 | }); 19 | 20 | set => _locations = value; 21 | } 22 | 23 | public DiagnosticSeverity Severity { get; set; } 24 | 25 | public string Id { get; set; } 26 | 27 | public string Message { get; set; } 28 | 29 | public string Path => 30 | Locations.Length > 0 31 | ? Locations[0] 32 | .Path 33 | : ""; 34 | 35 | public int Line => 36 | Locations.Length > 0 37 | ? Locations[0] 38 | .Line 39 | : -1; 40 | 41 | public int Column => 42 | Locations.Length > 0 43 | ? Locations[0] 44 | .Column 45 | : -1; 46 | } -------------------------------------------------------------------------------- /Roslyn.Testing/Model/DiagnosticResultLocation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Roslyn.Testing.Model; 4 | 5 | /// 6 | /// Location where the diagnostic appears, as determined by path, line number, and 7 | /// column number. 8 | /// 9 | public struct DiagnosticResultLocation 10 | { 11 | public DiagnosticResultLocation(string path, int line, int column) 12 | { 13 | if (line < -1) 14 | { 15 | throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1"); 16 | } 17 | 18 | if (column < -1) 19 | { 20 | throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1"); 21 | } 22 | 23 | Path = path; 24 | Line = line; 25 | Column = column; 26 | } 27 | 28 | public string Path { get; } 29 | 30 | public int Line { get; } 31 | 32 | public int Column { get; } 33 | } -------------------------------------------------------------------------------- /Roslyn.Testing/Model/FileReaderTest.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using JetBrains.Annotations; 3 | 4 | namespace Roslyn.Testing.Model; 5 | 6 | public abstract class FileReaderTest 7 | { 8 | public abstract string Filepath { get; } 9 | 10 | [UsedImplicitly] 11 | public abstract string PathToTestData { get; } 12 | 13 | public string ReadFile(string filename) 14 | { 15 | var pathToFile = Path.Combine(PathToTestData, Filepath, filename); 16 | 17 | return File.ReadAllText(pathToFile); 18 | } 19 | } -------------------------------------------------------------------------------- /Roslyn.Testing/Roslyn.Testing.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/Analyzers/DirectoryAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using Microsoft.CodeAnalysis; 4 | using Roslyn.Testing.Analyzer; 5 | using Roslyn.Testing.Model; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.Analyzers; 9 | 10 | public class DirectoryAnalyzerTests : CSharpDiagnosticAnalyzerTest 11 | { 12 | [Theory] 13 | [InlineData("Valid.txt")] 14 | [InlineData("FalsePositive.txt")] 15 | [InlineData("ModelObjectFalsePositive.txt")] 16 | [InlineData("UsingStaticFalsePositive.txt")] 17 | public void Analyzer_is_not_triggered(string filename) 18 | { 19 | var source = ReadFile(filename); 20 | VerifyNoDiagnosticTriggered(source); 21 | } 22 | 23 | [Theory] 24 | [InlineData("WithOutFileSystem.txt", 15, 4)] 25 | public void Analyzer_is_triggered(string filename, int diagnosticLine, int diagnosticColumn) 26 | { 27 | var source = ReadFile(filename); 28 | 29 | var expectedDiagnostic = new DiagnosticResult 30 | { 31 | Id = DirectoryAnalyzer.DiagnosticId, 32 | Message = DirectoryAnalyzer.MessageFormat, 33 | Severity = DiagnosticSeverity.Warning, 34 | Locations = new[] 35 | { 36 | new DiagnosticResultLocation("Test0.cs", diagnosticLine, diagnosticColumn) 37 | } 38 | }; 39 | 40 | VerifyDiagnostic(source, expectedDiagnostic); 41 | } 42 | 43 | [Fact] 44 | public void Empty_source_code_does_not_trigger_analyzer() 45 | { 46 | var source = string.Empty; 47 | VerifyNoDiagnosticTriggered(source); 48 | } 49 | 50 | protected override IEnumerable GetAdditionalReferences() => new[] 51 | { 52 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 53 | }; 54 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/Analyzers/DirectoryInfoAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using Microsoft.CodeAnalysis; 4 | using Roslyn.Testing.Analyzer; 5 | using Roslyn.Testing.Model; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.Analyzers; 9 | 10 | public class DirectoryInfoAnalyzerTests : CSharpDiagnosticAnalyzerTest 11 | { 12 | [Theory] 13 | [InlineData("Valid.txt")] 14 | [InlineData("ModelObjectFalsePositive.txt")] 15 | [InlineData("UsingStaticFalsePositive.txt")] 16 | public void Analyzer_is_not_triggered(string filename) 17 | { 18 | var source = ReadFile(filename); 19 | VerifyNoDiagnosticTriggered(source); 20 | } 21 | 22 | [Theory] 23 | [InlineData("WithOutFileSystem.txt", 15, 28)] 24 | [InlineData("StaticInvocation.txt", 15, 40)] 25 | public void Analyzer_is_triggered(string filename, int diagnosticLine, int diagnosticColumn) 26 | { 27 | var source = ReadFile(filename); 28 | 29 | var expectedDiagnostic = new DiagnosticResult 30 | { 31 | Id = DirectoryInfoAnalyzer.DiagnosticId, 32 | Message = DirectoryInfoAnalyzer.MessageFormat, 33 | Severity = DiagnosticSeverity.Warning, 34 | Locations = new[] 35 | { 36 | new DiagnosticResultLocation("Test0.cs", diagnosticLine, diagnosticColumn) 37 | } 38 | }; 39 | 40 | VerifyDiagnostic(source, expectedDiagnostic); 41 | } 42 | 43 | [Fact] 44 | public void Empty_source_code_does_not_trigger_analyzer() 45 | { 46 | var source = string.Empty; 47 | VerifyNoDiagnosticTriggered(source); 48 | } 49 | 50 | protected override IEnumerable GetAdditionalReferences() => new[] 51 | { 52 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 53 | }; 54 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/Analyzers/FileAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using Microsoft.CodeAnalysis; 4 | using Roslyn.Testing.Analyzer; 5 | using Roslyn.Testing.Model; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.Analyzers; 9 | 10 | public class FileAnalyzerTests : CSharpDiagnosticAnalyzerTest 11 | { 12 | [Theory] 13 | [InlineData("Valid.txt")] 14 | [InlineData("UsingStaticFalsePositive.txt")] 15 | public void Analyzer_is_not_triggered(string filename) 16 | { 17 | var source = ReadFile(filename); 18 | VerifyNoDiagnosticTriggered(source); 19 | } 20 | 21 | [Theory] 22 | [InlineData("WithOutFileSystem.txt", 15, 4)] 23 | public void Analyzer_is_triggered(string filename, int diagnosticLine, int diagnosticColumn) 24 | { 25 | var source = ReadFile(filename); 26 | 27 | var expectedDiagnostic = new DiagnosticResult 28 | { 29 | Id = Constants.Io0002, 30 | Message = FileAnalyzer.MessageFormat, 31 | Severity = DiagnosticSeverity.Warning, 32 | Locations = new[] 33 | { 34 | new DiagnosticResultLocation("Test0.cs", diagnosticLine, diagnosticColumn) 35 | } 36 | }; 37 | 38 | VerifyDiagnostic(source, expectedDiagnostic); 39 | } 40 | 41 | [Fact] 42 | public void Empty_source_code_does_not_trigger_analyzer() 43 | { 44 | var source = string.Empty; 45 | VerifyNoDiagnosticTriggered(source); 46 | } 47 | 48 | protected override IEnumerable GetAdditionalReferences() => new[] 49 | { 50 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 51 | }; 52 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/Analyzers/FileInfoAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using Microsoft.CodeAnalysis; 4 | using Roslyn.Testing.Analyzer; 5 | using Roslyn.Testing.Model; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.Analyzers; 9 | 10 | public class FileInfoAnalyzerTests : CSharpDiagnosticAnalyzerTest 11 | { 12 | [Theory] 13 | [InlineData("Valid.txt")] 14 | [InlineData("UsingStaticFalsePositive.txt")] 15 | public void Analyzer_is_not_triggered(string filename) 16 | { 17 | var source = ReadFile(filename); 18 | VerifyNoDiagnosticTriggered(source); 19 | } 20 | 21 | [Theory] 22 | [InlineData("WithOutFileSystem.txt", 15, 23)] 23 | public void Analyzer_is_triggered(string filename, int diagnosticLine, int diagnosticColumn) 24 | { 25 | var source = ReadFile(filename); 26 | 27 | var expectedDiagnostic = new DiagnosticResult 28 | { 29 | Id = FileInfoAnalyzer.DiagnosticId, 30 | Message = FileInfoAnalyzer.MessageFormat, 31 | Severity = DiagnosticSeverity.Warning, 32 | Locations = new[] 33 | { 34 | new DiagnosticResultLocation("Test0.cs", diagnosticLine, diagnosticColumn) 35 | } 36 | }; 37 | 38 | VerifyDiagnostic(source, expectedDiagnostic); 39 | } 40 | 41 | [Fact] 42 | public void Empty_source_code_does_not_trigger_analyzer() 43 | { 44 | var source = string.Empty; 45 | VerifyNoDiagnosticTriggered(source); 46 | } 47 | 48 | protected override IEnumerable GetAdditionalReferences() => new[] 49 | { 50 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 51 | }; 52 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/Analyzers/FileStreamAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using Microsoft.CodeAnalysis; 4 | using Roslyn.Testing.Analyzer; 5 | using Roslyn.Testing.Model; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.Analyzers; 9 | 10 | public class FileStreamAnalyzerTests : CSharpDiagnosticAnalyzerTest 11 | { 12 | [Theory] 13 | [InlineData("Valid.txt")] 14 | [InlineData("UsingStaticFalsePositive.txt")] 15 | public void Analyzer_is_not_triggered(string filename) 16 | { 17 | var source = ReadFile(filename); 18 | VerifyNoDiagnosticTriggered(source); 19 | } 20 | 21 | [Theory] 22 | [InlineData("WithOutFileSystem.txt", 15, 32)] 23 | public void Analyzer_is_triggered(string filename, int diagnosticLine, int diagnosticColumn) 24 | { 25 | var source = ReadFile(filename); 26 | 27 | var expectedDiagnostic = new DiagnosticResult 28 | { 29 | Id = FileStreamAnalyzer.DiagnosticId, 30 | Message = FileStreamAnalyzer.MessageFormat, 31 | Severity = DiagnosticSeverity.Warning, 32 | Locations = new[] 33 | { 34 | new DiagnosticResultLocation("Test0.cs", diagnosticLine, diagnosticColumn) 35 | } 36 | }; 37 | 38 | VerifyDiagnostic(source, expectedDiagnostic); 39 | } 40 | 41 | [Fact] 42 | public void Empty_source_code_does_not_trigger_analyzer() 43 | { 44 | var source = string.Empty; 45 | VerifyNoDiagnosticTriggered(source); 46 | } 47 | 48 | protected override IEnumerable GetAdditionalReferences() => new[] 49 | { 50 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 51 | }; 52 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/Analyzers/PathAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using Microsoft.CodeAnalysis; 4 | using Roslyn.Testing.Analyzer; 5 | using Roslyn.Testing.Model; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.Analyzers; 9 | 10 | public class PathAnalyzerTests : CSharpDiagnosticAnalyzerTest 11 | { 12 | [Theory] 13 | [InlineData("Valid.txt")] 14 | [InlineData("UsingStaticFalsePositive.txt")] 15 | [InlineData("UsingStaticLinqFalsePositive.txt")] 16 | public void Analyzer_is_not_triggered(string filename) 17 | { 18 | var source = ReadFile(filename); 19 | VerifyNoDiagnosticTriggered(source); 20 | } 21 | 22 | [Theory] 23 | [InlineData("WithOutFileSystem.txt", 15, 4)] 24 | public void Analyzer_is_triggered(string filename, int diagnosticLine, int diagnosticColumn) 25 | { 26 | var source = ReadFile(filename); 27 | 28 | var expectedDiagnostic = new DiagnosticResult 29 | { 30 | Id = PathAnalyzer.DiagnosticId, 31 | Message = PathAnalyzer.MessageFormat, 32 | Severity = DiagnosticSeverity.Warning, 33 | Locations = new[] 34 | { 35 | new DiagnosticResultLocation("Test0.cs", diagnosticLine, diagnosticColumn) 36 | } 37 | }; 38 | 39 | VerifyDiagnostic(source, expectedDiagnostic); 40 | } 41 | 42 | [Fact] 43 | public void Empty_source_code_does_not_trigger_analyzer() 44 | { 45 | var source = string.Empty; 46 | VerifyNoDiagnosticTriggered(source); 47 | } 48 | 49 | protected override IEnumerable GetAdditionalReferences() => new[] 50 | { 51 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 52 | }; 53 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/Analyzers/StreamReaderAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using Microsoft.CodeAnalysis; 4 | using Roslyn.Testing.Analyzer; 5 | using Roslyn.Testing.Model; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.Analyzers; 9 | 10 | public class StreamReaderAnalyzerTests : CSharpDiagnosticAnalyzerTest 11 | { 12 | [Theory] 13 | [InlineData("UsingStream.txt")] 14 | public void Analyzer_is_not_triggered(string filename) 15 | { 16 | var source = ReadFile(filename); 17 | VerifyNoDiagnosticTriggered(source); 18 | } 19 | 20 | [Theory] 21 | [InlineData("UsingFilename.txt", 15, 19)] 22 | [InlineData("UsingFilenameAndOtherParameters.txt", 16, 19)] 23 | public void Analyzer_is_triggered(string filename, int diagnosticLine, int diagnosticColumn) 24 | { 25 | var source = ReadFile(filename); 26 | 27 | var expectedDiagnostic = new DiagnosticResult 28 | { 29 | Id = StreamReaderAnalyzer.DiagnosticId, 30 | Message = StreamReaderAnalyzer.MessageFormat, 31 | Severity = DiagnosticSeverity.Warning, 32 | Locations = new[] 33 | { 34 | new DiagnosticResultLocation("Test0.cs", diagnosticLine, diagnosticColumn) 35 | } 36 | }; 37 | 38 | VerifyDiagnostic(source, expectedDiagnostic); 39 | } 40 | 41 | [Fact] 42 | public void Empty_source_code_does_not_trigger_analyzer() 43 | { 44 | var source = string.Empty; 45 | VerifyNoDiagnosticTriggered(source); 46 | } 47 | 48 | protected override IEnumerable GetAdditionalReferences() => new[] 49 | { 50 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 51 | }; 52 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/Analyzers/StreamWriterAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using Microsoft.CodeAnalysis; 4 | using Roslyn.Testing.Analyzer; 5 | using Roslyn.Testing.Model; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.Analyzers; 9 | 10 | public class StreamWriterAnalyzerTests : CSharpDiagnosticAnalyzerTest 11 | { 12 | [Theory] 13 | [InlineData("UsingStream.txt")] 14 | public void Analyzer_is_not_triggered(string filename) 15 | { 16 | var source = ReadFile(filename); 17 | VerifyNoDiagnosticTriggered(source); 18 | } 19 | 20 | [Theory] 21 | [InlineData("UsingFilename.txt", 15, 19)] 22 | [InlineData("UsingFilenameAndOtherParameters.txt", 16, 19)] 23 | public void Analyzer_is_triggered(string filename, int diagnosticLine, int diagnosticColumn) 24 | { 25 | var source = ReadFile(filename); 26 | 27 | var expectedDiagnostic = new DiagnosticResult 28 | { 29 | Id = StreamWriterAnalyzer.DiagnosticId, 30 | Message = StreamWriterAnalyzer.MessageFormat, 31 | Severity = DiagnosticSeverity.Warning, 32 | Locations = new[] 33 | { 34 | new DiagnosticResultLocation("Test0.cs", diagnosticLine, diagnosticColumn) 35 | } 36 | }; 37 | 38 | VerifyDiagnostic(source, expectedDiagnostic); 39 | } 40 | 41 | [Fact] 42 | public void Empty_source_code_does_not_trigger_analyzer() 43 | { 44 | var source = string.Empty; 45 | VerifyNoDiagnosticTriggered(source); 46 | } 47 | 48 | protected override IEnumerable GetAdditionalReferences() => new[] 49 | { 50 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 51 | }; 52 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/CodeFixes/DirectoryCodeFixTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using System.IO.Abstractions.Analyzers.CodeFixes; 4 | using Microsoft.CodeAnalysis; 5 | using Roslyn.Testing.CodeFix; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.CodeFixes; 9 | 10 | public class DirectoryCodeFixTests : 11 | CSharpCodeFixProviderTest 12 | { 13 | [Theory] 14 | [InlineData("BeforeFix.txt", "AfterFix.txt")] 15 | public void CodeFix(string sourceBefore, string sourceAfter) 16 | { 17 | var sourceBeforeFix = ReadFile(sourceBefore); 18 | var sourceAfterFix = ReadFile(sourceAfter); 19 | VerifyFix(sourceBeforeFix, sourceAfterFix, 0, true); 20 | } 21 | 22 | protected override IEnumerable GetAdditionalReferences() => new[] 23 | { 24 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 25 | }; 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/CodeFixes/DirectoryInfoCodeFixTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using System.IO.Abstractions.Analyzers.CodeFixes; 4 | using Microsoft.CodeAnalysis; 5 | using Roslyn.Testing.CodeFix; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.CodeFixes; 9 | 10 | public class DirectoryInfoCodeFixTests : 11 | CSharpCodeFixProviderTest 12 | { 13 | [Theory] 14 | [InlineData("BeforeFix.txt", "AfterFix.txt")] 15 | public void CodeFix(string sourceBefore, string sourceAfter) 16 | { 17 | var sourceBeforeFix = ReadFile(sourceBefore); 18 | var sourceAfterFix = ReadFile(sourceAfter); 19 | VerifyFix(sourceBeforeFix, sourceAfterFix, 0, true); 20 | } 21 | 22 | protected override IEnumerable GetAdditionalReferences() => new[] 23 | { 24 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 25 | }; 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/CodeFixes/FileCodeFixTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using System.IO.Abstractions.Analyzers.CodeFixes; 4 | using Microsoft.CodeAnalysis; 5 | using Roslyn.Testing.CodeFix; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.CodeFixes; 9 | 10 | public class FileCodeFixTests : 11 | CSharpCodeFixProviderTest 12 | { 13 | [Theory] 14 | [InlineData("BeforeFix.txt", "AfterFix.txt")] 15 | public void CodeFix(string sourceBefore, string sourceAfter) 16 | { 17 | var sourceBeforeFix = ReadFile(sourceBefore); 18 | var sourceAfterFix = ReadFile(sourceAfter); 19 | VerifyFix(sourceBeforeFix, sourceAfterFix, 0, true); 20 | } 21 | 22 | protected override IEnumerable GetAdditionalReferences() => new[] 23 | { 24 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 25 | }; 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/CodeFixes/FileInfoCodeFixTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using System.IO.Abstractions.Analyzers.CodeFixes; 4 | using Microsoft.CodeAnalysis; 5 | using Roslyn.Testing.CodeFix; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.CodeFixes; 9 | 10 | public class FileInfoCodeFixTests : 11 | CSharpCodeFixProviderTest 12 | { 13 | [Theory] 14 | [InlineData("BeforeFix.txt", "AfterFix.txt")] 15 | public void CodeFix(string sourceBefore, string sourceAfter) 16 | { 17 | var sourceBeforeFix = ReadFile(sourceBefore); 18 | var sourceAfterFix = ReadFile(sourceAfter); 19 | VerifyFix(sourceBeforeFix, sourceAfterFix, 0, true); 20 | } 21 | 22 | protected override IEnumerable GetAdditionalReferences() => new[] 23 | { 24 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 25 | }; 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/CodeFixes/FileServiceConstructorInitialCodeFixTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using System.IO.Abstractions.Analyzers.CodeFixes; 4 | using Microsoft.CodeAnalysis; 5 | using Roslyn.Testing.CodeFix; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.CodeFixes; 9 | 10 | public class FileServiceConstructorInitialCodeFixTests : 11 | CSharpCodeFixProviderTest 12 | { 13 | [Theory] 14 | [InlineData("BeforeFix.txt", "AfterFix.txt")] 15 | [InlineData("BeforeFixWithCustomField.txt", "AfterFixWithCustomField.txt")] 16 | public void CodeFix(string sourceBefore, string sourceAfter) 17 | { 18 | var sourceBeforeFix = ReadFile(sourceBefore); 19 | var sourceAfterFix = ReadFile(sourceAfter); 20 | VerifyFix(sourceBeforeFix, sourceAfterFix, 0, true); 21 | } 22 | 23 | protected override IEnumerable GetAdditionalReferences() => new[] 24 | { 25 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 26 | }; 27 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/CodeFixes/FileServiceInterfaceInjectionCodeFixTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using System.IO.Abstractions.Analyzers.CodeFixes; 4 | using Microsoft.CodeAnalysis; 5 | using Roslyn.Testing.CodeFix; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.CodeFixes; 9 | 10 | public class FileServiceInterfaceInjectionCodeFixTests : 11 | CSharpCodeFixProviderTest 12 | { 13 | [Theory] 14 | [InlineData("BeforeFix.txt", "AfterFix.txt")] 15 | [InlineData("BeforeFixWithoutConstructor.txt", "AfterFix.txt")] 16 | [InlineData("BeforeFixExistsAbstractionsUsing.txt", "AfterFix.txt")] 17 | [InlineData("BeforeFixExistingConstructorParameter.txt", "AfterFix.txt")] 18 | [InlineData("BeforeFixExistingConstructorParameterUniqName.txt", "AfterFixConstructorParameterUniqName.txt")] 19 | [InlineData("BeforeFixContainsAssignment.txt", "AfterFixContainsAssignment.txt")] 20 | public void CodeFix(string sourceBefore, string sourceAfter) 21 | { 22 | var sourceBeforeFix = ReadFile(sourceBefore); 23 | var sourceAfterFix = ReadFile(sourceAfter); 24 | VerifyFix(sourceBeforeFix, sourceAfterFix, 0, true); 25 | } 26 | 27 | protected override IEnumerable GetAdditionalReferences() => new[] 28 | { 29 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 30 | }; 31 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/CodeFixes/FileStreamCodeFixTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using System.IO.Abstractions.Analyzers.CodeFixes; 4 | using Microsoft.CodeAnalysis; 5 | using Roslyn.Testing.CodeFix; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.CodeFixes; 9 | 10 | public class FileStreamCodeFixTests : 11 | CSharpCodeFixProviderTest 12 | { 13 | [Theory] 14 | [InlineData("BeforeFix.txt", "AfterFix.txt")] 15 | public void CodeFix(string sourceBefore, string sourceAfter) 16 | { 17 | var sourceBeforeFix = ReadFile(sourceBefore); 18 | var sourceAfterFix = ReadFile(sourceAfter); 19 | VerifyFix(sourceBeforeFix, sourceAfterFix, 0, true); 20 | } 21 | 22 | protected override IEnumerable GetAdditionalReferences() => new[] 23 | { 24 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 25 | }; 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/CodeFixes/PathCodeFixTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using System.IO.Abstractions.Analyzers.CodeFixes; 4 | using Microsoft.CodeAnalysis; 5 | using Roslyn.Testing.CodeFix; 6 | using Xunit; 7 | 8 | namespace System.IO.Abstractions.Analyzers.Tests.CodeFixes; 9 | 10 | public class PathCodeFixTests : 11 | CSharpCodeFixProviderTest 12 | { 13 | [Theory] 14 | [InlineData("BeforeFix.txt", "AfterFix.txt")] 15 | public void CodeFix(string sourceBefore, string sourceAfter) 16 | { 17 | var sourceBeforeFix = ReadFile(sourceBefore); 18 | var sourceAfterFix = ReadFile(sourceAfter); 19 | VerifyFix(sourceBeforeFix, sourceAfterFix, 0, true); 20 | } 21 | 22 | protected override IEnumerable GetAdditionalReferences() => new[] 23 | { 24 | MetadataReference.CreateFromFile(typeof(IFileSystem).Assembly.Location) 25 | }; 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/System.IO.Abstractions.Analyzers.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/DirectoryAnalyzer/FalsePositive.txt: -------------------------------------------------------------------------------- 1 | namespace Foobar 2 | { 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.IO.Abstractions; 6 | using System.Linq; 7 | 8 | internal static class DirectoryExtensions 9 | { 10 | public static IEnumerable GetJpegs(this IDirectory directory, string path) => directory.EnumerateFiles(path, "*.jpeg", SearchOption.TopDirectoryOnly).Union( 11 | directory.EnumerateFiles(path, "*.jpg", SearchOption.TopDirectoryOnly)); 12 | } 13 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/DirectoryAnalyzer/ModelObjectFalsePositive.txt: -------------------------------------------------------------------------------- 1 | namespace Foobar 2 | { 3 | internal class Directory 4 | { 5 | public string Name { get; set; } 6 | 7 | public string Path { get; set; } 8 | } 9 | 10 | internal class Progam 11 | { 12 | public int Main(string[] args) 13 | { 14 | var directory = new Directory() { Path = "c:\Somewhere", Name = "Somewhere" }; 15 | retun 0; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/DirectoryAnalyzer/UsingStaticFalsePositive.txt: -------------------------------------------------------------------------------- 1 | namespace SomeNameSpace.Mocking 2 | { 3 | using System.IO.Abstractions; 4 | public static class FileSystemProxy 5 | { 6 | internal static IDirectory Directory { get; } 7 | } 8 | } 9 | 10 | namespace SomeNameSpace 11 | { 12 | using static SomeNameSpace.Mocking.FileSystemProxy; 13 | public class UsingStaticFalsePositive 14 | { 15 | public UsingStaticFalsePositive() 16 | { 17 | } 18 | 19 | public void SomeMethod() 20 | { 21 | const string path = "C:\\temp"; 22 | 23 | Directory.Delete(path); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/DirectoryAnalyzer/Valid.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class Valid 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public Valid(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/DirectoryAnalyzer/WithOutFileSystem.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public WithOutFileSystem() 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | const string path = "C:\\temp"; 14 | 15 | Directory.Remove(path); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/DirectoryInfoAnalyzer/ModelObjectFalsePositive.txt: -------------------------------------------------------------------------------- 1 | namespace Foobar 2 | { 3 | internal class DirectoryInfo 4 | { 5 | public string Name { get; set; } 6 | 7 | public string Path { get; set; } 8 | } 9 | 10 | internal class Progam 11 | { 12 | public int Main(string[] args) 13 | { 14 | var directory = new DirectoryInfo() { Path = "c:\Somewhere", Name = "Somewhere" }; 15 | retun 0; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/DirectoryInfoAnalyzer/StaticInvocation.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public WithOutFileSystem() 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | const string path = "C:\\temp.txt"; 14 | 15 | IEnumerable infos = DirectoryInfo.InternalEnumerateInfos(path, "*.txt",SearchTarget.Files, EnumerationOptions.Compatible); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/DirectoryInfoAnalyzer/UsingStaticFalsePositive.txt: -------------------------------------------------------------------------------- 1 | namespace SomeNameSpace.Mocking 2 | { 3 | using System.IO.Abstractions; 4 | public static class FileSystemProxy 5 | { 6 | internal static IDirectoryInfo DirectoryInfo { get; } 7 | } 8 | } 9 | 10 | namespace SomeNameSpace 11 | { 12 | using System.IO.Abstractions; 13 | using static SomeNameSpace.Mocking.FileSystemProxy; 14 | public class UsingStaticFalsePositive 15 | { 16 | public UsingStaticFalsePositive() 17 | { 18 | } 19 | 20 | public void SomeMethod() 21 | { 22 | const string path = "C:\\temp.txt"; 23 | IDirectoryInfo dirInfo = DirectoryInfo.FromDirectoryName(path); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/DirectoryInfoAnalyzer/Valid.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class Valid 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public Valid(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/DirectoryInfoAnalyzer/WithOutFileSystem.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public WithOutFileSystem() 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | const string path = "C:\\temp.txt"; 14 | 15 | DirectoryInfo dirInfo = new DirectoryInfo(path); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/FileAnalyzer/UsingStaticFalsePositive.txt: -------------------------------------------------------------------------------- 1 | namespace SomeNameSpace.Mocking 2 | { 3 | using System.IO.Abstractions; 4 | public static class FileSystemProxy 5 | { 6 | internal static IFile File { get; } 7 | } 8 | } 9 | 10 | namespace SomeNameSpace 11 | { 12 | using static SomeNameSpace.Mocking.FileSystemProxy; 13 | public class UsingStaticFalsePositive 14 | { 15 | public UsingStaticFalsePositive() 16 | { 17 | } 18 | 19 | public void SomeMethod() 20 | { 21 | const string filePath = "C:\\temp.txt"; 22 | 23 | File.Delete(filePath); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/FileAnalyzer/Valid.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class Valid 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public Valid(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/FileAnalyzer/WithOutFileSystem.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public WithOutFileSystem() 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | const string filePath = "C:\\temp.txt"; 14 | 15 | File.Delete(filePath); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/FileInfoAnalyzer/UsingStaticFalsePositive.txt: -------------------------------------------------------------------------------- 1 | namespace SomeNameSpace.Mocking 2 | { 3 | using System.IO.Abstractions; 4 | public static class FileSystemProxy 5 | { 6 | internal static IFileInfo FileInfo { get; } 7 | } 8 | } 9 | 10 | namespace SomeNameSpace 11 | { 12 | using System.IO.Abstractions; 13 | using static SomeNameSpace.Mocking.FileSystemProxy; 14 | public class UsingStaticFalsePositive 15 | { 16 | public UsingStaticFalsePositive() 17 | { 18 | } 19 | 20 | public void SomeMethod() 21 | { 22 | const string path = "C:\\temp.txt"; 23 | 24 | IFileInfo fileInf = FileInfo.FromFileName(path); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/FileInfoAnalyzer/Valid.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class Valid 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public Valid(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/FileInfoAnalyzer/WithOutFileSystem.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public WithOutFileSystem() 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | const string path = "C:\\temp.txt"; 14 | 15 | FileInfo fileInf = new FileInfo(path); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/FileStreamAnalyzer/UsingStaticFalsePositive.txt: -------------------------------------------------------------------------------- 1 | namespace SomeNameSpace.Mocking 2 | { 3 | using System.IO.Abstractions; 4 | public static class FileSystemProxy 5 | { 6 | internal static IFileStreamFactory FileStream { get; } 7 | } 8 | } 9 | 10 | namespace SomeNameSpace 11 | { 12 | using static SomeNameSpace.Mocking.FileSystemProxy; 13 | public class WithOutFileSystem 14 | { 15 | public UsingStaticFalsePositive() 16 | { 17 | } 18 | 19 | public void UsingStaticFalsePositive() 20 | { 21 | const string path = "C:\\temp.txt"; 22 | 23 | using (System.IO.Stream fstream = FileStream.Create(@"D:\note.dat", FileMode.OpenOrCreate)){ 24 | 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/FileStreamAnalyzer/Valid.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class Valid 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public Valid(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/FileStreamAnalyzer/WithOutFileSystem.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public WithOutFileSystem() 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | const string path = "C:\\temp"; 14 | 15 | using (FileStream fstream = new FileStream(@"D:\note.dat", FileMode.OpenOrCreate)){ 16 | 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/PathAnalyzer/UsingStaticFalsePositive.txt: -------------------------------------------------------------------------------- 1 | namespace SomeNameSpace.Mocking 2 | { 3 | using System.IO.Abstractions; 4 | public static class FileSystemProxy 5 | { 6 | internal static IPath Path { get; } 7 | } 8 | } 9 | 10 | namespace SomeNameSpace 11 | { 12 | using static SomeNameSpace.Mocking.FileSystemProxy; 13 | public class UsingStaticFalsePositive 14 | { 15 | public UsingStaticFalsePositive() 16 | { 17 | } 18 | 19 | public void SomeMethod() 20 | { 21 | const string filePath = "C:\\temp"; 22 | 23 | Path.GetTempPath(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/PathAnalyzer/UsingStaticLinqFalsePositive.txt: -------------------------------------------------------------------------------- 1 | namespace SomeNameSpace.Mocking 2 | { 3 | using System.IO.Abstractions; 4 | public static class FileSystemProxy 5 | { 6 | internal static IPath Path { get; } 7 | } 8 | } 9 | 10 | namespace SomeNameSpace 11 | { 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using static SomeNameSpace.Mocking.FileSystemProxy; 15 | public class UsingStaticFalsePositive 16 | { 17 | private IList files; 18 | public UsingStaticFalsePositive() 19 | { 20 | } 21 | 22 | public void SomeMethod() 23 | { 24 | var x = files 25 | .First(item => Path.GetFileName(item) 26 | .Equals("Pattern.txt", StringComparison.OrdinalIgnoreCase)); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/PathAnalyzer/Valid.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class Valid 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public Valid(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/PathAnalyzer/WithOutFileSystem.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public WithOutFileSystem() 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | const string filePath = "C:\\temp"; 14 | 15 | Path.GetTempPath(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/StreamReaderAnalyzer/UsingFilename.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class UsingFilename 6 | { 7 | public UsingFilename() 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | const string path = "C:\\temp.txt"; 14 | 15 | using (var x = new StreamReader(path)) 16 | { 17 | ; 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/StreamReaderAnalyzer/UsingFilenameAndOtherParameters.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace SomeNameSpace 5 | { 6 | public class UsingFilenameAndOtherParameters 7 | { 8 | public UsingFilenameAndOtherParameters() 9 | { 10 | } 11 | 12 | public void SomeMethod() 13 | { 14 | const string path = "C:\\temp.txt"; 15 | 16 | using (var x = new StreamReader(path, Encoding.UTF8)) 17 | { 18 | ; 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/StreamReaderAnalyzer/UsingStream.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Abstractions; 3 | using System.Text; 4 | 5 | namespace SomeNameSpace 6 | { 7 | public class UsingStream 8 | { 9 | private readonly IFileSystem _fileSystem; 10 | 11 | public UsingStream(IFileSystem fileSystem) 12 | { 13 | _fileSystem = fileSystem; 14 | } 15 | 16 | public void SomeMethod() 17 | { 18 | const string path = "C:\\temp.txt"; 19 | var stream = _fileSystem.FileStream.Create(path, FileMode.Open); 20 | using (var writer = new StreamReader(stream)) 21 | { 22 | ; 23 | } 24 | 25 | using (var writer = new StreamReader(stream, Encoding.UTF8)) 26 | { 27 | ; 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/StreamWriterAnalyzer/UsingFilename.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class UsingFilename 6 | { 7 | public UsingFilename() 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | const string path = "C:\\temp.txt"; 14 | 15 | using (var x = new StreamWriter(path)) 16 | { 17 | ; 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/StreamWriterAnalyzer/UsingFilenameAndOtherParameters.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace SomeNameSpace 5 | { 6 | public class UsingFilenameAndOtherParameters 7 | { 8 | public UsingFilenameAndOtherParameters() 9 | { 10 | } 11 | 12 | public void SomeMethod() 13 | { 14 | const string path = "C:\\temp.txt"; 15 | 16 | using (var x = new StreamWriter(path, true, Encoding.UTF8)) 17 | { 18 | ; 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/Analyzer/StreamWriterAnalyzer/UsingStream.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Abstractions; 3 | using System.Text; 4 | 5 | namespace SomeNameSpace 6 | { 7 | public class UsingStream 8 | { 9 | private readonly IFileSystem _fileSystem; 10 | 11 | public UsingStream(IFileSystem fileSystem) 12 | { 13 | _fileSystem = fileSystem; 14 | } 15 | 16 | public void SomeMethod() 17 | { 18 | const string path = "C:\\temp.txt"; 19 | var stream = _fileSystem.FileStream.Create(path, FileMode.Open); 20 | using (var writer = new StreamWriter(stream)) 21 | { 22 | ; 23 | } 24 | 25 | using (var writer = new StreamWriter(stream, true, Encoding.UTF8)) 26 | { 27 | ; 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/DirectoryCodeFix/AfterFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp"; 17 | 18 | new ConfigurationBuilder().SetBasePath(_fileSystem.Directory.GetCurrentDirectory()); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/DirectoryCodeFix/BeforeFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp"; 17 | 18 | new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/DirectoryInfoCodeFix/AfterFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp.txt"; 17 | 18 | DirectoryInfo dirInfo = _fileSystem.DirectoryInfo.FromDirectoryName(filePath); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/DirectoryInfoCodeFix/BeforeFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp.txt"; 17 | 18 | DirectoryInfo dirInfo = new DirectoryInfo(filePath); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileCodeFix/AfterFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp.txt"; 17 | 18 | _fSystem.File.Delete(filePath); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileCodeFix/BeforeFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp.txt"; 17 | 18 | File.Delete(filePath); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileInfoCodeFix/AfterFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp.txt"; 17 | 18 | FileInfo fileInf = _fileSystem.FileInfo.FromFileName(filePath); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileInfoCodeFix/BeforeFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp.txt"; 17 | 18 | FileInfo fileInf = new FileInfo(filePath); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceConstructorInitialCodeFix/AfterFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Abstractions; 3 | 4 | namespace SomeNameSpace 5 | { 6 | public class WithOutFileSystem 7 | { 8 | private readonly IFileSystem _fileSystem; 9 | 10 | public WithOutFileSystem() 11 | { 12 | _fileSystem = new FileSystem(); 13 | } 14 | 15 | public void SomeMethod() 16 | { 17 | var newPath = Path.Combine("C:\\", "temp.txt"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceConstructorInitialCodeFix/AfterFixWithCustomField.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Abstractions; 3 | 4 | namespace SomeNameSpace 5 | { 6 | public class WithOutFileSystem 7 | { 8 | private readonly IFileSystem _fSystem; 9 | 10 | public WithOutFileSystem() 11 | { 12 | _fSystem = new FileSystem(); 13 | } 14 | 15 | public void SomeMethod() 16 | { 17 | var newPath = Path.Combine("C:\\", "temp.txt"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceConstructorInitialCodeFix/BeforeFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public WithOutFileSystem() 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | var newPath = Path.Combine("C:\\", "temp.txt"); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceConstructorInitialCodeFix/BeforeFixWithCustomField.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fSystem; 8 | 9 | public WithOutFileSystem() 10 | { 11 | } 12 | 13 | public void SomeMethod() 14 | { 15 | var newPath = Path.Combine("C:\\", "temp.txt"); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceInterfaceInjectionCodeFix/AfterFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Abstractions; 3 | 4 | namespace SomeNameSpace 5 | { 6 | public class WithOutFileSystem 7 | { 8 | private readonly IFileSystem _fileSystem; 9 | 10 | public WithOutFileSystem(IFileSystem fileSystem) 11 | { 12 | _fileSystem = fileSystem; 13 | } 14 | 15 | public void SomeMethod() 16 | { 17 | var newPath = Path.Combine("C:\\", "temp.txt"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceInterfaceInjectionCodeFix/AfterFixConstructorParameterUniqName.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Abstractions; 3 | 4 | namespace SomeNameSpace 5 | { 6 | public class WithOutFileSystem 7 | { 8 | private readonly IFileSystem _fSystem; 9 | 10 | public WithOutFileSystem(IFileSystem fSystem) 11 | { 12 | _fSystem = fSystem; 13 | } 14 | 15 | public void SomeMethod() 16 | { 17 | var newPath = Path.Combine("C:\\", "temp.txt"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceInterfaceInjectionCodeFix/AfterFixContainsAssignment.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Abstractions; 3 | 4 | namespace SomeNameSpace 5 | { 6 | public class WithOutFileSystem 7 | { 8 | private readonly IFileSystem _fileSystem; 9 | 10 | public WithOutFileSystem(IFileSystem fileSystem) 11 | { 12 | _fileSystem = fileSystem; 13 | } 14 | 15 | public void SomeMethod() 16 | { 17 | var newPath = Path.Combine("C:\\", "temp.txt"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceInterfaceInjectionCodeFix/BeforeFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public WithOutFileSystem() 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | var newPath = Path.Combine("C:\\", "temp.txt"); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceInterfaceInjectionCodeFix/BeforeFixContainsAssignment.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public WithOutFileSystem(IFileSystem fileSystem) 8 | { 9 | _fileSystem = fileSystem; 10 | } 11 | 12 | public void SomeMethod() 13 | { 14 | var newPath = Path.Combine("C:\\", "temp.txt"); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceInterfaceInjectionCodeFix/BeforeFixExistingConstructorParameter.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public WithOutFileSystem(IFileSystem fileSystem) 8 | { 9 | } 10 | 11 | public void SomeMethod() 12 | { 13 | var newPath = Path.Combine("C:\\", "temp.txt"); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceInterfaceInjectionCodeFix/BeforeFixExistingConstructorParameterUniqName.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fSystem) 10 | { 11 | } 12 | 13 | public void SomeMethod() 14 | { 15 | var newPath = Path.Combine("C:\\", "temp.txt"); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceInterfaceInjectionCodeFix/BeforeFixExistsAbstractionsUsing.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Abstractions; 3 | 4 | namespace SomeNameSpace 5 | { 6 | public class WithOutFileSystem 7 | { 8 | public void SomeMethod() 9 | { 10 | var newPath = Path.Combine("C:\\", "temp.txt"); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileServiceInterfaceInjectionCodeFix/BeforeFixWithoutConstructor.txt: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | public void SomeMethod() 8 | { 9 | var newPath = Path.Combine("C:\\", "temp.txt"); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileStreamCodeFix/AfterFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp.txt"; 17 | 18 | using (FileStream fstream = _fileSystem.FileStream.Create(filePath, FileMode.OpenOrCreate)) 19 | { 20 | // преобразуем строку в байты 21 | byte[] array = System.Text.Encoding.Default.GetBytes(text); 22 | // запись массива байтов в файл 23 | fstream.Write(array, 0, array.Length); 24 | Console.WriteLine("Текст записан в файл"); 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/FileStreamCodeFix/BeforeFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp.txt"; 17 | 18 | using (FileStream fstream = new FileStream(filePath, FileMode.OpenOrCreate)) 19 | { 20 | // преобразуем строку в байты 21 | byte[] array = System.Text.Encoding.Default.GetBytes(text); 22 | // запись массива байтов в файл 23 | fstream.Write(array, 0, array.Length); 24 | Console.WriteLine("Текст записан в файл"); 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/PathCodeFix/AfterFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp.txt"; 17 | 18 | _fileSystem.Path.Exists(filePath); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Tests/TestData/CodeFix/PathCodeFix/BeforeFix.txt: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | 3 | namespace SomeNameSpace 4 | { 5 | public class WithOutFileSystem 6 | { 7 | private readonly IFileSystem _fileSystem; 8 | 9 | public WithOutFileSystem(IFileSystem fileSystem) 10 | { 11 | _fileSystem = fileSystem; 12 | } 13 | 14 | public void SomeMethod() 15 | { 16 | const string filePath = "C:\\temp.txt"; 17 | 18 | Path.Exists(filePath); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Vsix/System.IO.Abstractions.Analyzers.Vsix.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 15.0 6 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 7 | 8 | 9 | 14.0 10 | 11 | 12 | 13 | 14 | Debug 15 | AnyCPU 16 | AnyCPU 17 | 2.0 18 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 19 | {01F152B5-CC3C-44ED-A670-345CFCCC05DB} 20 | Library 21 | Properties 22 | System.IO.Abstractions.Analyzers.Vsix 23 | System.IO.Abstractions.Analyzers 24 | v4.6.1 25 | false 26 | false 27 | false 28 | false 29 | false 30 | false 31 | Roslyn 32 | 33 | 34 | true 35 | full 36 | false 37 | bin\Debug\ 38 | DEBUG;TRACE 39 | prompt 40 | 4 41 | 42 | 43 | pdbonly 44 | true 45 | bin\Release\ 46 | TRACE 47 | prompt 48 | 4 49 | 50 | 51 | Program 52 | $(DevEnvDir)devenv.exe 53 | /rootsuffix Roslyn 54 | 55 | 56 | 57 | Designer 58 | 59 | 60 | 61 | 62 | {8EEAAD65-1247-4503-9BBC-B3EA8BC06735} 63 | System.IO.Abstractions.Analyzers 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.Vsix/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | System.IO.Abstractions.Analyzers 6 | This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.IO.Abstractions.Analyzers", "System.IO.Abstractions.Analyzers\System.IO.Abstractions.Analyzers.csproj", "{220D98E4-533E-4A3D-82C5-02E8530FF698}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Roslyn.Testing", "Roslyn.Testing\Roslyn.Testing.csproj", "{129198CC-F00E-470E-B504-5EE8E38DE16C}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.IO.Abstractions.Analyzers.Tests", "System.IO.Abstractions.Analyzers.Tests\System.IO.Abstractions.Analyzers.Tests.csproj", "{540281A9-1726-4C99-91DA-2EBD9FE78E34}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {220D98E4-533E-4A3D-82C5-02E8530FF698}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {220D98E4-533E-4A3D-82C5-02E8530FF698}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {220D98E4-533E-4A3D-82C5-02E8530FF698}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {220D98E4-533E-4A3D-82C5-02E8530FF698}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {129198CC-F00E-470E-B504-5EE8E38DE16C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {129198CC-F00E-470E-B504-5EE8E38DE16C}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {129198CC-F00E-470E-B504-5EE8E38DE16C}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {129198CC-F00E-470E-B504-5EE8E38DE16C}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {540281A9-1726-4C99-91DA-2EBD9FE78E34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {540281A9-1726-4C99-91DA-2EBD9FE78E34}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {540281A9-1726-4C99-91DA-2EBD9FE78E34}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {540281A9-1726-4C99-91DA-2EBD9FE78E34}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/Analyzers/BaseFileSystemAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.Diagnostics; 2 | 3 | namespace System.IO.Abstractions.Analyzers.Analyzers; 4 | 5 | /// 6 | public abstract class BaseFileSystemAnalyzer : DiagnosticAnalyzer 7 | { 8 | /// 9 | /// Diagnostic Analyzer Category 10 | /// 11 | protected const string Category = Constants.FileSystemNameSpace; 12 | 13 | /// 14 | public override void Initialize(AnalysisContext context) 15 | { 16 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze|GeneratedCodeAnalysisFlags.ReportDiagnostics); 17 | context.EnableConcurrentExecution(); 18 | 19 | context.RegisterCompilationStartAction(compilationStartContext => 20 | { 21 | var fileSystemContext = new FileSystemContext(compilationStartContext.Compilation); 22 | 23 | if (fileSystemContext.HasReference) 24 | { 25 | AnalyzeCompilation(compilationStartContext, fileSystemContext); 26 | } 27 | }); 28 | } 29 | 30 | /// 31 | /// Analysis 32 | /// 33 | /// Compilation Start Analysis Context 34 | /// FileSystem Context 35 | protected abstract void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext, 36 | FileSystemContext fileSystemContext); 37 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/Analyzers/BaseFileSystemNodeAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | namespace System.IO.Abstractions.Analyzers.Analyzers; 8 | 9 | /// 10 | public abstract class BaseFileSystemNodeAnalyzer : BaseFileSystemAnalyzer 11 | { 12 | /// 13 | protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext, 14 | FileSystemContext fileSystemContext) 15 | { 16 | if (DoesNotUsedSystemIo()) 17 | { 18 | return; 19 | } 20 | 21 | compilationStartContext.RegisterSyntaxNodeAction(syntaxContext => 22 | { 23 | var invocation = (InvocationExpressionSyntax) syntaxContext.Node; 24 | 25 | if (IsStaticInvocationStartWith(invocation) && !IsInvocationFromAbstractions(syntaxContext, invocation)) 26 | { 27 | Analyze(syntaxContext, invocation); 28 | } 29 | }, 30 | SyntaxKind.InvocationExpression); 31 | 32 | compilationStartContext.RegisterSyntaxNodeAction(syntaxContext => 33 | { 34 | var creationExpressionSyntax = (ObjectCreationExpressionSyntax) syntaxContext.Node; 35 | var typeInfo = syntaxContext.SemanticModel.GetTypeInfo(creationExpressionSyntax); 36 | 37 | if (IsTypesEquals(typeInfo)) 38 | { 39 | Analyze(syntaxContext, creationExpressionSyntax); 40 | } 41 | }, 42 | SyntaxKind.ObjectCreationExpression); 43 | } 44 | 45 | protected abstract void Analyze(SyntaxNodeAnalysisContext context, ExpressionSyntax invocation); 46 | 47 | protected abstract Type GetFileSystemType(); 48 | 49 | private bool DoesNotUsedSystemIo() 50 | { 51 | var systemIoNamespace = typeof(Path).Namespace; 52 | 53 | return systemIoNamespace != null 54 | && !systemIoNamespace.Equals(GetFileSystemType() 55 | .Namespace); 56 | } 57 | 58 | private bool IsTypesEquals(TypeInfo typeInfo) 59 | { 60 | if (typeInfo.Type is null) 61 | { 62 | return false; 63 | } 64 | 65 | var namespaceSymbol = typeInfo.Type.ContainingNamespace; 66 | var fileSystemType = GetFileSystemType(); 67 | 68 | return typeInfo.Type.Name.Equals(fileSystemType.Name, StringComparison.Ordinal) 69 | && (namespaceSymbol.IsGlobalNamespace || namespaceSymbol.ToString() == fileSystemType.Namespace); 70 | } 71 | 72 | private bool IsStaticInvocationStartWith(InvocationExpressionSyntax invocation) => invocation.IsKind(SyntaxKind.InvocationExpression) 73 | && invocation.Expression.NormalizeWhitespace() 74 | .ToFullString() 75 | .StartsWith(GetFileSystemType() 76 | .Name 77 | + ".", StringComparison.Ordinal); 78 | 79 | private static bool IsInvocationFromAbstractions(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocation) => 80 | (invocation?.Expression as MemberAccessExpressionSyntax)?.Expression is ExpressionSyntax invokedMember 81 | && IsSymbolFromAbstractions(context.SemanticModel.GetSymbolInfo(invokedMember)); 82 | 83 | private static bool IsSymbolFromAbstractions(SymbolInfo symbolInfo) 84 | { 85 | if (symbolInfo.Symbol is ISymbol symbol) 86 | { 87 | return IsSymbolFromAbstractions(symbol); 88 | } 89 | 90 | return symbolInfo.CandidateSymbols.Length > 0 && symbolInfo.CandidateSymbols.All(IsSymbolFromAbstractions); 91 | } 92 | 93 | private static bool IsSymbolFromAbstractions(ISymbol symbol) 94 | { 95 | var namespaceSymbol = symbol switch 96 | { 97 | IPropertySymbol propertySymbol => propertySymbol.Type.ContainingNamespace, 98 | IFieldSymbol fieldSymbol => fieldSymbol.Type.ContainingNamespace, 99 | IMethodSymbol methodSymbol => methodSymbol.ContainingNamespace, 100 | var _ => null 101 | }; 102 | 103 | return namespaceSymbol is 104 | { 105 | IsGlobalNamespace: false 106 | } 107 | && namespaceSymbol.ToString() 108 | .StartsWith(Constants.FileSystemNameSpace, StringComparison.Ordinal); 109 | } 110 | 111 | protected static bool IsFirstConstructorParameterOfType(SyntaxNodeAnalysisContext context, ExpressionSyntax syntax) => 112 | (syntax as ObjectCreationExpressionSyntax)?.ArgumentList?.Arguments.FirstOrDefault() is ArgumentSyntax firstArgument 113 | && (context.SemanticModel.GetSymbolInfo(firstArgument.Expression) 114 | .Symbol as ILocalSymbol)?.Type is ITypeSymbol argumentType 115 | && argumentType.ContainingNamespace.Name == typeof(T).Namespace 116 | && argumentType.Name == typeof(T).Name; 117 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/Analyzers/FileSystemTypeAnalyzers/DirectoryAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using JetBrains.Annotations; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | namespace System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 8 | 9 | /// 10 | /// Search method invocation from Directory class in code 11 | /// 12 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 13 | public class DirectoryAnalyzer : BaseFileSystemNodeAnalyzer 14 | { 15 | /// 16 | /// Diagnostic Identifier 17 | /// 18 | [UsedImplicitly] 19 | public const string DiagnosticId = Constants.Io0003; 20 | 21 | /// 22 | /// Diagnostic Title 23 | /// 24 | private const string Title = "Replace Directory class with IFileSystem.Directory for improved testability"; 25 | 26 | /// 27 | /// Diagnostic Message Format 28 | /// 29 | public const string MessageFormat = Title; 30 | 31 | /// 32 | /// Diagnostic Description 33 | /// 34 | private const string Description = Title; 35 | 36 | private const string Link = "http://bit.ly/2Oz9iSx"; 37 | 38 | /// 39 | /// Diagnostic rule 40 | /// 41 | private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, 42 | Title, 43 | MessageFormat, 44 | Category, 45 | DiagnosticSeverity.Warning, 46 | true, 47 | Description, 48 | Link); 49 | 50 | /// 51 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 52 | 53 | /// 54 | protected override void Analyze(SyntaxNodeAnalysisContext context, ExpressionSyntax invocation) => 55 | context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation())); 56 | 57 | /// 58 | protected override Type GetFileSystemType() => typeof(Directory); 59 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/Analyzers/FileSystemTypeAnalyzers/DirectoryInfoAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using JetBrains.Annotations; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | namespace System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 8 | 9 | /// 10 | /// Search method invocation from DirectoryInfo class in code 11 | /// 12 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 13 | public class DirectoryInfoAnalyzer : BaseFileSystemNodeAnalyzer 14 | { 15 | /// 16 | /// Diagnostic Identifier 17 | /// 18 | [UsedImplicitly] 19 | public const string DiagnosticId = Constants.Io0007; 20 | 21 | /// 22 | /// Diagnostic Title 23 | /// 24 | private const string Title = "Replace DirectoryInfo class with IFileSystem.DirectoryInfo for improved testability"; 25 | 26 | /// 27 | /// Diagnostic Message Format 28 | /// 29 | public const string MessageFormat = Title; 30 | 31 | /// 32 | /// Diagnostic Description 33 | /// 34 | private const string Description = Title; 35 | 36 | private const string Link = "http://bit.ly/2OCAmQF"; 37 | 38 | /// 39 | /// Diagnostic rule 40 | /// 41 | private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, 42 | Title, 43 | MessageFormat, 44 | Category, 45 | DiagnosticSeverity.Warning, 46 | true, 47 | Description, 48 | Link); 49 | 50 | /// 51 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 52 | 53 | /// 54 | protected override void Analyze(SyntaxNodeAnalysisContext context, ExpressionSyntax syntax) => 55 | context.ReportDiagnostic(Diagnostic.Create(Rule, syntax.GetLocation())); 56 | 57 | /// 58 | protected override Type GetFileSystemType() => typeof(DirectoryInfo); 59 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/Analyzers/FileSystemTypeAnalyzers/FileAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using JetBrains.Annotations; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | namespace System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 8 | 9 | /// 10 | /// Search method invocation from File class in code 11 | /// 12 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 13 | public class FileAnalyzer : BaseFileSystemNodeAnalyzer 14 | { 15 | /// 16 | /// Diagnostic Identifier 17 | /// 18 | [UsedImplicitly] 19 | public const string DiagnosticId = Constants.Io0002; 20 | 21 | /// 22 | /// Diagnostic Title 23 | /// 24 | private const string Title = "Replace File class with IFileSystem.File for improved testability"; 25 | 26 | /// 27 | /// Diagnostic Message Format 28 | /// 29 | public const string MessageFormat = Title; 30 | 31 | /// 32 | /// Diagnostic Description 33 | /// 34 | private const string Description = Title; 35 | 36 | private const string Link = "http://bit.ly/2ODMgdq"; 37 | 38 | /// 39 | /// Diagnostic rule 40 | /// 41 | private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, 42 | Title, 43 | MessageFormat, 44 | Category, 45 | DiagnosticSeverity.Warning, 46 | true, 47 | Description, 48 | Link); 49 | 50 | /// 51 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 52 | 53 | /// 54 | protected override void Analyze(SyntaxNodeAnalysisContext context, ExpressionSyntax invocation) => 55 | context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation())); 56 | 57 | /// 58 | protected override Type GetFileSystemType() => typeof(File); 59 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/Analyzers/FileSystemTypeAnalyzers/FileInfoAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using JetBrains.Annotations; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | namespace System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 8 | 9 | /// 10 | /// Search method invocation from FileInfo class in code 11 | /// 12 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 13 | public class FileInfoAnalyzer : BaseFileSystemNodeAnalyzer 14 | { 15 | /// 16 | /// Diagnostic Identifier 17 | /// 18 | [UsedImplicitly] 19 | public const string DiagnosticId = Constants.Io0004; 20 | 21 | /// 22 | /// Diagnostic Title 23 | /// 24 | private const string Title = "Replace FileInfo class with IFileSystem.FileInfo for improved testability"; 25 | 26 | /// 27 | /// Diagnostic Message Format 28 | /// 29 | public const string MessageFormat = Title; 30 | 31 | /// 32 | /// Diagnostic Description 33 | /// 34 | private const string Description = Title; 35 | 36 | private const string Link = "http://bit.ly/2OAA4tM"; 37 | 38 | /// 39 | /// Diagnostic rule 40 | /// 41 | private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, 42 | Title, 43 | MessageFormat, 44 | Category, 45 | DiagnosticSeverity.Warning, 46 | true, 47 | Description, 48 | Link); 49 | 50 | /// 51 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 52 | 53 | /// 54 | protected override void Analyze(SyntaxNodeAnalysisContext context, ExpressionSyntax syntax) => 55 | context.ReportDiagnostic(Diagnostic.Create(Rule, syntax.GetLocation())); 56 | 57 | /// 58 | protected override Type GetFileSystemType() => typeof(FileInfo); 59 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/Analyzers/FileSystemTypeAnalyzers/FileStreamAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using JetBrains.Annotations; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | namespace System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 8 | 9 | /// 10 | /// Search method invocation from FileStream class in code 11 | /// 12 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 13 | public class FileStreamAnalyzer : BaseFileSystemNodeAnalyzer 14 | { 15 | /// 16 | /// Diagnostic Identifier 17 | /// 18 | [UsedImplicitly] 19 | public const string DiagnosticId = Constants.Io0005; 20 | 21 | /// 22 | /// Diagnostic Title 23 | /// 24 | private const string Title = "Replace FileStream class with IFileSystem.FileStream for improved testability"; 25 | 26 | /// 27 | /// Diagnostic Message Format 28 | /// 29 | public const string MessageFormat = Title; 30 | 31 | /// 32 | /// Diagnostic Description 33 | /// 34 | private const string Description = Title; 35 | 36 | private const string Link = "http://bit.ly/2Oz5MaM"; 37 | 38 | /// 39 | /// Diagnostic rule 40 | /// 41 | private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, 42 | Title, 43 | MessageFormat, 44 | Category, 45 | DiagnosticSeverity.Warning, 46 | true, 47 | Description, 48 | Link); 49 | 50 | /// 51 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 52 | 53 | /// 54 | protected override void Analyze(SyntaxNodeAnalysisContext context, ExpressionSyntax syntax) => 55 | context.ReportDiagnostic(Diagnostic.Create(Rule, syntax.GetLocation())); 56 | 57 | /// 58 | protected override Type GetFileSystemType() => typeof(FileStream); 59 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/Analyzers/FileSystemTypeAnalyzers/PathAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using JetBrains.Annotations; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | namespace System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 8 | 9 | /// 10 | /// Search method invocation from Path class in code 11 | /// 12 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 13 | public class PathAnalyzer : BaseFileSystemNodeAnalyzer 14 | { 15 | /// 16 | /// Diagnostic Identifier 17 | /// 18 | [UsedImplicitly] 19 | public const string DiagnosticId = Constants.Io0006; 20 | 21 | /// 22 | /// Diagnostic Title 23 | /// 24 | private const string Title = "Replace Path class with IFileSystem.Path for improved testability"; 25 | 26 | /// 27 | /// Diagnostic Message Format 28 | /// 29 | public const string MessageFormat = Title; 30 | 31 | /// 32 | /// Diagnostic Description 33 | /// 34 | private const string Description = Title; 35 | 36 | private const string Link = "http://bit.ly/2ODMkKc"; 37 | 38 | /// 39 | /// Diagnostic rule 40 | /// 41 | private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, 42 | Title, 43 | MessageFormat, 44 | Category, 45 | DiagnosticSeverity.Warning, 46 | true, 47 | Description, 48 | Link); 49 | 50 | /// 51 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 52 | 53 | /// 54 | protected override void Analyze(SyntaxNodeAnalysisContext context, ExpressionSyntax invocation) => 55 | context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation())); 56 | 57 | /// 58 | protected override Type GetFileSystemType() => typeof(Path); 59 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/Analyzers/FileSystemTypeAnalyzers/StreamReaderAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using JetBrains.Annotations; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | namespace System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 8 | 9 | /// 10 | /// Search StreamReader in Code 11 | /// 12 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 13 | public class StreamReaderAnalyzer : BaseFileSystemNodeAnalyzer 14 | { 15 | /// 16 | /// Diagnostic Identifier 17 | /// 18 | [UsedImplicitly] 19 | public const string DiagnosticId = "IO0011"; 20 | 21 | /// 22 | /// Diagnostic Title 23 | /// 24 | private const string Title = "Replace StreamReader string constructor overload with stream based overload" 25 | + " using a stream from IFileSystem.FileStream for improved testability"; 26 | 27 | /// 28 | /// Diagnostic Message Format 29 | /// 30 | public const string MessageFormat = Title; 31 | 32 | /// 33 | /// Diagnostic Description 34 | /// 35 | private const string Description = Title; 36 | 37 | /// 38 | /// Diagnostic rule 39 | /// 40 | private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, 41 | Title, 42 | MessageFormat, 43 | Category, 44 | DiagnosticSeverity.Warning, 45 | true, 46 | Description); 47 | 48 | /// 49 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 50 | 51 | /// 52 | protected override void Analyze(SyntaxNodeAnalysisContext context, ExpressionSyntax syntax) 53 | { 54 | if (IsFirstConstructorParameterOfType(context, syntax)) 55 | { 56 | context.ReportDiagnostic(Diagnostic.Create(Rule, syntax.GetLocation())); 57 | } 58 | } 59 | 60 | /// 61 | protected override Type GetFileSystemType() => typeof(StreamReader); 62 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/Analyzers/FileSystemTypeAnalyzers/StreamWriterAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using JetBrains.Annotations; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | 7 | namespace System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 8 | 9 | /// 10 | /// Search StreamWriter in Code 11 | /// 12 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 13 | public class StreamWriterAnalyzer : BaseFileSystemNodeAnalyzer 14 | { 15 | /// 16 | /// Diagnostic Identifier 17 | /// 18 | [UsedImplicitly] 19 | public const string DiagnosticId = "IO0010"; 20 | 21 | /// 22 | /// Diagnostic Title 23 | /// 24 | private const string Title = "Replace StreamWriter string constructor overload with stream based overload" 25 | + " using a stream from IFileSystem.FileStream for improved testability"; 26 | 27 | /// 28 | /// Diagnostic Message Format 29 | /// 30 | public const string MessageFormat = Title; 31 | 32 | /// 33 | /// Diagnostic Description 34 | /// 35 | private const string Description = Title; 36 | 37 | /// 38 | /// Diagnostic rule 39 | /// 40 | private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, 41 | Title, 42 | MessageFormat, 43 | Category, 44 | DiagnosticSeverity.Warning, 45 | true, 46 | Description); 47 | 48 | /// 49 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 50 | 51 | /// 52 | protected override void Analyze(SyntaxNodeAnalysisContext context, ExpressionSyntax syntax) 53 | { 54 | if (IsFirstConstructorParameterOfType(context, syntax)) 55 | { 56 | context.ReportDiagnostic(Diagnostic.Create(Rule, syntax.GetLocation())); 57 | } 58 | } 59 | 60 | /// 61 | protected override Type GetFileSystemType() => typeof(StreamWriter); 62 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeActions/DirectoryInfoCodeAction.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeActions; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.CodeAnalysis.Editing; 8 | using Microsoft.CodeAnalysis.Formatting; 9 | using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 10 | 11 | namespace System.IO.Abstractions.Analyzers.CodeActions; 12 | 13 | /// 14 | /// Code action to replace a with a IFileSystem.DirectoryInfo. 15 | /// 16 | public class DirectoryInfoCodeAction : CodeAction 17 | { 18 | private readonly ObjectCreationExpressionSyntax _creationExpressionSyntax; 19 | 20 | private readonly Document _document; 21 | 22 | private readonly FieldDeclarationSyntax _field; 23 | 24 | public DirectoryInfoCodeAction(string title, Document document, ObjectCreationExpressionSyntax creationExpressionSyntax, 25 | FieldDeclarationSyntax field) 26 | { 27 | Title = title; 28 | _document = document; 29 | _creationExpressionSyntax = creationExpressionSyntax; 30 | _field = field; 31 | } 32 | 33 | /// 34 | public override string Title { get; } 35 | 36 | /// 37 | public override string EquivalenceKey => Title; 38 | 39 | /// 40 | protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) 41 | { 42 | var editor = await DocumentEditor.CreateAsync(_document, cancellationToken) 43 | .ConfigureAwait(false); 44 | 45 | if (_creationExpressionSyntax.ArgumentList == null) 46 | { 47 | return _document; 48 | } 49 | 50 | var arguments = _creationExpressionSyntax.ArgumentList.Arguments.Select(x => x.ToFullString()); 51 | 52 | editor.ReplaceNode(_creationExpressionSyntax, 53 | SF.ParseExpression( 54 | $"{_field.Declaration.Variables.ToFullString()}.DirectoryInfo.FromDirectoryName({string.Join(",", arguments)})")); 55 | 56 | return await Formatter.FormatAsync(editor.GetChangedDocument(), cancellationToken: cancellationToken) 57 | .ConfigureAwait(false); 58 | } 59 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeActions/FileInfoCodeAction.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeActions; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.CodeAnalysis.Editing; 8 | using Microsoft.CodeAnalysis.Formatting; 9 | using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 10 | 11 | namespace System.IO.Abstractions.Analyzers.CodeActions; 12 | 13 | /// 14 | /// Code action to replace a with a IFileSystem.FileInfo. 15 | /// 16 | public class FileInfoCodeAction : CodeAction 17 | { 18 | private readonly ObjectCreationExpressionSyntax _creationExpressionSyntax; 19 | 20 | private readonly Document _document; 21 | 22 | private readonly FieldDeclarationSyntax _field; 23 | 24 | public FileInfoCodeAction(string title, Document document, ObjectCreationExpressionSyntax creationExpressionSyntax, 25 | FieldDeclarationSyntax field) 26 | { 27 | Title = title; 28 | _document = document; 29 | _creationExpressionSyntax = creationExpressionSyntax; 30 | _field = field; 31 | } 32 | 33 | /// 34 | public override string Title { get; } 35 | 36 | /// 37 | public override string EquivalenceKey => Title; 38 | 39 | /// 40 | protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) 41 | { 42 | var editor = await DocumentEditor.CreateAsync(_document, cancellationToken) 43 | .ConfigureAwait(false); 44 | 45 | if (_creationExpressionSyntax.ArgumentList == null) 46 | { 47 | return _document; 48 | } 49 | 50 | var arguments = _creationExpressionSyntax.ArgumentList.Arguments.Select(x => x.ToFullString()); 51 | 52 | editor.ReplaceNode(_creationExpressionSyntax, 53 | SF.ParseExpression($"{_field.Declaration.Variables.ToFullString()}.FileInfo.FromFileName({string.Join(",", arguments)})")); 54 | 55 | return await Formatter.FormatAsync(editor.GetChangedDocument(), cancellationToken: cancellationToken) 56 | .ConfigureAwait(false); 57 | } 58 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeActions/FileServiceConstructorInitialCodeAction.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions.Analyzers.RoslynToken; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeActions; 6 | using Microsoft.CodeAnalysis.CSharp; 7 | using Microsoft.CodeAnalysis.CSharp.Syntax; 8 | using Microsoft.CodeAnalysis.Editing; 9 | using Microsoft.CodeAnalysis.Formatting; 10 | using Microsoft.CodeAnalysis.Simplification; 11 | using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 12 | 13 | namespace System.IO.Abstractions.Analyzers.CodeActions; 14 | 15 | /// 16 | /// Code action to add IFileSystem as a property to a class. 17 | /// 18 | public class FileServiceConstructorInitialCodeAction : CodeAction 19 | { 20 | private readonly ClassDeclarationSyntax _class; 21 | 22 | private readonly Document _document; 23 | 24 | public FileServiceConstructorInitialCodeAction(string title, Document document, ClassDeclarationSyntax @class) 25 | { 26 | _class = @class; 27 | _document = document; 28 | Title = title; 29 | } 30 | 31 | /// 32 | public override string Title { get; } 33 | 34 | /// 35 | public override string EquivalenceKey => Title; 36 | 37 | /// 38 | protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) 39 | { 40 | var editor = await DocumentEditor.CreateAsync(_document, cancellationToken) 41 | .ConfigureAwait(false); 42 | 43 | if (!RoslynClassFileSystem.HasFileSystemField(_class)) 44 | { 45 | editor.InsertMembers(_class, 46 | 0, 47 | new SyntaxNode[] 48 | { 49 | RoslynClassFileSystem.CreateFileSystemFieldDeclaration() 50 | }); 51 | } 52 | 53 | ConstructorAddParameter(_class, editor); 54 | 55 | var compilationUnitSyntax = RoslynClassFileSystem.GetCompilationUnit(_class); 56 | 57 | if (!compilationUnitSyntax.Usings.Any()) 58 | { 59 | return editor.GetChangedDocument(); 60 | } 61 | 62 | var fileSystem = RoslynClassFileSystem.GetUsing(compilationUnitSyntax, Constants.FileSystemNameSpace); 63 | 64 | if (fileSystem != null) 65 | { 66 | return editor.GetChangedDocument(); 67 | } 68 | 69 | var systemIo = RoslynClassFileSystem.GetSystemIoUsing(compilationUnitSyntax); 70 | 71 | if (systemIo == null) 72 | { 73 | if (compilationUnitSyntax.Usings.Any()) 74 | { 75 | editor.InsertBefore(compilationUnitSyntax.Usings.FirstOrDefault()!, 76 | RoslynClassFileSystem.GetFileSystemUsing()); 77 | } 78 | } else 79 | { 80 | editor.InsertAfter(systemIo, RoslynClassFileSystem.GetFileSystemUsing()); 81 | } 82 | 83 | return await Formatter.FormatAsync(editor.GetChangedDocument(), cancellationToken: cancellationToken) 84 | .ConfigureAwait(false); 85 | } 86 | 87 | private static ExpressionStatementSyntax CreateAssignmentExpression(string field = Constants.FieldFileSystemName) => 88 | SF.ExpressionStatement(SF.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, 89 | SF.IdentifierName(field), 90 | SF.ObjectCreationExpression(SF.IdentifierName(Constants.FileSystemClassName)) 91 | .WithArgumentList(SF.ArgumentList()))); 92 | 93 | private static void ConstructorAddParameter(ClassDeclarationSyntax classDeclaration, SyntaxEditor editor) 94 | { 95 | var constructor = RoslynClassFileSystem.HasConstructor(classDeclaration) 96 | ? RoslynClassFileSystem.GetConstructor(classDeclaration) 97 | : SF.ConstructorDeclaration(classDeclaration.Identifier) 98 | .WithModifiers(SyntaxTokenList.Create(SF.Token(SyntaxKind.PublicKeyword))); 99 | 100 | var newConstructor = constructor.WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation) 101 | .NormalizeWhitespace(); 102 | 103 | if (!RoslynClassFileSystem.ConstructorHasAssignmentExpression(newConstructor)) 104 | { 105 | var statementSyntax = CreateAssignmentExpression(); 106 | 107 | if (RoslynClassFileSystem.HasFileSystemField(classDeclaration)) 108 | { 109 | var fileSystem = RoslynClassFileSystem.GetFileSystemFieldFromClass(classDeclaration); 110 | statementSyntax = CreateAssignmentExpression(fileSystem.Declaration.Variables.ToFullString()); 111 | } 112 | 113 | newConstructor = 114 | newConstructor.AddBodyStatements(statementSyntax); 115 | } 116 | 117 | if (RoslynClassFileSystem.HasConstructor(classDeclaration)) 118 | { 119 | editor.ReplaceNode(constructor, newConstructor); 120 | } else 121 | { 122 | editor.InsertBefore(RoslynClassFileSystem.GetMethod(classDeclaration), newConstructor); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeActions/FileServiceInterfaceInjectionCodeAction.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions.Analyzers.RoslynToken; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeActions; 6 | using Microsoft.CodeAnalysis.CSharp; 7 | using Microsoft.CodeAnalysis.CSharp.Syntax; 8 | using Microsoft.CodeAnalysis.Editing; 9 | using Microsoft.CodeAnalysis.Formatting; 10 | using Microsoft.CodeAnalysis.Simplification; 11 | using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 12 | 13 | namespace System.IO.Abstractions.Analyzers.CodeActions; 14 | 15 | /// 16 | /// Code action to replace FileSystem invocation to IFileSystem service. 17 | /// 18 | public class FileServiceInterfaceInjectionCodeAction : CodeAction 19 | { 20 | private readonly ClassDeclarationSyntax _class; 21 | 22 | private readonly Document _document; 23 | 24 | public FileServiceInterfaceInjectionCodeAction(string title, Document document, ClassDeclarationSyntax @class) 25 | { 26 | _class = @class; 27 | _document = document; 28 | Title = title; 29 | } 30 | 31 | /// 32 | public override string Title { get; } 33 | 34 | /// 35 | public override string EquivalenceKey => Title; 36 | 37 | /// 38 | protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) 39 | { 40 | var editor = await DocumentEditor.CreateAsync(_document, cancellationToken) 41 | .ConfigureAwait(false); 42 | 43 | if (!RoslynClassFileSystem.HasFileSystemField(_class)) 44 | { 45 | editor.InsertMembers(_class, 46 | 0, 47 | new SyntaxNode[] 48 | { 49 | RoslynClassFileSystem.CreateFileSystemFieldDeclaration() 50 | }); 51 | } 52 | 53 | ConstructorAddParameter(_class, editor); 54 | 55 | var compilationUnitSyntax = RoslynClassFileSystem.GetCompilationUnit(_class); 56 | 57 | if (!compilationUnitSyntax.Usings.Any()) 58 | { 59 | return editor.GetChangedDocument(); 60 | } 61 | 62 | var fileSystem = RoslynClassFileSystem.GetUsing(compilationUnitSyntax, Constants.FileSystemNameSpace); 63 | 64 | if (fileSystem != null) 65 | { 66 | return editor.GetChangedDocument(); 67 | } 68 | 69 | var systemIo = RoslynClassFileSystem.GetSystemIoUsing(compilationUnitSyntax); 70 | 71 | if (systemIo == null) 72 | { 73 | if (compilationUnitSyntax.Usings.Any()) 74 | { 75 | editor.InsertBefore(compilationUnitSyntax.Usings.FirstOrDefault()!, 76 | RoslynClassFileSystem.GetFileSystemUsing()); 77 | } 78 | } else 79 | { 80 | editor.InsertAfter(systemIo, RoslynClassFileSystem.GetFileSystemUsing()); 81 | } 82 | 83 | return await Formatter.FormatAsync(editor.GetChangedDocument(), cancellationToken: cancellationToken) 84 | .ConfigureAwait(false); 85 | } 86 | 87 | private static ExpressionStatementSyntax CreateAssignmentExpression(string field, string parameter) => SF.ExpressionStatement( 88 | SF.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, 89 | SF.IdentifierName(field), 90 | SF.IdentifierName(parameter))); 91 | 92 | private static void ConstructorAddParameter(ClassDeclarationSyntax classDeclaration, SyntaxEditor editor) 93 | { 94 | var constructor = RoslynClassFileSystem.HasConstructor(classDeclaration) 95 | ? RoslynClassFileSystem.GetConstructor(classDeclaration) 96 | : SF.ConstructorDeclaration(classDeclaration.Identifier) 97 | .WithModifiers(SyntaxTokenList.Create(SyntaxFactory.Token(SyntaxKind.PublicKeyword))); 98 | 99 | var newConstructor = constructor.WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation) 100 | .NormalizeWhitespace(); 101 | 102 | if (!RoslynClassFileSystem.ConstructorHasFileSystemParameter(newConstructor)) 103 | { 104 | var parameter = RoslynClassFileSystem.CreateFileSystemParameterDeclaration(); 105 | newConstructor = newConstructor.AddParameterListParameters(parameter); 106 | } 107 | 108 | if (!RoslynClassFileSystem.ConstructorHasAssignmentExpression(newConstructor)) 109 | { 110 | var parameterSyntax = RoslynClassFileSystem.GetFileSystemParameterFromConstructor(newConstructor); 111 | 112 | if (RoslynClassFileSystem.HasFileSystemField(classDeclaration)) 113 | { 114 | var fileSystem = RoslynClassFileSystem.GetFileSystemFieldFromClass(classDeclaration); 115 | 116 | newConstructor = newConstructor.AddBodyStatements(CreateAssignmentExpression( 117 | fileSystem.Declaration.Variables.ToFullString(), 118 | parameterSyntax.Identifier.Text)); 119 | } else 120 | { 121 | newConstructor = 122 | newConstructor.AddBodyStatements(CreateAssignmentExpression(Constants.FieldFileSystemName, 123 | parameterSyntax.Identifier.Text)); 124 | } 125 | } 126 | 127 | if (RoslynClassFileSystem.HasConstructor(classDeclaration)) 128 | { 129 | editor.ReplaceNode(constructor, newConstructor); 130 | } else 131 | { 132 | editor.InsertBefore(RoslynClassFileSystem.GetMethod(classDeclaration), newConstructor); 133 | } 134 | } 135 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeActions/FileStreamCodeAction.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeActions; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.CodeAnalysis.Editing; 8 | using Microsoft.CodeAnalysis.Formatting; 9 | using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 10 | 11 | namespace System.IO.Abstractions.Analyzers.CodeActions; 12 | 13 | /// 14 | /// Code action to replace a with a IFileSystem.File.Create call. 15 | /// 16 | public class FileStreamCodeAction : CodeAction 17 | { 18 | private readonly ObjectCreationExpressionSyntax _creationExpressionSyntax; 19 | 20 | private readonly Document _document; 21 | 22 | private readonly FieldDeclarationSyntax _field; 23 | 24 | public FileStreamCodeAction(string title, Document document, ObjectCreationExpressionSyntax creationExpressionSyntax, 25 | FieldDeclarationSyntax field) 26 | { 27 | Title = title; 28 | _document = document; 29 | _creationExpressionSyntax = creationExpressionSyntax; 30 | _field = field; 31 | } 32 | 33 | /// 34 | public override string Title { get; } 35 | 36 | /// 37 | public override string EquivalenceKey => Title; 38 | 39 | /// 40 | protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) 41 | { 42 | var editor = await DocumentEditor.CreateAsync(_document, cancellationToken) 43 | .ConfigureAwait(false); 44 | 45 | if (_creationExpressionSyntax.ArgumentList == null) 46 | { 47 | return _document; 48 | } 49 | 50 | var arguments = _creationExpressionSyntax.ArgumentList.Arguments.Select(x => x.ToFullString()); 51 | 52 | editor.ReplaceNode(_creationExpressionSyntax, 53 | SF.ParseExpression($"{_field.Declaration.Variables.ToFullString()}.FileStream.Create({string.Join(",", arguments)})")); 54 | 55 | return await Formatter.FormatAsync(editor.GetChangedDocument(), cancellationToken: cancellationToken) 56 | .ConfigureAwait(false); 57 | } 58 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeActions/FileSystemInvokeCodeAction.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CodeActions; 5 | using Microsoft.CodeAnalysis.CSharp.Syntax; 6 | using Microsoft.CodeAnalysis.Editing; 7 | using Microsoft.CodeAnalysis.Formatting; 8 | using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 9 | 10 | namespace System.IO.Abstractions.Analyzers.CodeActions; 11 | 12 | /// 13 | /// Code action to replace with a IFileSystem call. 14 | /// 15 | public class FileSystemInvokeCodeAction : CodeAction 16 | { 17 | private readonly Document _document; 18 | 19 | private readonly FieldDeclarationSyntax _field; 20 | 21 | private readonly InvocationExpressionSyntax _invocation; 22 | 23 | public FileSystemInvokeCodeAction(string title, Document document, InvocationExpressionSyntax invocation, 24 | FieldDeclarationSyntax field) 25 | { 26 | Title = title; 27 | _document = document; 28 | _invocation = invocation; 29 | _field = field; 30 | } 31 | 32 | /// 33 | public override string Title { get; } 34 | 35 | /// 36 | public override string EquivalenceKey => Title; 37 | 38 | /// 39 | protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) 40 | { 41 | var editor = await DocumentEditor.CreateAsync(_document, cancellationToken) 42 | .ConfigureAwait(false); 43 | 44 | if (_field.Declaration.Variables.Any()) 45 | { 46 | editor.ReplaceNode(_invocation, 47 | SF.ParseExpression( 48 | $"{_field.Declaration.Variables.FirstOrDefault()?.Identifier.Text}.{_invocation.NormalizeWhitespace().ToFullString()}")); 49 | } 50 | 51 | return await Formatter.FormatAsync(editor.GetChangedDocument(), cancellationToken: cancellationToken) 52 | .ConfigureAwait(false); 53 | } 54 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeFixes/BaseInvokeCodeFix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.IO.Abstractions.Analyzers.CodeActions; 3 | using System.IO.Abstractions.Analyzers.RoslynToken; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.CodeAnalysis.CodeFixes; 7 | using Microsoft.CodeAnalysis.CSharp.Syntax; 8 | 9 | namespace System.IO.Abstractions.Analyzers.CodeFixes; 10 | 11 | /// 12 | /// Base class for code fixes that replace a with a . 13 | /// 14 | public abstract class BaseInvokeCodeFix : CodeFixProvider 15 | { 16 | /// 17 | /// Diagnostic ID of the analyzer that this code fix provider can fix. 18 | /// 19 | protected abstract string DiagnosticId { get; } 20 | 21 | /// 22 | /// Title of the code fix. 23 | /// 24 | protected abstract string Title { get; } 25 | 26 | /// 27 | public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticId); 28 | 29 | /// 30 | public override sealed FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 31 | 32 | /// 33 | public override async sealed Task RegisterCodeFixesAsync(CodeFixContext context) 34 | { 35 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken) 36 | .ConfigureAwait(false); 37 | 38 | if (root == null) 39 | { 40 | return; 41 | } 42 | 43 | var classDeclaration = root.FindNode(context.Span) 44 | .FirstAncestorOrSelf(); 45 | 46 | if (RoslynClassFileSystem.HasFileSystemField(classDeclaration)) 47 | { 48 | var invocation = root.FindNode(context.Span) 49 | .DescendantNodesAndSelf() 50 | .OfType() 51 | .FirstOrDefault(); 52 | 53 | var field = RoslynClassFileSystem.GetFileSystemFieldFromClass(classDeclaration); 54 | 55 | context.RegisterCodeFix(new FileSystemInvokeCodeAction(Title, 56 | context.Document, 57 | invocation, 58 | field), 59 | context.Diagnostics); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeFixes/DirectoryCodeFix.cs: -------------------------------------------------------------------------------- 1 | using System.Composition; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CodeFixes; 5 | 6 | namespace System.IO.Abstractions.Analyzers.CodeFixes; 7 | 8 | /// 9 | /// Code fix provider for . 10 | /// 11 | [Shared] 12 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DirectoryCodeFix))] 13 | public class DirectoryCodeFix : BaseInvokeCodeFix 14 | { 15 | [ImportingConstructor] 16 | [Obsolete("This exported object must be obtained through the MEF export provider.", true)] 17 | public DirectoryCodeFix() 18 | { 19 | } 20 | 21 | /// 22 | protected override string DiagnosticId => Constants.Io0003; 23 | 24 | /// 25 | protected override string Title => "Use IFileSystem.Directory for improved testability"; 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeFixes/DirectoryInfoCodeFix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 4 | using System.IO.Abstractions.Analyzers.CodeActions; 5 | using System.IO.Abstractions.Analyzers.RoslynToken; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using Microsoft.CodeAnalysis; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CSharp.Syntax; 11 | 12 | namespace System.IO.Abstractions.Analyzers.CodeFixes; 13 | 14 | /// 15 | /// Code fix provider for . 16 | /// 17 | [Shared] 18 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DirectoryInfoCodeFix))] 19 | public class DirectoryInfoCodeFix : CodeFixProvider 20 | { 21 | private const string Title = "Use IDirectoryInfoFactory instead creation DirectoryInfo for improved testability"; 22 | 23 | [ImportingConstructor] 24 | [Obsolete("This exported object must be obtained through the MEF export provider.", true)] 25 | public DirectoryInfoCodeFix() 26 | { 27 | } 28 | 29 | /// 30 | public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(Constants.Io0007); 31 | 32 | /// 33 | public override sealed FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 34 | 35 | /// 36 | public override async Task RegisterCodeFixesAsync(CodeFixContext context) 37 | { 38 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken) 39 | .ConfigureAwait(false); 40 | 41 | var classDeclarationSyntax = root?.FindNode(context.Span) 42 | .FirstAncestorOrSelf(); 43 | 44 | if (classDeclarationSyntax is null || !RoslynClassFileSystem.HasFileSystemField(classDeclarationSyntax)) 45 | { 46 | return; 47 | } 48 | 49 | var creationExpressionSyntax = root.FindNode(context.Span) 50 | .FirstAncestorOrSelf(); 51 | 52 | var fieldDeclarationSyntax = classDeclarationSyntax 53 | .Members 54 | .OfType() 55 | .FirstOrDefault(x => 56 | x.Declaration.Type.NormalizeWhitespace() 57 | .ToFullString() 58 | == RoslynClassFileSystem.GetFileSystemType() 59 | .ToFullString()); 60 | 61 | context.RegisterCodeFix(new DirectoryInfoCodeAction(Title, 62 | context.Document, 63 | creationExpressionSyntax, 64 | fieldDeclarationSyntax), 65 | context.Diagnostics); 66 | } 67 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeFixes/FileCodeFix.cs: -------------------------------------------------------------------------------- 1 | using System.Composition; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CodeFixes; 5 | 6 | namespace System.IO.Abstractions.Analyzers.CodeFixes; 7 | 8 | /// 9 | /// Code fix provider for . 10 | /// 11 | [Shared] 12 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FileCodeFix))] 13 | public class FileCodeFix : BaseInvokeCodeFix 14 | { 15 | [ImportingConstructor] 16 | [Obsolete("This exported object must be obtained through the MEF export provider.", true)] 17 | public FileCodeFix() 18 | { 19 | } 20 | 21 | /// 22 | protected override string DiagnosticId => Constants.Io0002; 23 | 24 | /// 25 | protected override string Title => "Use IFileSystem.File for improved testability"; 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeFixes/FileInfoCodeFix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 4 | using System.IO.Abstractions.Analyzers.CodeActions; 5 | using System.IO.Abstractions.Analyzers.RoslynToken; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using Microsoft.CodeAnalysis; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CSharp.Syntax; 11 | 12 | namespace System.IO.Abstractions.Analyzers.CodeFixes; 13 | 14 | /// 15 | /// Code fix provider for . 16 | /// 17 | [Shared] 18 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FileInfoCodeFix))] 19 | public class FileInfoCodeFix : CodeFixProvider 20 | { 21 | private const string Title = "Use IFileInfoFactory instead creation FileInfo for improved testability"; 22 | 23 | [ImportingConstructor] 24 | [Obsolete("This exported object must be obtained through the MEF export provider.", true)] 25 | public FileInfoCodeFix() 26 | { 27 | } 28 | 29 | /// 30 | public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(Constants.Io0004); 31 | 32 | /// 33 | public override sealed FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 34 | 35 | /// 36 | public override async Task RegisterCodeFixesAsync(CodeFixContext context) 37 | { 38 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken) 39 | .ConfigureAwait(false); 40 | 41 | var classDeclarationSyntax = root?.FindNode(context.Span) 42 | .FirstAncestorOrSelf(); 43 | 44 | if (classDeclarationSyntax is null || !RoslynClassFileSystem.HasFileSystemField(classDeclarationSyntax)) 45 | { 46 | return; 47 | } 48 | 49 | var creationExpressionSyntax = root.FindNode(context.Span) 50 | .FirstAncestorOrSelf(); 51 | 52 | var fieldDeclarationSyntax = classDeclarationSyntax 53 | .Members 54 | .OfType() 55 | .FirstOrDefault(x => 56 | x.Declaration.Type.NormalizeWhitespace() 57 | .ToFullString() 58 | == RoslynClassFileSystem.GetFileSystemType() 59 | .ToFullString()); 60 | 61 | context.RegisterCodeFix(new FileInfoCodeAction(Title, 62 | context.Document, 63 | creationExpressionSyntax, 64 | fieldDeclarationSyntax), 65 | context.Diagnostics); 66 | } 67 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeFixes/FileServiceConstructorInitialCodeFix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.IO.Abstractions.Analyzers.CodeActions; 4 | using System.IO.Abstractions.Analyzers.RoslynToken; 5 | using System.Threading.Tasks; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.CodeFixes; 8 | using Microsoft.CodeAnalysis.CSharp.Syntax; 9 | 10 | namespace System.IO.Abstractions.Analyzers.CodeFixes; 11 | 12 | /// 13 | /// Code fix provider for injection IFileSystem in controller constructor. 14 | /// 15 | [Shared] 16 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FileServiceConstructorInitialCodeFix))] 17 | public class FileServiceConstructorInitialCodeFix : CodeFixProvider 18 | { 19 | private const string Title = "Create FileSystem in constructor and using System.IO.Abstractions"; 20 | 21 | [ImportingConstructor] 22 | [Obsolete("This exported object must be obtained through the MEF export provider.", true)] 23 | public FileServiceConstructorInitialCodeFix() 24 | { 25 | } 26 | 27 | /// 28 | public override sealed ImmutableArray FixableDiagnosticIds => 29 | ImmutableArray.Create(Constants.Io0002, 30 | Constants.Io0003, 31 | Constants.Io0004, 32 | Constants.Io0005, 33 | Constants.Io0006, 34 | Constants.Io0007); 35 | 36 | /// 37 | public override sealed FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 38 | 39 | /// 40 | public override async sealed Task RegisterCodeFixesAsync(CodeFixContext context) 41 | { 42 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken) 43 | .ConfigureAwait(false); 44 | 45 | if (root == null) 46 | { 47 | return; 48 | } 49 | 50 | var classDeclarationSyntax = root.FindNode(context.Span) 51 | .FirstAncestorOrSelf(); 52 | 53 | var constructor = RoslynClassFileSystem.GetConstructor(classDeclarationSyntax); 54 | 55 | if (!RoslynClassFileSystem.HasFileSystemField(classDeclarationSyntax) 56 | || (constructor != null && !RoslynClassFileSystem.ConstructorHasFileSystemParameter(constructor))) 57 | { 58 | context.RegisterCodeFix(new FileServiceConstructorInitialCodeAction(Title, 59 | context.Document, 60 | classDeclarationSyntax), 61 | context.Diagnostics); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeFixes/FileServiceInterfaceInjectionCodeFix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.IO.Abstractions.Analyzers.CodeActions; 4 | using System.IO.Abstractions.Analyzers.RoslynToken; 5 | using System.Threading.Tasks; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.CodeFixes; 8 | using Microsoft.CodeAnalysis.CSharp.Syntax; 9 | 10 | namespace System.IO.Abstractions.Analyzers.CodeFixes; 11 | 12 | /// 13 | /// Code fix provider for injecting IFileSystem into a class. 14 | /// 15 | [Shared] 16 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FileServiceInterfaceInjectionCodeFix))] 17 | public class FileServiceInterfaceInjectionCodeFix : CodeFixProvider 18 | { 19 | private const string Title = "Inject IFileSystem and using System.IO.Abstractions"; 20 | 21 | [ImportingConstructor] 22 | [Obsolete("This exported object must be obtained through the MEF export provider.", true)] 23 | public FileServiceInterfaceInjectionCodeFix() 24 | { 25 | } 26 | 27 | /// 28 | public override sealed ImmutableArray FixableDiagnosticIds => 29 | ImmutableArray.Create(Constants.Io0002, 30 | Constants.Io0003, 31 | Constants.Io0004, 32 | Constants.Io0005, 33 | Constants.Io0006, 34 | Constants.Io0007); 35 | 36 | /// 37 | public override sealed FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 38 | 39 | /// 40 | public override async sealed Task RegisterCodeFixesAsync(CodeFixContext context) 41 | { 42 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken) 43 | .ConfigureAwait(false); 44 | 45 | var classDeclarationSyntax = root?.FindNode(context.Span) 46 | .FirstAncestorOrSelf(); 47 | 48 | var constructor = RoslynClassFileSystem.GetConstructor(classDeclarationSyntax); 49 | 50 | if (!RoslynClassFileSystem.HasFileSystemField(classDeclarationSyntax) 51 | || (constructor != null && !RoslynClassFileSystem.ConstructorHasFileSystemParameter(constructor)) 52 | || (constructor != null && !RoslynClassFileSystem.ConstructorHasAssignmentExpression(constructor))) 53 | { 54 | context.RegisterCodeFix(new FileServiceInterfaceInjectionCodeAction(Title, 55 | context.Document, 56 | classDeclarationSyntax), 57 | context.Diagnostics); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeFixes/FileStreamCodeFix.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.IO.Abstractions.Analyzers.CodeActions; 4 | using System.IO.Abstractions.Analyzers.RoslynToken; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CodeFixes; 9 | using Microsoft.CodeAnalysis.CSharp.Syntax; 10 | 11 | namespace System.IO.Abstractions.Analyzers.CodeFixes; 12 | 13 | /// 14 | /// Code fix provider for . 15 | /// 16 | [Shared] 17 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FileStreamCodeFix))] 18 | public class FileStreamCodeFix : CodeFixProvider 19 | { 20 | private const string Title = "Use IFileStreamFactory instead creation FileStream for improved testability"; 21 | 22 | [ImportingConstructor] 23 | [Obsolete("This exported object must be obtained through the MEF export provider.", true)] 24 | public FileStreamCodeFix() 25 | { 26 | } 27 | 28 | /// 29 | public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(Constants.Io0005); 30 | 31 | /// 32 | public override sealed FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 33 | 34 | /// 35 | public override async Task RegisterCodeFixesAsync(CodeFixContext context) 36 | { 37 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken) 38 | .ConfigureAwait(false); 39 | 40 | var classDeclarationSyntax = root?.FindNode(context.Span) 41 | .FirstAncestorOrSelf(); 42 | 43 | if (classDeclarationSyntax is null || !RoslynClassFileSystem.HasFileSystemField(classDeclarationSyntax)) 44 | { 45 | return; 46 | } 47 | 48 | var creationExpressionSyntax = root.FindNode(context.Span) 49 | .FirstAncestorOrSelf(); 50 | 51 | var fieldDeclarationSyntax = classDeclarationSyntax 52 | .Members 53 | .OfType() 54 | .FirstOrDefault(x => 55 | x.Declaration.Type.NormalizeWhitespace() 56 | .ToFullString() 57 | == RoslynClassFileSystem.GetFileSystemType() 58 | .ToFullString()); 59 | 60 | context.RegisterCodeFix(new FileStreamCodeAction(Title, 61 | context.Document, 62 | creationExpressionSyntax, 63 | fieldDeclarationSyntax), 64 | context.Diagnostics); 65 | } 66 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/CodeFixes/PathCodeFix.cs: -------------------------------------------------------------------------------- 1 | using System.Composition; 2 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 3 | using Microsoft.CodeAnalysis; 4 | using Microsoft.CodeAnalysis.CodeFixes; 5 | 6 | namespace System.IO.Abstractions.Analyzers.CodeFixes; 7 | 8 | /// 9 | /// Code fix provider for . 10 | /// 11 | [Shared] 12 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PathCodeFix))] 13 | public class PathCodeFix : BaseInvokeCodeFix 14 | { 15 | [ImportingConstructor] 16 | [Obsolete("This exported object must be obtained through the MEF export provider.", true)] 17 | public PathCodeFix() 18 | { 19 | } 20 | 21 | /// 22 | protected override string DiagnosticId => Constants.Io0006; 23 | 24 | /// 25 | protected override string Title => "Use IFileSystem.Path for improved testability"; 26 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/Constants.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions.Analyzers.Analyzers.FileSystemTypeAnalyzers; 2 | 3 | namespace System.IO.Abstractions.Analyzers; 4 | 5 | public static class Constants 6 | { 7 | internal const string FileSystem = "System.IO.Abstractions.IFileSystem"; 8 | 9 | internal const string FileSystemInterfaceName = "I" + FileSystemClassName; 10 | 11 | internal const string FileSystemClassName = "FileSystem"; 12 | 13 | internal const string FileSystemNameSpace = "System.IO.Abstractions"; 14 | 15 | internal const string ParameterFileSystemName = "fileSystem"; 16 | 17 | internal const string FieldFileSystemName = "_fileSystem"; 18 | 19 | public const string Io0001 = "IO0001"; 20 | 21 | /// 22 | /// 23 | /// 24 | public const string Io0002 = "IO0002"; 25 | 26 | /// 27 | /// 28 | /// 29 | public const string Io0003 = "IO0003"; 30 | 31 | /// 32 | /// 33 | /// 34 | public const string Io0004 = "IO0004"; 35 | 36 | /// 37 | /// 38 | /// 39 | public const string Io0005 = "IO0005"; 40 | 41 | /// 42 | /// 43 | /// 44 | public const string Io0006 = "IO0006"; 45 | 46 | /// 47 | /// 48 | /// 49 | public const string Io0007 = "IO0007"; 50 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/FileSystemContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace System.IO.Abstractions.Analyzers; 4 | 5 | /// 6 | /// Context for a IFileSystem method invocation. 7 | /// 8 | public class FileSystemContext 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// 14 | public FileSystemContext(Compilation compilation) => FileSystemType = compilation.GetTypeByMetadataName(Constants.FileSystem); 15 | 16 | /// 17 | /// Project has reference to System.IO.Abstractions. 18 | /// 19 | public bool HasReference => FileSystemType is not null; 20 | 21 | private INamedTypeSymbol FileSystemType { get; } 22 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/RoslynToken/RoslynClassFileSystem.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; 6 | 7 | namespace System.IO.Abstractions.Analyzers.RoslynToken; 8 | 9 | public static class RoslynClassFileSystem 10 | { 11 | public static bool HasConstructor(SyntaxNode classDeclaration) => classDeclaration.ChildNodes() 12 | .OfType() 13 | .Any(); 14 | 15 | public static ConstructorDeclarationSyntax GetConstructor(SyntaxNode classDeclaration) => classDeclaration.ChildNodes() 16 | .OfType() 17 | .FirstOrDefault(); 18 | 19 | public static MethodDeclarationSyntax GetMethod(ClassDeclarationSyntax classDeclaration) => classDeclaration.ChildNodes() 20 | .OfType() 21 | .FirstOrDefault(); 22 | 23 | public static bool HasFileSystemField(TypeDeclarationSyntax classDeclaration) => classDeclaration.Members 24 | .OfType() 25 | .Any(x => x.Declaration.Type.NormalizeWhitespace() 26 | .ToFullString() 27 | == GetFileSystemType() 28 | .ToFullString()); 29 | 30 | public static TypeSyntax GetFileSystemType() => SF.ParseTypeName(Constants.FileSystemInterfaceName); 31 | 32 | public static UsingDirectiveSyntax GetFileSystemUsing() => SF.UsingDirective(SF.ParseName(Constants.FileSystemNameSpace)); 33 | 34 | public static UsingDirectiveSyntax GetSystemIoUsing(CompilationUnitSyntax unit) => GetUsing(unit, typeof(Path).Namespace); 35 | 36 | public static UsingDirectiveSyntax GetUsing(CompilationUnitSyntax unit, string usingName) => unit.Usings.FirstOrDefault(x => 37 | x.Name.NormalizeWhitespace() 38 | .ToFullString() 39 | .Equals(usingName, StringComparison.Ordinal)); 40 | 41 | public static FieldDeclarationSyntax CreateFileSystemFieldDeclaration() => SF.FieldDeclaration(SF 42 | .VariableDeclaration(GetFileSystemType()) 43 | .WithVariables(SF.SingletonSeparatedList(SF.VariableDeclarator(SF.Identifier(Constants.FieldFileSystemName))))) 44 | .WithModifiers(SF.TokenList(SF.Token(SyntaxKind.PrivateKeyword), 45 | SF.Token(SyntaxKind.ReadOnlyKeyword))); 46 | 47 | public static bool ConstructorHasFileSystemParameter(BaseMethodDeclarationSyntax constructor) => constructor.ParameterList.Parameters 48 | .Any(x => x.Type != null 49 | && x.Type.NormalizeWhitespace() 50 | .ToFullString() 51 | == GetFileSystemType() 52 | .NormalizeWhitespace() 53 | .ToFullString()); 54 | 55 | public static ParameterSyntax GetFileSystemParameterFromConstructor(ConstructorDeclarationSyntax constructor) => constructor 56 | .ParameterList.Parameters 57 | .FirstOrDefault(x => 58 | x.Type != null 59 | && x.Type.NormalizeWhitespace() 60 | .ToFullString() 61 | == GetFileSystemType() 62 | .NormalizeWhitespace() 63 | .ToFullString()); 64 | 65 | public static FieldDeclarationSyntax GetFileSystemFieldFromClass(ClassDeclarationSyntax @class) => @class.Members 66 | .OfType() 67 | .FirstOrDefault(x => 68 | x.Declaration.Type.NormalizeWhitespace() 69 | .ToFullString() 70 | == GetFileSystemType() 71 | .ToFullString()); 72 | 73 | public static ParameterSyntax CreateFileSystemParameterDeclaration() => SF.Parameter(SF.Identifier(Constants.ParameterFileSystemName)) 74 | .WithType(GetFileSystemType()) 75 | .NormalizeWhitespace(); 76 | 77 | public static CompilationUnitSyntax GetCompilationUnit(SyntaxNode node) => node switch 78 | { 79 | null => null, 80 | CompilationUnitSyntax compilationUnitSyntax => compilationUnitSyntax, 81 | var _ => GetCompilationUnit(node.Parent) 82 | }; 83 | 84 | public static bool ConstructorHasAssignmentExpression(BaseMethodDeclarationSyntax constructor, 85 | string field = Constants.FieldFileSystemName) 86 | { 87 | if (constructor.Body == null) 88 | { 89 | return false; 90 | } 91 | 92 | return constructor.Body.Statements.OfType() 93 | .Any(x => x.Expression.IsKind(SyntaxKind.SimpleAssignmentExpression) 94 | && x.Expression.ChildNodes() 95 | .OfType() 96 | .Any(a => a.Identifier.Text == field)); 97 | } 98 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/System.IO.Abstractions.Analyzers.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | false 6 | True 7 | latest 8 | true 9 | RS0016 10 | 11 | 12 | TestableIO.System.IO.Abstractions.Analyzers 13 | $(PackageId) 14 | 2022.0.0 15 | inyutin_maxim 16 | MIT 17 | https://github.com/TestableIO/System.IO.Abstractions.Analyzers/ 18 | https://avatars1.githubusercontent.com/u/40829877 19 | https://github.com/TestableIO/System.IO.Abstractions.Analyzers.git 20 | false 21 | TestableIO.System.IO.Abstractions.Analyzers 22 | https://github.com/TestableIO/System.IO.Abstractions.Analyzers/releases/tag/v$(PackageVersion) 23 | Copyright © System-IO-Abstractions 24 | TestableIO.System.IO.Abstractions.Analyzers, analyzers, System.IO.Abstractions, Roslyn 25 | true 26 | icon_256x256.png 27 | 28 | 29 | false 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/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 | } -------------------------------------------------------------------------------- /System.IO.Abstractions.Analyzers/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 | } -------------------------------------------------------------------------------- /docs/IO0001.MD: -------------------------------------------------------------------------------- 1 | # IO0001. Use System.IO.Abstractions for improved application testability 2 | 3 | | Topic | Value 4 | | :-- | :-- 5 | | Id | IO0001 6 | | Severity | Warning 7 | | Enabled | True 8 | | Category | System.IO.Abstractions 9 | 10 | ## Description 11 | 12 | Use System.IO.Abstractions for improved application testability 13 | 14 | ## Code with Diagnostic 15 | 16 | ``` csharp 17 | using System.IO; 18 | 19 | namespace SomeNameSpace 20 | { 21 | public class WithOutFileSystem 22 | { 23 | public WithOutFileSystem() 24 | { 25 | } 26 | 27 | public void SomeMethod() 28 | { 29 | const string filePath = "C:\\temp.txt"; 30 | 31 | if (File.Exists(filePath)) 32 | { 33 | File.Delete(filePath); 34 | } 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | ## Code with Fix 41 | 42 | ###### Constructor injection 43 | ``` csharp 44 | using System.IO.Abstractions; 45 | 46 | namespace SomeNameSpace 47 | { 48 | public class WithOutFileSystem 49 | { 50 | private readonly IFileSystem _fileSystem; 51 | 52 | public WithOutFileSystem(IFileSystem fileSystem) 53 | { 54 | _fileSystem = fileSystem; 55 | } 56 | 57 | public void SomeMethod() 58 | { 59 | const string filePath = "C:\\temp.txt"; 60 | 61 | if (File.Exists(filePath)) 62 | { 63 | File.Delete(filePath); 64 | } 65 | } 66 | } 67 | } 68 | ``` 69 | 70 | ###### Creation in constructor 71 | 72 | ```csharp 73 | using System.IO.Abstractions; 74 | 75 | namespace SomeNameSpace 76 | { 77 | public class WithOutFileSystem 78 | { 79 | private readonly IFileSystem _fileSystem; 80 | 81 | public WithOutFileSystem() 82 | { 83 | _fileSystem = new FileSystem(); 84 | } 85 | 86 | public void SomeMethod() 87 | { 88 | const string filePath = "C:\\temp.txt"; 89 | 90 | if (File.Exists(filePath)) 91 | { 92 | File.Delete(filePath); 93 | } 94 | } 95 | } 96 | } 97 | ``` -------------------------------------------------------------------------------- /docs/IO0002.MD: -------------------------------------------------------------------------------- 1 | # IO0002. Replace File class with IFileSystem.File for improved testability 2 | 3 | | Topic | Value 4 | | :-- | :-- 5 | | Id | IO0002 6 | | Severity | Warning 7 | | Enabled | True 8 | | Category | System.IO.Abstractions 9 | 10 | ## Description 11 | 12 | Replace File class with IFileSystem.File for improved testability 13 | 14 | ## Code with Diagnostic 15 | 16 | ``` csharp 17 | using System.IO.Abstractions; 18 | 19 | namespace SomeNameSpace 20 | { 21 | public class WithOutFileSystem 22 | { 23 | private readonly IFileSystem _fileSystem; 24 | 25 | public WithOutFileSystem(IFileSystem fileSystem) 26 | { 27 | _fileSystem = fileSystem; 28 | } 29 | 30 | public void SomeMethod() 31 | { 32 | const string filePath = "C:\\temp.txt"; 33 | 34 | File.Delete(filePath); 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | ## Code with Fix 41 | 42 | ``` csharp 43 | using System.IO.Abstractions; 44 | 45 | namespace SomeNameSpace 46 | { 47 | public class WithOutFileSystem 48 | { 49 | private readonly IFileSystem _fileSystem; 50 | 51 | public WithOutFileSystem(IFileSystem fileSystem) 52 | { 53 | _fileSystem = fileSystem; 54 | } 55 | 56 | public void SomeMethod() 57 | { 58 | const string filePath = "C:\\temp.txt"; 59 | 60 | _fileSystem.File.Delete(filePath); 61 | } 62 | } 63 | } 64 | ``` -------------------------------------------------------------------------------- /docs/IO0003.MD: -------------------------------------------------------------------------------- 1 | # IO0003. Replace Directory class with IFileSystem.Directory for improved testability 2 | 3 | | Topic | Value 4 | | :-- | :-- 5 | | Id | IO0003 6 | | Severity | Warning 7 | | Enabled | True 8 | | Category | System.IO.Abstractions 9 | 10 | ## Description 11 | 12 | Replace Directory class with IFileSystem.Directory for improved testability 13 | 14 | ## Code with Diagnostic 15 | 16 | ``` csharp 17 | using System.IO.Abstractions; 18 | 19 | namespace SomeNameSpace 20 | { 21 | public class WithOutFileSystem 22 | { 23 | private readonly IFileSystem _fileSystem; 24 | 25 | public WithOutFileSystem(IFileSystem fileSystem) 26 | { 27 | _fileSystem = fileSystem; 28 | } 29 | 30 | public void SomeMethod() 31 | { 32 | const string filePath = "C:\\temp"; 33 | 34 | Directory.Exists(filePath); 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | ## Code with Fix 41 | 42 | ``` csharp 43 | using System.IO.Abstractions; 44 | 45 | namespace SomeNameSpace 46 | { 47 | public class WithOutFileSystem 48 | { 49 | private readonly IFileSystem _fileSystem; 50 | 51 | public WithOutFileSystem(IFileSystem fileSystem) 52 | { 53 | _fileSystem = fileSystem; 54 | } 55 | 56 | public void SomeMethod() 57 | { 58 | const string filePath = "C:\\temp"; 59 | 60 | _fileSystem.Directory.Exists(filePath); 61 | } 62 | } 63 | } 64 | ``` -------------------------------------------------------------------------------- /docs/IO0004.MD: -------------------------------------------------------------------------------- 1 | # IO0004. Replace FileInfo class with IFileSystem.FileInfo for improved testability 2 | 3 | | Topic | Value 4 | | :-- | :-- 5 | | Id | IO0004 6 | | Severity | Warning 7 | | Enabled | True 8 | | Category | System.IO.Abstractions 9 | 10 | ## Description 11 | 12 | Replace FileInfo class with IFileSystem.FileInfo for improved testability 13 | 14 | ## Code with Diagnostic 15 | 16 | ``` csharp 17 | using System.IO.Abstractions; 18 | 19 | namespace SomeNameSpace 20 | { 21 | public class WithOutFileSystem 22 | { 23 | private readonly IFileSystem _fileSystem; 24 | 25 | public WithOutFileSystem(IFileSystem fileSystem) 26 | { 27 | _fileSystem = fileSystem; 28 | } 29 | 30 | public void SomeMethod() 31 | { 32 | const string filePath = "C:\\temp.txt"; 33 | 34 | FileInfo fileInf = new FileInfo(filePath); 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | ## Code with Fix 41 | 42 | ``` csharp 43 | using System.IO.Abstractions; 44 | 45 | namespace SomeNameSpace 46 | { 47 | public class WithOutFileSystem 48 | { 49 | private readonly IFileSystem _fileSystem; 50 | 51 | public WithOutFileSystem(IFileSystem fileSystem) 52 | { 53 | _fileSystem = fileSystem; 54 | } 55 | 56 | public void SomeMethod() 57 | { 58 | const string filePath = "C:\\temp.txt"; 59 | 60 | FileInfo fileInf = _fileSystem.FileInfo.FromFileName(filePath); 61 | } 62 | } 63 | } 64 | ``` -------------------------------------------------------------------------------- /docs/IO0005.MD: -------------------------------------------------------------------------------- 1 | # IO0005. Replace FileStream class with IFileSystem.FileStream for improved testability 2 | 3 | | Topic | Value 4 | | :-- | :-- 5 | | Id | IO0005 6 | | Severity | Warning 7 | | Enabled | True 8 | | Category | System.IO.Abstractions 9 | 10 | ## Description 11 | 12 | Replace FileStream class with IFileSystem.FileStream for improved testability 13 | 14 | ## Code with Diagnostic 15 | 16 | ``` csharp 17 | using System.IO.Abstractions; 18 | 19 | namespace SomeNameSpace 20 | { 21 | public class WithOutFileSystem 22 | { 23 | private readonly IFileSystem _fileSystem; 24 | 25 | public WithOutFileSystem(IFileSystem fileSystem) 26 | { 27 | _fileSystem = fileSystem; 28 | } 29 | 30 | public void SomeMethod() 31 | { 32 | const string filePath = "C:\\temp.txt"; 33 | 34 | using (FileStream fstream = new FileStream(filePath, FileMode.OpenOrCreate)) 35 | { 36 | // преобразуем строку в байты 37 | byte[] array = System.Text.Encoding.Default.GetBytes(text); 38 | // запись массива байтов в файл 39 | fstream.Write(array, 0, array.Length); 40 | Console.WriteLine("Текст записан в файл"); 41 | } 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | ## Code with Fix 48 | 49 | ``` csharp 50 | using System.IO.Abstractions; 51 | 52 | namespace SomeNameSpace 53 | { 54 | public class WithOutFileSystem 55 | { 56 | private readonly IFileSystem _fileSystem; 57 | 58 | public WithOutFileSystem(IFileSystem fileSystem) 59 | { 60 | _fileSystem = fileSystem; 61 | } 62 | 63 | public void SomeMethod() 64 | { 65 | const string filePath = "C:\\temp.txt"; 66 | 67 | using (FileStream fstream = _fileSystem.FileStream.Create(filePath, FileMode.OpenOrCreate)) 68 | { 69 | // преобразуем строку в байты 70 | byte[] array = System.Text.Encoding.Default.GetBytes(text); 71 | // запись массива байтов в файл 72 | fstream.Write(array, 0, array.Length); 73 | Console.WriteLine("Текст записан в файл"); 74 | } 75 | } 76 | } 77 | } 78 | ``` -------------------------------------------------------------------------------- /docs/IO0006.MD: -------------------------------------------------------------------------------- 1 | # IO0006. Replace Path class with IFileSystem.Path for improved testability 2 | 3 | | Topic | Value 4 | | :-- | :-- 5 | | Id | IO0006 6 | | Severity | Warning 7 | | Enabled | True 8 | | Category | System.IO.Abstractions 9 | 10 | ## Description 11 | 12 | Replace Path class with IFileSystem.Path for improved testability 13 | 14 | ## Code with Diagnostic 15 | 16 | ``` csharp 17 | using System.IO.Abstractions; 18 | 19 | namespace SomeNameSpace 20 | { 21 | public class WithOutFileSystem 22 | { 23 | private readonly IFileSystem _fileSystem; 24 | 25 | public WithOutFileSystem(IFileSystem fileSystem) 26 | { 27 | _fileSystem = fileSystem; 28 | } 29 | 30 | public void SomeMethod() 31 | { 32 | const string filePath = "C:\\temp.txt"; 33 | 34 | Path.Exists(filePath); 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | ## Code with Fix 41 | 42 | ``` csharp 43 | using System.IO.Abstractions; 44 | 45 | namespace SomeNameSpace 46 | { 47 | public class WithOutFileSystem 48 | { 49 | private readonly IFileSystem _fileSystem; 50 | 51 | public WithOutFileSystem(IFileSystem fileSystem) 52 | { 53 | _fileSystem = fileSystem; 54 | } 55 | 56 | public void SomeMethod() 57 | { 58 | const string filePath = "C:\\temp.txt"; 59 | 60 | _fileSystem.Path.Exists(filePath); 61 | } 62 | } 63 | } 64 | ``` -------------------------------------------------------------------------------- /docs/IO0007.MD: -------------------------------------------------------------------------------- 1 | # IO0007. Replace DirectoryInfo class with IFileSystem.DirectoryInfo for improved testability 2 | 3 | | Topic | Value 4 | | :-- | :-- 5 | | Id | IO0007 6 | | Severity | Warning 7 | | Enabled | True 8 | | Category | System.IO.Abstractions 9 | 10 | ## Description 11 | 12 | Replace DirectoryInfo class with IFileSystem.DirectoryInfo for improved testability 13 | 14 | ## Code with Diagnostic 15 | 16 | ``` csharp 17 | using System.IO.Abstractions; 18 | 19 | namespace SomeNameSpace 20 | { 21 | public class WithOutFileSystem 22 | { 23 | private readonly IFileSystem _fileSystem; 24 | 25 | public WithOutFileSystem(IFileSystem fileSystem) 26 | { 27 | _fileSystem = fileSystem; 28 | } 29 | 30 | public void SomeMethod() 31 | { 32 | const string filePath = "C:\\temp.txt"; 33 | 34 | DirectoryInfo dirInfo = new DirectoryInfo(filePath); 35 | } 36 | } 37 | } 38 | ``` 39 | 40 | ## Code with Fix 41 | 42 | ``` csharp 43 | using System.IO.Abstractions; 44 | 45 | namespace SomeNameSpace 46 | { 47 | public class WithOutFileSystem 48 | { 49 | private readonly IFileSystem _fileSystem; 50 | 51 | public WithOutFileSystem(IFileSystem fileSystem) 52 | { 53 | _fileSystem = fileSystem; 54 | } 55 | 56 | public void SomeMethod() 57 | { 58 | const string filePath = "C:\\temp.txt"; 59 | 60 | DirectoryInfo dirInfo = _fileSystem.DirectoryInfo.FromDirectoryName(filePath); 61 | } 62 | } 63 | } 64 | ``` -------------------------------------------------------------------------------- /icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TestableIO/System.IO.Abstractions.Analyzers/5a2a6cfa0c465769325c42e847cafb85e39dd872/icon_256x256.png -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | --------------------------------------------------------------------------------