Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CodeBlocker.Test/TemplateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 24 additions & 3 deletions CodeBlocker/Templates/SourceFileTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ public static class SourceFileTemplateExtensions
/// <param name="codeBlocker">The <see cref="CodeBlocker"/> to write to.</param>
/// <param name="template">The file to write.</param>
/// <returns>The same <see cref="CodeBlocker"/>, for chaining.</returns>
/// <remarks>
/// The only attributes a file can carry are global ones (<c>assembly:</c>, <c>module:</c>), 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.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// <paramref name="codeBlocker"/> or <paramref name="template"/> is <see langword="null"/>.
/// </exception>
Expand All @@ -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;
}
Expand Down
Loading