diff --git a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs index 58d52bb3a..48ed64849 100644 --- a/src/PowerShellEditorServices/Server/PsesLanguageServer.cs +++ b/src/PowerShellEditorServices/Server/PsesLanguageServer.cs @@ -118,6 +118,8 @@ public async Task StartAsync() .WithHandler() .WithHandler() .WithHandler() + .WithHandler() + .WithHandler() // NOTE: The OnInitialize delegate gets run when we first receive the // _Initialize_ request: // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#initialize diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/PrepareRenameSymbol.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PrepareRenameSymbol.cs new file mode 100644 index 000000000..4ea6c0c64 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/PrepareRenameSymbol.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using System.Management.Automation.Language; +using OmniSharp.Extensions.JsonRpc; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Refactoring; +using Microsoft.PowerShell.EditorServices.Services.Symbols; + +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + [Serial, Method("powerShell/PrepareRenameSymbol")] + internal interface IPrepareRenameSymbolHandler : IJsonRpcRequestHandler { } + + internal class PrepareRenameSymbolParams : IRequest + { + public string FileName { get; set; } + public int Line { get; set; } + public int Column { get; set; } + public string RenameTo { get; set; } + } + internal class PrepareRenameSymbolResult + { + public string message; + } + + internal class PrepareRenameSymbolHandler : IPrepareRenameSymbolHandler + { + private readonly WorkspaceService _workspaceService; + + public PrepareRenameSymbolHandler(WorkspaceService workspaceService) => _workspaceService = workspaceService; + + public async Task Handle(PrepareRenameSymbolParams request, CancellationToken cancellationToken) + { + if (!_workspaceService.TryGetFile(request.FileName, out ScriptFile scriptFile)) + { + // TODO: Unsaved file support. We need to find the unsaved file in the text documents synced to the LSP and use that as our Ast Base. + throw new HandlerErrorException($"File {request.FileName} not found in workspace. Unsaved files currently do not support the rename symbol feature."); + } + return await Task.Run(() => + { + PrepareRenameSymbolResult result = new() + { + message = "" + }; + // ast is FunctionDefinitionAst or CommandAst or VariableExpressionAst or StringConstantExpressionAst && + SymbolReference symbol = scriptFile.References.TryGetSymbolAtPosition(request.Line + 1, request.Column + 1); + Ast token = Utilities.GetAst(request.Line + 1, request.Column + 1, scriptFile.ScriptAst); + + if (token == null) + { + result.message = "Unable to find symbol"; + return result; + } + if (Utilities.AssertContainsDotSourced(scriptFile.ScriptAst)) + { + result.message = "Dot Source detected, this is currently not supported operation aborted"; + return result; + } + + bool IsFunction = false; + string tokenName = ""; + + switch (token) + { + + case FunctionDefinitionAst FuncAst: + IsFunction = true; + tokenName = FuncAst.Name; + break; + case VariableExpressionAst or CommandParameterAst or ParameterAst: + IsFunction = false; + tokenName = request.RenameTo; + break; + case StringConstantExpressionAst: + + if (token.Parent is CommandAst CommAst) + { + IsFunction = true; + tokenName = CommAst.GetCommandName(); + } + else + { + IsFunction = false; + } + break; + } + + if (IsFunction) + { + try + { + IterativeFunctionRename visitor = new(tokenName, + request.RenameTo, + token.Extent.StartLineNumber, + token.Extent.StartColumnNumber, + scriptFile.ScriptAst); + } + catch (FunctionDefinitionNotFoundException) + { + result.message = "Failed to Find function definition within current file"; + } + } + else + { + IterativeVariableRename visitor = new(tokenName, + token.Extent.StartLineNumber, + token.Extent.StartColumnNumber, + scriptFile.ScriptAst); + if (visitor.TargetVariableAst == null) + { + result.message = "Failed to find variable definition within the current file"; + } + } + return result; + }).ConfigureAwait(false); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Handlers/RenameSymbol.cs b/src/PowerShellEditorServices/Services/PowerShell/Handlers/RenameSymbol.cs new file mode 100644 index 000000000..6e4107ad4 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Handlers/RenameSymbol.cs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using System.Management.Automation.Language; +using OmniSharp.Extensions.JsonRpc; +using Microsoft.PowerShell.EditorServices.Services; +using Microsoft.PowerShell.EditorServices.Services.TextDocument; +using Microsoft.PowerShell.EditorServices.Refactoring; +using System; +namespace Microsoft.PowerShell.EditorServices.Handlers +{ + [Serial, Method("powerShell/renameSymbol")] + internal interface IRenameSymbolHandler : IJsonRpcRequestHandler { } + + public class RenameSymbolOptions + { + public bool CreateAlias { get; set; } + } + + public class RenameSymbolParams : IRequest + { + public string FileName { get; set; } + public int Line { get; set; } + public int Column { get; set; } + public string RenameTo { get; set; } + public RenameSymbolOptions Options { get; set; } + } + public class TextChange + { + public string NewText { get; set; } + public int StartLine { get; set; } + public int StartColumn { get; set; } + public int EndLine { get; set; } + public int EndColumn { get; set; } + } + public class ModifiedFileResponse + { + public string FileName { get; set; } + public List Changes { get; set; } + public ModifiedFileResponse(string fileName) + { + FileName = fileName; + Changes = new List(); + } + + public void AddTextChange(Ast Symbol, string NewText) + { + Changes.Add( + new TextChange + { + StartColumn = Symbol.Extent.StartColumnNumber - 1, + StartLine = Symbol.Extent.StartLineNumber - 1, + EndColumn = Symbol.Extent.EndColumnNumber - 1, + EndLine = Symbol.Extent.EndLineNumber - 1, + NewText = NewText + } + ); + } + } + public class RenameSymbolResult + { + public RenameSymbolResult() => Changes = new List(); + public List Changes { get; set; } + } + + internal class RenameSymbolHandler : IRenameSymbolHandler + { + private readonly WorkspaceService _workspaceService; + + public RenameSymbolHandler(WorkspaceService workspaceService) => _workspaceService = workspaceService; + + internal static ModifiedFileResponse RenameFunction(Ast token, Ast scriptAst, RenameSymbolParams request) + { + string tokenName = ""; + if (token is FunctionDefinitionAst funcDef) + { + tokenName = funcDef.Name; + } + else if (token.Parent is CommandAst CommAst) + { + tokenName = CommAst.GetCommandName(); + } + IterativeFunctionRename visitor = new(tokenName, + request.RenameTo, + token.Extent.StartLineNumber, + token.Extent.StartColumnNumber, + scriptAst); + visitor.Visit(scriptAst); + ModifiedFileResponse FileModifications = new(request.FileName) + { + Changes = visitor.Modifications + }; + return FileModifications; + } + + internal static ModifiedFileResponse RenameVariable(Ast symbol, Ast scriptAst, RenameSymbolParams request) + { + if (symbol is VariableExpressionAst or ParameterAst or CommandParameterAst or StringConstantExpressionAst) + { + + IterativeVariableRename visitor = new( + request.RenameTo, + symbol.Extent.StartLineNumber, + symbol.Extent.StartColumnNumber, + scriptAst, + request.Options ?? null + ); + visitor.Visit(scriptAst); + ModifiedFileResponse FileModifications = new(request.FileName) + { + Changes = visitor.Modifications + }; + return FileModifications; + + } + return null; + } + + public async Task Handle(RenameSymbolParams request, CancellationToken cancellationToken) + { + if (!_workspaceService.TryGetFile(request.FileName, out ScriptFile scriptFile)) + { + throw new InvalidOperationException("This should not happen as PrepareRename should have already checked for viability. File an issue if you see this."); + } + + return await Task.Run(() => + { + Ast token = Utilities.GetAst(request.Line + 1, request.Column + 1, scriptFile.ScriptAst); + if (token == null) { return null; } + + ModifiedFileResponse FileModifications = (token is FunctionDefinitionAst || token.Parent is CommandAst) + ? RenameFunction(token, scriptFile.ScriptAst, request) + : RenameVariable(token, scriptFile.ScriptAst, request); + + RenameSymbolResult result = new(); + + result.Changes.Add(FileModifications); + + return result; + }).ConfigureAwait(false); + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Refactoring/Exceptions.cs b/src/PowerShellEditorServices/Services/PowerShell/Refactoring/Exceptions.cs new file mode 100644 index 000000000..e447556cf --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Refactoring/Exceptions.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +namespace Microsoft.PowerShell.EditorServices.Refactoring +{ + + public class TargetSymbolNotFoundException : Exception + { + public TargetSymbolNotFoundException() + { + } + + public TargetSymbolNotFoundException(string message) + : base(message) + { + } + + public TargetSymbolNotFoundException(string message, Exception inner) + : base(message, inner) + { + } + } + + public class FunctionDefinitionNotFoundException : Exception + { + public FunctionDefinitionNotFoundException() + { + } + + public FunctionDefinitionNotFoundException(string message) + : base(message) + { + } + + public FunctionDefinitionNotFoundException(string message, Exception inner) + : base(message, inner) + { + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Refactoring/IterativeFunctionVistor.cs b/src/PowerShellEditorServices/Services/PowerShell/Refactoring/IterativeFunctionVistor.cs new file mode 100644 index 000000000..36a8536d9 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Refactoring/IterativeFunctionVistor.cs @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Handlers; + +namespace Microsoft.PowerShell.EditorServices.Refactoring +{ + + internal class IterativeFunctionRename + { + private readonly string OldName; + private readonly string NewName; + public List Modifications = new(); + internal int StartLineNumber; + internal int StartColumnNumber; + internal FunctionDefinitionAst TargetFunctionAst; + internal FunctionDefinitionAst DuplicateFunctionAst; + internal readonly Ast ScriptAst; + + public IterativeFunctionRename(string OldName, string NewName, int StartLineNumber, int StartColumnNumber, Ast ScriptAst) + { + this.OldName = OldName; + this.NewName = NewName; + this.StartLineNumber = StartLineNumber; + this.StartColumnNumber = StartColumnNumber; + this.ScriptAst = ScriptAst; + + Ast Node = Utilities.GetAstAtPositionOfType(StartLineNumber, StartColumnNumber, ScriptAst, + typeof(FunctionDefinitionAst), typeof(CommandAst)); + + if (Node != null) + { + if (Node is FunctionDefinitionAst FuncDef && FuncDef.Name.ToLower() == OldName.ToLower()) + { + TargetFunctionAst = FuncDef; + } + if (Node is CommandAst commdef && commdef.GetCommandName().ToLower() == OldName.ToLower()) + { + TargetFunctionAst = Utilities.GetFunctionDefByCommandAst(OldName, StartLineNumber, StartColumnNumber, ScriptAst); + if (TargetFunctionAst == null) + { + throw new FunctionDefinitionNotFoundException(); + } + this.StartColumnNumber = TargetFunctionAst.Extent.StartColumnNumber; + this.StartLineNumber = TargetFunctionAst.Extent.StartLineNumber; + } + } + } + + public class NodeProcessingState + { + public Ast Node { get; set; } + public bool ShouldRename { get; set; } + public IEnumerator ChildrenEnumerator { get; set; } + } + public bool DetermineChildShouldRenameState(NodeProcessingState currentState, Ast child) + { + // The Child Has the name we are looking for + if (child is FunctionDefinitionAst funcDef && funcDef.Name.ToLower() == OldName.ToLower()) + { + // The Child is the function we are looking for + if (child.Extent.StartLineNumber == StartLineNumber && + child.Extent.StartColumnNumber == StartColumnNumber) + { + return true; + + } + // Otherwise its a duplicate named function + else + { + DuplicateFunctionAst = funcDef; + return false; + } + + } + else if (child?.Parent?.Parent is ScriptBlockAst) + { + // The Child is in the same scriptblock as the Target Function + if (TargetFunctionAst.Parent.Parent == child?.Parent?.Parent) + { + return true; + } + // The Child is in the same ScriptBlock as the Duplicate Function + if (DuplicateFunctionAst?.Parent?.Parent == child?.Parent?.Parent) + { + return false; + } + } + else if (child?.Parent is StatementBlockAst) + { + + if (child?.Parent == TargetFunctionAst?.Parent) + { + return true; + } + + if (DuplicateFunctionAst?.Parent == child?.Parent) + { + return false; + } + } + return currentState.ShouldRename; + } + public void Visit(Ast root) + { + Stack processingStack = new(); + + processingStack.Push(new NodeProcessingState { Node = root, ShouldRename = false }); + + while (processingStack.Count > 0) + { + NodeProcessingState currentState = processingStack.Peek(); + + if (currentState.ChildrenEnumerator == null) + { + // First time processing this node. Do the initial processing. + ProcessNode(currentState.Node, currentState.ShouldRename); // This line is crucial. + + // Get the children and set up the enumerator. + IEnumerable children = currentState.Node.FindAll(ast => ast.Parent == currentState.Node, searchNestedScriptBlocks: true); + currentState.ChildrenEnumerator = children.GetEnumerator(); + } + + // Process the next child. + if (currentState.ChildrenEnumerator.MoveNext()) + { + Ast child = currentState.ChildrenEnumerator.Current; + bool childShouldRename = DetermineChildShouldRenameState(currentState, child); + processingStack.Push(new NodeProcessingState { Node = child, ShouldRename = childShouldRename }); + } + else + { + // All children have been processed, we're done with this node. + processingStack.Pop(); + } + } + } + + public void ProcessNode(Ast node, bool shouldRename) + { + + switch (node) + { + case FunctionDefinitionAst ast: + if (ast.Name.ToLower() == OldName.ToLower()) + { + if (ast.Extent.StartLineNumber == StartLineNumber && + ast.Extent.StartColumnNumber == StartColumnNumber) + { + TargetFunctionAst = ast; + TextChange Change = new() + { + NewText = NewName, + StartLine = ast.Extent.StartLineNumber - 1, + StartColumn = ast.Extent.StartColumnNumber + "function ".Length - 1, + EndLine = ast.Extent.StartLineNumber - 1, + EndColumn = ast.Extent.StartColumnNumber + "function ".Length + ast.Name.Length - 1, + }; + + Modifications.Add(Change); + //node.ShouldRename = true; + } + else + { + // Entering a duplicate functions scope and shouldnt rename + //node.ShouldRename = false; + DuplicateFunctionAst = ast; + } + } + break; + case CommandAst ast: + if (ast.GetCommandName()?.ToLower() == OldName.ToLower() && + TargetFunctionAst.Extent.StartLineNumber <= ast.Extent.StartLineNumber) + { + if (shouldRename) + { + TextChange Change = new() + { + NewText = NewName, + StartLine = ast.Extent.StartLineNumber - 1, + StartColumn = ast.Extent.StartColumnNumber - 1, + EndLine = ast.Extent.StartLineNumber - 1, + EndColumn = ast.Extent.StartColumnNumber + OldName.Length - 1, + }; + Modifications.Add(Change); + } + } + break; + } + } + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Refactoring/IterativeVariableVisitor.cs b/src/PowerShellEditorServices/Services/PowerShell/Refactoring/IterativeVariableVisitor.cs new file mode 100644 index 000000000..2a11dce88 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Refactoring/IterativeVariableVisitor.cs @@ -0,0 +1,531 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Management.Automation.Language; +using Microsoft.PowerShell.EditorServices.Handlers; +using System.Linq; +using System; + +namespace Microsoft.PowerShell.EditorServices.Refactoring +{ + + internal class IterativeVariableRename + { + private readonly string OldName; + private readonly string NewName; + internal bool ShouldRename; + public List Modifications = new(); + internal int StartLineNumber; + internal int StartColumnNumber; + internal VariableExpressionAst TargetVariableAst; + internal readonly Ast ScriptAst; + internal bool isParam; + internal bool AliasSet; + internal FunctionDefinitionAst TargetFunction; + internal RenameSymbolOptions options; + + public IterativeVariableRename(string NewName, int StartLineNumber, int StartColumnNumber, Ast ScriptAst, RenameSymbolOptions options = null) + { + this.NewName = NewName; + this.StartLineNumber = StartLineNumber; + this.StartColumnNumber = StartColumnNumber; + this.ScriptAst = ScriptAst; + this.options = options ?? new RenameSymbolOptions { CreateAlias = true }; + + VariableExpressionAst Node = (VariableExpressionAst)GetVariableTopAssignment(StartLineNumber, StartColumnNumber, ScriptAst); + if (Node != null) + { + if (Node.Parent is ParameterAst) + { + isParam = true; + Ast parent = Node; + // Look for a target function that the parameterAst will be within if it exists + parent = Utilities.GetAstParentOfType(parent, typeof(FunctionDefinitionAst)); + if (parent != null) + { + TargetFunction = (FunctionDefinitionAst)parent; + } + } + TargetVariableAst = Node; + OldName = TargetVariableAst.VariablePath.UserPath.Replace("$", ""); + this.StartColumnNumber = TargetVariableAst.Extent.StartColumnNumber; + this.StartLineNumber = TargetVariableAst.Extent.StartLineNumber; + } + } + + public static Ast GetVariableTopAssignment(int StartLineNumber, int StartColumnNumber, Ast ScriptAst) + { + + // Look up the target object + Ast node = Utilities.GetAstAtPositionOfType(StartLineNumber, StartColumnNumber, + ScriptAst, typeof(VariableExpressionAst), typeof(CommandParameterAst), typeof(StringConstantExpressionAst)); + + string name = node switch + { + CommandParameterAst commdef => commdef.ParameterName, + VariableExpressionAst varDef => varDef.VariablePath.UserPath, + // Key within a Hashtable + StringConstantExpressionAst strExp => strExp.Value, + _ => throw new TargetSymbolNotFoundException() + }; + + VariableExpressionAst splatAssignment = null; + // A rename of a parameter has been initiated from a splat + if (node is StringConstantExpressionAst) + { + Ast parent = node; + parent = Utilities.GetAstParentOfType(parent, typeof(AssignmentStatementAst)); + if (parent is not null and AssignmentStatementAst assignmentStatementAst) + { + splatAssignment = (VariableExpressionAst)assignmentStatementAst.Left.Find( + ast => ast is VariableExpressionAst, false); + } + } + + Ast TargetParent = GetAstParentScope(node); + + // Is the Variable sitting within a ParameterBlockAst that is within a Function Definition + // If so we don't need to look further as this is most likley the AssignmentStatement we are looking for + Ast paramParent = Utilities.GetAstParentOfType(node, typeof(ParamBlockAst)); + if (TargetParent is FunctionDefinitionAst && null != paramParent) + { + return node; + } + + // Find all variables and parameter assignments with the same name before + // The node found above + List VariableAssignments = ScriptAst.FindAll(ast => + { + return ast is VariableExpressionAst VarDef && + VarDef.Parent is AssignmentStatementAst or ParameterAst && + VarDef.VariablePath.UserPath.ToLower() == name.ToLower() && + // Look Backwards from the node above + (VarDef.Extent.EndLineNumber < node.Extent.StartLineNumber || + (VarDef.Extent.EndColumnNumber <= node.Extent.StartColumnNumber && + VarDef.Extent.EndLineNumber <= node.Extent.StartLineNumber)); + }, true).Cast().ToList(); + // return the def if we have no matches + if (VariableAssignments.Count == 0) + { + return node; + } + Ast CorrectDefinition = null; + for (int i = VariableAssignments.Count - 1; i >= 0; i--) + { + VariableExpressionAst element = VariableAssignments[i]; + + Ast parent = GetAstParentScope(element); + // closest assignment statement is within the scope of the node + if (TargetParent == parent) + { + CorrectDefinition = element; + break; + } + else if (node.Parent is AssignmentStatementAst) + { + // the node is probably the first assignment statement within the scope + CorrectDefinition = node; + break; + } + // node is proably just a reference to an assignment statement or Parameter within the global scope or higher + if (node.Parent is not AssignmentStatementAst) + { + if (null == parent || null == parent.Parent) + { + // we have hit the global scope of the script file + CorrectDefinition = element; + break; + } + + if (parent is FunctionDefinitionAst funcDef && node is CommandParameterAst or StringConstantExpressionAst) + { + if (node is StringConstantExpressionAst) + { + List SplatReferences = ScriptAst.FindAll(ast => + { + return ast is VariableExpressionAst varDef && + varDef.Splatted && + varDef.Parent is CommandAst && + varDef.VariablePath.UserPath.ToLower() == splatAssignment.VariablePath.UserPath.ToLower(); + }, true).Cast().ToList(); + + if (SplatReferences.Count >= 1) + { + CommandAst splatFirstRefComm = (CommandAst)SplatReferences.First().Parent; + if (funcDef.Name == splatFirstRefComm.GetCommandName() + && funcDef.Parent.Parent == TargetParent) + { + CorrectDefinition = element; + break; + } + } + } + + if (node.Parent is CommandAst commDef) + { + if (funcDef.Name == commDef.GetCommandName() + && funcDef.Parent.Parent == TargetParent) + { + CorrectDefinition = element; + break; + } + } + } + if (WithinTargetsScope(element, node)) + { + CorrectDefinition = element; + } + } + } + return CorrectDefinition ?? node; + } + + internal static Ast GetAstParentScope(Ast node) + { + Ast parent = node; + // Walk backwards up the tree looking for a ScriptBLock of a FunctionDefinition + parent = Utilities.GetAstParentOfType(parent, typeof(ScriptBlockAst), typeof(FunctionDefinitionAst), typeof(ForEachStatementAst), typeof(ForStatementAst)); + if (parent is ScriptBlockAst && parent.Parent != null && parent.Parent is FunctionDefinitionAst) + { + parent = parent.Parent; + } + // Check if the parent of the VariableExpressionAst is a ForEachStatementAst then check if the variable names match + // if so this is probably a variable defined within a foreach loop + else if (parent is ForEachStatementAst ForEachStmnt && node is VariableExpressionAst VarExp && + ForEachStmnt.Variable.VariablePath.UserPath == VarExp.VariablePath.UserPath) + { + parent = ForEachStmnt; + } + // Check if the parent of the VariableExpressionAst is a ForStatementAst then check if the variable names match + // if so this is probably a variable defined within a foreach loop + else if (parent is ForStatementAst ForStmnt && node is VariableExpressionAst ForVarExp && + ForStmnt.Initializer is AssignmentStatementAst AssignStmnt && AssignStmnt.Left is VariableExpressionAst VarExpStmnt && + VarExpStmnt.VariablePath.UserPath == ForVarExp.VariablePath.UserPath) + { + parent = ForStmnt; + } + + return parent; + } + + internal static bool IsVariableExpressionAssignedInTargetScope(VariableExpressionAst node, Ast scope) + { + bool r = false; + + List VariableAssignments = node.FindAll(ast => + { + return ast is VariableExpressionAst VarDef && + VarDef.Parent is AssignmentStatementAst or ParameterAst && + VarDef.VariablePath.UserPath.ToLower() == node.VariablePath.UserPath.ToLower() && + // Look Backwards from the node above + (VarDef.Extent.EndLineNumber < node.Extent.StartLineNumber || + (VarDef.Extent.EndColumnNumber <= node.Extent.StartColumnNumber && + VarDef.Extent.EndLineNumber <= node.Extent.StartLineNumber)) && + // Must be within the the designated scope + VarDef.Extent.StartLineNumber >= scope.Extent.StartLineNumber; + }, true).Cast().ToList(); + + if (VariableAssignments.Count > 0) + { + r = true; + } + // Node is probably the first Assignment Statement within scope + if (node.Parent is AssignmentStatementAst && node.Extent.StartLineNumber >= scope.Extent.StartLineNumber) + { + r = true; + } + + return r; + } + + internal static bool WithinTargetsScope(Ast Target, Ast Child) + { + bool r = false; + Ast childParent = Child.Parent; + Ast TargetScope = GetAstParentScope(Target); + while (childParent != null) + { + if (childParent is FunctionDefinitionAst FuncDefAst) + { + if (Child is VariableExpressionAst VarExpAst && !IsVariableExpressionAssignedInTargetScope(VarExpAst, FuncDefAst)) + { + + }else{ + break; + } + } + if (childParent == TargetScope) + { + break; + } + childParent = childParent.Parent; + } + if (childParent == TargetScope) + { + r = true; + } + return r; + } + + public class NodeProcessingState + { + public Ast Node { get; set; } + public IEnumerator ChildrenEnumerator { get; set; } + } + + public void Visit(Ast root) + { + Stack processingStack = new(); + + processingStack.Push(new NodeProcessingState { Node = root }); + + while (processingStack.Count > 0) + { + NodeProcessingState currentState = processingStack.Peek(); + + if (currentState.ChildrenEnumerator == null) + { + // First time processing this node. Do the initial processing. + ProcessNode(currentState.Node); // This line is crucial. + + // Get the children and set up the enumerator. + IEnumerable children = currentState.Node.FindAll(ast => ast.Parent == currentState.Node, searchNestedScriptBlocks: true); + currentState.ChildrenEnumerator = children.GetEnumerator(); + } + + // Process the next child. + if (currentState.ChildrenEnumerator.MoveNext()) + { + Ast child = currentState.ChildrenEnumerator.Current; + processingStack.Push(new NodeProcessingState { Node = child }); + } + else + { + // All children have been processed, we're done with this node. + processingStack.Pop(); + } + } + } + + public void ProcessNode(Ast node) + { + + switch (node) + { + case CommandAst commandAst: + ProcessCommandAst(commandAst); + break; + case CommandParameterAst commandParameterAst: + ProcessCommandParameterAst(commandParameterAst); + break; + case VariableExpressionAst variableExpressionAst: + ProcessVariableExpressionAst(variableExpressionAst); + break; + } + } + + private void ProcessCommandAst(CommandAst commandAst) + { + // Is the Target Variable a Parameter and is this commandAst the target function + if (isParam && commandAst.GetCommandName()?.ToLower() == TargetFunction?.Name.ToLower()) + { + // Check to see if this is a splatted call to the target function. + Ast Splatted = null; + foreach (Ast element in commandAst.CommandElements) + { + if (element is VariableExpressionAst varAst && varAst.Splatted) + { + Splatted = varAst; + break; + } + } + if (Splatted != null) + { + NewSplattedModification(Splatted); + } + else + { + // The Target Variable is a Parameter and the commandAst is the Target Function + ShouldRename = true; + } + } + } + + private void ProcessVariableExpressionAst(VariableExpressionAst variableExpressionAst) + { + if (variableExpressionAst.VariablePath.UserPath.ToLower() == OldName.ToLower()) + { + // Is this the Target Variable + if (variableExpressionAst.Extent.StartColumnNumber == StartColumnNumber && + variableExpressionAst.Extent.StartLineNumber == StartLineNumber) + { + ShouldRename = true; + TargetVariableAst = variableExpressionAst; + } + // Is this a Command Ast within scope + else if (variableExpressionAst.Parent is CommandAst commandAst) + { + if (WithinTargetsScope(TargetVariableAst, commandAst)) + { + ShouldRename = true; + } + // The TargetVariable is defined within a function + // This commandAst is not within that function's scope so we should not rename + if (GetAstParentScope(TargetVariableAst) is FunctionDefinitionAst && !WithinTargetsScope(TargetVariableAst, commandAst)) + { + ShouldRename = false; + } + + } + // Is this a Variable Assignment thats not within scope + else if (variableExpressionAst.Parent is AssignmentStatementAst assignment && + assignment.Operator == TokenKind.Equals) + { + if (!WithinTargetsScope(TargetVariableAst, variableExpressionAst)) + { + ShouldRename = false; + } + + } + // Else is the variable within scope + else + { + ShouldRename = WithinTargetsScope(TargetVariableAst, variableExpressionAst); + } + if (ShouldRename) + { + // have some modifications to account for the dollar sign prefix powershell uses for variables + TextChange Change = new() + { + NewText = NewName.Contains("$") ? NewName : "$" + NewName, + StartLine = variableExpressionAst.Extent.StartLineNumber - 1, + StartColumn = variableExpressionAst.Extent.StartColumnNumber - 1, + EndLine = variableExpressionAst.Extent.StartLineNumber - 1, + EndColumn = variableExpressionAst.Extent.StartColumnNumber + OldName.Length, + }; + // If the variables parent is a parameterAst Add a modification + if (variableExpressionAst.Parent is ParameterAst paramAst && !AliasSet && + options.CreateAlias) + { + TextChange aliasChange = NewParameterAliasChange(variableExpressionAst, paramAst); + Modifications.Add(aliasChange); + AliasSet = true; + } + Modifications.Add(Change); + + } + } + } + + private void ProcessCommandParameterAst(CommandParameterAst commandParameterAst) + { + if (commandParameterAst.ParameterName.ToLower() == OldName.ToLower()) + { + if (commandParameterAst.Extent.StartLineNumber == StartLineNumber && + commandParameterAst.Extent.StartColumnNumber == StartColumnNumber) + { + ShouldRename = true; + } + + if (TargetFunction != null && commandParameterAst.Parent is CommandAst commandAst && + commandAst.GetCommandName().ToLower() == TargetFunction.Name.ToLower() && isParam && ShouldRename) + { + TextChange Change = new() + { + NewText = NewName.Contains("-") ? NewName : "-" + NewName, + StartLine = commandParameterAst.Extent.StartLineNumber - 1, + StartColumn = commandParameterAst.Extent.StartColumnNumber - 1, + EndLine = commandParameterAst.Extent.StartLineNumber - 1, + EndColumn = commandParameterAst.Extent.StartColumnNumber + OldName.Length, + }; + Modifications.Add(Change); + } + else + { + ShouldRename = false; + } + } + } + + internal void NewSplattedModification(Ast Splatted) + { + // This Function should be passed a splatted VariableExpressionAst which + // is used by a CommandAst that is the TargetFunction. + + // Find the splats top assignment / definition + Ast SplatAssignment = GetVariableTopAssignment( + Splatted.Extent.StartLineNumber, + Splatted.Extent.StartColumnNumber, + ScriptAst); + // Look for the Parameter within the Splats HashTable + if (SplatAssignment.Parent is AssignmentStatementAst assignmentStatementAst && + assignmentStatementAst.Right is CommandExpressionAst commExpAst && + commExpAst.Expression is HashtableAst hashTableAst) + { + foreach (Tuple element in hashTableAst.KeyValuePairs) + { + if (element.Item1 is StringConstantExpressionAst strConstAst && + strConstAst.Value.ToLower() == OldName.ToLower()) + { + TextChange Change = new() + { + NewText = NewName, + StartLine = strConstAst.Extent.StartLineNumber - 1, + StartColumn = strConstAst.Extent.StartColumnNumber - 1, + EndLine = strConstAst.Extent.StartLineNumber - 1, + EndColumn = strConstAst.Extent.EndColumnNumber - 1, + }; + + Modifications.Add(Change); + break; + } + + } + } + } + + internal TextChange NewParameterAliasChange(VariableExpressionAst variableExpressionAst, ParameterAst paramAst) + { + // Check if an Alias AttributeAst already exists and append the new Alias to the existing list + // Otherwise Create a new Alias Attribute + // Add the modifications to the changes + // The Attribute will be appended before the variable or in the existing location of the original alias + TextChange aliasChange = new(); + foreach (Ast Attr in paramAst.Attributes) + { + if (Attr is AttributeAst AttrAst) + { + // Alias Already Exists + if (AttrAst.TypeName.FullName == "Alias") + { + string existingEntries = AttrAst.Extent.Text + .Substring("[Alias(".Length); + existingEntries = existingEntries.Substring(0, existingEntries.Length - ")]".Length); + string nentries = existingEntries + $", \"{OldName}\""; + + aliasChange.NewText = $"[Alias({nentries})]"; + aliasChange.StartLine = Attr.Extent.StartLineNumber - 1; + aliasChange.StartColumn = Attr.Extent.StartColumnNumber - 1; + aliasChange.EndLine = Attr.Extent.StartLineNumber - 1; + aliasChange.EndColumn = Attr.Extent.EndColumnNumber - 1; + + break; + } + + } + } + if (aliasChange.NewText == null) + { + aliasChange.NewText = $"[Alias(\"{OldName}\")]"; + aliasChange.StartLine = variableExpressionAst.Extent.StartLineNumber - 1; + aliasChange.StartColumn = variableExpressionAst.Extent.StartColumnNumber - 1; + aliasChange.EndLine = variableExpressionAst.Extent.StartLineNumber - 1; + aliasChange.EndColumn = variableExpressionAst.Extent.StartColumnNumber - 1; + } + + return aliasChange; + } + + } +} diff --git a/src/PowerShellEditorServices/Services/PowerShell/Refactoring/Utilities.cs b/src/PowerShellEditorServices/Services/PowerShell/Refactoring/Utilities.cs new file mode 100644 index 000000000..3f42c6d35 --- /dev/null +++ b/src/PowerShellEditorServices/Services/PowerShell/Refactoring/Utilities.cs @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation.Language; + +namespace Microsoft.PowerShell.EditorServices.Refactoring +{ + internal class Utilities + { + + public static Ast GetAstAtPositionOfType(int StartLineNumber, int StartColumnNumber, Ast ScriptAst, params Type[] type) + { + Ast result = null; + result = ScriptAst.Find(ast => + { + return ast.Extent.StartLineNumber == StartLineNumber && + ast.Extent.StartColumnNumber == StartColumnNumber && + type.Contains(ast.GetType()); + }, true); + if (result == null) + { + throw new TargetSymbolNotFoundException(); + } + return result; + } + + public static Ast GetAstParentOfType(Ast ast, params Type[] type) + { + Ast parent = ast; + // walk backwards till we hit a parent of the specified type or return null + while (null != parent) + { + if (type.Contains(parent.GetType())) + { + return parent; + } + parent = parent.Parent; + } + return null; + + } + + public static FunctionDefinitionAst GetFunctionDefByCommandAst(string OldName, int StartLineNumber, int StartColumnNumber, Ast ScriptFile) + { + // Look up the targeted object + CommandAst TargetCommand = (CommandAst)Utilities.GetAstAtPositionOfType(StartLineNumber, StartColumnNumber, ScriptFile + , typeof(CommandAst)); + + if (TargetCommand.GetCommandName().ToLower() != OldName.ToLower()) + { + TargetCommand = null; + } + + string FunctionName = TargetCommand.GetCommandName(); + + List FunctionDefinitions = ScriptFile.FindAll(ast => + { + return ast is FunctionDefinitionAst FuncDef && + FuncDef.Name.ToLower() == OldName.ToLower() && + (FuncDef.Extent.EndLineNumber < TargetCommand.Extent.StartLineNumber || + (FuncDef.Extent.EndColumnNumber <= TargetCommand.Extent.StartColumnNumber && + FuncDef.Extent.EndLineNumber <= TargetCommand.Extent.StartLineNumber)); + }, true).Cast().ToList(); + // return the function def if we only have one match + if (FunctionDefinitions.Count == 1) + { + return FunctionDefinitions[0]; + } + // Determine which function definition is the right one + FunctionDefinitionAst CorrectDefinition = null; + for (int i = FunctionDefinitions.Count - 1; i >= 0; i--) + { + FunctionDefinitionAst element = FunctionDefinitions[i]; + + Ast parent = element.Parent; + // walk backwards till we hit a functiondefinition if any + while (null != parent) + { + if (parent is FunctionDefinitionAst) + { + break; + } + parent = parent.Parent; + } + // we have hit the global scope of the script file + if (null == parent) + { + CorrectDefinition = element; + break; + } + + if (TargetCommand.Parent == parent) + { + CorrectDefinition = (FunctionDefinitionAst)parent; + } + } + return CorrectDefinition; + } + + public static bool AssertContainsDotSourced(Ast ScriptAst) + { + Ast dotsourced = ScriptAst.Find(ast => + { + return ast is CommandAst commandAst && commandAst.InvocationOperator == TokenKind.Dot; + }, true); + if (dotsourced != null) + { + return true; + } + return false; + } + public static Ast GetAst(int StartLineNumber, int StartColumnNumber, Ast Ast) + { + Ast token = null; + + token = Ast.Find(ast => + { + return StartLineNumber == ast.Extent.StartLineNumber && + ast.Extent.EndColumnNumber >= StartColumnNumber && + StartColumnNumber >= ast.Extent.StartColumnNumber; + }, true); + + if (token is NamedBlockAst) + { + // NamedBlockAST starts on the same line as potentially another AST, + // its likley a user is not after the NamedBlockAst but what it contains + IEnumerable stacked_tokens = token.FindAll(ast => + { + return StartLineNumber == ast.Extent.StartLineNumber && + ast.Extent.EndColumnNumber >= StartColumnNumber + && StartColumnNumber >= ast.Extent.StartColumnNumber; + }, true); + + if (stacked_tokens.Count() > 1) + { + return stacked_tokens.LastOrDefault(); + } + + return token.Parent; + } + + if (null == token) + { + IEnumerable LineT = Ast.FindAll(ast => + { + return StartLineNumber == ast.Extent.StartLineNumber && + StartColumnNumber >= ast.Extent.StartColumnNumber; + }, true); + return LineT.OfType()?.LastOrDefault(); + } + + IEnumerable tokens = token.FindAll(ast => + { + return ast.Extent.EndColumnNumber >= StartColumnNumber + && StartColumnNumber >= ast.Extent.StartColumnNumber; + }, true); + if (tokens.Count() > 1) + { + token = tokens.LastOrDefault(); + } + return token; + } + } +} diff --git a/src/PowerShellEditorServices/Utility/HandlerErrorException.cs b/src/PowerShellEditorServices/Utility/HandlerErrorException.cs new file mode 100644 index 000000000..14c3b949e --- /dev/null +++ b/src/PowerShellEditorServices/Utility/HandlerErrorException.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using OmniSharp.Extensions.JsonRpc; +using OmniSharp.Extensions.LanguageServer.Protocol.Models; + +namespace Microsoft.PowerShell.EditorServices.Handlers; + +/// +/// A convenience exception for handlers to throw when a request fails for a normal reason, +/// and to communicate that reason to the user without a full internal stacktrace. +/// +/// The message describing the reason for the request failure. +/// Additional details to be logged regarding the failure. It should be serializable to JSON. +/// The severity level of the message. This is only shown in internal logging. +public class HandlerErrorException +( + string message, + object logDetails = null, + MessageType severity = MessageType.Error +) : RpcErrorException((int)severity, logDetails!, message) +{ } diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCallWIthinStringExpression.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCallWIthinStringExpression.ps1 new file mode 100644 index 000000000..944e6d5df --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCallWIthinStringExpression.ps1 @@ -0,0 +1,3 @@ +function foo { + write-host "This will do recursion ... $(foo)" +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCallWIthinStringExpressionRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCallWIthinStringExpressionRenamed.ps1 new file mode 100644 index 000000000..44d843c5a --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCallWIthinStringExpressionRenamed.ps1 @@ -0,0 +1,3 @@ +function Renamed { + write-host "This will do recursion ... $(Renamed)" +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCmdlet.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCmdlet.ps1 new file mode 100644 index 000000000..1614b63a9 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCmdlet.ps1 @@ -0,0 +1,21 @@ +function Testing-Foo { + [CmdletBinding(SupportsShouldProcess)] + param ( + $Text, + $Param + ) + + begin { + if ($PSCmdlet.ShouldProcess("Target", "Operation")) { + Testing-Foo -Text "Param" -Param [1,2,3] + } + } + + process { + Testing-Foo -Text "Param" -Param [1,2,3] + } + + end { + Testing-Foo -Text "Param" -Param [1,2,3] + } +} \ No newline at end of file diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCmdletRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCmdletRenamed.ps1 new file mode 100644 index 000000000..ee14a9fb7 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionCmdletRenamed.ps1 @@ -0,0 +1,21 @@ +function Renamed { + [CmdletBinding(SupportsShouldProcess)] + param ( + $Text, + $Param + ) + + begin { + if ($PSCmdlet.ShouldProcess("Target", "Operation")) { + Renamed -Text "Param" -Param [1,2,3] + } + } + + process { + Renamed -Text "Param" -Param [1,2,3] + } + + end { + Renamed -Text "Param" -Param [1,2,3] + } +} \ No newline at end of file diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeach.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeach.ps1 new file mode 100644 index 000000000..2454effe6 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeach.ps1 @@ -0,0 +1,17 @@ +$x = 1..10 + +function testing_files { + param ( + $x + ) + write-host "Printing $x" +} + +foreach ($number in $x) { + testing_files $number + + function testing_files { + write-host "------------------" + } +} +testing_files "99" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeachObject.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeachObject.ps1 new file mode 100644 index 000000000..107c50223 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeachObject.ps1 @@ -0,0 +1,17 @@ +$x = 1..10 + +function testing_files { + param ( + $x + ) + write-host "Printing $x" +} + +$x | ForEach-Object { + testing_files $_ + + function testing_files { + write-host "------------------" + } +} +testing_files "99" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeachObjectRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeachObjectRenamed.ps1 new file mode 100644 index 000000000..80073c640 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeachObjectRenamed.ps1 @@ -0,0 +1,17 @@ +$x = 1..10 + +function Renamed { + param ( + $x + ) + write-host "Printing $x" +} + +$x | ForEach-Object { + Renamed $_ + + function testing_files { + write-host "------------------" + } +} +Renamed "99" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeachRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeachRenamed.ps1 new file mode 100644 index 000000000..cd0dcb424 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionForeachRenamed.ps1 @@ -0,0 +1,17 @@ +$x = 1..10 + +function Renamed { + param ( + $x + ) + write-host "Printing $x" +} + +foreach ($number in $x) { + Renamed $number + + function testing_files { + write-host "------------------" + } +} +Renamed "99" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionInnerIsNested.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionInnerIsNested.ps1 new file mode 100644 index 000000000..8e99c337b --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionInnerIsNested.ps1 @@ -0,0 +1,13 @@ +function outer { + function foo { + Write-Host "Inside nested foo" + } + foo +} + +function foo { + Write-Host "Inside top-level foo" +} + +outer +foo diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionInnerIsNestedRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionInnerIsNestedRenamed.ps1 new file mode 100644 index 000000000..2231571ef --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionInnerIsNestedRenamed.ps1 @@ -0,0 +1,13 @@ +function outer { + function bar { + Write-Host "Inside nested foo" + } + bar +} + +function foo { + Write-Host "Inside top-level foo" +} + +outer +foo diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionLoop.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionLoop.ps1 new file mode 100644 index 000000000..6973855a7 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionLoop.ps1 @@ -0,0 +1,6 @@ +for ($i = 0; $i -lt 2; $i++) { + function FunctionInLoop { + Write-Host "Function inside a loop" + } + FunctionInLoop +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionLoopRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionLoopRenamed.ps1 new file mode 100644 index 000000000..6e7632c46 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionLoopRenamed.ps1 @@ -0,0 +1,6 @@ +for ($i = 0; $i -lt 2; $i++) { + function Renamed { + Write-Host "Function inside a loop" + } + Renamed +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionMultipleOccurrences.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionMultipleOccurrences.ps1 new file mode 100644 index 000000000..76aeced88 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionMultipleOccurrences.ps1 @@ -0,0 +1,6 @@ +function foo { + Write-Host "Inside foo" +} + +foo +foo diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionMultipleOccurrencesRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionMultipleOccurrencesRenamed.ps1 new file mode 100644 index 000000000..cb78322be --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionMultipleOccurrencesRenamed.ps1 @@ -0,0 +1,6 @@ +function Renamed { + Write-Host "Inside foo" +} + +Renamed +Renamed diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionNestedRedefinition.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionNestedRedefinition.ps1 new file mode 100644 index 000000000..2454effe6 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionNestedRedefinition.ps1 @@ -0,0 +1,17 @@ +$x = 1..10 + +function testing_files { + param ( + $x + ) + write-host "Printing $x" +} + +foreach ($number in $x) { + testing_files $number + + function testing_files { + write-host "------------------" + } +} +testing_files "99" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionNestedRedefinitionRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionNestedRedefinitionRenamed.ps1 new file mode 100644 index 000000000..304a97c87 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionNestedRedefinitionRenamed.ps1 @@ -0,0 +1,17 @@ +$x = 1..10 + +function testing_files { + param ( + $x + ) + write-host "Printing $x" +} + +foreach ($number in $x) { + testing_files $number + + function Renamed { + write-host "------------------" + } +} +testing_files "99" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionOuterHasNestedFunction.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionOuterHasNestedFunction.ps1 new file mode 100644 index 000000000..966fdccb7 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionOuterHasNestedFunction.ps1 @@ -0,0 +1,7 @@ +function OuterFunction { + function NewInnerFunction { + Write-Host "This is the inner function" + } + NewInnerFunction +} +OuterFunction diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionOuterHasNestedFunctionRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionOuterHasNestedFunctionRenamed.ps1 new file mode 100644 index 000000000..98f89d16f --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionOuterHasNestedFunctionRenamed.ps1 @@ -0,0 +1,7 @@ +function Renamed { + function NewInnerFunction { + Write-Host "This is the inner function" + } + NewInnerFunction +} +Renamed diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionSameName.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionSameName.ps1 new file mode 100644 index 000000000..726ea6d56 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionSameName.ps1 @@ -0,0 +1,8 @@ +function SameNameFunction { + Write-Host "This is the outer function" + function SameNameFunction { + Write-Host "This is the inner function" + } + SameNameFunction +} +SameNameFunction diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionSameNameRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionSameNameRenamed.ps1 new file mode 100644 index 000000000..669266740 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionSameNameRenamed.ps1 @@ -0,0 +1,8 @@ +function SameNameFunction { + Write-Host "This is the outer function" + function RenamedSameNameFunction { + Write-Host "This is the inner function" + } + RenamedSameNameFunction +} +SameNameFunction diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionScriptblock.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionScriptblock.ps1 new file mode 100644 index 000000000..de0fd1737 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionScriptblock.ps1 @@ -0,0 +1,7 @@ +$scriptBlock = { + function FunctionInScriptBlock { + Write-Host "Inside a script block" + } + FunctionInScriptBlock +} +& $scriptBlock diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionScriptblockRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionScriptblockRenamed.ps1 new file mode 100644 index 000000000..727ca6f58 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionScriptblockRenamed.ps1 @@ -0,0 +1,7 @@ +$scriptBlock = { + function Renamed { + Write-Host "Inside a script block" + } + Renamed +} +& $scriptBlock diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInnerFunction.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInnerFunction.ps1 new file mode 100644 index 000000000..966fdccb7 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInnerFunction.ps1 @@ -0,0 +1,7 @@ +function OuterFunction { + function NewInnerFunction { + Write-Host "This is the inner function" + } + NewInnerFunction +} +OuterFunction diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInnerFunctionRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInnerFunctionRenamed.ps1 new file mode 100644 index 000000000..47e51012e --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInnerFunctionRenamed.ps1 @@ -0,0 +1,7 @@ +function OuterFunction { + function RenamedInnerFunction { + Write-Host "This is the inner function" + } + RenamedInnerFunction +} +OuterFunction diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInternalCalls.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInternalCalls.ps1 new file mode 100644 index 000000000..eae1f3a19 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInternalCalls.ps1 @@ -0,0 +1,5 @@ +function FunctionWithInternalCalls { + Write-Host "This function calls itself" + FunctionWithInternalCalls +} +FunctionWithInternalCalls diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInternalCallsRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInternalCallsRenamed.ps1 new file mode 100644 index 000000000..4926dffb9 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionWithInternalCallsRenamed.ps1 @@ -0,0 +1,5 @@ +function Renamed { + Write-Host "This function calls itself" + Renamed +} +Renamed diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionsSingle.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionsSingle.ps1 new file mode 100644 index 000000000..e13582550 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionsSingle.ps1 @@ -0,0 +1,5 @@ +function foo { + Write-Host "Inside foo" +} + +foo diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionsSingleRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionsSingleRenamed.ps1 new file mode 100644 index 000000000..26ffe37f9 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/FunctionsSingleRenamed.ps1 @@ -0,0 +1,5 @@ +function Renamed { + Write-Host "Inside foo" +} + +Renamed diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/RefactorsFunctionData.cs b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/RefactorsFunctionData.cs new file mode 100644 index 000000000..218257602 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Functions/RefactorsFunctionData.cs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using Microsoft.PowerShell.EditorServices.Handlers; + +namespace PowerShellEditorServices.Test.Shared.Refactoring.Functions +{ + internal class RefactorsFunctionData + { + + public static readonly RenameSymbolParams FunctionsSingle = new() + { + FileName = "FunctionsSingle.ps1", + Column = 1, + Line = 5, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams FunctionMultipleOccurrences = new() + { + FileName = "FunctionMultipleOccurrences.ps1", + Column = 1, + Line = 5, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams FunctionInnerIsNested = new() + { + FileName = "FunctionInnerIsNested.ps1", + Column = 5, + Line = 5, + RenameTo = "bar" + }; + public static readonly RenameSymbolParams FunctionOuterHasNestedFunction = new() + { + FileName = "FunctionOuterHasNestedFunction.ps1", + Column = 10, + Line = 1, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams FunctionWithInnerFunction = new() + { + FileName = "FunctionWithInnerFunction.ps1", + Column = 5, + Line = 5, + RenameTo = "RenamedInnerFunction" + }; + public static readonly RenameSymbolParams FunctionWithInternalCalls = new() + { + FileName = "FunctionWithInternalCalls.ps1", + Column = 1, + Line = 5, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams FunctionCmdlet = new() + { + FileName = "FunctionCmdlet.ps1", + Column = 10, + Line = 1, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams FunctionSameName = new() + { + FileName = "FunctionSameName.ps1", + Column = 14, + Line = 3, + RenameTo = "RenamedSameNameFunction" + }; + public static readonly RenameSymbolParams FunctionScriptblock = new() + { + FileName = "FunctionScriptblock.ps1", + Column = 5, + Line = 5, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams FunctionLoop = new() + { + FileName = "FunctionLoop.ps1", + Column = 5, + Line = 5, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams FunctionForeach = new() + { + FileName = "FunctionForeach.ps1", + Column = 5, + Line = 11, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams FunctionForeachObject = new() + { + FileName = "FunctionForeachObject.ps1", + Column = 5, + Line = 11, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams FunctionCallWIthinStringExpression = new() + { + FileName = "FunctionCallWIthinStringExpression.ps1", + Column = 10, + Line = 1, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams FunctionNestedRedefinition = new() + { + FileName = "FunctionNestedRedefinition.ps1", + Column = 15, + Line = 13, + RenameTo = "Renamed" + }; + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/RefactorUtilitiesData.cs b/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/RefactorUtilitiesData.cs new file mode 100644 index 000000000..5cc1ea89d --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/RefactorUtilitiesData.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using Microsoft.PowerShell.EditorServices.Handlers; + +namespace PowerShellEditorServices.Test.Shared.Refactoring.Utilities +{ + internal static class RenameUtilitiesData + { + + public static readonly RenameSymbolParams GetVariableExpressionAst = new() + { + Column = 11, + Line = 15, + RenameTo = "Renamed", + FileName = "TestDetection.ps1" + }; + public static readonly RenameSymbolParams GetVariableExpressionStartAst = new() + { + Column = 1, + Line = 15, + RenameTo = "Renamed", + FileName = "TestDetection.ps1" + }; + public static readonly RenameSymbolParams GetVariableWithinParameterAst = new() + { + Column = 21, + Line = 3, + RenameTo = "Renamed", + FileName = "TestDetection.ps1" + }; + public static readonly RenameSymbolParams GetHashTableKey = new() + { + Column = 9, + Line = 16, + RenameTo = "Renamed", + FileName = "TestDetection.ps1" + }; + public static readonly RenameSymbolParams GetVariableWithinCommandAst = new() + { + Column = 29, + Line = 6, + RenameTo = "Renamed", + FileName = "TestDetection.ps1" + }; + public static readonly RenameSymbolParams GetCommandParameterAst = new() + { + Column = 12, + Line = 21, + RenameTo = "Renamed", + FileName = "TestDetection.ps1" + }; + public static readonly RenameSymbolParams GetFunctionDefinitionAst = new() + { + Column = 12, + Line = 1, + RenameTo = "Renamed", + FileName = "TestDetection.ps1" + }; + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDetection.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDetection.ps1 new file mode 100644 index 000000000..d12a8652f --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDetection.ps1 @@ -0,0 +1,21 @@ +function New-User { + param ( + [string]$Username, + [string]$password + ) + write-host $username + $password + + $splat= @{ + Username = "JohnDeer" + Password = "SomePassword" + } + New-User @splat +} + +$UserDetailsSplat= @{ + Username = "JohnDoe" + Password = "SomePassword" +} +New-User @UserDetailsSplat + +New-User -Username "JohnDoe" -Password "SomePassword" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDetectionUnderFunctionDef.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDetectionUnderFunctionDef.ps1 new file mode 100644 index 000000000..6ef6e2652 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDetectionUnderFunctionDef.ps1 @@ -0,0 +1,15 @@ +function Write-Item($itemCount) { + $i = 1 + + while ($i -le $itemCount) { + $str = "Output $i" + Write-Output $str + + # In the gutter on the left, right click and select "Add Conditional Breakpoint" + # on the next line. Use the condition: $i -eq 25 + $i = $i + 1 + + # Slow down execution a bit so user can test the "Pause debugger" feature. + Start-Sleep -Milliseconds $DelayMilliseconds + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDotSourcingFalse.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDotSourcingFalse.ps1 new file mode 100644 index 000000000..d12a8652f --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDotSourcingFalse.ps1 @@ -0,0 +1,21 @@ +function New-User { + param ( + [string]$Username, + [string]$password + ) + write-host $username + $password + + $splat= @{ + Username = "JohnDeer" + Password = "SomePassword" + } + New-User @splat +} + +$UserDetailsSplat= @{ + Username = "JohnDoe" + Password = "SomePassword" +} +New-User @UserDetailsSplat + +New-User -Username "JohnDoe" -Password "SomePassword" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDotSourcingTrue.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDotSourcingTrue.ps1 new file mode 100644 index 000000000..b1cd25e65 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Utilities/TestDotSourcingTrue.ps1 @@ -0,0 +1,7 @@ +$sb = { $var = 30 } +$shouldDotSource = Get-Random -Minimum 0 -Maximum 2 +if ($shouldDotSource) { + . $sb +} else { + & $sb +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/RefactorVariablesData.cs b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/RefactorVariablesData.cs new file mode 100644 index 000000000..ab166b165 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/RefactorVariablesData.cs @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using Microsoft.PowerShell.EditorServices.Handlers; + +namespace PowerShellEditorServices.Test.Shared.Refactoring.Variables +{ + internal static class RenameVariableData + { + + public static readonly RenameSymbolParams SimpleVariableAssignment = new() + { + FileName = "SimpleVariableAssignment.ps1", + Column = 1, + Line = 1, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableRedefinition = new() + { + FileName = "VariableRedefinition.ps1", + Column = 1, + Line = 1, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableNestedScopeFunction = new() + { + FileName = "VariableNestedScopeFunction.ps1", + Column = 1, + Line = 1, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableInLoop = new() + { + FileName = "VariableInLoop.ps1", + Column = 1, + Line = 1, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableInPipeline = new() + { + FileName = "VariableInPipeline.ps1", + Column = 23, + Line = 2, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableInScriptblock = new() + { + FileName = "VariableInScriptblock.ps1", + Column = 26, + Line = 2, + RenameTo = "Renamed" + }; + + public static readonly RenameSymbolParams VariableInScriptblockScoped = new() + { + FileName = "VariableInScriptblockScoped.ps1", + Column = 36, + Line = 2, + RenameTo = "Renamed" + }; + + public static readonly RenameSymbolParams VariablewWithinHastableExpression = new() + { + FileName = "VariablewWithinHastableExpression.ps1", + Column = 46, + Line = 3, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableNestedFunctionScriptblock = new() + { + FileName = "VariableNestedFunctionScriptblock.ps1", + Column = 20, + Line = 4, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableWithinCommandAstScriptBlock = new() + { + FileName = "VariableWithinCommandAstScriptBlock.ps1", + Column = 75, + Line = 3, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableWithinForeachObject = new() + { + FileName = "VariableWithinForeachObject.ps1", + Column = 1, + Line = 2, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableusedInWhileLoop = new() + { + FileName = "VariableusedInWhileLoop.ps1", + Column = 5, + Line = 2, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableInParam = new() + { + FileName = "VariableInParam.ps1", + Column = 16, + Line = 24, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableCommandParameter = new() + { + FileName = "VariableCommandParameter.ps1", + Column = 9, + Line = 10, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableCommandParameterReverse = new() + { + FileName = "VariableCommandParameter.ps1", + Column = 17, + Line = 3, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableScriptWithParamBlock = new() + { + FileName = "VariableScriptWithParamBlock.ps1", + Column = 28, + Line = 1, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableNonParam = new() + { + FileName = "VariableNonParam.ps1", + Column = 1, + Line = 7, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableParameterCommandWithSameName = new() + { + FileName = "VariableParameterCommndWithSameName.ps1", + Column = 13, + Line = 9, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableCommandParameterSplattedFromCommandAst = new() + { + FileName = "VariableCommandParameterSplatted.ps1", + Column = 10, + Line = 21, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableCommandParameterSplattedFromSplat = new() + { + FileName = "VariableCommandParameterSplatted.ps1", + Column = 5, + Line = 16, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableInForeachDuplicateAssignment = new() + { + FileName = "VariableInForeachDuplicateAssignment.ps1", + Column = 18, + Line = 6, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableInForloopDuplicateAssignment = new() + { + FileName = "VariableInForloopDuplicateAssignment.ps1", + Column = 14, + Line = 9, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableNestedScopeFunctionRefactorInner = new() + { + FileName = "VariableNestedScopeFunctionRefactorInner.ps1", + Column = 5, + Line = 3, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableSimpleFunctionParameter = new() + { + FileName = "VariableSimpleFunctionParameter.ps1", + Column = 9, + Line = 6, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableDotNotationFromInnerFunction = new() + { + FileName = "VariableDotNotationFromInnerFunction.ps1", + Column = 26, + Line = 11, + RenameTo = "Renamed" + }; + public static readonly RenameSymbolParams VariableDotNotationFromOuterVar = new() + { + FileName = "VariableDotNotationFromInnerFunction.ps1", + Column = 1, + Line = 1, + RenameTo = "Renamed" + }; + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/SimpleVariableAssignment.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/SimpleVariableAssignment.ps1 new file mode 100644 index 000000000..6097d4154 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/SimpleVariableAssignment.ps1 @@ -0,0 +1,2 @@ +$var = 10 +Write-Output $var diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/SimpleVariableAssignmentRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/SimpleVariableAssignmentRenamed.ps1 new file mode 100644 index 000000000..3962ce503 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/SimpleVariableAssignmentRenamed.ps1 @@ -0,0 +1,2 @@ +$Renamed = 10 +Write-Output $Renamed diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameter.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameter.ps1 new file mode 100644 index 000000000..18eeb1e03 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameter.ps1 @@ -0,0 +1,10 @@ +function Get-foo { + param ( + [string]$string, + [int]$pos + ) + + return $string[$pos] + +} +Get-foo -string "Hello" -pos -1 diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameterRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameterRenamed.ps1 new file mode 100644 index 000000000..1e6ac9d0f --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameterRenamed.ps1 @@ -0,0 +1,10 @@ +function Get-foo { + param ( + [string][Alias("string")]$Renamed, + [int]$pos + ) + + return $Renamed[$pos] + +} +Get-foo -Renamed "Hello" -pos -1 diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameterSplatted.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameterSplatted.ps1 new file mode 100644 index 000000000..d12a8652f --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameterSplatted.ps1 @@ -0,0 +1,21 @@ +function New-User { + param ( + [string]$Username, + [string]$password + ) + write-host $username + $password + + $splat= @{ + Username = "JohnDeer" + Password = "SomePassword" + } + New-User @splat +} + +$UserDetailsSplat= @{ + Username = "JohnDoe" + Password = "SomePassword" +} +New-User @UserDetailsSplat + +New-User -Username "JohnDoe" -Password "SomePassword" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameterSplattedRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameterSplattedRenamed.ps1 new file mode 100644 index 000000000..c799fd852 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableCommandParameterSplattedRenamed.ps1 @@ -0,0 +1,21 @@ +function New-User { + param ( + [string][Alias("Username")]$Renamed, + [string]$password + ) + write-host $Renamed + $password + + $splat= @{ + Renamed = "JohnDeer" + Password = "SomePassword" + } + New-User @splat +} + +$UserDetailsSplat= @{ + Renamed = "JohnDoe" + Password = "SomePassword" +} +New-User @UserDetailsSplat + +New-User -Renamed "JohnDoe" -Password "SomePassword" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableDotNotationFromInnerFunction.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableDotNotationFromInnerFunction.ps1 new file mode 100644 index 000000000..126a2745d --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableDotNotationFromInnerFunction.ps1 @@ -0,0 +1,21 @@ +$NeededTools = @{ + OpenSsl = 'openssl for macOS' + PowerShellGet = 'PowerShellGet latest' + InvokeBuild = 'InvokeBuild latest' +} + +function getMissingTools () { + $missingTools = @() + + if (needsOpenSsl) { + $missingTools += $NeededTools.OpenSsl + } + if (needsPowerShellGet) { + $missingTools += $NeededTools.PowerShellGet + } + if (needsInvokeBuild) { + $missingTools += $NeededTools.InvokeBuild + } + + return $missingTools +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableDotNotationFromInnerFunctionRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableDotNotationFromInnerFunctionRenamed.ps1 new file mode 100644 index 000000000..d8c478ec6 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableDotNotationFromInnerFunctionRenamed.ps1 @@ -0,0 +1,21 @@ +$Renamed = @{ + OpenSsl = 'openssl for macOS' + PowerShellGet = 'PowerShellGet latest' + InvokeBuild = 'InvokeBuild latest' +} + +function getMissingTools () { + $missingTools = @() + + if (needsOpenSsl) { + $missingTools += $Renamed.OpenSsl + } + if (needsPowerShellGet) { + $missingTools += $Renamed.PowerShellGet + } + if (needsInvokeBuild) { + $missingTools += $Renamed.InvokeBuild + } + + return $missingTools +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForeachDuplicateAssignment.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForeachDuplicateAssignment.ps1 new file mode 100644 index 000000000..ba03d8eb3 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForeachDuplicateAssignment.ps1 @@ -0,0 +1,14 @@ + +$a = 1..5 +$b = 6..10 +function test { + process { + foreach ($testvar in $a) { + $testvar + } + + foreach ($testvar in $b) { + $testvar + } + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForeachDuplicateAssignmentRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForeachDuplicateAssignmentRenamed.ps1 new file mode 100644 index 000000000..4467e88cb --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForeachDuplicateAssignmentRenamed.ps1 @@ -0,0 +1,14 @@ + +$a = 1..5 +$b = 6..10 +function test { + process { + foreach ($Renamed in $a) { + $Renamed + } + + foreach ($testvar in $b) { + $testvar + } + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForloopDuplicateAssignment.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForloopDuplicateAssignment.ps1 new file mode 100644 index 000000000..66844c960 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForloopDuplicateAssignment.ps1 @@ -0,0 +1,17 @@ + +$a = 1..5 +$b = 6..10 +function test { + process { + + $i=10 + + for ($i = 0; $i -lt $a.Count; $i++) { + $i + } + + for ($i = 0; $i -lt $a.Count; $i++) { + $i + } + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForloopDuplicateAssignmentRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForloopDuplicateAssignmentRenamed.ps1 new file mode 100644 index 000000000..ff61eb4f6 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInForloopDuplicateAssignmentRenamed.ps1 @@ -0,0 +1,17 @@ + +$a = 1..5 +$b = 6..10 +function test { + process { + + $i=10 + + for ($Renamed = 0; $Renamed -lt $a.Count; $Renamed++) { + $Renamed + } + + for ($i = 0; $i -lt $a.Count; $i++) { + $i + } + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInLoop.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInLoop.ps1 new file mode 100644 index 000000000..bf5af6be8 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInLoop.ps1 @@ -0,0 +1,4 @@ +$var = 10 +for ($i = 0; $i -lt $var; $i++) { + Write-Output "Count: $i" +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInLoopRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInLoopRenamed.ps1 new file mode 100644 index 000000000..cfc98f0d5 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInLoopRenamed.ps1 @@ -0,0 +1,4 @@ +$Renamed = 10 +for ($i = 0; $i -lt $Renamed; $i++) { + Write-Output "Count: $i" +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInParam.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInParam.ps1 new file mode 100644 index 000000000..478990bfd --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInParam.ps1 @@ -0,0 +1,28 @@ +param([int]$Count=50, [int]$DelayMilliseconds=200) + +function Write-Item($itemCount) { + $i = 1 + + while ($i -le $itemCount) { + $str = "Output $i" + Write-Output $str + + # In the gutter on the left, right click and select "Add Conditional Breakpoint" + # on the next line. Use the condition: $i -eq 25 + $i = $i + 1 + + # Slow down execution a bit so user can test the "Pause debugger" feature. + Start-Sleep -Milliseconds $DelayMilliseconds + } +} + +# Do-Work will be underlined in green if you haven't disable script analysis. +# Hover over the function name below to see the PSScriptAnalyzer warning that "Do-Work" +# doesn't use an approved verb. +function Do-Work($workCount) { + Write-Output "Doing work..." + Write-Item $workcount + Write-Host "Done!" +} + +Do-Work $Count diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInParamRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInParamRenamed.ps1 new file mode 100644 index 000000000..4f567188c --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInParamRenamed.ps1 @@ -0,0 +1,28 @@ +param([int]$Count=50, [int]$DelayMilliseconds=200) + +function Write-Item($itemCount) { + $i = 1 + + while ($i -le $itemCount) { + $str = "Output $i" + Write-Output $str + + # In the gutter on the left, right click and select "Add Conditional Breakpoint" + # on the next line. Use the condition: $i -eq 25 + $i = $i + 1 + + # Slow down execution a bit so user can test the "Pause debugger" feature. + Start-Sleep -Milliseconds $DelayMilliseconds + } +} + +# Do-Work will be underlined in green if you haven't disable script analysis. +# Hover over the function name below to see the PSScriptAnalyzer warning that "Do-Work" +# doesn't use an approved verb. +function Do-Work([Alias("workCount")]$Renamed) { + Write-Output "Doing work..." + Write-Item $Renamed + Write-Host "Done!" +} + +Do-Work $Count diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInPipeline.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInPipeline.ps1 new file mode 100644 index 000000000..036a9b108 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInPipeline.ps1 @@ -0,0 +1,3 @@ +1..10 | +Where-Object { $_ -le $oldVarName } | +Write-Output diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInPipelineRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInPipelineRenamed.ps1 new file mode 100644 index 000000000..34af48896 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInPipelineRenamed.ps1 @@ -0,0 +1,3 @@ +1..10 | +Where-Object { $_ -le $Renamed } | +Write-Output diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblock.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblock.ps1 new file mode 100644 index 000000000..9c6609aa2 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblock.ps1 @@ -0,0 +1,3 @@ +$var = "Hello" +$action = { Write-Output $var } +&$action diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblockRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblockRenamed.ps1 new file mode 100644 index 000000000..5dcbd9a67 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblockRenamed.ps1 @@ -0,0 +1,3 @@ +$Renamed = "Hello" +$action = { Write-Output $Renamed } +&$action diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblockScoped.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblockScoped.ps1 new file mode 100644 index 000000000..76439a890 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblockScoped.ps1 @@ -0,0 +1,3 @@ +$var = "Hello" +$action = { $var="No";Write-Output $var } +&$action diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblockScopedRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblockScopedRenamed.ps1 new file mode 100644 index 000000000..54e1d31e4 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableInScriptblockScopedRenamed.ps1 @@ -0,0 +1,3 @@ +$var = "Hello" +$action = { $Renamed="No";Write-Output $Renamed } +&$action diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedFunctionScriptblock.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedFunctionScriptblock.ps1 new file mode 100644 index 000000000..393b2bdfd --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedFunctionScriptblock.ps1 @@ -0,0 +1,9 @@ +function Sample{ + $var = "Hello" + $sb = { + write-host $var + } + & $sb + $var +} +Sample diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedFunctionScriptblockRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedFunctionScriptblockRenamed.ps1 new file mode 100644 index 000000000..70a51b6b6 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedFunctionScriptblockRenamed.ps1 @@ -0,0 +1,9 @@ +function Sample{ + $Renamed = "Hello" + $sb = { + write-host $Renamed + } + & $sb + $Renamed +} +Sample diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunction.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunction.ps1 new file mode 100644 index 000000000..3c6c22651 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunction.ps1 @@ -0,0 +1,7 @@ +$var = 10 +function TestFunction { + $var = 20 + Write-Output $var +} +TestFunction +Write-Output $var diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunctionRefactorInner.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunctionRefactorInner.ps1 new file mode 100644 index 000000000..3c6c22651 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunctionRefactorInner.ps1 @@ -0,0 +1,7 @@ +$var = 10 +function TestFunction { + $var = 20 + Write-Output $var +} +TestFunction +Write-Output $var diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunctionRefactorInnerRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunctionRefactorInnerRenamed.ps1 new file mode 100644 index 000000000..d943f509a --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunctionRefactorInnerRenamed.ps1 @@ -0,0 +1,7 @@ +$var = 10 +function TestFunction { + $Renamed = 20 + Write-Output $Renamed +} +TestFunction +Write-Output $var diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunctionRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunctionRenamed.ps1 new file mode 100644 index 000000000..3886cf867 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNestedScopeFunctionRenamed.ps1 @@ -0,0 +1,7 @@ +$Renamed = 10 +function TestFunction { + $var = 20 + Write-Output $var +} +TestFunction +Write-Output $Renamed diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNonParam.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNonParam.ps1 new file mode 100644 index 000000000..78119ac37 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNonParam.ps1 @@ -0,0 +1,8 @@ +$params = @{ + HtmlBodyContent = "Testing JavaScript and CSS paths..." + JavaScriptPaths = ".\Assets\script.js" + StyleSheetPaths = ".\Assets\style.css" +} + +$view = New-VSCodeHtmlContentView -Title "Test View" -ShowInColumn Two +Set-VSCodeHtmlContentView -View $view @params diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNonParamRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNonParamRenamed.ps1 new file mode 100644 index 000000000..e6858827b --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableNonParamRenamed.ps1 @@ -0,0 +1,8 @@ +$params = @{ + HtmlBodyContent = "Testing JavaScript and CSS paths..." + JavaScriptPaths = ".\Assets\script.js" + StyleSheetPaths = ".\Assets\style.css" +} + +$Renamed = New-VSCodeHtmlContentView -Title "Test View" -ShowInColumn Two +Set-VSCodeHtmlContentView -View $Renamed @params diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableParameterCommndWithSameName.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableParameterCommndWithSameName.ps1 new file mode 100644 index 000000000..650271316 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableParameterCommndWithSameName.ps1 @@ -0,0 +1,56 @@ +function Test-AADConnected { + + param ( + [Parameter(Mandatory = $false)][Alias("UPName")][String]$UserPrincipalName + ) + Begin {} + Process { + [HashTable]$ConnectAADSplat = @{} + if ($UserPrincipalName) { + $ConnectAADSplat = @{ + AccountId = $UserPrincipalName + ErrorAction = 'Stop' + } + } + } +} + +function Set-MSolUMFA{ + [CmdletBinding(SupportsShouldProcess=$true)] + param ( + [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)][string]$UserPrincipalName, + [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)][ValidateSet('Enabled','Disabled','Enforced')][String]$StrongAuthenticationRequiremets + ) + begin{ + # Check if connected to Msol Session already + if (!(Test-MSolConnected)) { + Write-Verbose('No existing Msol session detected') + try { + Write-Verbose('Initiating connection to Msol') + Connect-MsolService -ErrorAction Stop + Write-Verbose('Connected to Msol successfully') + }catch{ + return Write-Error($_.Exception.Message) + } + } + if(!(Get-MsolUser -MaxResults 1 -ErrorAction Stop)){ + return Write-Error('Insufficient permissions to set MFA') + } + } + Process{ + # Get the time and calc 2 min to the future + $TimeStart = Get-Date + $TimeEnd = $timeStart.addminutes(1) + $Finished=$false + #Loop to check if the user exists already + if ($PSCmdlet.ShouldProcess($UserPrincipalName, "StrongAuthenticationRequiremets = "+$StrongAuthenticationRequiremets)) { + } + } + End{} +} + +Set-MsolUser -UserPrincipalName $UPN -StrongAuthenticationRequirements $sta -ErrorAction Stop +$UserPrincipalName = "Bob" +if ($UserPrincipalName) { + $SplatTestAADConnected.Add('UserPrincipalName', $UserPrincipalName) +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableParameterCommndWithSameNameRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableParameterCommndWithSameNameRenamed.ps1 new file mode 100644 index 000000000..9c88a44d4 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableParameterCommndWithSameNameRenamed.ps1 @@ -0,0 +1,56 @@ +function Test-AADConnected { + + param ( + [Parameter(Mandatory = $false)][Alias("UPName", "UserPrincipalName")][String]$Renamed + ) + Begin {} + Process { + [HashTable]$ConnectAADSplat = @{} + if ($Renamed) { + $ConnectAADSplat = @{ + AccountId = $Renamed + ErrorAction = 'Stop' + } + } + } +} + +function Set-MSolUMFA{ + [CmdletBinding(SupportsShouldProcess=$true)] + param ( + [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)][string]$UserPrincipalName, + [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)][ValidateSet('Enabled','Disabled','Enforced')][String]$StrongAuthenticationRequiremets + ) + begin{ + # Check if connected to Msol Session already + if (!(Test-MSolConnected)) { + Write-Verbose('No existing Msol session detected') + try { + Write-Verbose('Initiating connection to Msol') + Connect-MsolService -ErrorAction Stop + Write-Verbose('Connected to Msol successfully') + }catch{ + return Write-Error($_.Exception.Message) + } + } + if(!(Get-MsolUser -MaxResults 1 -ErrorAction Stop)){ + return Write-Error('Insufficient permissions to set MFA') + } + } + Process{ + # Get the time and calc 2 min to the future + $TimeStart = Get-Date + $TimeEnd = $timeStart.addminutes(1) + $Finished=$false + #Loop to check if the user exists already + if ($PSCmdlet.ShouldProcess($UserPrincipalName, "StrongAuthenticationRequiremets = "+$StrongAuthenticationRequiremets)) { + } + } + End{} +} + +Set-MsolUser -UserPrincipalName $UPN -StrongAuthenticationRequirements $sta -ErrorAction Stop +$UserPrincipalName = "Bob" +if ($UserPrincipalName) { + $SplatTestAADConnected.Add('UserPrincipalName', $UserPrincipalName) +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableRedefinition.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableRedefinition.ps1 new file mode 100644 index 000000000..1063dc887 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableRedefinition.ps1 @@ -0,0 +1,3 @@ +$var = 10 +$var = 20 +Write-Output $var diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableRedefinitionRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableRedefinitionRenamed.ps1 new file mode 100644 index 000000000..29f3f87c7 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableRedefinitionRenamed.ps1 @@ -0,0 +1,3 @@ +$Renamed = 10 +$Renamed = 20 +Write-Output $Renamed diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableScriptWithParamBlock.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableScriptWithParamBlock.ps1 new file mode 100644 index 000000000..c3175bd0d --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableScriptWithParamBlock.ps1 @@ -0,0 +1,28 @@ +param([int]$Count=50, [int]$DelayMilliSeconds=200) + +function Write-Item($itemCount) { + $i = 1 + + while ($i -le $itemCount) { + $str = "Output $i" + Write-Output $str + + # In the gutter on the left, right click and select "Add Conditional Breakpoint" + # on the next line. Use the condition: $i -eq 25 + $i = $i + 1 + + # Slow down execution a bit so user can test the "Pause debugger" feature. + Start-Sleep -Milliseconds $DelayMilliSeconds + } +} + +# Do-Work will be underlined in green if you haven't disable script analysis. +# Hover over the function name below to see the PSScriptAnalyzer warning that "Do-Work" +# doesn't use an approved verb. +function Do-Work($workCount) { + Write-Output "Doing work..." + Write-Item $workcount + Write-Host "Done!" +} + +Do-Work $Count diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableScriptWithParamBlockRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableScriptWithParamBlockRenamed.ps1 new file mode 100644 index 000000000..e218fce9f --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableScriptWithParamBlockRenamed.ps1 @@ -0,0 +1,28 @@ +param([int]$Count=50, [int][Alias("DelayMilliSeconds")]$Renamed=200) + +function Write-Item($itemCount) { + $i = 1 + + while ($i -le $itemCount) { + $str = "Output $i" + Write-Output $str + + # In the gutter on the left, right click and select "Add Conditional Breakpoint" + # on the next line. Use the condition: $i -eq 25 + $i = $i + 1 + + # Slow down execution a bit so user can test the "Pause debugger" feature. + Start-Sleep -Milliseconds $Renamed + } +} + +# Do-Work will be underlined in green if you haven't disable script analysis. +# Hover over the function name below to see the PSScriptAnalyzer warning that "Do-Work" +# doesn't use an approved verb. +function Do-Work($workCount) { + Write-Output "Doing work..." + Write-Item $workcount + Write-Host "Done!" +} + +Do-Work $Count diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableSimpleFunctionParameter.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableSimpleFunctionParameter.ps1 new file mode 100644 index 000000000..8e2a4ef5d --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableSimpleFunctionParameter.ps1 @@ -0,0 +1,18 @@ +$x = 1..10 + +function testing_files { + + param ( + $x + ) + write-host "Printing $x" +} + +foreach ($number in $x) { + testing_files $number + + function testing_files { + write-host "------------------" + } +} +testing_files "99" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableSimpleFunctionParameterRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableSimpleFunctionParameterRenamed.ps1 new file mode 100644 index 000000000..250d360ca --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableSimpleFunctionParameterRenamed.ps1 @@ -0,0 +1,18 @@ +$x = 1..10 + +function testing_files { + + param ( + [Alias("x")]$Renamed + ) + write-host "Printing $Renamed" +} + +foreach ($number in $x) { + testing_files $number + + function testing_files { + write-host "------------------" + } +} +testing_files "99" diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinCommandAstScriptBlock.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinCommandAstScriptBlock.ps1 new file mode 100644 index 000000000..4d2f47f74 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinCommandAstScriptBlock.ps1 @@ -0,0 +1,3 @@ +# Not same +$var = 10 +Get-ChildItem | Rename-Item -NewName { $var = $_.FullName + (Get-Random); $var } diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinCommandAstScriptBlockRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinCommandAstScriptBlockRenamed.ps1 new file mode 100644 index 000000000..56c4b4965 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinCommandAstScriptBlockRenamed.ps1 @@ -0,0 +1,3 @@ +# Not same +$var = 10 +Get-ChildItem | Rename-Item -NewName { $Renamed = $_.FullName + (Get-Random); $Renamed } diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinForeachObject.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinForeachObject.ps1 new file mode 100644 index 000000000..89ab6ca1d --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinForeachObject.ps1 @@ -0,0 +1,5 @@ +# Same +$var = 10 +0..10 | ForEach-Object { + $var += 5 +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinForeachObjectRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinForeachObjectRenamed.ps1 new file mode 100644 index 000000000..12f936b61 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableWithinForeachObjectRenamed.ps1 @@ -0,0 +1,5 @@ +# Same +$Renamed = 10 +0..10 | ForEach-Object { + $Renamed += 5 +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableusedInWhileLoop.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableusedInWhileLoop.ps1 new file mode 100644 index 000000000..6ef6e2652 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableusedInWhileLoop.ps1 @@ -0,0 +1,15 @@ +function Write-Item($itemCount) { + $i = 1 + + while ($i -le $itemCount) { + $str = "Output $i" + Write-Output $str + + # In the gutter on the left, right click and select "Add Conditional Breakpoint" + # on the next line. Use the condition: $i -eq 25 + $i = $i + 1 + + # Slow down execution a bit so user can test the "Pause debugger" feature. + Start-Sleep -Milliseconds $DelayMilliseconds + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableusedInWhileLoopRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableusedInWhileLoopRenamed.ps1 new file mode 100644 index 000000000..7a5a46479 --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariableusedInWhileLoopRenamed.ps1 @@ -0,0 +1,15 @@ +function Write-Item($itemCount) { + $Renamed = 1 + + while ($Renamed -le $itemCount) { + $str = "Output $Renamed" + Write-Output $str + + # In the gutter on the left, right click and select "Add Conditional Breakpoint" + # on the next line. Use the condition: $i -eq 25 + $Renamed = $Renamed + 1 + + # Slow down execution a bit so user can test the "Pause debugger" feature. + Start-Sleep -Milliseconds $DelayMilliseconds + } +} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariablewWithinHastableExpression.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariablewWithinHastableExpression.ps1 new file mode 100644 index 000000000..cb3f58b1c --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariablewWithinHastableExpression.ps1 @@ -0,0 +1,3 @@ +# Not same +$var = 10 +0..10 | Select-Object @{n='SomeProperty';e={ $var = 30 * $_; $var }} diff --git a/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariablewWithinHastableExpressionRenamed.ps1 b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariablewWithinHastableExpressionRenamed.ps1 new file mode 100644 index 000000000..0ee85fa2d --- /dev/null +++ b/test/PowerShellEditorServices.Test.Shared/Refactoring/Variables/VariablewWithinHastableExpressionRenamed.ps1 @@ -0,0 +1,3 @@ +# Not same +$var = 10 +0..10 | Select-Object @{n='SomeProperty';e={ $Renamed = 30 * $_; $Renamed }} diff --git a/test/PowerShellEditorServices.Test/Refactoring/RefactorFunctionTests.cs b/test/PowerShellEditorServices.Test/Refactoring/RefactorFunctionTests.cs new file mode 100644 index 000000000..0d1116d5c --- /dev/null +++ b/test/PowerShellEditorServices.Test/Refactoring/RefactorFunctionTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +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.TextDocument; +using Microsoft.PowerShell.EditorServices.Test; +using Microsoft.PowerShell.EditorServices.Test.Shared; +using Microsoft.PowerShell.EditorServices.Handlers; +using Xunit; +using Microsoft.PowerShell.EditorServices.Services.Symbols; +using Microsoft.PowerShell.EditorServices.Refactoring; +using PowerShellEditorServices.Test.Shared.Refactoring.Functions; +using static PowerShellEditorServices.Test.Refactoring.RefactorUtilities; + +namespace PowerShellEditorServices.Test.Refactoring +{ + + [Trait("Category", "RefactorFunction")] + public class RefactorFunctionTests : IAsyncLifetime + + { + private PsesInternalHost psesHost; + private WorkspaceService workspace; + public async Task InitializeAsync() + { + psesHost = await PsesHostFactory.Create(NullLoggerFactory.Instance); + workspace = new WorkspaceService(NullLoggerFactory.Instance); + } + + public async Task DisposeAsync() => await Task.Run(psesHost.StopAsync); + private ScriptFile GetTestScript(string fileName) => workspace.GetFile(TestUtilities.GetSharedPath(Path.Combine("Refactoring", "Functions", fileName))); + + internal static string TestRenaming(ScriptFile scriptFile, RenameSymbolParamsSerialized request, SymbolReference symbol) + { + IterativeFunctionRename iterative = new(symbol.NameRegion.Text, + request.RenameTo, + symbol.ScriptRegion.StartLineNumber, + symbol.ScriptRegion.StartColumnNumber, + scriptFile.ScriptAst); + iterative.Visit(scriptFile.ScriptAst); + ModifiedFileResponse changes = new(request.FileName) + { + Changes = iterative.Modifications + }; + return GetModifiedScript(scriptFile.Contents, changes); + } + + public class FunctionRenameTestData : TheoryData + { + public FunctionRenameTestData() + { + + // Simple + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionsSingle)); + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionWithInternalCalls)); + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionCmdlet)); + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionScriptblock)); + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionCallWIthinStringExpression)); + // Loops + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionLoop)); + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionForeach)); + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionForeachObject)); + // Nested + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionInnerIsNested)); + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionOuterHasNestedFunction)); + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionInnerIsNested)); + // Multi Occurance + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionMultipleOccurrences)); + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionSameName)); + Add(new RenameSymbolParamsSerialized(RefactorsFunctionData.FunctionNestedRedefinition)); + } + } + + [Theory] + [ClassData(typeof(FunctionRenameTestData))] + public void Rename(RenameSymbolParamsSerialized s) + { + // Arrange + RenameSymbolParamsSerialized request = s; + ScriptFile scriptFile = GetTestScript(request.FileName); + ScriptFile expectedContent = GetTestScript(request.FileName.Substring(0, request.FileName.Length - 4) + "Renamed.ps1"); + SymbolReference symbol = scriptFile.References.TryGetSymbolAtPosition( + request.Line, + request.Column); + // Act + string modifiedcontent = TestRenaming(scriptFile, request, symbol); + + // Assert + Assert.Equal(expectedContent.Contents, modifiedcontent); + } + } +} diff --git a/test/PowerShellEditorServices.Test/Refactoring/RefactorUtilities.cs b/test/PowerShellEditorServices.Test/Refactoring/RefactorUtilities.cs new file mode 100644 index 000000000..c21b9aa0e --- /dev/null +++ b/test/PowerShellEditorServices.Test/Refactoring/RefactorUtilities.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using Microsoft.PowerShell.EditorServices.Handlers; +using Xunit.Abstractions; +using MediatR; + +namespace PowerShellEditorServices.Test.Refactoring +{ + public class RefactorUtilities + + { + + internal static string GetModifiedScript(string OriginalScript, ModifiedFileResponse Modification) + { + Modification.Changes.Sort((a, b) => + { + if (b.StartLine == a.StartLine) + { + return b.EndColumn - a.EndColumn; + } + return b.StartLine - a.StartLine; + + }); + string[] Lines = OriginalScript.Split( + new string[] { Environment.NewLine }, + StringSplitOptions.None); + + foreach (TextChange change in Modification.Changes) + { + string TargetLine = Lines[change.StartLine]; + string begin = TargetLine.Substring(0, change.StartColumn); + string end = TargetLine.Substring(change.EndColumn); + Lines[change.StartLine] = begin + change.NewText + end; + } + + return string.Join(Environment.NewLine, Lines); + } + + public class RenameSymbolParamsSerialized : IRequest, IXunitSerializable + { + public string FileName { get; set; } + public int Line { get; set; } + public int Column { get; set; } + public string RenameTo { get; set; } + + // Default constructor needed for deserialization + public RenameSymbolParamsSerialized() { } + + // Parameterized constructor for convenience + public RenameSymbolParamsSerialized(RenameSymbolParams RenameSymbolParams) + { + FileName = RenameSymbolParams.FileName; + Line = RenameSymbolParams.Line; + Column = RenameSymbolParams.Column; + RenameTo = RenameSymbolParams.RenameTo; + } + + public void Deserialize(IXunitSerializationInfo info) + { + FileName = info.GetValue("FileName"); + Line = info.GetValue("Line"); + Column = info.GetValue("Column"); + RenameTo = info.GetValue("RenameTo"); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("FileName", FileName); + info.AddValue("Line", Line); + info.AddValue("Column", Column); + info.AddValue("RenameTo", RenameTo); + } + + public override string ToString() => $"{FileName}"; + } + + } +} diff --git a/test/PowerShellEditorServices.Test/Refactoring/RefactorUtilitiesTests.cs b/test/PowerShellEditorServices.Test/Refactoring/RefactorUtilitiesTests.cs new file mode 100644 index 000000000..70f91fd03 --- /dev/null +++ b/test/PowerShellEditorServices.Test/Refactoring/RefactorUtilitiesTests.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +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.TextDocument; +using Microsoft.PowerShell.EditorServices.Test; +using Microsoft.PowerShell.EditorServices.Test.Shared; +using Microsoft.PowerShell.EditorServices.Handlers; +using Xunit; +using System.Management.Automation.Language; +using static PowerShellEditorServices.Test.Refactoring.RefactorUtilities; +using Microsoft.PowerShell.EditorServices.Refactoring; +using PowerShellEditorServices.Test.Shared.Refactoring.Utilities; + +namespace PowerShellEditorServices.Test.Refactoring +{ + [Trait("Category", "RefactorUtilities")] + public class RefactorUtilitiesTests : IAsyncLifetime + { + private PsesInternalHost psesHost; + private WorkspaceService workspace; + + public async Task InitializeAsync() + { + psesHost = await PsesHostFactory.Create(NullLoggerFactory.Instance); + workspace = new WorkspaceService(NullLoggerFactory.Instance); + } + + public async Task DisposeAsync() => await Task.Run(psesHost.StopAsync); + private ScriptFile GetTestScript(string fileName) => workspace.GetFile(TestUtilities.GetSharedPath(Path.Combine("Refactoring", "Utilities", fileName))); + + + public class GetAstShouldDetectTestData : TheoryData + { + public GetAstShouldDetectTestData() + { + Add(new RenameSymbolParamsSerialized(RenameUtilitiesData.GetVariableExpressionAst), 15, 1); + Add(new RenameSymbolParamsSerialized(RenameUtilitiesData.GetVariableExpressionStartAst), 15, 1); + Add(new RenameSymbolParamsSerialized(RenameUtilitiesData.GetVariableWithinParameterAst), 3, 17); + Add(new RenameSymbolParamsSerialized(RenameUtilitiesData.GetHashTableKey), 16, 5); + Add(new RenameSymbolParamsSerialized(RenameUtilitiesData.GetVariableWithinCommandAst), 6, 28); + Add(new RenameSymbolParamsSerialized(RenameUtilitiesData.GetCommandParameterAst), 21, 10); + Add(new RenameSymbolParamsSerialized(RenameUtilitiesData.GetFunctionDefinitionAst), 1, 1); + } + } + + [Theory] + [ClassData(typeof(GetAstShouldDetectTestData))] + public void GetAstShouldDetect(RenameSymbolParamsSerialized s, int l, int c) + { + ScriptFile scriptFile = GetTestScript(s.FileName); + Ast symbol = Utilities.GetAst(s.Line, s.Column, scriptFile.ScriptAst); + // Assert the Line and Column is what is expected + Assert.Equal(l, symbol.Extent.StartLineNumber); + Assert.Equal(c, symbol.Extent.StartColumnNumber); + } + + [Fact] + public void GetVariableUnderFunctionDef() + { + RenameSymbolParams request = new() + { + Column = 5, + Line = 2, + RenameTo = "Renamed", + FileName = "TestDetectionUnderFunctionDef.ps1" + }; + ScriptFile scriptFile = GetTestScript(request.FileName); + + Ast symbol = Utilities.GetAst(request.Line, request.Column, scriptFile.ScriptAst); + Assert.IsType(symbol); + Assert.Equal(2, symbol.Extent.StartLineNumber); + Assert.Equal(5, symbol.Extent.StartColumnNumber); + + } + [Fact] + public void AssertContainsDotSourcingTrue() + { + ScriptFile scriptFile = GetTestScript("TestDotSourcingTrue.ps1"); + Assert.True(Utilities.AssertContainsDotSourced(scriptFile.ScriptAst)); + } + [Fact] + public void AssertContainsDotSourcingFalse() + { + ScriptFile scriptFile = GetTestScript("TestDotSourcingFalse.ps1"); + Assert.False(Utilities.AssertContainsDotSourced(scriptFile.ScriptAst)); + } + } +} diff --git a/test/PowerShellEditorServices.Test/Refactoring/RefactorVariableTests.cs b/test/PowerShellEditorServices.Test/Refactoring/RefactorVariableTests.cs new file mode 100644 index 000000000..f940ccdb8 --- /dev/null +++ b/test/PowerShellEditorServices.Test/Refactoring/RefactorVariableTests.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +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.TextDocument; +using Microsoft.PowerShell.EditorServices.Test; +using Microsoft.PowerShell.EditorServices.Test.Shared; +using Microsoft.PowerShell.EditorServices.Handlers; +using Xunit; +using PowerShellEditorServices.Test.Shared.Refactoring.Variables; +using static PowerShellEditorServices.Test.Refactoring.RefactorUtilities; +using Microsoft.PowerShell.EditorServices.Refactoring; + +namespace PowerShellEditorServices.Test.Refactoring +{ + [Trait("Category", "RenameVariables")] + public class RefactorVariableTests : IAsyncLifetime + + { + private PsesInternalHost psesHost; + private WorkspaceService workspace; + public async Task InitializeAsync() + { + psesHost = await PsesHostFactory.Create(NullLoggerFactory.Instance); + workspace = new WorkspaceService(NullLoggerFactory.Instance); + } + public async Task DisposeAsync() => await Task.Run(psesHost.StopAsync); + private ScriptFile GetTestScript(string fileName) => workspace.GetFile(TestUtilities.GetSharedPath(Path.Combine("Refactoring", "Variables", fileName))); + + internal static string TestRenaming(ScriptFile scriptFile, RenameSymbolParamsSerialized request) + { + + IterativeVariableRename iterative = new(request.RenameTo, + request.Line, + request.Column, + scriptFile.ScriptAst); + iterative.Visit(scriptFile.ScriptAst); + ModifiedFileResponse changes = new(request.FileName) + { + Changes = iterative.Modifications + }; + return GetModifiedScript(scriptFile.Contents, changes); + } + public class VariableRenameTestData : TheoryData + { + public VariableRenameTestData() + { + Add(new RenameSymbolParamsSerialized(RenameVariableData.SimpleVariableAssignment)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableRedefinition)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableNestedScopeFunction)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableInLoop)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableInPipeline)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableInScriptblock)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableInScriptblockScoped)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariablewWithinHastableExpression)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableNestedFunctionScriptblock)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableWithinCommandAstScriptBlock)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableWithinForeachObject)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableusedInWhileLoop)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableInParam)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableCommandParameter)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableCommandParameterReverse)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableScriptWithParamBlock)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableNonParam)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableParameterCommandWithSameName)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableCommandParameterSplattedFromCommandAst)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableCommandParameterSplattedFromSplat)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableInForeachDuplicateAssignment)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableInForloopDuplicateAssignment)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableNestedScopeFunctionRefactorInner)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableDotNotationFromOuterVar)); + Add(new RenameSymbolParamsSerialized(RenameVariableData.VariableDotNotationFromInnerFunction)); + } + } + + [Theory] + [ClassData(typeof(VariableRenameTestData))] + public void Rename(RenameSymbolParamsSerialized s) + { + RenameSymbolParamsSerialized request = s; + ScriptFile scriptFile = GetTestScript(request.FileName); + ScriptFile expectedContent = GetTestScript(request.FileName.Substring(0, request.FileName.Length - 4) + "Renamed.ps1"); + + string modifiedcontent = TestRenaming(scriptFile, request); + + Assert.Equal(expectedContent.Contents, modifiedcontent); + } + } + +}