From 895ce651b48be982695268e51e2ca9a388ff3b91 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 20 Feb 2020 23:33:21 -0800 Subject: [PATCH 01/17] Add AstTools module with pretty printer --- .gitignore | 3 + .../Microsoft.PowerShell.AstTools.csproj | 25 + .../PrettyPrinter.cs | 1331 +++++++++++++++++ 3 files changed, 1359 insertions(+) create mode 100644 Modules/Microsoft.PowerShell.AstTools/Microsoft.PowerShell.AstTools.csproj create mode 100644 Modules/Microsoft.PowerShell.AstTools/PrettyPrinter.cs 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/Microsoft.PowerShell.AstTools.csproj b/Modules/Microsoft.PowerShell.AstTools/Microsoft.PowerShell.AstTools.csproj new file mode 100644 index 0000000..ab692df --- /dev/null +++ b/Modules/Microsoft.PowerShell.AstTools/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/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/PrettyPrinter.cs new file mode 100644 index 0000000..542f8ff --- /dev/null +++ b/Modules/Microsoft.PowerShell.AstTools/PrettyPrinter.cs @@ -0,0 +1,1331 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Text; + +namespace Microsoft.PowerShell.PrettyPrinter +{ + public class PrettyPrinter : AstVisitor2 + { + private readonly StringBuilder _sb; + + private readonly string _newline; + + private readonly string _indentStr; + + private readonly string _comma; + + private int _indent; + + public PrettyPrinter() + { + _sb = new StringBuilder(); + _newline = Environment.NewLine; + _indentStr = " "; + _comma = ", "; + _indent = 0; + } + + public void Clear() + { + _sb.Clear(); + } + + public string GetString() + { + return _sb.ToString(); + } + + public override AstVisitAction VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) + { + _sb.Append("@("); + + arrayExpressionAst.SubExpression.Visit(this); + + _sb.Append(")"); + + 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); + + _sb.Append(' ').Append(GetTokenString(assignmentStatementAst.Operator)).Append(' '); + + assignmentStatementAst.Right.Visit(this); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitAttribute(AttributeAst attributeAst) + { + _sb.Append('[').Append(attributeAst.TypeName).Append('('); + + Intersperse(attributeAst.PositionalArguments, _comma); + + if (attributeAst.NamedArguments != null && attributeAst.NamedArguments.Count > 0) + { + _sb.Append(_comma); + Intersperse(attributeAst.NamedArguments, _comma); + } + + _sb.Append(")]"); + 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) + { + throw new NotImplementedException(); + } + + public override AstVisitAction VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) + { + binaryExpressionAst.Left.Visit(this); + + _sb.Append(' ').Append(GetTokenString(binaryExpressionAst.Operator)).Append(' '); + + binaryExpressionAst.Right.Visit(this); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitBlockStatement(BlockStatementAst blockStatementAst) + { + throw new NotImplementedException(); + } + + public override AstVisitAction VisitBreakStatement(BreakStatementAst breakStatementAst) + { + _sb.Append("break"); + + if (breakStatementAst.Label != null) + { + _sb.Append(' '); + breakStatementAst.Label.Visit(this); + } + + Newline(); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitCatchClause(CatchClauseAst catchClauseAst) + { + throw new NotImplementedException(); + } + + public override AstVisitAction VisitCommand(CommandAst commandAst) + { + if (commandAst.InvocationOperator != TokenKind.Unknown) + { + _sb.Append(GetTokenString(commandAst.InvocationOperator)).Append(' '); + } + + Intersperse(commandAst.CommandElements, " "); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitCommandExpression(CommandExpressionAst commandExpressionAst) + { + commandExpressionAst.Expression.Visit(this); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) + { + _sb.Append('-'); + _sb.Append(commandParameterAst.ParameterName); + + if (commandParameterAst.Argument != null) + { + _sb.Append(':'); + commandParameterAst.Argument.Visit(this); + } + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) + { + throw new NotImplementedException(); + } + + public override AstVisitAction VisitConstantExpression(ConstantExpressionAst constantExpressionAst) + { + if (constantExpressionAst.Value == null) + { + _sb.Append("$null"); + } + else if (constantExpressionAst.StaticType == typeof(bool)) + { + _sb.Append((bool)constantExpressionAst.Value ? "$true" : "$false"); + } + else + { + _sb.Append(constantExpressionAst.Value); + } + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitContinueStatement(ContinueStatementAst continueStatementAst) + { + _sb.Append("continue"); + + if (continueStatementAst.Label != null) + { + _sb.Append(' '); + continueStatementAst.Label.Visit(this); + } + + 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) + { + _sb.Append("do"); + doUntilStatementAst.Body.Visit(this); + _sb.Append(" until ("); + doUntilStatementAst.Condition.Visit(this); + _sb.Append(')'); + EndStatement(); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) + { + _sb.Append("do"); + doWhileStatementAst.Body.Visit(this); + _sb.Append(" while ("); + doWhileStatementAst.Condition.Visit(this); + _sb.Append(')'); + 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) + { + _sb.Append("exit"); + + if (exitStatementAst.Pipeline != null) + { + _sb.Append(' '); + exitStatementAst.Pipeline.Visit(this); + } + + EndStatement(); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) + { + _sb.Append('"').Append(expandableStringExpressionAst.Value).Append('"'); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitFileRedirection(FileRedirectionAst redirectionAst) + { + throw new NotImplementedException(); + } + + public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst) + { + _sb.Append("foreach ("); + forEachStatementAst.Variable.Visit(this); + _sb.Append(" in "); + forEachStatementAst.Condition.Visit(this); + _sb.Append(")"); + Newline(); + forEachStatementAst.Body.Visit(this); + EndStatement(); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitForStatement(ForStatementAst forStatementAst) + { + _sb.Append("for ("); + forStatementAst.Initializer.Visit(this); + _sb.Append("; "); + forStatementAst.Condition.Visit(this); + _sb.Append("; "); + forStatementAst.Iterator.Visit(this); + _sb.Append(')'); + forStatementAst.Body.Visit(this); + EndStatement(); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) + { + _sb.Append(functionDefinitionAst.IsFilter ? "filter " : "function "); + _sb.Append(functionDefinitionAst.Name); + Newline(); + functionDefinitionAst.Body.Visit(this); + EndStatement(); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) + { + throw new NotImplementedException(); + } + + public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) + { + _sb.Append("@{"); + + if (hashtableAst.KeyValuePairs == null || hashtableAst.KeyValuePairs.Count == 0) + { + _sb.Append('}'); + return AstVisitAction.SkipChildren; + } + + Indent(); + WriteHashtableEntry(hashtableAst.KeyValuePairs[0]); + for (int i = 1; i < hashtableAst.KeyValuePairs.Count; i++) + { + Newline(); + WriteHashtableEntry(hashtableAst.KeyValuePairs[i]); + } + Dedent(); + _sb.Append('}'); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst) + { + _sb.Append("if ("); + ifStmtAst.Clauses[0].Item1.Visit(this); + _sb.Append(')'); + Newline(); + ifStmtAst.Clauses[0].Item2.Visit(this); + + for (int i = 1; i < ifStmtAst.Clauses.Count; i++) + { + _sb.Append("elseif ("); + ifStmtAst.Clauses[i].Item1.Visit(this); + _sb.Append(')'); + Newline(); + ifStmtAst.Clauses[i].Item2.Visit(this); + } + + if (ifStmtAst.ElseClause != null) + { + _sb.Append("else"); + Newline(); + ifStmtAst.ElseClause.Visit(this); + } + + EndStatement(); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitIndexExpression(IndexExpressionAst indexExpressionAst) + { + indexExpressionAst.Target.Visit(this); + _sb.Append('['); + indexExpressionAst.Index.Visit(this); + _sb.Append(']'); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst) + { + methodCallAst.Expression.Visit(this); + _sb.Append(methodCallAst.Static ? "::" : "."); + methodCallAst.Member.Visit(this); + _sb.Append('('); + Intersperse(methodCallAst.Arguments, ", "); + _sb.Append(')'); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst) + { + memberExpressionAst.Expression.Visit(this); + _sb.Append(memberExpressionAst.Static ? "::" : "."); + memberExpressionAst.Member.Visit(this); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst redirectionAst) + { + throw new NotImplementedException(); + } + + public override AstVisitAction VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) + { + _sb.Append(namedAttributeArgumentAst.ArgumentName); + + if (!namedAttributeArgumentAst.ExpressionOmitted && namedAttributeArgumentAst.Argument != null) + { + _sb.Append(" = "); + namedAttributeArgumentAst.Argument.Visit(this); + } + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst) + { + if (!namedBlockAst.Unnamed) + { + _sb.Append(GetTokenString(namedBlockAst.BlockKind)).Append(' '); + } + + BeginBlock(); + + WriteStatementBlock(namedBlockAst.Statements, namedBlockAst.Traps); + + EndBlock(); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) + { + _sb.Append("param("); + + if (paramBlockAst.Parameters == null || paramBlockAst.Parameters.Count == 0) + { + _sb.Append(')'); + return AstVisitAction.SkipChildren; + } + + Indent(); + + paramBlockAst.Parameters[0].Visit(this); + + for (int i = 1; i < paramBlockAst.Parameters.Count; i++) + { + _sb.Append(','); + Newline(); + Newline(); + paramBlockAst.Parameters[i].Visit(this); + } + + _sb.Append(')'); + Dedent(); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitParameter(ParameterAst parameterAst) + { + if (parameterAst.Attributes != null && parameterAst.Attributes.Count > 0) + { + foreach (AttributeAst attribute in parameterAst.Attributes) + { + attribute.Visit(this); + Newline(); + } + } + + parameterAst.Name.Visit(this); + + if (parameterAst.DefaultValue != null) + { + _sb.Append(" = "); + parameterAst.DefaultValue.Visit(this); + } + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitParenExpression(ParenExpressionAst parenExpressionAst) + { + _sb.Append('('); + parenExpressionAst.Pipeline.Visit(this); + _sb.Append(')'); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) + { + Intersperse(pipelineAst.PipelineElements, " | "); + return AstVisitAction.SkipChildren; + } + +#if PS7 + public override AstVisitAction VisitPipelineChain(PipelineChainAst statementChain) + { + statementChain.LhsPipelineChain.Visit(this); + _sb.Append(' ').Append(GetTokenString(statementChain.Operator)).Append(' '); + statementChain.RhsPipeline.Visit(this); + return AstVisitAction.SkipChildren; + } +#endif + + public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) + { + throw new NotImplementedException(); + } + + public override AstVisitAction VisitReturnStatement(ReturnStatementAst returnStatementAst) + { + _sb.Append("return"); + + if (returnStatementAst.Pipeline != null) + { + _sb.Append(' '); + returnStatementAst.Pipeline.Visit(this); + } + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) + { + _sb.Append('{'); + Indent(); + scriptBlockAst.ParamBlock.Visit(this); + + bool useExplicitEndBlock = false; + + if (scriptBlockAst.DynamicParamBlock != null) + { + Newline(); + scriptBlockAst.DynamicParamBlock.Visit(this); + } + + if (scriptBlockAst.BeginBlock != null) + { + useExplicitEndBlock = true; + Newline(); + scriptBlockAst.BeginBlock.Visit(this); + } + + if (scriptBlockAst.ProcessBlock != null) + { + useExplicitEndBlock = true; + Newline(); + scriptBlockAst.ProcessBlock.Visit(this); + } + + Newline(); + if (useExplicitEndBlock) + { + scriptBlockAst.EndBlock.Visit(this); + } + else + { + WriteStatementBlock(scriptBlockAst.EndBlock.Statements, scriptBlockAst.EndBlock.Traps); + } + + Dedent(); + _sb.Append('}'); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) + { + scriptBlockExpressionAst.ScriptBlock.Visit(this); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitStatementBlock(StatementBlockAst statementBlockAst) + { + BeginBlock(); + WriteStatementBlock(statementBlockAst.Statements, statementBlockAst.Traps); + EndBlock(); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst) + { + switch (stringConstantExpressionAst.StringConstantType) + { + case StringConstantType.BareWord: + _sb.Append(stringConstantExpressionAst.Value); + break; + + case StringConstantType.SingleQuoted: + _sb.Append('\'').Append(stringConstantExpressionAst.Value.Replace("'", "''")).Append('\''); + break; + + case StringConstantType.DoubleQuoted: + WriteDoubleQuotedString(stringConstantExpressionAst.Value); + break; + + case StringConstantType.SingleQuotedHereString: + _sb.Append("@'\n").Append(stringConstantExpressionAst.Value).Append("\n'@"); + break; + + case StringConstantType.DoubleQuotedHereString: + _sb.Append("@\"\n").Append(stringConstantExpressionAst.Value).Append("\n\"@"); + break; + + default: + throw new ArgumentException($"Bad string contstant expression: '{stringConstantExpressionAst}' of type {stringConstantExpressionAst.StringConstantType}"); + } + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitSubExpression(SubExpressionAst subExpressionAst) + { + _sb.Append("$("); + subExpressionAst.SubExpression.Visit(this); + _sb.Append(')'); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchStatementAst) + { + throw new NotImplementedException(); + } + +#if PS7 + public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) + { + ternaryExpressionAst.Condition.Visit(this); + _sb.Append(" ? "); + ternaryExpressionAst.IfTrue.Visit(this); + _sb.Append(" : "); + ternaryExpressionAst.IfFalse.Visit(this); + return AstVisitAction.SkipChildren; + } +#endif + + public override AstVisitAction VisitThrowStatement(ThrowStatementAst throwStatementAst) + { + _sb.Append("throw"); + + if (throwStatementAst.Pipeline != null) + { + _sb.Append(' '); + throwStatementAst.Pipeline.Visit(this); + } + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitTrap(TrapStatementAst trapStatementAst) + { + _sb.Append("trap"); + + if (trapStatementAst.TrapType != null) + { + _sb.Append(' '); + trapStatementAst.TrapType.Visit(this); + } + + trapStatementAst.Body.Visit(this); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst) + { + throw new NotImplementedException(); + } + + public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) + { + _sb.Append('['); + WriteTypeName(typeConstraintAst.TypeName); + _sb.Append(']'); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + throw new NotImplementedException(); + } + + public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) + { + _sb.Append('['); + WriteTypeName(typeExpressionAst.TypeName); + _sb.Append(']'); + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) + { + _sb.Append(GetTokenString(unaryExpressionAst.TokenKind)).Append(' '); + unaryExpressionAst.Child.Visit(this); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpressionAst) + { + _sb.Append("$using:").Append(((VariableExpressionAst)usingExpressionAst.SubExpression).VariablePath.UserPath); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst) + { + throw new NotImplementedException(); + } + + public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) + { + _sb.Append('$').Append(variableExpressionAst.VariablePath.UserPath); + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitWhileStatement(WhileStatementAst whileStatementAst) + { + _sb.Append("while ("); + whileStatementAst.Condition.Visit(this); + _sb.Append(")"); + whileStatementAst.Body.Visit(this); + + return AstVisitAction.SkipChildren; + } + + private void WriteTypeName(ITypeName typeName) + { + switch (typeName) + { + case ArrayTypeName arrayTypeName: + WriteTypeName(arrayTypeName.ElementType); + if (arrayTypeName.Rank == 1) + { + _sb.Append("[]"); + } + else + { + _sb.Append('['); + for (int i = 1; i < arrayTypeName.Rank; i++) + { + _sb.Append(','); + } + _sb.Append(']'); + } + break; + + case GenericTypeName genericTypeName: + _sb.Append(genericTypeName.FullName) + .Append('['); + + WriteTypeName(genericTypeName.GenericArguments[0]); + for (int i = 1; i < genericTypeName.GenericArguments.Count; i++) + { + _sb.Append(_comma); + WriteTypeName(genericTypeName.GenericArguments[i]); + } + + _sb.Append(']'); + break; + + case TypeName simpleTypeName: + _sb.Append(simpleTypeName.FullName); + break; + + default: + throw new ArgumentException($"Unknown type name type: '{typeName.GetType().FullName}'"); + } + } + + private void WriteDoubleQuotedString(string strVal) + { + _sb.Append('"'); + + foreach (char c in strVal) + { + switch (c) + { + case '\0': + _sb.Append("`0"); + break; + + case '\a': + _sb.Append("`a"); + break; + + case '\b': + _sb.Append("`b"); + break; + + case '\f': + _sb.Append("`f"); + break; + + case '\n': + _sb.Append("`n"); + break; + + case '\r': + _sb.Append("`r"); + break; + + case '\t': + _sb.Append("`t"); + break; + + case '\v': + _sb.Append("`v"); + break; + + case '`': + _sb.Append("``"); + break; + + case '"': + _sb.Append("`\""); + break; + + case '$': + _sb.Append("`$"); + break; + + case '\u001b': + _sb.Append("`e"); + break; + + default: + if (c < 128) + { + _sb.Append(c); + break; + } + + _sb.Append("`u{").Append(((int)c).ToString("X")).Append('}'); + break; + } + } + + _sb.Append('"'); + } + + private void WriteStatementBlock(IReadOnlyList statements, IReadOnlyList traps = null) + { + if (traps != null) + { + foreach (TrapStatementAst trap in traps) + { + trap.Visit(this); + } + } + + foreach (StatementAst statement in statements) + { + statement.Visit(this); + } + } + + private void WriteHashtableEntry(Tuple entry) + { + entry.Item1.Visit(this); + _sb.Append(" = "); + entry.Item2.Visit(this); + } + + private void BeginBlock() + { + Newline(); + _sb.Append('{'); + Indent(); + } + + private void EndBlock() + { + Dedent(); + _sb.Append('}'); + } + + private void Newline() + { + _sb.Append(_newline); + + for (int i = 0; i < _indent; i++) + { + _sb.Append(_indentStr); + } + } + + private void EndStatement() + { + _sb.Append(_newline); + _sb.Append(_newline); + + for (int i = 0; i < _indent; i++) + { + _sb.Append(_indentStr); + } + } + + private void Indent() + { + _indent++; + Newline(); + } + + private void Dedent() + { + _indent--; + Newline(); + } + + private void Intersperse(IReadOnlyList asts, string separator) + { + if (asts == null || asts.Count == 0) + { + return; + } + + asts[0].Visit(this); + + for (int i = 1; i < asts.Count - 1; i++) + { + _sb.Append(separator); + asts[i].Visit(this); + } + } + + 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}'"); + } + } + } +} From cec3b2727e0e7b4ae832c0d604b1a0feda0e8de0 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 20 Feb 2020 23:50:12 -0800 Subject: [PATCH 02/17] Fix NRE --- .../PrettyPrinter.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Modules/Microsoft.PowerShell.AstTools/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/PrettyPrinter.cs index 542f8ff..ec666d9 100644 --- a/Modules/Microsoft.PowerShell.AstTools/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/PrettyPrinter.cs @@ -8,6 +8,13 @@ namespace Microsoft.PowerShell.PrettyPrinter { public class PrettyPrinter : AstVisitor2 { + public static string PrettyPrint(Ast ast) + { + var pp = new PrettyPrinter(); + ast.Visit(pp); + return pp.GetPrettyPrintResult(); + } + private readonly StringBuilder _sb; private readonly string _newline; @@ -32,7 +39,7 @@ public void Clear() _sb.Clear(); } - public string GetString() + public string GetPrettyPrintResult() { return _sb.ToString(); } @@ -530,7 +537,10 @@ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) { _sb.Append('{'); Indent(); - scriptBlockAst.ParamBlock.Visit(this); + if (scriptBlockAst.ParamBlock != null) + { + scriptBlockAst.ParamBlock.Visit(this); + } bool useExplicitEndBlock = false; @@ -857,6 +867,11 @@ private void WriteStatementBlock(IReadOnlyList statements, IReadOn foreach (StatementAst statement in statements) { statement.Visit(this); + + if (statement is PipelineBaseAst) + { + Newline(); + } } } From 00a4def2b81e4fbfff56e274d5141f8ccffdcabf Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Fri, 21 Feb 2020 12:21:00 -0800 Subject: [PATCH 03/17] Add tests, fix bugs --- .../Microsoft.PowerShell.AstTools.csproj | 0 .../{ => src}/PrettyPrinter.cs | 87 +++++++------------ .../test/PrettyPrintingTests.cs | 50 +++++++++++ .../test/test.csproj | 31 +++++++ 4 files changed, 112 insertions(+), 56 deletions(-) rename Modules/Microsoft.PowerShell.AstTools/{ => src}/Microsoft.PowerShell.AstTools.csproj (100%) rename Modules/Microsoft.PowerShell.AstTools/{ => src}/PrettyPrinter.cs (96%) create mode 100644 Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs create mode 100644 Modules/Microsoft.PowerShell.AstTools/test/test.csproj diff --git a/Modules/Microsoft.PowerShell.AstTools/Microsoft.PowerShell.AstTools.csproj b/Modules/Microsoft.PowerShell.AstTools/src/Microsoft.PowerShell.AstTools.csproj similarity index 100% rename from Modules/Microsoft.PowerShell.AstTools/Microsoft.PowerShell.AstTools.csproj rename to Modules/Microsoft.PowerShell.AstTools/src/Microsoft.PowerShell.AstTools.csproj diff --git a/Modules/Microsoft.PowerShell.AstTools/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs similarity index 96% rename from Modules/Microsoft.PowerShell.AstTools/PrettyPrinter.cs rename to Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index ec666d9..7c4ecda 100644 --- a/Modules/Microsoft.PowerShell.AstTools/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -28,7 +28,7 @@ public static string PrettyPrint(Ast ast) public PrettyPrinter() { _sb = new StringBuilder(); - _newline = Environment.NewLine; + _newline = "\n"; _indentStr = " "; _comma = ", "; _indent = 0; @@ -119,16 +119,7 @@ public override AstVisitAction VisitBlockStatement(BlockStatementAst blockStatem public override AstVisitAction VisitBreakStatement(BreakStatementAst breakStatementAst) { - _sb.Append("break"); - - if (breakStatementAst.Label != null) - { - _sb.Append(' '); - breakStatementAst.Label.Visit(this); - } - - Newline(); - + WriteControlFlowStatement("break", breakStatementAst.Label); return AstVisitAction.SkipChildren; } @@ -194,14 +185,7 @@ public override AstVisitAction VisitConstantExpression(ConstantExpressionAst con public override AstVisitAction VisitContinueStatement(ContinueStatementAst continueStatementAst) { - _sb.Append("continue"); - - if (continueStatementAst.Label != null) - { - _sb.Append(' '); - continueStatementAst.Label.Visit(this); - } - + WriteControlFlowStatement("continue", continueStatementAst.Label); return AstVisitAction.SkipChildren; } @@ -258,16 +242,7 @@ public override AstVisitAction VisitErrorStatement(ErrorStatementAst errorStatem public override AstVisitAction VisitExitStatement(ExitStatementAst exitStatementAst) { - _sb.Append("exit"); - - if (exitStatementAst.Pipeline != null) - { - _sb.Append(' '); - exitStatementAst.Pipeline.Visit(this); - } - - EndStatement(); - + WriteControlFlowStatement("exit", exitStatementAst.Pipeline); return AstVisitAction.SkipChildren; } @@ -522,14 +497,7 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem public override AstVisitAction VisitReturnStatement(ReturnStatementAst returnStatementAst) { - _sb.Append("return"); - - if (returnStatementAst.Pipeline != null) - { - _sb.Append(' '); - returnStatementAst.Pipeline.Visit(this); - } - + WriteControlFlowStatement("return", returnStatementAst.Pipeline); return AstVisitAction.SkipChildren; } @@ -627,7 +595,7 @@ public override AstVisitAction VisitStringConstantExpression(StringConstantExpre public override AstVisitAction VisitSubExpression(SubExpressionAst subExpressionAst) { _sb.Append("$("); - subExpressionAst.SubExpression.Visit(this); + WriteStatementBlock(subExpressionAst.SubExpression.Statements, subExpressionAst.SubExpression.Traps); _sb.Append(')'); return AstVisitAction.SkipChildren; } @@ -651,13 +619,7 @@ public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst terna public override AstVisitAction VisitThrowStatement(ThrowStatementAst throwStatementAst) { - _sb.Append("throw"); - - if (throwStatementAst.Pipeline != null) - { - _sb.Append(' '); - throwStatementAst.Pipeline.Visit(this); - } + WriteControlFlowStatement("throw", throwStatementAst.Pipeline); return AstVisitAction.SkipChildren; } @@ -739,6 +701,17 @@ public override AstVisitAction VisitWhileStatement(WhileStatementAst whileStatem return AstVisitAction.SkipChildren; } + private void WriteControlFlowStatement(string keyword, Ast childAst) + { + _sb.Append(keyword); + + if (childAst != null) + { + _sb.Append(' '); + childAst.Visit(this); + } + } + private void WriteTypeName(ITypeName typeName) { switch (typeName) @@ -856,21 +829,29 @@ private void WriteDoubleQuotedString(string strVal) private void WriteStatementBlock(IReadOnlyList statements, IReadOnlyList traps = null) { - if (traps != null) + bool wroteTrap = false; + if (traps != null && traps.Count > 0) { + wroteTrap = true; foreach (TrapStatementAst trap in traps) { trap.Visit(this); } } - foreach (StatementAst statement in statements) + if (statements != null && statements.Count > 0) { - statement.Visit(this); + if (wroteTrap) + { + Newline(); + } + + statements[0].Visit(this); - if (statement is PipelineBaseAst) + for (int i = 1; i < statements.Count; i++) { Newline(); + statements[i].Visit(this); } } } @@ -908,12 +889,6 @@ private void Newline() private void EndStatement() { _sb.Append(_newline); - _sb.Append(_newline); - - for (int i = 0; i < _indent; i++) - { - _sb.Append(_indentStr); - } } private void Indent() @@ -937,7 +912,7 @@ private void Intersperse(IReadOnlyList asts, string separator) asts[0].Visit(this); - for (int i = 1; i < asts.Count - 1; i++) + for (int i = 1; i < asts.Count; i++) { _sb.Append(separator); asts[i].Visit(this); diff --git a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs new file mode 100644 index 0000000..cadc5cb --- /dev/null +++ b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs @@ -0,0 +1,50 @@ +using System; +using System.Management.Automation.Language; +using Microsoft.PowerShell.PrettyPrinter; +using Xunit; + +namespace test +{ + public class PrettyPrinterTests + { + [Theory()] + [InlineData("$x")] + [InlineData("$x + $y")] + [InlineData("Get-ChildItem")] + [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""")] +#if PS7 + [InlineData(@"""I`e[31mlike`e[0mducks""")] + [InlineData("1 && 2")] + [InlineData("sudo apt update && sudo apt upgrade")] + [InlineData("Get-Item ./thing || $(throw 'Bad')")] +#endif + public void TestPrettyPrintingIdempotentForSimpleStatements(string input) + { + Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] _); + StatementAst statementAst = ((ScriptBlockAst)ast).EndBlock.Statements[0]; + Assert.Equal(input, PrettyPrinter.PrettyPrint(statementAst)); + } + } +} 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 + + + + + + + + + + + + + + + + + + + + + + From de3d54d029b999387ea229cdb119aa8ec8064173 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Fri, 21 Feb 2020 12:41:46 -0800 Subject: [PATCH 04/17] More tests --- .../src/PrettyPrinter.cs | 2 +- .../test/PrettyPrintingTests.cs | 323 ++++++++++++++++++ 2 files changed, 324 insertions(+), 1 deletion(-) diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index 7c4ecda..9364c89 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -48,7 +48,7 @@ public override AstVisitAction VisitArrayExpression(ArrayExpressionAst arrayExpr { _sb.Append("@("); - arrayExpressionAst.SubExpression.Visit(this); + WriteStatementBlock(arrayExpressionAst.SubExpression.Statements, arrayExpressionAst.SubExpression.Traps); _sb.Append(")"); diff --git a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs index cadc5cb..585286e 100644 --- a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs +++ b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs @@ -9,6 +9,9 @@ public class PrettyPrinterTests { [Theory()] [InlineData("$x")] + [InlineData("- $x")] + [InlineData("- 1")] + [InlineData("-1")] [InlineData("$x + $y")] [InlineData("Get-ChildItem")] [InlineData("Get-ChildItem -Recurse -Path ./here")] @@ -34,6 +37,12 @@ public class PrettyPrinterTests [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(@"""I`e[31mlike`e[0mducks""")] [InlineData("1 && 2")] @@ -41,6 +50,320 @@ public class PrettyPrinterTests [InlineData("Get-Item ./thing || $(throw 'Bad')")] #endif public void TestPrettyPrintingIdempotentForSimpleStatements(string input) + { + AssertPrettyPrintingIdentical(input); + } + + [Fact] + public void TestWhileLoop() + { + string script = @" +$i = 0 +while ($i -lt 10) +{ + $i++ +} +Write-Host ""`$i = $i"" +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestForeachLoop() + { + string script = @" +foreach ($n in 1,2,3) +{ + Write-Output ($n + 1) +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestForLoop() + { + string script = @" +for ($i = 0; $i -lt $args.Count; $i++) +{ + $args[$i] +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestSwitchCase() + { + string script = @" +switch ($x) +{ + 1 + { + 'One' + break + } + + 2 + { + 'Two' + break + } + + 3 + { + 'Three' + break + } +} +"; + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestDoWhileLoop() + { + string script = @" +$x = 0 +do +{ + $x++ +} while ($x -lt 10) +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestDoUntilLoop() + { + string script = @" +$x = 0 +do +{ + $x++ +} until ($x -eq 10) +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestIf() + { + string script = @" +if ($x) +{ + $x.Fun() +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestElseIf() + { + string script = @" +if ($x) +{ + $x.Fun() +} +elseif ($y) +{ + $y.NoFun() +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestElse() + { + string script = @" +if ($x) +{ + $x.Fun() +} +else +{ + 'nothing' +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestFullIfElse() + { + string script = @" +if ($x) +{ + $x.Fun() +} +elseif ($y) +{ + $y.NoFun() +} +else +{ + 'nothing' +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestTryCatch() + { + string script = @" +try +{ + Write-Error 'Bad' -ErrorAction Stop +} +catch +{ + $_ +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestTryCatchWithType() + { + string script = @" +try +{ + Write-Error 'Bad' -ErrorAction Stop +} +catch [System.Exception] +{ + $_ +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestTryFinally() + { + string script = @" +try +{ + Write-Error 'Bad' -ErrorAction Stop +} +finally +{ + Write-Host 'done' +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestTryCatchFinally() + { + string script = @" +try +{ + Write-Error 'Bad' -ErrorAction Stop +} +catch +{ + $_ +} +finally +{ + Write-Host 'done' +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestSimpleClass() + { + string script = @" +class Duck +{ +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestClassProperty() + { + string script = @" +class Duck +{ + [string]$Name +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestClassMethod() + { + string script = @" +class Duck +{ + [string]GetGreeting([string]$Name) + { + return ""Hi $Name"" + } +} +"; + + AssertPrettyPrintingIdentical(script); + } + + [Fact] + public void TestClassConstructor() + { + string script = @" +class Duck +{ + Duck($name) + { + $this.Name = $name + } +} +"; + + AssertPrettyPrintingIdentical(script); + } + + + [Fact] + public void TestClassConstructorWithBaseClass() + { + string script = @" +class Duck : object +{ + Duck() : base() + { + } +} +"; + + AssertPrettyPrintingIdentical(script); + } + public void AssertPrettyPrintingIdentical(string input) { Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] _); StatementAst statementAst = ((ScriptBlockAst)ast).EndBlock.Statements[0]; From 6353558426d69c9543508940ab2f570e57eb027c Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Fri, 21 Feb 2020 14:08:24 -0800 Subject: [PATCH 05/17] More tests and more passes --- .../src/PrettyPrinter.cs | 60 ++++++- .../test/PrettyPrintingTests.cs | 166 +++++++++++++++--- 2 files changed, 196 insertions(+), 30 deletions(-) diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index 9364c89..74e6c04 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -137,6 +137,11 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) Intersperse(commandAst.CommandElements, " "); + if (commandAst.Redirections != null) + { + Intersperse(commandAst.Redirections, " "); + } + return AstVisitAction.SkipChildren; } @@ -254,7 +259,42 @@ public override AstVisitAction VisitExpandableStringExpression(ExpandableStringE public override AstVisitAction VisitFileRedirection(FileRedirectionAst redirectionAst) { - throw new NotImplementedException(); + _sb.Append(GetStreamIndicator(redirectionAst.FromStream)) + .Append('>'); + + redirectionAst.Location.Visit(this); + + return AstVisitAction.SkipChildren; + } + + 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}'"); + } } public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst) @@ -329,27 +369,24 @@ public override AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst) _sb.Append("if ("); ifStmtAst.Clauses[0].Item1.Visit(this); _sb.Append(')'); - Newline(); ifStmtAst.Clauses[0].Item2.Visit(this); for (int i = 1; i < ifStmtAst.Clauses.Count; i++) { + Newline(); _sb.Append("elseif ("); ifStmtAst.Clauses[i].Item1.Visit(this); _sb.Append(')'); - Newline(); ifStmtAst.Clauses[i].Item2.Visit(this); } if (ifStmtAst.ElseClause != null) { - _sb.Append("else"); Newline(); + _sb.Append("else"); ifStmtAst.ElseClause.Visit(this); } - EndStatement(); - return AstVisitAction.SkipChildren; } @@ -477,6 +514,12 @@ public override AstVisitAction VisitParenExpression(ParenExpressionAst parenExpr public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) { Intersperse(pipelineAst.PipelineElements, " | "); +#if PS7 + if (pipelineAst.Background) + { + _sb.Append(" &"); + } +#endif return AstVisitAction.SkipChildren; } @@ -486,6 +529,10 @@ public override AstVisitAction VisitPipelineChain(PipelineChainAst statementChai statementChain.LhsPipelineChain.Visit(this); _sb.Append(' ').Append(GetTokenString(statementChain.Operator)).Append(' '); statementChain.RhsPipeline.Visit(this); + if (statementChain.Background) + { + _sb.Append(" &"); + } return AstVisitAction.SkipChildren; } #endif @@ -532,7 +579,6 @@ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) scriptBlockAst.ProcessBlock.Visit(this); } - Newline(); if (useExplicitEndBlock) { scriptBlockAst.EndBlock.Visit(this); diff --git a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs index 585286e..07b234a 100644 --- a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs +++ b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs @@ -14,6 +14,11 @@ public class PrettyPrinterTests [InlineData("-1")] [InlineData("$x + $y")] [InlineData("Get-ChildItem")] + [InlineData("gci >test.txt")] + [InlineData("gci 1>test.txt")] + [InlineData("gci 1>test.txt 2>errs.txt")] + [InlineData("gci 2>&1")] + [InlineData("Invoke-Expression 'runCommand' &")] [InlineData("Get-ChildItem -Recurse -Path ./here")] [InlineData("Get-ChildItem -Recurse -Path \"$PWD\\there\"")] [InlineData("exit 1")] @@ -47,11 +52,120 @@ public class PrettyPrinterTests [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) { - AssertPrettyPrintingIdentical(input); + AssertPrettyPrintedStatementIdentical(input); + } + + [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] @@ -66,20 +180,20 @@ public void TestWhileLoop() Write-Host ""`$i = $i"" "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] public void TestForeachLoop() { string script = @" -foreach ($n in 1,2,3) +foreach ($n in 1, 2, 3) { Write-Output ($n + 1) } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -92,7 +206,7 @@ public void TestForLoop() } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -120,7 +234,7 @@ public void TestSwitchCase() } } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -134,7 +248,7 @@ public void TestDoWhileLoop() } while ($x -lt 10) "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -148,7 +262,7 @@ public void TestDoUntilLoop() } until ($x -eq 10) "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -161,7 +275,7 @@ public void TestIf() } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -178,7 +292,7 @@ public void TestElseIf() } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -195,7 +309,7 @@ public void TestElse() } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -216,7 +330,7 @@ public void TestFullIfElse() } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -233,7 +347,7 @@ public void TestTryCatch() } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -250,7 +364,7 @@ public void TestTryCatchWithType() } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -267,7 +381,7 @@ public void TestTryFinally() } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -288,7 +402,7 @@ public void TestTryCatchFinally() } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -300,7 +414,7 @@ class Duck } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -313,7 +427,7 @@ class Duck } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -329,7 +443,7 @@ class Duck } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } [Fact] @@ -345,7 +459,7 @@ class Duck } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } @@ -361,13 +475,19 @@ class Duck : object } "; - AssertPrettyPrintingIdentical(script); + AssertPrettyPrintedStatementIdentical(script); } - public void AssertPrettyPrintingIdentical(string input) + + private void AssertPrettyPrintedStatementIdentical(string input) { Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] _); StatementAst statementAst = ((ScriptBlockAst)ast).EndBlock.Statements[0]; - Assert.Equal(input, PrettyPrinter.PrettyPrint(statementAst)); + Assert.Equal(NormalizeScriptInput(input), PrettyPrinter.PrettyPrint(statementAst)); + } + + private static string NormalizeScriptInput(string input) + { + return input.Trim().Replace(Environment.NewLine, "\n"); } } } From a00e4d4176e6e64393297dddafd66ffcea8f86f1 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Fri, 21 Feb 2020 16:40:58 -0800 Subject: [PATCH 06/17] Implement classes --- .../src/PrettyPrinter.cs | 226 ++++++++++++++++-- .../test/PrettyPrintingTests.cs | 113 +++++++-- 2 files changed, 298 insertions(+), 41 deletions(-) diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index 74e6c04..9278f1d 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -77,11 +77,20 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) { _sb.Append('[').Append(attributeAst.TypeName).Append('('); - Intersperse(attributeAst.PositionalArguments, _comma); + bool hadPositionalArgs = false; + if (attributeAst.PositionalArguments != null && attributeAst.PositionalArguments.Count > 0) + { + hadPositionalArgs = true; + Intersperse(attributeAst.PositionalArguments, _comma); + } if (attributeAst.NamedArguments != null && attributeAst.NamedArguments.Count > 0) { - _sb.Append(_comma); + if (hadPositionalArgs) + { + _sb.Append(_comma); + } + Intersperse(attributeAst.NamedArguments, _comma); } @@ -137,8 +146,9 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) Intersperse(commandAst.CommandElements, " "); - if (commandAst.Redirections != null) + if (commandAst.Redirections != null && commandAst.Redirections.Count > 0) { + _sb.Append(' '); Intersperse(commandAst.Redirections, " "); } @@ -259,8 +269,12 @@ public override AstVisitAction VisitExpandableStringExpression(ExpandableStringE public override AstVisitAction VisitFileRedirection(FileRedirectionAst redirectionAst) { - _sb.Append(GetStreamIndicator(redirectionAst.FromStream)) - .Append('>'); + if (redirectionAst.FromStream != RedirectionStream.Output) + { + _sb.Append(GetStreamIndicator(redirectionAst.FromStream)); + } + + _sb.Append('>'); redirectionAst.Location.Visit(this); @@ -304,7 +318,6 @@ public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEach _sb.Append(" in "); forEachStatementAst.Condition.Visit(this); _sb.Append(")"); - Newline(); forEachStatementAst.Body.Visit(this); EndStatement(); @@ -338,7 +351,32 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) { - throw new NotImplementedException(); + if (!functionMemberAst.IsConstructor) + { + if (functionMemberAst.IsStatic) + { + _sb.Append("static "); + } + + if (functionMemberAst.IsHidden) + { + _sb.Append("hidden "); + } + + if (functionMemberAst.ReturnType != null) + { + functionMemberAst.ReturnType.Visit(this); + } + } + + _sb.Append(functionMemberAst.Name).Append('('); + WriteInlineParameters(functionMemberAst.Parameters); + _sb.Append(')'); + Newline(); + + functionMemberAst.Body.Visit(this); + + return AstVisitAction.SkipChildren; } public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) @@ -387,6 +425,8 @@ public override AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst) ifStmtAst.ElseClause.Visit(this); } + EndStatement(); + return AstVisitAction.SkipChildren; } @@ -420,7 +460,11 @@ public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberE public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst redirectionAst) { - throw new NotImplementedException(); + _sb.Append(GetStreamIndicator(redirectionAst.FromStream)) + .Append(">&") + .Append(GetStreamIndicator(redirectionAst.ToStream)); + + return AstVisitAction.SkipChildren; } public override AstVisitAction VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) @@ -469,13 +513,12 @@ public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) for (int i = 1; i < paramBlockAst.Parameters.Count; i++) { _sb.Append(','); - Newline(); - Newline(); + Newline(count: 2); paramBlockAst.Parameters[i].Visit(this); } - _sb.Append(')'); Dedent(); + _sb.Append(')'); return AstVisitAction.SkipChildren; } @@ -484,7 +527,7 @@ public override AstVisitAction VisitParameter(ParameterAst parameterAst) { if (parameterAst.Attributes != null && parameterAst.Attributes.Count > 0) { - foreach (AttributeAst attribute in parameterAst.Attributes) + foreach (AttributeBaseAst attribute in parameterAst.Attributes) { attribute.Visit(this); Newline(); @@ -539,7 +582,30 @@ public override AstVisitAction VisitPipelineChain(PipelineChainAst statementChai public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { - throw new NotImplementedException(); + if (propertyMemberAst.IsStatic) + { + _sb.Append("static "); + } + + if (propertyMemberAst.IsHidden) + { + _sb.Append("hidden "); + } + + if (propertyMemberAst.PropertyType != null) + { + propertyMemberAst.PropertyType.Visit(this); + } + + _sb.Append('$').Append(propertyMemberAst.Name); + + if (propertyMemberAst.InitialValue != null) + { + _sb.Append(" = "); + propertyMemberAst.InitialValue.Visit(this); + } + + return AstVisitAction.SkipChildren; } public override AstVisitAction VisitReturnStatement(ReturnStatementAst returnStatementAst) @@ -701,7 +767,62 @@ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstra public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { - throw new NotImplementedException(); + if (typeDefinitionAst.IsClass) + { + _sb.Append("class "); + } + else if (typeDefinitionAst.IsInterface) + { + _sb.Append("interface "); + } + else if (typeDefinitionAst.IsEnum) + { + _sb.Append("enum "); + } + else + { + throw new ArgumentException($"Unknown PowerShell type definition type: '{typeDefinitionAst}'"); + } + + _sb.Append(typeDefinitionAst.Name); + + if (typeDefinitionAst.BaseTypes != null && typeDefinitionAst.BaseTypes.Count > 0) + { + _sb.Append(" : "); + + WriteTypeName(typeDefinitionAst.BaseTypes[0].TypeName); + + for (int i = 1; i < typeDefinitionAst.BaseTypes.Count; i++) + { + _sb.Append(_comma); + WriteTypeName(typeDefinitionAst.BaseTypes[i].TypeName); + } + } + + if (typeDefinitionAst.Members == null || typeDefinitionAst.Members.Count == 0) + { + Newline(); + _sb.Append('{'); + Newline(); + _sb.Append('}'); + + return AstVisitAction.SkipChildren; + } + + BeginBlock(); + + if (typeDefinitionAst.IsEnum) + { + Intersperse(typeDefinitionAst.Members, "," + _newline); + } + else if (typeDefinitionAst.IsClass) + { + Intersperse(typeDefinitionAst.Members, _newline + _newline); + } + + EndBlock(); + + return AstVisitAction.SkipChildren; } public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) @@ -715,8 +836,36 @@ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpress public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) { - _sb.Append(GetTokenString(unaryExpressionAst.TokenKind)).Append(' '); - unaryExpressionAst.Child.Visit(this); + switch (unaryExpressionAst.TokenKind) + { + case TokenKind.PlusPlus: + _sb.Append("++"); + unaryExpressionAst.Child.Visit(this); + break; + + case TokenKind.MinusMinus: + _sb.Append("--"); + unaryExpressionAst.Child.Visit(this); + break; + + case TokenKind.PostfixPlusPlus: + unaryExpressionAst.Child.Visit(this); + _sb.Append("++"); + break; + + case TokenKind.PostfixMinusMinus: + unaryExpressionAst.Child.Visit(this); + _sb.Append("--"); + break; + + default: + _sb.Append(GetTokenString(unaryExpressionAst.TokenKind)) + .Append(' '); + unaryExpressionAst.Child.Visit(this); + break; + + } + return AstVisitAction.SkipChildren; } @@ -747,6 +896,37 @@ public override AstVisitAction VisitWhileStatement(WhileStatementAst whileStatem return AstVisitAction.SkipChildren; } + private void WriteInlineParameters(IReadOnlyList parameters) + { + if (parameters == null || parameters.Count == 0) + { + return; + } + + WriteInlineParameter(parameters[0]); + + for (int i = 1; i < parameters.Count; i++) + { + WriteInlineParameter(parameters[i]); + } + } + + private void WriteInlineParameter(ParameterAst parameter) + { + foreach (AttributeBaseAst attribute in parameter.Attributes) + { + attribute.Visit(this); + } + parameter.Name.Visit(this); + + if (parameter.DefaultValue != null) + { + _sb.Append(" = "); + parameter.DefaultValue.Visit(this); + } + } + + private void WriteControlFlowStatement(string keyword, Ast childAst) { _sb.Append(keyword); @@ -889,14 +1069,14 @@ private void WriteStatementBlock(IReadOnlyList statements, IReadOn { if (wroteTrap) { - Newline(); + EndStatement(); } statements[0].Visit(this); for (int i = 1; i < statements.Count; i++) { - Newline(); + EndStatement(); statements[i].Visit(this); } } @@ -932,6 +1112,16 @@ private void Newline() } } + private void Newline(int count) + { + for (int i = 0; i < count - 1; i++) + { + _sb.Append(_newline); + } + + Newline(); + } + private void EndStatement() { _sb.Append(_newline); diff --git a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs index 07b234a..32a435a 100644 --- a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs +++ b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs @@ -12,13 +12,21 @@ public class PrettyPrinterTests [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 1>test.txt")] - [InlineData("gci 1>test.txt 2>errs.txt")] + [InlineData("gci >test.txt")] + [InlineData("gci >test.txt 2>errs.txt")] [InlineData("gci 2>&1")] - [InlineData("Invoke-Expression 'runCommand' &")] [InlineData("Get-ChildItem -Recurse -Path ./here")] [InlineData("Get-ChildItem -Recurse -Path \"$PWD\\there\"")] [InlineData("exit 1")] @@ -49,6 +57,7 @@ public class PrettyPrinterTests [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")] @@ -62,13 +71,81 @@ public void TestPrettyPrintingIdempotentForSimpleStatements(string input) } [Fact] - public void TestScriptBlock() + 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 { - $args[0] + 2 + 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); } @@ -79,8 +156,7 @@ public void TestScriptBlockInvocation() string script = @" & { $args[0] + 2 -} -"; +}"; AssertPrettyPrintedStatementIdentical(script); } @@ -91,8 +167,7 @@ public void TestScriptBlockDotSource() string script = @" . { $args[0] + 2 -} -"; +}"; AssertPrettyPrintedStatementIdentical(script); } @@ -103,8 +178,7 @@ public void TestScriptBlockEmptyParams() string script = @" { param() -} -"; +}"; AssertPrettyPrintedStatementIdentical(script); } @@ -119,8 +193,7 @@ public void TestScriptBlockParams() $Switch ) -} -"; +}"; AssertPrettyPrintedStatementIdentical(script); } @@ -139,8 +212,7 @@ public void TestScriptBlockAttributedParams() [switch] $Switch ) -} -"; +}"; AssertPrettyPrintedStatementIdentical(script); } @@ -162,8 +234,7 @@ public void TestScriptBlockParamAttributesWithArguments() [switch] $Switch ) -} -"; +}"; AssertPrettyPrintedStatementIdentical(script); } @@ -172,12 +243,10 @@ public void TestScriptBlockParamAttributesWithArguments() public void TestWhileLoop() { string script = @" -$i = 0 while ($i -lt 10) { $i++ } -Write-Host ""`$i = $i"" "; AssertPrettyPrintedStatementIdentical(script); @@ -241,7 +310,6 @@ public void TestSwitchCase() public void TestDoWhileLoop() { string script = @" -$x = 0 do { $x++ @@ -255,7 +323,6 @@ public void TestDoWhileLoop() public void TestDoUntilLoop() { string script = @" -$x = 0 do { $x++ @@ -482,10 +549,10 @@ private void AssertPrettyPrintedStatementIdentical(string input) { Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] _); StatementAst statementAst = ((ScriptBlockAst)ast).EndBlock.Statements[0]; - Assert.Equal(NormalizeScriptInput(input), PrettyPrinter.PrettyPrint(statementAst)); + Assert.Equal(NormalizeScript(input), NormalizeScript(PrettyPrinter.PrettyPrint(statementAst))); } - private static string NormalizeScriptInput(string input) + private static string NormalizeScript(string input) { return input.Trim().Replace(Environment.NewLine, "\n"); } From 4998e94c55da3f31ee67a1268acecf4bc1752c53 Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Fri, 21 Feb 2020 16:51:52 -0800 Subject: [PATCH 07/17] Fix base constructor calls --- Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index 9278f1d..b4d362f 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -107,7 +107,10 @@ public override AstVisitAction VisitAttributedExpression(AttributedExpressionAst public override AstVisitAction VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { - throw new NotImplementedException(); + _sb.Append("base("); + Intersperse(baseCtorInvokeMemberExpressionAst.Arguments, ", "); + _sb.Append(')'); + return AstVisitAction.SkipChildren; } public override AstVisitAction VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) From c934856e3e0efe534ab2f30015e6d24c983026ce Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Sun, 23 Feb 2020 17:42:17 -0800 Subject: [PATCH 08/17] Make all tests pass --- .../src/PrettyPrinter.cs | 260 +++++++++++++++--- .../Microsoft.PowerShell.AstTools/src/ex.ps1 | 1 + .../test/PrettyPrintingTests.cs | 4 +- 3 files changed, 229 insertions(+), 36 deletions(-) create mode 100644 Modules/Microsoft.PowerShell.AstTools/src/ex.ps1 diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index b4d362f..d2c0d75 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -78,13 +78,13 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) _sb.Append('[').Append(attributeAst.TypeName).Append('('); bool hadPositionalArgs = false; - if (attributeAst.PositionalArguments != null && attributeAst.PositionalArguments.Count > 0) + if (!IsEmpty(attributeAst.PositionalArguments)) { hadPositionalArgs = true; Intersperse(attributeAst.PositionalArguments, _comma); } - if (attributeAst.NamedArguments != null && attributeAst.NamedArguments.Count > 0) + if (!IsEmpty(attributeAst.NamedArguments)) { if (hadPositionalArgs) { @@ -107,9 +107,13 @@ public override AstVisitAction VisitAttributedExpression(AttributedExpressionAst public override AstVisitAction VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { - _sb.Append("base("); - Intersperse(baseCtorInvokeMemberExpressionAst.Arguments, ", "); - _sb.Append(')'); + if (!IsEmpty(baseCtorInvokeMemberExpressionAst.Arguments)) + { + _sb.Append("base("); + Intersperse(baseCtorInvokeMemberExpressionAst.Arguments, ", "); + _sb.Append(')'); + } + return AstVisitAction.SkipChildren; } @@ -137,7 +141,19 @@ public override AstVisitAction VisitBreakStatement(BreakStatementAst breakStatem public override AstVisitAction VisitCatchClause(CatchClauseAst catchClauseAst) { - throw new NotImplementedException(); + _sb.Append("catch"); + if (!IsEmpty(catchClauseAst.CatchTypes)) + { + foreach (TypeConstraintAst typeConstraint in catchClauseAst.CatchTypes) + { + _sb.Append(' '); + typeConstraint.Visit(this); + } + } + + catchClauseAst.Body.Visit(this); + + return AstVisitAction.SkipChildren; } public override AstVisitAction VisitCommand(CommandAst commandAst) @@ -149,7 +165,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) Intersperse(commandAst.CommandElements, " "); - if (commandAst.Redirections != null && commandAst.Redirections.Count > 0) + if (!IsEmpty(commandAst.Redirections)) { _sb.Append(' '); Intersperse(commandAst.Redirections, " "); @@ -375,9 +391,39 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem _sb.Append(functionMemberAst.Name).Append('('); WriteInlineParameters(functionMemberAst.Parameters); _sb.Append(')'); - Newline(); - functionMemberAst.Body.Visit(this); + IReadOnlyList statementAsts = functionMemberAst.Body.EndBlock.Statements; + + if (functionMemberAst.IsConstructor) + { + var baseCtorCall = (BaseCtorInvokeMemberExpressionAst)((CommandExpressionAst)functionMemberAst.Body.EndBlock.Statements[0]).Expression; + + if (!IsEmpty(baseCtorCall.Arguments)) + { + _sb.Append(" : "); + 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(); + _sb.Append('{'); + Newline(); + _sb.Append('}'); + return AstVisitAction.SkipChildren; + } + + BeginBlock(); + WriteStatementBlock(statementAsts, functionMemberAst.Body.EndBlock.Traps); + EndBlock(); return AstVisitAction.SkipChildren; } @@ -386,7 +432,7 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) { _sb.Append("@{"); - if (hashtableAst.KeyValuePairs == null || hashtableAst.KeyValuePairs.Count == 0) + if (IsEmpty(hashtableAst.KeyValuePairs)) { _sb.Append('}'); return AstVisitAction.SkipChildren; @@ -501,9 +547,18 @@ public override AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst) public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) { + if (!IsEmpty(paramBlockAst.Attributes)) + { + foreach (AttributeAst attributeAst in paramBlockAst.Attributes) + { + attributeAst.Visit(this); + Newline(); + } + } + _sb.Append("param("); - if (paramBlockAst.Parameters == null || paramBlockAst.Parameters.Count == 0) + if (IsEmpty(paramBlockAst.Parameters)) { _sb.Append(')'); return AstVisitAction.SkipChildren; @@ -528,7 +583,7 @@ public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) public override AstVisitAction VisitParameter(ParameterAst parameterAst) { - if (parameterAst.Attributes != null && parameterAst.Attributes.Count > 0) + if (!IsEmpty(parameterAst.Attributes)) { foreach (AttributeBaseAst attribute in parameterAst.Attributes) { @@ -621,8 +676,10 @@ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) { _sb.Append('{'); Indent(); + bool needNewline = false; if (scriptBlockAst.ParamBlock != null) { + needNewline = true; scriptBlockAst.ParamBlock.Visit(this); } @@ -630,31 +687,53 @@ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) if (scriptBlockAst.DynamicParamBlock != null) { - Newline(); + needNewline = useExplicitEndBlock = true; + if (needNewline) + { + Newline(count: 2); + } + scriptBlockAst.DynamicParamBlock.Visit(this); } if (scriptBlockAst.BeginBlock != null) { - useExplicitEndBlock = true; - Newline(); + needNewline = useExplicitEndBlock = true; + if (needNewline) + { + Newline(count: 2); + } + scriptBlockAst.BeginBlock.Visit(this); } if (scriptBlockAst.ProcessBlock != null) { - useExplicitEndBlock = true; - Newline(); + needNewline = useExplicitEndBlock = true; + if (needNewline) + { + Newline(count: 2); + } + scriptBlockAst.ProcessBlock.Visit(this); } - if (useExplicitEndBlock) + if (!IsEmpty(scriptBlockAst.EndBlock.Statements) || !IsEmpty(scriptBlockAst.EndBlock.Traps)) { - scriptBlockAst.EndBlock.Visit(this); - } - else - { - WriteStatementBlock(scriptBlockAst.EndBlock.Statements, 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); + } } Dedent(); @@ -717,7 +796,48 @@ public override AstVisitAction VisitSubExpression(SubExpressionAst subExpression public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchStatementAst) { - throw new NotImplementedException(); + if (switchStatementAst.Label != null) + { + _sb.Append(':').Append(switchStatementAst.Label).Append(' '); + } + + _sb.Append("switch ("); + switchStatementAst.Condition.Visit(this); + _sb.Append(')'); + + BeginBlock(); + + bool hasCases = false; + if (!IsEmpty(switchStatementAst.Clauses)) + { + hasCases = true; + + switchStatementAst.Clauses[0].Item1.Visit(this); + switchStatementAst.Clauses[0].Item2.Visit(this); + + for (int i = 1; i < switchStatementAst.Clauses.Count; i++) + { + Newline(count: 2); + switchStatementAst.Clauses[i].Item1.Visit(this); + switchStatementAst.Clauses[i].Item2.Visit(this); + } + } + + if (switchStatementAst.Default != null) + { + if (hasCases) + { + Newline(); + } + + _sb.Append("default"); + Newline(); + switchStatementAst.Default.Visit(this); + } + + EndBlock(); + + return AstVisitAction.SkipChildren; } #if PS7 @@ -756,7 +876,26 @@ public override AstVisitAction VisitTrap(TrapStatementAst trapStatementAst) public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst) { - throw new NotImplementedException(); + _sb.Append("try"); + tryStatementAst.Body.Visit(this); + + if (!IsEmpty(tryStatementAst.CatchClauses)) + { + foreach (CatchClauseAst catchClause in tryStatementAst.CatchClauses) + { + Newline(); + catchClause.Visit(this); + } + } + + if (tryStatementAst.Finally != null) + { + Newline(); + _sb.Append("finally"); + tryStatementAst.Finally.Visit(this); + } + + return AstVisitAction.SkipChildren; } public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) @@ -789,7 +928,7 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit _sb.Append(typeDefinitionAst.Name); - if (typeDefinitionAst.BaseTypes != null && typeDefinitionAst.BaseTypes.Count > 0) + if (!IsEmpty(typeDefinitionAst.BaseTypes)) { _sb.Append(" : "); @@ -802,7 +941,7 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit } } - if (typeDefinitionAst.Members == null || typeDefinitionAst.Members.Count == 0) + if (IsEmpty(typeDefinitionAst.Members)) { Newline(); _sb.Append('{'); @@ -880,7 +1019,54 @@ public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpr public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst) { - throw new NotImplementedException(); + _sb.Append("using "); + + switch (usingStatementAst.UsingStatementKind) + { + case UsingStatementKind.Assembly: + _sb.Append("assembly "); + break; + + case UsingStatementKind.Command: + _sb.Append("command "); + break; + + case UsingStatementKind.Module: + _sb.Append("module "); + break; + + case UsingStatementKind.Namespace: + _sb.Append("namespace "); + break; + + case UsingStatementKind.Type: + _sb.Append("type "); + break; + + default: + throw new ArgumentException($"Unknown using statement kind: '{usingStatementAst.UsingStatementKind}'"); + } + + if (usingStatementAst.ModuleSpecification != null) + { + usingStatementAst.ModuleSpecification.Visit(this); + return AstVisitAction.SkipChildren; + } + + if (usingStatementAst.Name != null) + { + usingStatementAst.Name.Visit(this); + } + + if (usingStatementAst.Alias != null) + { + _sb.Append(" = "); + usingStatementAst.Alias.Visit(this); + } + + EndStatement(); + + return AstVisitAction.SkipChildren; } public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) @@ -901,7 +1087,7 @@ public override AstVisitAction VisitWhileStatement(WhileStatementAst whileStatem private void WriteInlineParameters(IReadOnlyList parameters) { - if (parameters == null || parameters.Count == 0) + if (IsEmpty(parameters)) { return; } @@ -1059,7 +1245,7 @@ private void WriteDoubleQuotedString(string strVal) private void WriteStatementBlock(IReadOnlyList statements, IReadOnlyList traps = null) { bool wroteTrap = false; - if (traps != null && traps.Count > 0) + if (!IsEmpty(traps)) { wroteTrap = true; foreach (TrapStatementAst trap in traps) @@ -1068,18 +1254,18 @@ private void WriteStatementBlock(IReadOnlyList statements, IReadOn } } - if (statements != null && statements.Count > 0) + if (!IsEmpty(statements)) { if (wroteTrap) { - EndStatement(); + Newline(); } statements[0].Visit(this); for (int i = 1; i < statements.Count; i++) { - EndStatement(); + Newline(); statements[i].Visit(this); } } @@ -1144,7 +1330,7 @@ private void Dedent() private void Intersperse(IReadOnlyList asts, string separator) { - if (asts == null || asts.Count == 0) + if (IsEmpty(asts)) { return; } @@ -1556,5 +1742,11 @@ private string GetTokenString(TokenKind tokenKind) throw new ArgumentException($"Unable to stringify token kind '{tokenKind}'"); } } + + private static bool IsEmpty(IReadOnlyCollection collection) + { + return collection == null + || collection.Count == 0; + } } } diff --git a/Modules/Microsoft.PowerShell.AstTools/src/ex.ps1 b/Modules/Microsoft.PowerShell.AstTools/src/ex.ps1 new file mode 100644 index 0000000..c498611 --- /dev/null +++ b/Modules/Microsoft.PowerShell.AstTools/src/ex.ps1 @@ -0,0 +1 @@ +using module PSScriptAnalyzer = modmod diff --git a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs index 32a435a..902cd39 100644 --- a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs +++ b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs @@ -534,9 +534,9 @@ class Duck public void TestClassConstructorWithBaseClass() { string script = @" -class Duck : object +class MyHashtable : hashtable { - Duck() : base() + MyHashtable([int]$count) : base($count) { } } From ffee12e08e8833b6bb2b962f28cf559e7e119b38 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Sun, 23 Feb 2020 18:36:23 -0800 Subject: [PATCH 09/17] Add more tests --- .../src/PrettyPrinter.cs | 17 +++++++- .../test/PrettyPrintingTests.cs | 39 ++++++++++++++++++- .../Microsoft.PowerShell.AstTools/test/ex.ps1 | 1 + 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 Modules/Microsoft.PowerShell.AstTools/test/ex.ps1 diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index d2c0d75..3809b25 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -1049,7 +1049,22 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem if (usingStatementAst.ModuleSpecification != null) { - usingStatementAst.ModuleSpecification.Visit(this); + _sb.Append("@{ "); + usingStatementAst.ModuleSpecification.KeyValuePairs[0].Item1.Visit(this); + _sb.Append(" = "); + usingStatementAst.ModuleSpecification.KeyValuePairs[0].Item2.Visit(this); + + for (int i = 1; i < usingStatementAst.ModuleSpecification.KeyValuePairs.Count; i++) + { + _sb.Append("; "); + usingStatementAst.ModuleSpecification.KeyValuePairs[i].Item1.Visit(this); + _sb.Append(" = "); + usingStatementAst.ModuleSpecification.KeyValuePairs[i].Item2.Visit(this); + } + + _sb.Append(" }"); + EndStatement(); + return AstVisitAction.SkipChildren; } diff --git a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs index 902cd39..b51000c 100644 --- a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs +++ b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs @@ -24,7 +24,6 @@ public class PrettyPrinterTests [InlineData("1, 2, 3 -join ' '")] [InlineData("Get-ChildItem")] [InlineData("gci >test.txt")] - [InlineData("gci >test.txt")] [InlineData("gci >test.txt 2>errs.txt")] [InlineData("gci 2>&1")] [InlineData("Get-ChildItem -Recurse -Path ./here")] @@ -545,6 +544,37 @@ class MyHashtable : hashtable AssertPrettyPrintedStatementIdentical(script); } + [Fact] + public void TestUsingNamespace() + { + string script = "using namespace System.Collections.Generic\n"; + AssertPrettyPrintedUsingStatementIdentical(script); + } + +#if PS7 + // This test fails in WinPS due to non-determinism in the parser + [Fact] + public void TestUsingAssembly() + { + string script = "using assembly System.Windows.Forms\n"; + AssertPrettyPrintedUsingStatementIdentical(script); + } +#endif + + [Fact] + public void TestUsingModule() + { + string script = "using module PSScriptAnalyzer\n"; + AssertPrettyPrintedUsingStatementIdentical(script); + } + + [Fact] + public void TestUsingModuleWithHashtable() + { + string script = "using module @{ ModuleName = 'PSScriptAnalyzer'; ModuleVersion = '1.18.3' }\n"; + AssertPrettyPrintedUsingStatementIdentical(script); + } + private void AssertPrettyPrintedStatementIdentical(string input) { Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] _); @@ -552,6 +582,13 @@ private void AssertPrettyPrintedStatementIdentical(string input) Assert.Equal(NormalizeScript(input), NormalizeScript(PrettyPrinter.PrettyPrint(statementAst))); } + private void AssertPrettyPrintedUsingStatementIdentical(string input) + { + Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] _); + UsingStatementAst usingAst = ((ScriptBlockAst)ast).UsingStatements[0]; + Assert.Equal(NormalizeScript(input), NormalizeScript(PrettyPrinter.PrettyPrint(usingAst))); + } + private static string NormalizeScript(string input) { return input.Trim().Replace(Environment.NewLine, "\n"); diff --git a/Modules/Microsoft.PowerShell.AstTools/test/ex.ps1 b/Modules/Microsoft.PowerShell.AstTools/test/ex.ps1 new file mode 100644 index 0000000..616cee9 --- /dev/null +++ b/Modules/Microsoft.PowerShell.AstTools/test/ex.ps1 @@ -0,0 +1 @@ +using assembly System.Windows.Forms \ No newline at end of file From 28ed636350ccd996951b1884bdf50fff9da59efe Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Sun, 23 Feb 2020 19:14:05 -0800 Subject: [PATCH 10/17] Add a test with a real world script --- .../src/PrettyPrinter.cs | 48 +++++-- .../test/PrettyPrintingTests.cs | 119 ++++++++++++++++++ 2 files changed, 157 insertions(+), 10 deletions(-) diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index 3809b25..adb4f21 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -474,8 +474,6 @@ public override AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst) ifStmtAst.ElseClause.Visit(this); } - EndStatement(); - return AstVisitAction.SkipChildren; } @@ -533,7 +531,7 @@ public override AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst) { if (!namedBlockAst.Unnamed) { - _sb.Append(GetTokenString(namedBlockAst.BlockKind)).Append(' '); + _sb.Append(GetTokenString(namedBlockAst.BlockKind)); } BeginBlock(); @@ -674,8 +672,12 @@ public override AstVisitAction VisitReturnStatement(ReturnStatementAst returnSta public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) { - _sb.Append('{'); - Indent(); + if (scriptBlockAst.Parent != null) + { + _sb.Append('{'); + Indent(); + } + bool needNewline = false; if (scriptBlockAst.ParamBlock != null) { @@ -736,8 +738,12 @@ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) } } - Dedent(); - _sb.Append('}'); + if (scriptBlockAst.Parent != null) + { + Dedent(); + _sb.Append('}'); + } + return AstVisitAction.SkipChildren; } @@ -827,11 +833,10 @@ public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchSta { if (hasCases) { - Newline(); + Newline(count: 2); } _sb.Append("default"); - Newline(); switchStatementAst.Default.Visit(this); } @@ -1086,7 +1091,7 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { - _sb.Append('$').Append(variableExpressionAst.VariablePath.UserPath); + _sb.Append(variableExpressionAst.Splatted ? '@' : '$').Append(variableExpressionAst.VariablePath.UserPath); return AstVisitAction.SkipChildren; } @@ -1277,11 +1282,17 @@ private void WriteStatementBlock(IReadOnlyList statements, IReadOn } statements[0].Visit(this); + StatementAst previousStatement = statements[0]; for (int i = 1; i < statements.Count; i++) { + if (IsBlockStatement(previousStatement)) + { + _sb.Append(_newline); + } Newline(); statements[i].Visit(this); + previousStatement = statements[i]; } } } @@ -1359,6 +1370,23 @@ private void Intersperse(IReadOnlyList asts, string separator) } } + 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) diff --git a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs index b51000c..07d7f14 100644 --- a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs +++ b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs @@ -575,6 +575,119 @@ public void TestUsingModuleWithHashtable() 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) { Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] _); @@ -589,6 +702,12 @@ private void AssertPrettyPrintedUsingStatementIdentical(string input) Assert.Equal(NormalizeScript(input), NormalizeScript(PrettyPrinter.PrettyPrint(usingAst))); } + private void AssertPrettyPrintedScriptIdentical(string input) + { + Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] _); + Assert.Equal(NormalizeScript(input), NormalizeScript(PrettyPrinter.PrettyPrint(ast))); + } + private static string NormalizeScript(string input) { return input.Trim().Replace(Environment.NewLine, "\n"); From 379d5a4fc83f8678570b227c4931c74715eb39ec Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Sun, 23 Feb 2020 19:47:52 -0800 Subject: [PATCH 11/17] Fix class member printing --- .../src/PrettyPrinter.cs | 63 +++++++++++++++---- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index adb4f21..20636a3 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -720,7 +720,8 @@ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) scriptBlockAst.ProcessBlock.Visit(this); } - if (!IsEmpty(scriptBlockAst.EndBlock.Statements) || !IsEmpty(scriptBlockAst.EndBlock.Traps)) + if (scriptBlockAst.EndBlock != null + && (!IsEmpty(scriptBlockAst.EndBlock.Statements) || !IsEmpty(scriptBlockAst.EndBlock.Traps))) { if (useExplicitEndBlock) { @@ -958,13 +959,23 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit BeginBlock(); - if (typeDefinitionAst.IsEnum) + if (typeDefinitionAst.Members != null) { - Intersperse(typeDefinitionAst.Members, "," + _newline); - } - else if (typeDefinitionAst.IsClass) - { - Intersperse(typeDefinitionAst.Members, _newline + _newline); + if (typeDefinitionAst.IsEnum) + { + Intersperse(typeDefinitionAst.Members, () => + { + _sb.Append(','); + Newline(); + }); + } + else if (typeDefinitionAst.IsClass) + { + Intersperse(typeDefinitionAst.Members, () => + { + Newline(count: 2); + }); + } } EndBlock(); @@ -1112,11 +1123,9 @@ private void WriteInlineParameters(IReadOnlyList parameters) return; } - WriteInlineParameter(parameters[0]); - - for (int i = 1; i < parameters.Count; i++) + foreach (ParameterAst parameterAst in parameters) { - WriteInlineParameter(parameters[i]); + WriteInlineParameter(parameterAst); } } @@ -1370,6 +1379,38 @@ private void Intersperse(IReadOnlyList asts, string separator) } } + 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 bool IsBlockStatement(StatementAst statementAst) { switch (statementAst) From 88bfc322945d77bf6fdcb612bd10bd2c02b5d409 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Sun, 23 Feb 2020 19:57:58 -0800 Subject: [PATCH 12/17] Refactor code --- .../src/PrettyPrinter.cs | 71 +++++++------------ 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index 20636a3..fa24c0a 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -439,12 +439,12 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) } Indent(); - WriteHashtableEntry(hashtableAst.KeyValuePairs[0]); - for (int i = 1; i < hashtableAst.KeyValuePairs.Count; i++) - { - Newline(); - WriteHashtableEntry(hashtableAst.KeyValuePairs[i]); - } + + Intersperse( + hashtableAst.KeyValuePairs, + WriteHashtableEntry, + Newline); + Dedent(); _sb.Append('}'); @@ -564,14 +564,9 @@ public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) Indent(); - paramBlockAst.Parameters[0].Visit(this); - - for (int i = 1; i < paramBlockAst.Parameters.Count; i++) - { - _sb.Append(','); - Newline(count: 2); - paramBlockAst.Parameters[i].Visit(this); - } + Intersperse( + paramBlockAst.Parameters, + () => { _sb.Append(','); Newline(count: 2); }); Dedent(); _sb.Append(')'); @@ -819,15 +814,10 @@ public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchSta { hasCases = true; - switchStatementAst.Clauses[0].Item1.Visit(this); - switchStatementAst.Clauses[0].Item2.Visit(this); - - for (int i = 1; i < switchStatementAst.Clauses.Count; i++) - { - Newline(count: 2); - switchStatementAst.Clauses[i].Item1.Visit(this); - switchStatementAst.Clauses[i].Item2.Visit(this); - } + Intersperse( + switchStatementAst.Clauses, + (caseClause) => { caseClause.Item1.Visit(this); caseClause.Item2.Visit(this); }, + () => Newline(count: 2)); } if (switchStatementAst.Default != null) @@ -938,13 +928,10 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit { _sb.Append(" : "); - WriteTypeName(typeDefinitionAst.BaseTypes[0].TypeName); - - for (int i = 1; i < typeDefinitionAst.BaseTypes.Count; i++) - { - _sb.Append(_comma); - WriteTypeName(typeDefinitionAst.BaseTypes[i].TypeName); - } + Intersperse( + typeDefinitionAst.BaseTypes, + (baseType) => WriteTypeName(baseType.TypeName), + () => _sb.Append(_comma)); } if (IsEmpty(typeDefinitionAst.Members)) @@ -1066,17 +1053,11 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem if (usingStatementAst.ModuleSpecification != null) { _sb.Append("@{ "); - usingStatementAst.ModuleSpecification.KeyValuePairs[0].Item1.Visit(this); - _sb.Append(" = "); - usingStatementAst.ModuleSpecification.KeyValuePairs[0].Item2.Visit(this); - for (int i = 1; i < usingStatementAst.ModuleSpecification.KeyValuePairs.Count; i++) - { - _sb.Append("; "); - usingStatementAst.ModuleSpecification.KeyValuePairs[i].Item1.Visit(this); - _sb.Append(" = "); - usingStatementAst.ModuleSpecification.KeyValuePairs[i].Item2.Visit(this); - } + Intersperse( + usingStatementAst.ModuleSpecification.KeyValuePairs, + (kvp) => { kvp.Item1.Visit(this); _sb.Append(" = "); kvp.Item2.Visit(this); }, + () => { _sb.Append("; "); }); _sb.Append(" }"); EndStatement(); @@ -1181,12 +1162,10 @@ private void WriteTypeName(ITypeName typeName) _sb.Append(genericTypeName.FullName) .Append('['); - WriteTypeName(genericTypeName.GenericArguments[0]); - for (int i = 1; i < genericTypeName.GenericArguments.Count; i++) - { - _sb.Append(_comma); - WriteTypeName(genericTypeName.GenericArguments[i]); - } + Intersperse( + genericTypeName.GenericArguments, + (gtn) => WriteTypeName(gtn), + () => _sb.Append(_comma)); _sb.Append(']'); break; From 79209b30dfd7e8afa0870ea8818b6e6144131867 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Sun, 23 Feb 2020 22:06:02 -0800 Subject: [PATCH 13/17] Fix tests --- .../src/PrettyPrinter.cs | 62 ++++++++++++--- .../test/PrettyPrintingTests.cs | 79 +++++++++++++------ 2 files changed, 108 insertions(+), 33 deletions(-) diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index fa24c0a..eaba0b9 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -6,15 +6,42 @@ namespace Microsoft.PowerShell.PrettyPrinter { - public class PrettyPrinter : AstVisitor2 + public class PrettyPrinter { - public static string PrettyPrint(Ast ast) + private readonly PrettyPrintingVisitor _visitor; + + public PrettyPrinter() + { + _visitor = new PrettyPrintingVisitor(); + } + + public string PrettyPrintInput(string input) { - var pp = new PrettyPrinter(); - ast.Visit(pp); - return pp.GetPrettyPrintResult(); + Ast ast = Parser.ParseInput(input, out Token[] tokens, out ParseError[] errors); + + if (errors != null && errors.Length > 0) + { + throw new ParseException(errors); + } + + return _visitor.Run(ast, tokens); + } + + public string PrettyPrintFile(string filePath) + { + Ast ast = Parser.ParseFile(filePath, out Token[] tokens, out ParseError[] errors); + + if (errors != null && errors.Length > 0) + { + throw new ParseException(errors); + } + + return _visitor.Run(ast, tokens); } + } + internal class PrettyPrintingVisitor : AstVisitor2 + { private readonly StringBuilder _sb; private readonly string _newline; @@ -23,9 +50,11 @@ public static string PrettyPrint(Ast ast) private readonly string _comma; + private IReadOnlyList _tokens; + private int _indent; - public PrettyPrinter() + public PrettyPrintingVisitor() { _sb = new StringBuilder(); _newline = "\n"; @@ -34,13 +63,11 @@ public PrettyPrinter() _indent = 0; } - public void Clear() + public string Run(Ast ast, Token[] tokens) { _sb.Clear(); - } - - public string GetPrettyPrintResult() - { + _tokens = tokens; + ast.Visit(this); return _sb.ToString(); } @@ -680,6 +707,8 @@ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) scriptBlockAst.ParamBlock.Visit(this); } + Intersperse(scriptBlockAst.UsingStatements, Newline); + bool useExplicitEndBlock = false; if (scriptBlockAst.DynamicParamBlock != null) @@ -1812,4 +1841,15 @@ private static bool IsEmpty(IReadOnlyCollection collection) || 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 index 07d7f14..c8800c4 100644 --- a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs +++ b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Management.Automation.Language; using Microsoft.PowerShell.PrettyPrinter; using Xunit; @@ -7,6 +8,13 @@ namespace test { public class PrettyPrinterTests { + private readonly PrettyPrinter _pp; + + public PrettyPrinterTests() + { + _pp = new PrettyPrinter(); + } + [Theory()] [InlineData("$x")] [InlineData("- $x")] @@ -551,28 +559,24 @@ public void TestUsingNamespace() AssertPrettyPrintedUsingStatementIdentical(script); } -#if PS7 - // This test fails in WinPS due to non-determinism in the parser - [Fact] - public void TestUsingAssembly() - { - string script = "using assembly System.Windows.Forms\n"; - AssertPrettyPrintedUsingStatementIdentical(script); - } -#endif - [Fact] public void TestUsingModule() { - string script = "using module PSScriptAnalyzer\n"; - AssertPrettyPrintedUsingStatementIdentical(script); + 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 = 'PSScriptAnalyzer'; ModuleVersion = '1.18.3' }\n"; - AssertPrettyPrintedUsingStatementIdentical(script); + string script = "using module @{ ModuleName = 'PrettyPrintingTestModule'; ModuleVersion = '1.18' }\n"; + using (ModuleContext.Create("PrettyPrintingTestModule", new Version(1, 18))) + { + AssertPrettyPrintedUsingStatementIdentical(script); + } } [Fact] @@ -690,22 +694,17 @@ public void TestFullScript1() private void AssertPrettyPrintedStatementIdentical(string input) { - Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] _); - StatementAst statementAst = ((ScriptBlockAst)ast).EndBlock.Statements[0]; - Assert.Equal(NormalizeScript(input), NormalizeScript(PrettyPrinter.PrettyPrint(statementAst))); + Assert.Equal(NormalizeScript(input), NormalizeScript(_pp.PrettyPrintInput(input))); } private void AssertPrettyPrintedUsingStatementIdentical(string input) { - Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] _); - UsingStatementAst usingAst = ((ScriptBlockAst)ast).UsingStatements[0]; - Assert.Equal(NormalizeScript(input), NormalizeScript(PrettyPrinter.PrettyPrint(usingAst))); + Assert.Equal(NormalizeScript(input), NormalizeScript(_pp.PrettyPrintInput(input))); } private void AssertPrettyPrintedScriptIdentical(string input) { - Ast ast = Parser.ParseInput(input, out Token[] _, out ParseError[] _); - Assert.Equal(NormalizeScript(input), NormalizeScript(PrettyPrinter.PrettyPrint(ast))); + Assert.Equal(NormalizeScript(input), NormalizeScript(_pp.PrettyPrintInput(input))); } private static string NormalizeScript(string input) @@ -713,4 +712,40 @@ 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); + } + } } From 61aad302eb103c8a4474faf2384ba2ea9ae934fa Mon Sep 17 00:00:00 2001 From: Rob Holt Date: Mon, 24 Feb 2020 12:34:39 -0800 Subject: [PATCH 14/17] Remove accidental file --- Modules/Microsoft.PowerShell.AstTools/test/ex.ps1 | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Modules/Microsoft.PowerShell.AstTools/test/ex.ps1 diff --git a/Modules/Microsoft.PowerShell.AstTools/test/ex.ps1 b/Modules/Microsoft.PowerShell.AstTools/test/ex.ps1 deleted file mode 100644 index 616cee9..0000000 --- a/Modules/Microsoft.PowerShell.AstTools/test/ex.ps1 +++ /dev/null @@ -1 +0,0 @@ -using assembly System.Windows.Forms \ No newline at end of file From c5fcab2825845a4cfea8fb23126d73e84606d5e1 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Mon, 24 Feb 2020 21:09:26 -0800 Subject: [PATCH 15/17] Add preliminary comment support --- .../src/PrettyPrinter.cs | 175 ++++++++++++++---- 1 file changed, 142 insertions(+), 33 deletions(-) diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index eaba0b9..7a46a2c 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -50,6 +50,8 @@ internal class PrettyPrintingVisitor : AstVisitor2 private readonly string _comma; + private int _tokenIndex; + private IReadOnlyList _tokens; private int _indent; @@ -66,6 +68,7 @@ public PrettyPrintingVisitor() public string Run(Ast ast, Token[] tokens) { _sb.Clear(); + _tokenIndex = 0; _tokens = tokens; ast.Visit(this); return _sb.ToString(); @@ -73,10 +76,10 @@ public string Run(Ast ast, Token[] tokens) public override AstVisitAction VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) { - _sb.Append("@("); + WriteCommentsToAstPosition(arrayExpressionAst); + _sb.Append("@("); WriteStatementBlock(arrayExpressionAst.SubExpression.Statements, arrayExpressionAst.SubExpression.Traps); - _sb.Append(")"); return AstVisitAction.SkipChildren; @@ -102,6 +105,8 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a public override AstVisitAction VisitAttribute(AttributeAst attributeAst) { + WriteCommentsToAstPosition(attributeAst); + _sb.Append('[').Append(attributeAst.TypeName).Append('('); bool hadPositionalArgs = false; @@ -134,6 +139,8 @@ public override AstVisitAction VisitAttributedExpression(AttributedExpressionAst public override AstVisitAction VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { + WriteCommentsToAstPosition(baseCtorInvokeMemberExpressionAst); + if (!IsEmpty(baseCtorInvokeMemberExpressionAst.Arguments)) { _sb.Append("base("); @@ -162,12 +169,15 @@ public override AstVisitAction VisitBlockStatement(BlockStatementAst blockStatem public override AstVisitAction VisitBreakStatement(BreakStatementAst breakStatementAst) { + WriteCommentsToAstPosition(breakStatementAst); WriteControlFlowStatement("break", breakStatementAst.Label); return AstVisitAction.SkipChildren; } public override AstVisitAction VisitCatchClause(CatchClauseAst catchClauseAst) { + WriteCommentsToAstPosition(catchClauseAst); + _sb.Append("catch"); if (!IsEmpty(catchClauseAst.CatchTypes)) { @@ -185,6 +195,8 @@ public override AstVisitAction VisitCatchClause(CatchClauseAst catchClauseAst) public override AstVisitAction VisitCommand(CommandAst commandAst) { + WriteCommentsToAstPosition(commandAst); + if (commandAst.InvocationOperator != TokenKind.Unknown) { _sb.Append(GetTokenString(commandAst.InvocationOperator)).Append(' '); @@ -209,6 +221,8 @@ public override AstVisitAction VisitCommandExpression(CommandExpressionAst comma public override AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) { + WriteCommentsToAstPosition(commandParameterAst); + _sb.Append('-'); _sb.Append(commandParameterAst.ParameterName); @@ -228,6 +242,8 @@ public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinit public override AstVisitAction VisitConstantExpression(ConstantExpressionAst constantExpressionAst) { + WriteCommentsToAstPosition(constantExpressionAst); + if (constantExpressionAst.Value == null) { _sb.Append("$null"); @@ -246,6 +262,7 @@ public override AstVisitAction VisitConstantExpression(ConstantExpressionAst con public override AstVisitAction VisitContinueStatement(ContinueStatementAst continueStatementAst) { + WriteCommentsToAstPosition(continueStatementAst); WriteControlFlowStatement("continue", continueStatementAst.Label); return AstVisitAction.SkipChildren; } @@ -264,6 +281,7 @@ public override AstVisitAction VisitDataStatement(DataStatementAst dataStatement public override AstVisitAction VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { + WriteCommentsToAstPosition(doUntilStatementAst); _sb.Append("do"); doUntilStatementAst.Body.Visit(this); _sb.Append(" until ("); @@ -276,6 +294,7 @@ public override AstVisitAction VisitDoUntilStatement(DoUntilStatementAst doUntil public override AstVisitAction VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { + WriteCommentsToAstPosition(doWhileStatementAst); _sb.Append("do"); doWhileStatementAst.Body.Visit(this); _sb.Append(" while ("); @@ -309,12 +328,15 @@ public override AstVisitAction VisitExitStatement(ExitStatementAst exitStatement public override AstVisitAction VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) { + WriteCommentsToAstPosition(expandableStringExpressionAst); _sb.Append('"').Append(expandableStringExpressionAst.Value).Append('"'); return AstVisitAction.SkipChildren; } public override AstVisitAction VisitFileRedirection(FileRedirectionAst redirectionAst) { + WriteCommentsToAstPosition(redirectionAst); + if (redirectionAst.FromStream != RedirectionStream.Output) { _sb.Append(GetStreamIndicator(redirectionAst.FromStream)); @@ -327,38 +349,10 @@ public override AstVisitAction VisitFileRedirection(FileRedirectionAst redirecti return AstVisitAction.SkipChildren; } - 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}'"); - } - } - public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst) { + WriteCommentsToAstPosition(forEachStatementAst); + _sb.Append("foreach ("); forEachStatementAst.Variable.Visit(this); _sb.Append(" in "); @@ -372,6 +366,8 @@ public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEach public override AstVisitAction VisitForStatement(ForStatementAst forStatementAst) { + WriteCommentsToAstPosition(forStatementAst); + _sb.Append("for ("); forStatementAst.Initializer.Visit(this); _sb.Append("; "); @@ -386,6 +382,8 @@ public override AstVisitAction VisitForStatement(ForStatementAst forStatementAst public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { + WriteCommentsToAstPosition(functionDefinitionAst); + _sb.Append(functionDefinitionAst.IsFilter ? "filter " : "function "); _sb.Append(functionDefinitionAst.Name); Newline(); @@ -397,6 +395,8 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) { + WriteCommentsToAstPosition(functionMemberAst); + if (!functionMemberAst.IsConstructor) { if (functionMemberAst.IsStatic) @@ -457,6 +457,8 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) { + WriteCommentsToAstPosition(hashtableAst); + _sb.Append("@{"); if (IsEmpty(hashtableAst.KeyValuePairs)) @@ -480,6 +482,8 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) public override AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst) { + WriteCommentsToAstPosition(ifStmtAst); + _sb.Append("if ("); ifStmtAst.Clauses[0].Item1.Visit(this); _sb.Append(')'); @@ -506,6 +510,8 @@ public override AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst) public override AstVisitAction VisitIndexExpression(IndexExpressionAst indexExpressionAst) { + WriteCommentsToAstPosition(indexExpressionAst); + indexExpressionAst.Target.Visit(this); _sb.Append('['); indexExpressionAst.Index.Visit(this); @@ -526,6 +532,8 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst) { + WriteCommentsToAstPosition(memberExpressionAst); + memberExpressionAst.Expression.Visit(this); _sb.Append(memberExpressionAst.Static ? "::" : "."); memberExpressionAst.Member.Visit(this); @@ -534,6 +542,8 @@ public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberE public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst redirectionAst) { + WriteCommentsToAstPosition(redirectionAst); + _sb.Append(GetStreamIndicator(redirectionAst.FromStream)) .Append(">&") .Append(GetStreamIndicator(redirectionAst.ToStream)); @@ -543,6 +553,8 @@ public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst red public override AstVisitAction VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { + WriteCommentsToAstPosition(namedAttributeArgumentAst); + _sb.Append(namedAttributeArgumentAst.ArgumentName); if (!namedAttributeArgumentAst.ExpressionOmitted && namedAttributeArgumentAst.Argument != null) @@ -556,6 +568,8 @@ public override AstVisitAction VisitNamedAttributeArgument(NamedAttributeArgumen public override AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst) { + WriteCommentsToAstPosition(namedBlockAst); + if (!namedBlockAst.Unnamed) { _sb.Append(GetTokenString(namedBlockAst.BlockKind)); @@ -572,6 +586,8 @@ public override AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst) public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) { + WriteCommentsToAstPosition(paramBlockAst); + if (!IsEmpty(paramBlockAst.Attributes)) { foreach (AttributeAst attributeAst in paramBlockAst.Attributes) @@ -603,6 +619,8 @@ public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) public override AstVisitAction VisitParameter(ParameterAst parameterAst) { + WriteCommentsToAstPosition(parameterAst); + if (!IsEmpty(parameterAst.Attributes)) { foreach (AttributeBaseAst attribute in parameterAst.Attributes) @@ -625,6 +643,7 @@ public override AstVisitAction VisitParameter(ParameterAst parameterAst) public override AstVisitAction VisitParenExpression(ParenExpressionAst parenExpressionAst) { + WriteCommentsToAstPosition(parenExpressionAst); _sb.Append('('); parenExpressionAst.Pipeline.Visit(this); _sb.Append(')'); @@ -634,6 +653,8 @@ public override AstVisitAction VisitParenExpression(ParenExpressionAst parenExpr public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) { + WriteCommentsToAstPosition(pipelineAst); + Intersperse(pipelineAst.PipelineElements, " | "); #if PS7 if (pipelineAst.Background) @@ -647,6 +668,7 @@ public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) #if PS7 public override AstVisitAction VisitPipelineChain(PipelineChainAst statementChain) { + WriteCommentsToAstPosition(statementChain); statementChain.LhsPipelineChain.Visit(this); _sb.Append(' ').Append(GetTokenString(statementChain.Operator)).Append(' '); statementChain.RhsPipeline.Visit(this); @@ -660,6 +682,8 @@ public override AstVisitAction VisitPipelineChain(PipelineChainAst statementChai public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { + WriteCommentsToAstPosition(propertyMemberAst); + if (propertyMemberAst.IsStatic) { _sb.Append("static "); @@ -688,12 +712,15 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem 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) { _sb.Append('{'); @@ -780,6 +807,7 @@ public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionA public override AstVisitAction VisitStatementBlock(StatementBlockAst statementBlockAst) { + WriteCommentsToAstPosition(statementBlockAst); BeginBlock(); WriteStatementBlock(statementBlockAst.Statements, statementBlockAst.Traps); EndBlock(); @@ -788,6 +816,7 @@ public override AstVisitAction VisitStatementBlock(StatementBlockAst statementBl public override AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst) { + WriteCommentsToAstPosition(stringConstantExpressionAst); switch (stringConstantExpressionAst.StringConstantType) { case StringConstantType.BareWord: @@ -819,6 +848,7 @@ public override AstVisitAction VisitStringConstantExpression(StringConstantExpre public override AstVisitAction VisitSubExpression(SubExpressionAst subExpressionAst) { + WriteCommentsToAstPosition(subExpressionAst); _sb.Append("$("); WriteStatementBlock(subExpressionAst.SubExpression.Statements, subExpressionAst.SubExpression.Traps); _sb.Append(')'); @@ -827,6 +857,8 @@ public override AstVisitAction VisitSubExpression(SubExpressionAst subExpression public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchStatementAst) { + WriteCommentsToAstPosition(switchStatementAst); + if (switchStatementAst.Label != null) { _sb.Append(':').Append(switchStatementAst.Label).Append(' '); @@ -868,6 +900,8 @@ public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchSta #if PS7 public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) { + WriteCommentsToAstPosition(ternaryExpressionAst); + ternaryExpressionAst.Condition.Visit(this); _sb.Append(" ? "); ternaryExpressionAst.IfTrue.Visit(this); @@ -879,6 +913,8 @@ public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst terna public override AstVisitAction VisitThrowStatement(ThrowStatementAst throwStatementAst) { + WriteCommentsToAstPosition(throwStatementAst); + WriteControlFlowStatement("throw", throwStatementAst.Pipeline); return AstVisitAction.SkipChildren; @@ -886,6 +922,8 @@ public override AstVisitAction VisitThrowStatement(ThrowStatementAst throwStatem public override AstVisitAction VisitTrap(TrapStatementAst trapStatementAst) { + WriteCommentsToAstPosition(trapStatementAst); + _sb.Append("trap"); if (trapStatementAst.TrapType != null) @@ -901,6 +939,8 @@ public override AstVisitAction VisitTrap(TrapStatementAst trapStatementAst) public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst) { + WriteCommentsToAstPosition(tryStatementAst); + _sb.Append("try"); tryStatementAst.Body.Visit(this); @@ -925,6 +965,7 @@ public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { + WriteCommentsToAstPosition(typeConstraintAst); _sb.Append('['); WriteTypeName(typeConstraintAst.TypeName); _sb.Append(']'); @@ -934,6 +975,8 @@ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstra public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { + WriteCommentsToAstPosition(typeDefinitionAst); + if (typeDefinitionAst.IsClass) { _sb.Append("class "); @@ -1001,6 +1044,7 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) { + WriteCommentsToAstPosition(typeExpressionAst); _sb.Append('['); WriteTypeName(typeExpressionAst.TypeName); _sb.Append(']'); @@ -1010,6 +1054,8 @@ public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpress public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) { + WriteCommentsToAstPosition(unaryExpressionAst); + switch (unaryExpressionAst.TokenKind) { case TokenKind.PlusPlus: @@ -1045,12 +1091,15 @@ public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpr public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpressionAst) { + WriteCommentsToAstPosition(usingExpressionAst); _sb.Append("$using:").Append(((VariableExpressionAst)usingExpressionAst.SubExpression).VariablePath.UserPath); return AstVisitAction.SkipChildren; } public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst) { + WriteCommentsToAstPosition(usingStatementAst); + _sb.Append("using "); switch (usingStatementAst.UsingStatementKind) @@ -1085,7 +1134,14 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem Intersperse( usingStatementAst.ModuleSpecification.KeyValuePairs, - (kvp) => { kvp.Item1.Visit(this); _sb.Append(" = "); kvp.Item2.Visit(this); }, + (kvp) => + { + WriteCommentsToAstPosition(kvp.Item1); + kvp.Item1.Visit(this); + _sb.Append(" = "); + WriteCommentsToAstPosition(kvp.Item2); + kvp.Item2.Visit(this); + }, () => { _sb.Append("; "); }); _sb.Append(" }"); @@ -1112,12 +1168,14 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { + WriteCommentsToAstPosition(variableExpressionAst); _sb.Append(variableExpressionAst.Splatted ? '@' : '$').Append(variableExpressionAst.VariablePath.UserPath); return AstVisitAction.SkipChildren; } public override AstVisitAction VisitWhileStatement(WhileStatementAst whileStatementAst) { + WriteCommentsToAstPosition(whileStatementAst); _sb.Append("while ("); whileStatementAst.Condition.Visit(this); _sb.Append(")"); @@ -1145,6 +1203,7 @@ private void WriteInlineParameter(ParameterAst parameter) { attribute.Visit(this); } + parameter.Name.Visit(this); if (parameter.DefaultValue != null) @@ -1419,6 +1478,26 @@ private void Intersperse(IReadOnlyList astObjects, Action writeObject, } } + private void WriteCommentsToAstPosition(Ast ast) + { + Token currToken = _tokens[_tokenIndex]; + while (currToken.Extent.EndOffset < ast.Extent.StartOffset) + { + if (currToken.Kind == TokenKind.Comment) + { + _sb.Append(currToken.Text); + + if (currToken.Text.StartsWith("#")) + { + Newline(); + } + } + + _tokenIndex++; + currToken = _tokens[_tokenIndex]; + } + } + private bool IsBlockStatement(StatementAst statementAst) { switch (statementAst) @@ -1835,6 +1914,36 @@ private string GetTokenString(TokenKind 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 From efb3eda12220af3856b65e93a735d39e63888ffc Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 19 Mar 2020 11:01:52 -0700 Subject: [PATCH 16/17] Change pretty printer to allow arbitrary streams --- .../src/PrettyPrinter.cs | 467 +++++++++++------- .../Microsoft.PowerShell.AstTools/src/ex.ps1 | 1 - 2 files changed, 286 insertions(+), 182 deletions(-) delete mode 100644 Modules/Microsoft.PowerShell.AstTools/src/ex.ps1 diff --git a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs index 7a46a2c..830b6f2 100644 --- a/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs +++ b/Modules/Microsoft.PowerShell.AstTools/src/PrettyPrinter.cs @@ -1,21 +1,74 @@ using System; using System.Collections.Generic; -using System.Management.Automation; +using System.IO; using System.Management.Automation.Language; using System.Text; -namespace Microsoft.PowerShell.PrettyPrinter +namespace Microsoft.PowerShell.AstTools { - public class PrettyPrinter + 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; - public PrettyPrinter() + /// + /// Create a new pretty printer for use. + /// + protected PrettyPrinter() { _visitor = new PrettyPrintingVisitor(); } - public string PrettyPrintInput(string input) + 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); @@ -24,10 +77,15 @@ public string PrettyPrintInput(string input) throw new ParseException(errors); } - return _visitor.Run(ast, tokens); + DoPrettyPrintAst(ast, tokens); } - public string PrettyPrintFile(string filePath) + /// + /// 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); @@ -36,13 +94,28 @@ public string PrettyPrintFile(string filePath) throw new ParseException(errors); } - return _visitor.Run(ast, tokens); + 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 readonly StringBuilder _sb; + private TextWriter _tw; private readonly string _newline; @@ -58,29 +131,31 @@ internal class PrettyPrintingVisitor : AstVisitor2 public PrettyPrintingVisitor() { - _sb = new StringBuilder(); _newline = "\n"; _indentStr = " "; _comma = ", "; _indent = 0; } - public string Run(Ast ast, Token[] tokens) + public void Run( + TextWriter tw, + Ast ast, + IReadOnlyList tokens) { - _sb.Clear(); + _tw = tw; _tokenIndex = 0; _tokens = tokens; ast.Visit(this); - return _sb.ToString(); + _tw = null; } public override AstVisitAction VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) { WriteCommentsToAstPosition(arrayExpressionAst); - _sb.Append("@("); + _tw.Write("@("); WriteStatementBlock(arrayExpressionAst.SubExpression.Statements, arrayExpressionAst.SubExpression.Traps); - _sb.Append(")"); + _tw.Write(")"); return AstVisitAction.SkipChildren; } @@ -96,7 +171,9 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a { assignmentStatementAst.Left.Visit(this); - _sb.Append(' ').Append(GetTokenString(assignmentStatementAst.Operator)).Append(' '); + _tw.Write(' '); + _tw.Write(GetTokenString(assignmentStatementAst.Operator)); + _tw.Write(' '); assignmentStatementAst.Right.Visit(this); @@ -107,7 +184,9 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) { WriteCommentsToAstPosition(attributeAst); - _sb.Append('[').Append(attributeAst.TypeName).Append('('); + _tw.Write('['); + _tw.Write(attributeAst.TypeName); + _tw.Write('('); bool hadPositionalArgs = false; if (!IsEmpty(attributeAst.PositionalArguments)) @@ -120,13 +199,13 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) { if (hadPositionalArgs) { - _sb.Append(_comma); + _tw.Write(_comma); } Intersperse(attributeAst.NamedArguments, _comma); } - _sb.Append(")]"); + _tw.Write(")]"); return AstVisitAction.SkipChildren; } @@ -143,9 +222,9 @@ public override AstVisitAction VisitBaseCtorInvokeMemberExpression(BaseCtorInvok if (!IsEmpty(baseCtorInvokeMemberExpressionAst.Arguments)) { - _sb.Append("base("); + _tw.Write("base("); Intersperse(baseCtorInvokeMemberExpressionAst.Arguments, ", "); - _sb.Append(')'); + _tw.Write(')'); } return AstVisitAction.SkipChildren; @@ -155,7 +234,9 @@ public override AstVisitAction VisitBinaryExpression(BinaryExpressionAst binaryE { binaryExpressionAst.Left.Visit(this); - _sb.Append(' ').Append(GetTokenString(binaryExpressionAst.Operator)).Append(' '); + _tw.Write(' '); + _tw.Write(GetTokenString(binaryExpressionAst.Operator)); + _tw.Write(' '); binaryExpressionAst.Right.Visit(this); @@ -178,12 +259,12 @@ public override AstVisitAction VisitCatchClause(CatchClauseAst catchClauseAst) { WriteCommentsToAstPosition(catchClauseAst); - _sb.Append("catch"); + _tw.Write("catch"); if (!IsEmpty(catchClauseAst.CatchTypes)) { foreach (TypeConstraintAst typeConstraint in catchClauseAst.CatchTypes) { - _sb.Append(' '); + _tw.Write(' '); typeConstraint.Visit(this); } } @@ -199,14 +280,15 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) if (commandAst.InvocationOperator != TokenKind.Unknown) { - _sb.Append(GetTokenString(commandAst.InvocationOperator)).Append(' '); + _tw.Write(GetTokenString(commandAst.InvocationOperator)); + _tw.Write(' '); } Intersperse(commandAst.CommandElements, " "); if (!IsEmpty(commandAst.Redirections)) { - _sb.Append(' '); + _tw.Write(' '); Intersperse(commandAst.Redirections, " "); } @@ -223,12 +305,12 @@ public override AstVisitAction VisitCommandParameter(CommandParameterAst command { WriteCommentsToAstPosition(commandParameterAst); - _sb.Append('-'); - _sb.Append(commandParameterAst.ParameterName); + _tw.Write('-'); + _tw.Write(commandParameterAst.ParameterName); if (commandParameterAst.Argument != null) { - _sb.Append(':'); + _tw.Write(':'); commandParameterAst.Argument.Visit(this); } @@ -246,15 +328,15 @@ public override AstVisitAction VisitConstantExpression(ConstantExpressionAst con if (constantExpressionAst.Value == null) { - _sb.Append("$null"); + _tw.Write("$null"); } else if (constantExpressionAst.StaticType == typeof(bool)) { - _sb.Append((bool)constantExpressionAst.Value ? "$true" : "$false"); + _tw.Write((bool)constantExpressionAst.Value ? "$true" : "$false"); } else { - _sb.Append(constantExpressionAst.Value); + _tw.Write(constantExpressionAst.Value); } return AstVisitAction.SkipChildren; @@ -282,11 +364,11 @@ public override AstVisitAction VisitDataStatement(DataStatementAst dataStatement public override AstVisitAction VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { WriteCommentsToAstPosition(doUntilStatementAst); - _sb.Append("do"); + _tw.Write("do"); doUntilStatementAst.Body.Visit(this); - _sb.Append(" until ("); + _tw.Write(" until ("); doUntilStatementAst.Condition.Visit(this); - _sb.Append(')'); + _tw.Write(')'); EndStatement(); return AstVisitAction.SkipChildren; @@ -295,11 +377,11 @@ public override AstVisitAction VisitDoUntilStatement(DoUntilStatementAst doUntil public override AstVisitAction VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { WriteCommentsToAstPosition(doWhileStatementAst); - _sb.Append("do"); + _tw.Write("do"); doWhileStatementAst.Body.Visit(this); - _sb.Append(" while ("); + _tw.Write(" while ("); doWhileStatementAst.Condition.Visit(this); - _sb.Append(')'); + _tw.Write(')'); EndStatement(); return AstVisitAction.SkipChildren; @@ -329,7 +411,9 @@ public override AstVisitAction VisitExitStatement(ExitStatementAst exitStatement public override AstVisitAction VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) { WriteCommentsToAstPosition(expandableStringExpressionAst); - _sb.Append('"').Append(expandableStringExpressionAst.Value).Append('"'); + _tw.Write('"'); + _tw.Write(expandableStringExpressionAst.Value); + _tw.Write('"'); return AstVisitAction.SkipChildren; } @@ -339,10 +423,10 @@ public override AstVisitAction VisitFileRedirection(FileRedirectionAst redirecti if (redirectionAst.FromStream != RedirectionStream.Output) { - _sb.Append(GetStreamIndicator(redirectionAst.FromStream)); + _tw.Write(GetStreamIndicator(redirectionAst.FromStream)); } - _sb.Append('>'); + _tw.Write('>'); redirectionAst.Location.Visit(this); @@ -353,11 +437,11 @@ public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEach { WriteCommentsToAstPosition(forEachStatementAst); - _sb.Append("foreach ("); + _tw.Write("foreach ("); forEachStatementAst.Variable.Visit(this); - _sb.Append(" in "); + _tw.Write(" in "); forEachStatementAst.Condition.Visit(this); - _sb.Append(")"); + _tw.Write(")"); forEachStatementAst.Body.Visit(this); EndStatement(); @@ -368,13 +452,13 @@ public override AstVisitAction VisitForStatement(ForStatementAst forStatementAst { WriteCommentsToAstPosition(forStatementAst); - _sb.Append("for ("); + _tw.Write("for ("); forStatementAst.Initializer.Visit(this); - _sb.Append("; "); + _tw.Write("; "); forStatementAst.Condition.Visit(this); - _sb.Append("; "); + _tw.Write("; "); forStatementAst.Iterator.Visit(this); - _sb.Append(')'); + _tw.Write(')'); forStatementAst.Body.Visit(this); EndStatement(); return AstVisitAction.SkipChildren; @@ -384,8 +468,8 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun { WriteCommentsToAstPosition(functionDefinitionAst); - _sb.Append(functionDefinitionAst.IsFilter ? "filter " : "function "); - _sb.Append(functionDefinitionAst.Name); + _tw.Write(functionDefinitionAst.IsFilter ? "filter " : "function "); + _tw.Write(functionDefinitionAst.Name); Newline(); functionDefinitionAst.Body.Visit(this); EndStatement(); @@ -401,12 +485,12 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem { if (functionMemberAst.IsStatic) { - _sb.Append("static "); + _tw.Write("static "); } if (functionMemberAst.IsHidden) { - _sb.Append("hidden "); + _tw.Write("hidden "); } if (functionMemberAst.ReturnType != null) @@ -415,9 +499,10 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem } } - _sb.Append(functionMemberAst.Name).Append('('); + _tw.Write(functionMemberAst.Name); + _tw.Write('('); WriteInlineParameters(functionMemberAst.Parameters); - _sb.Append(')'); + _tw.Write(')'); IReadOnlyList statementAsts = functionMemberAst.Body.EndBlock.Statements; @@ -427,7 +512,7 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem if (!IsEmpty(baseCtorCall.Arguments)) { - _sb.Append(" : "); + _tw.Write(" : "); baseCtorCall.Visit(this); } @@ -442,9 +527,9 @@ public override AstVisitAction VisitFunctionMember(FunctionMemberAst functionMem if (IsEmpty(statementAsts) && IsEmpty(functionMemberAst.Body.EndBlock.Traps)) { Newline(); - _sb.Append('{'); + _tw.Write('{'); Newline(); - _sb.Append('}'); + _tw.Write('}'); return AstVisitAction.SkipChildren; } @@ -459,11 +544,11 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) { WriteCommentsToAstPosition(hashtableAst); - _sb.Append("@{"); + _tw.Write("@{"); if (IsEmpty(hashtableAst.KeyValuePairs)) { - _sb.Append('}'); + _tw.Write('}'); return AstVisitAction.SkipChildren; } @@ -475,7 +560,7 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) Newline); Dedent(); - _sb.Append('}'); + _tw.Write('}'); return AstVisitAction.SkipChildren; } @@ -484,24 +569,24 @@ public override AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst) { WriteCommentsToAstPosition(ifStmtAst); - _sb.Append("if ("); + _tw.Write("if ("); ifStmtAst.Clauses[0].Item1.Visit(this); - _sb.Append(')'); + _tw.Write(')'); ifStmtAst.Clauses[0].Item2.Visit(this); for (int i = 1; i < ifStmtAst.Clauses.Count; i++) { Newline(); - _sb.Append("elseif ("); + _tw.Write("elseif ("); ifStmtAst.Clauses[i].Item1.Visit(this); - _sb.Append(')'); + _tw.Write(')'); ifStmtAst.Clauses[i].Item2.Visit(this); } if (ifStmtAst.ElseClause != null) { Newline(); - _sb.Append("else"); + _tw.Write("else"); ifStmtAst.ElseClause.Visit(this); } @@ -513,20 +598,20 @@ public override AstVisitAction VisitIndexExpression(IndexExpressionAst indexExpr WriteCommentsToAstPosition(indexExpressionAst); indexExpressionAst.Target.Visit(this); - _sb.Append('['); + _tw.Write('['); indexExpressionAst.Index.Visit(this); - _sb.Append(']'); + _tw.Write(']'); return AstVisitAction.SkipChildren; } public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst) { methodCallAst.Expression.Visit(this); - _sb.Append(methodCallAst.Static ? "::" : "."); + _tw.Write(methodCallAst.Static ? "::" : "."); methodCallAst.Member.Visit(this); - _sb.Append('('); + _tw.Write('('); Intersperse(methodCallAst.Arguments, ", "); - _sb.Append(')'); + _tw.Write(')'); return AstVisitAction.SkipChildren; } @@ -535,7 +620,7 @@ public override AstVisitAction VisitMemberExpression(MemberExpressionAst memberE WriteCommentsToAstPosition(memberExpressionAst); memberExpressionAst.Expression.Visit(this); - _sb.Append(memberExpressionAst.Static ? "::" : "."); + _tw.Write(memberExpressionAst.Static ? "::" : "."); memberExpressionAst.Member.Visit(this); return AstVisitAction.SkipChildren; } @@ -544,9 +629,9 @@ public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst red { WriteCommentsToAstPosition(redirectionAst); - _sb.Append(GetStreamIndicator(redirectionAst.FromStream)) - .Append(">&") - .Append(GetStreamIndicator(redirectionAst.ToStream)); + _tw.Write(GetStreamIndicator(redirectionAst.FromStream)); + _tw.Write(">&"); + _tw.Write(GetStreamIndicator(redirectionAst.ToStream)); return AstVisitAction.SkipChildren; } @@ -555,11 +640,11 @@ public override AstVisitAction VisitNamedAttributeArgument(NamedAttributeArgumen { WriteCommentsToAstPosition(namedAttributeArgumentAst); - _sb.Append(namedAttributeArgumentAst.ArgumentName); + _tw.Write(namedAttributeArgumentAst.ArgumentName); if (!namedAttributeArgumentAst.ExpressionOmitted && namedAttributeArgumentAst.Argument != null) { - _sb.Append(" = "); + _tw.Write(" = "); namedAttributeArgumentAst.Argument.Visit(this); } @@ -572,7 +657,7 @@ public override AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst) if (!namedBlockAst.Unnamed) { - _sb.Append(GetTokenString(namedBlockAst.BlockKind)); + _tw.Write(GetTokenString(namedBlockAst.BlockKind)); } BeginBlock(); @@ -597,11 +682,11 @@ public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) } } - _sb.Append("param("); + _tw.Write("param("); if (IsEmpty(paramBlockAst.Parameters)) { - _sb.Append(')'); + _tw.Write(')'); return AstVisitAction.SkipChildren; } @@ -609,10 +694,10 @@ public override AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) Intersperse( paramBlockAst.Parameters, - () => { _sb.Append(','); Newline(count: 2); }); + () => { _tw.Write(','); Newline(count: 2); }); Dedent(); - _sb.Append(')'); + _tw.Write(')'); return AstVisitAction.SkipChildren; } @@ -634,7 +719,7 @@ public override AstVisitAction VisitParameter(ParameterAst parameterAst) if (parameterAst.DefaultValue != null) { - _sb.Append(" = "); + _tw.Write(" = "); parameterAst.DefaultValue.Visit(this); } @@ -644,9 +729,9 @@ public override AstVisitAction VisitParameter(ParameterAst parameterAst) public override AstVisitAction VisitParenExpression(ParenExpressionAst parenExpressionAst) { WriteCommentsToAstPosition(parenExpressionAst); - _sb.Append('('); + _tw.Write('('); parenExpressionAst.Pipeline.Visit(this); - _sb.Append(')'); + _tw.Write(')'); return AstVisitAction.SkipChildren; } @@ -659,7 +744,7 @@ public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) #if PS7 if (pipelineAst.Background) { - _sb.Append(" &"); + _tw.Write(" &"); } #endif return AstVisitAction.SkipChildren; @@ -670,11 +755,13 @@ public override AstVisitAction VisitPipelineChain(PipelineChainAst statementChai { WriteCommentsToAstPosition(statementChain); statementChain.LhsPipelineChain.Visit(this); - _sb.Append(' ').Append(GetTokenString(statementChain.Operator)).Append(' '); + _tw.Write(' '); + _tw.Write(GetTokenString(statementChain.Operator)); + _tw.Write(' '); statementChain.RhsPipeline.Visit(this); if (statementChain.Background) { - _sb.Append(" &"); + _tw.Write(" &"); } return AstVisitAction.SkipChildren; } @@ -686,12 +773,12 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem if (propertyMemberAst.IsStatic) { - _sb.Append("static "); + _tw.Write("static "); } if (propertyMemberAst.IsHidden) { - _sb.Append("hidden "); + _tw.Write("hidden "); } if (propertyMemberAst.PropertyType != null) @@ -699,11 +786,12 @@ public override AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMem propertyMemberAst.PropertyType.Visit(this); } - _sb.Append('$').Append(propertyMemberAst.Name); + _tw.Write('$'); + _tw.Write(propertyMemberAst.Name); if (propertyMemberAst.InitialValue != null) { - _sb.Append(" = "); + _tw.Write(" = "); propertyMemberAst.InitialValue.Visit(this); } @@ -723,7 +811,7 @@ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) if (scriptBlockAst.Parent != null) { - _sb.Append('{'); + _tw.Write('{'); Indent(); } @@ -793,7 +881,7 @@ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) if (scriptBlockAst.Parent != null) { Dedent(); - _sb.Append('}'); + _tw.Write('}'); } return AstVisitAction.SkipChildren; @@ -820,11 +908,13 @@ public override AstVisitAction VisitStringConstantExpression(StringConstantExpre switch (stringConstantExpressionAst.StringConstantType) { case StringConstantType.BareWord: - _sb.Append(stringConstantExpressionAst.Value); + _tw.Write(stringConstantExpressionAst.Value); break; case StringConstantType.SingleQuoted: - _sb.Append('\'').Append(stringConstantExpressionAst.Value.Replace("'", "''")).Append('\''); + _tw.Write('\''); + _tw.Write(stringConstantExpressionAst.Value.Replace("'", "''")); + _tw.Write('\''); break; case StringConstantType.DoubleQuoted: @@ -832,11 +922,15 @@ public override AstVisitAction VisitStringConstantExpression(StringConstantExpre break; case StringConstantType.SingleQuotedHereString: - _sb.Append("@'\n").Append(stringConstantExpressionAst.Value).Append("\n'@"); + _tw.Write("@'\n"); + _tw.Write(stringConstantExpressionAst.Value); + _tw.Write("\n'@"); break; case StringConstantType.DoubleQuotedHereString: - _sb.Append("@\"\n").Append(stringConstantExpressionAst.Value).Append("\n\"@"); + _tw.Write("@\"\n"); + _tw.Write(stringConstantExpressionAst.Value); + _tw.Write("\n\"@"); break; default: @@ -849,9 +943,9 @@ public override AstVisitAction VisitStringConstantExpression(StringConstantExpre public override AstVisitAction VisitSubExpression(SubExpressionAst subExpressionAst) { WriteCommentsToAstPosition(subExpressionAst); - _sb.Append("$("); + _tw.Write("$("); WriteStatementBlock(subExpressionAst.SubExpression.Statements, subExpressionAst.SubExpression.Traps); - _sb.Append(')'); + _tw.Write(')'); return AstVisitAction.SkipChildren; } @@ -861,12 +955,14 @@ public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchSta if (switchStatementAst.Label != null) { - _sb.Append(':').Append(switchStatementAst.Label).Append(' '); + _tw.Write(':'); + _tw.Write(switchStatementAst.Label); + _tw.Write(' '); } - _sb.Append("switch ("); + _tw.Write("switch ("); switchStatementAst.Condition.Visit(this); - _sb.Append(')'); + _tw.Write(')'); BeginBlock(); @@ -888,7 +984,7 @@ public override AstVisitAction VisitSwitchStatement(SwitchStatementAst switchSta Newline(count: 2); } - _sb.Append("default"); + _tw.Write("default"); switchStatementAst.Default.Visit(this); } @@ -903,9 +999,9 @@ public override AstVisitAction VisitTernaryExpression(TernaryExpressionAst terna WriteCommentsToAstPosition(ternaryExpressionAst); ternaryExpressionAst.Condition.Visit(this); - _sb.Append(" ? "); + _tw.Write(" ? "); ternaryExpressionAst.IfTrue.Visit(this); - _sb.Append(" : "); + _tw.Write(" : "); ternaryExpressionAst.IfFalse.Visit(this); return AstVisitAction.SkipChildren; } @@ -924,11 +1020,11 @@ public override AstVisitAction VisitTrap(TrapStatementAst trapStatementAst) { WriteCommentsToAstPosition(trapStatementAst); - _sb.Append("trap"); + _tw.Write("trap"); if (trapStatementAst.TrapType != null) { - _sb.Append(' '); + _tw.Write(' '); trapStatementAst.TrapType.Visit(this); } @@ -941,7 +1037,7 @@ public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst { WriteCommentsToAstPosition(tryStatementAst); - _sb.Append("try"); + _tw.Write("try"); tryStatementAst.Body.Visit(this); if (!IsEmpty(tryStatementAst.CatchClauses)) @@ -956,7 +1052,7 @@ public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst if (tryStatementAst.Finally != null) { Newline(); - _sb.Append("finally"); + _tw.Write("finally"); tryStatementAst.Finally.Visit(this); } @@ -966,9 +1062,9 @@ public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst public override AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { WriteCommentsToAstPosition(typeConstraintAst); - _sb.Append('['); + _tw.Write('['); WriteTypeName(typeConstraintAst.TypeName); - _sb.Append(']'); + _tw.Write(']'); return AstVisitAction.SkipChildren; } @@ -979,39 +1075,39 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit if (typeDefinitionAst.IsClass) { - _sb.Append("class "); + _tw.Write("class "); } else if (typeDefinitionAst.IsInterface) { - _sb.Append("interface "); + _tw.Write("interface "); } else if (typeDefinitionAst.IsEnum) { - _sb.Append("enum "); + _tw.Write("enum "); } else { throw new ArgumentException($"Unknown PowerShell type definition type: '{typeDefinitionAst}'"); } - _sb.Append(typeDefinitionAst.Name); + _tw.Write(typeDefinitionAst.Name); if (!IsEmpty(typeDefinitionAst.BaseTypes)) { - _sb.Append(" : "); + _tw.Write(" : "); Intersperse( typeDefinitionAst.BaseTypes, (baseType) => WriteTypeName(baseType.TypeName), - () => _sb.Append(_comma)); + () => _tw.Write(_comma)); } if (IsEmpty(typeDefinitionAst.Members)) { Newline(); - _sb.Append('{'); + _tw.Write('{'); Newline(); - _sb.Append('}'); + _tw.Write('}'); return AstVisitAction.SkipChildren; } @@ -1024,7 +1120,7 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit { Intersperse(typeDefinitionAst.Members, () => { - _sb.Append(','); + _tw.Write(','); Newline(); }); } @@ -1045,9 +1141,9 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit public override AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) { WriteCommentsToAstPosition(typeExpressionAst); - _sb.Append('['); + _tw.Write('['); WriteTypeName(typeExpressionAst.TypeName); - _sb.Append(']'); + _tw.Write(']'); return AstVisitAction.SkipChildren; } @@ -1059,28 +1155,28 @@ public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpr switch (unaryExpressionAst.TokenKind) { case TokenKind.PlusPlus: - _sb.Append("++"); + _tw.Write("++"); unaryExpressionAst.Child.Visit(this); break; case TokenKind.MinusMinus: - _sb.Append("--"); + _tw.Write("--"); unaryExpressionAst.Child.Visit(this); break; case TokenKind.PostfixPlusPlus: unaryExpressionAst.Child.Visit(this); - _sb.Append("++"); + _tw.Write("++"); break; case TokenKind.PostfixMinusMinus: unaryExpressionAst.Child.Visit(this); - _sb.Append("--"); + _tw.Write("--"); break; default: - _sb.Append(GetTokenString(unaryExpressionAst.TokenKind)) - .Append(' '); + _tw.Write(GetTokenString(unaryExpressionAst.TokenKind)); + _tw.Write(' '); unaryExpressionAst.Child.Visit(this); break; @@ -1092,7 +1188,8 @@ public override AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpr public override AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpressionAst) { WriteCommentsToAstPosition(usingExpressionAst); - _sb.Append("$using:").Append(((VariableExpressionAst)usingExpressionAst.SubExpression).VariablePath.UserPath); + _tw.Write("$using:"); + _tw.Write(((VariableExpressionAst)usingExpressionAst.SubExpression).VariablePath.UserPath); return AstVisitAction.SkipChildren; } @@ -1100,28 +1197,28 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem { WriteCommentsToAstPosition(usingStatementAst); - _sb.Append("using "); + _tw.Write("using "); switch (usingStatementAst.UsingStatementKind) { case UsingStatementKind.Assembly: - _sb.Append("assembly "); + _tw.Write("assembly "); break; case UsingStatementKind.Command: - _sb.Append("command "); + _tw.Write("command "); break; case UsingStatementKind.Module: - _sb.Append("module "); + _tw.Write("module "); break; case UsingStatementKind.Namespace: - _sb.Append("namespace "); + _tw.Write("namespace "); break; case UsingStatementKind.Type: - _sb.Append("type "); + _tw.Write("type "); break; default: @@ -1130,7 +1227,7 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem if (usingStatementAst.ModuleSpecification != null) { - _sb.Append("@{ "); + _tw.Write("@{ "); Intersperse( usingStatementAst.ModuleSpecification.KeyValuePairs, @@ -1138,13 +1235,13 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem { WriteCommentsToAstPosition(kvp.Item1); kvp.Item1.Visit(this); - _sb.Append(" = "); + _tw.Write(" = "); WriteCommentsToAstPosition(kvp.Item2); kvp.Item2.Visit(this); }, - () => { _sb.Append("; "); }); + () => { _tw.Write("; "); }); - _sb.Append(" }"); + _tw.Write(" }"); EndStatement(); return AstVisitAction.SkipChildren; @@ -1157,7 +1254,7 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem if (usingStatementAst.Alias != null) { - _sb.Append(" = "); + _tw.Write(" = "); usingStatementAst.Alias.Visit(this); } @@ -1169,16 +1266,17 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { WriteCommentsToAstPosition(variableExpressionAst); - _sb.Append(variableExpressionAst.Splatted ? '@' : '$').Append(variableExpressionAst.VariablePath.UserPath); + _tw.Write(variableExpressionAst.Splatted ? '@' : '$'); + _tw.Write(variableExpressionAst.VariablePath.UserPath); return AstVisitAction.SkipChildren; } public override AstVisitAction VisitWhileStatement(WhileStatementAst whileStatementAst) { WriteCommentsToAstPosition(whileStatementAst); - _sb.Append("while ("); + _tw.Write("while ("); whileStatementAst.Condition.Visit(this); - _sb.Append(")"); + _tw.Write(")"); whileStatementAst.Body.Visit(this); return AstVisitAction.SkipChildren; @@ -1208,7 +1306,7 @@ private void WriteInlineParameter(ParameterAst parameter) if (parameter.DefaultValue != null) { - _sb.Append(" = "); + _tw.Write(" = "); parameter.DefaultValue.Visit(this); } } @@ -1216,11 +1314,11 @@ private void WriteInlineParameter(ParameterAst parameter) private void WriteControlFlowStatement(string keyword, Ast childAst) { - _sb.Append(keyword); + _tw.Write(keyword); if (childAst != null) { - _sb.Append(' '); + _tw.Write(' '); childAst.Visit(this); } } @@ -1233,33 +1331,33 @@ private void WriteTypeName(ITypeName typeName) WriteTypeName(arrayTypeName.ElementType); if (arrayTypeName.Rank == 1) { - _sb.Append("[]"); + _tw.Write("[]"); } else { - _sb.Append('['); + _tw.Write('['); for (int i = 1; i < arrayTypeName.Rank; i++) { - _sb.Append(','); + _tw.Write(','); } - _sb.Append(']'); + _tw.Write(']'); } break; case GenericTypeName genericTypeName: - _sb.Append(genericTypeName.FullName) - .Append('['); + _tw.Write(genericTypeName.FullName); + _tw.Write('['); Intersperse( genericTypeName.GenericArguments, (gtn) => WriteTypeName(gtn), - () => _sb.Append(_comma)); + () => _tw.Write(_comma)); - _sb.Append(']'); + _tw.Write(']'); break; case TypeName simpleTypeName: - _sb.Append(simpleTypeName.FullName); + _tw.Write(simpleTypeName.FullName); break; default: @@ -1269,73 +1367,75 @@ private void WriteTypeName(ITypeName typeName) private void WriteDoubleQuotedString(string strVal) { - _sb.Append('"'); + _tw.Write('"'); foreach (char c in strVal) { switch (c) { case '\0': - _sb.Append("`0"); + _tw.Write("`0"); break; case '\a': - _sb.Append("`a"); + _tw.Write("`a"); break; case '\b': - _sb.Append("`b"); + _tw.Write("`b"); break; case '\f': - _sb.Append("`f"); + _tw.Write("`f"); break; case '\n': - _sb.Append("`n"); + _tw.Write("`n"); break; case '\r': - _sb.Append("`r"); + _tw.Write("`r"); break; case '\t': - _sb.Append("`t"); + _tw.Write("`t"); break; case '\v': - _sb.Append("`v"); + _tw.Write("`v"); break; case '`': - _sb.Append("``"); + _tw.Write("``"); break; case '"': - _sb.Append("`\""); + _tw.Write("`\""); break; case '$': - _sb.Append("`$"); + _tw.Write("`$"); break; case '\u001b': - _sb.Append("`e"); + _tw.Write("`e"); break; default: if (c < 128) { - _sb.Append(c); + _tw.Write(c); break; } - _sb.Append("`u{").Append(((int)c).ToString("X")).Append('}'); + _tw.Write("`u{"); + _tw.Write(((int)c).ToString("X")); + _tw.Write('}'); break; } } - _sb.Append('"'); + _tw.Write('"'); } private void WriteStatementBlock(IReadOnlyList statements, IReadOnlyList traps = null) @@ -1364,7 +1464,7 @@ private void WriteStatementBlock(IReadOnlyList statements, IReadOn { if (IsBlockStatement(previousStatement)) { - _sb.Append(_newline); + _tw.Write(_newline); } Newline(); statements[i].Visit(this); @@ -1376,30 +1476,30 @@ private void WriteStatementBlock(IReadOnlyList statements, IReadOn private void WriteHashtableEntry(Tuple entry) { entry.Item1.Visit(this); - _sb.Append(" = "); + _tw.Write(" = "); entry.Item2.Visit(this); } private void BeginBlock() { Newline(); - _sb.Append('{'); + _tw.Write('{'); Indent(); } private void EndBlock() { Dedent(); - _sb.Append('}'); + _tw.Write('}'); } private void Newline() { - _sb.Append(_newline); + _tw.Write(_newline); for (int i = 0; i < _indent; i++) { - _sb.Append(_indentStr); + _tw.Write(_indentStr); } } @@ -1407,7 +1507,7 @@ private void Newline(int count) { for (int i = 0; i < count - 1; i++) { - _sb.Append(_newline); + _tw.Write(_newline); } Newline(); @@ -1415,7 +1515,7 @@ private void Newline(int count) private void EndStatement() { - _sb.Append(_newline); + _tw.Write(_newline); } private void Indent() @@ -1441,7 +1541,7 @@ private void Intersperse(IReadOnlyList asts, string separator) for (int i = 1; i < asts.Count; i++) { - _sb.Append(separator); + _tw.Write(separator); asts[i].Visit(this); } } @@ -1480,12 +1580,17 @@ private void Intersperse(IReadOnlyList astObjects, Action writeObject, 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) { - _sb.Append(currToken.Text); + _tw.Write(currToken.Text); if (currToken.Text.StartsWith("#")) { diff --git a/Modules/Microsoft.PowerShell.AstTools/src/ex.ps1 b/Modules/Microsoft.PowerShell.AstTools/src/ex.ps1 deleted file mode 100644 index c498611..0000000 --- a/Modules/Microsoft.PowerShell.AstTools/src/ex.ps1 +++ /dev/null @@ -1 +0,0 @@ -using module PSScriptAnalyzer = modmod From 3b0e027ac7df7fcfb462d94e12ef2969ed93b911 Mon Sep 17 00:00:00 2001 From: Robert Holt Date: Thu, 19 Mar 2020 11:04:50 -0700 Subject: [PATCH 17/17] Fix tests --- .../test/PrettyPrintingTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs index c8800c4..a4b1674 100644 --- a/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs +++ b/Modules/Microsoft.PowerShell.AstTools/test/PrettyPrintingTests.cs @@ -1,18 +1,18 @@ +using Microsoft.PowerShell.AstTools; using System; using System.IO; using System.Management.Automation.Language; -using Microsoft.PowerShell.PrettyPrinter; using Xunit; namespace test { public class PrettyPrinterTests { - private readonly PrettyPrinter _pp; + private readonly StringPrettyPrinter _pp; public PrettyPrinterTests() { - _pp = new PrettyPrinter(); + _pp = new StringPrettyPrinter(); } [Theory()]