diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index ff86a5261..de8ab9482 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -549,9 +549,12 @@ namespace ICSharpCode.Decompiler.Tests.Helpers CompilerOptions.UseRoslyn1_3_2 => CSharp.LanguageVersion.CSharp6, CompilerOptions.UseRoslyn2_10_0 => CSharp.LanguageVersion.CSharp7_3, CompilerOptions.UseRoslyn3_11_0 => CSharp.LanguageVersion.CSharp9_0, - _ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp9_0, + _ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp10_0, + }; + return new DecompilerSettings(langVersion) { + // Never use file-scoped namespaces + FileScopedNamespaces = false }; - return new DecompilerSettings(langVersion); } else { diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index c8aa7e614..d175c6ce2 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -108,6 +108,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index aaf365db8..b6f3bb72f 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -113,13 +113,13 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void FSharpUsing_Debug() { - Run(settings: new DecompilerSettings { RemoveDeadStores = true, UseEnhancedUsing = false }); + Run(settings: new DecompilerSettings { RemoveDeadStores = true, UseEnhancedUsing = false, FileScopedNamespaces = false }); } [Test] public void FSharpUsing_Release() { - Run(settings: new DecompilerSettings { RemoveDeadStores = true, UseEnhancedUsing = false }); + Run(settings: new DecompilerSettings { RemoveDeadStores = true, UseEnhancedUsing = false, FileScopedNamespaces = false }); } [Test] @@ -137,13 +137,13 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void CS1xSwitch_Debug() { - Run(settings: new DecompilerSettings { SwitchExpressions = false }); + Run(settings: new DecompilerSettings { SwitchExpressions = false, FileScopedNamespaces = false }); } [Test] public void CS1xSwitch_Release() { - Run(settings: new DecompilerSettings { SwitchExpressions = false }); + Run(settings: new DecompilerSettings { SwitchExpressions = false, FileScopedNamespaces = false }); } [Test] @@ -240,14 +240,14 @@ namespace ICSharpCode.Decompiler.Tests public void FSharpLoops_Debug() { CopyFSharpCoreDll(); - Run(settings: new DecompilerSettings { RemoveDeadStores = true }); + Run(settings: new DecompilerSettings { RemoveDeadStores = true, FileScopedNamespaces = false }); } [Test] public void FSharpLoops_Release() { CopyFSharpCoreDll(); - Run(settings: new DecompilerSettings { RemoveDeadStores = true }); + Run(settings: new DecompilerSettings { RemoveDeadStores = true, FileScopedNamespaces = false }); } [Test] @@ -259,6 +259,11 @@ namespace ICSharpCode.Decompiler.Tests void Run([CallerMemberName] string testName = null, DecompilerSettings settings = null, AssemblerOptions assemblerOptions = AssemblerOptions.Library) { + if (settings == null) + { + // never use file-scoped namespaces, unless explicitly specified + settings = new DecompilerSettings { FileScopedNamespaces = false }; + } var ilFile = Path.Combine(TestCasePath, testName + ".il"); var csFile = Path.Combine(TestCasePath, testName + ".cs"); diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index e01d6dfdc..0254e2bab 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -180,7 +180,8 @@ namespace ICSharpCode.Decompiler.Tests RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { NullPropagation = false, // legacy csc generates a dead store in debug builds - RemoveDeadStores = (cscOptions == CompilerOptions.None) + RemoveDeadStores = (cscOptions == CompilerOptions.None), + FileScopedNamespaces = false, }); } @@ -191,6 +192,7 @@ namespace ICSharpCode.Decompiler.Tests // legacy csc generates a dead store in debug builds RemoveDeadStores = (cscOptions == CompilerOptions.None), SwitchExpressions = false, + FileScopedNamespaces = false, }); } @@ -235,7 +237,10 @@ namespace ICSharpCode.Decompiler.Tests { RunForLibrary( cscOptions: cscOptions, - decompilerSettings: new DecompilerSettings { UseEnhancedUsing = false } + decompilerSettings: new DecompilerSettings { + UseEnhancedUsing = false, + FileScopedNamespaces = false, + } ); } @@ -263,7 +268,8 @@ namespace ICSharpCode.Decompiler.Tests RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { // legacy csc generates a dead store in debug builds RemoveDeadStores = (cscOptions == CompilerOptions.None), - UseExpressionBodyForCalculatedGetterOnlyProperties = false + UseExpressionBodyForCalculatedGetterOnlyProperties = false, + FileScopedNamespaces = false, }); } @@ -407,7 +413,7 @@ namespace ICSharpCode.Decompiler.Tests { RunForLibrary( cscOptions: cscOptions, - decompilerSettings: new DecompilerSettings { UseEnhancedUsing = false } + decompilerSettings: new DecompilerSettings { UseEnhancedUsing = false, FileScopedNamespaces = false } ); } @@ -429,6 +435,12 @@ namespace ICSharpCode.Decompiler.Tests RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); } + [Test] + public void FileScopedNamespaces([ValueSource(nameof(roslynLatestOnlyOptions))] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings()); + } + [Test] public void FunctionPointers([ValueSource(nameof(roslynLatestOnlyOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/ForLoopTests.xml b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/ForLoopTests.xml index 1d41df003..ad3e3b093 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/ForLoopTests.xml +++ b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/ForLoopTests.xml @@ -3,26 +3,25 @@ diff --git a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/LambdaCapturing.xml b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/LambdaCapturing.xml index 62e069886..2f95d9ba2 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/LambdaCapturing.xml +++ b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/LambdaCapturing.xml @@ -3,21 +3,20 @@ a + b + captured); - } + int num = 4; + int captured = Environment.TickCount + num; + Test((int a, int b) => a + b + captured); + } - private static void Test(Func p) - { - p(1, 2); - } + private static void Test(Func p) + { + p(1, 2); } } ]]> diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FileScopedNamespaces.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FileScopedNamespaces.cs new file mode 100644 index 000000000..af5755e13 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/FileScopedNamespaces.cs @@ -0,0 +1,9 @@ +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty; + +internal delegate void DelegateInFileScopedNamespace(); + +internal class FileScopedNamespaces +{ + +} + diff --git a/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs b/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs index d475afd6d..e3ec45e64 100644 --- a/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs @@ -74,7 +74,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void NoArrayInitializers([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { - RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { + RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp1) { ArrayInitializers = false }); } @@ -82,7 +82,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void NoDecimalConstants([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { - RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { + RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp1) { DecimalConstants = false }); } @@ -90,7 +90,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void NoExtensionMethods([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { - RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { + RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp9_0) { ExtensionMethods = false }); } @@ -98,7 +98,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void NoForEachStatement([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { - RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { + RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp1) { ForEachStatement = false, UseEnhancedUsing = false, }); @@ -113,7 +113,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void NoPropertiesAndEvents([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { - RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { + RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp1) { AutomaticEvents = false, AutomaticProperties = false, }); @@ -122,7 +122,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void AggressiveScalarReplacementOfAggregates([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { - RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { + RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp3) { AggressiveScalarReplacementOfAggregates = true }); } diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index c7766b1e7..1e0deaff1 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1524,15 +1524,26 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor StartNode(namespaceDeclaration); WriteKeyword(Roles.NamespaceKeyword); namespaceDeclaration.NamespaceName.AcceptVisitor(this); - OpenBrace(policy.NamespaceBraceStyle); + if (namespaceDeclaration.IsFileScoped) + { + Semicolon(); + NewLine(); + } + else + { + OpenBrace(policy.NamespaceBraceStyle); + } foreach (var member in namespaceDeclaration.Members) { member.AcceptVisitor(this); MaybeNewLinesAfterUsings(member); } - CloseBrace(policy.NamespaceBraceStyle); - OptionalSemicolon(namespaceDeclaration.LastChild); - NewLine(); + if (!namespaceDeclaration.IsFileScoped) + { + CloseBrace(policy.NamespaceBraceStyle); + OptionalSemicolon(namespaceDeclaration.LastChild); + NewLine(); + } EndNode(namespaceDeclaration); } diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/NamespaceDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/NamespaceDeclaration.cs index 6da1f44ec..b41e9f882 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/NamespaceDeclaration.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/NamespaceDeclaration.cs @@ -42,6 +42,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } } + public bool IsFileScoped { get; set; } + public CSharpTokenNode NamespaceToken { get { return GetChildByRole(Roles.NamespaceKeyword); } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/NormalizeBlockStatements.cs b/ICSharpCode.Decompiler/CSharp/Transforms/NormalizeBlockStatements.cs index e4034be38..88c6ac2f2 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/NormalizeBlockStatements.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/NormalizeBlockStatements.cs @@ -11,6 +11,31 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms class NormalizeBlockStatements : DepthFirstAstVisitor, IAstTransform { 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) + { + 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) { diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index d564bee88..d76921cac 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -142,10 +142,16 @@ namespace ICSharpCode.Decompiler usePrimaryConstructorSyntax = false; covariantReturns = false; } + if (languageVersion < CSharp.LanguageVersion.CSharp10_0) + { + fileScopedNamespaces = false; + } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { + if (fileScopedNamespaces) + return CSharp.LanguageVersion.CSharp10_0; if (nativeIntegers || initAccessors || functionPointers || forEachWithGetEnumeratorExtension || recordClasses || withExpressions || usePrimaryConstructorSyntax || covariantReturns) return CSharp.LanguageVersion.CSharp9_0; @@ -323,6 +329,24 @@ namespace ICSharpCode.Decompiler } } + bool fileScopedNamespaces = true; + + /// + /// Use C# 10 file-scoped namespaces. + /// + [Category("C# 10.0 / VS 2022")] + [Description("DecompilerSettings.FileScopedNamespaces")] + public bool FileScopedNamespaces { + get { return fileScopedNamespaces; } + set { + if (fileScopedNamespaces != value) + { + fileScopedNamespaces = value; + OnPropertyChanged(); + } + } + } + bool anonymousMethods = true; /// diff --git a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs index 665b5e081..d2d9402dc 100644 --- a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs +++ b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs @@ -26,6 +26,7 @@ using System.Windows.Data; using System.Xml.Linq; using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpy.TreeNodes; namespace ICSharpCode.ILSpy.Options { @@ -157,7 +158,7 @@ namespace ICSharpCode.ILSpy.Options Settings = typeof(Decompiler.DecompilerSettings).GetProperties() .Where(p => p.GetCustomAttribute()?.Browsable != false) .Select(p => new CSharpDecompilerSetting(p) { IsEnabled = (bool)p.GetValue(settings) }) - .OrderBy(item => item.Category) + .OrderBy(item => item.Category, NaturalStringComparer.Instance) .ThenBy(item => item.Description) .ToArray(); } diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 6901b3577..1fdc2e2cf 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -954,6 +954,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Use file-scoped namespace declarations. + /// + public static string DecompilerSettings_FileScopedNamespaces { + get { + return ResourceManager.GetString("DecompilerSettings.FileScopedNamespaces", resourceCulture); + } + } + /// /// Looks up a localized string similar to Transform to for, if possible. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 98f62371d..b01f6a5ba 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -345,6 +345,9 @@ Are you sure you want to continue? F#-specific options + + Use file-scoped namespace declarations + Transform to for, if possible