diff --git a/.gitignore b/.gitignore
index f4281ce..a54098c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,6 @@ project.lock.json
# VSCode directories that are not at the repository root
/**/.vscode/
+
+# Visual Studio directories
+.vs/
diff --git a/Modules/Microsoft.PowerShell.AstTools/src/Microsoft.PowerShell.AstTools.csproj b/Modules/Microsoft.PowerShell.AstTools/src/Microsoft.PowerShell.AstTools.csproj
new file mode 100644
index 0000000..ab692df
--- /dev/null
+++ b/Modules/Microsoft.PowerShell.AstTools/src/Microsoft.PowerShell.AstTools.csproj
@@ -0,0 +1,25 @@
+
+
+
+ netcoreapp3.1;netstandard2.0
+ Microsoft.PowerShell.AstTools
+
+
+
+
+ $(DefineConstants);PS7
+
+
+
+ $(DefineConstants);PSSTD
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs
new file mode 100644
index 0000000..830b6f2
--- /dev/null
+++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs
@@ -0,0 +1,2069 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Management.Automation.Language;
+using System.Text;
+
+namespace Microsoft.PowerShell.AstTools
+{
+ public class StringPrettyPrinter : PrettyPrinter
+ {
+ private string _result;
+
+ private StringWriter _sw;
+
+ public string PrettyPrintInput(string input)
+ {
+ DoPrettyPrintInput(input);
+ return _result;
+ }
+
+ public string PrettyPrintFile(string filePath)
+ {
+ DoPrettyPrintFile(filePath);
+ return _result;
+ }
+
+ public string PrettyPrintAst(Ast ast, IReadOnlyList tokens)
+ {
+ DoPrettyPrintAst(ast, tokens);
+ return _result;
+ }
+
+ protected override TextWriter CreateTextWriter()
+ {
+ _sw = new StringWriter();
+ return _sw;
+ }
+
+ protected override void DoPostPrintAction()
+ {
+ _result = _sw.ToString();
+ }
+ }
+
+ ///
+ /// Prints a PowerShell AST based on its structure rather than text captured in extents.
+ ///
+ public abstract class PrettyPrinter
+ {
+ private readonly PrettyPrintingVisitor _visitor;
+
+ ///
+ /// Create a new pretty printer for use.
+ ///
+ protected PrettyPrinter()
+ {
+ _visitor = new PrettyPrintingVisitor();
+ }
+
+ protected abstract TextWriter CreateTextWriter();
+
+ protected virtual void DoPostPrintAction()
+ {
+ }
+
+ ///
+ /// Pretty print a PowerShell script provided as an inline string.
+ ///
+ /// The inline PowerShell script to parse and pretty print.
+ /// A pretty-printed version of the given PowerShell script.
+ protected void DoPrettyPrintInput(string input)
+ {
+ Ast ast = Parser.ParseInput(input, out Token[] tokens, out ParseError[] errors);
+
+ if (errors != null && errors.Length > 0)
+ {
+ throw new ParseException(errors);
+ }
+
+ DoPrettyPrintAst(ast, tokens);
+ }
+
+ ///
+ /// Pretty print the contents of a PowerShell file.
+ ///
+ /// The path of the PowerShell file to pretty print.
+ /// The pretty-printed file contents.
+ protected void DoPrettyPrintFile(string filePath)
+ {
+ Ast ast = Parser.ParseFile(filePath, out Token[] tokens, out ParseError[] errors);
+
+ if (errors != null && errors.Length > 0)
+ {
+ throw new ParseException(errors);
+ }
+
+ DoPrettyPrintAst(ast, tokens);
+ }
+
+ ///
+ /// Pretty print a given PowerShell AST.
+ ///
+ /// The PowerShell AST to print.
+ /// The token array generated when the AST was parsed. May be null.
+ /// The pretty-printed PowerShell AST in string form.
+ protected void DoPrettyPrintAst(Ast ast, IReadOnlyList tokens)
+ {
+ using (TextWriter textWriter = CreateTextWriter())
+ {
+ _visitor.Run(textWriter, ast, tokens);
+ DoPostPrintAction();
+ }
+ }
+ }
+
+ internal class PrettyPrintingVisitor : AstVisitor2
+ {
+ private TextWriter _tw;
+
+ private readonly string _newline;
+
+ private readonly string _indentStr;
+
+ private readonly string _comma;
+
+ private int _tokenIndex;
+
+ private IReadOnlyList _tokens;
+
+ private int _indent;
+
+ public PrettyPrintingVisitor()
+ {
+ _newline = "\n";
+ _indentStr = " ";
+ _comma = ", ";
+ _indent = 0;
+ }
+
+ public void Run(
+ TextWriter tw,
+ Ast ast,
+ IReadOnlyList tokens)
+ {
+ _tw = tw;
+ _tokenIndex = 0;
+ _tokens = tokens;
+ ast.Visit(this);
+ _tw = null;
+ }
+
+ public override AstVisitAction VisitArrayExpression(ArrayExpressionAst arrayExpressionAst)
+ {
+ WriteCommentsToAstPosition(arrayExpressionAst);
+
+ _tw.Write("@(");
+ WriteStatementBlock(arrayExpressionAst.SubExpression.Statements, arrayExpressionAst.SubExpression.Traps);
+ _tw.Write(")");
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst)
+ {
+ Intersperse(arrayLiteralAst.Elements, _comma);
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst)
+ {
+ assignmentStatementAst.Left.Visit(this);
+
+ _tw.Write(' ');
+ _tw.Write(GetTokenString(assignmentStatementAst.Operator));
+ _tw.Write(' ');
+
+ assignmentStatementAst.Right.Visit(this);
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitAttribute(AttributeAst attributeAst)
+ {
+ WriteCommentsToAstPosition(attributeAst);
+
+ _tw.Write('[');
+ _tw.Write(attributeAst.TypeName);
+ _tw.Write('(');
+
+ bool hadPositionalArgs = false;
+ if (!IsEmpty(attributeAst.PositionalArguments))
+ {
+ hadPositionalArgs = true;
+ Intersperse(attributeAst.PositionalArguments, _comma);
+ }
+
+ if (!IsEmpty(attributeAst.NamedArguments))
+ {
+ if (hadPositionalArgs)
+ {
+ _tw.Write(_comma);
+ }
+
+ Intersperse(attributeAst.NamedArguments, _comma);
+ }
+
+ _tw.Write(")]");
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst)
+ {
+ attributedExpressionAst.Attribute.Visit(this);
+ attributedExpressionAst.Child.Visit(this);
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst)
+ {
+ WriteCommentsToAstPosition(baseCtorInvokeMemberExpressionAst);
+
+ if (!IsEmpty(baseCtorInvokeMemberExpressionAst.Arguments))
+ {
+ _tw.Write("base(");
+ Intersperse(baseCtorInvokeMemberExpressionAst.Arguments, ", ");
+ _tw.Write(')');
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
+ {
+ binaryExpressionAst.Left.Visit(this);
+
+ _tw.Write(' ');
+ _tw.Write(GetTokenString(binaryExpressionAst.Operator));
+ _tw.Write(' ');
+
+ binaryExpressionAst.Right.Visit(this);
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitBlockStatement(BlockStatementAst blockStatementAst)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override AstVisitAction VisitBreakStatement(BreakStatementAst breakStatementAst)
+ {
+ WriteCommentsToAstPosition(breakStatementAst);
+ WriteControlFlowStatement("break", breakStatementAst.Label);
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitCatchClause(CatchClauseAst catchClauseAst)
+ {
+ WriteCommentsToAstPosition(catchClauseAst);
+
+ _tw.Write("catch");
+ if (!IsEmpty(catchClauseAst.CatchTypes))
+ {
+ foreach (TypeConstraintAst typeConstraint in catchClauseAst.CatchTypes)
+ {
+ _tw.Write(' ');
+ typeConstraint.Visit(this);
+ }
+ }
+
+ catchClauseAst.Body.Visit(this);
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitCommand(CommandAst commandAst)
+ {
+ WriteCommentsToAstPosition(commandAst);
+
+ if (commandAst.InvocationOperator != TokenKind.Unknown)
+ {
+ _tw.Write(GetTokenString(commandAst.InvocationOperator));
+ _tw.Write(' ');
+ }
+
+ Intersperse(commandAst.CommandElements, " ");
+
+ if (!IsEmpty(commandAst.Redirections))
+ {
+ _tw.Write(' ');
+ Intersperse(commandAst.Redirections, " ");
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitCommandExpression(CommandExpressionAst commandExpressionAst)
+ {
+ commandExpressionAst.Expression.Visit(this);
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst)
+ {
+ WriteCommentsToAstPosition(commandParameterAst);
+
+ _tw.Write('-');
+ _tw.Write(commandParameterAst.ParameterName);
+
+ if (commandParameterAst.Argument != null)
+ {
+ _tw.Write(':');
+ commandParameterAst.Argument.Visit(this);
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override AstVisitAction VisitConstantExpression(ConstantExpressionAst constantExpressionAst)
+ {
+ WriteCommentsToAstPosition(constantExpressionAst);
+
+ if (constantExpressionAst.Value == null)
+ {
+ _tw.Write("$null");
+ }
+ else if (constantExpressionAst.StaticType == typeof(bool))
+ {
+ _tw.Write((bool)constantExpressionAst.Value ? "$true" : "$false");
+ }
+ else
+ {
+ _tw.Write(constantExpressionAst.Value);
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitContinueStatement(ContinueStatementAst continueStatementAst)
+ {
+ WriteCommentsToAstPosition(continueStatementAst);
+ WriteControlFlowStatement("continue", continueStatementAst.Label);
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitConvertExpression(ConvertExpressionAst convertExpressionAst)
+ {
+ convertExpressionAst.Attribute.Visit(this);
+ convertExpressionAst.Child.Visit(this);
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override AstVisitAction VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst)
+ {
+ WriteCommentsToAstPosition(doUntilStatementAst);
+ _tw.Write("do");
+ doUntilStatementAst.Body.Visit(this);
+ _tw.Write(" until (");
+ doUntilStatementAst.Condition.Visit(this);
+ _tw.Write(')');
+ EndStatement();
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst)
+ {
+ WriteCommentsToAstPosition(doWhileStatementAst);
+ _tw.Write("do");
+ doWhileStatementAst.Body.Visit(this);
+ _tw.Write(" while (");
+ doWhileStatementAst.Condition.Visit(this);
+ _tw.Write(')');
+ EndStatement();
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordStatementAst)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override AstVisitAction VisitErrorExpression(ErrorExpressionAst errorExpressionAst)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override AstVisitAction VisitErrorStatement(ErrorStatementAst errorStatementAst)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override AstVisitAction VisitExitStatement(ExitStatementAst exitStatementAst)
+ {
+ WriteControlFlowStatement("exit", exitStatementAst.Pipeline);
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst)
+ {
+ WriteCommentsToAstPosition(expandableStringExpressionAst);
+ _tw.Write('"');
+ _tw.Write(expandableStringExpressionAst.Value);
+ _tw.Write('"');
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitFileRedirection(FileRedirectionAst redirectionAst)
+ {
+ WriteCommentsToAstPosition(redirectionAst);
+
+ if (redirectionAst.FromStream != RedirectionStream.Output)
+ {
+ _tw.Write(GetStreamIndicator(redirectionAst.FromStream));
+ }
+
+ _tw.Write('>');
+
+ redirectionAst.Location.Visit(this);
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst)
+ {
+ WriteCommentsToAstPosition(forEachStatementAst);
+
+ _tw.Write("foreach (");
+ forEachStatementAst.Variable.Visit(this);
+ _tw.Write(" in ");
+ forEachStatementAst.Condition.Visit(this);
+ _tw.Write(")");
+ forEachStatementAst.Body.Visit(this);
+ EndStatement();
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitForStatement(ForStatementAst forStatementAst)
+ {
+ WriteCommentsToAstPosition(forStatementAst);
+
+ _tw.Write("for (");
+ forStatementAst.Initializer.Visit(this);
+ _tw.Write("; ");
+ forStatementAst.Condition.Visit(this);
+ _tw.Write("; ");
+ forStatementAst.Iterator.Visit(this);
+ _tw.Write(')');
+ forStatementAst.Body.Visit(this);
+ EndStatement();
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
+ {
+ WriteCommentsToAstPosition(functionDefinitionAst);
+
+ _tw.Write(functionDefinitionAst.IsFilter ? "filter " : "function ");
+ _tw.Write(functionDefinitionAst.Name);
+ Newline();
+ functionDefinitionAst.Body.Visit(this);
+ EndStatement();
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst)
+ {
+ WriteCommentsToAstPosition(functionMemberAst);
+
+ if (!functionMemberAst.IsConstructor)
+ {
+ if (functionMemberAst.IsStatic)
+ {
+ _tw.Write("static ");
+ }
+
+ if (functionMemberAst.IsHidden)
+ {
+ _tw.Write("hidden ");
+ }
+
+ if (functionMemberAst.ReturnType != null)
+ {
+ functionMemberAst.ReturnType.Visit(this);
+ }
+ }
+
+ _tw.Write(functionMemberAst.Name);
+ _tw.Write('(');
+ WriteInlineParameters(functionMemberAst.Parameters);
+ _tw.Write(')');
+
+ IReadOnlyList statementAsts = functionMemberAst.Body.EndBlock.Statements;
+
+ if (functionMemberAst.IsConstructor)
+ {
+ var baseCtorCall = (BaseCtorInvokeMemberExpressionAst)((CommandExpressionAst)functionMemberAst.Body.EndBlock.Statements[0]).Expression;
+
+ if (!IsEmpty(baseCtorCall.Arguments))
+ {
+ _tw.Write(" : ");
+ baseCtorCall.Visit(this);
+ }
+
+ var newStatementAsts = new StatementAst[functionMemberAst.Body.EndBlock.Statements.Count - 1];
+ for (int i = 0; i < newStatementAsts.Length; i++)
+ {
+ newStatementAsts[i] = functionMemberAst.Body.EndBlock.Statements[i + 1];
+ }
+ statementAsts = newStatementAsts;
+ }
+
+ if (IsEmpty(statementAsts) && IsEmpty(functionMemberAst.Body.EndBlock.Traps))
+ {
+ Newline();
+ _tw.Write('{');
+ Newline();
+ _tw.Write('}');
+ return AstVisitAction.SkipChildren;
+ }
+
+ BeginBlock();
+ WriteStatementBlock(statementAsts, functionMemberAst.Body.EndBlock.Traps);
+ EndBlock();
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst)
+ {
+ WriteCommentsToAstPosition(hashtableAst);
+
+ _tw.Write("@{");
+
+ if (IsEmpty(hashtableAst.KeyValuePairs))
+ {
+ _tw.Write('}');
+ return AstVisitAction.SkipChildren;
+ }
+
+ Indent();
+
+ Intersperse(
+ hashtableAst.KeyValuePairs,
+ WriteHashtableEntry,
+ Newline);
+
+ Dedent();
+ _tw.Write('}');
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst)
+ {
+ WriteCommentsToAstPosition(ifStmtAst);
+
+ _tw.Write("if (");
+ ifStmtAst.Clauses[0].Item1.Visit(this);
+ _tw.Write(')');
+ ifStmtAst.Clauses[0].Item2.Visit(this);
+
+ for (int i = 1; i < ifStmtAst.Clauses.Count; i++)
+ {
+ Newline();
+ _tw.Write("elseif (");
+ ifStmtAst.Clauses[i].Item1.Visit(this);
+ _tw.Write(')');
+ ifStmtAst.Clauses[i].Item2.Visit(this);
+ }
+
+ if (ifStmtAst.ElseClause != null)
+ {
+ Newline();
+ _tw.Write("else");
+ ifStmtAst.ElseClause.Visit(this);
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitIndexExpression(IndexExpressionAst indexExpressionAst)
+ {
+ WriteCommentsToAstPosition(indexExpressionAst);
+
+ indexExpressionAst.Target.Visit(this);
+ _tw.Write('[');
+ indexExpressionAst.Index.Visit(this);
+ _tw.Write(']');
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst)
+ {
+ methodCallAst.Expression.Visit(this);
+ _tw.Write(methodCallAst.Static ? "::" : ".");
+ methodCallAst.Member.Visit(this);
+ _tw.Write('(');
+ Intersperse(methodCallAst.Arguments, ", ");
+ _tw.Write(')');
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst)
+ {
+ WriteCommentsToAstPosition(memberExpressionAst);
+
+ memberExpressionAst.Expression.Visit(this);
+ _tw.Write(memberExpressionAst.Static ? "::" : ".");
+ memberExpressionAst.Member.Visit(this);
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst redirectionAst)
+ {
+ WriteCommentsToAstPosition(redirectionAst);
+
+ _tw.Write(GetStreamIndicator(redirectionAst.FromStream));
+ _tw.Write(">&");
+ _tw.Write(GetStreamIndicator(redirectionAst.ToStream));
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst)
+ {
+ WriteCommentsToAstPosition(namedAttributeArgumentAst);
+
+ _tw.Write(namedAttributeArgumentAst.ArgumentName);
+
+ if (!namedAttributeArgumentAst.ExpressionOmitted && namedAttributeArgumentAst.Argument != null)
+ {
+ _tw.Write(" = ");
+ namedAttributeArgumentAst.Argument.Visit(this);
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst)
+ {
+ WriteCommentsToAstPosition(namedBlockAst);
+
+ if (!namedBlockAst.Unnamed)
+ {
+ _tw.Write(GetTokenString(namedBlockAst.BlockKind));
+ }
+
+ BeginBlock();
+
+ WriteStatementBlock(namedBlockAst.Statements, namedBlockAst.Traps);
+
+ EndBlock();
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst)
+ {
+ WriteCommentsToAstPosition(paramBlockAst);
+
+ if (!IsEmpty(paramBlockAst.Attributes))
+ {
+ foreach (AttributeAst attributeAst in paramBlockAst.Attributes)
+ {
+ attributeAst.Visit(this);
+ Newline();
+ }
+ }
+
+ _tw.Write("param(");
+
+ if (IsEmpty(paramBlockAst.Parameters))
+ {
+ _tw.Write(')');
+ return AstVisitAction.SkipChildren;
+ }
+
+ Indent();
+
+ Intersperse(
+ paramBlockAst.Parameters,
+ () => { _tw.Write(','); Newline(count: 2); });
+
+ Dedent();
+ _tw.Write(')');
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitParameter(ParameterAst parameterAst)
+ {
+ WriteCommentsToAstPosition(parameterAst);
+
+ if (!IsEmpty(parameterAst.Attributes))
+ {
+ foreach (AttributeBaseAst attribute in parameterAst.Attributes)
+ {
+ attribute.Visit(this);
+ Newline();
+ }
+ }
+
+ parameterAst.Name.Visit(this);
+
+ if (parameterAst.DefaultValue != null)
+ {
+ _tw.Write(" = ");
+ parameterAst.DefaultValue.Visit(this);
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitParenExpression(ParenExpressionAst parenExpressionAst)
+ {
+ WriteCommentsToAstPosition(parenExpressionAst);
+ _tw.Write('(');
+ parenExpressionAst.Pipeline.Visit(this);
+ _tw.Write(')');
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitPipeline(PipelineAst pipelineAst)
+ {
+ WriteCommentsToAstPosition(pipelineAst);
+
+ Intersperse(pipelineAst.PipelineElements, " | ");
+#if PS7
+ if (pipelineAst.Background)
+ {
+ _tw.Write(" &");
+ }
+#endif
+ return AstVisitAction.SkipChildren;
+ }
+
+#if PS7
+ public override AstVisitAction VisitPipelineChain(PipelineChainAst statementChain)
+ {
+ WriteCommentsToAstPosition(statementChain);
+ statementChain.LhsPipelineChain.Visit(this);
+ _tw.Write(' ');
+ _tw.Write(GetTokenString(statementChain.Operator));
+ _tw.Write(' ');
+ statementChain.RhsPipeline.Visit(this);
+ if (statementChain.Background)
+ {
+ _tw.Write(" &");
+ }
+ return AstVisitAction.SkipChildren;
+ }
+#endif
+
+ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst)
+ {
+ WriteCommentsToAstPosition(propertyMemberAst);
+
+ if (propertyMemberAst.IsStatic)
+ {
+ _tw.Write("static ");
+ }
+
+ if (propertyMemberAst.IsHidden)
+ {
+ _tw.Write("hidden ");
+ }
+
+ if (propertyMemberAst.PropertyType != null)
+ {
+ propertyMemberAst.PropertyType.Visit(this);
+ }
+
+ _tw.Write('$');
+ _tw.Write(propertyMemberAst.Name);
+
+ if (propertyMemberAst.InitialValue != null)
+ {
+ _tw.Write(" = ");
+ propertyMemberAst.InitialValue.Visit(this);
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitReturnStatement(ReturnStatementAst returnStatementAst)
+ {
+ WriteCommentsToAstPosition(returnStatementAst);
+ WriteControlFlowStatement("return", returnStatementAst.Pipeline);
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst)
+ {
+ WriteCommentsToAstPosition(scriptBlockAst);
+
+ if (scriptBlockAst.Parent != null)
+ {
+ _tw.Write('{');
+ Indent();
+ }
+
+ bool needNewline = false;
+ if (scriptBlockAst.ParamBlock != null)
+ {
+ needNewline = true;
+ scriptBlockAst.ParamBlock.Visit(this);
+ }
+
+ Intersperse(scriptBlockAst.UsingStatements, Newline);
+
+ bool useExplicitEndBlock = false;
+
+ if (scriptBlockAst.DynamicParamBlock != null)
+ {
+ needNewline = useExplicitEndBlock = true;
+ if (needNewline)
+ {
+ Newline(count: 2);
+ }
+
+ scriptBlockAst.DynamicParamBlock.Visit(this);
+ }
+
+ if (scriptBlockAst.BeginBlock != null)
+ {
+ needNewline = useExplicitEndBlock = true;
+ if (needNewline)
+ {
+ Newline(count: 2);
+ }
+
+ scriptBlockAst.BeginBlock.Visit(this);
+ }
+
+ if (scriptBlockAst.ProcessBlock != null)
+ {
+ needNewline = useExplicitEndBlock = true;
+ if (needNewline)
+ {
+ Newline(count: 2);
+ }
+
+ scriptBlockAst.ProcessBlock.Visit(this);
+ }
+
+ if (scriptBlockAst.EndBlock != null
+ && (!IsEmpty(scriptBlockAst.EndBlock.Statements) || !IsEmpty(scriptBlockAst.EndBlock.Traps)))
+ {
+ if (useExplicitEndBlock)
+ {
+ Newline(count: 2);
+ scriptBlockAst.EndBlock.Visit(this);
+ }
+ else
+ {
+ if (needNewline)
+ {
+ Newline(count: 2);
+ }
+
+ WriteStatementBlock(scriptBlockAst.EndBlock.Statements, scriptBlockAst.EndBlock.Traps);
+ }
+ }
+
+ if (scriptBlockAst.Parent != null)
+ {
+ Dedent();
+ _tw.Write('}');
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)
+ {
+ scriptBlockExpressionAst.ScriptBlock.Visit(this);
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitStatementBlock(StatementBlockAst statementBlockAst)
+ {
+ WriteCommentsToAstPosition(statementBlockAst);
+ BeginBlock();
+ WriteStatementBlock(statementBlockAst.Statements, statementBlockAst.Traps);
+ EndBlock();
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst)
+ {
+ WriteCommentsToAstPosition(stringConstantExpressionAst);
+ switch (stringConstantExpressionAst.StringConstantType)
+ {
+ case StringConstantType.BareWord:
+ _tw.Write(stringConstantExpressionAst.Value);
+ break;
+
+ case StringConstantType.SingleQuoted:
+ _tw.Write('\'');
+ _tw.Write(stringConstantExpressionAst.Value.Replace("'", "''"));
+ _tw.Write('\'');
+ break;
+
+ case StringConstantType.DoubleQuoted:
+ WriteDoubleQuotedString(stringConstantExpressionAst.Value);
+ break;
+
+ case StringConstantType.SingleQuotedHereString:
+ _tw.Write("@'\n");
+ _tw.Write(stringConstantExpressionAst.Value);
+ _tw.Write("\n'@");
+ break;
+
+ case StringConstantType.DoubleQuotedHereString:
+ _tw.Write("@\"\n");
+ _tw.Write(stringConstantExpressionAst.Value);
+ _tw.Write("\n\"@");
+ break;
+
+ default:
+ throw new ArgumentException($"Bad string contstant expression: '{stringConstantExpressionAst}' of type {stringConstantExpressionAst.StringConstantType}");
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitSubExpression(SubExpressionAst subExpressionAst)
+ {
+ WriteCommentsToAstPosition(subExpressionAst);
+ _tw.Write("$(");
+ WriteStatementBlock(subExpressionAst.SubExpression.Statements, subExpressionAst.SubExpression.Traps);
+ _tw.Write(')');
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchStatementAst)
+ {
+ WriteCommentsToAstPosition(switchStatementAst);
+
+ if (switchStatementAst.Label != null)
+ {
+ _tw.Write(':');
+ _tw.Write(switchStatementAst.Label);
+ _tw.Write(' ');
+ }
+
+ _tw.Write("switch (");
+ switchStatementAst.Condition.Visit(this);
+ _tw.Write(')');
+
+ BeginBlock();
+
+ bool hasCases = false;
+ if (!IsEmpty(switchStatementAst.Clauses))
+ {
+ hasCases = true;
+
+ Intersperse(
+ switchStatementAst.Clauses,
+ (caseClause) => { caseClause.Item1.Visit(this); caseClause.Item2.Visit(this); },
+ () => Newline(count: 2));
+ }
+
+ if (switchStatementAst.Default != null)
+ {
+ if (hasCases)
+ {
+ Newline(count: 2);
+ }
+
+ _tw.Write("default");
+ switchStatementAst.Default.Visit(this);
+ }
+
+ EndBlock();
+
+ return AstVisitAction.SkipChildren;
+ }
+
+#if PS7
+ public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst)
+ {
+ WriteCommentsToAstPosition(ternaryExpressionAst);
+
+ ternaryExpressionAst.Condition.Visit(this);
+ _tw.Write(" ? ");
+ ternaryExpressionAst.IfTrue.Visit(this);
+ _tw.Write(" : ");
+ ternaryExpressionAst.IfFalse.Visit(this);
+ return AstVisitAction.SkipChildren;
+ }
+#endif
+
+ public override AstVisitAction VisitThrowStatement(ThrowStatementAst throwStatementAst)
+ {
+ WriteCommentsToAstPosition(throwStatementAst);
+
+ WriteControlFlowStatement("throw", throwStatementAst.Pipeline);
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitTrap(TrapStatementAst trapStatementAst)
+ {
+ WriteCommentsToAstPosition(trapStatementAst);
+
+ _tw.Write("trap");
+
+ if (trapStatementAst.TrapType != null)
+ {
+ _tw.Write(' ');
+ trapStatementAst.TrapType.Visit(this);
+ }
+
+ trapStatementAst.Body.Visit(this);
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst)
+ {
+ WriteCommentsToAstPosition(tryStatementAst);
+
+ _tw.Write("try");
+ tryStatementAst.Body.Visit(this);
+
+ if (!IsEmpty(tryStatementAst.CatchClauses))
+ {
+ foreach (CatchClauseAst catchClause in tryStatementAst.CatchClauses)
+ {
+ Newline();
+ catchClause.Visit(this);
+ }
+ }
+
+ if (tryStatementAst.Finally != null)
+ {
+ Newline();
+ _tw.Write("finally");
+ tryStatementAst.Finally.Visit(this);
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst)
+ {
+ WriteCommentsToAstPosition(typeConstraintAst);
+ _tw.Write('[');
+ WriteTypeName(typeConstraintAst.TypeName);
+ _tw.Write(']');
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst)
+ {
+ WriteCommentsToAstPosition(typeDefinitionAst);
+
+ if (typeDefinitionAst.IsClass)
+ {
+ _tw.Write("class ");
+ }
+ else if (typeDefinitionAst.IsInterface)
+ {
+ _tw.Write("interface ");
+ }
+ else if (typeDefinitionAst.IsEnum)
+ {
+ _tw.Write("enum ");
+ }
+ else
+ {
+ throw new ArgumentException($"Unknown PowerShell type definition type: '{typeDefinitionAst}'");
+ }
+
+ _tw.Write(typeDefinitionAst.Name);
+
+ if (!IsEmpty(typeDefinitionAst.BaseTypes))
+ {
+ _tw.Write(" : ");
+
+ Intersperse(
+ typeDefinitionAst.BaseTypes,
+ (baseType) => WriteTypeName(baseType.TypeName),
+ () => _tw.Write(_comma));
+ }
+
+ if (IsEmpty(typeDefinitionAst.Members))
+ {
+ Newline();
+ _tw.Write('{');
+ Newline();
+ _tw.Write('}');
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ BeginBlock();
+
+ if (typeDefinitionAst.Members != null)
+ {
+ if (typeDefinitionAst.IsEnum)
+ {
+ Intersperse(typeDefinitionAst.Members, () =>
+ {
+ _tw.Write(',');
+ Newline();
+ });
+ }
+ else if (typeDefinitionAst.IsClass)
+ {
+ Intersperse(typeDefinitionAst.Members, () =>
+ {
+ Newline(count: 2);
+ });
+ }
+ }
+
+ EndBlock();
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst)
+ {
+ WriteCommentsToAstPosition(typeExpressionAst);
+ _tw.Write('[');
+ WriteTypeName(typeExpressionAst.TypeName);
+ _tw.Write(']');
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst)
+ {
+ WriteCommentsToAstPosition(unaryExpressionAst);
+
+ switch (unaryExpressionAst.TokenKind)
+ {
+ case TokenKind.PlusPlus:
+ _tw.Write("++");
+ unaryExpressionAst.Child.Visit(this);
+ break;
+
+ case TokenKind.MinusMinus:
+ _tw.Write("--");
+ unaryExpressionAst.Child.Visit(this);
+ break;
+
+ case TokenKind.PostfixPlusPlus:
+ unaryExpressionAst.Child.Visit(this);
+ _tw.Write("++");
+ break;
+
+ case TokenKind.PostfixMinusMinus:
+ unaryExpressionAst.Child.Visit(this);
+ _tw.Write("--");
+ break;
+
+ default:
+ _tw.Write(GetTokenString(unaryExpressionAst.TokenKind));
+ _tw.Write(' ');
+ unaryExpressionAst.Child.Visit(this);
+ break;
+
+ }
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpressionAst)
+ {
+ WriteCommentsToAstPosition(usingExpressionAst);
+ _tw.Write("$using:");
+ _tw.Write(((VariableExpressionAst)usingExpressionAst.SubExpression).VariablePath.UserPath);
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst)
+ {
+ WriteCommentsToAstPosition(usingStatementAst);
+
+ _tw.Write("using ");
+
+ switch (usingStatementAst.UsingStatementKind)
+ {
+ case UsingStatementKind.Assembly:
+ _tw.Write("assembly ");
+ break;
+
+ case UsingStatementKind.Command:
+ _tw.Write("command ");
+ break;
+
+ case UsingStatementKind.Module:
+ _tw.Write("module ");
+ break;
+
+ case UsingStatementKind.Namespace:
+ _tw.Write("namespace ");
+ break;
+
+ case UsingStatementKind.Type:
+ _tw.Write("type ");
+ break;
+
+ default:
+ throw new ArgumentException($"Unknown using statement kind: '{usingStatementAst.UsingStatementKind}'");
+ }
+
+ if (usingStatementAst.ModuleSpecification != null)
+ {
+ _tw.Write("@{ ");
+
+ Intersperse(
+ usingStatementAst.ModuleSpecification.KeyValuePairs,
+ (kvp) =>
+ {
+ WriteCommentsToAstPosition(kvp.Item1);
+ kvp.Item1.Visit(this);
+ _tw.Write(" = ");
+ WriteCommentsToAstPosition(kvp.Item2);
+ kvp.Item2.Visit(this);
+ },
+ () => { _tw.Write("; "); });
+
+ _tw.Write(" }");
+ EndStatement();
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ if (usingStatementAst.Name != null)
+ {
+ usingStatementAst.Name.Visit(this);
+ }
+
+ if (usingStatementAst.Alias != null)
+ {
+ _tw.Write(" = ");
+ usingStatementAst.Alias.Visit(this);
+ }
+
+ EndStatement();
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
+ {
+ WriteCommentsToAstPosition(variableExpressionAst);
+ _tw.Write(variableExpressionAst.Splatted ? '@' : '$');
+ _tw.Write(variableExpressionAst.VariablePath.UserPath);
+ return AstVisitAction.SkipChildren;
+ }
+
+ public override AstVisitAction VisitWhileStatement(WhileStatementAst whileStatementAst)
+ {
+ WriteCommentsToAstPosition(whileStatementAst);
+ _tw.Write("while (");
+ whileStatementAst.Condition.Visit(this);
+ _tw.Write(")");
+ whileStatementAst.Body.Visit(this);
+
+ return AstVisitAction.SkipChildren;
+ }
+
+ private void WriteInlineParameters(IReadOnlyList parameters)
+ {
+ if (IsEmpty(parameters))
+ {
+ return;
+ }
+
+ foreach (ParameterAst parameterAst in parameters)
+ {
+ WriteInlineParameter(parameterAst);
+ }
+ }
+
+ private void WriteInlineParameter(ParameterAst parameter)
+ {
+ foreach (AttributeBaseAst attribute in parameter.Attributes)
+ {
+ attribute.Visit(this);
+ }
+
+ parameter.Name.Visit(this);
+
+ if (parameter.DefaultValue != null)
+ {
+ _tw.Write(" = ");
+ parameter.DefaultValue.Visit(this);
+ }
+ }
+
+
+ private void WriteControlFlowStatement(string keyword, Ast childAst)
+ {
+ _tw.Write(keyword);
+
+ if (childAst != null)
+ {
+ _tw.Write(' ');
+ childAst.Visit(this);
+ }
+ }
+
+ private void WriteTypeName(ITypeName typeName)
+ {
+ switch (typeName)
+ {
+ case ArrayTypeName arrayTypeName:
+ WriteTypeName(arrayTypeName.ElementType);
+ if (arrayTypeName.Rank == 1)
+ {
+ _tw.Write("[]");
+ }
+ else
+ {
+ _tw.Write('[');
+ for (int i = 1; i < arrayTypeName.Rank; i++)
+ {
+ _tw.Write(',');
+ }
+ _tw.Write(']');
+ }
+ break;
+
+ case GenericTypeName genericTypeName:
+ _tw.Write(genericTypeName.FullName);
+ _tw.Write('[');
+
+ Intersperse(
+ genericTypeName.GenericArguments,
+ (gtn) => WriteTypeName(gtn),
+ () => _tw.Write(_comma));
+
+ _tw.Write(']');
+ break;
+
+ case TypeName simpleTypeName:
+ _tw.Write(simpleTypeName.FullName);
+ break;
+
+ default:
+ throw new ArgumentException($"Unknown type name type: '{typeName.GetType().FullName}'");
+ }
+ }
+
+ private void WriteDoubleQuotedString(string strVal)
+ {
+ _tw.Write('"');
+
+ foreach (char c in strVal)
+ {
+ switch (c)
+ {
+ case '\0':
+ _tw.Write("`0");
+ break;
+
+ case '\a':
+ _tw.Write("`a");
+ break;
+
+ case '\b':
+ _tw.Write("`b");
+ break;
+
+ case '\f':
+ _tw.Write("`f");
+ break;
+
+ case '\n':
+ _tw.Write("`n");
+ break;
+
+ case '\r':
+ _tw.Write("`r");
+ break;
+
+ case '\t':
+ _tw.Write("`t");
+ break;
+
+ case '\v':
+ _tw.Write("`v");
+ break;
+
+ case '`':
+ _tw.Write("``");
+ break;
+
+ case '"':
+ _tw.Write("`\"");
+ break;
+
+ case '$':
+ _tw.Write("`$");
+ break;
+
+ case '\u001b':
+ _tw.Write("`e");
+ break;
+
+ default:
+ if (c < 128)
+ {
+ _tw.Write(c);
+ break;
+ }
+
+ _tw.Write("`u{");
+ _tw.Write(((int)c).ToString("X"));
+ _tw.Write('}');
+ break;
+ }
+ }
+
+ _tw.Write('"');
+ }
+
+ private void WriteStatementBlock(IReadOnlyList statements, IReadOnlyList traps = null)
+ {
+ bool wroteTrap = false;
+ if (!IsEmpty(traps))
+ {
+ wroteTrap = true;
+ foreach (TrapStatementAst trap in traps)
+ {
+ trap.Visit(this);
+ }
+ }
+
+ if (!IsEmpty(statements))
+ {
+ if (wroteTrap)
+ {
+ Newline();
+ }
+
+ statements[0].Visit(this);
+ StatementAst previousStatement = statements[0];
+
+ for (int i = 1; i < statements.Count; i++)
+ {
+ if (IsBlockStatement(previousStatement))
+ {
+ _tw.Write(_newline);
+ }
+ Newline();
+ statements[i].Visit(this);
+ previousStatement = statements[i];
+ }
+ }
+ }
+
+ private void WriteHashtableEntry(Tuple entry)
+ {
+ entry.Item1.Visit(this);
+ _tw.Write(" = ");
+ entry.Item2.Visit(this);
+ }
+
+ private void BeginBlock()
+ {
+ Newline();
+ _tw.Write('{');
+ Indent();
+ }
+
+ private void EndBlock()
+ {
+ Dedent();
+ _tw.Write('}');
+ }
+
+ private void Newline()
+ {
+ _tw.Write(_newline);
+
+ for (int i = 0; i < _indent; i++)
+ {
+ _tw.Write(_indentStr);
+ }
+ }
+
+ private void Newline(int count)
+ {
+ for (int i = 0; i < count - 1; i++)
+ {
+ _tw.Write(_newline);
+ }
+
+ Newline();
+ }
+
+ private void EndStatement()
+ {
+ _tw.Write(_newline);
+ }
+
+ private void Indent()
+ {
+ _indent++;
+ Newline();
+ }
+
+ private void Dedent()
+ {
+ _indent--;
+ Newline();
+ }
+
+ private void Intersperse(IReadOnlyList asts, string separator)
+ {
+ if (IsEmpty(asts))
+ {
+ return;
+ }
+
+ asts[0].Visit(this);
+
+ for (int i = 1; i < asts.Count; i++)
+ {
+ _tw.Write(separator);
+ asts[i].Visit(this);
+ }
+ }
+
+ private void Intersperse(IReadOnlyList asts, Action writeSeparator)
+ {
+ if (IsEmpty(asts))
+ {
+ return;
+ }
+
+ asts[0].Visit(this);
+
+ for (int i = 1; i < asts.Count; i++)
+ {
+ writeSeparator();
+ asts[i].Visit(this);
+ }
+ }
+
+ private void Intersperse(IReadOnlyList astObjects, Action writeObject, Action writeSeparator)
+ {
+ if (IsEmpty(astObjects))
+ {
+ return;
+ }
+
+ writeObject(astObjects[0]);
+
+ for (int i = 1; i < astObjects.Count; i++)
+ {
+ writeSeparator();
+ writeObject(astObjects[i]);
+ }
+ }
+
+ private void WriteCommentsToAstPosition(Ast ast)
+ {
+ if (_tokens == null)
+ {
+ return;
+ }
+
+ Token currToken = _tokens[_tokenIndex];
+ while (currToken.Extent.EndOffset < ast.Extent.StartOffset)
+ {
+ if (currToken.Kind == TokenKind.Comment)
+ {
+ _tw.Write(currToken.Text);
+
+ if (currToken.Text.StartsWith("#"))
+ {
+ Newline();
+ }
+ }
+
+ _tokenIndex++;
+ currToken = _tokens[_tokenIndex];
+ }
+ }
+
+ private bool IsBlockStatement(StatementAst statementAst)
+ {
+ switch (statementAst)
+ {
+ case PipelineBaseAst _:
+ case ReturnStatementAst _:
+ case ThrowStatementAst _:
+ case ExitStatementAst _:
+ case BreakStatementAst _:
+ case ContinueStatementAst _:
+ return false;
+
+ default:
+ return true;
+ }
+ }
+
+ private string GetTokenString(TokenKind tokenKind)
+ {
+ switch (tokenKind)
+ {
+ case TokenKind.Ampersand:
+ return "&";
+
+ case TokenKind.And:
+ return "-and";
+
+ case TokenKind.AndAnd:
+ return "&&";
+
+ case TokenKind.As:
+ return "-as";
+
+ case TokenKind.Assembly:
+ return "assembly";
+
+ case TokenKind.AtCurly:
+ return "@{";
+
+ case TokenKind.AtParen:
+ return "@(";
+
+ case TokenKind.Band:
+ return "-band";
+
+ case TokenKind.Base:
+ return "base";
+
+ case TokenKind.Begin:
+ return "begin";
+
+ case TokenKind.Bnot:
+ return "-bnot";
+
+ case TokenKind.Bor:
+ return "-bnor";
+
+ case TokenKind.Break:
+ return "break";
+
+ case TokenKind.Bxor:
+ return "-bxor";
+
+ case TokenKind.Catch:
+ return "catch";
+
+ case TokenKind.Ccontains:
+ return "-ccontains";
+
+ case TokenKind.Ceq:
+ return "-ceq";
+
+ case TokenKind.Cge:
+ return "-cge";
+
+ case TokenKind.Cgt:
+ return "-cgt";
+
+ case TokenKind.Cin:
+ return "-cin";
+
+ case TokenKind.Class:
+ return "class";
+
+ case TokenKind.Cle:
+ return "-cle";
+
+ case TokenKind.Clike:
+ return "-clike";
+
+ case TokenKind.Clt:
+ return "-clt";
+
+ case TokenKind.Cmatch:
+ return "-cmatch";
+
+ case TokenKind.Cne:
+ return "-cne";
+
+ case TokenKind.Cnotcontains:
+ return "-cnotcontains";
+
+ case TokenKind.Cnotin:
+ return "-cnotin";
+
+ case TokenKind.Cnotlike:
+ return "-cnotlike";
+
+ case TokenKind.Cnotmatch:
+ return "-cnotmatch";
+
+ case TokenKind.Colon:
+ return ":";
+
+ case TokenKind.ColonColon:
+ return "::";
+
+ case TokenKind.Comma:
+ return ",";
+
+ case TokenKind.Configuration:
+ return "configuration";
+
+ case TokenKind.Continue:
+ return "continue";
+
+ case TokenKind.Creplace:
+ return "-creplace";
+
+ case TokenKind.Csplit:
+ return "-csplit";
+
+ case TokenKind.Data:
+ return "data";
+
+ case TokenKind.Define:
+ return "define";
+
+ case TokenKind.Divide:
+ return "/";
+
+ case TokenKind.DivideEquals:
+ return "/=";
+
+ case TokenKind.Do:
+ return "do";
+
+ case TokenKind.DollarParen:
+ return "$(";
+
+ case TokenKind.Dot:
+ return ".";
+
+ case TokenKind.DotDot:
+ return "..";
+
+ case TokenKind.Dynamicparam:
+ return "dynamicparam";
+
+ case TokenKind.Else:
+ return "else";
+
+ case TokenKind.ElseIf:
+ return "elseif";
+
+ case TokenKind.End:
+ return "end";
+
+ case TokenKind.Enum:
+ return "enum";
+
+ case TokenKind.Equals:
+ return "=";
+
+ case TokenKind.Exclaim:
+ return "!";
+
+ case TokenKind.Exit:
+ return "exit";
+
+ case TokenKind.Filter:
+ return "filter";
+
+ case TokenKind.Finally:
+ return "finally";
+
+ case TokenKind.For:
+ return "for";
+
+ case TokenKind.Foreach:
+ return "foreach";
+
+ case TokenKind.Format:
+ return "-f";
+
+ case TokenKind.From:
+ return "from";
+
+ case TokenKind.Function:
+ return "function";
+
+ case TokenKind.Hidden:
+ return "hidden";
+
+ case TokenKind.Icontains:
+ return "-contains";
+
+ case TokenKind.Ieq:
+ return "-eq";
+
+ case TokenKind.If:
+ return "if";
+
+ case TokenKind.Ige:
+ return "-ge";
+
+ case TokenKind.Igt:
+ return "-gt";
+
+ case TokenKind.Iin:
+ return "-in";
+
+ case TokenKind.Ile:
+ return "-le";
+
+ case TokenKind.Ilike:
+ return "-like";
+
+ case TokenKind.Ilt:
+ return "-lt";
+
+ case TokenKind.Imatch:
+ return "-match";
+
+ case TokenKind.In:
+ return "-in";
+
+ case TokenKind.Ine:
+ return "-ne";
+
+ case TokenKind.InlineScript:
+ return "inlinescript";
+
+ case TokenKind.Inotcontains:
+ return "-notcontains";
+
+ case TokenKind.Inotin:
+ return "-notin";
+
+ case TokenKind.Inotlike:
+ return "-notlike";
+
+ case TokenKind.Inotmatch:
+ return "-notmatch";
+
+ case TokenKind.Interface:
+ return "interface";
+
+ case TokenKind.Ireplace:
+ return "-replace";
+
+ case TokenKind.Is:
+ return "-is";
+
+ case TokenKind.IsNot:
+ return "-isnot";
+
+ case TokenKind.Isplit:
+ return "-split";
+
+ case TokenKind.Join:
+ return "-join";
+
+ case TokenKind.LBracket:
+ return "[";
+
+ case TokenKind.LCurly:
+ return "{";
+
+ case TokenKind.LParen:
+ return "(";
+
+ case TokenKind.Minus:
+ return "-";
+
+ case TokenKind.MinusEquals:
+ return "-=";
+
+ case TokenKind.MinusMinus:
+ return "--";
+
+ case TokenKind.Module:
+ return "module";
+
+ case TokenKind.Multiply:
+ return "*";
+
+ case TokenKind.MultiplyEquals:
+ return "*=";
+
+ case TokenKind.Namespace:
+ return "namespace";
+
+ case TokenKind.NewLine:
+ return Environment.NewLine;
+
+ case TokenKind.Not:
+ return "-not";
+
+ case TokenKind.Or:
+ return "-or";
+
+ case TokenKind.OrOr:
+ return "||";
+
+ case TokenKind.Parallel:
+ return "parallel";
+
+ case TokenKind.Param:
+ return "param";
+
+ case TokenKind.Pipe:
+ return "|";
+
+ case TokenKind.Plus:
+ return "+";
+
+ case TokenKind.PlusEquals:
+ return "+=";
+
+ case TokenKind.PlusPlus:
+ return "++";
+
+ case TokenKind.PostfixMinusMinus:
+ return "--";
+
+ case TokenKind.PostfixPlusPlus:
+ return "++";
+
+ case TokenKind.Private:
+ return "private";
+
+ case TokenKind.Process:
+ return "process";
+
+ case TokenKind.Public:
+ return "public";
+
+ case TokenKind.RBracket:
+ return "]";
+
+ case TokenKind.RCurly:
+ return "}";
+
+ case TokenKind.Rem:
+ return "%";
+
+ case TokenKind.RemainderEquals:
+ return "%=";
+
+ case TokenKind.Return:
+ return "return";
+
+ case TokenKind.RParen:
+ return ")";
+
+ case TokenKind.Semi:
+ return ";";
+
+ case TokenKind.Sequence:
+ return "sequence";
+
+ case TokenKind.Shl:
+ return "-shl";
+
+ case TokenKind.Shr:
+ return "-shr";
+
+ case TokenKind.Static:
+ return "static";
+
+ case TokenKind.Switch:
+ return "switch";
+
+ case TokenKind.Throw:
+ return "throw";
+
+ case TokenKind.Trap:
+ return "trap";
+
+ case TokenKind.Try:
+ return "try";
+
+ case TokenKind.Until:
+ return "until";
+
+ case TokenKind.Using:
+ return "using";
+
+ case TokenKind.Var:
+ return "var";
+
+ case TokenKind.While:
+ return "while";
+
+ case TokenKind.Workflow:
+ return "workflow";
+
+ case TokenKind.Xor:
+ return "-xor";
+
+ default:
+ throw new ArgumentException($"Unable to stringify token kind '{tokenKind}'");
+ }
+ }
+
+ private char GetStreamIndicator(RedirectionStream stream)
+ {
+ switch (stream)
+ {
+ case RedirectionStream.All:
+ return '*';
+
+ case RedirectionStream.Debug:
+ return '5';
+
+ case RedirectionStream.Error:
+ return '2';
+
+ case RedirectionStream.Information:
+ return '6';
+
+ case RedirectionStream.Output:
+ return '1';
+
+ case RedirectionStream.Verbose:
+ return '4';
+
+ case RedirectionStream.Warning:
+ return '3';
+
+ default:
+ throw new ArgumentException($"Unknown redirection stream: '{stream}'");
+ }
+ }
+
+ private static bool IsEmpty(IReadOnlyCollection collection)
+ {
+ return collection == null
+ || collection.Count == 0;
+ }
+ }
+
+ public class ParseException : Exception
+ {
+ public ParseException(IReadOnlyList parseErrors)
+ : base("A parse error was encountered while parsing the input script")
+ {
+ ParseErrors = parseErrors;
+ }
+
+ public IReadOnlyList ParseErrors { get; }
+ }
+}
diff --git a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs
new file mode 100644
index 0000000..a4b1674
--- /dev/null
+++ b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs
@@ -0,0 +1,751 @@
+using Microsoft.PowerShell.AstTools;
+using System;
+using System.IO;
+using System.Management.Automation.Language;
+using Xunit;
+
+namespace test
+{
+ public class PrettyPrinterTests
+ {
+ private readonly StringPrettyPrinter _pp;
+
+ public PrettyPrinterTests()
+ {
+ _pp = new StringPrettyPrinter();
+ }
+
+ [Theory()]
+ [InlineData("$x")]
+ [InlineData("- $x")]
+ [InlineData("- 1")]
+ [InlineData("-1")]
+ [InlineData("$x++")]
+ [InlineData("$i--")]
+ [InlineData("--$i")]
+ [InlineData("++$i")]
+ [InlineData("- --$i")]
+ [InlineData("-not $true")]
+ [InlineData("$x + $y")]
+ [InlineData("'{0}' -f 'Hi'")]
+ [InlineData("'1,2,3' -split ','")]
+ [InlineData("1, 2, 3 -join ' '")]
+ [InlineData("Get-ChildItem")]
+ [InlineData("gci >test.txt")]
+ [InlineData("gci >test.txt 2>errs.txt")]
+ [InlineData("gci 2>&1")]
+ [InlineData("Get-ChildItem -Recurse -Path ./here")]
+ [InlineData("Get-ChildItem -Recurse -Path \"$PWD\\there\"")]
+ [InlineData("exit 1")]
+ [InlineData("return ($result + 3)")]
+ [InlineData("throw [System.Exception]'Bad'")]
+ [InlineData("break outer")]
+ [InlineData("continue anotherLoop")]
+ [InlineData("3 + $(Get-Random)")]
+ [InlineData("'banana cake'")]
+ [InlineData("'banana''s cake'")]
+ [InlineData("Get-ChildItem | ? Name -like 'banana' | % FullPath")]
+ [InlineData("[type]::GetThings()")]
+ [InlineData("[type]::Member")]
+ [InlineData("$x.DoThings($x, 8, $y)")]
+ [InlineData("$x.Property")]
+ [InlineData("$x.$property")]
+ [InlineData("[type]::$property")]
+ [InlineData("$type::$property")]
+ [InlineData("[type]::$method(1, 2, 'x')")]
+ [InlineData("$k.$method(1, 2, 'x')")]
+ [InlineData(@"""I like ducks""")]
+ [InlineData(@"""I`nlike`nducks""")]
+ [InlineData(@"""I`tlike`n`rducks""")]
+ [InlineData(@"$x[0]")]
+ [InlineData(@"$x[$i + 1]")]
+ [InlineData(@"$x.Item[$i + 1]")]
+ [InlineData(@"1, 2, 3")]
+ [InlineData(@"1, 'Hi', 3")]
+ [InlineData(@"@(1, 'Hi', 3)")]
+#if PS7
+ [InlineData("Invoke-Expression 'runCommand' &")]
+ [InlineData(@"""I`e[31mlike`e[0mducks""")]
+ [InlineData("1 && 2")]
+ [InlineData("sudo apt update && sudo apt upgrade")]
+ [InlineData("firstthing && secondthing &")]
+ [InlineData("Get-Item ./thing || $(throw 'Bad')")]
+ [InlineData(@"$true ? 'true' : 'false'")]
+#endif
+ public void TestPrettyPrintingIdempotentForSimpleStatements(string input)
+ {
+ AssertPrettyPrintedStatementIdentical(input);
+ }
+
+ [Fact]
+ public void TestEmptyHashtable()
+ {
+ string script = "@{}";
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestSimpleHashtable()
+ {
+ string script = @"
+@{
+ One = 'One'
+ Two = $x
+ $banana = 7
+}
+";
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestComplexHashtable()
+ {
+ string script = @"
+@{
+ One = @{
+ SubOne = 1
+ SubTwo = {
+ $x
+ }
+ }
+ Two = $x
+ $banana = @(7, 3, 4)
+}
+";
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestFunction()
+ {
+ string script = @"
+function Test-Function
+{
+ Write-Host 'Hello!'
+}
+";
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestAdvancedFunction()
+ {
+ string script = @"
+function Test-Greeting
+{
+ [CmdletBinding()]
+ param(
+ [Parameter()]
+ [string]
+ $Greeting
+ )
+
+ Write-Host $Greeting
+}
+";
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestScriptBlock()
+ {
+ string script = @"
+{
+ $args[0] + 2
+}";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestScriptBlockInvocation()
+ {
+ string script = @"
+& {
+ $args[0] + 2
+}";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestScriptBlockDotSource()
+ {
+ string script = @"
+. {
+ $args[0] + 2
+}";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestScriptBlockEmptyParams()
+ {
+ string script = @"
+{
+ param()
+}";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestScriptBlockParams()
+ {
+ string script = @"
+{
+ param(
+ $String,
+
+ $Switch
+ )
+}";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestScriptBlockAttributedParams()
+ {
+ string script = @"
+{
+ param(
+ [Parameter()]
+ [string]
+ $String,
+
+ [Parameter()]
+ [switch]
+ $Switch
+ )
+}";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestScriptBlockParamAttributesWithArguments()
+ {
+ string script = @"
+{
+ param(
+ [Parameter(Mandatory)]
+ [string]
+ $String,
+
+ [Parameter(Mandatory = $true)]
+ [AnotherAttribute(1, 2)]
+ [ThirdAttribute(1, 2, Fun = $true)]
+ [ThirdAttribute(1, 2, Fun)]
+ [switch]
+ $Switch
+ )
+}";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestWhileLoop()
+ {
+ string script = @"
+while ($i -lt 10)
+{
+ $i++
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestForeachLoop()
+ {
+ string script = @"
+foreach ($n in 1, 2, 3)
+{
+ Write-Output ($n + 1)
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestForLoop()
+ {
+ string script = @"
+for ($i = 0; $i -lt $args.Count; $i++)
+{
+ $args[$i]
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestSwitchCase()
+ {
+ string script = @"
+switch ($x)
+{
+ 1
+ {
+ 'One'
+ break
+ }
+
+ 2
+ {
+ 'Two'
+ break
+ }
+
+ 3
+ {
+ 'Three'
+ break
+ }
+}
+";
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestDoWhileLoop()
+ {
+ string script = @"
+do
+{
+ $x++
+} while ($x -lt 10)
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestDoUntilLoop()
+ {
+ string script = @"
+do
+{
+ $x++
+} until ($x -eq 10)
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestIf()
+ {
+ string script = @"
+if ($x)
+{
+ $x.Fun()
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestElseIf()
+ {
+ string script = @"
+if ($x)
+{
+ $x.Fun()
+}
+elseif ($y)
+{
+ $y.NoFun()
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestElse()
+ {
+ string script = @"
+if ($x)
+{
+ $x.Fun()
+}
+else
+{
+ 'nothing'
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestFullIfElse()
+ {
+ string script = @"
+if ($x)
+{
+ $x.Fun()
+}
+elseif ($y)
+{
+ $y.NoFun()
+}
+else
+{
+ 'nothing'
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestTryCatch()
+ {
+ string script = @"
+try
+{
+ Write-Error 'Bad' -ErrorAction Stop
+}
+catch
+{
+ $_
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestTryCatchWithType()
+ {
+ string script = @"
+try
+{
+ Write-Error 'Bad' -ErrorAction Stop
+}
+catch [System.Exception]
+{
+ $_
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestTryFinally()
+ {
+ string script = @"
+try
+{
+ Write-Error 'Bad' -ErrorAction Stop
+}
+finally
+{
+ Write-Host 'done'
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestTryCatchFinally()
+ {
+ string script = @"
+try
+{
+ Write-Error 'Bad' -ErrorAction Stop
+}
+catch
+{
+ $_
+}
+finally
+{
+ Write-Host 'done'
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestSimpleClass()
+ {
+ string script = @"
+class Duck
+{
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestClassProperty()
+ {
+ string script = @"
+class Duck
+{
+ [string]$Name
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestClassMethod()
+ {
+ string script = @"
+class Duck
+{
+ [string]GetGreeting([string]$Name)
+ {
+ return ""Hi $Name""
+ }
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestClassConstructor()
+ {
+ string script = @"
+class Duck
+{
+ Duck($name)
+ {
+ $this.Name = $name
+ }
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+
+ [Fact]
+ public void TestClassConstructorWithBaseClass()
+ {
+ string script = @"
+class MyHashtable : hashtable
+{
+ MyHashtable([int]$count) : base($count)
+ {
+ }
+}
+";
+
+ AssertPrettyPrintedStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestUsingNamespace()
+ {
+ string script = "using namespace System.Collections.Generic\n";
+ AssertPrettyPrintedUsingStatementIdentical(script);
+ }
+
+ [Fact]
+ public void TestUsingModule()
+ {
+ string script = "using module PrettyPrintingTestModule\n";
+ using (ModuleContext.Create("PrettyPrintingTestModule", new Version(1, 0)))
+ {
+ AssertPrettyPrintedUsingStatementIdentical(script);
+ }
+ }
+
+ [Fact]
+ public void TestUsingModuleWithHashtable()
+ {
+ string script = "using module @{ ModuleName = 'PrettyPrintingTestModule'; ModuleVersion = '1.18' }\n";
+ using (ModuleContext.Create("PrettyPrintingTestModule", new Version(1, 18)))
+ {
+ AssertPrettyPrintedUsingStatementIdentical(script);
+ }
+ }
+
+ [Fact]
+ public void TestFullScript1()
+ {
+ string script = @"
+[CmdletBinding(DefaultParameterSetName = ""BuildOne"")]
+param(
+ [Parameter(ParameterSetName = ""BuildAll"")]
+ [switch]
+ $All,
+
+ [Parameter(ParameterSetName = ""BuildOne"")]
+ [ValidateRange(3, 7)]
+ [int]
+ $PSVersion = $PSVersionTable.PSVersion.Major,
+
+ [Parameter(ParameterSetName = ""BuildOne"")]
+ [Parameter(ParameterSetName = ""BuildAll"")]
+ [ValidateSet(""Debug"", ""Release"")]
+ [string]
+ $Configuration = ""Debug"",
+
+ [Parameter(ParameterSetName = ""BuildDocumentation"")]
+ [switch]
+ $Documentation,
+
+ [Parameter(ParameterSetName = 'BuildAll')]
+ [Parameter(ParameterSetName = 'BuildOne')]
+ [switch]
+ $Clobber,
+
+ [Parameter(Mandatory = $true, ParameterSetName = 'Clean')]
+ [switch]
+ $Clean,
+
+ [Parameter(Mandatory = $true, ParameterSetName = 'Test')]
+ [switch]
+ $Test,
+
+ [Parameter(ParameterSetName = 'Test')]
+ [switch]
+ $InProcess,
+
+ [Parameter(ParameterSetName = 'Bootstrap')]
+ [switch]
+ $Bootstrap
+)
+
+begin
+{
+ if ($PSVersion -gt 6)
+ {
+ Write-Host ""Building PowerShell Core version""
+ $PSVersion = 6
+ }
+}
+
+end
+{
+ Import-Module -Force (Join-Path $PSScriptRoot build.psm1)
+ if ($Clean -or $Clobber)
+ {
+ Remove-Build
+ if ($PSCmdlet.ParameterSetName -eq ""Clean"")
+ {
+ return
+ }
+ }
+
+ $setName = $PSCmdlet.ParameterSetName
+ switch ($setName)
+ {
+ ""BuildAll""
+ {
+ Start-ScriptAnalyzerBuild -All -Configuration $Configuration
+ }
+
+ ""BuildDocumentation""
+ {
+ Start-ScriptAnalyzerBuild -Documentation
+ }
+
+ ""BuildOne""
+ {
+ $buildArgs = @{
+ PSVersion = $PSVersion
+ Configuration = $Configuration
+ }
+ Start-ScriptAnalyzerBuild @buildArgs
+ }
+
+ ""Bootstrap""
+ {
+ Install-DotNet
+ return
+ }
+
+ ""Test""
+ {
+ Test-ScriptAnalyzer -InProcess:$InProcess
+ return
+ }
+
+ default
+ {
+ throw ""Unexpected parameter set '$setName'""
+ }
+ }
+}
+";
+
+ AssertPrettyPrintedScriptIdentical(script);
+ }
+
+ private void AssertPrettyPrintedStatementIdentical(string input)
+ {
+ Assert.Equal(NormalizeScript(input), NormalizeScript(_pp.PrettyPrintInput(input)));
+ }
+
+ private void AssertPrettyPrintedUsingStatementIdentical(string input)
+ {
+ Assert.Equal(NormalizeScript(input), NormalizeScript(_pp.PrettyPrintInput(input)));
+ }
+
+ private void AssertPrettyPrintedScriptIdentical(string input)
+ {
+ Assert.Equal(NormalizeScript(input), NormalizeScript(_pp.PrettyPrintInput(input)));
+ }
+
+ private static string NormalizeScript(string input)
+ {
+ return input.Trim().Replace(Environment.NewLine, "\n");
+ }
+ }
+
+ internal class ModuleContext : IDisposable
+ {
+ public static ModuleContext Create(string moduleName, Version moduleVersion)
+ {
+ string tmpDirPath = Path.GetTempPath();
+ Directory.CreateDirectory(tmpDirPath);
+ string modulePath = Path.Combine(tmpDirPath, moduleName);
+ Directory.CreateDirectory(modulePath);
+ string manifestPath = Path.Combine(modulePath, $"{moduleName}.psd1");
+ File.WriteAllText(manifestPath, $"@{{ ModuleVersion = '{moduleVersion}' }}");
+
+ string oldPSModulePath = Environment.GetEnvironmentVariable("PSModulePath");
+ Environment.SetEnvironmentVariable("PSModulePath", tmpDirPath);
+
+ return new ModuleContext(modulePath, oldPSModulePath);
+ }
+
+ private readonly string _psModulePath;
+
+ private readonly string _modulePath;
+
+ public ModuleContext(
+ string modulePath,
+ string psModulePath)
+ {
+ _modulePath = modulePath;
+ _psModulePath = psModulePath;
+ }
+
+ public void Dispose()
+ {
+ Directory.Delete(_modulePath, recursive: true);
+ Environment.SetEnvironmentVariable("PSModulePath", _psModulePath);
+ }
+ }
+}
diff --git a/Modules/Microsoft.PowerShell.AstTools/test/test.csproj b/Modules/Microsoft.PowerShell.AstTools/test/test.csproj
new file mode 100644
index 0000000..a88bb47
--- /dev/null
+++ b/Modules/Microsoft.PowerShell.AstTools/test/test.csproj
@@ -0,0 +1,31 @@
+
+
+
+ netcoreapp3.1;net471
+ false
+
+
+
+ $(DefineConstants);PS7
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+