Skip to content

Commit 160c960

Browse files
Fix for PSES 2.0 GA in PS7 (#51)
* Change version numbers, fix type resolution * Fix build issues * Fix bad command splat variable insertion points * Suppress analyzer fixes * Additional fixes for suppress analyzer
1 parent c9f3582 commit 160c960

11 files changed

+208
-57
lines changed

.vscode/extensions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// See http://go.microsoft.com/fwlink/?LinkId=827846
33
// for the documentation about the extensions.json format
44
"recommendations": [
5-
"ms-vscode.csharp",
5+
"ms-dotnettools.csharp",
66
"ms-vscode.powershell",
77
"DavidAnson.vscode-markdownlint",
88
],

EditorServicesCommandSuite.build.ps1

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,17 @@ task AssertPowerShellCore {
6969
}
7070

7171
if ($Force.IsPresent) {
72-
choco install powershell-core --version 6.2.3 -y
72+
choco install powershell-core --version 7.0.0 -y
7373
} else {
74-
choco install powershell-core --verison 6.2.3
74+
choco install powershell-core --verison 7.0.0
7575
}
7676

77-
$script:pwsh = Get-Command $env:ProgramFiles/PowerShell/6/pwsh.exe @FailOnError
77+
$script:pwsh = Get-Command $env:ProgramFiles/PowerShell/7/pwsh.exe @FailOnError
7878
}
7979

8080
task AssertRequiredModules {
8181
$assertRequiredModule = Get-Command $ToolsPath/AssertRequiredModule.ps1 @FailOnError
82-
& $assertRequiredModule platyPS -RequiredVersion 0.12.0 -Force:$Force.IsPresent
82+
& $assertRequiredModule platyPS -RequiredVersion 0.14.0 -Force:$Force.IsPresent
8383
}
8484

