11
11
using System . Security ;
12
12
using System . Text ;
13
13
using System . Runtime . InteropServices ;
14
+ using Microsoft . Extensions . FileSystemGlobbing ;
15
+ using Microsoft . Extensions . FileSystemGlobbing . Abstractions ;
14
16
15
17
namespace Microsoft . PowerShell . EditorServices
16
18
{
@@ -22,11 +24,28 @@ public class Workspace
22
24
{
23
25
#region Private Fields
24
26
25
- private static readonly string [ ] s_psFilePatterns = new [ ]
27
+ private static readonly string [ ] s_psFileExtensions =
26
28
{
27
- "*.ps1" ,
28
- "*.psm1" ,
29
- "*.psd1"
29
+ ".ps1" ,
30
+ ".psm1" ,
31
+ ".psd1"
32
+ } ;
33
+
34
+ // .Net Core doesn't appear to use the same three letter pattern matching rule although the docs
35
+ // suggest it should be find the '.ps1xml' files because we search for the pattern '*.ps1'.
36
+ // ref https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netcore-2.1#System_IO_Directory_GetFiles_System_String_System_String_System_IO_EnumerationOptions_
37
+ private static readonly string [ ] s_psFileExtensionsDotNetFramework =
38
+ {
39
+ ".ps1" ,
40
+ ".psm1" ,
41
+ ".psd1" ,
42
+ ".ps1xml"
43
+ } ;
44
+
45
+ // An array of globs which includes everything.
46
+ private static readonly string [ ] s_psIncludeAllGlob = new [ ]
47
+ {
48
+ "**/*"
30
49
} ;
31
50
32
51
private ILogger logger ;
@@ -42,6 +61,16 @@ public class Workspace
42
61
/// </summary>
43
62
public string WorkspacePath { get ; set ; }
44
63
64
+ /// <summary>
65
+ /// Gets or sets the default list of file globs to exclude during workspace searches.
66
+ /// </summary>
67
+ public List < string > ExcludeFilesGlob { get ; set ; }
68
+
69
+ /// <summary>
70
+ /// Gets or sets whether the workspace should follow symlinks in search operations.
71
+ /// </summary>
72
+ public bool FollowSymlinks { get ; set ; }
73
+
45
74
#endregion
46
75
47
76
#region Constructors
@@ -55,6 +84,8 @@ public Workspace(Version powerShellVersion, ILogger logger)
55
84
{
56
85
this . powerShellVersion = powerShellVersion ;
57
86
this . logger = logger ;
87
+ this . ExcludeFilesGlob = new List < string > ( ) ;
88
+ this . FollowSymlinks = true ;
58
89
}
59
90
60
91
#endregion
@@ -282,135 +313,64 @@ public string GetRelativePath(string filePath)
282
313
}
283
314
284
315
/// <summary>
285
- /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner
316
+ /// Whether the current runtime is Dot Net Framework as opposed to Core or Native
286
317
/// </summary>
287
- /// <returns>An enumerator over the PowerShell files found in the workspace</returns>
288
- public IEnumerable < string > EnumeratePSFiles ( )
318
+ private bool IsDotNetFrameWork ( )
289
319
{
290
- if ( WorkspacePath == null || ! Directory . Exists ( WorkspacePath ) )
291
- {
292
- return Enumerable . Empty < string > ( ) ;
293
- }
294
-
295
- var foundFiles = new List < string > ( ) ;
296
- this . RecursivelyEnumerateFiles ( WorkspacePath , ref foundFiles ) ;
297
- return foundFiles ;
320
+ return RuntimeInformation . FrameworkDescription . StartsWith ( ".NET Framework" , StringComparison . Ordinal ) ;
298
321
}
299
322
300
- #endregion
301
-
302
- #region Private Methods
303
-
304
323
/// <summary>
305
- /// Find PowerShell files recursively down from a given directory path.
306
- /// Currently collects files in depth-first order.
307
- /// Directory.GetFiles(folderPath, pattern, SearchOption.AllDirectories) would provide this,
308
- /// but a cycle in the filesystem will cause that to enter an infinite loop.
324
+ /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner, using default values.
309
325
/// </summary>
310
- /// <param name="folderPath">The path of the current directory to find files in</param>
311
- /// <param name="foundFiles">The accumulator for files found so far.</param>
312
- /// <param name="currDepth">The current depth of the recursion from the original base directory.</param>
313
- private void RecursivelyEnumerateFiles ( string folderPath , ref List < string > foundFiles , int currDepth = 0 )
326
+ /// <returns>An enumerator over the PowerShell files found in the workspace.</returns>
327
+ public IEnumerable < string > EnumeratePSFiles ( )
314
328
{
315
- const int recursionDepthLimit = 64 ;
316
-
317
- // Look for any PowerShell files in the current directory
318
- foreach ( string pattern in s_psFilePatterns )
319
- {
320
- string [ ] psFiles ;
321
- try
322
- {
323
- psFiles = Directory . GetFiles ( folderPath , pattern , SearchOption . TopDirectoryOnly ) ;
324
- }
325
- catch ( DirectoryNotFoundException e )
326
- {
327
- this . logger . WriteHandledException (
328
- $ "Could not enumerate files in the path '{ folderPath } ' due to it being an invalid path",
329
- e ) ;
330
-
331
- continue ;
332
- }
333
- catch ( PathTooLongException e )
334
- {
335
- this . logger . WriteHandledException (
336
- $ "Could not enumerate files in the path '{ folderPath } ' due to the path being too long",
337
- e ) ;
338
-
339
- continue ;
340
- }
341
- catch ( Exception e ) when ( e is SecurityException || e is UnauthorizedAccessException )
342
- {
343
- this . logger . WriteHandledException (
344
- $ "Could not enumerate files in the path '{ folderPath } ' due to the path not being accessible",
345
- e ) ;
346
-
347
- continue ;
348
- }
349
- catch ( Exception e )
350
- {
351
- this . logger . WriteHandledException (
352
- $ "Could not enumerate files in the path '{ folderPath } ' due to an exception",
353
- e ) ;
354
-
355
- continue ;
356
- }
357
-
358
- foundFiles . AddRange ( psFiles ) ;
359
- }
360
-
361
- // Prevent unbounded recursion here
362
- if ( currDepth >= recursionDepthLimit )
363
- {
364
- this . logger . Write ( LogLevel . Warning , $ "Recursion depth limit hit for path { folderPath } ") ;
365
- return ;
366
- }
367
-
368
- // Add the recursive directories to search next
369
- string [ ] subDirs ;
370
- try
371
- {
372
- subDirs = Directory . GetDirectories ( folderPath ) ;
373
- }
374
- catch ( DirectoryNotFoundException e )
375
- {
376
- this . logger . WriteHandledException (
377
- $ "Could not enumerate directories in the path '{ folderPath } ' due to it being an invalid path",
378
- e ) ;
379
-
380
- return ;
381
- }
382
- catch ( PathTooLongException e )
383
- {
384
- this . logger . WriteHandledException (
385
- $ "Could not enumerate directories in the path '{ folderPath } ' due to the path being too long",
386
- e ) ;
387
-
388
- return ;
389
- }
390
- catch ( Exception e ) when ( e is SecurityException || e is UnauthorizedAccessException )
391
- {
392
- this . logger . WriteHandledException (
393
- $ "Could not enumerate directories in the path '{ folderPath } ' due to the path not being accessible",
394
- e ) ;
329
+ return EnumeratePSFiles (
330
+ ExcludeFilesGlob . ToArray ( ) ,
331
+ s_psIncludeAllGlob ,
332
+ maxDepth : 64 ,
333
+ ignoreReparsePoints : ! FollowSymlinks
334
+ ) ;
335
+ }
395
336
396
- return ;
397
- }
398
- catch ( Exception e )
337
+ /// <summary>
338
+ /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner.
339
+ /// </summary>
340
+ /// <returns>An enumerator over the PowerShell files found in the workspace.</returns>
341
+ public IEnumerable < string > EnumeratePSFiles (
342
+ string [ ] excludeGlobs ,
343
+ string [ ] includeGlobs ,
344
+ int maxDepth ,
345
+ bool ignoreReparsePoints
346
+ )
347
+ {
348
+ if ( WorkspacePath == null || ! Directory . Exists ( WorkspacePath ) )
399
349
{
400
- this . logger . WriteHandledException (
401
- $ "Could not enumerate directories in the path '{ folderPath } ' due to an exception",
402
- e ) ;
403
-
404
- return ;
350
+ yield break ;
405
351
}
406
352
407
-
408
- foreach ( string subDir in subDirs )
353
+ var matcher = new Microsoft . Extensions . FileSystemGlobbing . Matcher ( ) ;
354
+ foreach ( string pattern in includeGlobs ) { matcher . AddInclude ( pattern ) ; }
355
+ foreach ( string pattern in excludeGlobs ) { matcher . AddExclude ( pattern ) ; }
356
+
357
+ var fsFactory = new WorkspaceFileSystemWrapperFactory (
358
+ WorkspacePath ,
359
+ maxDepth ,
360
+ this . IsDotNetFrameWork ( ) ? s_psFileExtensionsDotNetFramework : s_psFileExtensions ,
361
+ ignoreReparsePoints ,
362
+ this . logger
363
+ ) ;
364
+ var result = matcher . Execute ( fsFactory . RootDirectory ) ;
365
+ foreach ( FilePatternMatch item in result . Files )
409
366
{
410
- RecursivelyEnumerateFiles ( subDir , ref foundFiles , currDepth : currDepth + 1 ) ;
367
+ yield return Path . Combine ( WorkspacePath , item . Path . Replace ( '/' , Path . DirectorySeparatorChar ) ) ;
411
368
}
412
369
}
413
370
371
+ #endregion
372
+
373
+ #region Private Methods
414
374
/// <summary>
415
375
/// Recusrively searches through referencedFiles in scriptFiles
416
376
/// and builds a Dictonary of the file references
0 commit comments