Skip to content

Commit 3d6cd41

Browse files
committed
Fix crash and hang issues with Pester CodeLens feature
This change fixes a couple of issues with the new Pester CodeLens feature. One is that the "Describe" function is not recognized when it is not cased exactly like that and the other is that the language server has an internal error that causes it to hang when typing Describe blocks into a script. Resolves PowerShell/vscode-powershell#850 Resolves PowerShell/vscode-powershell#851 Resolves PowerShell/vscode-powershell#852
1 parent 7159276 commit 3d6cd41

File tree

2 files changed

+128
-34
lines changed

2 files changed

+128
-34
lines changed

src/PowerShellEditorServices.Host/CodeLens/PesterCodeLensProvider.cs

+7-14
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,9 @@ public PesterCodeLensProvider(EditorSession editorSession)
2727
}
2828

2929
private IEnumerable<CodeLens> GetPesterLens(
30-
SymbolReference symbol,
30+
PesterSymbolReference pesterSymbol,
3131
ScriptFile scriptFile)
3232
{
33-
// Trim the Describe "" { from the symbol name
34-
int startQuoteIndex = symbol.SourceLine.IndexOfAny(QuoteChars) + 1;
35-
int endQuoteIndex = symbol.SourceLine.LastIndexOfAny(QuoteChars);
36-
37-
string describeBlockName =
38-
symbol.SourceLine.Substring(
39-
startQuoteIndex,
40-
endQuoteIndex - startQuoteIndex);
41-
4233
var clientCommands = new ClientCommand[]
4334
{
4435
new ClientCommand(
@@ -48,7 +39,7 @@ private IEnumerable<CodeLens> GetPesterLens(
4839
{
4940
scriptFile.ClientFilePath,
5041
false, // Don't debug
51-
describeBlockName,
42+
pesterSymbol.TestName,
5243
}),
5344

5445
new ClientCommand(
@@ -58,7 +49,7 @@ private IEnumerable<CodeLens> GetPesterLens(
5849
{
5950
scriptFile.ClientFilePath,
6051
true, // Run in debugger
61-
describeBlockName,
52+
pesterSymbol.TestName,
6253
}),
6354
};
6455

@@ -68,7 +59,7 @@ private IEnumerable<CodeLens> GetPesterLens(
6859
new CodeLens(
6960
this,
7061
scriptFile,
71-
symbol.ScriptRegion,
62+
pesterSymbol.ScriptRegion,
7263
command));
7364
}
7465

@@ -80,8 +71,10 @@ public CodeLens[] ProvideCodeLenses(ScriptFile scriptFile)
8071

8172
var lenses =
8273
symbols
83-
.Where(s => s.SymbolName.StartsWith("Describe"))
74+
.OfType<PesterSymbolReference>()
75+
.Where(s => s.Command == PesterCommandType.Describe)
8476
.SelectMany(s => this.GetPesterLens(s, scriptFile))
77+
.Where(codeLens => codeLens != null)
8578
.ToArray();
8679

8780
return lenses;

src/PowerShellEditorServices/Symbols/PesterDocumentSymbolProvider.cs

+121-20
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ namespace Microsoft.PowerShell.EditorServices.Symbols
1616
/// </summary>
1717
public class PesterDocumentSymbolProvider : FeatureProviderBase, IDocumentSymbolProvider
1818
{
19-
private static char[] DefinitionTrimChars = new char[] { ' ', '{' };
2019

2120
IEnumerable<SymbolReference> IDocumentSymbolProvider.ProvideDocumentSymbols(
2221
ScriptFile scriptFile)
@@ -30,33 +29,135 @@ IEnumerable<SymbolReference> IDocumentSymbolProvider.ProvideDocumentSymbols(
3029

3130
var commandAsts = scriptFile.ScriptAst.FindAll(ast =>
3231
{
33-
switch ((ast as CommandAst)?.GetCommandName()?.ToLower())
34-
{
35-
case "describe":
36-
case "context":
37-
case "it":
38-
return true;
39-
40-
default:
41-
return false;
42-
}
32+
CommandAst commandAst = ast as CommandAst;
33+
34+
return
35+
commandAst != null &&
36+
PesterSymbolReference.GetCommandType(commandAst.GetCommandName()).HasValue &&
37+
commandAst.CommandElements.Count >= 2;
4338
},
4439
true);
4540

4641
return commandAsts.Select(
47-
ast => {
48-
var testDefinitionLine =
42+
ast =>
43+
{
44+
// By this point we know the Ast is a CommandAst with 2 or more CommandElements
45+
int testNameParamIndex = 1;
46+
CommandAst testAst = (CommandAst)ast;
47+
48+
// The -Name parameter
49+
for (int i = 1; i < testAst.CommandElements.Count; i++)
50+
{
51+
CommandParameterAst paramAst = testAst.CommandElements[i] as CommandParameterAst;
52+
if (paramAst != null &&
53+
paramAst.ParameterName.Equals("Name", StringComparison.OrdinalIgnoreCase))
54+
{
55+
testNameParamIndex = i + 1;
56+
break;
57+
}
58+
}
59+
60+
if (testNameParamIndex > testAst.CommandElements.Count - 1)
61+
{
62+
return null;
63+
}
64+
65+
StringConstantExpressionAst stringAst =
66+
testAst.CommandElements[testNameParamIndex] as StringConstantExpressionAst;
67+
68+
if (stringAst == null)
69+
{
70+
return null;
71+
}
72+
73+
string testDefinitionLine =
4974
scriptFile.GetLine(
5075
ast.Extent.StartLineNumber);
5176

5277
return
53-
new SymbolReference(
54-
SymbolType.Function,
55-
testDefinitionLine.TrimEnd(DefinitionTrimChars),
56-
ast.Extent,
57-
scriptFile.FilePath,
58-
testDefinitionLine);
59-
});
78+
new PesterSymbolReference(
79+
scriptFile,
80+
testAst.GetCommandName(),
81+
testDefinitionLine,
82+
stringAst.Value,
83+
ast.Extent);
84+
85+
}).Where(s => s != null);
86+
}
87+
}
88+
89+
/// <summary>
90+
/// Defines command types for Pester test blocks.
91+
/// </summary>
92+
public enum PesterCommandType
93+
{
94+
/// <summary>
95+
/// Identifies a Describe block.
96+
/// </summary>
97+
Describe,
98+
99+
/// <summary>
100+
/// Identifies a Context block.
101+
/// </summary>
102+
Context,
103+
104+
/// <summary>
105+
/// Identifies an It block.
106+
/// </summary>
107+
It
108+
}
109+
110+
/// <summary>
111+
/// Provides a specialization of SymbolReference containing
112+
/// extra information about Pester test symbols.
113+
/// </summary>
114+
public class PesterSymbolReference : SymbolReference
115+
{
116+
private static char[] DefinitionTrimChars = new char[] { ' ', '{' };
117+
118+
/// <summary>
119+
/// Gets the name of the test
120+
/// </summary>
121+
public string TestName { get; private set; }
122+
123+
/// <summary>
124+
/// Gets the test's command type.
125+
/// </summary>
126+
public PesterCommandType Command { get; private set; }
127+
128+
internal PesterSymbolReference(
129+
ScriptFile scriptFile,
130+
string commandName,
131+
string testLine,
132+
string testName,
133+
IScriptExtent scriptExtent)
134+
: base(
135+
SymbolType.Function,
136+
testLine.TrimEnd(DefinitionTrimChars),
137+
scriptExtent,
138+
scriptFile.FilePath,
139+
testLine)
140+
{
141+
this.Command = GetCommandType(commandName).Value;
142+
this.TestName = testName;
143+
}
144+
145+
internal static PesterCommandType? GetCommandType(string commandName)
146+
{
147+
switch (commandName.ToLower())
148+
{
149+
case "describe":
150+
return PesterCommandType.Describe;
151+
152+
case "context":
153+
return PesterCommandType.Context;
154+
155+
case "it":
156+
return PesterCommandType.It;
157+
158+
default:
159+
return null;
160+
}
60161
}
61162
}
62163
}

0 commit comments

Comments
 (0)