mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
8.5 KiB
271 lines
8.5 KiB
// 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")) } |
|
} |
|
}; |
|
|
|
/// <summary> |
|
/// Modifiers that are emitted on accessors, but can be moved to the property declaration. |
|
/// </summary> |
|
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>("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>("expression").Single().Detach(); |
|
indexerDeclaration.CopyAnnotationsFrom(getter); |
|
getter.Remove(); |
|
} |
|
} |
|
}
|
|
|