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,29 @@ public class Workspace
22
24
{
23
25
#region Private Fields
24
26
25
- private static readonly string [ ] s_psFilePatterns = new [ ]
27
+ // List of all file extensions considered PowerShell files in the .Net Core Framework.
28
+ private static readonly string [ ] s_psFileExtensionsCoreFramework =
26
29
{
27
- "*.ps1" ,
28
- "*.psm1" ,
29
- "*.psd1"
30
+ ".ps1" ,
31
+ ".psm1" ,
32
+ ".psd1"
33
+ } ;
34
+
35
+ // .Net Core doesn't appear to use the same three letter pattern matching rule although the docs
36
+ // suggest it should be find the '.ps1xml' files because we search for the pattern '*.ps1'.
37
+ // 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_
38
+ private static readonly string [ ] s_psFileExtensionsFullFramework =
39
+ {
40
+ ".ps1" ,
41
+ ".psm1" ,
42
+ ".psd1" ,
43
+ ".ps1xml"
44
+ } ;
45
+
46
+ // An array of globs which includes everything.
47
+ private static readonly string [ ] s_psIncludeAllGlob = new [ ]
48
+ {
49
+ "**/*"
30
50
} ;
31
51
32
52
private ILogger logger ;
@@ -42,6 +62,16 @@ public class Workspace
42
62
/// </summary>
43
63
public string WorkspacePath { get ; set ; }
44
64
65
+ /// <summary>
66
+ /// Gets or sets the default list of file globs to exclude during workspace searches.
67
+ /// </summary>
68
+ public List < string > ExcludeFilesGlob { get ; set ; }
69
+
70
+ /// <summary>
71
+ /// Gets or sets whether the workspace should follow symlinks in search operations.
72
+ /// </summary>
73
+ public bool FollowSymlinks { get ; set ; }
74
+
45
75
#endregion
46
76
47
77
#region Constructors
@@ -55,6 +85,8 @@ public Workspace(Version powerShellVersion, ILogger logger)
55
85
{
56
86
this . powerShellVersion = powerShellVersion ;
57
87
this . logger = logger ;
88
+ this . ExcludeFilesGlob = new List < string > ( ) ;
89
+ this . FollowSymlinks = true ;
58
90
}
59
91
60
92
#endregion
@@ -282,135 +314,56 @@ public string GetRelativePath(string filePath)
282
314
}
283
315
284
316
/// <summary>
285
- /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner
317
+ /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner, using default values.
286
318
/// </summary>
287
- /// <returns>An enumerator over the PowerShell files found in the workspace</returns>
319
+ /// <returns>An enumerator over the PowerShell files found in the workspace. </returns>
288
320
public IEnumerable < string > EnumeratePSFiles ( )
289
321
{
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 ;
322
+ return EnumeratePSFiles (
323
+ ExcludeFilesGlob . ToArray ( ) ,
324
+ s_psIncludeAllGlob ,
325
+ maxDepth : 64 ,
326
+ ignoreReparsePoints : ! FollowSymlinks
327
+ ) ;
298
328
}
299
329
300
- #endregion
301
-
302
- #region Private Methods
303
-
304
330
/// <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.
331
+ /// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner.
309
332
/// </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 )
333
+ /// <returns>An enumerator over the PowerShell files found in the workspace.</returns>
334
+ public IEnumerable < string > EnumeratePSFiles (
335
+ string [ ] excludeGlobs ,
336
+ string [ ] includeGlobs ,
337
+ int maxDepth ,
338
+ bool ignoreReparsePoints
339
+ )
314
340
{
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 ) ;
395
-
396
- return ;
397
- }
398
- catch ( Exception e )
341
+ if ( WorkspacePath == null || ! Directory . Exists ( WorkspacePath ) )
399
342
{
400
- this . logger . WriteHandledException (
401
- $ "Could not enumerate directories in the path '{ folderPath } ' due to an exception",
402
- e ) ;
403
-
404
- return ;
343
+ yield break ;
405
344
}
406
345
407
-
408
- foreach ( string subDir in subDirs )
346
+ var matcher = new Microsoft . Extensions . FileSystemGlobbing . Matcher ( ) ;
347
+ foreach ( string pattern in includeGlobs ) { matcher . AddInclude ( pattern ) ; }
348
+ foreach ( string pattern in excludeGlobs ) { matcher . AddExclude ( pattern ) ; }
349
+
350
+ var fsFactory = new WorkspaceFileSystemWrapperFactory (
351
+ WorkspacePath ,
352
+ maxDepth ,
353
+ Utils . IsNetCore ? s_psFileExtensionsCoreFramework : s_psFileExtensionsFullFramework ,
354
+ ignoreReparsePoints ,
355
+ logger
356
+ ) ;
357
+ var fileMatchResult = matcher . Execute ( fsFactory . RootDirectory ) ;
358
+ foreach ( FilePatternMatch item in fileMatchResult . Files )
409
359
{
410
- RecursivelyEnumerateFiles ( subDir , ref foundFiles , currDepth : currDepth + 1 ) ;
360
+ yield return Path . Combine ( WorkspacePath , item . Path . Replace ( '/' , Path . DirectorySeparatorChar ) ) ;
411
361
}
412
362
}
413
363
364
+ #endregion
365
+
366
+ #region Private Methods
414
367
/// <summary>
415
368
/// Recusrively searches through referencedFiles in scriptFiles
416
369
/// and builds a Dictonary of the file references
0 commit comments