diff --git a/CodeBlocker.Test/TemplateTests.cs b/CodeBlocker.Test/TemplateTests.cs index 999de40..dae094a 100644 --- a/CodeBlocker.Test/TemplateTests.cs +++ b/CodeBlocker.Test/TemplateTests.cs @@ -1094,6 +1094,37 @@ public void ASourceFileWritesItsPreambleThenItsTypes() Render(codeBlocker => codeBlocker.AddSourceFile(file))); } + [TestMethod] + public void ASourceFileWritesItsGlobalAttributesAfterItsUsingsAndBeforeItsNamespace() + { + SourceFileTemplate file = new() + { + Namespace = "Contoso", + Usings = { "System" }, + Attributes = { "assembly: CLSCompliant(true)" }, + Classes = { new ClassTemplate { Name = "Widget", Keywords = { "public" } } }, + }; + + Assert.AreEqual( + "using System;\n\n[assembly: CLSCompliant(true)]\n\nnamespace Contoso;\n\npublic class Widget\n{\n}\n\n", + Render(codeBlocker => codeBlocker.AddSourceFile(file))); + } + + [TestMethod] + public void ASourceFileWithNoNamespaceWritesItsGlobalAttributesAfterItsUsings() + { + SourceFileTemplate file = new() + { + Usings = { "System" }, + Attributes = { "assembly: CLSCompliant(true)" }, + Classes = { new ClassTemplate { Name = "Widget", Keywords = { "public" } } }, + }; + + Assert.AreEqual( + "using System;\n\n[assembly: CLSCompliant(true)]\n\npublic class Widget\n{\n}\n\n", + Render(codeBlocker => codeBlocker.AddSourceFile(file))); + } + #endregion #region Null handling diff --git a/CodeBlocker/Templates/SourceFileTemplate.cs b/CodeBlocker/Templates/SourceFileTemplate.cs index cb15d10..9b644f9 100644 --- a/CodeBlocker/Templates/SourceFileTemplate.cs +++ b/CodeBlocker/Templates/SourceFileTemplate.cs @@ -39,6 +39,13 @@ public static class SourceFileTemplateExtensions /// The to write to. /// The file to write. /// The same , for chaining. + /// + /// The only attributes a file can carry are global ones (assembly:, module:), and + /// C# only accepts them after every using directive and before the namespace. So when the file + /// has attributes, its usings come first, then the attributes, then the namespace; that is the one + /// order in which an attribute can be named through the file's own usings. A file with no + /// attributes keeps the usings inside its file-scoped namespace. + /// /// /// or is . /// @@ -47,9 +54,23 @@ public static CodeBlocker AddSourceFile(this CodeBlocker codeBlocker, SourceFile Ensure.NotNull(codeBlocker); Ensure.NotNull(template); - codeBlocker.AddTemplate(template); - codeBlocker.WriteFileScopedNamespace(template.Namespace); - codeBlocker.WriteUsings(template.Usings); + codeBlocker.AddDocumentation(template.Documentation); + codeBlocker.AddComments(template.Comments); + if (template.Attributes.Count > 0) + { + codeBlocker.WriteUsings(template.Usings); + codeBlocker.AddAttributes(template.Attributes); + codeBlocker.NewLine(); + codeBlocker.AddKeywords(template.Keywords); + codeBlocker.WriteFileScopedNamespace(template.Namespace); + } + else + { + codeBlocker.AddKeywords(template.Keywords); + codeBlocker.WriteFileScopedNamespace(template.Namespace); + codeBlocker.WriteUsings(template.Usings); + } + codeBlocker.AddClasses(template.Classes); return codeBlocker; }