Skip to content

Commit

Permalink
Fix for PSES 2.0 GA in PS7 (#51)
Browse files Browse the repository at this point in the history
* Change version numbers, fix type resolution

* Fix build issues

* Fix bad command splat variable insertion points

* Suppress analyzer fixes

* Additional fixes for suppress analyzer
  • Loading branch information
SeeminglyScience authored Mar 8, 2020
1 parent c9f3582 commit 160c960
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"ms-vscode.csharp",
"ms-dotnettools.csharp",
"ms-vscode.powershell",
"DavidAnson.vscode-markdownlint",
],
Expand Down
8 changes: 4 additions & 4 deletions EditorServicesCommandSuite.build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,17 @@ task AssertPowerShellCore {
}

if ($Force.IsPresent) {
choco install powershell-core --version 6.2.3 -y
choco install powershell-core --version 7.0.0 -y
} else {
choco install powershell-core --verison 6.2.3
choco install powershell-core --verison 7.0.0
}

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

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

task AssertDotNet {
Expand Down
2 changes: 1 addition & 1 deletion build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ param(
[switch] $Force
)
end {
& "$PSScriptRoot\tools\AssertRequiredModule.ps1" InvokeBuild 5.4.2 -Force:$Force.IsPresent
& "$PSScriptRoot\tools\AssertRequiredModule.ps1" InvokeBuild 5.5.6 -Force:$Force.IsPresent
$invokeBuildSplat = @{
Task = 'PrePublish'
File = "$PSScriptRoot/EditorServicesCommandSuite.build.ps1"
Expand Down
29 changes: 4 additions & 25 deletions module/EditorServicesCommandSuite.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'EditorServicesCommandSuite.psm1'

# Version number of this module.
ModuleVersion = '0.5.0'
ModuleVersion = '1.0.0'

# ID used to uniquely identify this module
GUID = '97607afd-d9bd-4a2e-a9f9-70fe1a0a9e4c'
Expand Down Expand Up @@ -69,30 +69,6 @@ VariablesToExport = @()
# 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.
AliasesToExport = 'Add-CommandToManifest'

# List of all files packaged with this module
FileList = 'en-US\EditorServicesCommandSuite-help.xml',
'EditorServicesCommandSuite.Classes.ps1',
'EditorServicesCommandSuite.deps.json',
'EditorServicesCommandSuite.dll',
'EditorServicesCommandSuite.EditorServices.deps.json',
'EditorServicesCommandSuite.EditorServices.dll',
'EditorServicesCommandSuite.EditorServices.pdb',
'EditorServicesCommandSuite.EditorServices.xml',
'EditorServicesCommandSuite.format.ps1xml',
'EditorServicesCommandSuite.pdb',
'EditorServicesCommandSuite.psd1',
'EditorServicesCommandSuite.psm1',
'EditorServicesCommandSuite.PSReadLine.deps.json',
'EditorServicesCommandSuite.PSReadLine.dll',
'EditorServicesCommandSuite.PSReadLine.pdb',
'EditorServicesCommandSuite.PSReadLine.xml',
'EditorServicesCommandSuite.RefactorCmdlets.cdxml',
'EditorServicesCommandSuite.xml',
'System.Buffers.dll',
'System.Memory.dll',
'System.Numerics.Vectors.dll',
'System.Runtime.CompilerServices.Unsafe.dll'

# 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.
PrivateData = @{

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

# Prerelease string of this module
Prerelease = 'beta1'

} # End of PSData hashtable

} # End of PrivateData hashtable
Expand Down
2 changes: 1 addition & 1 deletion module/EditorServicesCommandSuite.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Update-FormatData -AppendPath $PSScriptRoot/EditorServicesCommandSuite.format.ps

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

$assembly = $extensionService.LoadAssemblyInPsesLoadContext((
Expand Down
1 change: 1 addition & 0 deletions src/EditorServicesCommandSuite.Common.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<RunCodeAnalysis>true</RunCodeAnalysis>
<LangVersion>preview</LangVersion>
<TargetFrameworks>netstandard2.0;netcoreapp2.0</TargetFrameworks>
<Version>1.0.0.0</Version>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\EditorServicesCommandSuite\stylecop.json" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ internal class DocumentEditWriter : SpanEnabledStreamWriter

protected char[] _coreTab;

private protected int? _pendingIndent;

private readonly Stack<int> _indentStack = new Stack<int>();

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

private readonly byte[] _originalBuffer;

private int? _pendingIndent;

private bool _isWritePending;

private long _lastPositionSet;
Expand Down Expand Up @@ -498,9 +498,17 @@ private IEnumerable<DocumentEdit> ReduceEdits(IEnumerable<DocumentEdit> edits)
StartOffset = editGroup.Key,
EndOffset = highestOverride.EndOffset,
OriginalValue = highestOverride.OriginalValue,

// The order by is hacky fix for when some refactors insert into offset 0
// and also generate using namespace statements. A better fix would be to
// implement a weight concept to document edits.
NewValue = string.Concat(
editGroup
.OrderBy(edit => edit.Id)
.OrderByDescending(
edit => edit.NewValue.StartsWith(
"using namespace ",
StringComparison.OrdinalIgnoreCase))
.ThenBy(edit => edit.Id)
.Select(edit => edit.NewValue)),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,23 @@ public bool AddUsingStatements(HashSet<string> namespaces, out int replaceLength
SetPosition(0);
}

Write(UsingUtilities.GetUsingStatementString(usings));
var oldPendingIndent = _pendingIndent;
var oldIndent = Indent;
try
{
_pendingIndent = null;
Indent = 0;
Write(UsingUtilities.GetUsingStatementString(usings));

if (!existing.Any())
if (!existing.Any())
{
WriteLines(2);
}
}
finally
{
WriteLines(2);
_pendingIndent = oldPendingIndent;
Indent = oldIndent;
}

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

internal void WriteUsingStatement(string name, UsingStatementKind kind)
{
char[] kindSymbol = kind switch
var oldPendingIndent = _pendingIndent;
var oldIndent = Indent;
try
{
UsingStatementKind.Assembly => Symbols.Assembly,
UsingStatementKind.Module => Symbols.Module,
UsingStatementKind.Namespace => Symbols.Namespace,
_ => throw new InvalidOperationException(),
};
_pendingIndent = null;
Indent = 0;
char[] kindSymbol = kind switch
{
UsingStatementKind.Assembly => Symbols.Assembly,
UsingStatementKind.Module => Symbols.Module,
UsingStatementKind.Namespace => Symbols.Namespace,
_ => throw new InvalidOperationException(),
};

Write(Using);
Write(Space);
Write(kindSymbol);
Write(name);
Write(Using);
Write(Space);
Write(kindSymbol);
Write(name);
}
finally
{
_pendingIndent = oldPendingIndent;
Indent = oldIndent;
}
}

internal Task RegisterWorkspaceChangeAsync(DocumentContextBase context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,24 @@ internal class CommandSplatRefactor : RefactorProvider

private const string AmbiguousParameterSet = "AmbiguousParameterSet";

private static readonly Type s_ternaryExpressionAstType;

private static readonly Type s_pipelineChainAstType;

private static readonly HashSet<string> s_allCommonParameters =
new HashSet<string>(
Cmdlet.CommonParameters.Concat(Cmdlet.OptionalCommonParameters),
StringComparer.OrdinalIgnoreCase);

static CommandSplatRefactor()
{
s_ternaryExpressionAstType = typeof(PSObject).Assembly
.GetType("System.Management.Automation.Language.TernaryExpressionAst");

s_pipelineChainAstType = typeof(PSObject).Assembly
.GetType("System.Management.Automation.Language.PipelineChainAst");
}

internal CommandSplatRefactor(IRefactorUI ui)
{
UI = ui;
Expand Down Expand Up @@ -174,9 +187,122 @@ await AddAdditionalParameters(
return (parameterList, unresolvedPositionalArgs);
}

private static Ast FindVariableInjectionTargetAst(CommandAst command)
{
// Search up the tree for the right place to insert the splat variable
// assignment. This is done to avoid inserting it in scenarios like:
//
// $commandValue = $commandSplat = @{ Param = $true }
// Command @commandSplat
//
// or:
//
// ($commandSplat = @{ Param = $true}
// Command @commandSplat)
Ast target = command.FindParent<PipelineAst>();
while (true)
{
Ast parent = target.Parent;
if (parent == null)
{
return target;
}

bool shouldGetParent = false;
if (parent is StatementAst)
{
shouldGetParent =
parent is AssignmentStatementAst
|| parent is ThrowStatementAst
|| parent is ReturnStatementAst
|| parent is ExitStatementAst
|| parent is ContinueStatementAst
|| parent is CommandBaseAst
|| parent is HashtableAst
|| parent is BreakStatementAst
|| parent is PipelineBaseAst;
}

if (parent is ExpressionAst)
{
shouldGetParent =
parent is ParenExpressionAst
|| parent is BinaryExpressionAst
|| parent is UnaryExpressionAst
|| parent is AttributedExpressionAst
|| parent is MemberExpressionAst
|| parent is ExpandableStringExpressionAst
|| parent is ArrayLiteralAst
|| parent is UsingExpressionAst
|| parent is IndexExpressionAst;
}

if (shouldGetParent)
{
target = parent;
continue;
}

if (parent is IfStatementAst ifStatementAst)
{
foreach (Tuple<PipelineBaseAst, StatementBlockAst> clause in ifStatementAst.Clauses)
{
if (target == clause.Item1)
{
return ifStatementAst;
}
}

return target;
}

if (parent is ForStatementAst forStatement)
{
if (target == forStatement.Initializer ||
target == forStatement.Iterator ||
target == forStatement.Condition)
{
return forStatement;
}

return target;
}

if (parent is LoopStatementAst loop)
{
if (target == loop.Condition)
{
return loop;
}

return target;
}

// These don't actually work yet since AstEnumerable won't find a CommandAst
// hidden in either of these language elements.
if (s_ternaryExpressionAstType != null)
{
Type reflectionType = parent.GetType();
if (s_ternaryExpressionAstType.IsAssignableFrom(reflectionType))
{
target = parent;
continue;
}

if (s_pipelineChainAstType?.IsAssignableFrom(reflectionType) == true)
{
target = parent;
continue;
}
}

return target;
}
}

private static async Task<IEnumerable<DocumentEdit>> GetEdits(CommandSplatArguments args)
{
PipelineAst parentStatement = args.Command.FindParent<PipelineAst>();
Ast variableInjectionTarget = FindVariableInjectionTargetAst(args.Command);
string commandName = args.Command.GetCommandName();
IEnumerable<CommandElementAst> elements = args.Command.CommandElements.Skip(1);
IScriptExtent elementsExtent = elements.JoinExtents();
Expand All @@ -194,14 +320,14 @@ private static async Task<IEnumerable<DocumentEdit>> GetEdits(CommandSplatArgume
var splatWriter = new PowerShellScriptWriter(args.Command);
var elementsWriter = new PowerShellScriptWriter(args.Command);

splatWriter.SetPosition(parentStatement);
splatWriter.SetPosition(variableInjectionTarget);
splatWriter.WriteAssignment(
() => splatWriter.WriteVariable(args.VariableName),
() => splatWriter.OpenHashtable());

if (elementsExtent is Empty.Extent)
{
elementsWriter.SetPosition(parentStatement, atEnd: true);
elementsWriter.SetPosition(variableInjectionTarget, atEnd: true);
elementsWriter.Write(Symbols.Space);
}
else
Expand Down
Loading

0 comments on commit 160c960

Please sign in to comment.