mirror of
https://github.com/icsharpcode/ILSpy.git
synced 2026-10-12 02:15:58 +00:00
Backfills the standard MIT X11 header on hand-written files that never got one, attributing each to its first-commit author and year from git history. Code vendored from dotnet/runtime and Humanizr/Humanizer gets its origin's license lines and a provenance note instead. Generated files (Resources.Designer.cs, the version-info template), tool-managed suppression files, and BAML test-case fixtures intentionally stay header-less. Assisted-by: Claude:claude-fable-5:Claude Code
272 lines
8.5 KiB
C#
272 lines
8.5 KiB
C#
// 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();
|
|
}
|
|
}
|
|
}
|