initial
Some checks are pending
CI Tests / dotnet (push) Waiting to run
CI Tests / dotnet-1 (push) Waiting to run
CI Tests / dotnet-2 (push) Waiting to run
Emacs End-to-End Tests / ert (push) Waiting to run
Vim End-to-End Tests / themis (push) Waiting to run

This commit is contained in:
fwastring 2026-02-17 13:06:31 +01:00
commit baa0056244
352 changed files with 47928 additions and 0 deletions

View file

@ -0,0 +1,152 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.PowerShell.EditorServices.Handlers;
using Microsoft.PowerShell.EditorServices.Services;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
using Microsoft.PowerShell.EditorServices.Test;
using Microsoft.PowerShell.EditorServices.Test.Shared;
using Microsoft.PowerShell.EditorServices.Test.Shared.Completion;
using Microsoft.PowerShell.EditorServices.Utility;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using Xunit;
namespace PowerShellEditorServices.Test.Language
{
[Trait("Category", "Completions")]
public class CompletionHandlerTests : IAsyncLifetime
{
private PsesInternalHost psesHost;
private WorkspaceService workspace;
private PsesCompletionHandler completionHandler;
public async Task InitializeAsync()
{
psesHost = await PsesHostFactory.Create(NullLoggerFactory.Instance);
workspace = new WorkspaceService(NullLoggerFactory.Instance);
completionHandler = new PsesCompletionHandler(NullLoggerFactory.Instance, psesHost, psesHost, workspace);
}
public async Task DisposeAsync() => await Task.Run(psesHost.StopAsync);
private ScriptFile GetScriptFile(ScriptRegion scriptRegion) => workspace.GetFile(TestUtilities.GetSharedPath(scriptRegion.File));
private Task<CompletionResults> GetCompletionResultsAsync(ScriptRegion scriptRegion)
{
return completionHandler.GetCompletionsInFileAsync(
GetScriptFile(scriptRegion),
scriptRegion.StartLineNumber,
scriptRegion.StartColumnNumber,
CancellationToken.None);
}
[Fact]
public async Task CompletesCommandInFile()
{
(_, IEnumerable<CompletionItem> results) = await GetCompletionResultsAsync(CompleteCommandInFile.SourceDetails);
CompletionItem actual = Assert.Single(results);
Assert.Equal(CompleteCommandInFile.ExpectedCompletion, actual);
}
[Fact]
public async Task CompletesCommandFromModule()
{
(_, IEnumerable<CompletionItem> results) = await GetCompletionResultsAsync(CompleteCommandFromModule.SourceDetails);
CompletionItem actual = Assert.Single(results);
// NOTE: The tooltip varies across PowerShell and OS versions, so we ignore it.
Assert.Equal(CompleteCommandFromModule.ExpectedCompletion, actual with { Detail = "" });
Assert.StartsWith(CompleteCommandFromModule.GetRandomDetail, actual.Detail);
}
[SkippableFact]
public async Task CompletesTypeName()
{
Skip.If(VersionUtils.PSEdition == "Desktop", "Windows PowerShell has trouble with this test right now.");
(_, IEnumerable<CompletionItem> results) = await GetCompletionResultsAsync(CompleteTypeName.SourceDetails);
CompletionItem actual = Assert.Single(results);
if (VersionUtils.IsNetCore)
{
Assert.Equal(CompleteTypeName.ExpectedCompletion, actual);
}
else
{
// Windows PowerShell shows ArrayList as a Class.
Assert.Equal(CompleteTypeName.ExpectedCompletion with
{
Kind = CompletionItemKind.Class,
Detail = "System.Collections.ArrayList"
}, actual);
}
}
[SkippableFact]
public async Task CompletesNamespace()
{
Skip.If(VersionUtils.PSEdition == "Desktop", "Windows PowerShell has trouble with this test right now.");
(_, IEnumerable<CompletionItem> results) = await GetCompletionResultsAsync(CompleteNamespace.SourceDetails);
CompletionItem actual = Assert.Single(results);
Assert.Equal(CompleteNamespace.ExpectedCompletion, actual);
}
[Fact]
public async Task CompletesVariableInFile()
{
(_, IEnumerable<CompletionItem> results) = await GetCompletionResultsAsync(CompleteVariableInFile.SourceDetails);
CompletionItem actual = Assert.Single(results);
Assert.Equal(CompleteVariableInFile.ExpectedCompletion, actual);
}
[Fact]
public async Task CompletesAttributeValue()
{
(_, IEnumerable<CompletionItem> results) = await GetCompletionResultsAsync(CompleteAttributeValue.SourceDetails);
// NOTE: Since the completions come through un-ordered from PowerShell, their SortText
// (which has an index prepended from the original order) will mis-match our assumed
// order; hence we ignore it.
Assert.Collection(results.OrderBy(c => c.Label),
actual => Assert.Equal(actual with { Data = null, SortText = null }, CompleteAttributeValue.ExpectedCompletion1),
actual => Assert.Equal(actual with { Data = null, SortText = null }, CompleteAttributeValue.ExpectedCompletion2),
actual => Assert.Equal(actual with { Data = null, SortText = null }, CompleteAttributeValue.ExpectedCompletion3));
}
[Fact]
public async Task CompletesFilePath()
{
(_, IEnumerable<CompletionItem> results) = await GetCompletionResultsAsync(CompleteFilePath.SourceDetails);
Assert.NotEmpty(results);
CompletionItem actual = results.First();
// Paths are system dependent so we ignore the text and just check the type and range.
Assert.Equal(actual.TextEdit.TextEdit with { NewText = "" }, CompleteFilePath.ExpectedEdit);
Assert.All(results, r => Assert.True(r.Kind is CompletionItemKind.File or CompletionItemKind.Folder));
}
// TODO: These should be an integration tests at a higher level if/when https://github.com/PowerShell/PowerShell/pull/25108 is merged. As of today, we can't actually test this in the PS engine currently.
[Fact]
public void CanExtractTypeAndDescriptionFromTooltip()
{
string expectedType = "[string]";
string expectedDescription = "Test String";
string paramName = "TestParam";
string testHelp = $"{expectedType} {paramName} - {expectedDescription}";
Assert.True(PsesCompletionHandler.TryExtractType(testHelp, paramName, out string type, out string description));
Assert.Equal(expectedType, type);
Assert.Equal(expectedDescription, description);
}
[Fact]
public void CanExtractTypeFromTooltip()
{
string expectedType = "[string]";
string testHelp = $"{expectedType}";
Assert.True(PsesCompletionHandler.TryExtractType(testHelp, string.Empty, out string type, out string description));
Assert.Null(description);
Assert.Equal(expectedType, type);
}
}
}

