Skip to content

Commit fa11747

Browse files
authored
Merge pull request #436 from PowerShell/kapilmb/workspace-wide-reference
Enable reference look up in entire workspace
2 parents 87c2095 + 131de1d commit fa11747

File tree

8 files changed

+157
-19
lines changed

8 files changed

+157
-19
lines changed

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,8 @@ protected async Task HandleReferencesRequest(
679679
FindReferencesResult referencesResult =
680680
await editorSession.LanguageService.FindReferencesOfSymbol(
681681
foundSymbol,
682-
editorSession.Workspace.ExpandScriptReferences(scriptFile));
682+
editorSession.Workspace.ExpandScriptReferences(scriptFile),
683+
editorSession.Workspace);
683684

684685
Location[] referenceLocations = null;
685686

src/PowerShellEditorServices/Language/LanguageService.cs

+56-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System;
88
using System.Collections;
99
using System.Collections.Generic;
10+
using System.Collections.Specialized;
1011
using System.Linq;
1112
using System.Management.Automation;
1213
using System.Management.Automation.Language;
@@ -248,10 +249,12 @@ public FindOccurrencesResult FindSymbolsInFile(ScriptFile scriptFile)
248249
/// </summary>
249250
/// <param name="foundSymbol">The symbol to find all references for</param>
250251
/// <param name="referencedFiles">An array of scriptFiles too search for references in</param>
252+
/// <param name="workspace">The workspace that will be searched for symbols</param>
251253
/// <returns>FindReferencesResult</returns>
252254
public async Task<FindReferencesResult> FindReferencesOfSymbol(
253255
SymbolReference foundSymbol,
254-
ScriptFile[] referencedFiles)
256+
ScriptFile[] referencedFiles,
257+
Workspace workspace)
255258
{
256259
if (foundSymbol != null)
257260
{
@@ -262,9 +265,26 @@ public async Task<FindReferencesResult> FindReferencesOfSymbol(
262265
// Make sure aliases have been loaded
263266
await GetAliases();
264267

268+
// We want to look for references first in referenced files, hence we use ordered dictionary
269+
var fileMap = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
270+
foreach(ScriptFile file in referencedFiles)
271+
{
272+
fileMap.Add(file.FilePath, file);
273+
}
274+
275+
var allFiles = workspace.EnumeratePSFiles();
276+
foreach (var file in allFiles)
277+
{
278+
if (!fileMap.Contains(file))
279+
{
280+
fileMap.Add(file, new ScriptFile(file, null, this.powerShellContext.LocalPowerShellVersion.Version));
281+
}
282+
}
283+
265284
List<SymbolReference> symbolReferences = new List<SymbolReference>();
266-
foreach (ScriptFile file in referencedFiles)
285+
foreach (var fileName in fileMap.Keys)
267286
{
287+
var file = (ScriptFile)fileMap[fileName];
268288
IEnumerable<SymbolReference> symbolReferencesinFile =
269289
AstOperations
270290
.FindReferencesOfSymbol(
@@ -316,6 +336,8 @@ public async Task<GetDefinitionResult> GetDefinitionOfSymbol(
316336
workspace.ExpandScriptReferences(
317337
sourceFile);
318338

339+
var filesSearched = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
340+
319341
// look through the referenced files until definition is found
320342
// or there are no more file to look through
321343
SymbolReference foundDefinition = null;
@@ -326,14 +348,45 @@ public async Task<GetDefinitionResult> GetDefinitionOfSymbol(
326348
referencedFiles[i].ScriptAst,
327349
foundSymbol);
328350

351+
filesSearched.Add(referencedFiles[i].FilePath);
329352
if (foundDefinition != null)
330353
{
331354
foundDefinition.FilePath = referencedFiles[i].FilePath;
332355
break;
333356
}
334357
}
335358

336-
// if definition is not found in referenced files
359+
// if the definition the not found in referenced files
360+
// look for it in all the files in the workspace
361+
if (foundDefinition == null)
362+
{
363+
// Get a list of all powershell files in the workspace path
364+
var allFiles = workspace.EnumeratePSFiles();
365+
foreach (var file in allFiles)
366+
{
367+
if (filesSearched.Contains(file))
368+
{
369+
continue;
370+
}
371+
372+
Token[] tokens = null;
373+
ParseError[] parseErrors = null;
374+
foundDefinition =
375+
AstOperations.FindDefinitionOfSymbol(
376+
Parser.ParseFile(file, out tokens, out parseErrors),
377+
foundSymbol);
378+
379+
filesSearched.Add(file);
380+
if (foundDefinition != null)
381+
{
382+
foundDefinition.FilePath = file;
383+
break;
384+
}
385+
386+
}
387+
}
388+
389+
// if definition is not found in file in the workspace
337390
// look for it in the builtin commands
338391
if (foundDefinition == null)
339392
{

src/PowerShellEditorServices/Workspace/ScriptFile.cs

+23-5
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,32 @@ public ScriptFile(
161161
string clientFilePath,
162162
string initialBuffer,
163163
Version powerShellVersion)
164+
: this(
165+
filePath,
166+
clientFilePath,
167+
new StringReader(initialBuffer),
168+
powerShellVersion)
164169
{
165-
this.FilePath = filePath;
166-
this.ClientFilePath = clientFilePath;
167-
this.IsAnalysisEnabled = true;
168-
this.powerShellVersion = powerShellVersion;
169170

170-
this.SetFileContents(initialBuffer);
171171
}
172+
/// <summary>
173+
/// Creates a new ScriptFile instance with the specified filepath.
174+
/// </summary>
175+
/// <param name="filePath">The path at which the script file resides.</param>
176+
/// <param name="clientFilePath">The path which the client uses to identify the file.</param>
177+
/// <param name="powerShellVersion">The version of PowerShell for which the script is being parsed.</param>
178+
public ScriptFile (
179+
string filePath,
180+
string clientFilePath,
181+
Version powerShellVersion)
182+
: this(
183+
filePath,
184+
clientFilePath,
185+
File.ReadAllText(filePath),
186+
powerShellVersion)
187+
{
188+
189+
}
172190

173191
#endregion
174192

src/PowerShellEditorServices/Workspace/Workspace.cs

+22
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,28 @@ public string GetRelativePath(string filePath)
213213
return resolvedPath;
214214
}
215215

216+
/// <summary>
217+
/// Enumerate all the PowerShell (ps1, psm1, psd1) files in the workspace in a recursive manner
218+
/// </summary>
219+
/// <returns>An enumerator over the PowerShell files found in the workspace</returns>
220+
public IEnumerable<string> EnumeratePSFiles()
221+
{
222+
if (WorkspacePath == null
223+
|| !Directory.Exists(WorkspacePath))
224+
{
225+
yield break;
226+
}
227+
228+
var patterns = new string[] { @"*.ps1", @"*.psm1", @"*.psd1" };
229+
foreach(var pattern in patterns)
230+
{
231+
foreach (var file in Directory.EnumerateFiles(WorkspacePath, pattern, SearchOption.AllDirectories))
232+
{
233+
yield return file;
234+
}
235+
}
236+
}
237+
216238
#endregion
217239

218240
#region Private Methods
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using Microsoft.PowerShell.EditorServices;
7+
8+
namespace Microsoft.PowerShell.EditorServices.Test.Shared.Definition
9+
{
10+
public class FindsFunctionDefinitionInWorkspace
11+
{
12+
public static readonly ScriptRegion SourceDetails =
13+
new ScriptRegion
14+
{
15+
File = @"References\ReferenceFileD.ps1",
16+
StartLineNumber = 1,
17+
StartColumnNumber = 2
18+
};
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
My-FunctionInFileE "this is my function"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function My-FunctionInFileE
2+
{
3+
4+
}

test/PowerShellEditorServices.Test/Language/LanguageServiceTests.cs

+29-10
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public class LanguageServiceTests : IDisposable
2323
private Workspace workspace;
2424
private LanguageService languageService;
2525
private PowerShellContext powerShellContext;
26-
26+
private const string baseSharedScriptPath = @"..\..\..\..\PowerShellEditorServices.Test.Shared\";
27+
2728

2829
public LanguageServiceTests()
2930
{
@@ -156,6 +157,21 @@ await this.GetDefinition(
156157
Assert.Equal("My-Function", definition.SymbolName);
157158
}
158159

160+
[Fact]
161+
public async Task LanguageServiceFindsFunctionDefinitionInWorkspace()
162+
{
163+
var definitionResult =
164+
await this.GetDefinition(
165+
FindsFunctionDefinitionInWorkspace.SourceDetails,
166+
new Workspace(this.powerShellContext.LocalPowerShellVersion.Version)
167+
{
168+
WorkspacePath = Path.Combine(baseSharedScriptPath, @"References")
169+
});
170+
var definition = definitionResult.FoundDefinition;
171+
Assert.EndsWith("ReferenceFileE.ps1", definition.FilePath);
172+
Assert.Equal("My-FunctionInFileE", definition.SymbolName);
173+
}
174+
159175
[Fact]
160176
public async Task LanguageServiceFindsVariableDefinition()
161177
{
@@ -227,7 +243,7 @@ await this.GetReferences(
227243

228244
Assert.Equal(4, refsResult.FoundReferences.Count());
229245
}
230-
246+
231247
[Fact]
232248
public async Task LanguageServiceFindsReferencesOnFileWithReferencesFileC()
233249
{
@@ -297,12 +313,9 @@ public void LanguageServiceFindsSymbolsInNoSymbolsFile()
297313

298314
private ScriptFile GetScriptFile(ScriptRegion scriptRegion)
299315
{
300-
const string baseSharedScriptPath =
301-
@"..\..\..\..\PowerShellEditorServices.Test.Shared\";
302-
303316
string resolvedPath =
304317
Path.Combine(
305-
baseSharedScriptPath,
318+
baseSharedScriptPath,
306319
scriptRegion.File);
307320

308321
return
@@ -329,7 +342,7 @@ await this.languageService.FindParameterSetsInFile(
329342
scriptRegion.StartColumnNumber);
330343
}
331344

332-
private async Task<GetDefinitionResult> GetDefinition(ScriptRegion scriptRegion)
345+
private async Task<GetDefinitionResult> GetDefinition(ScriptRegion scriptRegion, Workspace workspace)
333346
{
334347
ScriptFile scriptFile = GetScriptFile(scriptRegion);
335348

@@ -345,7 +358,12 @@ private async Task<GetDefinitionResult> GetDefinition(ScriptRegion scriptRegion)
345358
await this.languageService.GetDefinitionOfSymbol(
346359
scriptFile,
347360
symbolReference,
348-
this.workspace);
361+
workspace);
362+
}
363+
364+
private async Task<GetDefinitionResult> GetDefinition(ScriptRegion scriptRegion)
365+
{
366+
return await GetDefinition(scriptRegion, this.workspace);
349367
}
350368

351369
private async Task<FindReferencesResult> GetReferences(ScriptRegion scriptRegion)
@@ -363,11 +381,12 @@ private async Task<FindReferencesResult> GetReferences(ScriptRegion scriptRegion
363381
return
364382
await this.languageService.FindReferencesOfSymbol(
365383
symbolReference,
366-
this.workspace.ExpandScriptReferences(scriptFile));
384+
this.workspace.ExpandScriptReferences(scriptFile),
385+
this.workspace);
367386
}
368387

369388
private FindOccurrencesResult GetOccurrences(ScriptRegion scriptRegion)
370-
{
389+
{
371390
return
372391
this.languageService.FindOccurrencesInFile(
373392
GetScriptFile(scriptRegion),

0 commit comments

Comments
 (0)