// Copyright (c) 2017 Siegfried Pammer // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics.CodeAnalysis; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; namespace ICSharpCode.Decompiler.CSharp.Transforms { class NormalizeBlockStatements : DepthFirstAstVisitor, IAstTransform { [AllowNull] TransformContext context; bool hasNamespace; NamespaceDeclaration? singleNamespaceDeclaration; public override void VisitSyntaxTree(SyntaxTree syntaxTree) { singleNamespaceDeclaration = null; hasNamespace = false; base.VisitSyntaxTree(syntaxTree); if (context.Settings.FileScopedNamespaces && singleNamespaceDeclaration != null) { context.Step("Use file-scoped namespace", singleNamespaceDeclaration); singleNamespaceDeclaration.IsFileScoped = true; } } public override void VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration) { singleNamespaceDeclaration = null; if (!hasNamespace) { hasNamespace = true; singleNamespaceDeclaration = namespaceDeclaration; } base.VisitNamespaceDeclaration(namespaceDeclaration); } public override void VisitIfElseStatement(IfElseStatement ifElseStatement) { base.VisitIfElseStatement(ifElseStatement); DoTransform(ifElseStatement.TrueStatement, ifElseStatement); DoTransform(ifElseStatement.FalseStatement, ifElseStatement); } public override void VisitWhileStatement(WhileStatement whileStatement) { base.VisitWhileStatement(whileStatement); InsertBlock(whileStatement.EmbeddedStatement); } public override void VisitDoWhileStatement(DoWhileStatement doWhileStatement) { base.VisitDoWhileStatement(doWhileStatement); InsertBlock(doWhileStatement.EmbeddedStatement); } public override void VisitForeachStatement(ForeachStatement foreachStatement) { base.VisitForeachStatement(foreachStatement); InsertBlock(foreachStatement.EmbeddedStatement); } public override void VisitForStatement(ForStatement forStatement) { base.VisitForStatement(forStatement); InsertBlock(forStatement.EmbeddedStatement); } public override void VisitFixedStatement(FixedStatement fixedStatement) { base.VisitFixedStatement(fixedStatement); InsertBlock(fixedStatement.EmbeddedStatement); } public override void VisitLockStatement(LockStatement lockStatement) { base.VisitLockStatement(lockStatement); InsertBlock(lockStatement.EmbeddedStatement); } public override void VisitUsingStatement(UsingStatement usingStatement) { base.VisitUsingStatement(usingStatement); DoTransform(usingStatement.EmbeddedStatement, usingStatement); } void DoTransform(Statement? statement, Statement parent) { if (statement is null) return; if (context.Settings.AlwaysUseBraces) { if (!IsElseIf(statement, parent)) { InsertBlock(statement); } } else { if (statement is BlockStatement b && b.Statements.Count == 1 && IsAllowedAsEmbeddedStatement(b.Statements.First(), parent)) { context.Step("Remove redundant block statement", statement); var innerStatement = b.Statements.First().Detach(); statement.ReplaceWith(innerStatement); context.EndStep(innerStatement); } else if (!IsAllowedAsEmbeddedStatement(statement, parent)) { InsertBlock(statement); } } } bool IsElseIf(Statement statement, Statement parent) { return parent is IfElseStatement && statement.Slot?.Kind == Slots.FalseStatement; } void InsertBlock(Statement statement) { if (!(statement is BlockStatement)) { var b = new BlockStatement(); context.Step("Add block statement", statement); statement.ReplaceWith(b); if (statement is EmptyStatement && !statement.HasChildren) { b.CopyAnnotationsFrom(statement); } else { b.Add(statement); } context.EndStep(b); } } bool IsAllowedAsEmbeddedStatement(Statement statement, Statement parent) { switch (statement) { case IfElseStatement ies: return parent is IfElseStatement && ies.Slot?.Kind == Slots.FalseStatement; case VariableDeclarationStatement vds: case WhileStatement ws: case DoWhileStatement dws: case SwitchStatement ss: case ForeachStatement fes: case ForStatement fs: case LockStatement ls: case FixedStatement fxs: return false; case UsingStatement us: return parent is UsingStatement && !us.IsEnhanced; default: return !(parent?.Parent is IfElseStatement); } } void IAstTransform.Run(AstNode rootNode, TransformContext context) { this.context = context; rootNode.AcceptVisitor(this); } public override void VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration) { if (context.Settings.UseExpressionBodyForCalculatedGetterOnlyProperties) { SimplifyPropertyDeclaration(propertyDeclaration); } base.VisitPropertyDeclaration(propertyDeclaration); } public override void VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration) { if (context.Settings.UseExpressionBodyForCalculatedGetterOnlyProperties) { SimplifyIndexerDeclaration(indexerDeclaration); } base.VisitIndexerDeclaration(indexerDeclaration); } static readonly PropertyDeclaration CalculatedGetterOnlyPropertyPattern = new PropertyDeclaration() { Attributes = { new Repeat(new AnyNode()) }, Modifiers = Modifiers.Any, Name = Pattern.AnyString, PrivateImplementationType = new AnyNodeOrNull(), ReturnType = new AnyNode(), Getter = new Accessor() { Modifiers = Modifiers.Any, Body = new BlockStatement() { new ReturnStatement(new AnyNode("expression")) } } }; static readonly IndexerDeclaration CalculatedGetterOnlyIndexerPattern = new IndexerDeclaration() { Attributes = { new Repeat(new AnyNode()) }, Modifiers = Modifiers.Any, PrivateImplementationType = new AnyNodeOrNull(), Parameters = { new Repeat(new AnyNode())! }, ReturnType = new AnyNode(), Getter = new Accessor() { Modifiers = Modifiers.Any, Body = new BlockStatement() { new ReturnStatement(new AnyNode("expression")) } } }; /// /// Modifiers that are emitted on accessors, but can be moved to the property declaration. /// const Modifiers movableModifiers = Modifiers.Readonly; void SimplifyPropertyDeclaration(PropertyDeclaration propertyDeclaration) { var m = CalculatedGetterOnlyPropertyPattern.Match(propertyDeclaration); if (!m.Success) return; if (propertyDeclaration.Getter is not { } getter) return; if ((getter.Modifiers & ~movableModifiers) != 0) return; context.Step("Use expression-bodied property", propertyDeclaration); propertyDeclaration.Modifiers |= getter.Modifiers; propertyDeclaration.ExpressionBody = m.Get("expression").Single().Detach(); propertyDeclaration.CopyAnnotationsFrom(getter); getter.Remove(); } void SimplifyIndexerDeclaration(IndexerDeclaration indexerDeclaration) { var m = CalculatedGetterOnlyIndexerPattern.Match(indexerDeclaration); if (!m.Success) return; if (indexerDeclaration.Getter is not { } getter) return; if ((getter.Modifiers & ~movableModifiers) != 0) return; context.Step("Use expression-bodied indexer", indexerDeclaration); indexerDeclaration.Modifiers |= getter.Modifiers; indexerDeclaration.ExpressionBody = m.Get("expression").Single().Detach(); indexerDeclaration.CopyAnnotationsFrom(getter); getter.Remove(); } } }