diff --git a/src/PowerShellEditorServices/Extensions/Api/DocumentSymbolService.cs b/src/PowerShellEditorServices/Extensions/Api/DocumentSymbolService.cs
deleted file mode 100644
index 58137ce49..000000000
--- a/src/PowerShellEditorServices/Extensions/Api/DocumentSymbolService.cs
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Management.Automation.Language;
-using Microsoft.PowerShell.EditorServices.Services;
-using Microsoft.PowerShell.EditorServices.Services.TextDocument;
-
-using Internal = Microsoft.PowerShell.EditorServices.Services.Symbols;
-
-// TODO: This is currently disabled in the csproj
-// Redesign this API and bring it back once it's fit for purpose
-
-namespace Microsoft.PowerShell.EditorServices.Extensions.Services
-{
- ///
- /// A way to define symbols on a higher level
- ///
- public enum SymbolType
- {
- ///
- /// The symbol type is unknown
- ///
- Unknown = 0,
-
- ///
- /// The symbol is a vairable
- ///
- Variable = 1,
-
- ///
- /// The symbol is a function
- ///
- Function = 2,
-
- ///
- /// The symbol is a parameter
- ///
- Parameter = 3,
-
- ///
- /// The symbol is a DSC configuration
- ///
- Configuration = 4,
-
- ///
- /// The symbol is a workflow
- ///
- Workflow = 5,
-
- ///
- /// The symbol is a hashtable key
- ///
- HashtableKey = 6,
- }
-
- ///
- /// Interface to instantiate to create a provider of document symbols.
- ///
- public interface IDocumentSymbolProvider
- {
- ///
- /// The unique ID of this provider.
- ///
- string ProviderId { get; }
-
- ///
- /// Run this provider to provide symbols to PSES from the given file.
- ///
- /// The script file to provide symbols for.
- /// Symbols about the file.
- IEnumerable ProvideDocumentSymbols(IEditorScriptFile scriptFile);
- }
-
- ///
- /// A class that holds the type, name, script extent, and source line of a symbol
- ///
- [DebuggerDisplay("SymbolType = {SymbolType}, SymbolName = {SymbolName}")]
- public class SymbolReference
- {
- ///
- /// Constructs an instance of a SymbolReference
- ///
- /// The higher level type of the symbol
- /// The script extent of the symbol
- /// The file path of the symbol
- /// The line contents of the given symbol (defaults to empty string)
- public SymbolReference(SymbolType symbolType, IScriptExtent scriptExtent)
- : this(symbolType, scriptExtent.Text, scriptExtent)
- {
- }
-
- ///
- /// Constructs and instance of a SymbolReference
- ///
- /// The higher level type of the symbol
- /// The name of the symbol
- /// The script extent of the symbol
- /// The file path of the symbol
- /// The line contents of the given symbol (defaults to empty string)
- public SymbolReference(
- SymbolType symbolType,
- string symbolName,
- IScriptExtent scriptExtent)
- : this(symbolType, scriptExtent, symbolName, filePath: string.Empty, sourceLine: string.Empty)
- {
- }
-
- public SymbolReference(
- SymbolType symbolType,
- IScriptExtent scriptExtent,
- string symbolName,
- string filePath)
- : this(symbolType, scriptExtent, symbolName, filePath, sourceLine: string.Empty)
- {
- }
-
- public SymbolReference(SymbolType symbolType, IScriptExtent scriptExtent, string symbolName, string filePath, string sourceLine)
- {
- // TODO: Verify params
- SymbolType = symbolType;
- ScriptRegion = ScriptRegion.Create(scriptExtent);
- SymbolName = symbolName;
- FilePath = filePath;
- SourceLine = sourceLine;
-
- // TODO: Make sure end column number usage is correct
- }
-
- #region Properties
-
- ///
- /// Gets the symbol's type
- ///
- public SymbolType SymbolType { get; }
-
- ///
- /// Gets the name of the symbol
- ///
- public string SymbolName { get; }
-
- ///
- /// Gets the script extent of the symbol
- ///
- public ScriptRegion ScriptRegion { get; }
-
- ///
- /// Gets the contents of the line the given symbol is on
- ///
- public string SourceLine { get; }
-
- ///
- /// Gets the path of the file in which the symbol was found.
- ///
- public string FilePath { get; internal set; }
-
- #endregion
- }
-
- ///
- /// Service for registration of document symbol providers in PSES.
- ///
- public interface IDocumentSymbolService
- {
- ///
- /// Register a document symbol provider by its ID.
- /// If another provider is already registered by the same ID, this will fail and return false.
- ///
- /// The document symbol provider to register.
- /// True if the symbol provider was successfully registered, false otherwise.
- bool RegisterDocumentSymbolProvider(IDocumentSymbolProvider documentSymbolProvider);
-
- ///
- /// Deregister a symbol provider of the given ID.
- ///
- /// The ID of the provider to deregister.
- /// True if a provider by the given ID was deregistered, false if no such provider was found.
- bool DeregisterDocumentSymbolProvider(string providerId);
- }
-
- internal class DocumentSymbolService : IDocumentSymbolService
- {
- private readonly SymbolsService _symbolsService;
-
- internal DocumentSymbolService(SymbolsService symbolsService)
- {
- _symbolsService = symbolsService;
- }
-
- public bool RegisterDocumentSymbolProvider(IDocumentSymbolProvider documentSymbolProvider)
- {
- return _symbolsService.TryRegisterDocumentSymbolProvider(new ExternalDocumentSymbolProviderAdapter(documentSymbolProvider));
- }
-
- public bool DeregisterDocumentSymbolProvider(string providerId)
- {
- return _symbolsService.DeregisterCodeLensProvider(providerId);
- }
- }
-
- internal class ExternalDocumentSymbolProviderAdapter : Internal.IDocumentSymbolProvider
- {
- private readonly IDocumentSymbolProvider _symbolProvider;
-
- public ExternalDocumentSymbolProviderAdapter(
- IDocumentSymbolProvider externalDocumentSymbolProvider)
- {
- _symbolProvider = externalDocumentSymbolProvider;
- }
-
- public string ProviderId => _symbolProvider.ProviderId;
-
- public IEnumerable ProvideDocumentSymbols(ScriptFile scriptFile)
- {
- foreach (SymbolReference symbolReference in _symbolProvider.ProvideDocumentSymbols(new EditorScriptFile(scriptFile)))
- {
- yield return new ExternalSymbolReferenceAdapter(symbolReference);
- }
- }
- }
-
- internal class ExternalSymbolReferenceAdapter : Internal.ISymbolReference
- {
- private readonly SymbolReference _symbolReference;
-
- public ExternalSymbolReferenceAdapter(SymbolReference symbolReference)
- {
- _symbolReference = symbolReference;
- }
-
- public Internal.SymbolType SymbolType => _symbolReference.SymbolType.ToInternalSymbolType();
-
- public string SymbolName => _symbolReference.SymbolName;
-
- public ScriptRegion ScriptRegion => _symbolReference.ScriptRegion;
-
- public string SourceLine => _symbolReference.SourceLine;
-
- public string FilePath => _symbolReference.FilePath;
- }
-
- internal static class SymbolTypeExtensions
- {
- public static Internal.SymbolType ToInternalSymbolType(this SymbolType symbolType)
- {
- switch (symbolType)
- {
- case SymbolType.Unknown:
- return Internal.SymbolType.Unknown;
-
- case SymbolType.Variable:
- return Internal.SymbolType.Variable;
-
- case SymbolType.Function:
- return Internal.SymbolType.Function;
-
- case SymbolType.Parameter:
- return Internal.SymbolType.Parameter;
-
- case SymbolType.Configuration:
- return Internal.SymbolType.Configuration;
-
- case SymbolType.Workflow:
- return Internal.SymbolType.Workflow;
-
- case SymbolType.HashtableKey:
- return Internal.SymbolType.HashtableKey;
-
- default:
- throw new InvalidOperationException($"Unknown symbol type '{symbolType}'");
- }
- }
- }
-}
-
diff --git a/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs b/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs
index dc2809962..d750923fb 100644
--- a/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs
+++ b/src/PowerShellEditorServices/Extensions/Api/EditorExtensionServiceProvider.cs
@@ -42,7 +42,6 @@ internal EditorExtensionServiceProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
LanguageServer = new LanguageServerService(_serviceProvider.GetService());
- //DocumentSymbols = new DocumentSymbolService(_serviceProvider.GetService());
ExtensionCommands = new ExtensionCommandService(_serviceProvider.GetService());
Workspace = new WorkspaceService(_serviceProvider.GetService());
EditorContext = new EditorContextService(_serviceProvider.GetService());
diff --git a/src/PowerShellEditorServices/PowerShellEditorServices.csproj b/src/PowerShellEditorServices/PowerShellEditorServices.csproj
index 9eefce9de..ef9b2696b 100644
--- a/src/PowerShellEditorServices/PowerShellEditorServices.csproj
+++ b/src/PowerShellEditorServices/PowerShellEditorServices.csproj
@@ -55,7 +55,6 @@
-
diff --git a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs
index 881bbc037..50ddcfc22 100644
--- a/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs
+++ b/src/PowerShellEditorServices/Services/CodeLens/PesterCodeLensProvider.cs
@@ -7,7 +7,6 @@
using Microsoft.PowerShell.EditorServices.Services;
using Microsoft.PowerShell.EditorServices.Services.Symbols;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
-using Microsoft.PowerShell.EditorServices.Utility;
using Newtonsoft.Json.Linq;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Serialization;
@@ -47,7 +46,7 @@ public PesterCodeLensProvider(ConfigurationService configurationService)
private static CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, ScriptFile scriptFile)
{
string word = pesterSymbol.Command == PesterCommandType.It ? "test" : "tests";
- CodeLens[] codeLensResults = new CodeLens[]
+ return new CodeLens[]
{
new CodeLens()
{
@@ -92,8 +91,6 @@ private static CodeLens[] GetPesterLens(PesterSymbolReference pesterSymbol, Scri
}
}
};
-
- return codeLensResults;
}
///
@@ -120,7 +117,7 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken can
continue;
}
- // Skip codelense for setup/teardown block
+ // Skip CodeLens for setup/teardown block
if (!PesterSymbolReference.IsPesterTestCommand(pesterSymbol.Command))
{
continue;
diff --git a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs
index eb50ce2d5..6e1e4cc81 100644
--- a/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs
+++ b/src/PowerShellEditorServices/Services/CodeLens/ReferencesCodeLensProvider.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
@@ -9,7 +8,6 @@
using Microsoft.PowerShell.EditorServices.Services;
using Microsoft.PowerShell.EditorServices.Services.Symbols;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
-using Microsoft.PowerShell.EditorServices.Utility;
using Newtonsoft.Json.Linq;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
@@ -22,8 +20,6 @@ namespace Microsoft.PowerShell.EditorServices.CodeLenses
///
internal class ReferencesCodeLensProvider : ICodeLensProvider
{
- private static readonly Location[] s_emptyLocationArray = Array.Empty();
-
///
/// The document symbol provider to supply symbols to generate the code lenses.
///
@@ -57,14 +53,19 @@ public ReferencesCodeLensProvider(WorkspaceService workspaceService, SymbolsServ
///
/// The PowerShell script file to get code lenses for.
///
- /// An array of CodeLenses describing all functions in the given script file.
+ /// An array of CodeLenses describing all functions, classes and enums in the given script file.
public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken cancellationToken)
{
List acc = new();
- foreach (SymbolReference sym in _symbolProvider.ProvideDocumentSymbols(scriptFile))
+ foreach (SymbolReference symbol in _symbolProvider.ProvideDocumentSymbols(scriptFile))
{
cancellationToken.ThrowIfCancellationRequested();
- if (sym.SymbolType == SymbolType.Function)
+ // TODO: Can we support more here?
+ if (symbol.IsDeclaration &&
+ symbol.Type is
+ SymbolType.Function or
+ SymbolType.Class or
+ SymbolType.Enum)
{
acc.Add(new CodeLens
{
@@ -73,7 +74,7 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken can
Uri = scriptFile.DocumentUri,
ProviderId = nameof(ReferencesCodeLensProvider)
}, LspSerializer.Instance.JsonSerializer),
- Range = sym.ScriptRegion.ToRange(),
+ Range = symbol.NameRegion.ToRange(),
});
}
}
@@ -82,68 +83,49 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile, CancellationToken can
}
///
- /// Take a codelens and create a new codelens object with updated references.
+ /// Take a CodeLens and create a new CodeLens object with updated references.
///
/// The old code lens to get updated references for.
///
///
- /// A new code lens object describing the same data as the old one but with updated references.
+ /// A new CodeLens object describing the same data as the old one but with updated references.
public async Task ResolveCodeLens(
CodeLens codeLens,
ScriptFile scriptFile,
CancellationToken cancellationToken)
{
- ScriptFile[] references = _workspaceService.ExpandScriptReferences(
- scriptFile);
-
- SymbolReference foundSymbol = SymbolsService.FindFunctionDefinitionAtLocation(
+ SymbolReference foundSymbol = SymbolsService.FindSymbolDefinitionAtLocation(
scriptFile,
codeLens.Range.Start.Line + 1,
codeLens.Range.Start.Character + 1);
- List referencesResult = await _symbolsService.FindReferencesOfSymbol(
- foundSymbol,
- references,
- _workspaceService,
- cancellationToken).ConfigureAwait(false);
-
- Location[] referenceLocations;
- if (referencesResult == null)
- {
- referenceLocations = s_emptyLocationArray;
- }
- else
+ List acc = new();
+ foreach (SymbolReference foundReference in await _symbolsService.ScanForReferencesOfSymbolAsync(
+ foundSymbol, cancellationToken).ConfigureAwait(false))
{
- List acc = new();
- foreach (SymbolReference foundReference in referencesResult)
+ // We only show lenses on declarations, so we exclude those from the references.
+ if (foundReference.IsDeclaration)
{
- // This async method is pretty dense with synchronous code
- // so it's helpful to add some yields.
- await Task.Yield();
- cancellationToken.ThrowIfCancellationRequested();
- if (IsReferenceDefinition(foundSymbol, foundReference))
- {
- continue;
- }
-
- DocumentUri uri = DocumentUri.From(foundReference.FilePath);
- // For any vscode-notebook-cell, we need to ignore the backing file on disk.
- if (uri.Scheme == "file" &&
- scriptFile.DocumentUri.Scheme == "vscode-notebook-cell" &&
- uri.Path == scriptFile.DocumentUri.Path)
- {
- continue;
- }
+ continue;
+ }
- acc.Add(new Location
- {
- Uri = uri,
- Range = foundReference.ScriptRegion.ToRange()
- });
+ DocumentUri uri = DocumentUri.From(foundReference.FilePath);
+ // For any vscode-notebook-cell, we need to ignore the backing file on disk.
+ if (uri.Scheme == "file" &&
+ scriptFile.DocumentUri.Scheme == "vscode-notebook-cell" &&
+ uri.Path == scriptFile.DocumentUri.Path)
+ {
+ continue;
}
- referenceLocations = acc.ToArray();
+
+ acc.Add(new Location
+ {
+ Uri = uri,
+ Range = foundReference.NameRegion.ToRange()
+ });
}
+ Location[] referenceLocations = acc.ToArray();
return new CodeLens
{
Data = codeLens.Data,
@@ -163,27 +145,6 @@ public async Task ResolveCodeLens(
};
}
- ///
- /// Check whether a SymbolReference is the actual definition of that symbol.
- ///
- /// The symbol definition that may be referenced.
- /// The reference symbol to check.
- /// True if the reference is not a reference to the definition, false otherwise.
- private static bool IsReferenceDefinition(
- SymbolReference definition,
- SymbolReference reference)
- {
- // First check if we are in the same file as the definition. if we are...
- // check if it's on the same line number.
-
- // TODO: Do we care about two symbol definitions of the same name?
- // if we do, how could we possibly know that a reference in one file is a reference
- // of a particular symbol definition?
- return
- definition.FilePath == reference.FilePath &&
- definition.ScriptRegion.StartLineNumber == reference.ScriptRegion.StartLineNumber;
- }
-
///
/// Get the code lens header for the number of references on a definition,
/// given the number of references.
diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs
index 39e12974b..3c5e7a1d2 100644
--- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs
+++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs
@@ -35,7 +35,9 @@ public LegacyReadLine(
_onIdleAction = onIdleAction;
}
+#pragma warning disable CA1502 // Cyclomatic complexity we don't care about
public override string ReadLine(CancellationToken cancellationToken)
+#pragma warning restore CA1502
{
string inputBeforeCompletion = null;
string inputAfterCompletion = null;
diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs
index 5a6c8fb31..419aebf8c 100644
--- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs
+++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs
@@ -29,7 +29,9 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host
using Microsoft.PowerShell.EditorServices.Server;
using OmniSharp.Extensions.DebugAdapter.Protocol.Server;
+#pragma warning disable CA1506 // Coupling complexity we don't care about
internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext, IInternalPowerShellExecutionService
+#pragma warning restore CA1506
{
internal const string DefaultPrompt = "> ";
diff --git a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs
index f5590a026..da4a89a34 100644
--- a/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs
+++ b/src/PowerShellEditorServices/Services/PowerShell/Utility/CommandHelpers.cs
@@ -14,6 +14,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility
{
///
/// Provides utility methods for working with PowerShell commands.
+ /// TODO: Handle the `fn ` prefix better.
///
internal static class CommandHelpers
{
@@ -114,6 +115,12 @@ public static async Task GetCommandInfoAsync(
Validate.IsNotNull(nameof(commandName), commandName);
Validate.IsNotNull(nameof(executionService), executionService);
+ // Remove the bucket identifier from symbol references.
+ if (commandName.StartsWith("fn "))
+ {
+ commandName = commandName.Substring(3);
+ }
+
// If we have a CommandInfo cached, return that.
if (s_commandInfoCache.TryGetValue(commandName, out CommandInfo cmdInfo))
{
@@ -239,11 +246,11 @@ public static async Task GetAliasesAsync(
// TODO: When we move to netstandard2.1, we can use another overload which generates
// static delegates and thus reduces allocations.
s_cmdletToAliasCache.AddOrUpdate(
- aliasInfo.Definition,
- (_) => new List { aliasInfo.Name },
- (_, v) => { v.Add(aliasInfo.Name); return v; });
+ "fn " + aliasInfo.Definition,
+ (_) => new List { "fn " + aliasInfo.Name },
+ (_, v) => { v.Add("fn " + aliasInfo.Name); return v; });
- s_aliasToCmdletCache.TryAdd(aliasInfo.Name, aliasInfo.Definition);
+ s_aliasToCmdletCache.TryAdd("fn " + aliasInfo.Name, "fn " + aliasInfo.Definition);
}
return new AliasMap(
diff --git a/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbolProvider.cs
index 3b01e382a..6abcdb162 100644
--- a/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbolProvider.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/IDocumentSymbolProvider.cs
@@ -20,6 +20,6 @@ internal interface IDocumentSymbolProvider
/// The document for which SymbolReferences should be provided.
///
/// An IEnumerable collection of SymbolReferences.
- IEnumerable ProvideDocumentSymbols(ScriptFile scriptFile);
+ IEnumerable ProvideDocumentSymbols(ScriptFile scriptFile);
}
}
diff --git a/src/PowerShellEditorServices/Services/Symbols/ParameterSetSignatures.cs b/src/PowerShellEditorServices/Services/Symbols/ParameterSetSignatures.cs
index aa094ce7b..8f8af2ca4 100644
--- a/src/PowerShellEditorServices/Services/Symbols/ParameterSetSignatures.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/ParameterSetSignatures.cs
@@ -46,8 +46,8 @@ public ParameterSetSignatures(IEnumerable commandInfoSe
paramSetSignatures.Add(new ParameterSetSignature(setInfo));
}
Signatures = paramSetSignatures.ToArray();
- CommandName = foundSymbol.ScriptRegion.Text;
- ScriptRegion = foundSymbol.ScriptRegion;
+ CommandName = foundSymbol.NameRegion.Text;
+ ScriptRegion = foundSymbol.NameRegion;
}
}
diff --git a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs
index 3c6469bc6..f38e72372 100644
--- a/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/PesterDocumentSymbolProvider.cs
@@ -17,7 +17,7 @@ internal class PesterDocumentSymbolProvider : IDocumentSymbolProvider
{
string IDocumentSymbolProvider.ProviderId => nameof(PesterDocumentSymbolProvider);
- IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols(
+ IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols(
ScriptFile scriptFile)
{
if (!scriptFile.FilePath.EndsWith(
@@ -75,6 +75,8 @@ private static bool IsPesterCommand(CommandAst commandAst)
return true;
}
+ private static readonly char[] DefinitionTrimChars = new char[] { ' ', '{' };
+
///
/// Convert a CommandAst known to represent a Pester command and a reference to the scriptfile
/// it is in into symbol representing a Pester call for code lens
@@ -84,7 +86,11 @@ private static bool IsPesterCommand(CommandAst commandAst)
/// a symbol representing the Pester call containing metadata for CodeLens to use
private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFile scriptFile, CommandAst pesterCommandAst)
{
- string testLine = scriptFile.GetLine(pesterCommandAst.Extent.StartLineNumber);
+ string symbolName = scriptFile
+ .GetLine(pesterCommandAst.Extent.StartLineNumber)
+ .TrimStart()
+ .TrimEnd(DefinitionTrimChars);
+
PesterCommandType? commandName = PesterSymbolReference.GetCommandType(pesterCommandAst.GetCommandName());
if (commandName == null)
{
@@ -126,7 +132,7 @@ private static PesterSymbolReference ConvertPesterAstToSymbolReference(ScriptFil
return new PesterSymbolReference(
scriptFile,
commandName.Value,
- testLine,
+ symbolName,
testName,
pesterCommandAst.Extent
);
@@ -196,7 +202,7 @@ internal enum PesterCommandType
/// Provides a specialization of SymbolReference containing
/// extra information about Pester test symbols.
///
- internal class PesterSymbolReference : SymbolReference
+ internal record PesterSymbolReference : SymbolReference
{
///
/// Lookup for Pester keywords we support. Ideally we could extract these from Pester itself
@@ -206,10 +212,9 @@ internal class PesterSymbolReference : SymbolReference
.Cast()
.ToDictionary(pct => pct.ToString(), pct => pct, StringComparer.OrdinalIgnoreCase);
- private static readonly char[] DefinitionTrimChars = new char[] { ' ', '{' };
-
///
/// Gets the name of the test
+ /// TODO: We could get rid of this and use DisplayName now, but first attempt didn't work great.
///
public string TestName { get; }
@@ -221,15 +226,17 @@ internal class PesterSymbolReference : SymbolReference
internal PesterSymbolReference(
ScriptFile scriptFile,
PesterCommandType commandType,
- string testLine,
+ string symbolName,
string testName,
IScriptExtent scriptExtent)
: base(
SymbolType.Function,
- testLine.TrimStart().TrimEnd(DefinitionTrimChars),
+ symbolName,
+ symbolName + " { }",
+ scriptExtent,
scriptExtent,
- scriptFile.FilePath,
- testLine)
+ scriptFile,
+ isDeclaration: true)
{
Command = commandType;
TestName = testName;
diff --git a/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs
index ff3ffe47b..a0a032578 100644
--- a/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/PsdDocumentSymbolProvider.cs
@@ -17,13 +17,13 @@ internal class PsdDocumentSymbolProvider : IDocumentSymbolProvider
{
string IDocumentSymbolProvider.ProviderId => nameof(PsdDocumentSymbolProvider);
- IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols(
+ IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols(
ScriptFile scriptFile)
{
if ((scriptFile.FilePath?.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase) == true) ||
IsPowerShellDataFileAst(scriptFile.ScriptAst))
{
- FindHashtableSymbolsVisitor findHashtableSymbolsVisitor = new();
+ FindHashtableSymbolsVisitor findHashtableSymbolsVisitor = new(scriptFile);
scriptFile.ScriptAst.Visit(findHashtableSymbolsVisitor);
return findHashtableSymbolsVisitor.SymbolReferences;
}
@@ -35,7 +35,7 @@ IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols(
/// Checks if a given ast represents the root node of a *.psd1 file.
///
/// The abstract syntax tree of the given script
- /// true if the AST represts a *.psd1 file, otherwise false
+ /// true if the AST represents a *.psd1 file, otherwise false
public static bool IsPowerShellDataFileAst(Ast ast)
{
// sometimes we don't have reliable access to the filename
diff --git a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs
index 2c3242c83..620fe2ea1 100644
--- a/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/ReferenceTable.cs
@@ -7,8 +7,9 @@
using System.Collections.Concurrent;
using System.Management.Automation.Language;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
-using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility;
using Microsoft.PowerShell.EditorServices.Services.Symbols;
+using System.Collections.Generic;
+using System.Linq;
namespace Microsoft.PowerShell.EditorServices.Services;
@@ -19,14 +20,14 @@ internal sealed class ReferenceTable
{
private readonly ScriptFile _parent;
- private readonly ConcurrentDictionary> _symbolReferences = new(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary> _symbolReferences = new(StringComparer.OrdinalIgnoreCase);
private bool _isInited;
public ReferenceTable(ScriptFile parent) => _parent = parent;
///
- /// Clears the reference table causing it to rescan the source AST when queried.
+ /// Clears the reference table causing it to re-scan the source AST when queried.
///
public void TagAsChanged()
{
@@ -34,15 +35,40 @@ public void TagAsChanged()
_isInited = false;
}
- // Prefer checking if the dictionary has contents to determine if initialized. The field
- // `_isInited` is to guard against rescanning files with no command references, but will
- // generally be less reliable of a check.
+ ///
+ /// Prefer checking if the dictionary has contents to determine if initialized. The field
+ /// `_isInited` is to guard against re-scanning files with no command references, but will
+ /// generally be less reliable of a check.
+ ///
private bool IsInitialized => !_symbolReferences.IsEmpty || _isInited;
- internal bool TryGetReferences(string command, out ConcurrentBag? references)
+ internal IEnumerable TryGetReferences(SymbolReference? symbol)
{
EnsureInitialized();
- return _symbolReferences.TryGetValue(command, out references);
+ return symbol is not null
+ && _symbolReferences.TryGetValue(symbol.Id, out ConcurrentBag? bag)
+ ? bag
+ : Enumerable.Empty();
+ }
+
+ // Gets symbol whose name contains the position
+ internal SymbolReference? TryGetSymbolAtPosition(int line, int column) => GetAllReferences()
+ .FirstOrDefault(i => i.NameRegion.ContainsPosition(line, column));
+
+ // Gets symbol whose whole extent contains the position
+ internal SymbolReference? TryGetSymbolContainingPosition(int line, int column) => GetAllReferences()
+ .FirstOrDefault(i => i.ScriptRegion.ContainsPosition(line, column));
+
+ internal IEnumerable GetAllReferences()
+ {
+ EnsureInitialized();
+ foreach (ConcurrentBag bag in _symbolReferences.Values)
+ {
+ foreach (SymbolReference symbol in bag)
+ {
+ yield return symbol;
+ }
+ }
}
internal void EnsureInitialized()
@@ -52,67 +78,26 @@ internal void EnsureInitialized()
return;
}
- _parent.ScriptAst.Visit(new ReferenceVisitor(this));
+ _parent.ScriptAst.Visit(new SymbolVisitor(_parent, AddReference));
}
- private void AddReference(string symbol, IScriptExtent extent)
+ private AstVisitAction AddReference(SymbolReference symbol)
{
+ // We have to exclude implicit things like `$this` that don't actually exist.
+ if (symbol.ScriptRegion.IsEmpty())
+ {
+ return AstVisitAction.Continue;
+ }
+
_symbolReferences.AddOrUpdate(
- symbol,
- _ => new ConcurrentBag { extent },
+ symbol.Id,
+ _ => new ConcurrentBag { symbol },
(_, existing) =>
{
- existing.Add(extent);
+ existing.Add(symbol);
return existing;
});
- }
- private sealed class ReferenceVisitor : AstVisitor
- {
- private readonly ReferenceTable _references;
-
- public ReferenceVisitor(ReferenceTable references) => _references = references;
-
- public override AstVisitAction VisitCommand(CommandAst commandAst)
- {
- string? commandName = GetCommandName(commandAst);
- if (string.IsNullOrEmpty(commandName))
- {
- return AstVisitAction.Continue;
- }
-
- _references.AddReference(
- CommandHelpers.StripModuleQualification(commandName, out _),
- commandAst.CommandElements[0].Extent);
-
- return AstVisitAction.Continue;
-
- static string? GetCommandName(CommandAst commandAst)
- {
- string commandName = commandAst.GetCommandName();
- if (!string.IsNullOrEmpty(commandName))
- {
- return commandName;
- }
-
- if (commandAst.CommandElements[0] is not ExpandableStringExpressionAst expandableStringExpressionAst)
- {
- return null;
- }
-
- return AstOperations.TryGetInferredValue(expandableStringExpressionAst, out string value) ? value : null;
- }
- }
-
- public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
- {
- // TODO: Consider tracking unscoped variable references only when they declared within
- // the same function definition.
- _references.AddReference(
- $"${variableExpressionAst.VariablePath.UserPath}",
- variableExpressionAst.Extent);
-
- return AstVisitAction.Continue;
- }
+ return AstVisitAction.Continue;
}
}
diff --git a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs
index 92b33c8de..c2f60f86b 100644
--- a/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/ScriptDocumentSymbolProvider.cs
@@ -2,8 +2,6 @@
// Licensed under the MIT License.
using System.Collections.Generic;
-using System.Linq;
-using System.Management.Automation.Language;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
namespace Microsoft.PowerShell.EditorServices.Services.Symbols
@@ -16,37 +14,7 @@ internal class ScriptDocumentSymbolProvider : IDocumentSymbolProvider
{
string IDocumentSymbolProvider.ProviderId => nameof(ScriptDocumentSymbolProvider);
- IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols(
- ScriptFile scriptFile)
- {
- // If we have an AST, then we know it's a PowerShell file
- // so lets try to find symbols in the document.
- return scriptFile?.ScriptAst != null
- ? FindSymbolsInDocument(scriptFile.ScriptAst)
- : Enumerable.Empty();
- }
-
- ///
- /// Finds all symbols in a script
- ///
- /// The abstract syntax tree of the given script
- /// A collection of SymbolReference objects
- public static IEnumerable FindSymbolsInDocument(Ast scriptAst)
- {
- // TODO: Restore this when we figure out how to support multiple
- // PS versions in the new PSES-as-a-module world (issue #276)
- // if (powerShellVersion >= new Version(5,0))
- // {
- //#if PowerShell v5
- // FindSymbolsVisitor2 findSymbolsVisitor = new FindSymbolsVisitor2();
- // scriptAst.Visit(findSymbolsVisitor);
- // symbolReferences = findSymbolsVisitor.SymbolReferences;
- //#endif
- // }
- // else
- FindSymbolsVisitor findSymbolsVisitor = new();
- scriptAst.Visit(findSymbolsVisitor);
- return findSymbolsVisitor.SymbolReferences;
- }
+ IEnumerable IDocumentSymbolProvider.ProvideDocumentSymbols(
+ ScriptFile scriptFile) => scriptFile.References.GetAllReferences();
}
}
diff --git a/src/PowerShellEditorServices/Services/Symbols/ScriptExtent.cs b/src/PowerShellEditorServices/Services/Symbols/ScriptExtent.cs
index 2224be725..1cd87f44f 100644
--- a/src/PowerShellEditorServices/Services/Symbols/ScriptExtent.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/ScriptExtent.cs
@@ -91,6 +91,8 @@ public int EndOffset
set;
}
+ public override string ToString() => Text;
+
///
/// Gets the ending script position of the extent.
///
diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs
index 80a1dd8b4..ddca0f0a7 100644
--- a/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/SymbolDetails.cs
@@ -12,6 +12,7 @@ namespace Microsoft.PowerShell.EditorServices.Services.Symbols
{
///
/// Provides detailed information for a given symbol.
+ /// TODO: Get rid of this and just use return documentation.
///
[DebuggerDisplay("SymbolReference = {SymbolReference.SymbolType}/{SymbolReference.SymbolName}, DisplayString = {DisplayString}")]
internal class SymbolDetails
@@ -23,11 +24,6 @@ internal class SymbolDetails
///
public SymbolReference SymbolReference { get; private set; }
- ///
- /// Gets the display string for this symbol.
- ///
- public string DisplayString { get; private set; }
-
///
/// Gets the documentation string for this symbol. Returns an
/// empty string if the symbol has no documentation.
@@ -48,43 +44,28 @@ internal static async Task CreateAsync(
SymbolReference = symbolReference
};
- switch (symbolReference.SymbolType)
+ if (symbolReference.Type is SymbolType.Function)
{
- case SymbolType.Function:
- CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync(
- symbolReference.SymbolName,
- currentRunspace,
- executionService).ConfigureAwait(false);
-
- if (commandInfo != null)
+ CommandInfo commandInfo = await CommandHelpers.GetCommandInfoAsync(
+ symbolReference.Id,
+ currentRunspace,
+ executionService).ConfigureAwait(false);
+
+ if (commandInfo is not null)
+ {
+ symbolDetails.Documentation =
+ await CommandHelpers.GetCommandSynopsisAsync(
+ commandInfo,
+ executionService).ConfigureAwait(false);
+
+ if (commandInfo.CommandType == CommandTypes.Application)
{
- symbolDetails.Documentation =
- await CommandHelpers.GetCommandSynopsisAsync(
- commandInfo,
- executionService).ConfigureAwait(false);
-
- if (commandInfo.CommandType == CommandTypes.Application)
- {
- symbolDetails.DisplayString = "(application) " + symbolReference.SymbolName;
- return symbolDetails;
- }
+ symbolDetails.SymbolReference = symbolReference with { Name = $"(application) ${symbolReference.Name}" };
}
-
- symbolDetails.DisplayString = "function " + symbolReference.SymbolName;
- return symbolDetails;
-
- case SymbolType.Parameter:
- // TODO: Get parameter help
- symbolDetails.DisplayString = "(parameter) " + symbolReference.SymbolName;
- return symbolDetails;
-
- case SymbolType.Variable:
- symbolDetails.DisplayString = symbolReference.SymbolName;
- return symbolDetails;
-
- default:
- return symbolDetails;
+ }
}
+
+ return symbolDetails;
}
#endregion
diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs
index 25cb15cc1..2c6c6ef8f 100644
--- a/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/SymbolReference.cs
@@ -1,113 +1,70 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+#nullable enable
+
using System.Diagnostics;
using System.Management.Automation.Language;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
namespace Microsoft.PowerShell.EditorServices.Services.Symbols
{
- internal interface ISymbolReference
- {
- ///
- /// Gets the symbol's type
- ///
- SymbolType SymbolType { get; }
-
- ///
- /// Gets the name of the symbol
- ///
- string SymbolName { get; }
-
- ///
- /// Gets the script extent of the symbol
- ///
- ScriptRegion ScriptRegion { get; }
-
- ///
- /// Gets the contents of the line the given symbol is on
- ///
- string SourceLine { get; }
-
- ///
- /// Gets the path of the file in which the symbol was found.
- ///
- string FilePath { get; }
- }
-
///
/// A class that holds the type, name, script extent, and source line of a symbol
///
- [DebuggerDisplay("SymbolType = {SymbolType}, SymbolName = {SymbolName}")]
- internal class SymbolReference : ISymbolReference
+ [DebuggerDisplay("Type = {Type}, Id = {Id}, Name = {Name}")]
+ internal record SymbolReference
{
- #region Properties
+ public SymbolType Type { get; init; }
- ///
- /// Gets the symbol's type
- ///
- public SymbolType SymbolType { get; }
+ public string Id { get; init; }
- ///
- /// Gets the name of the symbol
- ///
- public string SymbolName { get; }
+ public string Name { get; init; }
- ///
- /// Gets the script extent of the symbol
- ///
- public ScriptRegion ScriptRegion { get; }
+ public ScriptRegion NameRegion { get; init; }
+
+ public ScriptRegion ScriptRegion { get; init; }
- ///
- /// Gets the contents of the line the given symbol is on
- ///
public string SourceLine { get; internal set; }
- ///
- /// Gets the path of the file in which the symbol was found.
- ///
public string FilePath { get; internal set; }
- #endregion
+ public bool IsDeclaration { get; init; }
///
/// Constructs and instance of a SymbolReference
///
- /// The higher level type of the symbol
- /// The name of the symbol
+ /// The higher level type of the symbol
+ /// The name of the symbol
+ /// The string used by outline, hover, etc.
+ /// The extent of the symbol's name
/// The script extent of the symbol
- /// The file path of the symbol
- /// The line contents of the given symbol (defaults to empty string)
+ /// The script file that has the symbol
+ /// True if this reference is the definition of the symbol
public SymbolReference(
- SymbolType symbolType,
- string symbolName,
+ SymbolType type,
+ string id,
+ string name,
+ IScriptExtent nameExtent,
IScriptExtent scriptExtent,
- string filePath = "",
- string sourceLine = "")
- {
- // TODO: Verify params
- SymbolType = symbolType;
- SymbolName = symbolName;
- ScriptRegion = ScriptRegion.Create(scriptExtent);
- FilePath = filePath;
- SourceLine = sourceLine;
-
- // TODO: Make sure end column number usage is correct
-
- // Build the display string
- //this.DisplayString =
- // string.Format(
- // "{0} {1}")
- }
-
- ///
- /// Constructs an instance of a SymbolReference
- ///
- /// The higher level type of the symbol
- /// The script extent of the symbol
- public SymbolReference(SymbolType symbolType, IScriptExtent scriptExtent)
- : this(symbolType, scriptExtent.Text, scriptExtent, scriptExtent.File, "")
+ ScriptFile file,
+ bool isDeclaration)
{
+ Type = type;
+ Id = id;
+ Name = name;
+ NameRegion = new(nameExtent);
+ ScriptRegion = new(scriptExtent);
+ FilePath = file.FilePath;
+ try
+ {
+ SourceLine = file.GetLine(ScriptRegion.StartLineNumber);
+ }
+ catch (System.ArgumentOutOfRangeException)
+ {
+ SourceLine = string.Empty;
+ }
+ IsDeclaration = isDeclaration;
}
}
}
diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs
index 02778b106..6533e7726 100644
--- a/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/SymbolType.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using OmniSharp.Extensions.LanguageServer.Protocol.Models;
+
namespace Microsoft.PowerShell.EditorServices.Services.Symbols
{
///
@@ -14,7 +16,7 @@ internal enum SymbolType
Unknown = 0,
///
- /// The symbol is a vairable
+ /// The symbol is a variable
///
Variable,
@@ -41,6 +43,62 @@ internal enum SymbolType
///
/// The symbol is a hashtable key
///
- HashtableKey
+ HashtableKey,
+
+ ///
+ /// The symbol is a class
+ ///
+ Class,
+
+ ///
+ /// The symbol is a enum
+ ///
+ Enum,
+
+ ///
+ /// The symbol is a enum member/value
+ ///
+ EnumMember,
+
+ ///
+ /// The symbol is a class property
+ ///
+ Property,
+
+ ///
+ /// The symbol is a class method
+ ///
+ Method,
+
+ ///
+ /// The symbol is a class constructor
+ ///
+ Constructor,
+
+ ///
+ /// The symbol is a type reference
+ ///
+ Type,
+ }
+
+ internal static class SymbolTypeUtils
+ {
+ internal static SymbolKind GetSymbolKind(SymbolType symbolType)
+ {
+ return symbolType switch
+ {
+ SymbolType.Function or SymbolType.Configuration or SymbolType.Workflow => SymbolKind.Function,
+ SymbolType.Enum => SymbolKind.Enum,
+ SymbolType.Class => SymbolKind.Class,
+ SymbolType.Constructor => SymbolKind.Constructor,
+ SymbolType.Method => SymbolKind.Method,
+ SymbolType.Property => SymbolKind.Property,
+ SymbolType.EnumMember => SymbolKind.EnumMember,
+ SymbolType.Variable or SymbolType.Parameter => SymbolKind.Variable,
+ SymbolType.HashtableKey => SymbolKind.Key,
+ SymbolType.Type => SymbolKind.TypeParameter,
+ SymbolType.Unknown or _ => SymbolKind.Object,
+ };
+ }
}
}
diff --git a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs
index f052520f0..cf4040642 100644
--- a/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs
@@ -1,16 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+#nullable enable
+
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
-using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@@ -42,8 +40,9 @@ internal class SymbolsService
private readonly ConcurrentDictionary _codeLensProviders;
private readonly ConcurrentDictionary _documentSymbolProviders;
private readonly ConfigurationService _configurationService;
- #endregion
+ private Task? _workspaceScanCompleted;
+ #endregion Private Fields
#region Constructors
///
@@ -72,12 +71,13 @@ public SymbolsService(
if (configurationService.CurrentSettings.EnableReferencesCodeLens)
{
ReferencesCodeLensProvider referencesProvider = new(_workspaceService, this);
- _codeLensProviders.TryAdd(referencesProvider.ProviderId, referencesProvider);
+ _ = _codeLensProviders.TryAdd(referencesProvider.ProviderId, referencesProvider);
}
PesterCodeLensProvider pesterProvider = new(configurationService);
- _codeLensProviders.TryAdd(pesterProvider.ProviderId, pesterProvider);
+ _ = _codeLensProviders.TryAdd(pesterProvider.ProviderId, pesterProvider);
+ // TODO: Is this complication so necessary?
_documentSymbolProviders = new ConcurrentDictionary();
IDocumentSymbolProvider[] documentSymbolProviders = new IDocumentSymbolProvider[]
{
@@ -85,13 +85,14 @@ public SymbolsService(
new PsdDocumentSymbolProvider(),
new PesterDocumentSymbolProvider(),
};
+
foreach (IDocumentSymbolProvider documentSymbolProvider in documentSymbolProviders)
{
- _documentSymbolProviders.TryAdd(documentSymbolProvider.ProviderId, documentSymbolProvider);
+ _ = _documentSymbolProviders.TryAdd(documentSymbolProvider.ProviderId, documentSymbolProvider);
}
}
- #endregion
+ #endregion Constructors
public bool TryRegisterCodeLensProvider(ICodeLensProvider codeLensProvider) => _codeLensProviders.TryAdd(codeLensProvider.ProviderId, codeLensProvider);
@@ -106,268 +107,139 @@ public SymbolsService(
public IEnumerable GetDocumentSymbolProviders() => _documentSymbolProviders.Values;
///
- /// Finds all the symbols in a file.
+ /// Finds all the symbols in a file, through all document symbol providers.
///
/// The ScriptFile in which the symbol can be located.
- ///
- public List FindSymbolsInFile(ScriptFile scriptFile)
+ public IEnumerable FindSymbolsInFile(ScriptFile scriptFile)
{
Validate.IsNotNull(nameof(scriptFile), scriptFile);
- List foundOccurrences = new();
foreach (IDocumentSymbolProvider symbolProvider in GetDocumentSymbolProviders())
{
- foreach (SymbolReference reference in symbolProvider.ProvideDocumentSymbols(scriptFile))
+ foreach (SymbolReference symbol in symbolProvider.ProvideDocumentSymbols(scriptFile))
{
- reference.SourceLine = scriptFile.GetLine(reference.ScriptRegion.StartLineNumber);
- reference.FilePath = scriptFile.FilePath;
- foundOccurrences.Add(reference);
+ yield return symbol;
}
}
-
- return foundOccurrences;
}
///
- /// Finds the symbol in the script given a file location
+ /// Finds the symbol in the script given a file location.
///
- /// The details and contents of a open script file
- /// The line number of the cursor for the given script
- /// The column number of the cursor for the given script
- /// A SymbolReference of the symbol found at the given location
- /// or null if there is no symbol at that location
- ///
- public static SymbolReference FindSymbolAtLocation(
- ScriptFile scriptFile,
- int lineNumber,
- int columnNumber)
+ public static SymbolReference? FindSymbolAtLocation(
+ ScriptFile scriptFile, int line, int column)
+ {
+ Validate.IsNotNull(nameof(scriptFile), scriptFile);
+ return scriptFile.References.TryGetSymbolAtPosition(line, column);
+ }
+
+ // Using a private method here to get a bit more readability and to avoid roslynator
+ // asserting we should use a giant nested ternary.
+ private static string[] GetIdentifiers(string symbolName, SymbolType symbolType, CommandHelpers.AliasMap aliases)
{
- SymbolReference symbolReference =
- AstOperations.FindSymbolAtPosition(
- scriptFile.ScriptAst,
- lineNumber,
- columnNumber);
+ if (symbolType is not SymbolType.Function)
+ {
+ return new[] { symbolName };
+ }
- if (symbolReference != null)
+ if (!aliases.CmdletToAliases.TryGetValue(symbolName, out List foundAliasList))
{
- symbolReference.FilePath = scriptFile.FilePath;
+ return new[] { symbolName };
}
- return symbolReference;
+ return foundAliasList.Prepend(symbolName)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToArray();
}
///
- /// Finds all the references of a symbol
+ /// Finds all the references of a symbol in the workspace, resolving aliases.
+ /// TODO: One day use IAsyncEnumerable.
///
- /// The symbol to find all references for
- /// An array of scriptFiles too search for references in
- /// The workspace that will be searched for symbols
- ///
- /// FindReferencesResult
- public async Task> FindReferencesOfSymbol(
- SymbolReference foundSymbol,
- ScriptFile[] referencedFiles,
- WorkspaceService workspace,
+ public async Task> ScanForReferencesOfSymbolAsync(
+ SymbolReference symbol,
CancellationToken cancellationToken = default)
{
- if (foundSymbol == null)
+ if (symbol is null)
{
- return null;
+ return Enumerable.Empty();
}
+ // TODO: Should we handle aliases at a lower level?
CommandHelpers.AliasMap aliases = await CommandHelpers.GetAliasesAsync(
_executionService,
cancellationToken).ConfigureAwait(false);
- string targetName = foundSymbol.SymbolName;
- if (foundSymbol.SymbolType is SymbolType.Function)
+ string targetName = symbol.Id;
+ if (symbol.Type is SymbolType.Function
+ && aliases.AliasToCmdlets.TryGetValue(symbol.Id, out string aliasDefinition))
{
- targetName = CommandHelpers.StripModuleQualification(targetName, out _);
- if (aliases.AliasToCmdlets.TryGetValue(foundSymbol.SymbolName, out string aliasDefinition))
- {
- targetName = aliasDefinition;
- }
- }
-
- // We want to look for references first in referenced files, hence we use ordered dictionary
- // TODO: File system case-sensitivity is based on filesystem not OS, but OS is a much cheaper heuristic
- OrderedDictionary fileMap = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
- ? new OrderedDictionary()
- : new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
-
- foreach (ScriptFile scriptFile in referencedFiles)
- {
- fileMap[scriptFile.FilePath] = scriptFile;
+ targetName = aliasDefinition;
}
await ScanWorkspacePSFiles(cancellationToken).ConfigureAwait(false);
- List symbolReferences = new();
-
- // Using a nested method here to get a bit more readability and to avoid roslynator
- // asserting we should use a giant nested ternary here.
- static string[] GetIdentifiers(string symbolName, SymbolType symbolType, CommandHelpers.AliasMap aliases)
- {
- if (symbolType is not SymbolType.Function)
- {
- return new[] { symbolName };
- }
-
- if (!aliases.CmdletToAliases.TryGetValue(symbolName, out List foundAliasList))
- {
- return new[] { symbolName };
- }
-
- return foundAliasList.Prepend(symbolName)
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .ToArray();
- }
-
- string[] allIdentifiers = GetIdentifiers(targetName, foundSymbol.SymbolType, aliases);
+ List symbols = new();
+ string[] allIdentifiers = GetIdentifiers(targetName, symbol.Type, aliases);
foreach (ScriptFile file in _workspaceService.GetOpenedFiles())
{
foreach (string targetIdentifier in allIdentifiers)
{
- if (!file.References.TryGetReferences(targetIdentifier, out ConcurrentBag references))
- {
- continue;
- }
-
- foreach (IScriptExtent extent in references.OrderBy(e => e.StartOffset))
- {
- SymbolReference reference = new(
- SymbolType.Function,
- foundSymbol.SymbolName,
- extent);
-
- try
- {
- reference.SourceLine = file.GetLine(reference.ScriptRegion.StartLineNumber);
- }
- catch (ArgumentOutOfRangeException e)
- {
- reference.SourceLine = string.Empty;
- _logger.LogException("Found reference is out of range in script file", e);
- }
- reference.FilePath = file.FilePath;
- symbolReferences.Add(reference);
- }
-
await Task.Yield();
cancellationToken.ThrowIfCancellationRequested();
+ symbols.AddRange(file.References.TryGetReferences(symbol with { Id = targetIdentifier }));
}
}
- return symbolReferences;
+ return symbols;
}
///
- /// Finds all the occurrences of a symbol in the script given a file location
+ /// Finds all the occurrences of a symbol in the script given a file location.
+ /// TODO: Doesn't support aliases, is it worth it?
///
- /// The details and contents of a open script file
- /// The line number of the cursor for the given script
- /// The column number of the cursor for the given script
- /// FindOccurrencesResult
- public static IReadOnlyList FindOccurrencesInFile(
- ScriptFile file,
- int symbolLineNumber,
- int symbolColumnNumber)
- {
- SymbolReference foundSymbol = AstOperations.FindSymbolAtPosition(
- file.ScriptAst,
- symbolLineNumber,
- symbolColumnNumber);
-
- if (foundSymbol == null)
- {
- return null;
- }
-
- return AstOperations.FindReferencesOfSymbol(file.ScriptAst, foundSymbol).ToArray();
- }
+ public static IEnumerable FindOccurrencesInFile(
+ ScriptFile scriptFile, int line, int column) => scriptFile
+ .References
+ .TryGetReferences(FindSymbolAtLocation(scriptFile, line, column));
///
- /// Finds a function definition in the script given a file location
+ /// Finds the symbol at the location and returns it if it's a declaration.
///
- /// The details and contents of a open script file
- /// The line number of the cursor for the given script
- /// The column number of the cursor for the given script
- /// A SymbolReference of the symbol found at the given location
- /// or null if there is no symbol at that location
- ///
- public static SymbolReference FindFunctionDefinitionAtLocation(
- ScriptFile scriptFile,
- int lineNumber,
- int columnNumber)
+ public static SymbolReference? FindSymbolDefinitionAtLocation(
+ ScriptFile scriptFile, int line, int column)
{
- SymbolReference symbolReference =
- AstOperations.FindSymbolAtPosition(
- scriptFile.ScriptAst,
- lineNumber,
- columnNumber,
- includeFunctionDefinitions: true);
-
- if (symbolReference != null)
- {
- symbolReference.FilePath = scriptFile.FilePath;
- }
-
- return symbolReference;
+ SymbolReference? symbol = FindSymbolAtLocation(scriptFile, line, column);
+ return symbol?.IsDeclaration == true ? symbol : null;
}
///
/// Finds the details of the symbol at the given script file location.
///
- /// The ScriptFile in which the symbol can be located.
- /// The line number at which the symbol can be located.
- /// The column number at which the symbol can be located.
- ///
- public Task FindSymbolDetailsAtLocationAsync(
- ScriptFile scriptFile,
- int lineNumber,
- int columnNumber)
+ public Task FindSymbolDetailsAtLocationAsync(
+ ScriptFile scriptFile, int line, int column)
{
- SymbolReference symbolReference =
- AstOperations.FindSymbolAtPosition(
- scriptFile.ScriptAst,
- lineNumber,
- columnNumber);
-
- if (symbolReference == null)
- {
- return Task.FromResult(null);
- }
-
- symbolReference.FilePath = scriptFile.FilePath;
- return SymbolDetails.CreateAsync(
- symbolReference,
- _runspaceContext.CurrentRunspace,
- _executionService);
+ SymbolReference? symbol = FindSymbolAtLocation(scriptFile, line, column);
+ return symbol is null
+ ? Task.FromResult(null)
+ : SymbolDetails.CreateAsync(symbol, _runspaceContext.CurrentRunspace, _executionService);
}
///
/// Finds the parameter set hints of a specific command (determined by a given file location)
///
- /// The details and contents of a open script file
- /// The line number of the cursor for the given script
- /// The column number of the cursor for the given script
- /// ParameterSetSignatures
- public async Task FindParameterSetsInFileAsync(
- ScriptFile file,
- int lineNumber,
- int columnNumber)
+ public async Task FindParameterSetsInFileAsync(
+ ScriptFile scriptFile, int line, int column)
{
- SymbolReference foundSymbol =
- AstOperations.FindCommandAtPosition(
- file.ScriptAst,
- lineNumber,
- columnNumber);
+ // This needs to get by whole extent, not just the name, as it completes e.g.
+ // `Get-Process -` (after the dash).
+ SymbolReference? symbol = scriptFile.References.TryGetSymbolContainingPosition(line, column);
// If we are not possibly looking at a Function, we don't
// need to continue because we won't be able to get the
// CommandInfo object.
- if (foundSymbol?.SymbolType is not SymbolType.Function
+ if (symbol?.Type is not SymbolType.Function
and not SymbolType.Unknown)
{
return null;
@@ -375,19 +247,20 @@ public async Task FindParameterSetsInFileAsync(
CommandInfo commandInfo =
await CommandHelpers.GetCommandInfoAsync(
- foundSymbol.SymbolName,
+ symbol.Id,
_runspaceContext.CurrentRunspace,
_executionService).ConfigureAwait(false);
- if (commandInfo == null)
+ if (commandInfo is null)
{
return null;
}
try
{
+ // TODO: We should probably look at 'Parameters' instead of 'ParameterSets'
IEnumerable commandParamSets = commandInfo.ParameterSets;
- return new ParameterSetSignatures(commandParamSets, foundSymbol);
+ return new ParameterSetSignatures(commandParamSets, symbol);
}
catch (RuntimeException e)
{
@@ -408,123 +281,40 @@ await CommandHelpers.GetCommandInfoAsync(
}
///
- /// Finds the definition of a symbol in the script file or any of the
- /// files that it references.
+ /// Finds the possible definitions of the symbol in the file or workspace.
+ /// TODO: One day use IAsyncEnumerable.
+ /// TODO: Fix searching for definition of built-in commands.
+ /// TODO: Fix "definition" of dot-source (maybe?)
///
- /// The initial script file to be searched for the symbol's definition.
- /// The symbol for which a definition will be found.
- /// The resulting GetDefinitionResult for the symbol's definition.
- public async Task GetDefinitionOfSymbolAsync(
- ScriptFile sourceFile,
- SymbolReference foundSymbol)
+ public async Task> GetDefinitionOfSymbolAsync(
+ ScriptFile scriptFile,
+ SymbolReference symbol,
+ CancellationToken cancellationToken = default)
{
- Validate.IsNotNull(nameof(sourceFile), sourceFile);
- Validate.IsNotNull(nameof(foundSymbol), foundSymbol);
-
- // If symbol is an alias, resolve it.
- (Dictionary> _, Dictionary aliasToCmdlets) =
- await CommandHelpers.GetAliasesAsync(_executionService).ConfigureAwait(false);
-
- if (aliasToCmdlets.ContainsKey(foundSymbol.SymbolName))
+ List declarations = new();
+ declarations.AddRange(scriptFile.References.TryGetReferences(symbol).Where(i => i.IsDeclaration));
+ if (declarations.Count > 0)
{
- foundSymbol = new SymbolReference(
- foundSymbol.SymbolType,
- aliasToCmdlets[foundSymbol.SymbolName],
- foundSymbol.ScriptRegion,
- foundSymbol.FilePath,
- foundSymbol.SourceLine);
+ _logger.LogDebug($"Found possible declaration in same file ${declarations}");
+ return declarations;
}
- ScriptFile[] referencedFiles =
- _workspaceService.ExpandScriptReferences(
- sourceFile);
-
- HashSet filesSearched = new(StringComparer.OrdinalIgnoreCase);
-
- // look through the referenced files until definition is found
- // or there are no more file to look through
- SymbolReference foundDefinition = null;
- foreach (ScriptFile scriptFile in referencedFiles)
- {
- foundDefinition = AstOperations.FindDefinitionOfSymbol(scriptFile.ScriptAst, foundSymbol);
+ IEnumerable references =
+ await ScanForReferencesOfSymbolAsync(symbol, cancellationToken).ConfigureAwait(false);
+ declarations.AddRange(references.Where(i => i.IsDeclaration));
- filesSearched.Add(scriptFile.FilePath);
- if (foundDefinition is not null)
- {
- foundDefinition.FilePath = scriptFile.FilePath;
- break;
- }
-
- if (foundSymbol.SymbolType == SymbolType.Function)
- {
- // Dot-sourcing is parsed as a "Function" Symbol.
- string dotSourcedPath = GetDotSourcedPath(foundSymbol, scriptFile);
- if (scriptFile.FilePath == dotSourcedPath)
- {
- foundDefinition = new SymbolReference(SymbolType.Function, foundSymbol.SymbolName, scriptFile.ScriptAst.Extent, scriptFile.FilePath);
- break;
- }
- }
- }
-
- // if the definition the not found in referenced files
- // look for it in all the files in the workspace
- if (foundDefinition is null)
- {
- // Get a list of all powershell files in the workspace path
- foreach (string file in _workspaceService.EnumeratePSFiles())
- {
- if (filesSearched.Contains(file))
- {
- continue;
- }
-
- foundDefinition =
- AstOperations.FindDefinitionOfSymbol(
- Parser.ParseFile(file, out Token[] tokens, out ParseError[] parseErrors),
- foundSymbol);
-
- filesSearched.Add(file);
- if (foundDefinition is not null)
- {
- foundDefinition.FilePath = file;
- break;
- }
- }
- }
-
- // if the definition is not found in a file in the workspace
- // look for it in the builtin commands but only if the symbol
- // we are looking at is possibly a Function.
- if (foundDefinition is null
- && (foundSymbol.SymbolType == SymbolType.Function
- || foundSymbol.SymbolType == SymbolType.Unknown))
- {
- CommandInfo cmdInfo =
- await CommandHelpers.GetCommandInfoAsync(
- foundSymbol.SymbolName,
- _runspaceContext.CurrentRunspace,
- _executionService).ConfigureAwait(false);
-
- foundDefinition =
- FindDeclarationForBuiltinCommand(
- cmdInfo,
- foundSymbol);
- }
-
- return foundDefinition;
+ _logger.LogDebug($"Found possible declaration in workspace ${declarations}");
+ return declarations;
}
- private Task _workspaceScanCompleted;
-
- private async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = default)
+ internal async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = default)
{
if (_configurationService.CurrentSettings.AnalyzeOpenDocumentsOnly)
{
return;
}
- Task scanTask = _workspaceScanCompleted;
+ Task? scanTask = _workspaceScanCompleted;
// It's not impossible for two scans to start at once but it should be exceedingly
// unlikely, and shouldn't break anything if it happens to. So we can save some
// lock time by accepting that possibility.
@@ -564,109 +354,22 @@ private async Task ScanWorkspacePSFiles(CancellationToken cancellationToken = de
// TODO: There's a new API in net6 that lets you await a task with a cancellation token.
// we should #if that in if feasible.
TaskCompletionSource cancelled = new();
- cancellationToken.Register(() => cancelled.TrySetCanceled());
- await Task.WhenAny(scanTask, cancelled.Task).ConfigureAwait(false);
- }
-
- ///
- /// Gets a path from a dot-source symbol.
- ///
- /// The symbol representing the dot-source expression.
- /// The script file containing the symbol
- ///
- private string GetDotSourcedPath(SymbolReference symbol, ScriptFile scriptFile)
- {
- string cleanedUpSymbol = PathUtils.NormalizePathSeparators(symbol.SymbolName.Trim('\'', '"'));
- string psScriptRoot = Path.GetDirectoryName(scriptFile.FilePath);
- return _workspaceService.ResolveRelativeScriptPath(psScriptRoot,
- Regex.Replace(cleanedUpSymbol, @"\$PSScriptRoot|\${PSScriptRoot}", psScriptRoot, RegexOptions.IgnoreCase));
- }
-
- private SymbolReference FindDeclarationForBuiltinCommand(
- CommandInfo commandInfo,
- SymbolReference foundSymbol)
- {
- if (commandInfo == null)
- {
- return null;
- }
-
- ScriptFile[] nestedModuleFiles =
- GetBuiltinCommandScriptFiles(
- commandInfo.Module);
-
- SymbolReference foundDefinition = null;
- foreach (ScriptFile nestedModuleFile in nestedModuleFiles)
- {
- foundDefinition = AstOperations.FindDefinitionOfSymbol(
- nestedModuleFile.ScriptAst,
- foundSymbol);
-
- if (foundDefinition != null)
- {
- foundDefinition.FilePath = nestedModuleFile.FilePath;
- break;
- }
- }
-
- return foundDefinition;
- }
-
- private ScriptFile[] GetBuiltinCommandScriptFiles(
- PSModuleInfo moduleInfo)
- {
- if (moduleInfo == null)
- {
- return Array.Empty();
- }
-
- string modPath = moduleInfo.Path;
- List scriptFiles = new();
- ScriptFile newFile;
-
- // find any files where the moduleInfo's path ends with ps1 or psm1
- // and add it to allowed script files
- if (modPath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) ||
- modPath.EndsWith(".psm1", StringComparison.OrdinalIgnoreCase))
- {
- newFile = _workspaceService.GetFile(modPath);
- newFile.IsAnalysisEnabled = false;
- scriptFiles.Add(newFile);
- }
-
- if (moduleInfo.NestedModules.Count > 0)
- {
- foreach (PSModuleInfo nestedInfo in moduleInfo.NestedModules)
- {
- string nestedModPath = nestedInfo.Path;
- if (nestedModPath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) ||
- nestedModPath.EndsWith(".psm1", StringComparison.OrdinalIgnoreCase))
- {
- newFile = _workspaceService.GetFile(nestedModPath);
- newFile.IsAnalysisEnabled = false;
- scriptFiles.Add(newFile);
- }
- }
- }
-
- return scriptFiles.ToArray();
+ _ = cancellationToken.Register(() => cancelled.TrySetCanceled());
+ _ = await Task.WhenAny(scanTask, cancelled.Task).ConfigureAwait(false);
}
///
/// Finds a function definition that follows or contains the given line number.
///
- /// Open script file.
- /// The 1 based line on which to look for function definition.
- ///
- /// If found, returns the function definition, otherwise, returns null.
- public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment(
+ public static FunctionDefinitionAst? GetFunctionDefinitionForHelpComment(
ScriptFile scriptFile,
- int lineNumber,
- out string helpLocation)
+ int line,
+ out string? helpLocation)
{
+ Validate.IsNotNull(nameof(scriptFile), scriptFile);
// check if the next line contains a function definition
- FunctionDefinitionAst funcDefnAst = GetFunctionDefinitionAtLine(scriptFile, lineNumber + 1);
- if (funcDefnAst != null)
+ FunctionDefinitionAst? funcDefnAst = GetFunctionDefinitionAtLine(scriptFile, line + 1);
+ if (funcDefnAst is not null)
{
helpLocation = "before";
return funcDefnAst;
@@ -681,8 +384,8 @@ public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment(
return false;
}
- return fdAst.Body.Extent.StartLineNumber < lineNumber &&
- fdAst.Body.Extent.EndLineNumber > lineNumber;
+ return fdAst.Body.Extent.StartLineNumber < line &&
+ fdAst.Body.Extent.EndLineNumber > line;
},
true);
@@ -696,7 +399,7 @@ public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment(
// definition that contains `lineNumber`
foreach (FunctionDefinitionAst foundAst in foundAsts.Cast())
{
- if (funcDefnAst == null)
+ if (funcDefnAst is null)
{
funcDefnAst = foundAst;
continue;
@@ -709,14 +412,14 @@ public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment(
}
}
- // TODO use tokens to check for non empty character instead of just checking for line offset
- if (funcDefnAst.Body.Extent.StartLineNumber == lineNumber - 1)
+ // TODO: use tokens to check for non empty character instead of just checking for line offset
+ if (funcDefnAst?.Body.Extent.StartLineNumber == line - 1)
{
helpLocation = "begin";
return funcDefnAst;
}
- if (funcDefnAst.Body.Extent.EndLineNumber == lineNumber + 1)
+ if (funcDefnAst?.Body.Extent.EndLineNumber == line + 1)
{
helpLocation = "end";
return funcDefnAst;
@@ -729,11 +432,9 @@ public static FunctionDefinitionAst GetFunctionDefinitionForHelpComment(
///
/// Gets the function defined on a given line.
+ /// TODO: Remove this.
///
- /// Open script file.
- /// The 1 based line on which to look for function definition.
- /// If found, returns the function definition on the given line. Otherwise, returns null.
- public static FunctionDefinitionAst GetFunctionDefinitionAtLine(
+ public static FunctionDefinitionAst? GetFunctionDefinitionAtLine(
ScriptFile scriptFile,
int lineNumber)
{
@@ -748,7 +449,7 @@ internal void OnConfigurationUpdated(object _, LanguageServerSettings e)
{
if (e.AnalyzeOpenDocumentsOnly)
{
- Task scanInProgress = _workspaceScanCompleted;
+ Task? scanInProgress = _workspaceScanCompleted;
if (scanInProgress is not null)
{
// Wait until after the scan completes to close unopened files.
diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/AstOperations.cs
similarity index 51%
rename from src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs
rename to src/PowerShellEditorServices/Services/Symbols/Visitors/AstOperations.cs
index e14b2051a..93b8fd924 100644
--- a/src/PowerShellEditorServices/Services/Symbols/Vistors/AstOperations.cs
+++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/AstOperations.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation;
using System.Management.Automation.Language;
@@ -141,178 +140,6 @@ await executionService.ExecuteDelegateAsync(
return commandCompletion;
}
- ///
- /// Finds the symbol at a given file location
- ///
- /// The abstract syntax tree of the given script
- /// The line number of the cursor for the given script
- /// The column number of the cursor for the given script
- /// Includes full function definition ranges in the search.
- /// SymbolReference of found symbol
- public static SymbolReference FindSymbolAtPosition(
- Ast scriptAst,
- int lineNumber,
- int columnNumber,
- bool includeFunctionDefinitions = false)
- {
- FindSymbolVisitor symbolVisitor =
- new(
- lineNumber,
- columnNumber,
- includeFunctionDefinitions);
-
- scriptAst.Visit(symbolVisitor);
-
- return symbolVisitor.FoundSymbolReference;
- }
-
- ///
- /// Finds the symbol (always Command type) at a given file location
- ///
- /// The abstract syntax tree of the given script
- /// The line number of the cursor for the given script
- /// The column number of the cursor for the given script
- /// SymbolReference of found command
- public static SymbolReference FindCommandAtPosition(Ast scriptAst, int lineNumber, int columnNumber)
- {
- FindCommandVisitor commandVisitor = new(lineNumber, columnNumber);
- scriptAst.Visit(commandVisitor);
-
- return commandVisitor.FoundCommandReference;
- }
-
- ///
- /// Finds all references (including aliases) in a script for the given symbol
- ///
- /// The abstract syntax tree of the given script
- /// The symbol that we are looking for references of
- /// Dictionary maping cmdlets to aliases for finding alias references
- /// Dictionary maping aliases to cmdlets for finding alias references
- ///
- public static IEnumerable FindReferencesOfSymbol(
- Ast scriptAst,
- SymbolReference symbolReference,
- IDictionary> cmdletToAliasDictionary = default,
- IDictionary aliasToCmdletDictionary = default)
- {
- // find the symbol evaluators for the node types we are handling
- FindReferencesVisitor referencesVisitor = new(
- symbolReference,
- cmdletToAliasDictionary,
- aliasToCmdletDictionary);
-
- scriptAst.Visit(referencesVisitor);
-
- return referencesVisitor.FoundReferences;
- }
-
- ///
- /// Finds the definition of the symbol
- ///
- /// The abstract syntax tree of the given script
- /// The symbol that we are looking for the definition of
- /// A SymbolReference of the definition of the symbolReference
- public static SymbolReference FindDefinitionOfSymbol(
- Ast scriptAst,
- SymbolReference symbolReference)
- {
- FindDeclarationVisitor declarationVisitor = new(symbolReference);
- scriptAst.Visit(declarationVisitor);
- return declarationVisitor.FoundDeclaration;
- }
-
- ///
- /// Finds all symbols in a script
- ///
- /// The abstract syntax tree of the given script
- /// A collection of SymbolReference objects
- public static IEnumerable FindSymbolsInDocument(Ast scriptAst)
- {
- // TODO: Restore this when we figure out how to support multiple
- // PS versions in the new PSES-as-a-module world (issue #276)
- // if (powerShellVersion >= new Version(5,0))
- // {
- //#if PowerShell v5
- // FindSymbolsVisitor2 findSymbolsVisitor = new FindSymbolsVisitor2();
- // scriptAst.Visit(findSymbolsVisitor);
- // symbolReferences = findSymbolsVisitor.SymbolReferences;
- //#endif
- // }
- // else
-
- FindSymbolsVisitor findSymbolsVisitor = new();
- scriptAst.Visit(findSymbolsVisitor);
- return findSymbolsVisitor.SymbolReferences;
- }
-
- ///
- /// Checks if a given ast represents the root node of a *.psd1 file.
- ///
- /// The abstract syntax tree of the given script
- /// true if the AST represts a *.psd1 file, otherwise false
- public static bool IsPowerShellDataFileAst(Ast ast)
- {
- // sometimes we don't have reliable access to the filename
- // so we employ heuristics to check if the contents are
- // part of a psd1 file.
- return IsPowerShellDataFileAstNode(
- new { Item = ast, Children = new List() },
- new Type[] {
- typeof(ScriptBlockAst),
- typeof(NamedBlockAst),
- typeof(PipelineAst),
- typeof(CommandExpressionAst),
- typeof(HashtableAst) },
- 0);
- }
-
- private static bool IsPowerShellDataFileAstNode(dynamic node, Type[] levelAstMap, int level)
- {
- dynamic levelAstTypeMatch = node.Item.GetType().Equals(levelAstMap[level]);
- if (!levelAstTypeMatch)
- {
- return false;
- }
-
- if (level == levelAstMap.Length - 1)
- {
- return levelAstTypeMatch;
- }
-
- IEnumerable astsFound = (node.Item as Ast)?.FindAll(a => a is not null, false);
- if (astsFound != null)
- {
- foreach (Ast astFound in astsFound)
- {
- if (!astFound.Equals(node.Item)
- && node.Item.Equals(astFound.Parent)
- && IsPowerShellDataFileAstNode(
- new { Item = astFound, Children = new List() },
- levelAstMap,
- level + 1))
- {
- return true;
- }
- }
- }
-
- return false;
- }
-
- ///
- /// Finds all files dot sourced in a script
- ///
- /// The abstract syntax tree of the given script
- /// Pre-calculated value of $PSScriptRoot
- ///
- public static string[] FindDotSourcedIncludes(Ast scriptAst, string psScriptRoot)
- {
- FindDotSourcedVisitor dotSourcedVisitor = new(psScriptRoot);
- scriptAst.Visit(dotSourcedVisitor);
-
- return dotSourcedVisitor.DotSourcedFiles.ToArray();
- }
-
internal static bool TryGetInferredValue(ExpandableStringExpressionAst expandableStringExpressionAst, out string value)
{
// Currently we only support inferring the value of `$PSScriptRoot`. We could potentially
diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/HashTableVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/HashTableVisitor.cs
new file mode 100644
index 000000000..3d9100cf5
--- /dev/null
+++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/HashTableVisitor.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System.Collections.Generic;
+using System.Management.Automation.Language;
+using Microsoft.PowerShell.EditorServices.Services.TextDocument;
+
+namespace Microsoft.PowerShell.EditorServices.Services.Symbols
+{
+ ///
+ /// Visitor to find all the keys in Hashtable AST
+ ///
+ internal class FindHashtableSymbolsVisitor : AstVisitor
+ {
+ private readonly ScriptFile _file;
+
+ ///
+ /// List of symbols (keys) found in the hashtable
+ ///
+ public List SymbolReferences { get; }
+
+ ///
+ /// Initializes a new instance of FindHashtableSymbolsVisitor class
+ ///
+ public FindHashtableSymbolsVisitor(ScriptFile file)
+ {
+ SymbolReferences = new List();
+ _file = file;
+ }
+
+ ///
+ /// Adds keys in the input hashtable to the symbol reference
+ ///
+ /// A HashtableAst in the script's AST
+ /// A visit action that continues the search for references
+ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst)
+ {
+ if (hashtableAst.KeyValuePairs == null)
+ {
+ return AstVisitAction.Continue;
+ }
+
+ foreach (System.Tuple kvp in hashtableAst.KeyValuePairs)
+ {
+ if (kvp.Item1 is StringConstantExpressionAst keyStrConstExprAst)
+ {
+ IScriptExtent nameExtent = new ScriptExtent()
+ {
+ Text = keyStrConstExprAst.Value,
+ StartLineNumber = kvp.Item1.Extent.StartLineNumber,
+ EndLineNumber = kvp.Item2.Extent.EndLineNumber,
+ StartColumnNumber = kvp.Item1.Extent.StartColumnNumber,
+ EndColumnNumber = kvp.Item2.Extent.EndColumnNumber,
+ File = hashtableAst.Extent.File
+ };
+
+ SymbolReferences.Add(
+ // TODO: Should we fill this out better?
+ new SymbolReference(
+ SymbolType.HashtableKey,
+ nameExtent.Text,
+ nameExtent.Text,
+ nameExtent,
+ nameExtent,
+ _file,
+ isDeclaration: false));
+ }
+ }
+
+ return AstVisitAction.Continue;
+ }
+ }
+}
diff --git a/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs
new file mode 100644
index 000000000..1035f2557
--- /dev/null
+++ b/src/PowerShellEditorServices/Services/Symbols/Visitors/SymbolVisitor.cs
@@ -0,0 +1,245 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Management.Automation.Language;
+using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility;
+using Microsoft.PowerShell.EditorServices.Services.TextDocument;
+using Microsoft.PowerShell.EditorServices.Utility;
+
+namespace Microsoft.PowerShell.EditorServices.Services.Symbols;
+
+///
+/// The goal of this is to be our one and only visitor, which parses a file when necessary
+/// performing Action, which takes a SymbolReference (that this visitor creates) and returns an
+/// AstVisitAction. In this way, all our symbols are created with the same initialization logic.
+/// TODO: Visit hashtable keys, constants, arrays, namespaces, interfaces, operators, etc.
+///
+internal sealed class SymbolVisitor : AstVisitor2
+{
+ private readonly ScriptFile _file;
+
+ private readonly Func _action;
+
+ public SymbolVisitor(ScriptFile file, Func action)
+ {
+ _file = file;
+ _action = action;
+ }
+
+ // TODO: Make all the display strings better (and performant).
+ public override AstVisitAction VisitCommand(CommandAst commandAst)
+ {
+ string? commandName = VisitorUtils.GetCommandName(commandAst);
+ if (commandName is null)
+ {
+ return AstVisitAction.Continue;
+ }
+
+ return _action(new SymbolReference(
+ SymbolType.Function,
+ "fn " + CommandHelpers.StripModuleQualification(commandName, out _),
+ commandName,
+ commandAst.CommandElements[0].Extent,
+ commandAst.Extent,
+ _file,
+ isDeclaration: false));
+ }
+
+ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
+ {
+ SymbolType symbolType = functionDefinitionAst.IsWorkflow
+ ? SymbolType.Workflow
+ : SymbolType.Function;
+
+ // Extent for constructors and method trigger both this and VisitFunctionMember(). Covered in the latter.
+ // This will not exclude nested functions as they have ScriptBlockAst as parent
+ if (functionDefinitionAst.Parent is FunctionMemberAst)
+ {
+ return AstVisitAction.Continue;
+ }
+
+ IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionDefinitionAst);
+ return _action(new SymbolReference(
+ symbolType,
+ "fn " + functionDefinitionAst.Name,
+ VisitorUtils.GetFunctionDisplayName(functionDefinitionAst),
+ nameExtent,
+ functionDefinitionAst.Extent,
+ _file,
+ isDeclaration: true));
+ }
+
+ public override AstVisitAction VisitParameter(ParameterAst parameterAst)
+ {
+ // TODO: Can we fix the display name's type by visiting this in VisitVariableExpression and
+ // getting the TypeConstraintAst somehow?
+ return _action(new SymbolReference(
+ SymbolType.Parameter,
+ "var " + parameterAst.Name.VariablePath.UserPath,
+ VisitorUtils.GetParamDisplayName(parameterAst),
+ parameterAst.Name.Extent,
+ parameterAst.Extent,
+ _file,
+ isDeclaration: true));
+ }
+
+ public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
+ {
+ // Parameters are visited earlier, and we don't want to skip their children because we do
+ // want to visit their type constraints.
+ if (variableExpressionAst.Parent is ParameterAst)
+ {
+ return AstVisitAction.Continue;
+ }
+
+ // TODO: Consider tracking unscoped variable references only when they're declared within
+ // the same function definition.
+ return _action(new SymbolReference(
+ SymbolType.Variable,
+ "var " + VisitorUtils.GetUnqualifiedVariableName(variableExpressionAst.VariablePath),
+ "$" + variableExpressionAst.VariablePath.UserPath,
+ variableExpressionAst.Extent,
+ variableExpressionAst.Extent, // TODO: Maybe parent?
+ _file,
+ isDeclaration: variableExpressionAst.Parent is AssignmentStatementAst or ParameterAst));
+ }
+
+ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst)
+ {
+ SymbolType symbolType = typeDefinitionAst.IsEnum
+ ? SymbolType.Enum
+ : SymbolType.Class;
+
+ IScriptExtent nameExtent = VisitorUtils.GetNameExtent(typeDefinitionAst);
+ return _action(new SymbolReference(
+ symbolType,
+ "type " + typeDefinitionAst.Name,
+ (symbolType is SymbolType.Enum ? "enum " : "class ") + typeDefinitionAst.Name + " { }",
+ nameExtent,
+ typeDefinitionAst.Extent,
+ _file,
+ isDeclaration: true));
+ }
+
+ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst)
+ {
+ return _action(new SymbolReference(
+ SymbolType.Type,
+ "type " + typeExpressionAst.TypeName.Name,
+ "(type) " + typeExpressionAst.TypeName.Name,
+ typeExpressionAst.Extent,
+ typeExpressionAst.Extent,
+ _file,
+ isDeclaration: false));
+ }
+
+ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst)
+ {
+ return _action(new SymbolReference(
+ SymbolType.Type,
+ "type " + typeConstraintAst.TypeName.Name,
+ "(type) " + typeConstraintAst.TypeName.Name,
+ typeConstraintAst.Extent,
+ typeConstraintAst.Extent,
+ _file,
+ isDeclaration: false));
+ }
+
+ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst)
+ {
+ SymbolType symbolType = functionMemberAst.IsConstructor
+ ? SymbolType.Constructor
+ : SymbolType.Method;
+
+ IScriptExtent nameExtent = VisitorUtils.GetNameExtent(functionMemberAst);
+
+ return _action(new SymbolReference(
+ symbolType,
+ "mtd " + functionMemberAst.Name, // We bucket all the overloads.
+ VisitorUtils.GetMemberOverloadName(functionMemberAst),
+ nameExtent,
+ functionMemberAst.Extent,
+ _file,
+ isDeclaration: true));
+ }
+
+ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst)
+ {
+ // Enum members and properties are the "same" according to PowerShell, so the symbol name's
+ // must be the same since we can't distinguish them in VisitMemberExpression.
+ SymbolType symbolType =
+ propertyMemberAst.Parent is TypeDefinitionAst typeAst && typeAst.IsEnum
+ ? SymbolType.EnumMember
+ : SymbolType.Property;
+
+ return _action(new SymbolReference(
+ symbolType,
+ "prop " + propertyMemberAst.Name,
+ VisitorUtils.GetMemberOverloadName(propertyMemberAst),
+ VisitorUtils.GetNameExtent(propertyMemberAst),
+ propertyMemberAst.Extent,
+ _file,
+ isDeclaration: true));
+ }
+
+ public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst)
+ {
+ string? memberName = memberExpressionAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null;
+ if (string.IsNullOrEmpty(memberName))
+ {
+ return AstVisitAction.Continue;
+ }
+
+ // TODO: It's too bad we can't get the property's real symbol and reuse its display string.
+ return _action(new SymbolReference(
+ SymbolType.Property,
+ "prop " + memberName,
+ "(property) " + memberName,
+ memberExpressionAst.Member.Extent,
+ memberExpressionAst.Extent,
+ _file,
+ isDeclaration: false));
+ }
+
+ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst)
+ {
+ string? memberName = methodCallAst.Member is StringConstantExpressionAst stringConstant ? stringConstant.Value : null;
+ if (string.IsNullOrEmpty(memberName))
+ {
+ return AstVisitAction.Continue;
+ }
+
+ // TODO: It's too bad we can't get the member's real symbol and reuse its display string.
+ return _action(new SymbolReference(
+ SymbolType.Method,
+ "mtd " + memberName,
+ "(method) " + memberName,
+ methodCallAst.Member.Extent,
+ methodCallAst.Extent,
+ _file,
+ isDeclaration: false));
+ }
+
+ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst)
+ {
+ string? name = configurationDefinitionAst.InstanceName is StringConstantExpressionAst stringConstant
+ ? stringConstant.Value : null;
+ if (string.IsNullOrEmpty(name))
+ {
+ return AstVisitAction.Continue;
+ }
+
+ IScriptExtent nameExtent = VisitorUtils.GetNameExtent(configurationDefinitionAst);
+ return _action(new SymbolReference(
+ SymbolType.Configuration,
+ "dsc " + name,
+ "configuration " + name + " { }",
+ nameExtent,
+ configurationDefinitionAst.Extent,
+ _file,
+ isDeclaration: true));
+ }
+}
diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindCommandVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindCommandVisitor.cs
deleted file mode 100644
index 4a7b78faa..000000000
--- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindCommandVisitor.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Linq;
-using System.Management.Automation.Language;
-
-namespace Microsoft.PowerShell.EditorServices.Services.Symbols
-{
- ///
- /// The vistior used to find the commandAst of a specific location in an AST
- ///
- internal class FindCommandVisitor : AstVisitor
- {
- private readonly int lineNumber;
- private readonly int columnNumber;
-
- public SymbolReference FoundCommandReference { get; private set; }
-
- public FindCommandVisitor(int lineNumber, int columnNumber)
- {
- this.lineNumber = lineNumber;
- this.columnNumber = columnNumber;
- }
-
- public override AstVisitAction VisitPipeline(PipelineAst pipelineAst)
- {
- if (lineNumber == pipelineAst.Extent.StartLineNumber)
- {
- // Which command is the cursor in?
- foreach (CommandAst commandAst in pipelineAst.PipelineElements.OfType())
- {
- int trueEndColumnNumber = commandAst.Extent.EndColumnNumber;
- string currentLine = commandAst.Extent.StartScriptPosition.Line;
-
- if (currentLine.Length >= trueEndColumnNumber)
- {
- // Get the text left in the line after the command's extent
- string remainingLine =
- currentLine.Substring(
- commandAst.Extent.EndColumnNumber);
-
- // Calculate the "true" end column number by finding out how many
- // whitespace characters are between this command and the next (or
- // the end of the line).
- // NOTE: +1 is added to trueEndColumnNumber to account for the position
- // just after the last character in the command string or script line.
- int preTrimLength = remainingLine.Length;
- int postTrimLength = remainingLine.TrimStart().Length;
- trueEndColumnNumber =
- commandAst.Extent.EndColumnNumber +
- (preTrimLength - postTrimLength) + 1;
- }
-
- if (commandAst.Extent.StartColumnNumber <= columnNumber &&
- trueEndColumnNumber >= columnNumber)
- {
- FoundCommandReference =
- new SymbolReference(
- SymbolType.Function,
- commandAst.CommandElements[0].Extent);
-
- return AstVisitAction.StopVisit;
- }
- }
- }
-
- return base.VisitPipeline(pipelineAst);
- }
- }
-}
diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs
deleted file mode 100644
index 5c3071451..000000000
--- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDeclarationVisitor.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Management.Automation.Language;
-
-namespace Microsoft.PowerShell.EditorServices.Services.Symbols
-{
- ///
- /// The visitor used to find the definition of a symbol
- ///
- internal class FindDeclarationVisitor : AstVisitor
- {
- private readonly SymbolReference symbolRef;
- private readonly string variableName;
-
- public SymbolReference FoundDeclaration { get; private set; }
-
- public FindDeclarationVisitor(SymbolReference symbolRef)
- {
- this.symbolRef = symbolRef;
- if (this.symbolRef.SymbolType == SymbolType.Variable)
- {
- // converts `$varName` to `varName` or of the form ${varName} to varName
- variableName = symbolRef.SymbolName.TrimStart('$').Trim('{', '}');
- }
- }
-
- ///
- /// Decides if the current function definition is the right definition
- /// for the symbol being searched for. The definition of the symbol will be a of type
- /// SymbolType.Function and have the same name as the symbol
- ///
- /// A FunctionDefinitionAst in the script's AST
- /// A decision to stop searching if the right FunctionDefinitionAst was found,
- /// or a decision to continue if it wasn't found
- public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
- {
- // Get the start column number of the function name,
- // instead of the the start column of 'function' and create new extent for the functionName
- int startColumnNumber =
- functionDefinitionAst.Extent.Text.IndexOf(
- functionDefinitionAst.Name, StringComparison.OrdinalIgnoreCase) + 1;
-
- IScriptExtent nameExtent = new ScriptExtent()
- {
- Text = functionDefinitionAst.Name,
- StartLineNumber = functionDefinitionAst.Extent.StartLineNumber,
- StartColumnNumber = startColumnNumber,
- EndLineNumber = functionDefinitionAst.Extent.StartLineNumber,
- EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length,
- File = functionDefinitionAst.Extent.File
- };
-
- // We compare to the SymbolName instead of its text because it may have been resolved
- // from an alias.
- if (symbolRef.SymbolType.Equals(SymbolType.Function) &&
- nameExtent.Text.Equals(symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase))
- {
- FoundDeclaration =
- new SymbolReference(
- SymbolType.Function,
- nameExtent);
-
- return AstVisitAction.StopVisit;
- }
-
- return base.VisitFunctionDefinition(functionDefinitionAst);
- }
-
- ///
- /// Check if the left hand side of an assignmentStatementAst is a VariableExpressionAst
- /// with the same name as that of symbolRef.
- ///
- /// An AssignmentStatementAst
- /// A decision to stop searching if the right VariableExpressionAst was found,
- /// or a decision to continue if it wasn't found
- public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst)
- {
- if (variableName == null)
- {
- return AstVisitAction.Continue;
- }
-
- // We want to check VariableExpressionAsts from within this AssignmentStatementAst so we visit it.
- FindDeclarationVariableExpressionVisitor visitor = new(symbolRef);
- assignmentStatementAst.Left.Visit(visitor);
-
- if (visitor.FoundDeclaration != null)
- {
- FoundDeclaration = visitor.FoundDeclaration;
- return AstVisitAction.StopVisit;
- }
- return AstVisitAction.Continue;
- }
-
- ///
- /// The private visitor used to find the variable expression that matches a symbol
- ///
- private class FindDeclarationVariableExpressionVisitor : AstVisitor
- {
- private readonly SymbolReference symbolRef;
- private readonly string variableName;
-
- public SymbolReference FoundDeclaration { get; private set; }
-
- public FindDeclarationVariableExpressionVisitor(SymbolReference symbolRef)
- {
- this.symbolRef = symbolRef;
- if (this.symbolRef.SymbolType == SymbolType.Variable)
- {
- // converts `$varName` to `varName` or of the form ${varName} to varName
- variableName = symbolRef.SymbolName.TrimStart('$').Trim('{', '}');
- }
- }
-
- ///
- /// Check if the VariableExpressionAst has the same name as that of symbolRef.
- ///
- /// A VariableExpressionAst
- /// A decision to stop searching if the right VariableExpressionAst was found,
- /// or a decision to continue if it wasn't found
- public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
- {
- if (variableExpressionAst.VariablePath.UserPath.Equals(variableName, StringComparison.OrdinalIgnoreCase))
- {
- // TODO also find instances of set-variable
- FoundDeclaration = new SymbolReference(SymbolType.Variable, variableExpressionAst.Extent);
- return AstVisitAction.StopVisit;
- }
- return AstVisitAction.Continue;
- }
-
- public override AstVisitAction VisitMemberExpression(MemberExpressionAst functionDefinitionAst) =>
- // We don't want to discover any variables in member expressisons (`$something.Foo`)
- AstVisitAction.SkipChildren;
-
- public override AstVisitAction VisitIndexExpression(IndexExpressionAst functionDefinitionAst) =>
- // We don't want to discover any variables in index expressions (`$something[0]`)
- AstVisitAction.SkipChildren;
- }
- }
-}
diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDotSourcedVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDotSourcedVisitor.cs
deleted file mode 100644
index 416a5d611..000000000
--- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindDotSourcedVisitor.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using System.Management.Automation.Language;
-using Microsoft.PowerShell.EditorServices.Utility;
-
-namespace Microsoft.PowerShell.EditorServices.Services.Symbols
-{
- ///
- /// The visitor used to find the dot-sourced files in an AST
- ///
- internal class FindDotSourcedVisitor : AstVisitor
- {
- private readonly string _psScriptRoot;
-
- ///
- /// A hash set of the dot sourced files (because we don't want duplicates)
- ///
- public HashSet DotSourcedFiles { get; }
-
- ///
- /// Creates a new instance of the FindDotSourcedVisitor class.
- ///
- /// Pre-calculated value of $PSScriptRoot
- public FindDotSourcedVisitor(string psScriptRoot)
- {
- DotSourcedFiles = new HashSet(StringComparer.CurrentCultureIgnoreCase);
- _psScriptRoot = psScriptRoot;
- }
-
- ///
- /// Checks to see if the command invocation is a dot
- /// in order to find a dot sourced file
- ///
- /// A CommandAst object in the script's AST
- /// A decision to stop searching if the right commandAst was found,
- /// or a decision to continue if it wasn't found
- public override AstVisitAction VisitCommand(CommandAst commandAst)
- {
- CommandElementAst commandElementAst = commandAst.CommandElements[0];
- if (commandAst.InvocationOperator.Equals(TokenKind.Dot))
- {
- string path = commandElementAst switch
- {
- StringConstantExpressionAst stringConstantExpressionAst => stringConstantExpressionAst.Value,
- ExpandableStringExpressionAst expandableStringExpressionAst => GetPathFromExpandableStringExpression(expandableStringExpressionAst),
- _ => null,
- };
- if (!string.IsNullOrWhiteSpace(path))
- {
- DotSourcedFiles.Add(PathUtils.NormalizePathSeparators(path));
- }
- }
-
- return base.VisitCommand(commandAst);
- }
-
- private string GetPathFromExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst)
- {
- string path = expandableStringExpressionAst.Value;
- foreach (ExpressionAst nestedExpression in expandableStringExpressionAst.NestedExpressions)
- {
- // If the string contains the variable $PSScriptRoot, we replace it with the corresponding value.
- if (!(nestedExpression is VariableExpressionAst variableAst
- && variableAst.VariablePath.UserPath.Equals("PSScriptRoot", StringComparison.OrdinalIgnoreCase)))
- {
- return null; // We return null instead of a partially evaluated ExpandableStringExpression.
- }
-
- path = path.Replace(variableAst.ToString(), _psScriptRoot);
- }
-
- return path;
- }
- }
-}
diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs
deleted file mode 100644
index 44b64c8f5..000000000
--- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindReferencesVisitor.cs
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using System.Management.Automation.Language;
-using Microsoft.PowerShell.EditorServices.Utility;
-
-namespace Microsoft.PowerShell.EditorServices.Services.Symbols
-{
- ///
- /// The visitor used to find the references of a symbol in a script's AST
- ///
- internal class FindReferencesVisitor : AstVisitor
- {
- private readonly SymbolReference _symbolRef;
- private readonly IDictionary> _cmdletToAliasDictionary;
- private readonly IDictionary _aliasToCmdletDictionary;
- private readonly string _symbolRefCommandName;
- private readonly bool _needsAliases;
-
- public List FoundReferences { get; set; }
-
- ///
- /// Constructor used when searching for aliases is needed
- ///
- /// The found symbolReference that other symbols are being compared to
- /// Dictionary maping cmdlets to aliases for finding alias references
- /// Dictionary maping aliases to cmdlets for finding alias references
- public FindReferencesVisitor(
- SymbolReference symbolReference,
- IDictionary> cmdletToAliasDictionary = default,
- IDictionary aliasToCmdletDictionary = default)
- {
- _symbolRef = symbolReference;
- FoundReferences = new List();
-
- if (cmdletToAliasDictionary is null || aliasToCmdletDictionary is null)
- {
- _needsAliases = false;
- return;
- }
-
- _needsAliases = true;
- _cmdletToAliasDictionary = cmdletToAliasDictionary;
- _aliasToCmdletDictionary = aliasToCmdletDictionary;
-
- // Try to get the symbolReference's command name of an alias. If a command name does not
- // exists (if the symbol isn't an alias to a command) set symbolRefCommandName to an
- // empty string.
- aliasToCmdletDictionary.TryGetValue(symbolReference.ScriptRegion.Text, out _symbolRefCommandName);
-
- if (_symbolRefCommandName == null)
- {
- _symbolRefCommandName = string.Empty;
- }
- }
-
- ///
- /// Decides if the current command is a reference of the symbol being searched for.
- /// A reference of the symbol will be a of type SymbolType.Function
- /// and have the same name as the symbol
- ///
- /// A CommandAst in the script's AST
- /// A visit action that continues the search for references
- public override AstVisitAction VisitCommand(CommandAst commandAst)
- {
- Ast commandNameAst = commandAst.CommandElements[0];
- string commandName = commandNameAst.Extent.Text;
-
- if (_symbolRef.SymbolType.Equals(SymbolType.Function))
- {
- if (_needsAliases)
- {
- // Try to get the commandAst's name and aliases.
- //
- // If a command does not exist (if the symbol isn't an alias to a command) set
- // command to an empty string value string command.
- //
- // If the aliases do not exist (if the symbol isn't a command that has aliases)
- // set aliases to an empty List
- _cmdletToAliasDictionary.TryGetValue(commandName, out List aliases);
- _aliasToCmdletDictionary.TryGetValue(commandName, out string command);
- if (aliases == null) { aliases = new List(); }
- if (command == null) { command = string.Empty; }
-
- // Check if the found symbol's name is the same as the commandAst's name OR
- // if the symbol's name is an alias for this commandAst's name (commandAst is a cmdlet) OR
- // if the symbol's name is the same as the commandAst's cmdlet name (commandAst is a alias)
- if (commandName.Equals(_symbolRef.SymbolName, StringComparison.OrdinalIgnoreCase)
- // Note that PowerShell command names and aliases are case insensitive.
- || aliases.Exists((match) => string.Equals(match, _symbolRef.ScriptRegion.Text, StringComparison.OrdinalIgnoreCase))
- || command.Equals(_symbolRef.ScriptRegion.Text, StringComparison.OrdinalIgnoreCase)
- || (!string.IsNullOrEmpty(command)
- && command.Equals(_symbolRefCommandName, StringComparison.OrdinalIgnoreCase)))
- {
- FoundReferences.Add(new SymbolReference(SymbolType.Function, commandNameAst.Extent));
- }
- }
- else // search does not include aliases
- {
- if (commandName.Equals(_symbolRef.SymbolName, StringComparison.OrdinalIgnoreCase))
- {
- FoundReferences.Add(new SymbolReference(SymbolType.Function, commandNameAst.Extent));
- }
- }
- }
-
- return base.VisitCommand(commandAst);
- }
-
- ///
- /// Decides if the current function definition is a reference of the symbol being searched for.
- /// A reference of the symbol will be a of type SymbolType.Function and have the same name as the symbol
- ///
- /// A functionDefinitionAst in the script's AST
- /// A visit action that continues the search for references
- public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
- {
- (int startColumnNumber, int startLineNumber) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst);
-
- IScriptExtent nameExtent = new ScriptExtent()
- {
- Text = functionDefinitionAst.Name,
- StartLineNumber = startLineNumber,
- EndLineNumber = startLineNumber,
- StartColumnNumber = startColumnNumber,
- EndColumnNumber = startColumnNumber + functionDefinitionAst.Name.Length,
- File = functionDefinitionAst.Extent.File
- };
-
- if (_symbolRef.SymbolType.Equals(SymbolType.Function) &&
- nameExtent.Text.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase))
- {
- FoundReferences.Add(new SymbolReference(SymbolType.Function, nameExtent));
- }
- return base.VisitFunctionDefinition(functionDefinitionAst);
- }
-
- ///
- /// Decides if the current function definition is a reference of the symbol being searched for.
- /// A reference of the symbol will be a of type SymbolType.Parameter and have the same name as the symbol
- ///
- /// A commandParameterAst in the script's AST
- /// A visit action that continues the search for references
- public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst)
- {
- if (_symbolRef.SymbolType.Equals(SymbolType.Parameter) &&
- commandParameterAst.Extent.Text.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase))
- {
- FoundReferences.Add(new SymbolReference(SymbolType.Parameter, commandParameterAst.Extent));
- }
- return AstVisitAction.Continue;
- }
-
- ///
- /// Decides if the current function definition is a reference of the symbol being searched for.
- /// A reference of the symbol will be a of type SymbolType.Variable and have the same name as the symbol
- ///
- /// A variableExpressionAst in the script's AST
- /// A visit action that continues the search for references
- public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
- {
- if (_symbolRef.SymbolType.Equals(SymbolType.Variable)
- && variableExpressionAst.Extent.Text.Equals(_symbolRef.SymbolName, StringComparison.CurrentCultureIgnoreCase))
- {
- FoundReferences.Add(new SymbolReference(SymbolType.Variable, variableExpressionAst.Extent));
- }
- return AstVisitAction.Continue;
- }
- }
-}
diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs
deleted file mode 100644
index bf2520a3c..000000000
--- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolVisitor.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Management.Automation.Language;
-using Microsoft.PowerShell.EditorServices.Utility;
-
-namespace Microsoft.PowerShell.EditorServices.Services.Symbols
-{
- ///
- /// The visitor used to find the symbol at a specific location in the AST
- ///
- internal class FindSymbolVisitor : AstVisitor
- {
- private readonly int lineNumber;
- private readonly int columnNumber;
- private readonly bool includeFunctionDefinitions;
-
- public SymbolReference FoundSymbolReference { get; private set; }
-
- public FindSymbolVisitor(
- int lineNumber,
- int columnNumber,
- bool includeFunctionDefinitions)
- {
- this.lineNumber = lineNumber;
- this.columnNumber = columnNumber;
- this.includeFunctionDefinitions = includeFunctionDefinitions;
- }
-
- ///
- /// Checks to see if this command ast is the symbol we are looking for.
- ///
- /// A CommandAst object in the script's AST
- /// A decision to stop searching if the right symbol was found,
- /// or a decision to continue if it wasn't found
- public override AstVisitAction VisitCommand(CommandAst commandAst)
- {
- Ast commandNameAst = commandAst.CommandElements[0];
-
- if (IsPositionInExtent(commandNameAst.Extent))
- {
- FoundSymbolReference =
- new SymbolReference(
- SymbolType.Function,
- commandNameAst.Extent);
-
- return AstVisitAction.StopVisit;
- }
-
- return base.VisitCommand(commandAst);
- }
-
- ///
- /// Checks to see if this function definition is the symbol we are looking for.
- ///
- /// A functionDefinitionAst object in the script's AST
- /// A decision to stop searching if the right symbol was found,
- /// or a decision to continue if it wasn't found
- public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
- {
- int startLineNumber = functionDefinitionAst.Extent.StartLineNumber;
- int startColumnNumber = functionDefinitionAst.Extent.StartColumnNumber;
- int endLineNumber = functionDefinitionAst.Extent.EndLineNumber;
- int endColumnNumber = functionDefinitionAst.Extent.EndColumnNumber;
-
- if (!includeFunctionDefinitions)
- {
- // We only want the function name
- (int startColumn, int startLine) = VisitorUtils.GetNameStartColumnAndLineNumbersFromAst(functionDefinitionAst);
- startLineNumber = startLine;
- startColumnNumber = startColumn;
- endLineNumber = startLine;
- endColumnNumber = startColumn + functionDefinitionAst.Name.Length;
- }
-
- IScriptExtent nameExtent = new ScriptExtent()
- {
- Text = functionDefinitionAst.Name,
- StartLineNumber = startLineNumber,
- EndLineNumber = endLineNumber,
- StartColumnNumber = startColumnNumber,
- EndColumnNumber = endColumnNumber,
- File = functionDefinitionAst.Extent.File
- };
-
- if (IsPositionInExtent(nameExtent))
- {
- FoundSymbolReference =
- new SymbolReference(
- SymbolType.Function,
- nameExtent);
-
- return AstVisitAction.StopVisit;
- }
-
- return base.VisitFunctionDefinition(functionDefinitionAst);
- }
-
- ///
- /// Checks to see if this command parameter is the symbol we are looking for.
- ///
- /// A CommandParameterAst object in the script's AST
- /// A decision to stop searching if the right symbol was found,
- /// or a decision to continue if it wasn't found
- public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst)
- {
- if (IsPositionInExtent(commandParameterAst.Extent))
- {
- FoundSymbolReference =
- new SymbolReference(
- SymbolType.Parameter,
- commandParameterAst.Extent);
- return AstVisitAction.StopVisit;
- }
- return AstVisitAction.Continue;
- }
-
- ///
- /// Checks to see if this variable expression is the symbol we are looking for.
- ///
- /// A VariableExpressionAst object in the script's AST
- /// A decision to stop searching if the right symbol was found,
- /// or a decision to continue if it wasn't found
- public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
- {
- if (IsPositionInExtent(variableExpressionAst.Extent))
- {
- FoundSymbolReference =
- new SymbolReference(
- SymbolType.Variable,
- variableExpressionAst.Extent);
-
- return AstVisitAction.StopVisit;
- }
-
- return AstVisitAction.Continue;
- }
-
- ///
- /// Is the position of the given location is in the ast's extent
- ///
- /// The script extent of the element
- /// True if the given position is in the range of the element's extent
- private bool IsPositionInExtent(IScriptExtent extent)
- {
- return extent.StartLineNumber == lineNumber &&
- extent.StartColumnNumber <= columnNumber &&
- extent.EndColumnNumber >= columnNumber;
- }
- }
-}
diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs
deleted file mode 100644
index 970acb4e7..000000000
--- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Collections.Generic;
-using System.Management.Automation.Language;
-
-namespace Microsoft.PowerShell.EditorServices.Services.Symbols
-{
- ///
- /// The visitor used to find all the symbols (function and class defs) in the AST.
- ///
- ///
- /// Requires PowerShell v3 or higher
- ///
- internal class FindSymbolsVisitor : AstVisitor
- {
- public List SymbolReferences { get; }
-
- public FindSymbolsVisitor() => SymbolReferences = new List();
-
- ///
- /// Adds each function definition as a
- ///
- /// A functionDefinitionAst object in the script's AST
- /// A decision to stop searching if the right symbol was found,
- /// or a decision to continue if it wasn't found
- public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
- {
- IScriptExtent nameExtent = new ScriptExtent()
- {
- Text = functionDefinitionAst.Name,
- StartLineNumber = functionDefinitionAst.Extent.StartLineNumber,
- EndLineNumber = functionDefinitionAst.Extent.EndLineNumber,
- StartColumnNumber = functionDefinitionAst.Extent.StartColumnNumber,
- EndColumnNumber = functionDefinitionAst.Extent.EndColumnNumber,
- File = functionDefinitionAst.Extent.File
- };
-
- SymbolType symbolType =
- functionDefinitionAst.IsWorkflow ?
- SymbolType.Workflow : SymbolType.Function;
-
- SymbolReferences.Add(
- new SymbolReference(
- symbolType,
- nameExtent));
-
- return AstVisitAction.Continue;
- }
-
- ///
- /// Checks to see if this variable expression is the symbol we are looking for.
- ///
- /// A VariableExpressionAst object in the script's AST
- /// A decision to stop searching if the right symbol was found,
- /// or a decision to continue if it wasn't found
- public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
- {
- if (!IsAssignedAtScriptScope(variableExpressionAst))
- {
- return AstVisitAction.Continue;
- }
-
- SymbolReferences.Add(
- new SymbolReference(
- SymbolType.Variable,
- variableExpressionAst.Extent));
-
- return AstVisitAction.Continue;
- }
-
- private static bool IsAssignedAtScriptScope(VariableExpressionAst variableExpressionAst)
- {
- Ast parent = variableExpressionAst.Parent;
- if (parent is not AssignmentStatementAst)
- {
- return false;
- }
-
- parent = parent.Parent;
- return parent is null || parent.Parent is null || parent.Parent.Parent is null;
- }
- }
-
- ///
- /// Visitor to find all the keys in Hashtable AST
- ///
- internal class FindHashtableSymbolsVisitor : AstVisitor
- {
- ///
- /// List of symbols (keys) found in the hashtable
- ///
- public List SymbolReferences { get; }
-
- ///
- /// Initializes a new instance of FindHashtableSymbolsVisitor class
- ///
- public FindHashtableSymbolsVisitor() => SymbolReferences = new List();
-
- ///
- /// Adds keys in the input hashtable to the symbol reference
- ///
- public override AstVisitAction VisitHashtable(HashtableAst hashtableAst)
- {
- if (hashtableAst.KeyValuePairs == null)
- {
- return AstVisitAction.Continue;
- }
-
- foreach (System.Tuple kvp in hashtableAst.KeyValuePairs)
- {
- if (kvp.Item1 is StringConstantExpressionAst keyStrConstExprAst)
- {
- IScriptExtent nameExtent = new ScriptExtent()
- {
- Text = keyStrConstExprAst.Value,
- StartLineNumber = kvp.Item1.Extent.StartLineNumber,
- EndLineNumber = kvp.Item2.Extent.EndLineNumber,
- StartColumnNumber = kvp.Item1.Extent.StartColumnNumber,
- EndColumnNumber = kvp.Item2.Extent.EndColumnNumber,
- File = hashtableAst.Extent.File
- };
-
- const SymbolType symbolType = SymbolType.HashtableKey;
-
- SymbolReferences.Add(
- new SymbolReference(
- symbolType,
- nameExtent));
- }
- }
-
- return AstVisitAction.Continue;
- }
- }
-}
diff --git a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs b/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs
deleted file mode 100644
index 15f5e49db..000000000
--- a/src/PowerShellEditorServices/Services/Symbols/Vistors/FindSymbolsVisitor2.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-namespace Microsoft.PowerShell.EditorServices.Services.Symbols
-{
- // TODO: Restore this when we figure out how to support multiple
- // PS versions in the new PSES-as-a-module world (issue #276)
-
- /////
- ///// The visitor used to find all the symbols (function and class defs) in the AST.
- /////
- /////
- ///// Requires PowerShell v5 or higher
- /////
- /////
- //internal class FindSymbolsVisitor2 : AstVisitor2
- //{
- // private FindSymbolsVisitor findSymbolsVisitor;
-
- // public List SymbolReferences
- // {
- // get
- // {
- // return this.findSymbolsVisitor.SymbolReferences;
- // }
- // }
-
- // public FindSymbolsVisitor2()
- // {
- // this.findSymbolsVisitor = new FindSymbolsVisitor();
- // }
-
- // ///
- // /// Adds each function definition as a
- // ///
- // /// A functionDefinitionAst object in the script's AST
- // /// A decision to stop searching if the right symbol was found,
- // /// or a decision to continue if it wasn't found
- // public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
- // {
- // return this.findSymbolsVisitor.VisitFunctionDefinition(functionDefinitionAst);
- // }
-
- // ///
- // /// Checks to see if this variable expression is the symbol we are looking for.
- // ///
- // /// A VariableExpressionAst object in the script's AST
- // /// A decision to stop searching if the right symbol was found,
- // /// or a decision to continue if it wasn't found
- // public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
- // {
- // return this.findSymbolsVisitor.VisitVariableExpression(variableExpressionAst);
- // }
-
- // public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst)
- // {
- // IScriptExtent nameExtent = new ScriptExtent()
- // {
- // Text = configurationDefinitionAst.InstanceName.Extent.Text,
- // StartLineNumber = configurationDefinitionAst.Extent.StartLineNumber,
- // EndLineNumber = configurationDefinitionAst.Extent.EndLineNumber,
- // StartColumnNumber = configurationDefinitionAst.Extent.StartColumnNumber,
- // EndColumnNumber = configurationDefinitionAst.Extent.EndColumnNumber
- // };
-
- // this.findSymbolsVisitor.SymbolReferences.Add(
- // new SymbolReference(
- // SymbolType.Configuration,
- // nameExtent));
-
- // return AstVisitAction.Continue;
- // }
- //}
-}
diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs
index 2681baa19..895c804ec 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/CodeActionHandler.cs
@@ -95,7 +95,7 @@ public override async Task Handle(CodeActionParams
{
Uri = request.TextDocument.Uri
},
- Edits = new TextEditContainer(ScriptRegion.ToTextEdit(markerCorrection.Edit))
+ Edits = new TextEditContainer(markerCorrection.Edit.ToTextEdit())
}))
}
});
diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs
index 8b9c2fc60..f39ca9917 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DefinitionHandler.cs
@@ -43,43 +43,38 @@ public override async Task Handle(DefinitionParams requ
request.Position.Line + 1,
request.Position.Character + 1);
- List definitionLocations = new();
- if (foundSymbol != null)
+ if (foundSymbol is null)
{
- SymbolReference foundDefinition = await _symbolsService.GetDefinitionOfSymbolAsync(
- scriptFile,
- foundSymbol).ConfigureAwait(false);
+ return new LocationOrLocationLinks();
+ }
- if (foundDefinition != null)
- {
- definitionLocations.Add(
+ // Short-circuit if we're already on the definition.
+ if (foundSymbol.IsDeclaration)
+ {
+ return new LocationOrLocationLinks(
+ new LocationOrLocationLink[] {
new LocationOrLocationLink(
new Location
{
- Uri = DocumentUri.From(foundDefinition.FilePath),
- Range = GetRangeFromScriptRegion(foundDefinition.ScriptRegion)
- }));
- }
+ Uri = DocumentUri.From(foundSymbol.FilePath),
+ Range = foundSymbol.NameRegion.ToRange()
+ })});
}
- return new LocationOrLocationLinks(definitionLocations);
- }
-
- private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion)
- {
- return new Range
+ List definitionLocations = new();
+ foreach (SymbolReference foundDefinition in await _symbolsService.GetDefinitionOfSymbolAsync(
+ scriptFile, foundSymbol, cancellationToken).ConfigureAwait(false))
{
- Start = new Position
- {
- Line = scriptRegion.StartLineNumber - 1,
- Character = scriptRegion.StartColumnNumber - 1
- },
- End = new Position
- {
- Line = scriptRegion.EndLineNumber - 1,
- Character = scriptRegion.EndColumnNumber - 1
- }
- };
+ definitionLocations.Add(
+ new LocationOrLocationLink(
+ new Location
+ {
+ Uri = DocumentUri.From(foundDefinition.FilePath),
+ Range = foundDefinition.NameRegion.ToRange()
+ }));
+ }
+
+ return new LocationOrLocationLinks(definitionLocations);
}
}
}
diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs
index 8e318f3bc..5758e59a1 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentHighlightHandler.cs
@@ -40,25 +40,26 @@ public override Task Handle(
{
ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri);
- IReadOnlyList symbolOccurrences = SymbolsService.FindOccurrencesInFile(
+ IEnumerable occurrences = SymbolsService.FindOccurrencesInFile(
scriptFile,
request.Position.Line + 1,
request.Position.Character + 1);
- if (symbolOccurrences is null)
+ if (occurrences is null)
{
return Task.FromResult(s_emptyHighlightContainer);
}
- DocumentHighlight[] highlights = new DocumentHighlight[symbolOccurrences.Count];
- for (int i = 0; i < symbolOccurrences.Count; i++)
+ List highlights = new();
+ foreach (SymbolReference occurrence in occurrences)
{
- highlights[i] = new DocumentHighlight
+ highlights.Add(new DocumentHighlight
{
Kind = DocumentHighlightKind.Write, // TODO: Which symbol types are writable?
- Range = symbolOccurrences[i].ScriptRegion.ToRange()
- };
+ Range = occurrence.NameRegion.ToRange() // Just the symbol name
+ });
}
+
_logger.LogDebug("Highlights: " + highlights);
return Task.FromResult(new DocumentHighlightContainer(highlights));
diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs
index f0973a7f0..823d01f26 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/DocumentSymbolHandler.cs
@@ -44,44 +44,66 @@ public PsesDocumentSymbolHandler(ILoggerFactory factory, WorkspaceService worksp
DocumentSelector = LspUtils.PowerShellDocumentSelector
};
- public override Task Handle(DocumentSymbolParams request, CancellationToken cancellationToken)
+ // AKA the outline feature
+ public override async Task Handle(DocumentSymbolParams request, CancellationToken cancellationToken)
{
ScriptFile scriptFile = _workspaceService.GetFile(request.TextDocument.Uri);
- IEnumerable foundSymbols =
- ProvideDocumentSymbols(scriptFile);
-
- SymbolInformationOrDocumentSymbol[] symbols = null;
+ IEnumerable foundSymbols = ProvideDocumentSymbols(scriptFile);
+ if (foundSymbols is null)
+ {
+ return null;
+ }
string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath);
- symbols = foundSymbols != null
- ? foundSymbols
- .Select(r =>
- {
- return new SymbolInformationOrDocumentSymbol(new SymbolInformation
- {
- ContainerName = containerName,
- Kind = GetSymbolKind(r.SymbolType),
- Location = new Location
- {
- Uri = DocumentUri.From(r.FilePath),
- Range = GetRangeFromScriptRegion(r.ScriptRegion)
- },
- Name = GetDecoratedSymbolName(r)
- });
- })
- .ToArray()
- : Array.Empty();
-
- return Task.FromResult(new SymbolInformationOrDocumentSymbolContainer(symbols));
+ List symbols = new();
+ foreach (SymbolReference r in foundSymbols)
+ {
+ // This async method is pretty dense with synchronous code
+ // so it's helpful to add some yields.
+ await Task.Yield();
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Outline view should only include declarations.
+ //
+ // TODO: We should also include function invocations that are part of DSLs (like
+ // Invoke-Build etc.).
+ if (!r.IsDeclaration || r.Type is SymbolType.Parameter)
+ {
+ continue;
+ }
+
+ // TODO: This should be a DocumentSymbol now as SymbolInformation is deprecated. But
+ // this requires figuring out how to populate `children`. Once we do that, the range
+ // can be NameRegion.
+ //
+ // symbols.Add(new SymbolInformationOrDocumentSymbol(new DocumentSymbol
+ // {
+ // Name = SymbolTypeUtils.GetDecoratedSymbolName(r),
+ // Kind = SymbolTypeUtils.GetSymbolKind(r.SymbolType),
+ // Range = r.ScriptRegion.ToRange(),
+ // SelectionRange = r.NameRegion.ToRange()
+ // }));
+ symbols.Add(new SymbolInformationOrDocumentSymbol(new SymbolInformation
+ {
+ ContainerName = containerName,
+ Kind = SymbolTypeUtils.GetSymbolKind(r.Type),
+ Location = new Location
+ {
+ Uri = DocumentUri.From(r.FilePath),
+ Range = new Range(r.NameRegion.ToRange().Start, r.ScriptRegion.ToRange().End) // Jump to name start, but keep whole range to support symbol tree in outline
+ },
+ Name = r.Name
+ }));
+ }
+
+ return new SymbolInformationOrDocumentSymbolContainer(symbols);
}
- private IEnumerable ProvideDocumentSymbols(
- ScriptFile scriptFile)
+ private IEnumerable ProvideDocumentSymbols(ScriptFile scriptFile)
{
- return
- InvokeProviders(p => p.ProvideDocumentSymbols(scriptFile))
+ return InvokeProviders(p => p.ProvideDocumentSymbols(scriptFile))
.SelectMany(r => r);
}
@@ -123,45 +145,5 @@ protected IEnumerable InvokeProviders(
return providerResults;
}
-
- private static SymbolKind GetSymbolKind(SymbolType symbolType)
- {
- return symbolType switch
- {
- SymbolType.Configuration or SymbolType.Function or SymbolType.Workflow => SymbolKind.Function,
- _ => SymbolKind.Variable,
- };
- }
-
- private static string GetDecoratedSymbolName(ISymbolReference symbolReference)
- {
- string name = symbolReference.SymbolName;
-
- if (symbolReference.SymbolType is SymbolType.Configuration or
- SymbolType.Function or
- SymbolType.Workflow)
- {
- name += " { }";
- }
-
- return name;
- }
-
- private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion)
- {
- return new Range
- {
- Start = new Position
- {
- Line = scriptRegion.StartLineNumber - 1,
- Character = scriptRegion.StartColumnNumber - 1
- },
- End = new Position
- {
- Line = scriptRegion.EndLineNumber - 1,
- Character = scriptRegion.EndColumnNumber - 1
- }
- };
- }
}
}
diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs
index cf90c6565..567da0159 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/HoverHandler.cs
@@ -52,14 +52,14 @@ await _symbolsService.FindSymbolDetailsAtLocationAsync(
request.Position.Line + 1,
request.Position.Character + 1).ConfigureAwait(false);
- if (symbolDetails == null)
+ if (symbolDetails is null)
{
return null;
}
List symbolInfo = new()
{
- new MarkedString("PowerShell", symbolDetails.DisplayString)
+ new MarkedString("PowerShell", symbolDetails.SymbolReference.Name)
};
if (!string.IsNullOrEmpty(symbolDetails.Documentation))
@@ -67,29 +67,10 @@ await _symbolsService.FindSymbolDetailsAtLocationAsync(
symbolInfo.Add(new MarkedString("markdown", symbolDetails.Documentation));
}
- Range symbolRange = GetRangeFromScriptRegion(symbolDetails.SymbolReference.ScriptRegion);
-
return new Hover
{
Contents = new MarkedStringsOrMarkupContent(symbolInfo),
- Range = symbolRange
- };
- }
-
- private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion)
- {
- return new Range
- {
- Start = new Position
- {
- Line = scriptRegion.StartLineNumber - 1,
- Character = scriptRegion.StartColumnNumber - 1
- },
- End = new Position
- {
- Line = scriptRegion.EndLineNumber - 1,
- Character = scriptRegion.EndColumnNumber - 1
- }
+ Range = symbolDetails.SymbolReference.NameRegion.ToRange()
};
}
}
diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs
index d76aa7c66..1d41f3571 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/ReferencesHandler.cs
@@ -41,45 +41,24 @@ public override async Task Handle(ReferenceParams request, Ca
request.Position.Line + 1,
request.Position.Character + 1);
- List referencesResult =
- await _symbolsService.FindReferencesOfSymbol(
- foundSymbol,
- _workspaceService.ExpandScriptReferences(scriptFile),
- _workspaceService,
- cancellationToken).ConfigureAwait(false);
-
List locations = new();
-
- if (referencesResult != null)
+ foreach (SymbolReference foundReference in await _symbolsService.ScanForReferencesOfSymbolAsync(
+ foundSymbol, cancellationToken).ConfigureAwait(false))
{
- foreach (SymbolReference foundReference in referencesResult)
+ // Respect the request's setting to include declarations.
+ if (!request.Context.IncludeDeclaration && foundReference.IsDeclaration)
{
- locations.Add(new Location
- {
- Uri = DocumentUri.From(foundReference.FilePath),
- Range = GetRangeFromScriptRegion(foundReference.ScriptRegion)
- });
+ continue;
}
+
+ locations.Add(new Location
+ {
+ Uri = DocumentUri.From(foundReference.FilePath),
+ Range = foundReference.NameRegion.ToRange()
+ });
}
return new LocationContainer(locations);
}
-
- private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion)
- {
- return new Range
- {
- Start = new Position
- {
- Line = scriptRegion.StartLineNumber - 1,
- Character = scriptRegion.StartColumnNumber - 1
- },
- End = new Position
- {
- Line = scriptRegion.EndLineNumber - 1,
- Character = scriptRegion.EndColumnNumber - 1
- }
- };
- }
}
}
diff --git a/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs b/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs
index 3e294a1e2..240cef13d 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/Handlers/SignatureHelpHandler.cs
@@ -34,7 +34,7 @@ public PsesSignatureHelpHandler(
protected override SignatureHelpRegistrationOptions CreateRegistrationOptions(SignatureHelpCapability capability, ClientCapabilities clientCapabilities) => new()
{
DocumentSelector = LspUtils.PowerShellDocumentSelector,
- // A sane default of " ". We may be able to include others like "-".
+ // TODO: We should evaluate what triggers (and re-triggers) signature help (like dash?)
TriggerCharacters = new Container(" ")
};
@@ -54,7 +54,7 @@ await _symbolsService.FindParameterSetsInFileAsync(
request.Position.Line + 1,
request.Position.Character + 1).ConfigureAwait(false);
- if (parameterSets == null)
+ if (parameterSets is null)
{
return new SignatureHelp();
}
@@ -89,7 +89,7 @@ private static ParameterInformation CreateParameterInfo(ParameterInfo parameterI
return new ParameterInformation
{
Label = parameterInfo.Name,
- Documentation = string.Empty
+ Documentation = parameterInfo.HelpMessage
};
}
}
diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs
index c3e5ac303..3e52e4938 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptFile.cs
@@ -7,7 +7,6 @@
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
-using Microsoft.PowerShell.EditorServices.Services.Symbols;
using Microsoft.PowerShell.EditorServices.Utility;
using OmniSharp.Extensions.LanguageServer.Protocol;
@@ -53,6 +52,7 @@ internal sealed class ScriptFile
/// Gets or sets a boolean that determines whether
/// semantic analysis should be enabled for this file.
/// For internal use only.
+ /// TODO: Actually use and respect this property to avoid built-in files from being analyzed.
///
internal bool IsAnalysisEnabled { get; set; }
@@ -110,15 +110,6 @@ public Token[] ScriptTokens
private set;
}
- ///
- /// Gets the array of filepaths dot sourced in this ScriptFile
- ///
- public string[] ReferencedFiles
- {
- get;
- private set;
- }
-
internal ReferenceTable References { get; }
internal bool IsOpen { get; set; }
@@ -599,20 +590,6 @@ private void ParseFileContents()
parseErrors
.Select(ScriptFileMarker.FromParseError)
.ToList();
-
- // Untitled files have no directory
- // Discussed in https://github.com/PowerShell/PowerShellEditorServices/pull/815.
- // Rather than working hard to enable things for untitled files like a phantom directory,
- // users should save the file.
- if (IsInMemory)
- {
- // Need to initialize the ReferencedFiles property to an empty array.
- ReferencedFiles = Array.Empty();
- return;
- }
-
- // Get all dot sourced referenced files and store them
- ReferencedFiles = AstOperations.FindDotSourcedIncludes(ScriptAst, Path.GetDirectoryName(FilePath));
}
#endregion
diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptFileMarker.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptFileMarker.cs
index 43c465701..e3273e3e6 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/ScriptFileMarker.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptFileMarker.cs
@@ -34,19 +34,19 @@ public enum ScriptFileMarkerLevel
///
/// Information: This warning is trivial, but may be useful. They are recommended by PowerShell best practice.
///
- Information = 0,
+ Information = 0,
///
/// WARNING: This warning may cause a problem or does not follow PowerShell's recommended guidelines.
///
- Warning = 1,
+ Warning = 1,
///
/// ERROR: This warning is likely to cause a problem or does not follow PowerShell's required guidelines.
///
- Error = 2,
+ Error = 2,
///
/// ERROR: This diagnostic is caused by an actual parsing error, and is generated only by the engine.
///
- ParseError = 3
+ ParseError = 3
};
///
@@ -102,7 +102,7 @@ internal static ScriptFileMarker FromParseError(
{
Message = parseError.Message,
Level = ScriptFileMarkerLevel.Error,
- ScriptRegion = ScriptRegion.Create(parseError.Extent),
+ ScriptRegion = new(parseError.Extent),
Source = "PowerShell"
};
}
@@ -157,7 +157,7 @@ internal static ScriptFileMarker FromDiagnosticRecord(PSObject psObject)
Message = diagnosticRecord.Message as string ?? string.Empty,
RuleName = diagnosticRecord.RuleName as string ?? string.Empty,
Level = level,
- ScriptRegion = ScriptRegion.Create(diagnosticRecord.Extent as IScriptExtent),
+ ScriptRegion = new(diagnosticRecord.Extent as IScriptExtent),
Corrections = markerCorrections,
Source = "PSScriptAnalyzer"
};
diff --git a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs
index c4d407105..e818b6d63 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/ScriptRegion.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+#nullable enable
+
using System;
using System.Management.Automation.Language;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
@@ -12,64 +14,42 @@ namespace Microsoft.PowerShell.EditorServices.Services.TextDocument
///
public sealed class ScriptRegion : IScriptExtent
{
- #region Static Methods
+ internal TextEdit ToTextEdit() => new() { NewText = Text, Range = ToRange() };
- ///
- /// Creates a new instance of the ScriptRegion class from an
- /// instance of an IScriptExtent implementation.
- ///
- ///
- /// The IScriptExtent to copy into the ScriptRegion.
- ///
- ///
- /// A new ScriptRegion instance with the same details as the IScriptExtent.
- ///
- public static ScriptRegion Create(IScriptExtent scriptExtent)
+ internal Range ToRange()
{
- // IScriptExtent throws an ArgumentOutOfRange exception if Text is null
- string scriptExtentText;
- try
+ return new Range
{
- scriptExtentText = scriptExtent.Text;
- }
- catch (ArgumentOutOfRangeException)
- {
- scriptExtentText = string.Empty;
- }
+ Start = new Position
+ {
+ Line = StartLineNumber - 1,
+ Character = StartColumnNumber - 1
+ },
+ End = new Position
+ {
+ Line = EndLineNumber - 1,
+ Character = EndColumnNumber - 1
+ }
+ };
+ }
- return new ScriptRegion(
- scriptExtent.File,
- scriptExtentText,
- scriptExtent.StartLineNumber,
- scriptExtent.StartColumnNumber,
- scriptExtent.StartOffset,
- scriptExtent.EndLineNumber,
- scriptExtent.EndColumnNumber,
- scriptExtent.EndOffset);
+ // Same as PowerShell's EmptyScriptExtent
+ internal bool IsEmpty()
+ {
+ return StartLineNumber == 0 && StartColumnNumber == 0
+ && EndLineNumber == 0 && EndColumnNumber == 0
+ && string.IsNullOrEmpty(File)
+ && string.IsNullOrEmpty(Text);
}
- internal static TextEdit ToTextEdit(ScriptRegion scriptRegion)
+ // Do not use PowerShell's ContainsLineAndColumn, it's nonsense.
+ internal bool ContainsPosition(int line, int column)
{
- return new TextEdit
- {
- NewText = scriptRegion.Text,
- Range = new Range
- {
- Start = new Position
- {
- Line = scriptRegion.StartLineNumber - 1,
- Character = scriptRegion.StartColumnNumber - 1,
- },
- End = new Position
- {
- Line = scriptRegion.EndLineNumber - 1,
- Character = scriptRegion.EndColumnNumber - 1,
- }
- }
- };
+ return StartLineNumber <= line && line <= EndLineNumber
+ && StartColumnNumber <= column && column <= EndColumnNumber;
}
- #endregion
+ public override string ToString() => $"Start {StartLineNumber}:{StartColumnNumber}, End {EndLineNumber}:{EndColumnNumber}";
#region Constructors
@@ -93,6 +73,33 @@ public ScriptRegion(
EndOffset = endOffset;
}
+ public ScriptRegion(IScriptExtent scriptExtent)
+ {
+ File = scriptExtent.File;
+
+ // IScriptExtent throws an ArgumentOutOfRange exception if Text is null
+ try
+ {
+ Text = scriptExtent.Text;
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ Text = string.Empty;
+ }
+
+ StartLineNumber = scriptExtent.StartLineNumber;
+ StartColumnNumber = scriptExtent.StartColumnNumber;
+ StartOffset = scriptExtent.StartOffset;
+ EndLineNumber = scriptExtent.EndLineNumber;
+ EndColumnNumber = scriptExtent.EndColumnNumber;
+ EndOffset = scriptExtent.EndOffset;
+ }
+
+ ///
+ /// NOTE: While unused, we kept this as it was previously exposed on a public class.
+ ///
+ public static ScriptRegion Create(IScriptExtent scriptExtent) => new(scriptExtent);
+
#endregion
#region Properties
diff --git a/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs b/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs
index 67853388a..d1a3ca5e5 100644
--- a/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs
+++ b/src/PowerShellEditorServices/Services/TextDocument/TokenOperations.cs
@@ -25,8 +25,9 @@ internal static class TokenOperations
///
/// Extracts all of the unique foldable regions in a script given the list tokens
///
- internal static FoldingReferenceList FoldableReferences(
- Token[] tokens)
+#pragma warning disable CA1502 // Cyclomatic complexity we don't care about
+ internal static FoldingReferenceList FoldableReferences(Token[] tokens)
+#pragma warning restore CA1502
{
FoldingReferenceList refList = new();
diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs
index 8a9aaa815..7f9a43049 100644
--- a/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs
+++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/WorkspaceSymbolsHandler.cs
@@ -34,30 +34,40 @@ public PsesWorkspaceSymbolsHandler(ILoggerFactory loggerFactory, SymbolsService
public override async Task> Handle(WorkspaceSymbolParams request, CancellationToken cancellationToken)
{
+ await _symbolsService.ScanWorkspacePSFiles(cancellationToken).ConfigureAwait(false);
List symbols = new();
foreach (ScriptFile scriptFile in _workspaceService.GetOpenedFiles())
{
- List foundSymbols =
- _symbolsService.FindSymbolsInFile(
- scriptFile);
+ IEnumerable foundSymbols = _symbolsService.FindSymbolsInFile(scriptFile);
// TODO: Need to compute a relative path that is based on common path for all workspace files
string containerName = Path.GetFileNameWithoutExtension(scriptFile.FilePath);
- foreach (SymbolReference foundOccurrence in foundSymbols)
+ foreach (SymbolReference symbol in foundSymbols)
{
// This async method is pretty dense with synchronous code
// so it's helpful to add some yields.
await Task.Yield();
cancellationToken.ThrowIfCancellationRequested();
- if (!IsQueryMatch(request.Query, foundOccurrence.SymbolName))
+
+ if (!symbol.IsDeclaration)
+ {
+ continue;
+ }
+
+ if (symbol.Type is SymbolType.Parameter)
+ {
+ continue;
+ }
+
+ if (!IsQueryMatch(request.Query, symbol.Name))
{
continue;
}
// Exclude Pester setup/teardown symbols as they're unnamed
- if (foundOccurrence is PesterSymbolReference pesterSymbol &&
+ if (symbol is PesterSymbolReference pesterSymbol &&
!PesterSymbolReference.IsPesterTestCommand(pesterSymbol.Command))
{
continue;
@@ -65,16 +75,17 @@ public override async Task> Handle(WorkspaceSymbolP
Location location = new()
{
- Uri = DocumentUri.From(foundOccurrence.FilePath),
- Range = GetRangeFromScriptRegion(foundOccurrence.ScriptRegion)
+ Uri = DocumentUri.From(symbol.FilePath),
+ Range = symbol.NameRegion.ToRange()
};
+ // TODO: This should be a WorkplaceSymbol now as SymbolInformation is deprecated.
symbols.Add(new SymbolInformation
{
ContainerName = containerName,
- Kind = foundOccurrence.SymbolType == SymbolType.Variable ? SymbolKind.Variable : SymbolKind.Function,
+ Kind = SymbolTypeUtils.GetSymbolKind(symbol.Type),
Location = location,
- Name = GetDecoratedSymbolName(foundOccurrence)
+ Name = symbol.Name
});
}
}
@@ -86,37 +97,6 @@ public override async Task> Handle(WorkspaceSymbolP
private static bool IsQueryMatch(string query, string symbolName) => symbolName.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
- private static Range GetRangeFromScriptRegion(ScriptRegion scriptRegion)
- {
- return new Range
- {
- Start = new Position
- {
- Line = scriptRegion.StartLineNumber - 1,
- Character = scriptRegion.StartColumnNumber - 1
- },
- End = new Position
- {
- Line = scriptRegion.EndLineNumber - 1,
- Character = scriptRegion.EndColumnNumber - 1
- }
- };
- }
-
- private static string GetDecoratedSymbolName(SymbolReference symbolReference)
- {
- string name = symbolReference.SymbolName;
-
- if (symbolReference.SymbolType is SymbolType.Configuration or
- SymbolType.Function or
- SymbolType.Workflow)
- {
- name += " { }";
- }
-
- return name;
- }
-
#endregion
}
}
diff --git a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs
index 0d9a614e4..002a757ad 100644
--- a/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs
+++ b/src/PowerShellEditorServices/Services/Workspace/WorkspaceService.cs
@@ -5,7 +5,6 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Security;
using System.Text;
using Microsoft.Extensions.FileSystemGlobbing;
@@ -275,10 +274,9 @@ public ScriptFile GetFileBuffer(DocumentUri documentUri, string initialBuffer)
}
///
- /// Gets an array of all opened ScriptFiles in the workspace.
+ /// Gets an IEnumerable of all opened ScriptFiles in the workspace.
///
- /// An array of all opened ScriptFiles in the workspace.
- public ScriptFile[] GetOpenedFiles() => workspaceFiles.Values.ToArray();
+ public IEnumerable GetOpenedFiles() => workspaceFiles.Values;
///
/// Closes a currently open script file with the given file path.
@@ -292,35 +290,6 @@ public void CloseFile(ScriptFile scriptFile)
workspaceFiles.TryRemove(keyName, out ScriptFile _);
}
- ///
- /// Gets all file references by recursively searching
- /// through referenced files in a scriptfile
- ///
- /// Contains the details and contents of an open script file
- /// A scriptfile array where the first file
- /// in the array is the "root file" of the search
- public ScriptFile[] ExpandScriptReferences(ScriptFile scriptFile)
- {
- Dictionary referencedScriptFiles = new();
- List expandedReferences = new();
-
- // add original file so it's not searched for, then find all file references
- referencedScriptFiles.Add(scriptFile.Id, scriptFile);
- RecursivelyFindReferences(scriptFile, referencedScriptFiles);
-
- // remove original file from referenced file and add it as the first element of the
- // expanded referenced list to maintain order so the original file is always first in the list
- referencedScriptFiles.Remove(scriptFile.Id);
- expandedReferences.Add(scriptFile);
-
- if (referencedScriptFiles.Count > 0)
- {
- expandedReferences.AddRange(referencedScriptFiles.Values);
- }
-
- return expandedReferences.ToArray();
- }
-
///
/// Gets the workspace-relative path of the given file path.
///
@@ -372,7 +341,7 @@ public IEnumerable EnumeratePSFiles(
bool ignoreReparsePoints
)
{
- if (WorkspacePath == null || !Directory.Exists(WorkspacePath))
+ if (WorkspacePath is null || !Directory.Exists(WorkspacePath))
{
yield break;
}
@@ -401,54 +370,6 @@ bool ignoreReparsePoints
#endregion
#region Private Methods
- ///
- /// Recursively searches through referencedFiles in scriptFiles
- /// and builds a Dictionary of the file references
- ///
- /// Details an contents of "root" script file
- /// A Dictionary of referenced script files
- private void RecursivelyFindReferences(
- ScriptFile scriptFile,
- Dictionary referencedScriptFiles)
- {
- // Get the base path of the current script for use in resolving relative paths
- string baseFilePath = scriptFile.IsInMemory
- ? WorkspacePath
- : Path.GetDirectoryName(scriptFile.FilePath);
-
- foreach (string referencedFileName in scriptFile.ReferencedFiles)
- {
- string resolvedScriptPath =
- ResolveRelativeScriptPath(
- baseFilePath,
- referencedFileName);
-
- // If there was an error resolving the string, skip this reference
- if (resolvedScriptPath == null)
- {
- continue;
- }
-
- logger.LogDebug(
- string.Format(
- "Resolved relative path '{0}' to '{1}'",
- referencedFileName,
- resolvedScriptPath));
-
- // Get the referenced file if it's not already in referencedScriptFiles
- if (TryGetFile(resolvedScriptPath, out ScriptFile referencedFile))
- {
- // Normalize the resolved script path and add it to the
- // referenced files list if it isn't there already
- resolvedScriptPath = resolvedScriptPath.ToLower();
- if (!referencedScriptFiles.ContainsKey(resolvedScriptPath))
- {
- referencedScriptFiles.Add(resolvedScriptPath, referencedFile);
- RecursivelyFindReferences(referencedFile, referencedScriptFiles);
- }
- }
- }
- }
internal static StreamReader OpenStreamReader(DocumentUri uri)
{
diff --git a/src/PowerShellEditorServices/Utility/Extensions.cs b/src/PowerShellEditorServices/Utility/Extensions.cs
index c280f1b14..88a9fa4f3 100644
--- a/src/PowerShellEditorServices/Utility/Extensions.cs
+++ b/src/PowerShellEditorServices/Utility/Extensions.cs
@@ -2,9 +2,6 @@
// Licensed under the MIT License.
using System;
-using System.Linq;
-using System.Collections.Generic;
-using System.Management.Automation.Language;
using System.Text;
namespace Microsoft.PowerShell.EditorServices.Utility
@@ -33,119 +30,6 @@ public static string SafeToString(this object obj)
return str;
}
- ///
- /// Get the maximum of the elements from the given enumerable.
- ///
- /// Type of object for which the enumerable is defined.
- /// An enumerable object of type T
- /// A comparer for ordering elements of type T. The comparer should handle null values.
- /// An object of type T. If the enumerable is empty or has all null elements, then the method returns null.
- public static T MaxElement(this IEnumerable elements, Func comparer) where T : class
- {
- if (elements == null)
- {
- throw new ArgumentNullException(nameof(elements));
- }
-
- if (comparer == null)
- {
- throw new ArgumentNullException(nameof(comparer));
- }
-
- if (!elements.Any())
- {
- return null;
- }
-
- T maxElement = elements.First();
- foreach (T element in elements.Skip(1))
- {
- if (element != null && comparer(element, maxElement) > 0)
- {
- maxElement = element;
- }
- }
-
- return maxElement;
- }
-
- ///
- /// Get the minimum of the elements from the given enumerable.
- ///
- /// Type of object for which the enumerable is defined.
- /// An enumerable object of type T
- /// A comparer for ordering elements of type T. The comparer should handle null values.
- /// An object of type T. If the enumerable is empty or has all null elements, then the method returns null.
- public static T MinElement(this IEnumerable elements, Func comparer) where T : class => MaxElement(elements, (elementX, elementY) => -1 * comparer(elementX, elementY));
-
- ///
- /// Compare extents with respect to their widths.
- ///
- /// Width of an extent is defined as the difference between its EndOffset and StartOffest properties.
- ///
- /// Extent of type IScriptExtent.
- /// Extent of type IScriptExtent.
- /// 0 if extentX and extentY are equal in width. 1 if width of extent X is greater than that of extent Y. Otherwise, -1.
- public static int ExtentWidthComparer(this IScriptExtent extentX, IScriptExtent extentY)
- {
- if (extentX == null && extentY == null)
- {
- return 0;
- }
-
- if (extentX != null && extentY == null)
- {
- return 1;
- }
-
- if (extentX == null)
- {
- return -1;
- }
-
- int extentWidthX = extentX.EndOffset - extentX.StartOffset;
- int extentWidthY = extentY.EndOffset - extentY.StartOffset;
- if (extentWidthX > extentWidthY)
- {
- return 1;
- }
- else if (extentWidthX < extentWidthY)
- {
- return -1;
- }
- else
- {
- return 0;
- }
- }
-
- ///
- /// Check if the given coordinates are wholly contained in the instance's extent.
- ///
- /// Extent of type IScriptExtent.
- /// 1-based line number.
- /// 1-based column number
- /// True if the coordinates are wholly contained in the instance's extent, otherwise, false.
- public static bool Contains(this IScriptExtent scriptExtent, int line, int column)
- {
- if (scriptExtent.StartLineNumber > line || scriptExtent.EndLineNumber < line)
- {
- return false;
- }
-
- if (scriptExtent.StartLineNumber == line)
- {
- return scriptExtent.StartColumnNumber <= column;
- }
-
- if (scriptExtent.EndLineNumber == line)
- {
- return scriptExtent.EndColumnNumber >= column;
- }
-
- return true;
- }
-
///
/// Same as but never CRLF. Use this when building
/// formatting for clients that may not render CRLF correctly.
diff --git a/src/PowerShellEditorServices/Utility/VisitorUtils.cs b/src/PowerShellEditorServices/Utility/VisitorUtils.cs
index 861d04acc..fbfdd1413 100644
--- a/src/PowerShellEditorServices/Utility/VisitorUtils.cs
+++ b/src/PowerShellEditorServices/Utility/VisitorUtils.cs
@@ -1,7 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Management.Automation;
using System.Management.Automation.Language;
+using System.Text;
+using PSESSymbols = Microsoft.PowerShell.EditorServices.Services.Symbols;
namespace Microsoft.PowerShell.EditorServices.Utility
{
@@ -10,42 +17,347 @@ namespace Microsoft.PowerShell.EditorServices.Utility
///
internal static class VisitorUtils
{
+ internal static string? GetCommandName(CommandAst commandAst)
+ {
+ string commandName = commandAst.GetCommandName();
+ if (!string.IsNullOrEmpty(commandName))
+ {
+ return commandName;
+ }
+
+ if (commandAst.CommandElements[0] is not ExpandableStringExpressionAst expandableStringExpressionAst)
+ {
+ return null;
+ }
+
+ return PSESSymbols.AstOperations.TryGetInferredValue(expandableStringExpressionAst, out string value) ? value : null;
+ }
+
+ // Strip the qualification, if there is any, so $var is a reference of $script:var etc.
+ internal static string GetUnqualifiedVariableName(VariablePath variablePath)
+ {
+ return variablePath.IsUnqualified
+ ? variablePath.UserPath
+ : variablePath.UserPath.Substring(variablePath.UserPath.IndexOf(':') + 1);
+ }
+
///
- /// Calculates the start line and column of the actual function name in a function definition AST.
+ /// Calculates the start line and column of the actual symbol name in a AST.
///
- /// A FunctionDefinitionAst object in the script's AST
- /// A tuple with start column and line for the function name
- internal static (int startColumn, int startLine) GetNameStartColumnAndLineNumbersFromAst(FunctionDefinitionAst ast)
+ /// An Ast object in the script's AST
+ /// An int specifying start index of name in the AST's extent text
+ /// A tuple with start column and line of the symbol name
+ private static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(Ast ast, int nameStartIndex)
{
int startColumnNumber = ast.Extent.StartColumnNumber;
int startLineNumber = ast.Extent.StartLineNumber;
- int astOffset = ast.IsFilter ? "filter".Length : ast.IsWorkflow ? "workflow".Length : "function".Length;
string astText = ast.Extent.Text;
- // The line offset represents the offset on the line that we're on where as
// astOffset is the offset on the entire text of the AST.
- int lineOffset = astOffset;
- for (; astOffset < astText.Length; astOffset++, lineOffset++)
+ for (int astOffset = 0; astOffset <= ast.Extent.Text.Length; astOffset++, startColumnNumber++)
{
if (astText[astOffset] == '\n')
{
// reset numbers since we are operating on a different line and increment the line number.
startColumnNumber = 0;
startLineNumber++;
- lineOffset = 0;
}
else if (astText[astOffset] == '\r')
{
// Do nothing with carriage returns... we only look for line feeds since those
// are used on every platform.
}
- else if (!char.IsWhiteSpace(astText[astOffset]))
+ else if (astOffset >= nameStartIndex && !char.IsWhiteSpace(astText[astOffset]))
{
// This is the start of the function name so we've found our start column and line number.
break;
}
}
- return (startColumnNumber + lineOffset, startLineNumber);
+ return (startColumnNumber, startLineNumber);
+ }
+
+ ///
+ /// Calculates the start line and column of the actual function name in a function definition AST.
+ ///
+ /// A FunctionDefinitionAst object in the script's AST
+ /// A tuple with start column and line for the function name
+ internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(FunctionDefinitionAst functionDefinitionAst)
+ {
+ int startOffset = functionDefinitionAst.IsFilter ? "filter".Length : functionDefinitionAst.IsWorkflow ? "workflow".Length : "function".Length;
+ return GetNameStartColumnAndLineFromAst(functionDefinitionAst, startOffset);
+ }
+
+ ///
+ /// Calculates the start line and column of the actual class/enum name in a type definition AST.
+ ///
+ /// A TypeDefinitionAst object in the script's AST
+ /// A tuple with start column and line for the type name
+ internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(TypeDefinitionAst typeDefinitionAst)
+ {
+ int startOffset = typeDefinitionAst.IsEnum ? "enum".Length : "class".Length;
+ return GetNameStartColumnAndLineFromAst(typeDefinitionAst, startOffset);
+ }
+
+ ///
+ /// Calculates the start line and column of the actual method/constructor name in a function member AST.
+ ///
+ /// A FunctionMemberAst object in the script's AST
+ /// A tuple with start column and line for the method/constructor name
+ internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(FunctionMemberAst functionMemberAst)
+ {
+ // find name index to get offset even with attributes, static, hidden ++
+ int nameStartIndex = functionMemberAst.Extent.Text.IndexOf(
+ functionMemberAst.Name + '(', StringComparison.OrdinalIgnoreCase);
+ return GetNameStartColumnAndLineFromAst(functionMemberAst, nameStartIndex);
+ }
+
+ ///
+ /// Calculates the start line and column of the actual property name in a property member AST.
+ ///
+ /// A PropertyMemberAst object in the script's AST
+ /// A bool indicating this is a enum member
+ /// A tuple with start column and line for the property name
+ internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(PropertyMemberAst propertyMemberAst, bool isEnumMember)
+ {
+ // find name index to get offset even with attributes, static, hidden ++
+ string searchString = isEnumMember
+ ? propertyMemberAst.Name : '$' + propertyMemberAst.Name;
+ int nameStartIndex = propertyMemberAst.Extent.Text.IndexOf(
+ searchString, StringComparison.OrdinalIgnoreCase);
+ return GetNameStartColumnAndLineFromAst(propertyMemberAst, nameStartIndex);
+ }
+
+ ///
+ /// Calculates the start line and column of the actual configuration name in a configuration definition AST.
+ ///
+ /// A ConfigurationDefinitionAst object in the script's AST
+ /// A tuple with start column and line for the configuration name
+ internal static (int startColumn, int startLine) GetNameStartColumnAndLineFromAst(ConfigurationDefinitionAst configurationDefinitionAst)
+ {
+ const int startOffset = 13; // "configuration".Length
+ return GetNameStartColumnAndLineFromAst(configurationDefinitionAst, startOffset);
+ }
+
+ ///
+ /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable)
+ ///
+ /// A FunctionDefinitionAst in the script's AST
+ /// A ScriptExtent with for the symbol name only
+ internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionDefinitionAst functionDefinitionAst)
+ {
+ (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(functionDefinitionAst);
+
+ return new PSESSymbols.ScriptExtent()
+ {
+ Text = functionDefinitionAst.Name,
+ StartLineNumber = startLine,
+ EndLineNumber = startLine,
+ StartColumnNumber = startColumn,
+ EndColumnNumber = startColumn + functionDefinitionAst.Name.Length,
+ File = functionDefinitionAst.Extent.File
+ };
+ }
+
+ ///
+ /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable)
+ ///
+ /// A TypeDefinitionAst in the script's AST
+ /// A ScriptExtent with for the symbol name only
+ internal static PSESSymbols.ScriptExtent GetNameExtent(TypeDefinitionAst typeDefinitionAst)
+ {
+ (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(typeDefinitionAst);
+
+ return new PSESSymbols.ScriptExtent()
+ {
+ Text = typeDefinitionAst.Name,
+ StartLineNumber = startLine,
+ EndLineNumber = startLine,
+ StartColumnNumber = startColumn,
+ EndColumnNumber = startColumn + typeDefinitionAst.Name.Length,
+ File = typeDefinitionAst.Extent.File
+ };
+ }
+
+ ///
+ /// Gets a new ScriptExtent for a given Ast for the symbol name only (variable)
+ ///
+ /// A FunctionMemberAst in the script's AST
+ /// A ScriptExtent with for the symbol name only
+ internal static PSESSymbols.ScriptExtent GetNameExtent(FunctionMemberAst functionMemberAst)
+ {
+ (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(functionMemberAst);
+
+ return new PSESSymbols.ScriptExtent()
+ {
+ Text = GetMemberOverloadName(functionMemberAst),
+ StartLineNumber = startLine,
+ EndLineNumber = startLine,
+ StartColumnNumber = startColumn,
+ EndColumnNumber = startColumn + functionMemberAst.Name.Length,
+ File = functionMemberAst.Extent.File
+ };
+ }
+
+ ///
+ /// Gets a new ScriptExtent for a given Ast for the property name only
+ ///
+ /// A PropertyMemberAst in the script's AST
+ /// A ScriptExtent with for the symbol name only
+ internal static PSESSymbols.ScriptExtent GetNameExtent(PropertyMemberAst propertyMemberAst)
+ {
+ bool isEnumMember = propertyMemberAst.Parent is TypeDefinitionAst typeDef && typeDef.IsEnum;
+ (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(propertyMemberAst, isEnumMember);
+
+ // +1 when class property to as start includes $
+ int endColumnNumber = isEnumMember ?
+ startColumn + propertyMemberAst.Name.Length :
+ startColumn + propertyMemberAst.Name.Length + 1;
+
+ return new PSESSymbols.ScriptExtent()
+ {
+ Text = GetMemberOverloadName(propertyMemberAst),
+ StartLineNumber = startLine,
+ EndLineNumber = startLine,
+ StartColumnNumber = startColumn,
+ EndColumnNumber = endColumnNumber,
+ File = propertyMemberAst.Extent.File
+ };
+ }
+
+ ///
+ /// Gets a new ScriptExtent for a given Ast for the configuration instance name only
+ ///
+ /// A ConfigurationDefinitionAst in the script's AST
+ /// A ScriptExtent with for the symbol name only
+ internal static PSESSymbols.ScriptExtent GetNameExtent(ConfigurationDefinitionAst configurationDefinitionAst)
+ {
+ string configurationName = configurationDefinitionAst.InstanceName.Extent.Text;
+ (int startColumn, int startLine) = GetNameStartColumnAndLineFromAst(configurationDefinitionAst);
+
+ return new PSESSymbols.ScriptExtent()
+ {
+ Text = configurationName,
+ StartLineNumber = startLine,
+ EndLineNumber = startLine,
+ StartColumnNumber = startColumn,
+ EndColumnNumber = startColumn + configurationName.Length,
+ File = configurationDefinitionAst.Extent.File
+ };
+ }
+
+ ///
+ /// Gets the function name with parameters and return type.
+ ///
+ internal static string GetFunctionDisplayName(FunctionDefinitionAst functionDefinitionAst)
+ {
+ StringBuilder sb = new();
+ if (functionDefinitionAst.IsWorkflow)
+ {
+ sb.Append("workflow");
+ }
+ else if (functionDefinitionAst.IsFilter)
+ {
+ sb.Append("filter");
+ }
+ else
+ {
+ sb.Append("function");
+ }
+ sb.Append(' ').Append(functionDefinitionAst.Name).Append(" (");
+ // Add parameters
+ // TODO: Fix the parameters, this doesn't work for those specified in the body.
+ if (functionDefinitionAst.Parameters?.Count > 0)
+ {
+ List parameters = new(functionDefinitionAst.Parameters.Count);
+ foreach (ParameterAst param in functionDefinitionAst.Parameters)
+ {
+ parameters.Add(param.Extent.Text);
+ }
+
+ sb.Append(string.Join(", ", parameters));
+ }
+ sb.Append(')');
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Gets the display name of a parameter with its default value.
+ ///
+ internal static string GetParamDisplayName(ParameterAst parameterAst)
+ {
+ StringBuilder sb = new();
+
+ sb.Append("(parameter) ");
+ if (parameterAst.StaticType is not null)
+ {
+ sb.Append('[').Append(parameterAst.StaticType).Append(']');
+ }
+ sb.Append('$').Append(parameterAst.Name.VariablePath.UserPath);
+ string? constantValue = parameterAst.DefaultValue is ConstantExpressionAst constant
+ ? constant.Value.ToString() : null;
+
+ if (!string.IsNullOrEmpty(constantValue))
+ {
+ sb.Append(" = ").Append(constantValue);
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Gets the method or constructor name with parameters for current overload.
+ ///
+ /// A FunctionMemberAst object in the script's AST
+ /// Function member name with return type (optional) and parameters
+ internal static string GetMemberOverloadName(FunctionMemberAst functionMemberAst)
+ {
+ StringBuilder sb = new();
+
+ // Prepend return type and class. Used for symbol details (hover)
+ if (!functionMemberAst.IsConstructor)
+ {
+ sb.Append(functionMemberAst.ReturnType?.TypeName.Name ?? "void").Append(' ');
+ }
+
+ sb.Append(functionMemberAst.Name);
+
+ // Add parameters
+ sb.Append('(');
+ if (functionMemberAst.Parameters.Count > 0)
+ {
+ List parameters = new(functionMemberAst.Parameters.Count);
+ foreach (ParameterAst param in functionMemberAst.Parameters)
+ {
+ parameters.Add(param.Extent.Text);
+ }
+
+ sb.Append(string.Join(", ", parameters));
+ }
+ sb.Append(')');
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Gets the property name with type and class/enum.
+ ///
+ /// A PropertyMemberAst object in the script's AST
+ /// Property name with type (optional) and class/enum
+ internal static string GetMemberOverloadName(PropertyMemberAst propertyMemberAst)
+ {
+ StringBuilder sb = new();
+
+ // Prepend return type and class. Used for symbol details (hover)
+ if (propertyMemberAst.Parent is TypeDefinitionAst typeAst && !typeAst.IsEnum)
+ {
+ sb.Append('[')
+ .Append(propertyMemberAst.PropertyType?.TypeName.Name ?? "object")
+ .Append("] $");
+ }
+
+ sb.Append(propertyMemberAst.Name);
+ return sb.ToString();
}
}
}
diff --git a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs
index 0ca4bb2e3..d990ebe09 100644
--- a/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs
+++ b/test/PowerShellEditorServices.Test.E2E/LanguageServerProtocolMessageTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
@@ -442,7 +442,7 @@ await PsesLanguageClient
Range range = symInfoOrDocSym.SymbolInformation.Location.Range;
Assert.Equal(1, range.Start.Line);
- Assert.Equal(0, range.Start.Character);
+ Assert.Equal(9, range.Start.Character);
Assert.Equal(3, range.End.Line);
Assert.Equal(1, range.End.Character);
});
@@ -841,7 +841,7 @@ public async Task NoMessageIfPesterCodeLensDisabled()
}
[Fact]
- public async Task CanSendReferencesCodeLensRequestAsync()
+ public async Task CanSendFunctionReferencesCodeLensRequestAsync()
{
string filePath = NewTestFile(@"
function CanSendReferencesCodeLensRequest {
@@ -867,7 +867,7 @@ function CanSendReferencesCodeLensRequest {
Range range = codeLens.Range;
Assert.Equal(1, range.Start.Line);
- Assert.Equal(0, range.Start.Character);
+ Assert.Equal(9, range.Start.Character);
Assert.Equal(3, range.End.Line);
Assert.Equal(1, range.End.Character);
@@ -878,6 +878,110 @@ function CanSendReferencesCodeLensRequest {
Assert.Equal("1 reference", codeLensResolveResult.Command.Title);
}
+ [Fact]
+ public async Task CanSendClassReferencesCodeLensRequestAsync()
+ {
+ string filePath = NewTestFile(@"
+param(
+ [MyBaseClass]$enumValue
+)
+
+class MyBaseClass {
+
+}
+
+class ChildClass : MyBaseClass, System.IDisposable {
+
+}
+
+$o = [MyBaseClass]::new()
+$o -is [MyBaseClass]
+");
+
+ CodeLensContainer codeLenses = await PsesLanguageClient
+ .SendRequest(
+ "textDocument/codeLens",
+ new CodeLensParams
+ {
+ TextDocument = new TextDocumentIdentifier
+ {
+ Uri = new Uri(filePath)
+ }
+ })
+ .Returning(CancellationToken.None).ConfigureAwait(true);
+
+ Assert.Collection(codeLenses,
+ codeLens =>
+ {
+ Range range = codeLens.Range;
+ Assert.Equal(5, range.Start.Line);
+ Assert.Equal(6, range.Start.Character);
+ Assert.Equal(7, range.End.Line);
+ Assert.Equal(1, range.End.Character);
+ },
+ codeLens =>
+ {
+ Range range = codeLens.Range;
+ Assert.Equal(9, range.Start.Line);
+ Assert.Equal(6, range.Start.Character);
+ Assert.Equal(11, range.End.Line);
+ Assert.Equal(1, range.End.Character);
+ }
+ );
+
+ CodeLens baseClassCodeLens = codeLenses.First();
+ CodeLens codeLensResolveResult = await PsesLanguageClient
+ .SendRequest("codeLens/resolve", baseClassCodeLens)
+ .Returning(CancellationToken.None).ConfigureAwait(true);
+
+ Assert.Equal("4 references", codeLensResolveResult.Command.Title);
+ }
+
+ [Fact]
+ public async Task CanSendEnumReferencesCodeLensRequestAsync()
+ {
+ string filePath = NewTestFile(@"
+param(
+ [MyEnum]$enumValue
+)
+
+enum MyEnum {
+ First = 1
+ Second
+ Third
+}
+
+[MyEnum]::First
+'First' -is [MyEnum]
+");
+
+ CodeLensContainer codeLenses = await PsesLanguageClient
+ .SendRequest(
+ "textDocument/codeLens",
+ new CodeLensParams
+ {
+ TextDocument = new TextDocumentIdentifier
+ {
+ Uri = new Uri(filePath)
+ }
+ })
+ .Returning(CancellationToken.None).ConfigureAwait(true);
+
+ CodeLens codeLens = Assert.Single(codeLenses);
+
+ Range range = codeLens.Range;
+ Assert.Equal(5, range.Start.Line);
+ Assert.Equal(5, range.Start.Character);
+ Assert.Equal(9, range.End.Line);
+ Assert.Equal(1, range.End.Character);
+
+ CodeLens codeLensResolveResult = await PsesLanguageClient
+ .SendRequest("codeLens/resolve", codeLens)
+ .Returning(CancellationToken.None).ConfigureAwait(true);
+
+ Assert.Equal("3 references", codeLensResolveResult.Command.Title);
+ }
+
[SkippableFact]
public async Task CanSendCodeActionRequestAsync()
{
diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs b/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs
index b7456faeb..842dec9b8 100644
--- a/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs
+++ b/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInDotSourceReference.cs
@@ -5,7 +5,7 @@
namespace Microsoft.PowerShell.EditorServices.Test.Shared.Definition
{
- public static class FindsFunctionDefinitionInDotSourceReferenceData
+ public static class FindsFunctionDefinitionInWorkspaceData
{
public static readonly ScriptRegion SourceDetails = new(
file: TestUtilities.NormalizePath("References/FileWithReferences.ps1"),
diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInWorkspace.cs b/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInWorkspace.cs
deleted file mode 100644
index a88c143bf..000000000
--- a/test/PowerShellEditorServices.Test.Shared/Definition/FindsFunctionDefinitionInWorkspace.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using Microsoft.PowerShell.EditorServices.Services.TextDocument;
-
-namespace Microsoft.PowerShell.EditorServices.Test.Shared.Definition
-{
- public static class FindsFunctionDefinitionInWorkspaceData
- {
- public static readonly ScriptRegion SourceDetails = new(
- file: TestUtilities.NormalizePath("References/ReferenceFileD.ps1"),
- text: string.Empty,
- startLineNumber: 1,
- startColumnNumber: 2,
- startOffset: 0,
- endLineNumber: 0,
- endColumnNumber: 0,
- endOffset: 0);
- }
-}
diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypeSymbolsDefinition.cs b/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypeSymbolsDefinition.cs
new file mode 100644
index 000000000..a0b5ae61c
--- /dev/null
+++ b/test/PowerShellEditorServices.Test.Shared/Definition/FindsTypeSymbolsDefinition.cs
@@ -0,0 +1,90 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.PowerShell.EditorServices.Services.TextDocument;
+
+namespace Microsoft.PowerShell.EditorServices.Test.Shared.Definition
+{
+ public static class FindsTypeSymbolsDefinitionData
+ {
+ public static readonly ScriptRegion ClassSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 8,
+ startColumnNumber: 14,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion EnumSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 39,
+ startColumnNumber: 10,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion TypeExpressionSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 45,
+ startColumnNumber: 5,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion TypeConstraintSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 25,
+ startColumnNumber: 24,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion ConstructorSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 9,
+ startColumnNumber: 14,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion MethodSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 19,
+ startColumnNumber: 25,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion PropertySourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 15,
+ startColumnNumber: 32,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion EnumMemberSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 41,
+ startColumnNumber: 11,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+ }
+}
diff --git a/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs
new file mode 100644
index 000000000..675dbe455
--- /dev/null
+++ b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnTypeSymbols.cs
@@ -0,0 +1,90 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.PowerShell.EditorServices.Services.TextDocument;
+
+namespace Microsoft.PowerShell.EditorServices.Test.Shared.Occurrences
+{
+ public static class FindsOccurrencesOnTypeSymbolsData
+ {
+ public static readonly ScriptRegion ClassSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 8,
+ startColumnNumber: 16,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion EnumSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 39,
+ startColumnNumber: 7,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion TypeExpressionSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 34,
+ startColumnNumber: 16,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion TypeConstraintSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 8,
+ startColumnNumber: 24,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion ConstructorSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 13,
+ startColumnNumber: 14,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion MethodSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 28,
+ startColumnNumber: 22,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion PropertySourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 15,
+ startColumnNumber: 18,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion EnumMemberSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 45,
+ startColumnNumber: 16,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+ }
+}
diff --git a/test/PowerShellEditorServices.Test.Shared/Definition/FindsDotSourcedFile.cs b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnVariable.cs
similarity index 57%
rename from test/PowerShellEditorServices.Test.Shared/Definition/FindsDotSourcedFile.cs
rename to test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnVariable.cs
index e69976d2c..c01db0591 100644
--- a/test/PowerShellEditorServices.Test.Shared/Definition/FindsDotSourcedFile.cs
+++ b/test/PowerShellEditorServices.Test.Shared/Occurrences/FindsOccurrencesOnVariable.cs
@@ -1,16 +1,16 @@
-// Copyright (c) Microsoft Corporation.
+// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
-namespace Microsoft.PowerShell.EditorServices.Test.Shared.Definition
+namespace Microsoft.PowerShell.EditorServices.Test.Shared.Occurrences
{
- public static class FindsDotSourcedFileData
+ public static class FindsOccurrencesOnVariableData
{
public static readonly ScriptRegion SourceDetails = new(
- file: TestUtilities.NormalizePath("References/DotSources.ps1"),
+ file: TestUtilities.NormalizePath("References/SimpleFile.ps1"),
text: string.Empty,
- startLineNumber: 1,
+ startLineNumber: 8,
startColumnNumber: 3,
startOffset: 0,
endLineNumber: 0,
diff --git a/test/PowerShellEditorServices.Test.Shared/References/DotSources.ps1 b/test/PowerShellEditorServices.Test.Shared/References/DotSources.ps1
deleted file mode 100644
index 685183ea6..000000000
--- a/test/PowerShellEditorServices.Test.Shared/References/DotSources.ps1
+++ /dev/null
@@ -1,15 +0,0 @@
-. ./ReferenceFileE.ps1
-. "$PSScriptRoot/ReferenceFileE.ps1"
-. "${PSScriptRoot}/ReferenceFileE.ps1"
-. './ReferenceFileE.ps1'
-. "./ReferenceFileE.ps1"
-. .\ReferenceFileE.ps1
-. '.\ReferenceFileE.ps1'
-. ".\ReferenceFileE.ps1"
-. ReferenceFileE.ps1
-. 'ReferenceFileE.ps1'
-. "ReferenceFileE.ps1"
-. ./dir/../ReferenceFileE.ps1
-. ./invalidfile.ps1
-. ""
-. $someVar
diff --git a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnFunctionMultiFileDotSource.cs b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnFunctionMultiFileDotSource.cs
deleted file mode 100644
index 6a231ae60..000000000
--- a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnFunctionMultiFileDotSource.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using Microsoft.PowerShell.EditorServices.Services.TextDocument;
-
-namespace Microsoft.PowerShell.EditorServices.Test.Shared.References
-{
- public static class FindsReferencesOnFunctionMultiFileDotSourceFileB
- {
- public static readonly ScriptRegion SourceDetails = new(
- file: TestUtilities.NormalizePath("References/ReferenceFileB.ps1"),
- text: string.Empty,
- startLineNumber: 5,
- startColumnNumber: 8,
- startOffset: 0,
- endLineNumber: 0,
- endColumnNumber: 0,
- endOffset: 0);
- }
-
- public static class FindsReferencesOnFunctionMultiFileDotSourceFileC
- {
- public static readonly ScriptRegion SourceDetails = new(
- file: TestUtilities.NormalizePath("References/ReferenceFileC.ps1"),
- text: string.Empty,
- startLineNumber: 4,
- startColumnNumber: 10,
- startOffset: 0,
- endLineNumber: 0,
- endColumnNumber: 0,
- endOffset: 0);
- }
-}
diff --git a/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs
new file mode 100644
index 000000000..ecd892610
--- /dev/null
+++ b/test/PowerShellEditorServices.Test.Shared/References/FindsReferencesOnTypeSymbols.cs
@@ -0,0 +1,90 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.PowerShell.EditorServices.Services.TextDocument;
+
+namespace Microsoft.PowerShell.EditorServices.Test.Shared.References
+{
+ public static class FindsReferencesOnTypeSymbolsData
+ {
+ public static readonly ScriptRegion ClassSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 8,
+ startColumnNumber: 12,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion EnumSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 39,
+ startColumnNumber: 8,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion ConstructorSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 9,
+ startColumnNumber: 8,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion MethodSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 36,
+ startColumnNumber: 16,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion PropertySourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 35,
+ startColumnNumber: 12,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion EnumMemberSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 45,
+ startColumnNumber: 16,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion TypeExpressionSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 34,
+ startColumnNumber: 12,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+
+ public static readonly ScriptRegion TypeConstraintSourceDetails = new(
+ file: TestUtilities.NormalizePath("References/TypeAndClassesFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 25,
+ startColumnNumber: 22,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+ }
+}
diff --git a/test/PowerShellEditorServices.Test.Shared/References/FunctionReference.ps1 b/test/PowerShellEditorServices.Test.Shared/References/FunctionReference.ps1
new file mode 100644
index 000000000..279f262b7
--- /dev/null
+++ b/test/PowerShellEditorServices.Test.Shared/References/FunctionReference.ps1
@@ -0,0 +1,24 @@
+function BasicFunction {}
+BasicFunction
+
+function FunctionWithExtraSpace
+{
+
+} FunctionWithExtraSpace
+
+function
+
+
+ FunctionNameOnDifferentLine
+
+
+
+
+
+
+ {}
+
+
+ FunctionNameOnDifferentLine
+
+ function IndentedFunction { } IndentedFunction
diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileA.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileA.ps1
deleted file mode 100644
index 31ef35600..000000000
--- a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileA.ps1
+++ /dev/null
@@ -1,9 +0,0 @@
-. .\ReferenceFileA.ps1
-. ./ReferenceFileB.ps1
-. .\ReferenceFileC.ps1
-
-function My-Function ($myInput)
-{
- My-Function $myInput
-}
-Get-ChildItem
diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileB.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileB.ps1
deleted file mode 100644
index 980ed33da..000000000
--- a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileB.ps1
+++ /dev/null
@@ -1,5 +0,0 @@
-. "$PSScriptRoot\ReferenceFileC.ps1"
-
-Get-ChildItem
-
-My-Function "testb"
diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileC.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileC.ps1
deleted file mode 100644
index 6e1ee3131..000000000
--- a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileC.ps1
+++ /dev/null
@@ -1,4 +0,0 @@
-. ./ReferenceFileA.ps1
-Get-ChildItem
-
-My-Function "testc"
diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileD.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileD.ps1
deleted file mode 100644
index 39aad7d37..000000000
--- a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileD.ps1
+++ /dev/null
@@ -1 +0,0 @@
-My-FunctionInFileE "this is my function"
\ No newline at end of file
diff --git a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileE.ps1 b/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileE.ps1
deleted file mode 100644
index 2a3e9b2a9..000000000
--- a/test/PowerShellEditorServices.Test.Shared/References/ReferenceFileE.ps1
+++ /dev/null
@@ -1,4 +0,0 @@
-function My-FunctionInFileE
-{
-
-}
\ No newline at end of file
diff --git a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1 b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1
index e7a271447..b60389c63 100644
--- a/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1
+++ b/test/PowerShellEditorServices.Test.Shared/References/SimpleFile.ps1
@@ -1,6 +1,6 @@
function My-Function ($myInput)
{
- My-Function $myInput
+ My-Function $myInput
}
$things = 4
diff --git a/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1 b/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1
new file mode 100644
index 000000000..ac2888f9f
--- /dev/null
+++ b/test/PowerShellEditorServices.Test.Shared/References/TypeAndClassesFile.ps1
@@ -0,0 +1,46 @@
+Get-ChildItem ./file1.ps1
+$myScriptVar = 123
+
+class BaseClass {
+
+}
+
+class SuperClass : BaseClass {
+ SuperClass([string]$name) {
+
+ }
+
+ SuperClass() { }
+
+ [string]$SomePropWithDefault = 'this is a default value'
+
+ [int]$SomeProp
+
+ [string]MyClassMethod([string]$param1, $param2, [int]$param3) {
+ $this.SomePropWithDefault = 'something happend'
+ return 'finished'
+ }
+
+ [string]
+ MyClassMethod([MyEnum]$param1) {
+ return 'hello world'
+ }
+ [string]MyClassMethod() {
+ return 'hello world'
+ }
+}
+
+New-Object SuperClass
+$o = [SuperClass]::new()
+$o.SomeProp
+$o.MyClassMethod()
+
+
+enum MyEnum {
+ First
+ Second
+ Third
+}
+
+[MyEnum]::First
+'First' -is [MyEnum]
diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/DSCFile.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/DSCFile.ps1
new file mode 100644
index 000000000..defec6863
--- /dev/null
+++ b/test/PowerShellEditorServices.Test.Shared/Symbols/DSCFile.ps1
@@ -0,0 +1,4 @@
+# This file represents a script with a DSC configuration
+configuration AConfiguration {
+ Node "TEST-PC" {}
+}
diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInDSCFile.cs b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInDSCFile.cs
new file mode 100644
index 000000000..6e3d45ff2
--- /dev/null
+++ b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInDSCFile.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.PowerShell.EditorServices.Services.TextDocument;
+
+namespace Microsoft.PowerShell.EditorServices.Test.Shared.Symbols
+{
+ public static class FindSymbolsInDSCFile
+ {
+ public static readonly ScriptRegion SourceDetails =
+ new(
+ file: TestUtilities.NormalizePath("Symbols/DSCFile.ps1"),
+ text: string.Empty,
+ startLineNumber: 0,
+ startColumnNumber: 0,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+ }
+}
diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInNewLineSymbolFile.cs b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInNewLineSymbolFile.cs
new file mode 100644
index 000000000..0be43f8d1
--- /dev/null
+++ b/test/PowerShellEditorServices.Test.Shared/Symbols/FindSymbolsInNewLineSymbolFile.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.PowerShell.EditorServices.Services.TextDocument;
+
+namespace Microsoft.PowerShell.EditorServices.Test.Shared.Symbols
+{
+ public static class FindSymbolsInNewLineSymbolFile
+ {
+ public static readonly ScriptRegion SourceDetails =
+ new(
+ file: TestUtilities.NormalizePath("Symbols/NewLineSymbols.ps1"),
+ text: string.Empty,
+ startLineNumber: 0,
+ startColumnNumber: 0,
+ startOffset: 0,
+ endLineNumber: 0,
+ endColumnNumber: 0,
+ endOffset: 0);
+ }
+}
diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1
index f234fed03..db53a6c1a 100644
--- a/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1
+++ b/test/PowerShellEditorServices.Test.Shared/Symbols/MultipleSymbols.ps1
@@ -22,10 +22,22 @@ function AnAdvancedFunction {
workflow AWorkflow {}
-Configuration AConfiguration {
- Node "TEST-PC" {}
+class AClass {
+ [string]$AProperty
+
+ AClass([string]$AParameter) {
+
+ }
+
+ [void]AMethod([string]$param1, [int]$param2, $param3) {
+
+ }
+}
+
+enum AEnum {
+ AValue = 0
}
AFunction
1..3 | AFilter
-AnAdvancedFunction
\ No newline at end of file
+AnAdvancedFunction
diff --git a/test/PowerShellEditorServices.Test.Shared/Symbols/NewLineSymbols.ps1 b/test/PowerShellEditorServices.Test.Shared/Symbols/NewLineSymbols.ps1
new file mode 100644
index 000000000..5ca44f02a
--- /dev/null
+++ b/test/PowerShellEditorServices.Test.Shared/Symbols/NewLineSymbols.ps1
@@ -0,0 +1,28 @@
+function
+returnTrue {
+ $true
+}
+
+class
+NewLineClass {
+ NewLineClass() {
+
+ }
+
+ static
+ hidden
+ [string]
+ $SomePropWithDefault = 'some value'
+
+ static
+ hidden
+ [string]
+ MyClassMethod([MyNewLineEnum]$param1) {
+ return 'hello world $param1'
+ }
+}
+
+enum
+MyNewLineEnum {
+ First
+}
diff --git a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs
index e7938c287..53993a484 100644
--- a/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs
+++ b/test/PowerShellEditorServices.Test/Language/SymbolsServiceTests.cs
@@ -3,9 +3,9 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Management.Automation;
+using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
@@ -22,6 +22,7 @@
using Microsoft.PowerShell.EditorServices.Test.Shared.References;
using Microsoft.PowerShell.EditorServices.Test.Shared.SymbolDetails;
using Microsoft.PowerShell.EditorServices.Test.Shared.Symbols;
+using Microsoft.PowerShell.EditorServices.Utility;
using Xunit;
namespace PowerShellEditorServices.Test.Language
@@ -32,11 +33,15 @@ public class SymbolsServiceTests : IDisposable
private readonly PsesInternalHost psesHost;
private readonly WorkspaceService workspace;
private readonly SymbolsService symbolsService;
+ private static readonly bool s_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public SymbolsServiceTests()
{
psesHost = PsesHostFactory.Create(NullLoggerFactory.Instance);
- workspace = new WorkspaceService(NullLoggerFactory.Instance);
+ workspace = new WorkspaceService(NullLoggerFactory.Instance)
+ {
+ WorkspacePath = TestUtilities.GetSharedPath("References")
+ };
symbolsService = new SymbolsService(
NullLoggerFactory.Instance,
psesHost,
@@ -55,6 +60,19 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+ private static void AssertIsRegion(
+ ScriptRegion region,
+ int startLineNumber,
+ int startColumnNumber,
+ int endLineNumber,
+ int endColumnNumber)
+ {
+ Assert.Equal(startLineNumber, region.StartLineNumber);
+ Assert.Equal(startColumnNumber, region.StartColumnNumber);
+ Assert.Equal(endLineNumber, region.EndLineNumber);
+ Assert.Equal(endColumnNumber, region.EndColumnNumber);
+ }
+
private ScriptFile GetScriptFile(ScriptRegion scriptRegion) => workspace.GetFile(TestUtilities.GetSharedPath(scriptRegion.File));
private Task GetParamSetSignatures(ScriptRegion scriptRegion)
@@ -65,72 +83,94 @@ private Task GetParamSetSignatures(ScriptRegion scriptRe
scriptRegion.StartColumnNumber);
}
- private Task GetDefinition(ScriptRegion scriptRegion)
+ private async Task> GetDefinitions(ScriptRegion scriptRegion)
{
ScriptFile scriptFile = GetScriptFile(scriptRegion);
- SymbolReference symbolReference = SymbolsService.FindSymbolAtLocation(
+ // TODO: We should just use the name to find it.
+ SymbolReference symbol = SymbolsService.FindSymbolAtLocation(
scriptFile,
scriptRegion.StartLineNumber,
scriptRegion.StartColumnNumber);
- Assert.NotNull(symbolReference);
+ Assert.NotNull(symbol);
- return symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbolReference);
+ IEnumerable symbols =
+ await symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbol).ConfigureAwait(true);
+
+ return symbols.OrderBy((i) => i.ScriptRegion.ToRange().Start);
}
- private Task> GetReferences(ScriptRegion scriptRegion)
+ private async Task GetDefinition(ScriptRegion scriptRegion)
+ {
+ IEnumerable definitions = await GetDefinitions(scriptRegion).ConfigureAwait(true);
+ return definitions.FirstOrDefault();
+ }
+
+ private async Task> GetReferences(ScriptRegion scriptRegion)
{
ScriptFile scriptFile = GetScriptFile(scriptRegion);
- SymbolReference symbolReference = SymbolsService.FindSymbolAtLocation(
+ SymbolReference symbol = SymbolsService.FindSymbolAtLocation(
scriptFile,
scriptRegion.StartLineNumber,
scriptRegion.StartColumnNumber);
- Assert.NotNull(symbolReference);
+ Assert.NotNull(symbol);
+
+ IEnumerable symbols =
+ await symbolsService.ScanForReferencesOfSymbolAsync(symbol).ConfigureAwait(true);
- return symbolsService.FindReferencesOfSymbol(
- symbolReference,
- workspace.ExpandScriptReferences(scriptFile),
- workspace);
+ return symbols.OrderBy((i) => i.ScriptRegion.ToRange().Start);
}
- private IReadOnlyList GetOccurrences(ScriptRegion scriptRegion)
+ private IEnumerable GetOccurrences(ScriptRegion scriptRegion)
{
- return SymbolsService.FindOccurrencesInFile(
- GetScriptFile(scriptRegion),
- scriptRegion.StartLineNumber,
- scriptRegion.StartColumnNumber);
+ return SymbolsService
+ .FindOccurrencesInFile(
+ GetScriptFile(scriptRegion),
+ scriptRegion.StartLineNumber,
+ scriptRegion.StartColumnNumber)
+ .OrderBy(symbol => symbol.ScriptRegion.ToRange().Start)
+ .ToArray();
}
- private List FindSymbolsInFile(ScriptRegion scriptRegion) => symbolsService.FindSymbolsInFile(GetScriptFile(scriptRegion));
+ private IEnumerable FindSymbolsInFile(ScriptRegion scriptRegion)
+ {
+ return symbolsService
+ .FindSymbolsInFile(GetScriptFile(scriptRegion))
+ .OrderBy(symbol => symbol.ScriptRegion.ToRange().Start);
+ }
[Fact]
public async Task FindsParameterHintsOnCommand()
{
- ParameterSetSignatures paramSignatures = await GetParamSetSignatures(FindsParameterSetsOnCommandData.SourceDetails).ConfigureAwait(true);
- Assert.NotNull(paramSignatures);
- Assert.Equal("Get-Process", paramSignatures.CommandName);
- Assert.Equal(6, paramSignatures.Signatures.Length);
+ // TODO: Fix signatures to use parameters, not sets.
+ ParameterSetSignatures signatures = await GetParamSetSignatures(FindsParameterSetsOnCommandData.SourceDetails).ConfigureAwait(true);
+ Assert.NotNull(signatures);
+ Assert.Equal("Get-Process", signatures.CommandName);
+ Assert.Equal(6, signatures.Signatures.Length);
}
[Fact]
public async Task FindsCommandForParamHintsWithSpaces()
{
- ParameterSetSignatures paramSignatures = await GetParamSetSignatures(FindsParameterSetsOnCommandWithSpacesData.SourceDetails).ConfigureAwait(true);
- Assert.NotNull(paramSignatures);
- Assert.Equal("Write-Host", paramSignatures.CommandName);
- Assert.Single(paramSignatures.Signatures);
+ ParameterSetSignatures signatures = await GetParamSetSignatures(FindsParameterSetsOnCommandWithSpacesData.SourceDetails).ConfigureAwait(true);
+ Assert.NotNull(signatures);
+ Assert.Equal("Write-Host", signatures.CommandName);
+ Assert.Single(signatures.Signatures);
}
[Fact]
public async Task FindsFunctionDefinition()
{
- SymbolReference definitionResult = await GetDefinition(FindsFunctionDefinitionData.SourceDetails).ConfigureAwait(true);
- Assert.Equal(1, definitionResult.ScriptRegion.StartLineNumber);
- Assert.Equal(10, definitionResult.ScriptRegion.StartColumnNumber);
- Assert.Equal("My-Function", definitionResult.SymbolName);
+ SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionData.SourceDetails).ConfigureAwait(true);
+ Assert.Equal("fn My-Function", symbol.Id);
+ Assert.Equal("function My-Function ($myInput)", symbol.Name);
+ Assert.Equal(SymbolType.Function, symbol.Type);
+ AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21);
+ AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2);
+ Assert.True(symbol.IsDeclaration);
}
[Fact]
@@ -142,19 +182,48 @@ await psesHost.ExecutePSCommandAsync(
new PSCommand().AddScript("Set-Alias -Name My-Alias -Value My-Function"),
CancellationToken.None).ConfigureAwait(true);
- SymbolReference definitionResult = await GetDefinition(FindsFunctionDefinitionOfAliasData.SourceDetails).ConfigureAwait(true);
- Assert.Equal(1, definitionResult.ScriptRegion.StartLineNumber);
- Assert.Equal(10, definitionResult.ScriptRegion.StartColumnNumber);
- Assert.Equal("My-Function", definitionResult.SymbolName);
+ SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionOfAliasData.SourceDetails).ConfigureAwait(true);
+ Assert.Equal("function My-Function ($myInput)", symbol.Name);
+ Assert.Equal(SymbolType.Function, symbol.Type);
+ AssertIsRegion(symbol.NameRegion, 1, 10, 1, 21);
+ AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2);
+ Assert.True(symbol.IsDeclaration);
}
[Fact]
public async Task FindsReferencesOnFunction()
{
- List referencesResult = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true);
- Assert.Equal(2, referencesResult.Count);
- Assert.Equal(3, referencesResult[0].ScriptRegion.StartLineNumber);
- Assert.Equal(2, referencesResult[0].ScriptRegion.StartColumnNumber);
+ IEnumerable symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("fn My-Function", i.Id);
+ Assert.Equal("function My-Function ($myInput)", i.Name);
+ Assert.Equal(SymbolType.Function, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("fn My-Function", i.Id);
+ Assert.Equal("My-Function", i.Name);
+ Assert.Equal(SymbolType.Function, i.Type);
+ Assert.EndsWith(FindsFunctionDefinitionInWorkspaceData.SourceDetails.File, i.FilePath);
+ Assert.False(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("fn My-Function", i.Id);
+ Assert.Equal("My-Function", i.Name);
+ Assert.Equal(SymbolType.Function, i.Type);
+ Assert.False(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("fn My-Function", i.Id);
+ Assert.Equal("My-Function", i.Name);
+ Assert.Equal(SymbolType.Function, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
}
[Fact]
@@ -165,218 +234,676 @@ await psesHost.ExecutePSCommandAsync(
new PSCommand().AddScript("Set-Alias -Name My-Alias -Value My-Function"),
CancellationToken.None).ConfigureAwait(true);
- List referencesResult = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true);
- Assert.Equal(3, referencesResult.Count);
- Assert.Equal(3, referencesResult[0].ScriptRegion.StartLineNumber);
- Assert.Equal(2, referencesResult[0].ScriptRegion.StartColumnNumber);
+ IEnumerable symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails).ConfigureAwait(true);
+
+ Assert.Collection(symbols,
+ (i) => AssertIsRegion(i.NameRegion, 1, 10, 1, 21),
+ (i) => AssertIsRegion(i.NameRegion, 3, 1, 3, 12),
+ (i) => AssertIsRegion(i.NameRegion, 3, 5, 3, 16),
+ (i) => AssertIsRegion(i.NameRegion, 10, 1, 10, 12),
+ // The alias.
+ (i) =>
+ {
+ AssertIsRegion(i.NameRegion, 20, 1, 20, 9);
+ Assert.Equal("fn My-Alias", i.Id);
+ });
}
[Fact]
- public async Task FindsFunctionDefinitionInDotSourceReference()
+ public async Task FindsFunctionDefinitionInWorkspace()
{
- SymbolReference definitionResult = await GetDefinition(FindsFunctionDefinitionInDotSourceReferenceData.SourceDetails).ConfigureAwait(true);
- Assert.True(
- definitionResult.FilePath.EndsWith(FindsFunctionDefinitionData.SourceDetails.File),
- "Unexpected reference file: " + definitionResult.FilePath);
- Assert.Equal(1, definitionResult.ScriptRegion.StartLineNumber);
- Assert.Equal(10, definitionResult.ScriptRegion.StartColumnNumber);
- Assert.Equal("My-Function", definitionResult.SymbolName);
+ IEnumerable symbols = await GetDefinitions(FindsFunctionDefinitionInWorkspaceData.SourceDetails).ConfigureAwait(true);
+ SymbolReference symbol = Assert.Single(symbols);
+ Assert.Equal("fn My-Function", symbol.Id);
+ Assert.Equal("function My-Function ($myInput)", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+ Assert.EndsWith(FindsFunctionDefinitionData.SourceDetails.File, symbol.FilePath);
}
[Fact]
- public async Task FindsDotSourcedFile()
+ public async Task FindsVariableDefinition()
{
- SymbolReference definitionResult = await GetDefinition(FindsDotSourcedFileData.SourceDetails).ConfigureAwait(true);
- Assert.NotNull(definitionResult);
- Assert.True(
- definitionResult.FilePath.EndsWith(Path.Combine("References", "ReferenceFileE.ps1")),
- "Unexpected reference file: " + definitionResult.FilePath);
- Assert.Equal(1, definitionResult.ScriptRegion.StartLineNumber);
- Assert.Equal(1, definitionResult.ScriptRegion.StartColumnNumber);
- Assert.Equal("./ReferenceFileE.ps1", definitionResult.SymbolName);
+ SymbolReference symbol = await GetDefinition(FindsVariableDefinitionData.SourceDetails).ConfigureAwait(true);
+ Assert.Equal("var things", symbol.Id);
+ Assert.Equal("$things", symbol.Name);
+ Assert.Equal(SymbolType.Variable, symbol.Type);
+ Assert.True(symbol.IsDeclaration);
+ AssertIsRegion(symbol.NameRegion, 6, 1, 6, 8);
}
[Fact]
- public async Task FindsFunctionDefinitionInWorkspace()
+ public async Task FindsReferencesOnVariable()
{
- workspace.WorkspacePath = TestUtilities.GetSharedPath("References");
- SymbolReference definitionResult = await GetDefinition(FindsFunctionDefinitionInWorkspaceData.SourceDetails).ConfigureAwait(true);
- Assert.EndsWith("ReferenceFileE.ps1", definitionResult.FilePath);
- Assert.Equal("My-FunctionInFileE", definitionResult.SymbolName);
+ IEnumerable symbols = await GetReferences(FindsReferencesOnVariableData.SourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("var things", i.Id);
+ Assert.Equal("$things", i.Name);
+ Assert.Equal(SymbolType.Variable, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("var things", i.Id);
+ Assert.Equal("$things", i.Name);
+ Assert.Equal(SymbolType.Variable, i.Type);
+ Assert.False(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("var things", i.Id);
+ Assert.Equal("$things", i.Name);
+ Assert.Equal(SymbolType.Variable, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
+
+ Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnVariableData.SourceDetails));
}
[Fact]
- public async Task FindsVariableDefinition()
+ public void FindsOccurrencesOnFunction()
{
- SymbolReference definitionResult = await GetDefinition(FindsVariableDefinitionData.SourceDetails).ConfigureAwait(true);
- Assert.Equal(6, definitionResult.ScriptRegion.StartLineNumber);
- Assert.Equal(1, definitionResult.ScriptRegion.StartColumnNumber);
- Assert.Equal("$things", definitionResult.SymbolName);
+ IEnumerable symbols = GetOccurrences(FindsOccurrencesOnFunctionData.SourceDetails);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("fn My-Function", i.Id);
+ Assert.Equal(SymbolType.Function, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("fn My-Function", i.Id);
+ Assert.Equal(SymbolType.Function, i.Type);
+ Assert.False(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("fn My-Function", i.Id);
+ Assert.Equal(SymbolType.Function, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
}
[Fact]
- public async Task FindsReferencesOnVariable()
+ public void FindsOccurrencesOnParameter()
{
- List referencesResult = await GetReferences(FindsReferencesOnVariableData.SourceDetails).ConfigureAwait(true);
- Assert.Equal(3, referencesResult.Count);
- Assert.Equal(10, referencesResult[referencesResult.Count - 1].ScriptRegion.StartLineNumber);
- Assert.Equal(13, referencesResult[referencesResult.Count - 1].ScriptRegion.StartColumnNumber);
+ IEnumerable symbols = GetOccurrences(FindOccurrencesOnParameterData.SourceDetails);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("var myInput", i.Id);
+ // TODO: Parameter names need work.
+ Assert.Equal("(parameter) [System.Object]$myInput", i.Name);
+ Assert.Equal(SymbolType.Parameter, i.Type);
+ AssertIsRegion(i.NameRegion, 1, 23, 1, 31);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("var myInput", i.Id);
+ Assert.Equal("$myInput", i.Name);
+ Assert.Equal(SymbolType.Variable, i.Type);
+ AssertIsRegion(i.NameRegion, 3, 17, 3, 25);
+ Assert.False(i.IsDeclaration);
+ });
}
[Fact]
- public void FindsOccurrencesOnFunction()
+ public async Task FindsReferencesOnCommandWithAlias()
{
- IReadOnlyList occurrencesResult = GetOccurrences(FindsOccurrencesOnFunctionData.SourceDetails);
- Assert.Equal(3, occurrencesResult.Count);
- Assert.Equal(10, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber);
- Assert.Equal(1, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartColumnNumber);
+ // NOTE: This doesn't use GetOccurrences as it's testing for aliases.
+ IEnumerable symbols = await GetReferences(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols.Where(
+ (i) => i.FilePath
+ .EndsWith(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails.File)),
+ (i) => Assert.Equal("fn Get-ChildItem", i.Id),
+ (i) => Assert.Equal("fn gci", i.Id),
+ (i) => Assert.Equal("fn dir", i.Id),
+ (i) => Assert.Equal("fn Get-ChildItem", i.Id));
}
[Fact]
- public void FindsOccurrencesOnParameter()
+ public async Task FindsClassDefinition()
{
- IReadOnlyList occurrencesResult = GetOccurrences(FindOccurrencesOnParameterData.SourceDetails);
- Assert.Equal(2, occurrencesResult.Count);
- Assert.Equal("$myInput", occurrencesResult[occurrencesResult.Count - 1].SymbolName);
- Assert.Equal(3, occurrencesResult[occurrencesResult.Count - 1].ScriptRegion.StartLineNumber);
+ SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.ClassSourceDetails).ConfigureAwait(true);
+ Assert.Equal("type SuperClass", symbol.Id);
+ Assert.Equal("class SuperClass { }", symbol.Name);
+ Assert.Equal(SymbolType.Class, symbol.Type);
+ Assert.True(symbol.IsDeclaration);
+ AssertIsRegion(symbol.NameRegion, 8, 7, 8, 17);
}
[Fact]
- public async Task FindsReferencesOnCommandWithAlias()
+ public async Task FindsReferencesOnClass()
{
- List referencesResult = await GetReferences(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails).ConfigureAwait(true);
- Assert.Equal(4, referencesResult.Count);
- Assert.Equal("Get-ChildItem", referencesResult[1].SymbolName);
- Assert.Equal("Get-ChildItem", referencesResult[2].SymbolName);
- Assert.Equal("Get-ChildItem", referencesResult[referencesResult.Count - 1].SymbolName);
+ IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.ClassSourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("type SuperClass", i.Id);
+ Assert.Equal("class SuperClass { }", i.Name);
+ Assert.Equal(SymbolType.Class, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("type SuperClass", i.Id);
+ Assert.Equal("(type) SuperClass", i.Name);
+ Assert.Equal(SymbolType.Type, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
+
+ Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ClassSourceDetails));
}
[Fact]
- public async Task FindsReferencesOnFileWithReferencesFileB()
+ public async Task FindsEnumDefinition()
{
- List referencesResult = await GetReferences(FindsReferencesOnFunctionMultiFileDotSourceFileB.SourceDetails).ConfigureAwait(true);
- Assert.Equal(3, referencesResult.Count);
+ SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumSourceDetails).ConfigureAwait(true);
+ Assert.Equal("type MyEnum", symbol.Id);
+ Assert.Equal("enum MyEnum { }", symbol.Name);
+ Assert.Equal(SymbolType.Enum, symbol.Type);
+ Assert.True(symbol.IsDeclaration);
+ AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12);
}
[Fact]
- public async Task FindsReferencesOnFileWithReferencesFileC()
+ public async Task FindsReferencesOnEnum()
{
- List referencesResult = await GetReferences(FindsReferencesOnFunctionMultiFileDotSourceFileC.SourceDetails).ConfigureAwait(true);
- Assert.Equal(3, referencesResult.Count);
+ IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumSourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("type MyEnum", i.Id);
+ Assert.Equal("(type) MyEnum", i.Name);
+ Assert.Equal(SymbolType.Type, i.Type);
+ Assert.False(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("type MyEnum", i.Id);
+ Assert.Equal("enum MyEnum { }", i.Name);
+ Assert.Equal(SymbolType.Enum, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("type MyEnum", i.Id);
+ Assert.Equal("(type) MyEnum", i.Name);
+ Assert.Equal(SymbolType.Type, i.Type);
+ Assert.False(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("type MyEnum", i.Id);
+ Assert.Equal("(type) MyEnum", i.Name);
+ Assert.Equal(SymbolType.Type, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
+
+ Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumSourceDetails));
}
[Fact]
- public async Task FindsDetailsForBuiltInCommand()
+ public async Task FindsTypeExpressionDefinition()
{
- SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync(
- GetScriptFile(FindsDetailsForBuiltInCommandData.SourceDetails),
- FindsDetailsForBuiltInCommandData.SourceDetails.StartLineNumber,
- FindsDetailsForBuiltInCommandData.SourceDetails.StartColumnNumber).ConfigureAwait(true);
+ SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeExpressionSourceDetails).ConfigureAwait(true);
+ AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12);
+ Assert.Equal("type MyEnum", symbol.Id);
+ Assert.Equal("enum MyEnum { }", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+ }
- Assert.NotNull(symbolDetails.Documentation);
- Assert.NotEqual("", symbolDetails.Documentation);
+ [Fact]
+ public async Task FindsReferencesOnTypeExpression()
+ {
+ IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeExpressionSourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("type SuperClass", i.Id);
+ Assert.Equal("class SuperClass { }", i.Name);
+ Assert.Equal(SymbolType.Class, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("type SuperClass", i.Id);
+ Assert.Equal("(type) SuperClass", i.Name);
+ Assert.Equal(SymbolType.Type, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
+
+ Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.TypeExpressionSourceDetails));
}
[Fact]
- public void FindsSymbolsInFile()
+ public async Task FindsTypeConstraintDefinition()
{
- List symbolsResult =
- FindSymbolsInFile(
- FindSymbolsInMultiSymbolFile.SourceDetails);
+ SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeConstraintSourceDetails).ConfigureAwait(true);
+ AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12);
+ Assert.Equal("type MyEnum", symbol.Id);
+ Assert.Equal("enum MyEnum { }", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+ }
- Assert.Equal(4, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Function));
- Assert.Equal(3, symbolsResult.Count(symbolReference => symbolReference.SymbolType == SymbolType.Variable));
- Assert.Single(symbolsResult.Where(symbolReference => symbolReference.SymbolType == SymbolType.Workflow));
+ [Fact]
+ public async Task FindsReferencesOnTypeConstraint()
+ {
+ IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeConstraintSourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("type MyEnum", i.Id);
+ Assert.Equal("(type) MyEnum", i.Name);
+ Assert.Equal(SymbolType.Type, i.Type);
+ Assert.False(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("type MyEnum", i.Id);
+ Assert.Equal("enum MyEnum { }", i.Name);
+ Assert.Equal(SymbolType.Enum, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("type MyEnum", i.Id);
+ Assert.Equal("(type) MyEnum", i.Name);
+ Assert.Equal(SymbolType.Type, i.Type);
+ Assert.False(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("type MyEnum", i.Id);
+ Assert.Equal("(type) MyEnum", i.Name);
+ Assert.Equal(SymbolType.Type, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
+ }
- SymbolReference firstFunctionSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Function);
- Assert.Equal("AFunction", firstFunctionSymbol.SymbolName);
- Assert.Equal(7, firstFunctionSymbol.ScriptRegion.StartLineNumber);
- Assert.Equal(1, firstFunctionSymbol.ScriptRegion.StartColumnNumber);
+ [Fact]
+ public void FindsOccurrencesOnTypeConstraint()
+ {
+ IEnumerable symbols = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.TypeConstraintSourceDetails);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("type BaseClass", i.Id);
+ Assert.Equal("class BaseClass { }", i.Name);
+ Assert.Equal(SymbolType.Class, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("type BaseClass", i.Id);
+ Assert.Equal("(type) BaseClass", i.Name);
+ Assert.Equal(SymbolType.Type, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
+ }
- SymbolReference lastVariableSymbol = symbolsResult.Last(r => r.SymbolType == SymbolType.Variable);
- Assert.Equal("$Script:ScriptVar2", lastVariableSymbol.SymbolName);
- Assert.Equal(3, lastVariableSymbol.ScriptRegion.StartLineNumber);
- Assert.Equal(1, lastVariableSymbol.ScriptRegion.StartColumnNumber);
+ [Fact]
+ public async Task FindsConstructorDefinition()
+ {
+ IEnumerable symbols = await GetDefinitions(FindsTypeSymbolsDefinitionData.ConstructorSourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("mtd SuperClass", i.Id);
+ Assert.Equal("SuperClass([string]$name)", i.Name);
+ Assert.Equal(SymbolType.Constructor, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("mtd SuperClass", i.Id);
+ Assert.Equal("SuperClass()", i.Name);
+ Assert.Equal(SymbolType.Constructor, i.Type);
+ Assert.True(i.IsDeclaration);
+ });
+
+ Assert.Equal(symbols, await GetReferences(FindsReferencesOnTypeSymbolsData.ConstructorSourceDetails).ConfigureAwait(true));
+ Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ConstructorSourceDetails));
+ }
- SymbolReference firstWorkflowSymbol = symbolsResult.First(r => r.SymbolType == SymbolType.Workflow);
- Assert.Equal("AWorkflow", firstWorkflowSymbol.SymbolName);
- Assert.Equal(23, firstWorkflowSymbol.ScriptRegion.StartLineNumber);
- Assert.Equal(1, firstWorkflowSymbol.ScriptRegion.StartColumnNumber);
+ [Fact]
+ public async Task FindsMethodDefinition()
+ {
+ IEnumerable symbols = await GetDefinitions(FindsTypeSymbolsDefinitionData.MethodSourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("mtd MyClassMethod", i.Id);
+ Assert.Equal("string MyClassMethod([string]$param1, $param2, [int]$param3)", i.Name);
+ Assert.Equal(SymbolType.Method, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("mtd MyClassMethod", i.Id);
+ Assert.Equal("string MyClassMethod([MyEnum]$param1)", i.Name);
+ Assert.Equal(SymbolType.Method, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("mtd MyClassMethod", i.Id);
+ Assert.Equal("string MyClassMethod()", i.Name);
+ Assert.Equal(SymbolType.Method, i.Type);
+ Assert.True(i.IsDeclaration);
+ });
+ }
- // TODO: Bring this back when we can use AstVisitor2 again (#276)
- //Assert.Equal(1, symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Configuration).Count());
- //SymbolReference firstConfigurationSymbol = symbolsResult.FoundOccurrences.Where(r => r.SymbolType == SymbolType.Configuration).First();
- //Assert.Equal("AConfiguration", firstConfigurationSymbol.SymbolName);
- //Assert.Equal(25, firstConfigurationSymbol.ScriptRegion.StartLineNumber);
- //Assert.Equal(1, firstConfigurationSymbol.ScriptRegion.StartColumnNumber);
+ [Fact]
+ public async Task FindsReferencesOnMethod()
+ {
+ IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.MethodSourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols,
+ (i) => Assert.Equal("string MyClassMethod([string]$param1, $param2, [int]$param3)", i.Name),
+ (i) => Assert.Equal("string MyClassMethod([MyEnum]$param1)", i.Name),
+ (i) => Assert.Equal("string MyClassMethod()", i.Name),
+ (i) => // The invocation!
+ {
+ Assert.Equal("mtd MyClassMethod", i.Id);
+ Assert.Equal("(method) MyClassMethod", i.Name);
+ Assert.Equal("$o.MyClassMethod()", i.SourceLine);
+ Assert.Equal(SymbolType.Method, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
+
+ Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.MethodSourceDetails));
}
[Fact]
- public void FindsSymbolsInPesterFile()
+ public async Task FindsPropertyDefinition()
{
- List symbolsResult = FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails).OfType().ToList();
- Assert.Equal(12, symbolsResult.Count(r => r.SymbolType == SymbolType.Function));
+ SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.PropertySourceDetails).ConfigureAwait(true);
+ Assert.Equal("prop SomePropWithDefault", symbol.Id);
+ Assert.Equal("[string] $SomePropWithDefault", symbol.Name);
+ Assert.Equal(SymbolType.Property, symbol.Type);
+ Assert.True(symbol.IsDeclaration);
+ }
- Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.Describe));
- SymbolReference firstDescribeSymbol = symbolsResult.First(r => r.Command == PesterCommandType.Describe);
- Assert.Equal("Describe \"Testing Pester symbols\"", firstDescribeSymbol.SymbolName);
- Assert.Equal(9, firstDescribeSymbol.ScriptRegion.StartLineNumber);
- Assert.Equal(1, firstDescribeSymbol.ScriptRegion.StartColumnNumber);
+ [Fact]
+ public async Task FindsReferencesOnProperty()
+ {
+ IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.PropertySourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("prop SomeProp", i.Id);
+ Assert.Equal("[int] $SomeProp", i.Name);
+ Assert.Equal(SymbolType.Property, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("prop SomeProp", i.Id);
+ Assert.Equal("(property) SomeProp", i.Name);
+ Assert.Equal(SymbolType.Property, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
+ }
+
+ [Fact]
+ public void FindsOccurrencesOnProperty()
+ {
+ IEnumerable symbols = GetOccurrences(FindsOccurrencesOnTypeSymbolsData.PropertySourceDetails);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("prop SomePropWithDefault", i.Id);
+ Assert.Equal("[string] $SomePropWithDefault", i.Name);
+ Assert.Equal(SymbolType.Property, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("prop SomePropWithDefault", i.Id);
+ Assert.Equal("(property) SomePropWithDefault", i.Name);
+ Assert.Equal(SymbolType.Property, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
+ }
+
+ [Fact]
+ public async Task FindsEnumMemberDefinition()
+ {
+ SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumMemberSourceDetails).ConfigureAwait(true);
+ Assert.Equal("prop Second", symbol.Id);
+ // Doesn't include [MyEnum]:: because that'd be redundant in the outline.
+ Assert.Equal("Second", symbol.Name);
+ Assert.Equal(SymbolType.EnumMember, symbol.Type);
+ Assert.True(symbol.IsDeclaration);
+ AssertIsRegion(symbol.NameRegion, 41, 5, 41, 11);
+
+ symbol = await GetDefinition(FindsReferencesOnTypeSymbolsData.EnumMemberSourceDetails).ConfigureAwait(true);
+ Assert.Equal("prop First", symbol.Id);
+ Assert.Equal("First", symbol.Name);
+ Assert.Equal(SymbolType.EnumMember, symbol.Type);
+ Assert.True(symbol.IsDeclaration);
+ AssertIsRegion(symbol.NameRegion, 40, 5, 40, 10);
+ }
+
+ [Fact]
+ public async Task FindsReferencesOnEnumMember()
+ {
+ IEnumerable symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumMemberSourceDetails).ConfigureAwait(true);
+ Assert.Collection(symbols,
+ (i) =>
+ {
+ Assert.Equal("prop First", i.Id);
+ Assert.Equal("First", i.Name);
+ Assert.Equal(SymbolType.EnumMember, i.Type);
+ Assert.True(i.IsDeclaration);
+ },
+ (i) =>
+ {
+ Assert.Equal("prop First", i.Id);
+ // The reference is just a member invocation, and so indistinguishable from a property.
+ Assert.Equal("(property) First", i.Name);
+ Assert.Equal(SymbolType.Property, i.Type);
+ Assert.False(i.IsDeclaration);
+ });
+
+ Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.EnumMemberSourceDetails));
+ }
- Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.Context));
- SymbolReference firstContextSymbol = symbolsResult.First(r => r.Command == PesterCommandType.Context);
- Assert.Equal("Context \"When a Pester file is given\"", firstContextSymbol.SymbolName);
- Assert.Equal(10, firstContextSymbol.ScriptRegion.StartLineNumber);
- Assert.Equal(5, firstContextSymbol.ScriptRegion.StartColumnNumber);
+ [SkippableFact]
+ public async Task FindsDetailsForBuiltInCommand()
+ {
+ Skip.IfNot(VersionUtils.IsMacOS, "macOS gets the right synopsis but others don't.");
+ SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync(
+ GetScriptFile(FindsDetailsForBuiltInCommandData.SourceDetails),
+ FindsDetailsForBuiltInCommandData.SourceDetails.StartLineNumber,
+ FindsDetailsForBuiltInCommandData.SourceDetails.StartColumnNumber).ConfigureAwait(true);
- Assert.Equal(4, symbolsResult.Count(r => r.Command == PesterCommandType.It));
- SymbolReference lastItSymbol = symbolsResult.Last(r => r.Command == PesterCommandType.It);
- Assert.Equal("It \"Should return setup and teardown symbols\"", lastItSymbol.SymbolName);
- Assert.Equal(31, lastItSymbol.ScriptRegion.StartLineNumber);
- Assert.Equal(9, lastItSymbol.ScriptRegion.StartColumnNumber);
+ Assert.Equal("Gets the processes that are running on the local computer.", symbolDetails.Documentation);
+ }
- Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.BeforeDiscovery));
- SymbolReference firstBeforeDisocverySymbol = symbolsResult.First(r => r.Command == PesterCommandType.BeforeDiscovery);
- Assert.Equal("BeforeDiscovery", firstBeforeDisocverySymbol.SymbolName);
- Assert.Equal(1, firstBeforeDisocverySymbol.ScriptRegion.StartLineNumber);
- Assert.Equal(1, firstBeforeDisocverySymbol.ScriptRegion.StartColumnNumber);
+ [Fact]
+ public void FindsSymbolsInFile()
+ {
+ IEnumerable symbols = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails);
+
+ Assert.Equal(7, symbols.Count(i => i.Type == SymbolType.Function));
+ Assert.Equal(8, symbols.Count(i => i.Type == SymbolType.Variable));
+ Assert.Equal(4, symbols.Count(i => i.Type == SymbolType.Parameter));
+ Assert.Equal(12, symbols.Count(i => i.Id.StartsWith("var ")));
+ Assert.Equal(2, symbols.Count(i => i.Id.StartsWith("prop ")));
+
+ SymbolReference symbol = symbols.First(i => i.Type == SymbolType.Function);
+ Assert.Equal("fn AFunction", symbol.Id);
+ Assert.Equal("function AFunction ()", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+
+ symbol = symbols.First(i => i.Id == "fn AFilter");
+ Assert.Equal("filter AFilter ()", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+
+ symbol = symbols.Last(i => i.Type == SymbolType.Variable);
+ Assert.Equal("var nestedVar", symbol.Id);
+ Assert.Equal("$nestedVar", symbol.Name);
+ Assert.False(symbol.IsDeclaration);
+ AssertIsRegion(symbol.NameRegion, 16, 29, 16, 39);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Workflow));
+ Assert.Equal("fn AWorkflow", symbol.Id);
+ Assert.Equal("workflow AWorkflow ()", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Class));
+ Assert.Equal("type AClass", symbol.Id);
+ Assert.Equal("class AClass { }", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Property));
+ Assert.Equal("prop AProperty", symbol.Id);
+ Assert.Equal("[string] $AProperty", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Constructor));
+ Assert.Equal("mtd AClass", symbol.Id);
+ Assert.Equal("AClass([string]$AParameter)", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Method));
+ Assert.Equal("mtd AMethod", symbol.Id);
+ Assert.Equal("void AMethod([string]$param1, [int]$param2, $param3)", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Enum));
+ Assert.Equal("type AEnum", symbol.Id);
+ Assert.Equal("enum AEnum { }", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.EnumMember));
+ Assert.Equal("prop AValue", symbol.Id);
+ Assert.Equal("AValue", symbol.Name);
+ Assert.True(symbol.IsDeclaration);
+ }
- Assert.Equal(2, symbolsResult.Count(r => r.Command == PesterCommandType.BeforeAll));
- SymbolReference lastBeforeAllSymbol = symbolsResult.Last(r => r.Command == PesterCommandType.BeforeAll);
- Assert.Equal("BeforeAll", lastBeforeAllSymbol.SymbolName);
- Assert.Equal(11, lastBeforeAllSymbol.ScriptRegion.StartLineNumber);
- Assert.Equal(9, lastBeforeAllSymbol.ScriptRegion.StartColumnNumber);
+ [Fact]
+ public void FindsSymbolsWithNewLineInFile()
+ {
+ IEnumerable symbols = FindSymbolsInFile(FindSymbolsInNewLineSymbolFile.SourceDetails);
+
+ SymbolReference symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Function));
+ Assert.Equal("fn returnTrue", symbol.Id);
+ AssertIsRegion(symbol.NameRegion, 2, 1, 2, 11);
+ AssertIsRegion(symbol.ScriptRegion, 1, 1, 4, 2);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Class));
+ Assert.Equal("type NewLineClass", symbol.Id);
+ AssertIsRegion(symbol.NameRegion, 7, 1, 7, 13);
+ AssertIsRegion(symbol.ScriptRegion, 6, 1, 23, 2);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Constructor));
+ Assert.Equal("mtd NewLineClass", symbol.Id);
+ AssertIsRegion(symbol.NameRegion, 8, 5, 8, 17);
+ AssertIsRegion(symbol.ScriptRegion, 8, 5, 10, 6);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Property));
+ Assert.Equal("prop SomePropWithDefault", symbol.Id);
+ AssertIsRegion(symbol.NameRegion, 15, 5, 15, 25);
+ AssertIsRegion(symbol.ScriptRegion, 12, 5, 15, 40);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Method));
+ Assert.Equal("mtd MyClassMethod", symbol.Id);
+ Assert.Equal("string MyClassMethod([MyNewLineEnum]$param1)", symbol.Name);
+ AssertIsRegion(symbol.NameRegion, 20, 5, 20, 18);
+ AssertIsRegion(symbol.ScriptRegion, 17, 5, 22, 6);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.Enum));
+ Assert.Equal("type MyNewLineEnum", symbol.Id);
+ AssertIsRegion(symbol.NameRegion, 26, 1, 26, 14);
+ AssertIsRegion(symbol.ScriptRegion, 25, 1, 28, 2);
+
+ symbol = Assert.Single(symbols.Where(i => i.Type == SymbolType.EnumMember));
+ Assert.Equal("prop First", symbol.Id);
+ AssertIsRegion(symbol.NameRegion, 27, 5, 27, 10);
+ AssertIsRegion(symbol.ScriptRegion, 27, 5, 27, 10);
+ }
- Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.BeforeEach));
- SymbolReference firstBeforeEachSymbol = symbolsResult.First(r => r.Command == PesterCommandType.BeforeEach);
- Assert.Equal("BeforeEach", firstBeforeEachSymbol.SymbolName);
- Assert.Equal(15, firstBeforeEachSymbol.ScriptRegion.StartLineNumber);
- Assert.Equal(9, firstBeforeEachSymbol.ScriptRegion.StartColumnNumber);
+ [Fact(Skip="DSC symbols don't work yet.")]
+ public void FindsSymbolsInDSCFile()
+ {
+ Skip.If(!s_isWindows, "DSC only works properly on Windows.");
- Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.AfterEach));
- SymbolReference firstAfterEachSymbol = symbolsResult.First(r => r.Command == PesterCommandType.AfterEach);
- Assert.Equal("AfterEach", firstAfterEachSymbol.SymbolName);
- Assert.Equal(35, firstAfterEachSymbol.ScriptRegion.StartLineNumber);
- Assert.Equal(9, firstAfterEachSymbol.ScriptRegion.StartColumnNumber);
+ IEnumerable symbols = FindSymbolsInFile(FindSymbolsInDSCFile.SourceDetails);
+ SymbolReference symbol = Assert.Single(symbols, i => i.Type == SymbolType.Configuration);
+ Assert.Equal("AConfiguration", symbol.Id);
+ Assert.Equal(2, symbol.ScriptRegion.StartLineNumber);
+ Assert.Equal(15, symbol.ScriptRegion.StartColumnNumber);
+ }
- Assert.Equal(1, symbolsResult.Count(r => r.Command == PesterCommandType.AfterAll));
- SymbolReference firstAfterAllSymbol = symbolsResult.First(r => r.Command == PesterCommandType.AfterAll);
- Assert.Equal("AfterAll", firstAfterAllSymbol.SymbolName);
- Assert.Equal(40, firstAfterAllSymbol.ScriptRegion.StartLineNumber);
- Assert.Equal(5, firstAfterAllSymbol.ScriptRegion.StartColumnNumber);
+ [Fact]
+ public void FindsSymbolsInPesterFile()
+ {
+ IEnumerable symbols = FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails).OfType();
+ Assert.Equal(12, symbols.Count(i => i.Type == SymbolType.Function));
+
+ SymbolReference symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.Describe);
+ Assert.Equal("Describe \"Testing Pester symbols\"", symbol.Id);
+ Assert.Equal(9, symbol.ScriptRegion.StartLineNumber);
+ Assert.Equal(1, symbol.ScriptRegion.StartColumnNumber);
+
+ symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.Context);
+ Assert.Equal("Context \"When a Pester file is given\"", symbol.Id);
+ Assert.Equal(10, symbol.ScriptRegion.StartLineNumber);
+ Assert.Equal(5, symbol.ScriptRegion.StartColumnNumber);
+
+ Assert.Equal(4, symbols.Count(i => i.Command == PesterCommandType.It));
+ symbol = symbols.Last(i => i.Command == PesterCommandType.It);
+ Assert.Equal("It \"Should return setup and teardown symbols\"", symbol.Id);
+ Assert.Equal(31, symbol.ScriptRegion.StartLineNumber);
+ Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber);
+
+ symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.BeforeDiscovery);
+ Assert.Equal("BeforeDiscovery", symbol.Id);
+ Assert.Equal(1, symbol.ScriptRegion.StartLineNumber);
+ Assert.Equal(1, symbol.ScriptRegion.StartColumnNumber);
+
+ Assert.Equal(2, symbols.Count(i => i.Command == PesterCommandType.BeforeAll));
+ symbol = symbols.Last(i => i.Command == PesterCommandType.BeforeAll);
+ Assert.Equal("BeforeAll", symbol.Id);
+ Assert.Equal(11, symbol.ScriptRegion.StartLineNumber);
+ Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber);
+
+ symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.BeforeEach);
+ Assert.Equal("BeforeEach", symbol.Id);
+ Assert.Equal(15, symbol.ScriptRegion.StartLineNumber);
+ Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber);
+
+ symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.AfterEach);
+ Assert.Equal("AfterEach", symbol.Id);
+ Assert.Equal(35, symbol.ScriptRegion.StartLineNumber);
+ Assert.Equal(9, symbol.ScriptRegion.StartColumnNumber);
+
+ symbol = Assert.Single(symbols, i => i.Command == PesterCommandType.AfterAll);
+ Assert.Equal("AfterAll", symbol.Id);
+ Assert.Equal(40, symbol.ScriptRegion.StartLineNumber);
+ Assert.Equal(5, symbol.ScriptRegion.StartColumnNumber);
}
[Fact]
- public void LangServerFindsSymbolsInPSDFile()
+ public void FindsSymbolsInPSDFile()
{
- List symbolsResult = FindSymbolsInFile(FindSymbolsInPSDFile.SourceDetails);
- Assert.Equal(3, symbolsResult.Count);
+ IEnumerable symbols = FindSymbolsInFile(FindSymbolsInPSDFile.SourceDetails);
+ Assert.All(symbols, i => Assert.Equal(SymbolType.HashtableKey, i.Type));
+ Assert.Collection(symbols,
+ i => Assert.Equal("property1", i.Id),
+ i => Assert.Equal("property2", i.Id),
+ i => Assert.Equal("property3", i.Id));
}
[Fact]
public void FindsSymbolsInNoSymbolsFile()
{
- List symbolsResult = FindSymbolsInFile(FindSymbolsInNoSymbolsFile.SourceDetails);
+ IEnumerable symbolsResult = FindSymbolsInFile(FindSymbolsInNoSymbolsFile.SourceDetails);
Assert.Empty(symbolsResult);
}
}
diff --git a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs
index 6789c4323..649ef32fd 100644
--- a/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs
+++ b/test/PowerShellEditorServices.Test/Services/Symbols/AstOperationsTests.cs
@@ -2,9 +2,12 @@
// Licensed under the MIT License.
using System.Collections.Generic;
-using System.Management.Automation;
-using System.Management.Automation.Language;
+using System.Linq;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.PowerShell.EditorServices.Services;
using Microsoft.PowerShell.EditorServices.Services.Symbols;
+using Microsoft.PowerShell.EditorServices.Services.TextDocument;
+using Microsoft.PowerShell.EditorServices.Test.Shared;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using Xunit;
@@ -13,64 +16,46 @@ namespace PowerShellEditorServices.Test.Services.Symbols
[Trait("Category", "AstOperations")]
public class AstOperationsTests
{
- private const string s_scriptString = @"function BasicFunction {}
-BasicFunction
+ private readonly ScriptFile scriptFile;
-function FunctionWithExtraSpace
-{
-
-} FunctionWithExtraSpace
-
-function
-
-
- FunctionNameOnDifferentLine
-
-
-
-
-
-
- {}
-
-
- FunctionNameOnDifferentLine
-
- function IndentedFunction { } IndentedFunction
-";
- private static readonly ScriptBlockAst s_ast = (ScriptBlockAst)ScriptBlock.Create(s_scriptString).Ast;
+ public AstOperationsTests()
+ {
+ WorkspaceService workspace = new(NullLoggerFactory.Instance);
+ scriptFile = workspace.GetFile(TestUtilities.GetSharedPath("References/FunctionReference.ps1"));
+ }
[Theory]
- [InlineData(1, 15, "BasicFunction")]
- [InlineData(2, 3, "BasicFunction")]
- [InlineData(4, 31, "FunctionWithExtraSpace")]
- [InlineData(7, 18, "FunctionWithExtraSpace")]
- [InlineData(12, 22, "FunctionNameOnDifferentLine")]
- [InlineData(22, 13, "FunctionNameOnDifferentLine")]
- [InlineData(24, 30, "IndentedFunction")]
- [InlineData(24, 52, "IndentedFunction")]
- public void CanFindSymbolAtPosition(int lineNumber, int columnNumber, string expectedName)
+ [InlineData(1, 15, "fn BasicFunction")]
+ [InlineData(2, 3, "fn BasicFunction")]
+ [InlineData(4, 31, "fn FunctionWithExtraSpace")]
+ [InlineData(7, 18, "fn FunctionWithExtraSpace")]
+ [InlineData(12, 22, "fn FunctionNameOnDifferentLine")]
+ [InlineData(22, 13, "fn FunctionNameOnDifferentLine")]
+ [InlineData(24, 30, "fn IndentedFunction")]
+ [InlineData(24, 52, "fn IndentedFunction")]
+ public void CanFindSymbolAtPosition(int line, int column, string expectedName)
{
- SymbolReference reference = AstOperations.FindSymbolAtPosition(s_ast, lineNumber, columnNumber);
- Assert.NotNull(reference);
- Assert.Equal(expectedName, reference.SymbolName);
+ SymbolReference symbol = scriptFile.References.TryGetSymbolAtPosition(line, column);
+ Assert.NotNull(symbol);
+ Assert.Equal(expectedName, symbol.Id);
}
[Theory]
[MemberData(nameof(FindReferencesOfSymbolAtPositionData))]
- public void CanFindReferencesOfSymbolAtPosition(int lineNumber, int columnNumber, Range[] symbolRange)
+ public void CanFindReferencesOfSymbolAtPosition(int line, int column, Range[] symbolRange)
{
- SymbolReference symbol = AstOperations.FindSymbolAtPosition(s_ast, lineNumber, columnNumber);
+ SymbolReference symbol = scriptFile.References.TryGetSymbolAtPosition(line, column);
- IEnumerable references = AstOperations.FindReferencesOfSymbol(s_ast, symbol);
+ IEnumerable references = scriptFile.References.TryGetReferences(symbol);
+ Assert.NotEmpty(references);
int positionsIndex = 0;
- foreach (SymbolReference reference in references)
+ foreach (SymbolReference reference in references.OrderBy((i) => i.ScriptRegion.ToRange().Start))
{
- Assert.Equal(symbolRange[positionsIndex].Start.Line, reference.ScriptRegion.StartLineNumber);
- Assert.Equal(symbolRange[positionsIndex].Start.Character, reference.ScriptRegion.StartColumnNumber);
- Assert.Equal(symbolRange[positionsIndex].End.Line, reference.ScriptRegion.EndLineNumber);
- Assert.Equal(symbolRange[positionsIndex].End.Character, reference.ScriptRegion.EndColumnNumber);
+ Assert.Equal(symbolRange[positionsIndex].Start.Line, reference.NameRegion.StartLineNumber);
+ Assert.Equal(symbolRange[positionsIndex].Start.Character, reference.NameRegion.StartColumnNumber);
+ Assert.Equal(symbolRange[positionsIndex].End.Line, reference.NameRegion.EndLineNumber);
+ Assert.Equal(symbolRange[positionsIndex].End.Character, reference.NameRegion.EndColumnNumber);
positionsIndex++;
}
diff --git a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs
index 40efc5c26..4839eacfa 100644
--- a/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs
+++ b/test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs
@@ -174,30 +174,6 @@ public void CanAppendToEndOfFile()
);
}
- [Trait("Category", "ScriptFile")]
- [Fact]
- public void FindsDotSourcedFiles()
- {
- string exampleScriptContents = TestUtilities.PlatformNormalize(
- ". ./athing.ps1\n" +
- ". ./somefile.ps1\n" +
- ". ./somefile.ps1\n" +
- "Do-Stuff $uri\n" +
- ". simpleps.ps1");
-
- using StringReader stringReader = new(exampleScriptContents);
- ScriptFile scriptFile =
- new(
- // Use any absolute path. Even if it doesn't exist.
- DocumentUri.FromFileSystemPath(Path.Combine(Path.GetTempPath(), "TestFile.ps1")),
- stringReader,
- PowerShellVersion);
-
- Assert.Equal(3, scriptFile.ReferencedFiles.Length);
- Console.Write("a" + scriptFile.ReferencedFiles[0]);
- Assert.Equal(TestUtilities.NormalizePath("./athing.ps1"), scriptFile.ReferencedFiles[0]);
- }
-
[Trait("Category", "ScriptFile")]
[Fact]
public void ThrowsExceptionWithEditOutsideOfRange()
@@ -610,7 +586,6 @@ public void PropertiesInitializedCorrectlyForFile()
Assert.Equal(path, scriptFile.FilePath, ignoreCase: !VersionUtils.IsLinux);
Assert.True(scriptFile.IsAnalysisEnabled);
Assert.False(scriptFile.IsInMemory);
- Assert.Empty(scriptFile.ReferencedFiles);
Assert.Empty(scriptFile.DiagnosticMarkers);
Assert.Single(scriptFile.ScriptTokens);
Assert.Single(scriptFile.FileLines);
@@ -635,7 +610,6 @@ public void PropertiesInitializedCorrectlyForUntitled()
Assert.Equal(path, scriptFile.DocumentUri);
Assert.True(scriptFile.IsAnalysisEnabled);
Assert.True(scriptFile.IsInMemory);
- Assert.Empty(scriptFile.ReferencedFiles);
Assert.Empty(scriptFile.DiagnosticMarkers);
Assert.Equal(10, scriptFile.ScriptTokens.Length);
Assert.Equal(3, scriptFile.FileLines.Count);