// 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();
}
}
}