Skip to content

Commit 46fe7d4

Browse files
committed
(todo) Use globbing library
1 parent 904f05d commit 46fe7d4

File tree

7 files changed

+418
-112
lines changed

7 files changed

+418
-112
lines changed

PowerShellEditorServices.build.ps1

+2
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,14 @@ Schema is:
5151
}
5252
#>
5353
$script:RequiredBuildAssets = @{
54+
# TODO: Add glob.cs.dll
5455
$script:ModuleBinPath = @{
5556
'PowerShellEditorServices' = @(
5657
'publish/Serilog.dll',
5758
'publish/Serilog.Sinks.Async.dll',
5859
'publish/Serilog.Sinks.Console.dll',
5960
'publish/Serilog.Sinks.File.dll',
61+
'publish/Microsoft.Extensions.FileSystemGlobbing.dll',
6062
'Microsoft.PowerShell.EditorServices.dll',
6163
'Microsoft.PowerShell.EditorServices.pdb'
6264
)

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

+9
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,9 @@ await this.RunScriptDiagnosticsAsync(
725725
}
726726
}
727727

728+
// NEED TO CHECK FILE GLOBBING HERE!!
729+
// This is the handler for textDocument/definition - https://microsoft.github.io/language-server-protocol/specification#textDocument_definition
730+
// It searches the workspace for a function reference, which is then used as the definition
728731
protected async Task HandleDefinitionRequestAsync(
729732
TextDocumentPositionParams textDocumentPosition,
730733
RequestContext<Location[]> requestContext)
@@ -748,6 +751,7 @@ protected async Task HandleDefinitionRequestAsync(
748751
await editorSession.LanguageService.GetDefinitionOfSymbolAsync(
749752
scriptFile,
750753
foundSymbol,
754+
// Probably something here...
751755
editorSession.Workspace);
752756

753757
if (definition != null)
@@ -764,6 +768,9 @@ await editorSession.LanguageService.GetDefinitionOfSymbolAsync(
764768
await requestContext.SendResultAsync(definitionLocations.ToArray());
765769
}
766770

771+
// NEED TO CHECK FILE GLOBBING HERE!!
772+
// This is the handler for textDocument/References - https://microsoft.github.io/language-server-protocol/specification#textDocument_references
773+
// It searches the workspace for a function reference
767774
protected async Task HandleReferencesRequestAsync(
768775
ReferencesParams referencesParams,
769776
RequestContext<Location[]> requestContext)
@@ -781,7 +788,9 @@ protected async Task HandleReferencesRequestAsync(
781788
FindReferencesResult referencesResult =
782789
await editorSession.LanguageService.FindReferencesOfSymbolAsync(
783790
foundSymbol,
791+
// Probably something here...
784792
editorSession.Workspace.ExpandScriptReferences(scriptFile),
793+
// Probably something here...
785794
editorSession.Workspace);
786795

787796
Location[] referenceLocations = s_emptyLocationResult;

src/PowerShellEditorServices/Language/LanguageService.cs

+2
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ public async Task<FindReferencesResult> FindReferencesOfSymbolAsync(
337337
fileMap[scriptFile.FilePath] = scriptFile;
338338
}
339339

340+
// Probably something here...
340341
foreach (string filePath in workspace.EnumeratePSFiles())
341342
{
342343
if (!fileMap.Contains(filePath))
@@ -451,6 +452,7 @@ public async Task<GetDefinitionResult> GetDefinitionOfSymbolAsync(
451452
if (foundDefinition == null)
452453
{
453454
// Get a list of all powershell files in the workspace path
455+
// Probably something here...
454456
IEnumerable<string> allFiles = workspace.EnumeratePSFiles();
455457
foreach (string file in allFiles)
456458
{

src/PowerShellEditorServices/PowerShellEditorServices.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
2121
<PackageReference Include="Serilog.Sinks.Async" Version="1.3.0" />
2222
<PackageReference Include="System.Runtime.Extensions" Version="4.3.0" />
23+
<PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="2.2.0" />
2324
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
2425
</ItemGroup>
2526
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">

src/PowerShellEditorServices/Workspace/Workspace.cs

+51-112
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using System.Security;
1212
using System.Text;
1313
using System.Runtime.InteropServices;
14+
using Microsoft.Extensions.FileSystemGlobbing;
15+
using Microsoft.Extensions.FileSystemGlobbing.Abstractions;
1416

1517
namespace Microsoft.PowerShell.EditorServices
1618
{
@@ -22,11 +24,21 @@ public class Workspace
2224
{
2325
#region Private Fields
2426

25-
private static readonly string[] s_psFilePatterns = new []
27+
private static readonly string[] s_psFileExtensions = new []
2628
{
27-
"*.ps1",
28-
"*.psm1",
29-
"*.psd1"
29+
".ps1",
30+
".psm1",
31+
".psd1"
32+
};
33+
// .Net Core doesn't appear to use the same three letter pattern matching rule although the docs
34+
// suggest it should be find the '.ps1xml' files because we search for the pattern '*.ps1'
35+
// 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_
36+
private static readonly string[] s_psFileExtensionsDotNetFramework = new []
37+
{
38+
".ps1",
39+
".psm1",
40+
".psd1",
41+
".ps1xml"
3042
};
3143

3244
private ILogger logger;
@@ -282,135 +294,62 @@ public string GetRelativePath(string filePath)
282294
}
283295

284296
/// <summary>
285-
/// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner
297+
/// Whether the current runtime is Dot Net Framework as opposed to Core or Native
286298
/// </summary>
287-
/// <returns>An enumerator over the PowerShell files found in the workspace</returns>
288-
public IEnumerable<string> EnumeratePSFiles()
299+
private Boolean IsDotNetFrameWork()
289300
{
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;
301+
return RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework");
298302
}
299303

300-
#endregion
301-
302-
#region Private Methods
303-
304304
/// <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.
305+
/// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner
309306
/// </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)
307+
/// <returns>An enumerator over the PowerShell files found in the workspace</returns>
308+
public IEnumerable<string> EnumeratePSFiles(
309+
string[] excludeGlobs = null,
310+
string[] includeGlobs = null,
311+
int maxDepth = 64,
312+
Boolean ignoreReparsePoints = false
313+
)
314314
{
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
315+
if (WorkspacePath == null || !Directory.Exists(WorkspacePath))
371316
{
372-
subDirs = Directory.GetDirectories(folderPath);
317+
yield break;
373318
}
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);
379319

380-
return;
381-
}
382-
catch (PathTooLongException e)
320+
var matcher = new Microsoft.Extensions.FileSystemGlobbing.Matcher();
321+
if (includeGlobs == null || includeGlobs.Length == 0)
383322
{
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;
323+
// Include everything as the FileSystemWrapper will filter the
324+
// files based on extension prematurely
325+
matcher.AddInclude("**/*");
389326
}
390-
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
327+
else
391328
{
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;
329+
foreach (string pattern in includeGlobs) { matcher.AddInclude(pattern); }
397330
}
398-
catch (Exception e)
331+
if (excludeGlobs != null)
399332
{
400-
this.logger.WriteHandledException(
401-
$"Could not enumerate directories in the path '{folderPath}' due to an exception",
402-
e);
403-
404-
return;
333+
foreach (string pattern in excludeGlobs) { matcher.AddExclude(pattern); }
405334
}
406335

407-
408-
foreach (string subDir in subDirs)
336+
var fsFactory = new WorkspaceFileSystemWrapperFactory(
337+
WorkspacePath,
338+
maxDepth,
339+
this.IsDotNetFrameWork() ? s_psFileExtensionsDotNetFramework : s_psFileExtensions,
340+
ignoreReparsePoints,
341+
this.logger
342+
);
343+
var result = matcher.Execute(fsFactory.RootDirectory);
344+
foreach (FilePatternMatch item in result.Files)
409345
{
410-
RecursivelyEnumerateFiles(subDir, ref foundFiles, currDepth: currDepth + 1);
346+
yield return Path.Combine(WorkspacePath, item.Path.Replace('/', Path.DirectorySeparatorChar));
411347
}
412348
}
413349

350+
#endregion
351+
352+
#region Private Methods
414353
/// <summary>
415354
/// Recusrively searches through referencedFiles in scriptFiles
416355
/// and builds a Dictonary of the file references

0 commit comments

Comments
 (0)