11using Xunit ;
22using System ;
33using System . Collections . Generic ;
4+ using System . IO ;
5+ using System . Text ;
46using Semmle . Extraction . CSharp . DependencyFetching ;
7+ using Semmle . Util . Logging ;
58
69namespace Semmle . Extraction . Tests
710{
811 internal class UnsafeFileReaderStub : IUnsafeFileReader
912 {
10- private readonly IEnumerable < string > lines ;
13+ private readonly IReadOnlyDictionary < string , string > contents ;
14+ public int OpenCount { get ; private set ; }
15+ public MemoryStream ? LastStream { get ; private set ; }
1116
12- public UnsafeFileReaderStub ( IEnumerable < string > lines )
17+ public UnsafeFileReaderStub ( IReadOnlyDictionary < string , string > contents )
1318 {
14- this . lines = lines ;
19+ this . contents = contents ;
1520 }
1621
17- public IEnumerable < string > ReadLines ( string file )
22+ public Stream OpenRead ( string file )
1823 {
19- foreach ( var line in lines )
20- {
21- yield return line ;
22- }
24+ OpenCount ++ ;
25+ LastStream = new MemoryStream ( Encoding . UTF8 . GetBytes ( contents [ file ] ) ) ;
26+ return LastStream ;
2327 }
2428 }
2529
2630 internal class TestFileContent : FileContent
2731 {
28- public TestFileContent ( IEnumerable < string > lines ) : base ( new LoggerStub ( ) ,
29- new List < string > ( ) { "test1.cs" } ,
30- new UnsafeFileReaderStub ( lines ) )
32+ public TestFileContent ( IEnumerable < string > lines , string file = "test1.csproj" ) : base ( new LoggerStub ( ) ,
33+ new [ ] { file } ,
34+ new UnsafeFileReaderStub ( new Dictionary < string , string > { [ file ] = string . Join ( Environment . NewLine , lines ) } ) )
3135 { }
3236 }
3337
@@ -41,10 +45,11 @@ public void TestFileContent1()
4145 {
4246 "<Project Sdk=\" Microsoft.NET.Sdk\" >" ,
4347 "<PackageReference Include=\" DotNetAnalyzers.DocumentationAnalyzers\" Version=\" 1.0.0-beta.59\" PrivateAssets=\" all\" />" ,
44- "<PackageReference Version=\" 7.0.0\" Include=\" Microsoft.CodeAnalysis.NetAnalyzers\" PrivateAssets=\" all\" />" ,
45- "<PackageReference Include=\" StyleCop.Analyzers\" Version=\" 1.2.0-beta.406\" >" ,
48+ "<PackageReference Version=\" 7.0.0\" Include=\" Microsoft.CodeAnalysis.NetAnalyzers\" PrivateAssets=\" all\" />" ,
49+ "<PackageReference Include=\" StyleCop.Analyzers\" Version=\" 1.2.0-beta.406\" ></PackageReference> " ,
4650 "<!-- <PackageReference Include=\" NUnit\" Version=\" 3.10.1\" /> -->" ,
47- "<FrameworkReference Include=\" My.Framework\" />"
51+ "<FrameworkReference Include=\" My.Framework\" />" ,
52+ "</Project>"
4853 } ;
4954 var fileContent = new TestFileContent ( lines ) ;
5055
@@ -68,8 +73,9 @@ public void TestFileContent2()
6873 {
6974 "<Project Sdk=\" Microsoft.NET.Sdk.Web\" >" ,
7075 "<FrameworkReference Include=\" My.Framework\" />" ,
71- "<PackageReference Version=\" 7.0.0\" Include=\" Microsoft.CodeAnalysis.NetAnalyzers\" PrivateAssets=\" all\" />" ,
72- "<PackageReference Include=\" StyleCop.Analyzers\" Version=\" 1.2.0-beta.406\" >"
76+ "<PackageReference Version=\" 7.0.0\" Include=\" Microsoft.CodeAnalysis.NetAnalyzers\" PrivateAssets=\" all\" />" ,
77+ "<PackageReference Include=\" StyleCop.Analyzers\" Version=\" 1.2.0-beta.406\" />" ,
78+ "</Project>"
7379 } ;
7480 var fileContent = new TestFileContent ( lines ) ;
7581
@@ -148,9 +154,11 @@ public void TestFileContent_ImplicitUsingsAdditional()
148154 // Setup
149155 var lines = new List < string > ( )
150156 {
157+ "<Project>" ,
151158 "<Using Include=\" Ns0.Ns1\" />" ,
152159 "<Using Include=\" Ns2\" />" ,
153160 "<Using Remove=\" Ns3\" />" ,
161+ "</Project>"
154162 } ;
155163 var fileContent = new TestFileContent ( lines ) ;
156164
@@ -173,6 +181,7 @@ public void TestFileContent_LegacyProjectStructure()
173181 <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
174182 <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
175183 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
184+ </Project>
176185 """ ;
177186 var lines = input . Split ( Environment . NewLine ) ;
178187 var fileContent = new TestFileContent ( lines ) ;
@@ -210,5 +219,122 @@ public void TestFileContent_NewProjectStructure()
210219 Assert . True ( isNew ) ;
211220 Assert . False ( isLegacy ) ;
212221 }
222+
223+ [ Fact ]
224+ public void TestFileContent_XmlSyntax ( )
225+ {
226+ var input =
227+ """
228+ <msb:Project xmlns:msb="http://schemas.microsoft.com/developer/msbuild/2003"
229+ Sdk='Microsoft.NET.Sdk'>
230+ <msb:ItemGroup>
231+ <msb:PackageReference
232+ Version='1.0' Include='My.Package' />
233+ <msb:PackageReference Include='Other.Package'><Version>2.0</Version></msb:PackageReference>
234+ <msb:PackageReference Update='Ignored.Package' />
235+ <msb:PackageReference Include='' />
236+ <msb:FrameworkReference
237+ Include='Microsoft.AspNetCore.App' />
238+ <msb:Using Include='My.Namespace' />
239+ </msb:ItemGroup>
240+ <msb:PropertyGroup>
241+ <msb:ImplicitUsings>
242+ ENABLE
243+ </msb:ImplicitUsings><msb:UseWPF> true </msb:UseWPF>
244+ <msb:UseWindowsForms><![CDATA[true]]></msb:UseWindowsForms>
245+ </msb:PropertyGroup>
246+ </msb:Project>
247+ """ ;
248+ var fileContent = new TestFileContent ( new [ ] { input } ) ;
249+
250+ Assert . Equal ( 2 , fileContent . AllPackages . Count ) ;
251+ Assert . Contains ( new PackageReference ( "my.package" , PackageReferenceSource . SdkCsProj ) , fileContent . AllPackages ) ;
252+ Assert . Contains ( new PackageReference ( "other.package" , PackageReferenceSource . SdkCsProj ) , fileContent . AllPackages ) ;
253+ Assert . True ( fileContent . IsAspNetCoreDetected ) ;
254+ Assert . True ( fileContent . UseImplicitUsings ) ;
255+ Assert . True ( fileContent . UseWpf ) ;
256+ Assert . True ( fileContent . UseWindowsForms ) ;
257+ Assert . Contains ( "My.Namespace" , fileContent . CustomImplicitUsings ) ;
258+ }
259+
260+ [ Fact ]
261+ public void TestFileContent_CommentsAreIgnored ( )
262+ {
263+ var input =
264+ """
265+ <Project>
266+ <!--
267+ <Project Sdk="Microsoft.NET.Sdk.Web" />
268+ <PackageReference Include="Ignored.Package" />
269+ <FrameworkReference Include="Microsoft.AspNetCore.App" />
270+ <ImplicitUsings>true</ImplicitUsings>
271+ <UseWPF>true</UseWPF>
272+ <UseWindowsForms>true</UseWindowsForms>
273+ <Using Include="Ignored.Namespace" />
274+ <Import Project="Microsoft.CSharp.targets" />
275+ -->
276+ </Project>
277+ """ ;
278+ var fileContent = new TestFileContent ( new [ ] { input } ) ;
279+
280+ Assert . Empty ( fileContent . AllPackages ) ;
281+ Assert . Empty ( fileContent . CustomImplicitUsings ) ;
282+ Assert . False ( fileContent . UseAspNetCoreDlls ) ;
283+ Assert . False ( fileContent . UseImplicitUsings ) ;
284+ Assert . False ( fileContent . UseWpf ) ;
285+ Assert . False ( fileContent . UseWindowsForms ) ;
286+ Assert . False ( fileContent . IsLegacyProjectStructureUsed ) ;
287+ Assert . False ( fileContent . IsNewProjectStructureUsed ) ;
288+ }
289+
290+ [ Fact ]
291+ public void TestFileContent_PackagesConfig ( )
292+ {
293+ var input =
294+ """
295+ <?xml version="1.0" encoding="utf-8"?>
296+ <packages>
297+ <package
298+ version='1.0' id='My.Package' />
299+ <package id="Other.Package" version="2.0" />
300+ <package id="" />
301+ <package />
302+ <!-- <package id="Ignored.Package" /> -->
303+ <PackageReference Include="Ignored.Package" />
304+ </packages>
305+ """ ;
306+ var fileContent = new TestFileContent ( new [ ] { input } , "PACKAGES.CONFIG" ) ;
307+
308+ Assert . Equal ( 2 , fileContent . AllPackages . Count ) ;
309+ Assert . Contains ( new PackageReference ( "my.package" , PackageReferenceSource . PackagesConfig ) , fileContent . AllPackages ) ;
310+ Assert . Contains ( new PackageReference ( "other.package" , PackageReferenceSource . PackagesConfig ) , fileContent . AllPackages ) ;
311+ }
312+
313+ private sealed class RecordingLogger : ILogger
314+ {
315+ public List < string > Messages { get ; } = new List < string > ( ) ;
316+ public void Log ( Severity severity , string message , int ? threadId = null ) => Messages . Add ( message ) ;
317+ public void Dispose ( ) { }
318+ }
319+
320+ [ Theory ]
321+ [ InlineData ( "This is not XML." ) ]
322+ [ InlineData ( "<Project><PackageReference Include='Incomplete'" ) ]
323+ [ InlineData ( "<!DOCTYPE Project [<!ENTITY sdk 'Microsoft.NET.Sdk.Web'>]><Project Sdk='&sdk;' />" ) ]
324+ public void TestFileContent_InvalidXmlDoesNotPreventReadingOtherFiles ( string invalidXml )
325+ {
326+ var logger = new RecordingLogger ( ) ;
327+ var stub = new UnsafeFileReaderStub ( new Dictionary < string , string >
328+ {
329+ [ "invalid.csproj" ] = invalidXml ,
330+ [ "valid.csproj" ] = "<Project Sdk='Microsoft.NET.Sdk.Web'><PackageReference Include='My.Package' /></Project>"
331+ } ) ;
332+ var fileContent = new FileContent ( logger , [ "invalid.csproj" , "valid.csproj" ] , stub ) ;
333+
334+ Assert . True ( fileContent . IsAspNetCoreDetected ) ;
335+ Assert . Contains ( new PackageReference ( "my.package" , PackageReferenceSource . SdkCsProj ) , fileContent . AllPackages ) ;
336+ Assert . Contains ( "Failed to parse invalid.csproj as an XML file." , logger . Messages ) ;
337+ Assert . Equal ( 2 , stub . OpenCount ) ;
338+ }
213339 }
214340}
0 commit comments