View file

@ -0,0 +1,200 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation.Language;
using Microsoft.PowerShell.EditorServices.Handlers;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using Xunit;
namespace PowerShellEditorServices.Test.Language
{
public class SemanticTokenTest
{
[Fact]
public void TokenizesFunctionElements()
{
const string text = @"
function Get-Sum {
param( [parameter()] [int]$a, [int]$b )
:loopLabel while (0) {break loopLabel}
return $a + $b
}
";
ScriptFile scriptFile = ScriptFile.Create(
// Use any absolute path. Even if it doesn't exist.
DocumentUri.FromFileSystemPath(Path.Combine(Path.GetTempPath(), "TestFile.ps1")),
text,
Version.Parse("5.0"));
foreach (Token t in scriptFile.ScriptTokens)
{
List<SemanticToken> mappedTokens = new(PsesSemanticTokensHandler.ConvertToSemanticTokens(t));
switch (t.Text)
{
case "function":
case "param":
case "return":
case "while":
case "break":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Keyword == sToken.Type);
break;
case "parameter":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Decorator == sToken.Type);
break;
case "0":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Number == sToken.Type);
break;
case ":loopLabel":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Label == sToken.Type);
break;
case "loopLabel":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Property == sToken.Type);
break;
case "$a":
case "$b":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Variable == sToken.Type);
break;
case "[int]":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Type == sToken.Type);
break;
case "+":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Operator == sToken.Type);
break;
}
}
}
[Fact]
public void TokenizesStringExpansion()
{
const string text = "Write-Host \"$(Test-Property Get-Whatever) $(Get-Whatever)\"";
ScriptFile scriptFile = ScriptFile.Create(
// Use any absolute path. Even if it doesn't exist.
DocumentUri.FromFileSystemPath(Path.Combine(Path.GetTempPath(), "TestFile.ps1")),
text,
Version.Parse("5.0"));
Token commandToken = scriptFile.ScriptTokens[0];
List<SemanticToken> mappedTokens = new(PsesSemanticTokensHandler.ConvertToSemanticTokens(commandToken));
Assert.Single(mappedTokens, sToken => SemanticTokenType.Function == sToken.Type);
Token stringExpandableToken = scriptFile.ScriptTokens[1];
mappedTokens = new List<SemanticToken>(PsesSemanticTokensHandler.ConvertToSemanticTokens(stringExpandableToken));
Assert.Collection(mappedTokens,
sToken => Assert.Equal(SemanticTokenType.Function, sToken.Type),
sToken => Assert.Equal(SemanticTokenType.Function, sToken.Type)
);
}
[Fact]
public void RecognizesTokensWithAsterisk()
{
const string text = @"
function Get-A*A {
}
Get-A*A
";
ScriptFile scriptFile = ScriptFile.Create(
// Use any absolute path. Even if it doesn't exist.
DocumentUri.FromFileSystemPath(Path.Combine(Path.GetTempPath(), "TestFile.ps1")),
text,
Version.Parse("5.0"));
foreach (Token t in scriptFile.ScriptTokens)
{
List<SemanticToken> mappedTokens = new(PsesSemanticTokensHandler.ConvertToSemanticTokens(t));
switch (t.Text)
{
case "function":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Keyword == sToken.Type);
break;
case "Get-A*A":
if (t.TokenFlags.HasFlag(TokenFlags.CommandName))
{
Assert.Single(mappedTokens, sToken => SemanticTokenType.Function == sToken.Type);
}
break;
}
}
}
[Fact]
public void RecognizesArrayPropertyInExpandableString()
{
const string text = "\"$(@($Array).Count) OtherText\"";
ScriptFile scriptFile = ScriptFile.Create(
// Use any absolute path. Even if it doesn't exist.
DocumentUri.FromFileSystemPath(Path.Combine(Path.GetTempPath(), "TestFile.ps1")),
text,
Version.Parse("5.0"));
foreach (Token t in scriptFile.ScriptTokens)
{
List<SemanticToken> mappedTokens = new(PsesSemanticTokensHandler.ConvertToSemanticTokens(t));
switch (t.Text)
{
case "$Array":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Variable == sToken.Type);
break;
case "Count":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Property == sToken.Type);
break;
}
}
}
[Fact]
public void RecognizesCurlyQuotedString()
{
const string text = "“^[-'a-z]*”";
ScriptFile scriptFile = ScriptFile.Create(
// Use any absolute path. Even if it doesn't exist.
DocumentUri.FromFileSystemPath(Path.Combine(Path.GetTempPath(), "TestFile.ps1")),
text,
Version.Parse("5.0"));
List<SemanticToken> mappedTokens = new(PsesSemanticTokensHandler.ConvertToSemanticTokens(scriptFile.ScriptTokens[0]));
Assert.Single(mappedTokens, sToken => SemanticTokenType.String == sToken.Type);
}
[Fact]
public void RecognizeEnum()
{
const string text = @"
enum MyEnum{
one
two
three
}
";
ScriptFile scriptFile = ScriptFile.Create(
// Use any absolute path. Even if it doesn't exist.
DocumentUri.FromFileSystemPath(Path.Combine(Path.GetTempPath(), "TestFile.ps1")),
text,
Version.Parse("5.0"));
foreach (Token t in scriptFile.ScriptTokens)
{
List<SemanticToken> mappedTokens = new(PsesSemanticTokensHandler.ConvertToSemanticTokens(t));
switch (t.Text)
{
case "enum":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Keyword == sToken.Type);
break;
case "MyEnum":
case "one":
case "two":
case "three":
Assert.Single(mappedTokens, sToken => SemanticTokenType.Property == sToken.Type);
break;
}
}
}
}
}

