Skip to content

Commit 25ddfd3

Browse files
JustinGroteCopilot
andcommitted
Update function finder to work for filter and workflow keywords
Co-authored-by: Copilot <[email protected]>
1 parent 07cf2ea commit 25ddfd3

7 files changed

Lines changed: 102 additions & 44 deletions

File tree

src/PowerShellEditorServices/Utility/AstExtensions.cs

Lines changed: 35 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -579,11 +579,12 @@ internal static ScriptExtentAdapter GetFunctionNameExtent(this FunctionDefinitio
579579
ScriptExtentAdapter funcExtent = new(ast.Extent);
580580
string? extentText = ast.Extent.Text;
581581

582-
if (TryGetFunctionNameOffsets(extentText, out int nameStartOffset, out int nameEndOffset))
582+
if (!string.IsNullOrEmpty(extentText))
583583
{
584+
(int nameStartOffset, int nameEndOffset) = GetFunctionNameOffsets(extentText, ast.Name);
584585
ScriptPosition originalStart = funcExtent.Start;
585-
funcExtent.Start = GetPositionAtOffset(originalStart, extentText!, nameStartOffset);
586-
funcExtent.End = GetPositionAtOffset(originalStart, extentText!, nameEndOffset);
586+
funcExtent.Start = GetPositionAtOffset(originalStart, extentText, nameStartOffset);
587+
funcExtent.End = GetPositionAtOffset(originalStart, extentText, nameEndOffset);
587588
return funcExtent;
588589
}
589590

@@ -641,65 +642,55 @@ private static ScriptPosition AdjustPosition(
641642
basePosition.ColumnNumber + columnDelta,
642643
basePosition.Line);
643644

644-
private static bool TryGetFunctionNameOffsets(
645-
string? text,
646-
out int nameStartOffset,
647-
out int nameEndOffset)
645+
internal static (int NameStartOffset, int NameEndOffset) GetFunctionNameOffsets(
646+
string text,
647+
string functionName)
648648
{
649-
nameStartOffset = 0;
650-
nameEndOffset = 0;
651-
652-
if (text is null || text.Length == 0)
649+
if (string.IsNullOrEmpty(text))
653650
{
654-
return false;
651+
throw new ArgumentException("Function definition text cannot be null or empty.", nameof(text));
655652
}
656653

657-
string nonNullText = text;
658-
659-
const string functionKeyword = "function";
660-
int functionIndex = nonNullText.IndexOf(functionKeyword, StringComparison.OrdinalIgnoreCase);
661-
if (functionIndex < 0)
654+
if (string.IsNullOrEmpty(functionName))
662655
{
663-
return false;
656+
throw new ArgumentException("Function name cannot be null or empty.", nameof(functionName));
664657
}
665658

666-
int index = functionIndex + functionKeyword.Length;
667-
while (index < nonNullText.Length && char.IsWhiteSpace(nonNullText[index]))
659+
int keywordStart = 0;
660+
while (keywordStart < text.Length && char.IsWhiteSpace(text[keywordStart]))
668661
{
669-
index++;
662+
keywordStart++;
670663
}
671664

672-
if (index >= nonNullText.Length)
665+
if (keywordStart >= text.Length)
673666
{
674-
return false;
667+
throw new InvalidOperationException("Function definition text does not contain a declaration keyword.");
675668
}
676669

677-
nameStartOffset = index;
678-
679-
while (index < nonNullText.Length)
670+
int keywordEnd = keywordStart;
671+
while (keywordEnd < text.Length && !char.IsWhiteSpace(text[keywordEnd]))
680672
{
681-
char current = nonNullText[index];
682-
683-
if (current == '`')
684-
{
685-
index++;
686-
if (index < nonNullText.Length)
687-
{
688-
index++;
689-
}
673+
keywordEnd++;
674+
}
690675

691-
continue;
692-
}
676+
string keyword = text.Substring(keywordStart, keywordEnd - keywordStart);
677+
bool isKnownKeyword = keyword.Equals("function", StringComparison.OrdinalIgnoreCase)
678+
|| keyword.Equals("filter", StringComparison.OrdinalIgnoreCase)
679+
|| keyword.Equals("workflow", StringComparison.OrdinalIgnoreCase);
693680

694-
if (char.IsWhiteSpace(current) || current == '{' || current == '(')
695-
{
696-
break;
697-
}
681+
if (!isKnownKeyword)
682+
{
683+
throw new InvalidOperationException($"Unexpected function definition keyword '{keyword}'.");
684+
}
698685

699-
index++;
686+
int nameStartOffset = text.IndexOf(functionName, keywordEnd, StringComparison.Ordinal);
687+
if (nameStartOffset < 0)
688+
{
689+
throw new InvalidOperationException($"Could not find function name '{functionName}' in function definition text.");
700690
}
701691

702-
nameEndOffset = index;
703-
return nameEndOffset > nameStartOffset;
692+
int nameEndOffset = nameStartOffset + functionName.Length;
693+
694+
return (nameStartOffset, nameEndOffset);
704695
}
705696
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
filter AFilter {
2+
$_
3+
}
4+
5+
1..3 | AFilter
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
filter Renamed {
2+
$_
3+
}
4+
5+
1..3 | Renamed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
workflow AWorkflow {
2+
"workflow"
3+
}
4+
5+
AWorkflow
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
workflow Renamed {
2+
"workflow"
3+
}
4+
5+
Renamed

test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/_RefactorFunctionTestCases.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public static class RefactorFunctionTestCases
1616
new("FunctionSimple.ps1", Line: 1, Column: 11, NewName: "Bad Name", ShouldThrow: true ),
1717
new("FunctionCallWIthinStringExpression.ps1", Line: 1, Column: 10 ),
1818
new("FunctionCmdlet.ps1", Line: 1, Column: 10 ),
19+
new("FunctionFilter.ps1", Line: 1, Column: 8 ),
1920
new("FunctionForeach.ps1", Line: 11, Column: 5 ),
2021
new("FunctionForeachObject.ps1", Line: 11, Column: 5 ),
2122
new("FunctionInnerIsNested.ps1", Line: 5, Column: 5 ),
@@ -27,5 +28,6 @@ public static class RefactorFunctionTestCases
2728
new("FunctionScriptblock.ps1", Line: 5, Column: 5 ),
2829
new("FunctionWithInnerFunction.ps1", Line: 5, Column: 5 ),
2930
new("FunctionWithInternalCalls.ps1", Line: 3, Column: 6 ),
31+
new("FunctionWorkflow.ps1", Line: 1, Column: 10 ),
3032
];
3133
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using Microsoft.PowerShell.EditorServices.Language;
6+
using Xunit;
7+
8+
namespace PowerShellEditorServices.Test.Utility;
9+
10+
[Trait("Category", "AstExtensions")]
11+
public class AstExtensionsTests
12+
{
13+
[Fact]
14+
public void GetFunctionNameOffsetsHandlesFilter()
15+
{
16+
const string definition = "filter AFilter { $_ }";
17+
const string name = "AFilter";
18+
19+
(int start, int end) = AstExtensions.GetFunctionNameOffsets(definition, name);
20+
21+
Assert.Equal(definition.IndexOf(name, StringComparison.Ordinal), start);
22+
Assert.Equal(start + name.Length, end);
23+
}
24+
25+
[Fact]
26+
public void GetFunctionNameOffsetsHandlesWorkflow()
27+
{
28+
const string definition = "workflow AWorkflow { \"ok\" }";
29+
const string name = "AWorkflow";
30+
31+
(int start, int end) = AstExtensions.GetFunctionNameOffsets(definition, name);
32+
33+
Assert.Equal(definition.IndexOf(name, StringComparison.Ordinal), start);
34+
Assert.Equal(start + name.Length, end);
35+
}
36+
37+
[Fact]
38+
public void GetFunctionNameOffsetsThrowsForUnexpectedKeyword()
39+
{
40+
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
41+
() => AstExtensions.GetFunctionNameOffsets("configuration MyConfig {}", "MyConfig"));
42+
43+
Assert.Contains("Unexpected function definition keyword", ex.Message);
44+
}
45+
}

0 commit comments

Comments
 (0)