8585
task AssertDotNet {

build.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ param(
88
[switch] $Force
99
)
1010
end {
11-
& "$PSScriptRoot\tools\AssertRequiredModule.ps1" InvokeBuild 5.4.2 -Force:$Force.IsPresent
11+
& "$PSScriptRoot\tools\AssertRequiredModule.ps1" InvokeBuild 5.5.6 -Force:$Force.IsPresent
1212
$invokeBuildSplat = @{
1313
Task = 'PrePublish'
1414
File = "$PSScriptRoot/EditorServicesCommandSuite.build.ps1"

module/EditorServicesCommandSuite.psd1

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
RootModule = 'EditorServicesCommandSuite.psm1'
1313

1414
# Version number of this module.
15-
ModuleVersion = '0.5.0'
15+
ModuleVersion = '1.0.0'
1616

1717
# ID used to uniquely identify this module
1818
GUID = '97607afd-d9bd-4a2e-a9f9-70fe1a0a9e4c'
@@ -69,30 +69,6 @@ VariablesToExport = @()
6969
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
7070
AliasesToExport = 'Add-CommandToManifest'
7171

72-
# List of all files packaged with this module
73-
FileList = 'en-US\EditorServicesCommandSuite-help.xml',
74-
'EditorServicesCommandSuite.Classes.ps1',
75-
'EditorServicesCommandSuite.deps.json',
76-
'EditorServicesCommandSuite.dll',
77-
'EditorServicesCommandSuite.EditorServices.deps.json',
78-
'EditorServicesCommandSuite.EditorServices.dll',
79-
'EditorServicesCommandSuite.EditorServices.pdb',
80-
'EditorServicesCommandSuite.EditorServices.xml',
81-
'EditorServicesCommandSuite.format.ps1xml',
82-
'EditorServicesCommandSuite.pdb',
83-
'EditorServicesCommandSuite.psd1',
84-
'EditorServicesCommandSuite.psm1',
85-
'EditorServicesCommandSuite.PSReadLine.deps.json',
86-
'EditorServicesCommandSuite.PSReadLine.dll',
87-
'EditorServicesCommandSuite.PSReadLine.pdb',
88-
'EditorServicesCommandSuite.PSReadLine.xml',
89-
'EditorServicesCommandSuite.RefactorCmdlets.cdxml',
90-
'EditorServicesCommandSuite.xml',
91-
'System.Buffers.dll',
92-
'System.Memory.dll',
93-
'System.Numerics.Vectors.dll',
94-
'System.Runtime.CompilerServices.Unsafe.dll'
95-
9672
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
9773
PrivateData = @{
9874

@@ -115,6 +91,9 @@ PrivateData = @{
11591
- New editor command ConvertTo-FunctionDefinition for generating functions from selected text.
11692
'@
11793

94+
# Prerelease string of this module
95+
Prerelease = 'beta1'
96+
11897
} # End of PSData hashtable
11998

12099
} # End of PrivateData hashtable

module/EditorServicesCommandSuite.psm1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Update-FormatData -AppendPath $PSScriptRoot/EditorServicesCommandSuite.format.ps
44

55
if ($null -ne $psEditor) {
66
if ($PSVersionTable.PSVersion.Major -ge 6) {
7-
$extensionService = [Microsoft.PowerShell.EditorServices.Extensions.EditorObjectExtensions]::
7+
$extensionService = [Microsoft.PowerShell.EditorServices.Extensions.EditorObjectExtensions, Microsoft.PowerShell.EditorServices]::
88
GetExtensionServiceProvider($psEditor)
99

1010
$assembly = $extensionService.LoadAssemblyInPsesLoadContext((

src/EditorServicesCommandSuite.Common.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<RunCodeAnalysis>true</RunCodeAnalysis>
88
<LangVersion>preview</LangVersion>
99
<TargetFrameworks>netstandard2.0;netcoreapp2.0</TargetFrameworks>
10+
<Version>1.0.0.0</Version>
1011
</PropertyGroup>
1112
<ItemGroup>
1213
<AdditionalFiles Include="..\EditorServicesCommandSuite\stylecop.json" />

src/EditorServicesCommandSuite/CodeGeneration/DocumentEditWriter.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ internal class DocumentEditWriter : SpanEnabledStreamWriter
1919

2020
protected char[] _coreTab;
2121

22+
private protected int? _pendingIndent;
23+
2224
private readonly Stack<int> _indentStack = new Stack<int>();
2325

2426
private readonly int _byteOffsetModifier;
@@ -35,8 +37,6 @@ internal class DocumentEditWriter : SpanEnabledStreamWriter
3537

3638
private readonly byte[] _originalBuffer;
3739

38-
private int? _pendingIndent;
39-
4040
private bool _isWritePending;
4141

4242
private long _lastPositionSet;
@@ -498,9 +498,17 @@ private IEnumerable<DocumentEdit> ReduceEdits(IEnumerable<DocumentEdit> edits)
498498
StartOffset = editGroup.Key,
499499
EndOffset = highestOverride.EndOffset,
500500
OriginalValue = highestOverride.OriginalValue,
501+
502+
// The order by is hacky fix for when some refactors insert into offset 0
503+
// and also generate using namespace statements. A better fix would be to
504+
// implement a weight concept to document edits.
501505
NewValue = string.Concat(
502506
editGroup
503-
.OrderBy(edit => edit.Id)
507+
.OrderByDescending(
508+
edit => edit.NewValue.StartsWith(
509+
"using namespace ",
510+
StringComparison.OrdinalIgnoreCase))
511+
.ThenBy(edit => edit.Id)
504512
.Select(edit => edit.NewValue)),
505513
};
506514
}

src/EditorServicesCommandSuite/CodeGeneration/PowerShellScriptWriter.cs

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,23 @@ public bool AddUsingStatements(HashSet<string> namespaces, out int replaceLength
143143
SetPosition(0);
144144
}
145145

146-
Write(UsingUtilities.GetUsingStatementString(usings));
146+
var oldPendingIndent = _pendingIndent;
147+
var oldIndent = Indent;
148+
try
149+
{
150+
_pendingIndent = null;
151+
Indent = 0;
152+
Write(UsingUtilities.GetUsingStatementString(usings));
147153

148-
if (!existing.Any())
154+
if (!existing.Any())
155+
{
156+
WriteLines(2);
157+
}
158+
}
159+
finally
149160
{
150-
WriteLines(2);
161+
_pendingIndent = oldPendingIndent;
162+
Indent = oldIndent;
151163
}
152164

153165
return true;
@@ -762,18 +774,30 @@ internal void CloseParamBlock(bool shouldPopIndent = false)
762774

763775
internal void WriteUsingStatement(string name, UsingStatementKind kind)
764776
{
765-
char[] kindSymbol = kind switch
777+
var oldPendingIndent = _pendingIndent;
778+
var oldIndent = Indent;
779+
try
766780
{
767-
UsingStatementKind.Assembly => Symbols.Assembly,
768-
UsingStatementKind.Module => Symbols.Module,
769-
UsingStatementKind.Namespace => Symbols.Namespace,
770-
_ => throw new InvalidOperationException(),
771-
};
781+
_pendingIndent = null;
782+
Indent = 0;
783+
char[] kindSymbol = kind switch
784+
{
785+
UsingStatementKind.Assembly => Symbols.Assembly,
786+
UsingStatementKind.Module => Symbols.Module,
787+
UsingStatementKind.Namespace => Symbols.Namespace,
788+
_ => throw new InvalidOperationException(),
789+
};
772790

773-
Write(Using);
774-
Write(Space);
775-
Write(kindSymbol);
776-
Write(name);
791+
Write(Using);
792+
Write(Space);
793+
Write(kindSymbol);
794+
Write(name);
795+
}
796+
finally
797+
{
798+
_pendingIndent = oldPendingIndent;
799+
Indent = oldIndent;
800+
}
777801
}
778802

779803
internal Task RegisterWorkspaceChangeAsync(DocumentContextBase context)

src/EditorServicesCommandSuite/CodeGeneration/Refactors/CommandSplatRefactor.cs

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,24 @@ internal class CommandSplatRefactor : RefactorProvider
2424

2525
private const string AmbiguousParameterSet = "AmbiguousParameterSet";
2626

27+
private static readonly Type s_ternaryExpressionAstType;
28+
29+
private static readonly Type s_pipelineChainAstType;
30+
2731
private static readonly HashSet<string> s_allCommonParameters =
2832
new HashSet<string>(
2933
Cmdlet.CommonParameters.Concat(Cmdlet.OptionalCommonParameters),
3034
StringComparer.OrdinalIgnoreCase);
3135

36+
static CommandSplatRefactor()
37+
{
38+
s_ternaryExpressionAstType = typeof(PSObject).Assembly
39+
.GetType("System.Management.Automation.Language.TernaryExpressionAst");
40+
41+
s_pipelineChainAstType = typeof(PSObject).Assembly
42+
.GetType("System.Management.Automation.Language.PipelineChainAst");
43+
}
44+
3245
internal CommandSplatRefactor(IRefactorUI ui)
3346
{
3447
UI = ui;
@@ -174,9 +187,122 @@ await AddAdditionalParameters(
174187
return (parameterList, unresolvedPositionalArgs);
175188
}
176189

190+
private static Ast FindVariableInjectionTargetAst(CommandAst command)
191+
{
192+
// Search up the tree for the right place to insert the splat variable
193+
// assignment. This is done to avoid inserting it in scenarios like:
194+
//
195+
// $commandValue = $commandSplat = @{ Param = $true }
196+
// Command @commandSplat
197+
//
198+
// or:
199+
//
200+
// ($commandSplat = @{ Param = $true}
201+
// Command @commandSplat)
202+
Ast target = command.FindParent<PipelineAst>();
203+
while (true)
204+
{
205+
Ast parent = target.Parent;
206+
if (parent == null)
207+
{
208+
return target;
209+
}
210+
211+
bool shouldGetParent = false;
212+
if (parent is StatementAst)
213+
{
214+
shouldGetParent =
215+
parent is AssignmentStatementAst
216+
|| parent is ThrowStatementAst
217+
|| parent is ReturnStatementAst
218+
|| parent is ExitStatementAst
219+
|| parent is ContinueStatementAst
220+
|| parent is CommandBaseAst
221+
|| parent is HashtableAst
222+
|| parent is BreakStatementAst
223+
|| parent is PipelineBaseAst;
224+
}
225+
226+
if (parent is ExpressionAst)
227+
{
228+
shouldGetParent =
229+
parent is ParenExpressionAst
230+
|| parent is BinaryExpressionAst
231+
|| parent is UnaryExpressionAst
232+
|| parent is AttributedExpressionAst
233+
|| parent is MemberExpressionAst
234+
|| parent is ExpandableStringExpressionAst
235+
|| parent is ArrayLiteralAst
236+
|| parent is UsingExpressionAst
237+
|| parent is IndexExpressionAst;
238+
}
239+
240+
if (shouldGetParent)
241+
{
242+
target = parent;
243+
continue;
244+
}
245+
246+
if (parent is IfStatementAst ifStatementAst)
247+
{
248+
foreach (Tuple<PipelineBaseAst, StatementBlockAst> clause in ifStatementAst.Clauses)
249+
{
250+
if (target == clause.Item1)
251+
{
252+
return ifStatementAst;
253+
}
254+
}
255+
256+
return target;
257+
}
258+
259+
if (parent is ForStatementAst forStatement)
260+
{
261+
if (target == forStatement.Initializer ||
262+
target == forStatement.Iterator ||
263+
target == forStatement.Condition)
264+
{
265+
return forStatement;
266+
}
267+
268+
return target;
269+
}
270+
271+
if (parent is LoopStatementAst loop)
272+
{
273+
if (target == loop.Condition)
274+
{
275+
return loop;
276+
}
277+
278+
return target;
279+
}
280+
281+
// These don't actually work yet since AstEnumerable won't find a CommandAst
282+
// hidden in either of these language elements.
283+
if (s_ternaryExpressionAstType != null)
284+
{
285+
Type reflectionType = parent.GetType();
286+
if (s_ternaryExpressionAstType.IsAssignableFrom(reflectionType))
287+
{
288+
target = parent;
289+
continue;
290+
}
291+
292+
if (s_pipelineChainAstType?.IsAssignableFrom(reflectionType) == true)
293+
{
294+
target = parent;
295+
continue;
296+
}
297+
}
298+
299+
return target;
300+
}
301+
}
302+
177303
private static async Task<IEnumerable<DocumentEdit>> GetEdits(CommandSplatArguments args)
178304
{
179-
PipelineAst parentStatement = args.Command.FindParent<PipelineAst>();
305+
Ast variableInjectionTarget = FindVariableInjectionTargetAst(args.Command);
180306
string commandName = args.Command.GetCommandName();
181307
IEnumerable<CommandElementAst> elements = args.Command.CommandElements.Skip(1);
182308
IScriptExtent elementsExtent = elements.JoinExtents();
@@ -194,14 +320,14 @@ private static async Task<IEnumerable<DocumentEdit>> GetEdits(CommandSplatArgume
194320
var splatWriter = new PowerShellScriptWriter(args.Command);
195321
var elementsWriter = new PowerShellScriptWriter(args.Command);
196322

197-
splatWriter.SetPosition(parentStatement);
323+
splatWriter.SetPosition(variableInjectionTarget);
198324
splatWriter.WriteAssignment(
199325
() => splatWriter.WriteVariable(args.VariableName),
200326
() => splatWriter.OpenHashtable());
201327

202328
if (elementsExtent is Empty.Extent)
203329
{
204-
elementsWriter.SetPosition(parentStatement, atEnd: true);
330+
elementsWriter.SetPosition(variableInjectionTarget, atEnd: true);
205331
elementsWriter.Write(Symbols.Space);
206332
}
207333
else

0 commit comments

Comments
 (0)