View file

@ -0,0 +1,993 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.PowerShell.EditorServices.Services;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility;
using Microsoft.PowerShell.EditorServices.Services.Symbols;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
using Microsoft.PowerShell.EditorServices.Test;
using Microsoft.PowerShell.EditorServices.Test.Shared;
using Microsoft.PowerShell.EditorServices.Test.Shared.Definition;
using Microsoft.PowerShell.EditorServices.Test.Shared.Occurrences;
using Microsoft.PowerShell.EditorServices.Test.Shared.ParameterHint;
using Microsoft.PowerShell.EditorServices.Test.Shared.References;
using Microsoft.PowerShell.EditorServices.Test.Shared.SymbolDetails;
using Microsoft.PowerShell.EditorServices.Test.Shared.Symbols;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using Xunit;
namespace PowerShellEditorServices.Test.Language
{
[Trait("Category", "Symbols")]
public class SymbolsServiceTests : IAsyncLifetime
{
private PsesInternalHost psesHost;
private WorkspaceService workspace;
private SymbolsService symbolsService;
private static readonly bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public async Task InitializeAsync()
{
psesHost = await PsesHostFactory.Create(NullLoggerFactory.Instance);
workspace = new WorkspaceService(NullLoggerFactory.Instance);
workspace.WorkspaceFolders.Add(new WorkspaceFolder
{
Uri = DocumentUri.FromFileSystemPath(TestUtilities.GetSharedPath("References"))
});
symbolsService = new SymbolsService(
NullLoggerFactory.Instance,
psesHost,
psesHost,
workspace,
new ConfigurationService());
}
public async Task DisposeAsync()
{
psesHost.StopAsync();
CommandHelpers.s_cmdletToAliasCache.Clear();
CommandHelpers.s_aliasToCmdletCache.Clear();
}
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<ParameterSetSignatures> GetParamSetSignatures(ScriptRegion scriptRegion)
{
return symbolsService.FindParameterSetsInFileAsync(
GetScriptFile(scriptRegion),
scriptRegion.StartLineNumber,
scriptRegion.StartColumnNumber);
}
private async Task<IEnumerable<SymbolReference>> GetDefinitions(ScriptRegion scriptRegion)
{
ScriptFile scriptFile = GetScriptFile(scriptRegion);
// TODO: We should just use the name to find it.
SymbolReference symbol = SymbolsService.FindSymbolAtLocation(
scriptFile,
scriptRegion.StartLineNumber,
scriptRegion.StartColumnNumber);
Assert.NotNull(symbol);
IEnumerable<SymbolReference> symbols =
await symbolsService.GetDefinitionOfSymbolAsync(scriptFile, symbol);
return symbols.OrderBy((i) => i.ScriptRegion.ToRange().Start);
}
private async Task<SymbolReference> GetDefinition(ScriptRegion scriptRegion)
{
IEnumerable<SymbolReference> definitions = await GetDefinitions(scriptRegion);
return definitions.FirstOrDefault();
}
private async Task<IEnumerable<SymbolReference>> GetReferences(ScriptRegion scriptRegion)
{
ScriptFile scriptFile = GetScriptFile(scriptRegion);
SymbolReference symbol = SymbolsService.FindSymbolAtLocation(
scriptFile,
scriptRegion.StartLineNumber,
scriptRegion.StartColumnNumber);
Assert.NotNull(symbol);
IEnumerable<SymbolReference> symbols =
await symbolsService.ScanForReferencesOfSymbolAsync(symbol);
return symbols.OrderBy((i) => i.ScriptRegion.ToRange().Start);
}
private IEnumerable<SymbolReference> GetOccurrences(ScriptRegion scriptRegion)
{
return SymbolsService
.FindOccurrencesInFile(
GetScriptFile(scriptRegion),
scriptRegion.StartLineNumber,
scriptRegion.StartColumnNumber)
.OrderBy(symbol => symbol.ScriptRegion.ToRange().Start)
.ToArray();
}
private IEnumerable<SymbolReference> FindSymbolsInFile(ScriptRegion scriptRegion)
{
return symbolsService
.FindSymbolsInFile(GetScriptFile(scriptRegion))
.OrderBy(symbol => symbol.ScriptRegion.ToRange().Start);
}
[Fact]
public async Task FindsParameterHintsOnCommand()
{
// TODO: Fix signatures to use parameters, not sets.
ParameterSetSignatures signatures = await GetParamSetSignatures(FindsParameterSetsOnCommandData.SourceDetails);
Assert.NotNull(signatures);
Assert.Equal("Get-Process", signatures.CommandName);
Assert.Equal(6, signatures.Signatures.Length);
}
[Fact]
public async Task FindsCommandForParamHintsWithSpaces()
{
ParameterSetSignatures signatures = await GetParamSetSignatures(FindsParameterSetsOnCommandWithSpacesData.SourceDetails);
Assert.NotNull(signatures);
Assert.Equal("Write-Host", signatures.CommandName);
Assert.Single(signatures.Signatures);
}
[Fact]
public async Task FindsFunctionDefinition()
{
SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionData.SourceDetails);
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]
public async Task FindsFunctionDefinitionForAlias()
{
// TODO: Eventually we should get the aliases through the AST instead of relying on them
// being defined in the runspace.
await psesHost.ExecutePSCommandAsync(
new PSCommand().AddScript("Set-Alias -Name My-Alias -Value My-Function"),
CancellationToken.None);
SymbolReference symbol = await GetDefinition(FindsFunctionDefinitionOfAliasData.SourceDetails);
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()
{
IEnumerable<SymbolReference> symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails);
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);
},
(i) =>
{
Assert.Equal("fn My-Function", i.Id);
Assert.Equal("$Function:My-Function", i.Name);
Assert.Equal(SymbolType.Function, i.Type);
Assert.False(i.IsDeclaration);
});
}
[Fact]
public async Task FindsReferenceAcrossMultiRootWorkspace()
{
workspace.WorkspaceFolders = new[] { "Debugging", "ParameterHints", "SymbolDetails" }
.Select(i => new WorkspaceFolder
{
Uri = DocumentUri.FromFileSystemPath(TestUtilities.GetSharedPath(i))
}).ToList();
SymbolReference symbol = new("fn Get-Process", SymbolType.Function);
IEnumerable<SymbolReference> symbols = await symbolsService.ScanForReferencesOfSymbolAsync(symbol);
Assert.Collection(symbols.OrderBy(i => i.FilePath),
i => Assert.EndsWith("VariableTest.ps1", i.FilePath),
i => Assert.EndsWith("ParamHints.ps1", i.FilePath),
i => Assert.EndsWith("SymbolDetails.ps1", i.FilePath));
}
[Fact]
public async Task FindsReferencesOnFunctionIncludingAliases()
{
// TODO: Same as in FindsFunctionDefinitionForAlias.
await psesHost.ExecutePSCommandAsync(
new PSCommand().AddScript("Set-Alias -Name My-Alias -Value My-Function"),
CancellationToken.None);
IEnumerable<SymbolReference> symbols = await GetReferences(FindsReferencesOnFunctionData.SourceDetails);
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);
},
(i) => AssertIsRegion(i.NameRegion, 22, 29, 22, 52));
}
[Fact]
public async Task FindsFunctionDefinitionInWorkspace()
{
IEnumerable<SymbolReference> symbols = await GetDefinitions(FindsFunctionDefinitionInWorkspaceData.SourceDetails);
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 FindsVariableDefinition()
{
IEnumerable<SymbolReference> definitions = await GetDefinitions(FindsVariableDefinitionData.SourceDetails);
SymbolReference symbol = Assert.Single(definitions); // Even though it's re-assigned
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 FindsTypedVariableDefinition()
{
IEnumerable<SymbolReference> definitions = await GetDefinitions(FindsTypedVariableDefinitionData.SourceDetails);
SymbolReference symbol = Assert.Single(definitions);
Assert.Equal("var hello", symbol.Id);
Assert.Equal("$hello", symbol.Name);
Assert.Equal(SymbolType.Variable, symbol.Type);
Assert.True(symbol.IsDeclaration);
AssertIsRegion(symbol.NameRegion, 24, 9, 24, 15);
}
[Fact]
public async Task FindsReferencesOnVariable()
{
IEnumerable<SymbolReference> symbols = await GetReferences(FindsReferencesOnVariableData.SourceDetails);
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 void FindsOccurrencesOnFunction()
{
IEnumerable<SymbolReference> 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);
},
(i) =>
{
Assert.Equal("fn My-Function", i.Id);
Assert.Equal("$Function:My-Function", i.Name);
Assert.Equal(SymbolType.Function, i.Type);
Assert.False(i.IsDeclaration);
});
}
[Fact]
public void FindsOccurrencesOnParameter()
{
IEnumerable<SymbolReference> 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 async Task FindsReferencesOnCommandWithAlias()
{
// NOTE: This doesn't use GetOccurrences as it's testing for aliases.
IEnumerable<SymbolReference> symbols = await GetReferences(FindsReferencesOnBuiltInCommandWithAliasData.SourceDetails);
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 async Task FindsClassDefinition()
{
SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.ClassSourceDetails);
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 FindsReferencesOnClass()
{
IEnumerable<SymbolReference> symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.ClassSourceDetails);
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 FindsEnumDefinition()
{
SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.EnumSourceDetails);
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 FindsReferencesOnEnum()
{
IEnumerable<SymbolReference> symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumSourceDetails);
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 FindsTypeExpressionDefinition()
{
SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeExpressionSourceDetails);
AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12);
Assert.Equal("type MyEnum", symbol.Id);
Assert.Equal("enum MyEnum { }", symbol.Name);
Assert.True(symbol.IsDeclaration);
}
[Fact]
public async Task FindsReferencesOnTypeExpression()
{
IEnumerable<SymbolReference> symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeExpressionSourceDetails);
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 async Task FindsTypeConstraintDefinition()
{
SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.TypeConstraintSourceDetails);
AssertIsRegion(symbol.NameRegion, 39, 6, 39, 12);
Assert.Equal("type MyEnum", symbol.Id);
Assert.Equal("enum MyEnum { }", symbol.Name);
Assert.True(symbol.IsDeclaration);
}
[Fact]
public async Task FindsReferencesOnTypeConstraint()
{
IEnumerable<SymbolReference> symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.TypeConstraintSourceDetails);
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);
});
}
[Fact]
public void FindsOccurrencesOnTypeConstraint()
{
IEnumerable<SymbolReference> 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);
});
}
[Fact]
public async Task FindsConstructorDefinition()
{
IEnumerable<SymbolReference> symbols = await GetDefinitions(FindsTypeSymbolsDefinitionData.ConstructorSourceDetails);
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));
Assert.Equal(symbols, GetOccurrences(FindsOccurrencesOnTypeSymbolsData.ConstructorSourceDetails));
}
[Fact]
public async Task FindsMethodDefinition()
{
IEnumerable<SymbolReference> symbols = await GetDefinitions(FindsTypeSymbolsDefinitionData.MethodSourceDetails);
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);
});
}
[Fact]
public async Task FindsReferencesOnMethod()
{
IEnumerable<SymbolReference> symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.MethodSourceDetails);
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 async Task FindsPropertyDefinition()
{
SymbolReference symbol = await GetDefinition(FindsTypeSymbolsDefinitionData.PropertySourceDetails);
Assert.Equal("prop SomePropWithDefault", symbol.Id);
Assert.Equal("[string] $SomePropWithDefault", symbol.Name);
Assert.Equal(SymbolType.Property, symbol.Type);
Assert.True(symbol.IsDeclaration);
}
[Fact]
public async Task FindsReferencesOnProperty()
{
IEnumerable<SymbolReference> symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.PropertySourceDetails);
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<SymbolReference> 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);
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);
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<SymbolReference> symbols = await GetReferences(FindsReferencesOnTypeSymbolsData.EnumMemberSourceDetails);
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));
}
[Fact]
public async Task FindsDetailsForBuiltInCommand()
{
SymbolDetails symbolDetails = await symbolsService.FindSymbolDetailsAtLocationAsync(
GetScriptFile(FindsDetailsForBuiltInCommandData.SourceDetails),
FindsDetailsForBuiltInCommandData.SourceDetails.StartLineNumber,
FindsDetailsForBuiltInCommandData.SourceDetails.StartColumnNumber,
CancellationToken.None);
Assert.Equal("Extracts files from a specified archive (zipped) file.", symbolDetails.Documentation);
}
[Fact]
public void FindsSymbolsInFile()
{
IEnumerable<SymbolReference> 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 script:AFunction ()", symbol.Name);
Assert.True(symbol.IsDeclaration);
Assert.Equal(2, GetOccurrences(symbol.NameRegion).Count());
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, 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, 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, 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, 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, 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, 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, i => i.Type == SymbolType.EnumMember);
Assert.Equal("prop AValue", symbol.Id);
Assert.Equal("AValue", symbol.Name);
Assert.True(symbol.IsDeclaration);
// There should be no region symbols unless the provider has been registered.
Assert.DoesNotContain(symbols, i => i.Type == SymbolType.Region);
}
[Fact]
public void FindsRegionsInFile()
{
symbolsService.TryRegisterDocumentSymbolProvider(new RegionDocumentSymbolProvider());
IEnumerable<SymbolReference> symbols = FindSymbolsInFile(FindSymbolsInMultiSymbolFile.SourceDetails);
Assert.Collection(symbols.Where(i => i.Type == SymbolType.Region),
(i) =>
{
Assert.Equal("region find me outer", i.Id);
Assert.Equal("#region find me outer", i.Name);
Assert.Equal(SymbolType.Region, i.Type);
Assert.True(i.IsDeclaration);
AssertIsRegion(i.NameRegion, 51, 1, 51, 22);
AssertIsRegion(i.ScriptRegion, 51, 1, 55, 11);
},
(i) =>
{
Assert.Equal("region find me inner", i.Id);
Assert.Equal("#region find me inner", i.Name);
Assert.Equal(SymbolType.Region, i.Type);
Assert.True(i.IsDeclaration);
AssertIsRegion(i.NameRegion, 52, 1, 52, 22);
AssertIsRegion(i.ScriptRegion, 52, 1, 54, 11);
});
}
[Fact]
public void FindsSymbolsWithNewLineInFile()
{
IEnumerable<SymbolReference> symbols = FindSymbolsInFile(FindSymbolsInNewLineSymbolFile.SourceDetails);
SymbolReference symbol = Assert.Single(symbols, 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, 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, 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, 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, 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, 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, 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);
}
[SkippableFact]
public void FindsSymbolsInDSCFile()
{
Skip.If(!isWindows, "DSC only works properly on Windows.");
IEnumerable<SymbolReference> symbols = FindSymbolsInFile(FindSymbolsInDSCFile.SourceDetails);
SymbolReference symbol = Assert.Single(symbols, i => i.Type == SymbolType.Configuration);
// The prefix "dsc" is added for sorting reasons.
Assert.Equal("dsc AConfiguration", symbol.Id);
Assert.Equal(2, symbol.ScriptRegion.StartLineNumber);
Assert.Equal(1, symbol.ScriptRegion.StartColumnNumber);
}
[Fact]
public void FindsSymbolsInPesterFile()
{
IEnumerable<PesterSymbolReference> symbols = FindSymbolsInFile(FindSymbolsInPesterFile.SourceDetails).OfType<PesterSymbolReference>();
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 FindsSymbolsInPSKoansFile()
{
IEnumerable<PesterSymbolReference> symbols = FindSymbolsInFile(FindSymbolsInPSKoansFile.SourceDetails).OfType<PesterSymbolReference>();
// Pester symbols are properly tested in FindsSymbolsInPesterFile so only counting to make sure they appear
Assert.Equal(7, symbols.Count(i => i.Type == SymbolType.Function));
}
[Fact]
public void FindsSymbolsInPSDFile()
{
IEnumerable<SymbolReference> 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()
{
IEnumerable<SymbolReference> symbolsResult = FindSymbolsInFile(FindSymbolsInNoSymbolsFile.SourceDetails);
Assert.Empty(symbolsResult);
}
}
}

