From 9f94990ec79fd71498cd1a1ebd46c42230d2225a Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Wed, 9 Oct 2024 11:16:10 -0300 Subject: [PATCH] Since types can be public, ensure we always have XML comments This is important in particular when we add intermediate automatic "area" classes (such as when a constant is Foo.Bar.Baz), since in those cases, the user has no way of specifying a comment to fix the issue. --- src/ThisAssembly.Constants/CSharp.sbntxt | 6 ++-- src/ThisAssembly.Constants/Model.cs | 25 ++++++++++++--- src/ThisAssembly.Resources/CSharp.sbntxt | 17 ++++++++-- src/ThisAssembly.Resources/Model.cs | 32 +++++++++++++++---- .../ResourcesGenerator.cs | 2 +- src/ThisAssembly.Strings/CSharp.sbntxt | 1 + src/ThisAssembly.Tests/Extensions.cs | 3 ++ src/ThisAssembly.Tests/ScribanTests.cs | 2 ++ src/ThisAssembly.Tests/Tests.cs | 31 ++++++++++++++++++ .../ThisAssembly.Tests.csproj | 6 ++-- 10 files changed, 105 insertions(+), 20 deletions(-) diff --git a/src/ThisAssembly.Constants/CSharp.sbntxt b/src/ThisAssembly.Constants/CSharp.sbntxt index a92ce7c2..77711f19 100644 --- a/src/ThisAssembly.Constants/CSharp.sbntxt +++ b/src/ThisAssembly.Constants/CSharp.sbntxt @@ -34,6 +34,9 @@ {{~ end ~}} {{ end }} {{ func render }} + /// + /// {{ $0.Comment }} + /// public static partial class {{ $0.Name | string.replace "-" "_" | string.replace " " "_" }} { {{~ for value in $0.Values ~}} @@ -73,8 +76,5 @@ namespace {{ Namespace }}; /// {{ Visibility }}partial class ThisAssembly { - /// - /// {{ RootArea.Comment }} - /// {{ render RootArea }} } \ No newline at end of file diff --git a/src/ThisAssembly.Constants/Model.cs b/src/ThisAssembly.Constants/Model.cs index 8ca8e2b7..10f9ee13 100644 --- a/src/ThisAssembly.Constants/Model.cs +++ b/src/ThisAssembly.Constants/Model.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using System.Xml.Linq; using Microsoft.CodeAnalysis.CSharp; namespace ThisAssembly; @@ -22,10 +23,21 @@ record Model(Area RootArea, string? Namespace, bool IsPublic) } [DebuggerDisplay("Name = {Name}, NestedAreas = {NestedAreas.Count}, Values = {Values.Count}")] -record Area(string Name, string Prefix, string Comment) +record Area(string Name, string Prefix) { - public List NestedAreas { get; init; } = new(); - public List Values { get; init; } = new(); + string? comment = null; + Area? parent = null; + + public string Comment + { + get => comment ?? $"Provides access to constants under {Path}"; + set => comment = value; + } + + public string Path => parent == null ? Name : $"{parent.Path}/{Name}"; + + public List NestedAreas { get; init; } = []; + public List Values { get; init; } = []; static string EscapeIdentifier(string identifier) { @@ -50,7 +62,7 @@ static string EscapeIdentifier(string identifier) public static Area Load(List constants, string rootArea = "Constants", string comment = "Provides access project-defined constants.") { - var root = new Area(rootArea, "", comment); + var root = new Area(rootArea, "") { Comment = comment }; foreach (var constant in constants) { @@ -96,7 +108,10 @@ static Area GetArea(Area area, IEnumerable areaPath) "Area name '{0}' is already in use as a value name under area '{1}'.", areaName, currentArea.Name)); - existing = new Area(areaName, currentArea.Prefix + areaName + ".", ""); + existing = new Area(areaName, currentArea.Prefix + areaName + ".") + { + parent = currentArea + }; currentArea.NestedAreas.Add(existing); } diff --git a/src/ThisAssembly.Resources/CSharp.sbntxt b/src/ThisAssembly.Resources/CSharp.sbntxt index 9b0520c0..dae7ce5a 100644 --- a/src/ThisAssembly.Resources/CSharp.sbntxt +++ b/src/ThisAssembly.Resources/CSharp.sbntxt @@ -31,17 +31,31 @@ { {{~ if $0.IsText ~}} private static string text; + + /// + /// Gets the resource as plain text. + /// public static string Text => text ??= EmbeddedResource.GetContent(@"{{ $0.Path }}"); {{~ end ~}} + /// + /// Gets the resource as a byte array. + /// public static byte[] GetBytes() => EmbeddedResource.GetBytes(@"{{ $0.Path }}"); + + /// + /// Gets the resource as a stream. + /// public static Stream GetStream() => EmbeddedResource.GetStream(@"{{ $0.Path }}"); } {{ end }} {{ func render }} + /// + /// {{ $0.Comment }} + /// public static partial class {{ $0.Name | string.replace "-" "_" | string.replace " " "_" }} { {{~ if $0.Resources ~}} @@ -65,8 +79,5 @@ namespace {{ Namespace }}; /// {{ Visibility }}partial class ThisAssembly { - /// - /// Provides access to assembly embedded resources. - /// {{ render RootArea }} } diff --git a/src/ThisAssembly.Resources/Model.cs b/src/ThisAssembly.Resources/Model.cs index 4652e2af..9d7f2d55 100644 --- a/src/ThisAssembly.Resources/Model.cs +++ b/src/ThisAssembly.Resources/Model.cs @@ -19,12 +19,34 @@ record Model(Area RootArea, string? Namespace, bool IsPublic) [DebuggerDisplay("Name = {Name}")] record Area(string Name) { - public Area? NestedArea { get; private set; } + Area? nestedArea = null; + Area? parent = null; + string? comment = null; + + public string? Comment + { + get => comment ?? $"Provides access to embedded resources under {Path}"; + set => comment = value; + } + + public Area? NestedArea + { + get => nestedArea; + set + { + nestedArea = value; + if (nestedArea != null) + nestedArea.parent = this; + } + } + + public string Path => parent == null ? Name : $"{parent.Path}/{Name}"; + public IEnumerable? Resources { get; private set; } - public static Area Load(string basePath, List resources, string rootArea = "Resources") + public static Area Load(string basePath, List resources, string rootArea = "Resources", string comment = "Provides access to embedded resources.") { - var root = new Area(rootArea); + var root = new Area(rootArea) { Comment = comment }; // Splits: ([area].)*[name] var area = root; @@ -36,9 +58,7 @@ public static Area Load(string basePath, List resources, string rootAr { var partStr = PathSanitizer.Sanitize(part, parent); parent = partStr; - - area.NestedArea = new Area(partStr); - area = area.NestedArea; + area = area.NestedArea = new Area(partStr); } area.Resources = resources diff --git a/src/ThisAssembly.Resources/ResourcesGenerator.cs b/src/ThisAssembly.Resources/ResourcesGenerator.cs index 80326fd5..fa170a92 100644 --- a/src/ThisAssembly.Resources/ResourcesGenerator.cs +++ b/src/ThisAssembly.Resources/ResourcesGenerator.cs @@ -42,7 +42,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { if (!p.GlobalOptions.TryGetValue("build_property.EmbeddedResourceStringExtensions", out var extensions) || extensions == null) - return Array.Empty(); + return []; return extensions.Split('|'); }) diff --git a/src/ThisAssembly.Strings/CSharp.sbntxt b/src/ThisAssembly.Strings/CSharp.sbntxt index 105ab253..966983bb 100644 --- a/src/ThisAssembly.Strings/CSharp.sbntxt +++ b/src/ThisAssembly.Strings/CSharp.sbntxt @@ -29,6 +29,7 @@ {{~ end ~}} {{ end }} {{ func render }} + /// public static partial class {{ $0.Id }} { {{~ for value in $0.Values }} diff --git a/src/ThisAssembly.Tests/Extensions.cs b/src/ThisAssembly.Tests/Extensions.cs index 6385bccc..fed45d71 100644 --- a/src/ThisAssembly.Tests/Extensions.cs +++ b/src/ThisAssembly.Tests/Extensions.cs @@ -1,9 +1,12 @@ using System; +/// public static class Extensions { + /// public static string ReplaceLineEndings(this string input) => ReplaceLineEndings(input, Environment.NewLine); + /// public static string ReplaceLineEndings(this string input, string replacementText) { #if NET6_0_OR_GREATER diff --git a/src/ThisAssembly.Tests/ScribanTests.cs b/src/ThisAssembly.Tests/ScribanTests.cs index c0831e46..1a764321 100644 --- a/src/ThisAssembly.Tests/ScribanTests.cs +++ b/src/ThisAssembly.Tests/ScribanTests.cs @@ -9,8 +9,10 @@ namespace ThisAssemblyTests; +/// public class ScribanTests(ITestOutputHelper Console) { + /// [Fact] public void CanRenderModel() { diff --git a/src/ThisAssembly.Tests/Tests.cs b/src/ThisAssembly.Tests/Tests.cs index 21bd1698..a9daf305 100644 --- a/src/ThisAssembly.Tests/Tests.cs +++ b/src/ThisAssembly.Tests/Tests.cs @@ -9,16 +9,20 @@ namespace ThisAssemblyTests; +/// public record class Tests(ITestOutputHelper Output) { + /// [Fact] public void CanReadResourceFile() => Assert.NotNull(ResourceFile.Load("Resources.resx", "Strings")); + /// [Fact] public void CanUseInfo() => Assert.Equal("ThisAssembly.Tests", ThisAssembly.Info.Title); + /// [Fact] public void CanUseInfoDescription() => Assert.Equal( @@ -29,6 +33,7 @@ with a newline and // Some comments too. """.ReplaceLineEndings(), ThisAssembly.Info.Description.ReplaceLineEndings()); + /// [Fact] public void CanUseMultilineProjectProperty() => Assert.Equal( @@ -39,6 +44,7 @@ with a newline and // Some comments too. """.ReplaceLineEndings(), ThisAssembly.Project.Multiline.ReplaceLineEndings()); + /// [Fact] public void CanUseProjectFullFileContents() { @@ -46,98 +52,122 @@ public void CanUseProjectFullFileContents() Assert.False(ThisAssembly.Project.ProjectFile.StartsWith("|")); } + /// [Fact] public void CanUseConstants() => Assert.Equal("Baz", ThisAssembly.Constants.Foo.Bar); + /// [Fact] public void CanUseTypedIntConstant() => Assert.Equal(123, ThisAssembly.Constants.TypedInt); + /// [Fact] public void CanUseTypedInt64Constant() => Assert.Equal(123, ThisAssembly.Constants.TypedInt64); + /// [Fact] public void CanUseTypedLongConstant() => Assert.Equal(123, ThisAssembly.Constants.TypedLong); + /// [Fact] public void CanUseTypedBoolConstant() => Assert.True(ThisAssembly.Constants.TypedBoolean); + /// [Fact] public void CanUseTypedDoubleConstant() => Assert.Equal(1.23, ThisAssembly.Constants.TypedDouble); + /// [Fact] public void CanUseTypedTimeSpanStaticProp() => Assert.Equal(TimeSpan.FromSeconds(5), ThisAssembly.Constants.TypedTimeSpan); + /// [Fact] public void CanUseFileConstants() => Assert.Equal(ThisAssembly.Constants.Content.Docs.License, Path.Combine("Content", "Docs", "License.md")); + /// [Fact] public void CanUseFileConstantInvalidIdentifier() => Assert.Equal(ThisAssembly.Constants.Content.Docs._12._Readme_copy_, Path.Combine("Content", "Docs", "12. Readme (copy).txt")); + /// [Fact] public void CanUseFileConstantLinkedFile() => Assert.Equal(ThisAssembly.Constants.Included.Readme, Path.Combine("Included", "Readme.txt")); + /// [Fact] public void CanUseMetadata() => Assert.Equal("Bar", ThisAssembly.Metadata.Foo); + /// [Fact] public void CanUseHierarchicalMetadata() => Assert.Equal("Baz", ThisAssembly.Metadata.Root.Foo.Bar); + /// [Fact] public void CanUseProjectProperty() => Assert.Equal("Bar", ThisAssembly.Project.Foo); + /// [Fact] public void CanUseStringsNamedArguments() => Assert.NotNull(ThisAssembly.Strings.Named("hello", "world")); + /// [Fact] public void CanUseStringsIndexedArguments() => Assert.NotNull(ThisAssembly.Strings.Indexed("hello", "world")); + /// [Fact] public void CanUseStringsNamedFormattedArguments() => Assert.Equal("Year 2020, Month 03", ThisAssembly.Strings.WithNamedFormat(new DateTime(2020, 3, 20))); + /// [Fact] public void CanUseStringsIndexedFormattedArguments() => Assert.Equal("Year 2020, Month 03", ThisAssembly.Strings.WithIndexedFormat(new DateTime(2020, 3, 20))); + /// [Fact] public void CanUseStringResource() => Assert.Equal("Value", ThisAssembly.Strings.Foo.Bar.Baz); + /// [Fact] public void CanUseTextResource() => Assert.NotNull(ThisAssembly.Resources.Content.Styles.Custom.Text); + /// [Fact] public void CanUseByteResource() => Assert.NotNull(ThisAssembly.Resources.Content.Styles.Main.GetBytes()); + /// [Fact] public void CanUseSameNameDifferentExtensions() => Assert.NotNull(ThisAssembly.Resources.Content.Swagger.swagger_ui.css.GetBytes()); + /// [Fact] public void CanUseFileInvalidCharacters() => Assert.NotNull(ThisAssembly.Resources.webhook_data.Text); + /// [Fact] public void CanUseGitConstants() => Assert.NotEmpty(ThisAssembly.Git.Commit); + /// [Fact] public void CanUseGitBranchConstants() { @@ -145,6 +175,7 @@ public void CanUseGitBranchConstants() Output.WriteLine(ThisAssembly.Git.Branch); } + /// [Fact] public void CanUseSemicolonsInConstant() => Assert.Equal("A;B;C", ThisAssembly.Constants.WithSemiColon); diff --git a/src/ThisAssembly.Tests/ThisAssembly.Tests.csproj b/src/ThisAssembly.Tests/ThisAssembly.Tests.csproj index e0eb23f8..c430c5fe 100644 --- a/src/ThisAssembly.Tests/ThisAssembly.Tests.csproj +++ b/src/ThisAssembly.Tests/ThisAssembly.Tests.csproj @@ -19,10 +19,12 @@ // Some comments too. net472 ThisAssemblyTests - + true CS0618;CS8981;TA100;$(NoWarn) false $([System.IO.File]::ReadAllText($(MSBuildProjectFullPath))) + true + true @@ -69,7 +71,7 @@ - +