View file

@ -0,0 +1,351 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.IO;
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
using OmniSharp.Extensions.LanguageServer.Protocol;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using Xunit;
namespace PowerShellEditorServices.Test.Language
{
public class TokenOperationsTests
{
/// <summary>
/// Helper method to create a stub script file and then call FoldableRegions
/// </summary>
private static FoldingReference[] GetRegions(string text)
{
ScriptFile scriptFile = ScriptFile.Create(
// Use any absolute path. Even if it doesn't exist.
DocumentUri.FromFileSystemPath(Path.Combine(Path.GetTempPath(), "TestFile.ps1")),
text,
Version.Parse("5.0"));
FoldingReference[] result = TokenOperations.FoldableReferences(scriptFile.ScriptTokens).ToArray();
// The foldable regions need to be deterministic for testing so sort the array.
Array.Sort(result);
return result;
}
/// <summary>
/// Helper method to create FoldingReference objects with less typing
/// </summary>
private static FoldingReference CreateFoldingReference(int startLine, int startCharacter, int endLine, int endCharacter, FoldingRangeKind? matchKind)
{
return new FoldingReference
{
StartLine = startLine,
StartCharacter = startCharacter,
EndLine = endLine,
EndCharacter = endCharacter,
Kind = matchKind
};
}
// This PowerShell script will exercise all of the
// folding regions and regions which should not be
// detected. Due to file encoding this could be CLRF or LF line endings
private const string allInOneScript =
@"#Region This should fold
<#
Nested different comment types. This should fold
#>
#EndRegion
# region This should not fold due to whitespace
$shouldFold = $false
# endRegion
function short-func-not-fold {};
<#
.SYNOPSIS
This whole comment block should fold, not just the SYNOPSIS
.EXAMPLE
This whole comment block should fold, not just the EXAMPLE
#>
function New-VSCodeShouldFold {
<#
.SYNOPSIS
This whole comment block should fold, not just the SYNOPSIS
.EXAMPLE
This whole comment block should fold, not just the EXAMPLE
#>
$I = @'
herestrings should fold
'@
# This won't confuse things
Get-Command -Param @I
$I = @""
double quoted herestrings should also fold
""@
# this won't be folded
# This block of comments should be foldable as a single block
# This block of comments should be foldable as a single block
# This block of comments should be foldable as a single block
#region This fools the indentation folding.
Write-Host ""Hello""
#region Nested regions should be foldable
Write-Host ""Hello""
# comment1
Write-Host ""Hello""
#endregion
Write-Host ""Hello""
# comment2
Write-Host ""Hello""
#endregion
$c = {
Write-Host ""Script blocks should be foldable""
}
# Array fools indentation folding
$d = @(
'should fold1',
'should fold2'
)
}
# Make sure contiguous comment blocks can be folded properly
# Comment Block 1
# Comment Block 1
# Comment Block 1
#region Comment Block 3
# Comment Block 2
# Comment Block 2
# Comment Block 2
$something = $true
#endregion Comment Block 3
# What about anonymous variable assignment
${this
is
valid} = 5
#RegIon This should fold due to casing
$foo = 'bar'
#EnDReGion
";
private readonly FoldingReference[] expectedAllInOneScriptFolds = {
CreateFoldingReference(0, 0, 4, 10, FoldingRangeKind.Region),
CreateFoldingReference(1, 0, 3, 2, FoldingRangeKind.Comment),
CreateFoldingReference(10, 0, 15, 2, FoldingRangeKind.Comment),
CreateFoldingReference(16, 30, 63, 1, null),
CreateFoldingReference(17, 0, 22, 2, FoldingRangeKind.Comment),
CreateFoldingReference(23, 7, 26, 2, null),
CreateFoldingReference(31, 5, 34, 2, null),
CreateFoldingReference(38, 2, 40, 0, FoldingRangeKind.Comment),
CreateFoldingReference(42, 2, 52, 14, FoldingRangeKind.Region),
CreateFoldingReference(44, 4, 48, 14, FoldingRangeKind.Region),
CreateFoldingReference(54, 7, 56, 3, null),
CreateFoldingReference(59, 7, 62, 3, null),
CreateFoldingReference(67, 0, 69, 0, FoldingRangeKind.Comment),
CreateFoldingReference(70, 0, 75, 26, FoldingRangeKind.Region),
CreateFoldingReference(71, 0, 73, 0, FoldingRangeKind.Comment),
CreateFoldingReference(78, 0, 80, 6, null),
};
/// <summary>
/// Assertion helper to compare two FoldingReference arrays.
/// </summary>
private static void AssertFoldingReferenceArrays(
FoldingReference[] expected,
FoldingReference[] actual)
{
for (int index = 0; index < expected.Length; index++)
{
Assert.Equal(expected[index], actual[index]);
}
Assert.Equal(expected.Length, actual.Length);
}
[Trait("Category", "Folding")]
[Fact]
public void LanguageServiceFindsFoldablRegionsWithLF()
{
// Remove and CR characters
string testString = allInOneScript.Replace("\r", "");
// Ensure that there are no CR characters in the string
Assert.False(testString.Contains("\r\n"), "CRLF should not be present in the test string");
FoldingReference[] result = GetRegions(testString);
AssertFoldingReferenceArrays(expectedAllInOneScriptFolds, result);
}
[Trait("Category", "Folding")]
[Fact]
public void LanguageServiceFindsFoldablRegionsWithCRLF()
{
// The Foldable regions should be the same regardless of line ending type
// Enforce CRLF line endings, if none exist
string testString = allInOneScript;
if (!testString.Contains("\r\n"))
{
testString = testString.Replace("\n", "\r\n");
}
// Ensure that there are CRLF characters in the string
Assert.True(testString.IndexOf("\r\n") != -1, "CRLF should be present in the teststring");
FoldingReference[] result = GetRegions(testString);
AssertFoldingReferenceArrays(expectedAllInOneScriptFolds, result);
}
[Trait("Category", "Folding")]
[Fact]
public void LanguageServiceFindsFoldablRegionsWithMismatchedRegions()
{
const string testString =
@"#endregion should not fold - mismatched
#region This should fold
$something = 'foldable'
#endregion
#region should not fold - mismatched
";
FoldingReference[] expectedFolds = {
CreateFoldingReference(2, 0, 4, 10, FoldingRangeKind.Region)
};
FoldingReference[] result = GetRegions(testString);
AssertFoldingReferenceArrays(expectedFolds, result);
}
[Trait("Category", "Folding")]
[Fact]
public void LanguageServiceFindsFoldablRegionsWithDuplicateRegions()
{
const string testString =
@"# This script causes duplicate/overlapping ranges due to the `(` and `{` characters
$AnArray = @(Get-ChildItem -Path C:\ -Include *.ps1 -File).Where({
$_.FullName -ne 'foo'}).ForEach({
# Do Something
})
";
FoldingReference[] expectedFolds = {
CreateFoldingReference(1, 64, 2, 27, null),
CreateFoldingReference(2, 35, 4, 2, null)
};
FoldingReference[] result = GetRegions(testString);
AssertFoldingReferenceArrays(expectedFolds, result);
}
// This tests that token matching { -> }, @{ -> } and
// ( -> ), @( -> ) and $( -> ) does not confuse the folder
[Trait("Category", "Folding")]
[Fact]
public void LanguageServiceFindsFoldablRegionsWithSameEndToken()
{
const string testString =
@"foreach ($1 in $2) {
$x = @{
'abc' = 'def'
}
}
$y = $(
$arr = @('1', '2'); Write-Host ($arr)
)
";
FoldingReference[] expectedFolds = {
CreateFoldingReference(0, 19, 5, 1, null),
CreateFoldingReference(2, 9, 4, 5, null),
CreateFoldingReference(7, 5, 9, 1, null)
};
FoldingReference[] result = GetRegions(testString);
AssertFoldingReferenceArrays(expectedFolds, result);
}
// A simple PowerShell Classes test
[Trait("Category", "Folding")]
[Fact]
public void LanguageServiceFindsFoldablRegionsWithClasses()
{
const string testString =
@"class TestClass {
[string[]] $TestProperty = @(
'first',
'second',
'third')
[string] TestMethod() {
return $this.TestProperty[0]
}
}
";
FoldingReference[] expectedFolds = {
CreateFoldingReference(0, 16, 9, 1, null),
CreateFoldingReference(1, 31, 4, 16, null),
CreateFoldingReference(6, 26, 8, 5, null)
};
FoldingReference[] result = GetRegions(testString);
AssertFoldingReferenceArrays(expectedFolds, result);
}
// This tests DSC style keywords and param blocks
[Trait("Category", "Folding")]
[Fact]
public void LanguageServiceFindsFoldablRegionsWithDSC()
{
const string testString =
@"Configuration Example
{
param
(
[Parameter()]
[System.String[]]
$NodeName = 'localhost',
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.Management.Automation.PSCredential]
$Credential
)
Import-DscResource -Module ActiveDirectoryCSDsc
Node $AllNodes.NodeName
{
WindowsFeature ADCS-Cert-Authority
{
Ensure = 'Present'
Name = 'ADCS-Cert-Authority'
}
AdcsCertificationAuthority CertificateAuthority
{
IsSingleInstance = 'Yes'
Ensure = 'Present'
Credential = $Credential
CAType = 'EnterpriseRootCA'
DependsOn = '[WindowsFeature]ADCS-Cert-Authority'
}
}
}
";
FoldingReference[] expectedFolds = {
CreateFoldingReference(1, 0, 33, 1, null),
CreateFoldingReference(3, 4, 12, 5, null),
CreateFoldingReference(17, 4, 32, 5, null),
CreateFoldingReference(19, 8, 22, 9, null),
CreateFoldingReference(25, 8, 31, 9, null)
};
FoldingReference[] result = GetRegions(testString);
AssertFoldingReferenceArrays(expectedFolds, result);
}
}
}